I am working on a 2D platformer game for my, last, HS year project.
The game is basically about a player walking back & forward, collecting points and reaching goals... The player can shoot bullets and when bullets hit a block, it is destroyed. Now, I wanted to add an explosion effect using so called "particle" objects. I have written the manager class for it and it seemed to have worked the first time but after shooting a few times, i noticed that the particles stopped getting deleted, they just continue and travel out of screen. The life-time limit is 500ns.
I have also noticed that if i shoot bullets as soon as the game starts, the effect finishes as it is supposed to. but after waiting for a few more seconds and then shooting bullets, the effect particles do not behave as they should.
Here is what it looks like when i shoot bullets as soon as i start the game (What it's supposed to look like):
and here is what it looks like, after waiting a few seconds before shooting the bullets.
ParticleManager.java
public class ParticleManager {
private ArrayList<Particle> particles;
private ArrayList<Particle> removeParticles;
public ParticleManager() {
particles = new ArrayList<Particle>();
removeParticles = new ArrayList<Particle>();
}
public int getListSize() {
return particles.size();
}
/*
Generate particles
*/
public void genParticle(int x, int y, int amount) {
for(int i = 0; i < amount; i++) {
particles.add(new Particle("explosion" , x,y, i));
}
}
public void update() {
// Iterate trough particle objects
// update them & check for lifeTime
for(Particle p: particles) {
// Updating particle object before
// checking for time lapse
p.update();
// Append outdated particles to removeParticles
// if time limit has passed
if(System.nanoTime() - p.timePassed >= Config.particleLife) {
removeParticles.add(p);
}
}
// finally, delete all "remove-marked" objects
particles.removeAll(removeParticles);
}
public void render(Graphics2D g) {
for(Particle p: particles) {
p.render(g);
}
}
}
Particle.java
class Particle {
private double px, py, x, y;
private int radius, angle;
public long timePassed;
String type;
public Particle(String type, double x, double y, int angle) {
this.x = x;
this.y = y;
this.radius = 0;
this.angle = angle;
this.timePassed = 0;
this.type = type; // explosion, tail
}
public void update() {
px = x + radius * Math.cos(angle);
py = y + radius * Math.sin(angle);
radius += 2;
this.timePassed = System.nanoTime();
}
public void render(Graphics2D g) {
g.setColor(Color.WHITE);
g.fillOval((int)px, (int)py, 5, 5);
}
}
I haven't figured out what I am doing wrong here, I've googled about some stuff and at one point i came across an answer mentioning that some references don't get deleted directly for some reason...
and my question is "How can I make these particles vanish after a certain amount of time has passed? - as shown in the first GIF"
I think the problem is that you are constantly overwriting timePassed.
// Updating particle object before
// checking for time lapse
p.update();
// Append outdated particles to removeParticles
// if time limit has passed
if(System.nanoTime() - p.timePassed >= Config.particleLife) {
removeParticles.add(p);
}
p.update() sets timePassed to now and then the if check checks if time passed is far from now (it will never be since it was just set).
I think you do want to set timePassed in the constructor (maybe it would be better named timeCreated).
Additionally, just a heads up, you never clear removeParticles so that list is going to grow forever until it causes the process to run out of memory.
Related
public class Physics {
private int weight;
private int durability;
private int xPos;
private int yPos;
private int x;
private int y;
private Level level;
int tileWidth = Main.WIDTH / 16;
int tileHeight = Main.HEIGHT / 16;
public Physics(Level level) {
this.level = level;
}
private void timer(int time) {//this is a subroutine that creates a slight pause
long currTime = System.currentTimeMillis();//this is getting the current time in miliseconds
long target = currTime + time;//this gets the current time and sets the target pause time to the current time plus the time you want to pause for in miliseconds
while(System.currentTimeMillis() < target);//this makes the program idle for the target amount of time
}
public void gravity(int weight,int durability, int xPos, int yPos, Tile tile, TileType tileTypes, boolean newblock){//this is the physics subroutine for when you place a block
if(tile.getType() == TileType.SKY) {//if the tile you clicked on is a sky tile
tile.setType(tileTypes, weight, durability);//it sets the new tiles type weight and durability to the ones that were entered dependant on which block the user chose from the pallet
timer(50);//this is the pause time between the block falling one y coordinate down
tile.setType(TileType.SKY, 0, 0);//once the tile is drawn one tile down it changeds the tile above it back to a sky tile
yPos++;//this the increases the y possition by one to foxus on the next block down
gravity(weight,durability, xPos, yPos, level.tiles[xPos][yPos],tileTypes,true);//this then calls the subroutine within itself and this keeps going until it hits the bottom
}
//if (newblock == true) {
if(tile.getType() != TileType.SKY){//once the tile below the tile we're focusing on is not a sky tile this if statement is run
Tile tile1 = level.tiles[xPos][yPos-1];//it sets the tile were focusing on to the tile above the non skytile
tile1.setType(tileTypes, weight, durability);//it then sets this tile to the tile you chose from the pallet
weight = tile1.getWeight();
durability = tile1.getDurability();
System.out.println(weight+"kg, "+durability);
}
//}
//if (newblock == false) {
//}
}
public void blockDestruction(Tile tile) {
}
public void tick(int x, int y) {
Tile tile = level.tiles[x][y];
Tile tile1 = level.tiles[x][y-1];
TileType tilecheck = tile1.getType();
if(tile1.getType() != TileType.SKY && tile1.getType() != TileType.GRASS) {
gravity(tile.getWeight(),tile.getDurability(),x,y,tile,tile.getType(),false);
y = y-1;
/*for (int i = y-1; i>0;i--) {
if (tilecheck != TileType.SKY) {
tile1 = level.tiles[x][i];
tilecheck = tile1.getType();
}else
tile1 = level.tiles[x][i+1];
tile1.setType(TileType.SKY);
}*/
}
}
}
the tick method at the end there is what I'm struggling with what im trying to create is a method that i would be able to put inside of my tick method in my main class which will check all blocks to see if there is a block underneath them and if not then it will apply the gravity method to that block. Sorry for the code being such a mess ive been trying loads of solutions and have gotten completely stuck so it is currently a bit of a mess if anything need explaining then please let me know.
this is what the program looks like
I'm making a game where you're controlling a square, and objects will spawn in random places, which you have to pick up to get points, while also having to dodge big squares going from side to side. Right now I have 2 classes (one for enemies - big squares -, and one for the hero), I haven't done the point system yet with the spawning objects, but that's not what I'm trying to do now.
So my problem right now is that I don't really know how to make the person lose the game/a life when you touch the "enemies" with your hero. I would know how to do it without classes, but I'd like to know how to do it when they are in separate classes.
If someone could explain with code and comments how this would be done, it would help me out a lot :) (I read something about 'extends' for classes but I'm not sure if this is what I should use or not).
Here's a screenshot of what my game looks like at the moment, just to better illustrate it:
Here's the main code page:
Hero myHero = new Hero(400,480,5);
Enemies myEnemies = new Enemies(50,50,10);
Enemies myEnemies2 = new Enemies(50,350,15);
Enemies myEnemies3 = new Enemies(50,650,12);
void setup() {
size(900,800);
frameRate(30);
smooth();
}
void draw() {
background(0);
myHero.keyPressed();
myEnemies.enemyDisplay();
myEnemies.enemyMove();
myEnemies2.enemyDisplay();
myEnemies2.enemyMove();
myEnemies3.enemyDisplay();
myEnemies3.enemyMove();
}
Class 1:
class Enemies {
float xpos, ypos, speed;
Enemies(float x, float y, float s) {
xpos = x;
ypos = y;
speed = s;
}
void enemyDisplay() {
rect(xpos, ypos, 100, 100);
}
void enemyMove() {
xpos += speed;
if((xpos > width - 100) || (xpos < 0)) {
speed *= -1;
}
}
}
Class 2:
class Hero {
float xpos_, ypos_, speed_;
Hero(float x, float y, float s) {
xpos_ = x;
ypos_ = y;
speed_ = s;
}
void keyPressed() {
if (key == CODED) {
if (keyCode == UP) {
ypos_ -= speed_;
}
if (keyCode == DOWN) {
ypos_ += speed_;
}
if (keyCode == LEFT) {
xpos_ -= speed_;
}
if (keyCode == RIGHT) {
xpos_ += speed_;
}
}
rect(xpos_,ypos_,30,30);
}
}
I believe the question you are asking involves basic collision detection and object interactions.
I would first make the Enemies a List and create it / add elements during the setup() call:
List<Enemies> enemies = new List<Enemies>();
enemies.add(new Enemies(50,50,10));
This allows you to store all of your Enemies under one object. So your draw() method would look something like:
void draw(){
background(0);
myHero.keyPressed();
for(Enemies enemy : enemies)
{
enemy.enemyDisplay();
enemy.enemyMove();
if (hero.isCollidingWith(enemy)) // collision method defined in the hero object, but you could define it in the Enemies class as well, it doesn't really matter
{
hero.removeHealth(); // method defined in hero that removes health
}
}
}
This method would be in one of your classes:
public boolean isColliding(Enemies enemy)
{
// check the x and y coordinates of each object
}
I hope this helps to point you in the right direction.
you need to figure out collision detection and when your objects collide with each other, something basic like:
class Enemy
{
//...
public boolean isColliding(Hero hero)
{
//figure out the distance between two objects, if its less than their size, they are colliding..
//...
}
//...
}
then you need a part of your Game Loop that checks if anything is colliding with your hero, pickups, walls, etc...
It seems the first part you need help with is collision detection. The short answer I would give that will almost undoubtedly lead you to more questions is to look at the Area class (specifically Area.intersect). You might also want to look at the classes I've put together for displaying & managing areas in this project here
There are several problems here that touch on both application design as well as conventions. These should be addressed first before trying to tackle the collision detection problem.
The Enemies class only represents a single enemy, so the name of the class should reflect that. Additionally, prefixing the method names with "enemy" is redundant and can be removed. Other changes have been commented in the revised class below.
public class Enemy {
// Instead of hard-coding in the width and height of an enemy, allow the
// instantiating code to specify the enemy's size. This will allow you
// to have different size enemies and prevents you from having "magic numbers"
// in your code.
private float xpos, ypos, width, height, speed;
public Enemy(float x, float y, float s, float w, float h) {
xpos = x;
ypos = y;
width = w;
height = h;
speed = s;
}
/* These getters will be used for collision detection later */
public float getX() {
return xpos;
}
public float getY() {
return ypos;
}
public float getWidth() {
return width;
}
public float getHeight() {
return height;
}
// I've changed `display` to `draw` to be consistent with the method name in
// your main `draw` method.
public void draw() {
rect(xpos, ypos, width, height);
}
// This method now accepts a screenWidth parameter so that the enemy can know
// when they've collided with the left or right wall of the screen without
// having to rely on an global variable.
public void move(int screenWidth) {
xpos += speed;
if ((xpos > screenWidth - width) || (xpos < 0)) {
speed *= -1;
}
}
}
I mention the "magic numbers" in one of the comments above. See this wikipedia article for more on that.
The Hero class contains property names that have an underscore postfix. This ranges from unconventional to inconsistent with respect to all of your other property names in your other classes. The original keyPressed() method mixes the logic for both drawing and moving. These two things have been separated and the methods named like those of the Enemy class for consistency.
public class Hero {
private float xpos, ypos, width, height, speed;
public Hero(float x, float y, float s, float w, float h) {
xpos = x;
ypos = y;
width = w;
height = h;
speed = s;
}
// Change this method name to draw for consistency with the Enemy class
public void draw() {
// Key press functionality has been moved to the `move` method for consistency
// with the Enemy class.
rect(xpos, ypos, WIDTH, HEIGHT);
}
// This method uses the variables key, keyCoded, UP, DOWN, LEFT, and RIGHT. You
// did not include any import statements with your code, so they may be coming
// from there; however, if they are globals, you should pass them to this method
// as arguments whenever you call it.
public void move() {
// If this condition isn't satisfied, return immediately. This prevents
// unnecessary nesting and work below.
if (key != CODED) {
return;
}
if (keyCode == UP) {
ypos -= speed;
}
// Use `else if` here and below to prevent multiple unnecessary
// comparisons of keyCode.
else if (keyCode == DOWN) {
ypos += speed;
}
else if (keyCode == LEFT) {
xpos -= speed;
}
else if (keyCode == RIGHT) {
xpos += speed;
}
}
public boolean isColliding(Enemy enemy) {
// Collision detection is easy since all of your game entities (the hero and
// the enemies) are all rectangles and axis-aligned (not rotated). You can
// use a method known as "bounding box intersection."
return (Math.abs(enemy.getX() - xpos) * 2 < (enemy.getWidth() + width))
&& (Math.abs(enemy.getY() - ypos) * 2 < (enemy.getHeight() + height));
}
}
For more on bounding box intersection, see this gamedev stackexchange question.
Now that your classes are in order, its time to address your main code. We'll need to update the method names and, as #James T suggested, you should make a list of enemies instead of creating a new independent object for each enemy. This will make it easier for you to add or remove enemies in the future and to be able to process all enemies with one block of code without repeating yourself.
// Use constants to remove magic numbers.
private static final int SCREEN_WIDTH = 900;
private static final int SCREEN_HEIGHT = 800;
private Hero myHero = new Hero(400, 480, 30, 30, 5);
private List<Enemy> enemies = new ArrayList<Enemy>();
void setup() {
size(SCREEN_WIDTH, SCREEN_HEIGHT);
frameRate(30);
smooth();
enemies.add(new Enemy(50, 50, 100, 100, 10));
enemies.add(new Enemy(50, 350, 100, 100, 15));
enemies.add(new Enemy(50, 650, 100, 100, 12));
}
void draw() {
hasCollision = false;
background(0);
// I've changed the order of draw->move to move->draw. If you draw first, then
// move, then detect collisions, it will appear to your user that your hero has
// not yet collided with an enemy even though you act as they have (e.g.: they
// will not see the collision until the next time you draw the scene).
myHero.move();
myHero.draw();
for (Enemy enemy : enemies) {
enemy.move();
enemy.draw(SCREEN_WIDTH);
if (!hasCollision && myHero.isColliding(enemy)) {
hasCollision = true;
}
}
if (hasCollision) {
// Handle enemy collision here
}
}
You'll notice that I've also added accessibility modifiers to everything. While it is technically valid to exclude them and use the defaults, it makes your code more readable to include them because it is more obvious. When you're first starting out, the more obvious the better.
Alright got it to work thanks to all you helpful wonderful people!
I did this:
public boolean isColliding(Enemies h){
float distance = dist(x,y,h.x,h.y);
if(distance<100){
return true;
}else{
return false;
}
}
and in my draw(){ I have
if(myHero.isColliding(myEnemies)){
println("You lost!");
}
I had a very similar 'fix' earlier, but the reason I got an error was because I had Hero h instead of Enemies h in the 'if' function, so it was just a very dumb error that I overlooked :P
I'm having an issue showing that one rectangle has collided with another. So my question is, how can I get the intersect method to check for collision? Or are there any other ways to handle collision in this situation?
I'm creating a turn-based combat game (similar to Final Fantasy or Legend of Dragoon) where the player's character is on the right side of the screen and the enemy is on the left side. The player and enemy each take turns attacking. So when the player attacks, the sprite animation moves across the screen from right to left until it stops in front of the enemy, attacks, and returns to it's starting coordinates. Both the player and enemy have a rectangle drawn around them to represent the bounds of each character.
When the player moves forward, he passes through the Enemy's rectangle and stops within it. At this point there should be output to the console saying "INTERSECT!" to show that there was a collision between the two rectangles, but unfortunately there isn't.
Please note that I have omitted the unnecessary pieces of code within my classes and tried to provide the code that pertains to my problem.
This is my entry point, GameClass:
public class GameClass extends BasicGame{
//other variable declarations
public void init(GameContainer container) throws SlickException {
player = new Player();
enemy = new Enemy();
skeleton = new Skeleton();
enemy = skeleton;
playX = player.getStartX(); //700
playY = player.getStartY(); //140
playW = player.rect.getWidth(); //40
playH = player.rect.getHeight(); //70
enemyX = enemy.getStartX(); //100
enemyY = enemy.getStartY(); //140
enemyWidth = enemy.getWidth(); //50
enemyHeight = enemy.getHeight(); //55
SpriteSheet sheet = new SpriteSheet("data/homeranim.png", 36, 65);
anim = new Animation();
for (int i=0;i<8;i++) {
anim.addFrame(sheet.getSprite(i,0), 150);
}
}
public void render(GameContainer container, Graphics g)
throws SlickException {
anim.draw(playX,playY); // draws player animation
skeletonAnim.draw(enemyX, enemyY); // draws enemy
g.draw(player.rect); //draws player bounds
g.draw(enemy.rect); //draws enemy bounds
}
public void update(GameContainer container, int delta)
throws SlickException {
playerUpdate(delta);
if (player.rect.intersects(enemy.rect)) {
System.out.println("INTERSECT!");
System.out.println("Player minX: " + player.rect.getMinX());
System.out.println("Player maxX: " + player.rect.getMaxX());
System.out.println("Enemy minX: " + enemy.rect.getMinX());
System.out.println("Enemy maxX: " + enemy.rect.getMaxX());
}
}
public void playerUpdate(int delta) {
if (playerForward == true){
playX -= delta * 0.4f;
if (playX <= 140) {
playX = 140;
playerForward = false;
playerBackward = true;}
}
if (playerBackward == true) {
playX += delta * 0.4f;
if (playX >= 700) {
playX = 700;
playerBackward = false;
delay = 1250;
}
public void keyReleased(int key, char c) {
if (key == Input.KEY_ENTER){
playerForward = true;}
}
}
This is a glimpse at my Player class:
public class Player {
private int startX = 700;
private int startY = 140;
public Shape rect = new Rectangle(startX, startY, 40, 70);
//plus getters and setters
}
And my entire Enemy class:
public class Enemy {
private int startX, startY, width, height;
public Shape rect = new Rectangle(startX, startY, width, height);
// plus getters and setters
}
My Skeleton class extends Enemy:
public class Skeleton extends Enemy {
public Skeleton() {
// TODO Auto-generated constructor stub
setMaxHealth(120);
setStartX(100);
setStartY(140);
setWidth(50);
setHeight(55);
}
}
Note: Since I've switched g.drawRect() to g.draw(), enemy rectangle isn't being drawn.
Rect bounds at starting point: http://i.imgur.com/QDDk858.png
Rect bound where collision should be: http://i.imgur.com/pOANfvN.png
I hope I've provided enough code to show you what my problem is. I've rummaged through the internet for hours with no luck. If there is any other code I need to provide, please do not hesitate to ask. Thank you very much for your help and support!
You are not updating the hitbox positions themselves.
You are drawing this:
g.drawRect(playX, playY, playW, playH); //draws player bounds
g.drawRect(enemyX, enemyY, enemyW, enemyH); //draws enemy bounds
But this isn't the actual hitbox, it's just the position of the player/enemy and the rectangles drawn here will be on the correct position while the hitboxes themselves aren't.
I suggest you do the following:
public void update(GameContainer container, int delta)
{
playerUpdate(delta);
player.rect.setLocation(playX, playY);
enemy.rect.setLocation(enemyX, enemyY); // update the hitboxes to the new positions
if (player.rect.intersects(enemy.rect))
{
System.out.println("INTERSECT!");
}
}
public void playerUpdate(int delta)
{
if (playerForward == true)
{
playX -= delta * 0.4f;
if (playX <= 140)
{
playX = 140;
playerForward = false;
playerBackward = true;
}
}
if (playerBackward == true)
{
playX += delta * 0.4f;
if (playX >= 700)
{
playX = 700;
playerBackward = false;
delay = 1250;
}
}
}
public void keyReleased(int key, char c)
{
if (key == Input.KEY_ENTER)
{
playerForward = true;
}
}
Furthermore, as you seem to be new to game development in Java, some tips for you:
Format your code properly
Always place full {...} after if, else, switch, while, for, etc.; proper line indentation, .
Think OO (Object-Oriented)
This one is pretty important. Your enemy and player class should both extend some kind of entity class because they both will pretty much want to obtain similar behavior (avoid code duplication!). Sum up similar behavior to a super class, simplify the behavior to be controlled with a few adjustable parameters and so on.
For example, you store the positions of your enemy and player as a static integer in your main class. This is not OO. Move the positions to the entity class where you can implement it in whatever manner you wish.
Don't just throw an exception for no reason
Your update(...) method throws a SlickException even though it's never needed.
Be careful about encapsulation
This is something a lot of beginners do: Just grab some parameters, put them in a class as private (or maybe even public) and generate getters- and setters for them. This is not encapsulation. This is almost as bad as making them public in the first place.
But why don't we just make everything public?
We don't want anyone (or even ourselves) to rely on some parameters that just happen to be there because of some very specific implementation of something we might want to change later. Don't just put all possible values out there to be changed, the sense of encapsulation is to be independent from what kind of implementation we end up using and to protect the usability of our code by guarding what can be set/changed.
Performance does matter
This is one of the aspects you should watch out for, for any kind of software, but you can often most drastically see the consequences in games. Performance is important! And by that, I don't mean that you have to watch out for every single detail, but just keep an overview in mind on how to improve and fasten up your code, especially with frequently called methods such as update(..) and render(..) in Slick2D.
Update
As a solution to another problem discussed in the comments:
public class Enemy {
private int startX, startY, width, height;
public Shape rect = new Rectangle(startX, startY, width, height);
// plus getters and setters
}
width and height can only be 0 as they are never assigned an integers have the value 0 per default, so the enemy rectangle hitbox does have 0 width and will never trigger.
Try something like:
public class Enemy {
private int startX, startY, width = 50, height = 70;
public Shape rect = new Rectangle(startX, startY, width, height);
// plus getters and setters
}
This should work, but you should probably move all these attributes to the enemy class and put them in the constructor.
I am currently working on a space invaders style game but I have run into a bit of trouble with multiple instances of bullets. At the moment I can only fire one. I have been trying to get it to work with an Array List but I just can't seem to get it to work. The closest I got I got it to working was, it fired multiple bullets but they all spawned from the same location as in the bullets didn't spawn in relation to the ships position. The game also crashed when I removed an object after it exceeded it's boundary. Can anyone help me to see where I am going wrong. Here is some code that I have so far the parts commented out are my attempts at getting the array list to work
import java.util.ArrayList;
import org.newdawn.slick.Input;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.GameContainer;
public class Player extends Entity
{
private int speed = 5;
private ArrayList<Bullet> bulletList;
private boolean firing;
private Bullet bullet;
public Player()
{
bullet = new Bullet();
//bulletList = new ArrayList<Bullet>();
this.setImage("ship");
this.setPosition(350,450);
this.setDimenseions(100, 100);
this.createRectangle();
}
#Override
public void entityLogic(GameContainer gc, int deltaTime)
{
Input input = gc.getInput();
if(input.isKeyDown(Input.KEY_A))
{
this.x -= speed;
}
if(input.isKeyDown(Input.KEY_D))
{
this.x += speed;
}
if(input.isKeyDown(Input.KEY_W))
{
this.y -= speed;
}
if(input.isKeyDown(Input.KEY_S))
{
this.y += speed;
}
if(input.isKeyPressed(Input.KEY_SPACE))
{
firing = true;
bullet.x = this.getX()+40;
//BulletList.add(new Bullet());
}
if(firing)
{
/*Carries out the logic for the bullet*/
//for(Bullet b : bulletList)
//{
//b.entityLogic(gc, deltaTime);
//}
//Moves the bullet negativly along the y axis
bullet.entityLogic(gc, deltaTime);
}
}
#Override
public void entityRendering(Graphics g)
{
g.drawImage(this.getImage(), this.getX(), this.getY());
if(firing)
{
/*Draws each bullet object in the list*/
//for(Bullet b : bulletList)
//{
//b.entityRendering(g);
//}
bullet.entityRendering(g);
}
}
}
First of all forget about your Bullet bullet instance variable. You don't need it, the list is enough.
Another thing is that you could use a LinkedList instead that an ArrayList because you don't need random access and you have to add and remove items frequently, when you iterate over bullets to check for collision use a ListIterator<T> and remove them on the fly.
Finally it should be something like:
List<Bullet> bullets = new ArrayList<Bullet>();
public void entityLogic(GameContainer gc, int deltaTime) {
// since this method is called many times you should shoot a bullet just every X msec
if (spacebar pressed) {
// you spawn a new bullet according to player position
Bullet bullet = new Bullet(player.x,player.y);
// you add it to the list
bullets.add(bullet);
}
// destroy bullets which are outside the viewport
for (int i = 0; i < bullets.size(); ++i) {
Bullet bullet = bullets.get(i);
if (bullet.isOutsideBounds()) {
bullets.remove(i);
i--;
}
}
public void entityRendering(Graphics g) {
for (Bullet bullet : bullets)
bullets.entityRenering(g);
}
}
This is just to give you the basic idea.
I don't know slick2d and how it manages the rendering and logic threads, if they are two different threads then you should use a syncronized list, eg:
List<Bullet> bullets = Collections.synchronizedList(new ArrayList<Bullet>());
I try to create a very simple physics engine for my study (processing used for a interactive installation).
The target is to have a ground covered with balls that you can throw around with gestures (based on Kinect information).
Therefor I need to do some basic physic simulation like bouncing and thats what I started with. So there are just balls falling down and bouncing. I simulated the air resistance with a simple 0.995f multiplication on the speed if the ball moves up. Works nice and looks realistic. The main problem is, that the balls never stay calm on the ground. Instead they start to tremble on the ground. That means there is a movement of 1 or 2 pixels up and down.
How can I prevent that without implementing some "borders" on which I set the position directly to the bottom and the speed to zero?
My applet:
public class BubblePhysicApplet extends PApplet {
public static int width = 640;
public static int height = 480;
long lastTime = -1;
Bubble[] mBubbles = new Bubble[10];
Random mRandom = new Random();
public void setup() {
// size(width, height, OPENGL);
size(width, height, P2D);
for (int i = 0; i < mBubbles.length; i++) {
mBubbles[i] = new Bubble(mRandom.nextInt(width), mRandom.nextInt(height), 50);
}
lastTime = System.currentTimeMillis();
}
public void draw() {
background(0);
long tmp = System.currentTimeMillis();
long elapsed = tmp - lastTime;
for (Bubble bubble : mBubbles) {
bubble.animate(elapsed);
bubble.draw(this);
}
lastTime = System.currentTimeMillis();
}
}
The ball/bubble:
public class Bubble {
float mX;
float mY;
float mSize;
float mSpeedX = 0;
float mSpeedY = 0;
public Bubble(int x, int y, int size) {
mX = x;
mY = y;
mSize = size;
}
public void draw(PApplet applet) {
applet.stroke(255);
applet.noFill();
applet.ellipseMode(PApplet.CENTER);
applet.ellipse(mX, mY, mSize, mSize);
}
public void animate(long elapsed) {
updateSpeedY(elapsed);
if (mSpeedX != 0 || mSpeedY != 0) {
checkBorders();
}
}
private void checkBorders() {
if (mY > BubblePhysicApplet.height - mSize / 2) {
mY = 2 * BubblePhysicApplet.height - (mY + mSize);
mSpeedY = -mSpeedY;
}
if (mX > BubblePhysicApplet.width) {
mX = BubblePhysicApplet.width - (mX - BubblePhysicApplet.width);
mSpeedX = -mSpeedX;
}
}
private void updateSpeedX() {
}
private void updateSpeedY(long elapsed) {
mSpeedY += (elapsed / 1000f) * 9.81f;
if (mSpeedY < 0) {
mSpeedY *= 0.95f;
}
mY += mSpeedY;
}
}
It's not only air resistance that slows the ball down, but the fact that it's not perfectly elastic as this line suggests: mSpeedY = -mSpeedY;
The ball absorbs energy when it squishes against the floor before it bounces back, so it doesn't bounce as high. Try a real super ball. I seem to remember it only bounces 80% as high or so. You might try:
mSpeedY = - (0.8 * mSpeedY);
you have to fix your check borders method, read this answer I just gave a complete formulas needed for realistic physical simulation. and it's also more realistic if you move objects using hist method (p = v*dt + 1/2*adtdt)
The problem is that in updateSpeedY we have mSpeedY += (elapsed / 1000f) * 9.81f; even when there is a collision. That said collision is detected later in checkBorders where the speed is flipped mSpeedY = -mSpeedY;. The problem is that if the ball is hitting the floor with a speed near 0, it bounces with a speed of 0 + (elapsed / 1000f) * 9.81f;!!
You have to rethink your code.
in the same fashion you used a friction factor for the air, you can also include a friction factor for the contact with the ground, and which even higher values, so at each contact, it starts to lose eneger rapidly and finally stops