I am trying to make a 2d game in java with realistic physics. The game is supposed to be set in space from a top down view. The best way I can explain the view is to use this link: 3d graph found one gooogle images with search "3d graph xyz". The view is supposed to be set so that you are looking from the top of the +y towards the -y.
In the game I currently only have it showing one sun and one planet to test the gravity. However, when I run the game the planet only moves away from the sun in a towards the bottom right. I believe this to be due to me misusing the equation when moving it to java. For reference the equations I used were Newton's Law of Universal Gravitation (F=(G*M1*M2)/D^2) and Newton's Second Law of Motion (F=MA, but used as A=F/M).
In short, My question is what did I do wrong with my equations? I will post all my code below, but fair warning it is designed to be expanded upon so there is a lot of excess currently.
Thanks for any help in advance.
Core class:
package src.main;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import src.main.input.Input;
import src.main.input.InputHandler;
import src.main.map.Map;
public class Core extends JPanel
{
private static InputHandler iHandler = new InputHandler();
private static Input input = new Input(iHandler);
private long ticks;
private Map map;
public static Dimension SIZE;
public static Random rand = new Random();
public Core(Dimension d)
{
setPreferredSize(d);
SIZE = d;
ticks = 0;
map = new Map(SIZE.width, SIZE.height);
start();
}
public synchronized void update()
{
ticks++;
iHandler.update(ticks);
/*if (iHandler.getWheelRotation() != 0)
{
int i = iHandler.getWheelRotation();
map.changeMagnification(i > 0);
if (i > 0)
i--;
else
i++;
iHandler.setWheelRotation(i);
if (i < 0 && map.getMagnification() == 0)
iHandler.setWheelRotation(0);
if (i > 0 && map.getMagnification() == Map.MAX_MAGNIFY)
iHandler.setWheelRotation(0);
}*/
map.update(ticks);
}
public synchronized void paintComponent(Graphics g2)
{
Graphics2D g = (Graphics2D) g2;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(0, 0, SIZE.width, SIZE.height);
map.draw(g);
}
public void start()
{
Thread thread = new Thread()
{
public void run()
{
while (true)
{
long time = System.currentTimeMillis();
update();
repaint();
time = (1000 / 128) - (System.currentTimeMillis() - time);
if (time > 0)
{
try
{
Thread.sleep(time);
} catch (Exception ex)
{
ex.printStackTrace();
}
}
}
}
};
thread.start();
}
public static void main(String args[])
{
SwingUtilities.invokeLater(new Runnable()
{
Rectangle r = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()[0].getDefaultConfiguration().getBounds();
public void run()
{
JFrame frame = new JFrame();
frame.setUndecorated(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(new Core(new Dimension(r.width, r.height)));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.addKeyListener(input);
frame.addMouseListener(input);
frame.addMouseMotionListener(input);
frame.addMouseWheelListener(input);
}
});
}
}
Map Class:
package src.main.map;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.util.ArrayList;
import src.main.Core;
import src.main.celestials.SolarSystem;
public class Map
{
private int magnification;
public static final int MAX_MAGNIFY = 15;
private ArrayList<SolarSystem> systems = new ArrayList<SolarSystem>();
public Map(int x, int y)
{
SolarSystem s=new SolarSystem(new Point(500,500), "sun", (int) (1.9891 * Math.pow(10, 30)));
s.addPlanet(new Point(550, 550), "planet");
systems.add(s);
}
public void update(long ticks)
{
for(int i = 0; i < systems.size(); i++)
systems.get(i).update(ticks);
}
public void draw(Graphics2D g)
{
g.setColor(Color.DARK_GRAY);
magnification= 1;
for(int i=0; i<systems.size(); i++)
{
systems.get(i).draw(g, magnification);
}
}
public int getMagnification()
{
return magnification;
}
public void changeMagnification(boolean bigger)
{
if (bigger)
magnification++;
else
magnification--;
if (magnification > MAX_MAGNIFY)
magnification = MAX_MAGNIFY;
if (magnification < 1)
magnification = 1;
}
}
Body Class:
package src.main.celestials;
import java.awt.Point;
import src.main.Core;
import src.main.map.Map;
public abstract class Body
{
protected String name;
protected double x, y, mass;
public Body(Point t, String s, int m)
{
x = t.x;
y = t.y;
name = s;
mass = m;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public double getMass() {
return mass;
}
public void setMass(double mass) {
this.mass = mass;
}
public abstract void update(long ticks);
}
SolarSystem Class:
package src.main.celestials;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import src.main.celestials.Planet;
public class SolarSystem extends Body {
private ArrayList<Planet> planets = new ArrayList<Planet>();
private BufferedImage[] images = new BufferedImage[7];
private BufferedImage sun;
public SolarSystem(Point t, String s, int m) {
super(t, s, m);
try
{
String loc = System.getProperty("user.home") + "\\Desktop\\Proof of Concept Game\\Proof of Concept Game\\Gravity Test\\Suns\\";
images[0] = ImageIO.read(new File(loc + "Sun_Blue.png"));
images[1] = ImageIO.read(new File(loc + "Sun_BlueWhite.png"));
images[2] = ImageIO.read(new File(loc + "Sun_Orange.png"));
images[3] = ImageIO.read(new File(loc + "Sun_Red.png"));
images[4] = ImageIO.read(new File(loc + "Sun_White.png"));
images[5] = ImageIO.read(new File(loc + "Sun_Yellow.png"));
images[6] = ImageIO.read(new File(loc + "Sun_YellowWhite.png"));
} catch (Exception e)
{
e.printStackTrace();
}
sun = images[(int) (Math.random() * images.length)];
}
//earth mass:5.97219 × 10^24KG
//sun mass: 1.9891 × 10^30KG
//sun/earth ratio: 333060.4016
#Override
public void update(long ticks) {
// run interactions between planets and solar systems
for(int i=0; i<planets.size(); i++)
{
planets.get(i).setxAccel(genAccelX(i));
planets.get(i).setyAccel(genAccelY(i));
}
// update using acceleration and velocity
for(int i=0; i<planets.size(); i++)
{
planets.get(i).update(ticks);
}
}
public void addPlanet(Point point, String name)
{
planets.add(new Planet(point, name, (int) (5.97219*Math.pow(10, 24))));
}
/*public double genAccel(int i)
{
double G=6.67*Math.pow(10, -11);
double Dx=Math.pow(planets.get(i).getX()-getX(), 2);
double Dy=Math.pow(planets.get(i).getY()-getY(), 2);
double D=Math.sqrt(Dx+Dy);
return G*planets.get(i).getMass()/Math.pow(D, 2);
}
public double genAccelX(int i)
{
double Dx=Math.pow(planets.get(i).getX()-getX(), 2);
double Dy=Math.pow(planets.get(i).getY()-getY(), 2);
double D=Math.sqrt(Dx+Dy);
double A=genAccel(i);
return Dx*A/D;
}
public double genAccelY(int i)
{
double Dx=Math.pow(planets.get(i).getX()-getX(), 2);
double Dy=Math.pow(planets.get(i).getY()-getY(), 2);
double D=Math.sqrt(Dx+Dy);
double A=genAccel(i);
return Dy*A/D;
}*/
public double genAccelX(int i)
{
double x1=0;
double x2=0;
/*if(getX()>planets.get(i).getX())
{*/
x1=getX();
x2=planets.get(i).getX();
/*}
else
{
x1=planets.get(i).getX();
x2=getX();
}*/
double G=6.67e-11;
double d=x1-x2;
d*=d;
planets.get(i).setxForce((G*getMass()*planets.get(i).getMass())/d);
return planets.get(i).getxForce()/planets.get(i).getMass();
}
public double genAccelY(int i)
{
double y1=0;
double y2=0;
/*if(getY()<planets.get(i).getY())
{*/
y1=getY();
y2=planets.get(i).getY();
/*}
else
{
y1=planets.get(i).getY();
y2=getY();
}*/
double G=6.67e-11;
double d=y1-y2;
d*=d;
planets.get(i).setyForce((G*getMass()*planets.get(i).getMass())/d);
return planets.get(i).getyForce()/planets.get(i).getMass();
}
public void draw(Graphics2D g, int magnification) {
g.drawImage(sun, (int)(x) * magnification + 2, (int)(y) * magnification + 2, 50, 50, null);
for(int i=0; i<planets.size(); i++)
{
g.setColor(Color.PINK);
g.fillOval((int)planets.get(i).getX(), (int)planets.get(i).getY(), 20, 20);
}
}
}
Planet Class:
package src.main.celestials;
import java.awt.Point;
public class Planet extends Body {
double xForce, yForce, xAccel, yAccel, xVel, yVel;
public Planet(Point t, String s, int m) {
super(t, s, m);
}
public double getxForce() {
return xForce;
}
public void setxForce(double xForce) {
this.xForce = xForce;
}
public double getyForce() {
return yForce;
}
public void setyForce(double yForce) {
this.yForce = yForce;
}
public double getxAccel() {
return xAccel;
}
public void setxAccel(double xAccel) {
this.xAccel = xAccel;
}
public double getyAccel() {
return yAccel;
}
public void setyAccel(double yAccel) {
this.yAccel = yAccel;
}
public double getxVel() {
return xVel;
}
public void setxVel(double xVel) {
this.xVel = xVel;
}
public double getyVel() {
return yVel;
}
public void setyVel(double yVel) {
this.yVel = yVel;
}
#Override
public void update(long ticks) {
xVel+=xAccel;
yVel+=yAccel;
x+=xVel;
y+=yVel;
System.out.println("X: "+(int)x+"\t\t"+"Y: "+(int)y);
System.out.println("XVel: "+xVel+"\t\t"+"YVel: "+yVel);
System.out.println("XAccel: "+xAccel+"\t"+"YAccel: "+yAccel);
}
}
First of all, concerning your comment
I thought I was in the java forum?
(also #PM77-1 :) StackOverflow is not a forum at all! Also see https://meta.stackexchange.com/a/92110 . It is a "Question And Answer" site, and this imposes some constraints on how you should ask, and what to expect as a response. (Actually, the fact that I'm writing this here is already a "violation" of these rules, and I'm risking downvotes for that). The question in its current form is hardly suitable for any site of the StackExchange network. If you write a question here, then you should ...
Include a clear and focussed question (namely, one that goes beyond "What is wrong with my code?")
Provide Minimal, Complete, and Verifiable example (preferably, if possible and appropriate, as a single code block, with no dependencies to other, unrelated classes, and no dependencies to external resources etc.)
That said, regarding your actual "question": The formula that you are using is correct. But it is not properly implemented. The formula is
(according to Wikipedia)
The "r" refers to the distance between the centers of mass of the objects.
In your code, you tried to implement this in the SolarSystem#genAccelX and SolarSystem#genAccelY methods:
...
double x1=0;
double x2=0;
x1=getX();
x2=planets.get(i).getX();
double G=6.67e-11;
double d=x1-x2;
d*=d;
planets.get(i).setxForce((G*getMass()*planets.get(i).getMass())/d);
return planets.get(i).getxForce()/planets.get(i).getMass();
But the problem is that you are computing this separately for x and for y. Thus, the resulting force is losing its actual direction (and is "wrong" anyhow).
To put it simply: The d that you are computing there is negative, because the planet should move to the left. But by taking d*=d, it becomes positive (and this, the planet is moving to the right)
The solution:
You should describe all positions, velocities and forces as vectors instead of computing everything separately for x and y. Basically, it will boil down to a class like
class Vector {
private double x, y;
// Setters, getters...
....
double distanceSquared(Vector other) {
double dx = x - other.x;
double dy = y - other.y;
return Math.sqrt(dx*dx+dy*dy);
}
double distance(Vector other) {
return Math.sqrt(distanceSquared(other));
}
// Some other useful methods:
double length() { ... }
void normalize(double factor) { ... }
void scale(double factor) { ... }
void add(Vector other) { ... }
void addScaled(double factor, Vector other) { ... }
void sub(Vector other) { ... }
Vector difference(Vector other) { ... }
}
Given such a class, the method for computing the force and acceleration could roughly look like this:
// The bodies here may be the sun and a planet:
public double computeForceStrength(Body body0, Body body1)
{
double G=6.67e-11;
Vector position0 = body0.getPosition();
Vector position1 = body1.getPosition();
double distanceSquared = position0.distanceSquared(position1);
// The formula from wikipedia:
double f = G * body0.getMass() * body1.getMass() / distanceSquared;
return f;
}
void performSomeTimeStep(Body body0, Body body1)
{
Vector direction = position1.difference(position0);
direction.normalize();
double f = computeForceStrength(body0, body1);
body0.getVelocity().addScaled(direction, f / body0.getMass());
body1.getVelocity().addScaled(direction, -f / body1.getMass());
}
(just to show the basic idea - again: There are many degrees of freedom)
There are several other (stylistic) issues with the code, but ... when it is working, you may want to submit it to https://codereview.stackexchange.com/ ....
Related
I put in a particle system but when i run the program, when I spawn some particles, they don't render. I looked at the ArrayList and its value would always be 0 even when i added a particle to it.
heres the code for main class:
package Main;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
import me.mango.rendering.Particle;
//do double buffering
public class Game extends JPanel {
private static final long serialVersionUID = 1L;
public static final int height = 400;
public static final int width = height * 16 / 9;
JPanel p;
Game game;
Graphics g;
JFrame frame;
KeyListener kl;
MouseListener ml;
public boolean running = true;
private ArrayList<Particle> particles = new ArrayList<Particle>(500);
public Game(){
kl = new KeyListener(){
public void keyPressed(KeyEvent e) {
}
public void keyReleased(KeyEvent e) {
}
public void keyTyped(KeyEvent e) {
}
};
ml = new MouseListener(){
public void mousePressed(MouseEvent e) {
addParticle(true);addParticle(false);addParticle(true);
addParticle(false);addParticle(true);addParticle(false);
}
public void mouseReleased(MouseEvent e) {
}
public void mouseClicked(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
};
}
public void addParticle(boolean b){
int dx,dy;
int x = 100;
int y = 100;
if(b){
dx = (int) (Math.random()*5);
dy = (int) (Math.random()*5);
}else{
dx = (int) (Math.random()*-5);
dy = (int) (Math.random()*-5);
}
int size = (int) (Math.random()*12);
int life = (int) Math.random()*(120)+380;
particles.add(new Particle(x,y,dx,dy,size,life,Color.blue));
}
public void update(double delta){
for(int i = 0; i<= particles.size() - 1;i++){
if(particles.get(i).update()) particles.remove(i);
}
System.out.println(particles.size());
}
#Override
public void paint(Graphics g){
g.clearRect(0, 0, getWidth(), getHeight());
//render here
renderParticles(g);
g.dispose();
}
public void renderParticles(Graphics g){
for(int i =0;i <= particles.size() - 1;i++){
particles.get(i).render(g);
System.out.println("spawned");
}
}
public void run(){
//initialize time loop variables
long lastLoopTime = System.nanoTime();
final int TARGET_FPS = 60;
final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;
double lastFpsTime = 0;
//Main game loop
while(running)
{
//Calculate since last update
long now = System.nanoTime();
long updateLength = now - lastLoopTime;
lastLoopTime = now;
double delta = updateLength / ((double)OPTIMAL_TIME);
//update frame counter
lastFpsTime += updateLength;
//update FPS counter
if(lastFpsTime >= 1000000000)
{
lastFpsTime = 0;
}
//game updates
game.update(delta);
//graphics (gameState)
game.repaint();
try{
Thread.sleep((Math.abs(lastLoopTime - System.nanoTime() + OPTIMAL_TIME)/1000000));
}catch(Exception e){
System.out.println("Error in sleep");
}
}
}
public void start(){
frame = new JFrame("Game");
game = new Game();
frame.add(game);
frame.pack();
frame.setSize(width, height);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.addKeyListener(kl);
frame.addMouseListener(ml);
frame.setVisible(true);
run();
}
public static void main(String[] args){
new Game().start();
}
}
and for the particle class:
package me.mango.rendering;
import java.awt.Color;
import java.awt.Graphics;
public class Particle {
private int x;
private int y;
private int dx;
private int dy;
private int size;
private int life;
private Color color;
public Particle(int x, int y, int dx, int dy, int size, int life, Color c){
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.size = size;
this.life = life;
this.color = c;
}
public boolean update(){
x += dx;
y += dy;
life--;
if(life <= 0){
return true;
}
return false;
}
public void render(Graphics g){
g.setColor(color);
g.fillRect(x-(size/2), y-(size/2), size, size);
g.dispose();
}
}
Thanks!
You have a thing called game inside the class Game: that's not good design at all. Apparently you dont understand the meaning of creating an object.
In main() you created an object game: that should be enough. That thing you have to manipulate.
Therefore calling game.something() inside the class game is a convolution. Get rid of it.
game = new Game();
Game game;
These things must go.
And any reference to game.someMethod()
should be replaced with just someMethod(), if you are inside Game.
Plus you have things like run() and start() etc: do you think you are creating some threads?? by just using those names for your methods?
No.
We are making a six-player multiplayer game and I am responsible for a networking class. The game incorporates Slick 2D. In the game there are entities(code shown below)
package Game;
import org.newdawn.slick.*;
public abstract class Entity {
double maxHealth, health, speed, damage, width, height, locationX, locationY;
Animation standing, walkingLeft, walkingRight, attacking, jumping, current;
long pastTime = 0;
public boolean isReadyToAttack(long delta) {
if(pastTime < 2 * 500) { //multiply by 1000 to get milliseconds
pastTime += delta;
return false;
}else{
pastTime = 0;
return true;
}
}
public void setMaxHealth(double d){
maxHealth = d;
}
public void setHealth(double d){
health = d;
}
public void setSpeed(double x){
speed = x;
}
public void setDamage(double x){
damage = x;
}
public void setLocation(double x, double y){
locationX = x;
locationY = y;
}
public void setSprites(Animation s, Animation r){
standing = s;
walkingRight = r;
}
public void setCurrent(Animation a){
current = a;
}
public double getMaxHealth(){
return maxHealth;
}
public double getHealth(){
return health;
}
public double getSpeed(){
return speed;
}
public double getDamage(){
return damage;
}
public double getLocationX(){
return locationX;
}
public double getLocationY(){
return locationY;
}
public Animation getStanding(){
return standing;
}
public Animation getRight(){
return walkingRight;
}
public Animation getCurrent(){
return current;
}
public abstract double getAttackRange();
}
The entities class is also extended by the Heroes class which is further extended by the Ares class. Ares is one of the six characters in our game and when you start the game you can choose one of the six in the start menu.
Here is the Hero class:
package Game;
import org.newdawn.slick.Animation;
public abstract class Hero extends Entity{
static double gravity = 0.1;
double velocity;
int level;
boolean disabled, grounded = true;
public void setLevel(int i){
level = i;
}
public void setVelocity(double d){
velocity = d;
}
public void setDisabled(boolean b){
disabled = b;
}
public void setGrounded(boolean b){
grounded = b;
}
public int getLevel(){
return level;
}
public double getVelocity(){
return velocity;
}
public boolean getDisabled(){
return disabled;
}
public boolean getGrounded(){
return grounded;
}
public double getGravity(){
return gravity;
}
public boolean isAlive(){
if(getHealth() > 0)
return true;
return false;
}
public void attack(Entity gettingAttacked, Hero attacking) {
gettingAttacked.setHealth(gettingAttacked.getHealth() - attacking.getDamage());
}
public boolean isReadyToRegenerate(long delta) {
if(pastTime < 1 * 1000){
pastTime += delta;
return false;
}
else{
pastTime = 0;
return true;
}
}
public static void regenerate(Hero h, long delta){
if(h.getHealth() >= 0 && h.getHealth() < h.getMaxHealth())
if(h.isReadyToRegenerate(delta))
h.setHealth(h.getHealth() + (h.getMaxHealth()) * 0.01);
}
public abstract void levelUp();
}
Here is the Ares class:
package Game;
import org.newdawn.slick.Animation;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;
public class Ares extends Hero {
public Ares() throws SlickException {
setHealth(500);
setSpeed(0.4);
setDamage(70);
setLocation(850,625);
setSprites(new Animation(new SpriteSheet("res/Ares.png", 191, 275), 200), new Animation(new SpriteSheet("res/AresWalk.png", 191, 275), 200));
}
public void levelUp() {
level += 1;
health *= 1.1;
damage *= 1.1;
}
public double getAttackRange() {
return 250;
}
public double maxHealth() {
return 500;
}
}
Here is the Battle code, which contains the main gameplay code, the code is currently uncompleted and only has movement:
package Game;
import java.util.ArrayList;
import org.lwjgl.input.*;
import org.newdawn.slick.*;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.state.*;
public class Battle extends BasicGameState {
private ArrayList<Entity> entities = new ArrayList<Entity>();
private Hero player;
private Image background;
private int midScreen, ground;
public void init(GameContainer gc, StateBasedGame sbg) throws SlickException{
gc.setVSync(true);
player = new Ares();
player.setCurrent(player.getStanding());
entities.add(player);
background = new Image("res/Background.png");
midScreen = 800;
ground = 625;
}
public void render(GameContainer gc, StateBasedGame sbg, Graphics g) throws SlickException{
g.translate((float)-player.getLocationX() + midScreen, (float)-player.getLocationY() + ground);
background.draw(0, -920);
for(int i = 0; i < entities.size(); i++)
entities.get(i).getCurrent().draw((float)entities.get(i).getLocationX(), (float)entities.get(i).getLocationY());
}
public void update(GameContainer gc, StateBasedGame sbg, int delta) throws SlickException {
player.getCurrent().update(delta); // ensures
Input kb = gc.getInput();
if (kb.isKeyDown(Input.KEY_SPACE) && player.getGrounded()) {
player.setGrounded(false);
player.setVelocity(7);
player.setLocation(player.getLocationX(), player.getLocationY() - player.getVelocity());
player.setCurrent(player.getStanding());
}
else if (kb.isKeyDown(Input.KEY_A) && player.getLocationX() > 800) {
player.setLocation(player.getLocationX() - player.getSpeed() * delta, player.getLocationY());
if(player.grounded)
player.setCurrent(player.getRight());
}
else if (kb.isKeyDown(Input.KEY_D) && player.getLocationX() < 8580) {
player.setLocation(player.getLocationX() + player.getSpeed() * delta, player.getLocationY());
if(player.grounded)
player.setCurrent(player.getRight());
}
else
player.setCurrent(player.getStanding());
if (Math.abs(player.getLocationY() - ground) < .01)
player.setGrounded(true);
if (player.getGrounded() == false) {
player.setVelocity(player.getVelocity() - player.getGravity());
player.setLocation(player.getLocationX(), player.getLocationY() - player.getVelocity());
}
}
public int getID(){
return 1;
}
}
What I am having difficulty with is creating a server and client that gets the input from the players, such as location, who chose which player, etc. I'm wondering that if in Battle, when Player objects are made, how would I be able to get location and other specific attributes of the objects and give them to the other computers.
Note: I am coding in Java, and trying to use a TCP connection to code this. I am open to UDP if you think it may be easier. Just give a basic overview of what needs to be done and perhaps a little code, do not simply give me the answer please.
Let me be brief. I am creating a n-body simulation for a school-project. I've encountered a problem: I get the error IndexOutOfBoundsException only sometimes when I run the simulation. There does not seem to be any correlation between when I get the error. I am assuming I am destroying the bodies (via the collision detection method) too quickly for it to register, and it tries to access an index that no longer exists. I thought
if(bodies.get(i)!=null&&bodies.get(n)!= null)
in the update method would fix it, but it did not. Could someone take a look at the code and tell me what might be causing the error? To recreate the error: Simply press 'o' in the simulation, or randomly spawn massive bodies with 'm' until it occurs. Please keep in mind that I am only in high school, and I have not got a lot of programming experience.
MAIN:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Main extends JPanel implements Runnable, KeyListener{
public static int width = 1400;
public static int height = (width/16) * 9;
public String title = "Orbital Mechanics Simulator -";
public Dimension dim = new Dimension(width, height);
public boolean running;
Color bgColor = new Color(0x000000);
public static long timeScale = (long) Math.pow(10,7.5); //how many simulation-seconds one IRL-second represents.
public static long distanceScale = ((Physics.astUnit*10L) / width); //how many meters does one pixel represent
ArrayList<Body> bodies = new ArrayList<Body>();
long initFpsTime;
long secondFpsTime;
long nowTime;
long lastTime;
long timeElapsed;
float deltaTime;
int fps = 200;
public Main(){
this.setPreferredSize(dim);
this.setBackground(bgColor);
this.start();
}
public void start(){
running = true;
Thread program = new Thread(this, "update");
program.start();
}
public void run(){
lastTime = System.nanoTime();
while(running){
nowTime = System.nanoTime();
deltaTime = nowTime - lastTime;
update(deltaTime);
initFpsTime = System.currentTimeMillis();
if((initFpsTime - secondFpsTime) > Math.pow(10,3) / fps){
render();
secondFpsTime = System.currentTimeMillis();
}
}
}
public void update(float deltaTime){
for(int i=0; i<bodies.size();i++){
resetForces();
bodies.get(i).update((float)(deltaTime / Math.pow(10,9))*timeScale);
lastTime = System.nanoTime();
//sets the forces for all bodies
for(int n=0; n<bodies.size();n++){
if(bodies.get(i)!=bodies.get(n)){
if(bodies.get(i)!=null&&bodies.get(n)!= null)
bodies.get(i).setForce(Physics.getFx(bodies.get(i), bodies.get(n)), Physics.getFy(bodies.get(i), bodies.get(n)));
//collision detection
if(Physics.getDistanceBetween(bodies.get(i), bodies.get(n)) < (bodies.get(i).radius + bodies.get(n).radius)*distanceScale){
collision(bodies.get(i),bodies.get(n));
}
}
}
}
}
//DIFFERENT SYSTEMS
public void solarSystem(){
Body sun;
Body earth;
Body mars;
sun = new Body("Sun", Physics.massSun, 20, 0,0, new Color(0xffff00), (float)0, (float)0);
earth = new Body("earth", Physics.massEarth, 10, Physics.astUnit/distanceScale,0, new Color(0x0000ff), (float)0, (float)0);
mars = new Body("Mars", Physics.massEarth, 10, (long)(1.5*Physics.astUnit/distanceScale) ,0, new Color(0x00ff00), (float)0, (float)0);
earth.setVelocity(0,(float)Physics.getInitVy((long)Physics.getDistanceBetween(earth, sun), sun));
mars.setVelocity(0,(float)Physics.getInitVy((long)Physics.getDistanceBetween(mars, sun), sun));
bodies.add(sun);
bodies.add(earth);
bodies.add(mars);
}
public void twoBodies(){
bodies.add(new Body("Sun", Physics.massSun, 20, 0,0, new Color(0xffff00), (float)0, (float)0));
bodies.add(new Body("earth", Physics.massEarth, 10, Physics.astUnit/distanceScale,0, new Color(0x0000ff), (float)0, (float)0));
bodies.add(new Body("Mars", Physics.massMars, 10, (long)(1.5*Physics.astUnit/(distanceScale)),0 ,new Color(0x00ff00), (float)0, (float)0));
//earth.setVelocity(0,(float)Physics.getInitVy((long)Physics.getDistanceBetween(earth, sun), sun));
//mars.setVelocity(0,(float)Physics.getInitVy((long)Physics.getDistanceBetween(mars, sun), sun));
}
public void createRandomBody(){
bodies.add(new Body("randomBody",Physics.massSun,10, Physics.randomXPos(), Physics.randomYPos(), Physics.randomColor(),(float)0,(float)0));
}
public void createMassiveBody(){
bodies.add(new Body("Sun",Physics.massSun,10, Physics.randomXPos(), Physics.randomYPos(), Physics.randomColor(),(float)0,(float)0));
}
public void createSmallBody(){
bodies.add(new Body("Earth",Physics.massEarth,10, 0, 0, Physics.randomColor(), (float)0, (float)0));
}
public void createSystem(){
for(int i=0; i<20;i++){
for(int n=0; n<20;n++){
bodies.add(new Body("Random", Physics.massSun, 4, -width/2 + n*20 , height/2 - i*20 , Color.WHITE, (float)0, (float)0 ));
}
}
}
public void resetForces(){
if(bodies.get(0) != null);
for(int i=0;i<bodies.size();i++){
if(i<=0 && i < bodies.size()){
bodies.get(i).resetForce();
}
}
}
public void collision(Body a, Body b){
Body newBody = new Body("newBody", a.mass+b.mass, 20, (a.xPos+b.xPos)/2, (a.yPos+b.yPos)/2, Physics.randomColor(), (a.mass*a.vx + b.mass*b.vx)/(a.mass+b.mass), (a.mass*a.vy+b.mass*b.vy)/(a.mass+b.mass));
bodies.remove(a);
bodies.remove(b);
bodies.add(newBody);
}
public void render(){
repaint();
}
public void keyPressed(KeyEvent event) {
switch(event.getKeyChar()){
case 'r': createRandomBody();
break;
case 'f': twoBodies();
break;
case 'm':
createMassiveBody();
break;
case 'z':
createSmallBody();
break;
case 'o':
createSystem();
break;
case 's':
solarSystem();
break;
}
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); //quick vs quality - preferring quality
for(int i=0;i<bodies.size();i++){
bodies.get(i).displayPlanet(g2d);
}
}
public static void gui(){
Main main = new Main();
JFrame frame = new JFrame();
frame.setResizable(false);
frame.setTitle(main.title);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(main);
frame.addKeyListener(main);
frame.pack();
frame.setVisible(true);
}
public static void main(String args[]){
SwingUtilities.invokeLater(new Runnable(){ //Event dispatching thread
public void run(){
gui();
}
});
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
}
BODY
import java.awt.Color;
import java.awt.Graphics;
public class Body {
public static int xOrigo = Main.width / 2;
public static int yOrigo = ((Main.width * 9) / 16) / 2;
public static int numOfBodies; // how many bodies the program contains.
public static float distanceScale = Main.distanceScale;
public String name;
public float mass, fx, fy, accX, accY, vx, vy, initVx, initVy, deltaX, deltaY;
public long xDisplay, yDisplay;
public long xPos, yPos;
public int radius;
public Color color;
public Body(String name, float mass, int radius, long xPos, long yPos, Color color, float initVx, float initVy){
this.name = name;
this.mass = mass;
this.radius = radius;
this.color = color;
numOfBodies++;
setX(xPos);
setY(yPos);
setVelocity(initVx, initVy);
}
public void update(float deltaTime){
this.deltaX += this.vx * deltaTime;
if(this.deltaX >= distanceScale){
this.incX();
deltaX = 0;
}else if(this.deltaX <= -distanceScale){
this.decX();
deltaX = 0;
}
this.deltaY += this.vy * deltaTime;
if(this.deltaY >= distanceScale){
this.incY();
deltaY = 0;
}else if(this.deltaY <= -distanceScale){
this.decY();
deltaY = 0;
}
this.vx += (this.accX * deltaTime);
this.vy += (this.accY * deltaTime);
}
public void setX(long xPos){
this.xPos = xPos;
this.xDisplay = xOrigo + xPos - radius;
}
public void setY(long yPos){
this.yPos = yPos;
this.yDisplay = yOrigo - yPos - radius;
}
public void incX(){
this.xPos++;
this.xDisplay = xOrigo + xPos - radius;
}
public void decX(){
this.xPos--;
this.xDisplay = xOrigo + xPos - radius;
}
public void incY(){
this.yPos++;
this.yDisplay = yOrigo - yPos - radius;
}
public void decY(){
this.yPos--;
this.yDisplay = yOrigo - yPos - radius;
}
public void setForce(float fx, float fy){
this.fx += fx;
this.accX = fx / this.mass;
this.fy += fy;
this.accY = fy / this.mass;
}
public void setVelocity(float vx, float vy){
this.vx += vx;
this.vy += vy;
}
public void displayPlanet(Graphics g){
g.setColor(this.color);
g.fillOval((int )this.xDisplay, (int)this.yDisplay, this.radius*2, this.radius*2); //temporary fix
}
public long getXPos(){
return this.xPos;
}
public long getYPos(){
return this.yPos;
}
public void resetForce(){
this.fx = 0;
this.fy = 0;
}
}
PHYSICS:
import java.awt.Color;
import java.util.Random;
public class Physics {
public static Random rand = new Random();
public static final double G = 6.67384*(Math.pow(10, -11));
public static long astUnit = 149597871000L; //L IS TO INDICATE IT'S A LONG VALUE, otherwise neg value
public static float massEarth = (float)(5.97219*Math.pow(10,24));
public static float massSun = (float)(1.9891*Math.pow(10,30));
public static float massMars = (float) (6.41693*(Math.pow(10,23)));
public static float randomMass(){
return (float) Math.pow(((rand.nextDouble()*(massSun-massEarth))),rand.nextDouble())+massEarth;
}
public static double randInitV(){
return (double) rand.nextDouble()*Math.pow(10,4);
}
public static int randomXPos(){
return rand.nextInt(Main.width)-Main.width/2;
}
public static int randomYPos(){
return rand.nextInt(Main.height)-Main.height/2;
}
public static Color randomColor(){
return new Color(rand.nextInt(0xffffff));
}
public static int randomRadius(){
return rand.nextInt(50)+5;
}
public Physics(){
}
/*
public static Vector getVectorBetween(Body a, Body b){
float force = (float)((G*a.mass*b.mass) / Math.pow(getDistanceBetween(a,b),2));
double angle = Math.atan2(Math.abs(a.y - b.y),Math.abs(a.x - b.x));
Vector vector = new Vector(force,angle);
return vector;
}
*/
public static double getInitVy(long d, Body a){
return Math.sqrt((G*a.mass) / d);
}
public static float getFx(Body a, Body b){
float force = getForceBetween(a,b);
double angle = getAngleBetween(a,b);
float fx = (float)(force*Math.cos(angle));
if(a.xPos > b.xPos){
return -fx;
}else{
return fx;
}
}
public static float getFy(Body a, Body b){
float force = getForceBetween(a,b);
double angle = getAngleBetween(a,b);
float fy = (float)(force*Math.sin(angle));
if(a.yPos > b.yPos){
return -fy;
}else{
return fy;
}
}
public static float getForceBetween(Body a, Body b){
float force = (float)((G*a.mass*b.mass) / Math.pow(getDistanceBetween(a,b),2));
return force;
}
public static double getDistanceBetween(Body a, Body b){
double xKatet = Math.abs(a.getXPos()*Main.distanceScale - b.getXPos()*Main.distanceScale);
double yKatet = Math.abs(a.getYPos()*Main.distanceScale - b.getYPos()*Main.distanceScale);
double distance = Math.hypot(xKatet, yKatet);
return distance;
}
public static double getAngleBetween(Body a, Body b){
long deltaX = Math.abs(a.xPos-b.xPos);
long deltaY = Math.abs(a.yPos-b.yPos);
double angle = Math.atan2(deltaY, deltaX);
return angle;
}
}
(Given the few information and the wall of code, I may be going too far out on a limb, but in doubt, I can delete the answer)
You are accessing the same list with different threads. The collision handling method
public void collision(Body a, Body b) {
...
bodies.remove(a);
bodies.remove(b);
bodies.add(newBody);
}
is executed by the main physics thread that is started in the Main#start() method. This thread is modifying the list. And this may happen while the Swing Event Dispatch Thread (that is also reponsible for painting) is iterating over the bodies in the paintComponent method:
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
...
for(int i=0;i<bodies.size();i++){
bodies.get(i).displayPlanet(g2d);
}
}
Adding additional checks will not solve this issue. You'll need some form of synchronization. The brute-force-hammer would be to just synchronize on the bodies list:
public void collision(Body a, Body b) {
...
synchronized (bodies)
{
bodies.remove(a);
bodies.remove(b);
bodies.add(newBody);
}
}
and
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
...
synchronized (bodies)
{
for(int i=0;i<bodies.size();i++){
bodies.get(i).displayPlanet(g2d);
}
}
}
(basically, something like this would have to be done in all places where one thread might read the list while another thread is writing to the list).
But again: This is rather pragmatic. You could consider one of the thread-safe data structures from the java.util.concurrent package, or some manual, more fine-grained locking solution, probably with some ReadWriteLock.
It's far too much code to give a more focussed answer here (but at least, I wanted to point out what is (almost certainly) the reason for your problem)
You have a problem in your method Main.update(), the essentials of which are:
public void update(float deltaTime){
for(int i=0; i<bodies.size();i++){
// ...
//sets the forces for all bodies
for(int n=0; n<bodies.size();n++){
// ...
//collision detection
if(Physics.getDistanceBetween(bodies.get(i), bodies.get(n)) < (bodies.get(i).radius + bodies.get(n).radius)*distanceScale){
collision(bodies.get(i),bodies.get(n));
}
}
}
}
In the event that i is bodies.size() - 1 and there is a collision with another body, the two colliding bodies are replaced with a single body, reducing the total number of bodies by 1. At that point i is bodies.size(), which is not a valid index into bodies. You nevertheless continue the inner loop, during which you perform bodies.get(i) again, generating an IndexOutOfBoundsException.
here is the entire code for the classes Ship,Asteroids,BaseShapeClass. Ship Class inherits from the BaseShapeClass for its shape. Asteroid class is the main source code which declares the Graphics2D object,AffineTransform(for identity creation),declares double image buffer...
Code for BaseShapeClass..
package baseshapeclass;
import java.awt.Shape;
public class BaseShapeClass {
private Shape shape;
private double x, y;
private double velX, velY;
private double moveAngle, faceAngle;
private boolean alive;
//accessors and mutators
public Shape getShape(){return shape;}
public void setShape(Shape shape){ this.shape = shape; }
public double getX() { return x; }
public void setX(double x) { this.x = x; }
public void incX(double ix) { this.x += ix; }
public double getY() { return y; }
public void setY(double y) { this.y = y; }
public void incY(double iy) { this.y += iy; }
public double getVelX() { return velX; }
public void setVelX(double velX) { this.velX = velX; }
public void incVelX(double ivX) { this.velX += ivX; }
public double getVelY() { return velY; }
public void setVelY(double velY) { this.velY = velY; }
public void incVelY(double ivY) { this.velY += ivY; }
//MoveAngle refers to the objects angular movement
public double getMoveAngle() { return moveAngle; }
public void setMoveAngle(double mAngle) { this.moveAngle = mAngle; }
public void incMoveAngle(double imAngle) { this.moveAngle += imAngle; }
//FaceAngle refers to the objects face/heads angular movement
public double getFaceAngle() { return faceAngle; }
public void setFaceAngle(double fAngle) { this.faceAngle = fAngle; }
public void incFaceAngle(double ifAngle) { this.faceAngle += ifAngle; }
public boolean isAlive() { return alive; }
public void setAlive(boolean alive) { this.alive = alive; }
//default constructor everything will be set to original state
//when update is called everything will start to move
BaseShapeClass(){
setShape(null);
setAlive(false);
//all of them are set to '0' representing their initial position,
//which will be called during the update() Event of the graphics objects
setX(0.0);
setY(0.0);
setVelX(0.0);
setVelY(0.0);
setMoveAngle(0.0);
setFaceAngle(0.0);
}
}
Code for Ship class...
package baseshapeclass;
import java.awt.Rectangle;
import java.awt.Polygon;
public class Ship extends BaseShapeClass {
//ships shape along the x and y cordinates
private final int[] shipx = {-6,3,0,3,6,0};
private final int[] shipy = {6,7,7,7,6,-7};
public Rectangle getBounds(){
Rectangle r = new Rectangle((int)getX()-6, (int)getY()-6, 12, 12);
return r;
}
Ship(){
setShape(new Polygon(shipx, shipy, shipx.length));
setAlive(true);
}
}
Code for Asteroid(Main source code)...
package baseshapeclass;
import java.awt.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.applet.*;
import java.util.*;
public abstract class Asteroid extends Applet implements Runnable, KeyListener {
BufferedImage backbuffer;
Graphics2D g2d;
Ship ship = new Ship();
boolean showBounds= true;
AffineTransform identity = new AffineTransform();
#Override public void init(){
backbuffer = new BufferedImage(640,480,BufferedImage.TYPE_INT_RGB);
g2d = backbuffer.createGraphics();
ship.setX(320);
ship.setY(240);
addKeyListener(this);
}
#Override public void update(Graphics g){
g2d.setTransform(identity);
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getSize().width, getSize().height);
g2d.setColor(Color.WHITE);
g2d.drawString("Ship: "+Math.round(ship.getX())+" , "+Math.round(ship.getY()),2, 150);
g2d.drawString("Face Angle: "+Math.toRadians(ship.getFaceAngle()),5, 30);
g2d.drawString("Move Angle: "+Math.toRadians(ship.getMoveAngle())+90,5,50);
drawShip();
paint(g);
}
public void drawShip(){
g2d.setTransform(identity);
g2d.translate(ship.getX(),ship.getY());
g2d.rotate(Math.toRadians(ship.getFaceAngle()));
g2d.setColor(Color.ORANGE);
g2d.fill(ship.getShape());
}
}
I hope you guys get a better idea with all the code in place. Just wanted to know on the part of Ship class why are the ships x and y cordinates such as under:
public class ship extends BaseShapeClass{
private int[] shipx = {-6,3,0,3,6,0};
private int[] shipy = {6,7,7,7,6,-7};
}
I cant follow on how those values will make upto a Polygon??
Ship(){
setShape(new Polygon(shipx,shipy,shipx.length));
setAlive(true);
}
You can see that the two arrays you are confused about go into the initialization of a Polygon. These two arrays, taken as a pair, give the x and y coordinates of each point in the Polygon.
This post is in answer to your comment in Kronion's answer; I was going to post it as a comment, but there is too much to say and I wanted to show you some code, which is not as legible in the comments.
As Kronion said, the Polygon class does indeed accept an array of X coordinates, and an array of Y coordinates. The reason for this is that the X and Y coordinate are stored at the same position in both arrays. So if int index = 0, then that X,Y coordinate pair would be xArray[index] and yArray[index].
If that doesn't make any sense, examine the Polygon class source code. For example, you'll see this happening in the contains method, here:
for (int i = 0; i < npoints; lastx = curx, lasty = cury, i++) {
curx = xpoints[i];
cury = ypoints[i];
// remainder of loop
}
So in short, they are assigned in this manner because the X and Y are paired by their index positions.
Hope that helps.
import java.awt.Graphics;
import javax.swing.JApplet;
import javax.swing.JPanel;
public class Circle extends JPanel {
int x = 75;
int y = 100;
int diameter = 50;
public void setAnimationY(int y) {
this.y = y;
}
public int getAnimationY() {
return y;
}
public int getDiameter() {
return diameter;
}
public void setDiameter(int startDiameter) {
diameter = startDiameter;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawOval(x, y, diameter, diameter);
}
}
import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JApplet;
import javax.swing.JPanel;
import javax.swing.Timer;
public class BouncingBall extends JApplet {
private int speed = 5;
private Timer timer;
private Circle draw;
#Override
public void init() {
super.init();
setLayout(new BorderLayout());
draw = new Circle();
add(draw);
timer = new Timer(30, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int y = draw.getAnimationY();
int diameter = draw.getDiameter();
int roof = getHeight();
y += speed;
if (y < 0) {
y = 0;
speed *= -1;
} else if (y + diameter > roof) {
y = roof - diameter;
speed *= -1;
}
draw.setAnimationY(y);
repaint();
}
});
}
#Override
public void start() {
super.start();
timer.start();
}
#Override
public void stop() {
timer.stop();
super.stop();
}
}
I am trying to create a JApplet that contains a ball that is bouncing up and down. So far I have been able to get the ball to go up and down but now I am trying to make the ball more "life-like" so I want the height of the ball to decrease each time the ball bounces until eventually it stops.
I have attempted to do a while loop using the roof variable that I created for the getHeight() method but for some reason when I tried to use it either the ball didn't move at all or the loop had no affect on the ball.
I have also tried a for loop but I ran into the same problem that I got into with the while loop. I believe the problem is that I am not placing this for loop in the correct spot for it to work correctly.
thanks in advance.
Little modifications to your code that can give you some trails:
#Override
public void actionPerformed(ActionEvent e) {
int y = draw.getAnimationY();
int diameter = draw.getDiameter();
int roof = getHeight();
y += speed;
//
// Reduce the ball size at the bottom of the screen
//
if(y + diameter > roof) {
if(diameter > minDiameter) {
diameter -= (roof - y);
} else {
diameter = minDiameter;
}
} else if (diameter < maxDiameter) {
diameter++;
}
draw.setDiameter(diameter);
if (y < 0) {
y = 0;
speed *= -1;
} else if (y + diameter > roof) {
y = roof - diameter;
speed *= -1;
}
// Simulates a little gravity
speed += 0.5;
draw.setAnimationY(y);
repaint();
}
For more realism, the best way would to find an equation that is function of the ball position and a coefficient of hardness for the ball and would give you the ball size.
Well let use continue with #MadProgrammer's solution from your other related question:
In your class of DrawPane we can easily define the height, getAnimationHeight() and setAnimationHeight(int) to control the height decrease as soon as it touches the ground. Please remember that in java left-top co-ordinate is (0, 0) and right-bottom co-ordinate is (getWidth(), getHeight()). Suppose that it starts from height = 0(top). Then it will start from y = height(top) and eventually move to the getHeight()(bottom) of your container. We will increase the height(top y) using setAnimationHeight() by adding an amount(say 30) to current height(which getAnimationHeight() will return) .
So, this little tweak made to #MadeProgrammer's solution in your other question will be the following demo.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.Timer;
public class Circle extends JApplet {
private int delta = 8;
private Timer timer;
private DrawPane drawPane;
#Override
public void init() {
super.init();
setLayout(new BorderLayout());
drawPane = new DrawPane();
add(drawPane);
timer = new Timer(100, new ActionListener() {
int frameCount = 0;
#Override
public void actionPerformed(ActionEvent e) {
int y = drawPane.getAnimationY();
int diameter = drawPane.getDiameter();
y += delta;
if (y < drawPane.getAnimationHeight()) {
y = drawPane.getAnimationHeight();
delta *= -1;
} else if (y + diameter > getHeight()) {
y = getHeight()- diameter;
delta *= -1;
int animationHeight = drawPane.getAnimationHeight();
animationHeight = animationHeight + (getHeight() - diameter - animationHeight)/2;
drawPane.setAnimationHeight(animationHeight);
if(animationHeight + diameter + 2 >= getHeight())
{
System.out.println("true");
drawPane.setAnimationY(getHeight() - diameter);
repaint();
timer.stop();
return;
}
}
drawPane.setAnimationY(y);
repaint();
}
});
}
#Override
public void start() {
super.start();
timer.start();
}
#Override
public void stop() {
timer.stop();
super.stop();
}
public class DrawPane extends JPanel {
int x = 100;
int y = 0;
int diameter = 50;
int height = 0;
public void setAnimationX(int x) {
this.x = x;
}
public void setAnimationY(int y) {
this.y = y;
}
public void setAnimationHeight(int h)
{
height = h;
}
public int getAnimationHeight()
{
return height;
}
public int getAnimationX() {
return x;
}
public int getAnimationY() {
return y;
}
public int getDiameter() {
return diameter;
}
public void setDiameter(int startDiameter) {
diameter = startDiameter;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawOval(x, y, diameter, diameter);
}
}
}
NOTE: As soon as it touches the bottom finally, you should stop the Timer to get rid of the flickering of the ball. This task is left as an exercise for you.