Right now, I am currently making a marbles game where two players try and shoot their marbles into a circle. The player with more marbles in the circle wins.
The player whose turn it is will click and drag in the opposite direction of which they want the ball to move (sort of like a sling shot). In my Marble class, there is a thread which starts when the mouse click is released. The marble will move and bounce off the walls, slowly reaching a X and Y velocity of zero.
When this happens though, my thread continues looping and eating through the computers CPU.
You can tell that the thread is still running because it continues printing out the velocity even when the ball has stopped moving.
I need help trying to figure out a efficient way of stopping the thread without using the Thread class's stop() method.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Ball
{
float x, y, lastX, lastY;
int width, height;
float xVelocity, yVelocity;
float speed;
public Ball()
{
width = (int) (Math.random() * 50 + 10);
height = (int) (Math.random() * 50 + 10);
x = (float) (Math.random() * (Marble.gamePanel.getWidth() - width) + width/2);
y = (float) (Math.random() * (Marble.gamePanel.getHeight() - height) + height/2);
lastX = x;
lastY = y;
xVelocity = (float) Math.random() * speed*2 - speed;
yVelocity = (float) Math.random() * speed*2 - speed;
}
public void update()
{
lastX = x;
lastY = y;
x += xVelocity;
y += yVelocity;
if(x + width/2 >= Marble.gamePanel.getWidth())
{
xVelocity *= -0.75;
x = Marble.gamePanel.getWidth() - width/2;
}
else if (x - width/2 <= 0)
{
xVelocity *= -0.75;
x = width/2;
}
if (y + height/2 >= Marble.gamePanel.getHeight())
{
yVelocity *= -0.75;
y = Marble.gamePanel.getHeight() - height/2;
}
else if (y - height/2 <= 0)
{
yVelocity *= -0.75;
y = height/2;
}
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.List;
import java.util.ArrayList;
/**
* Write a description of class Marble here.
*
* #author (Ryan Lawes)
* #version (5-23-14)
*/
public class Marble extends JFrame implements MouseListener, MouseMotionListener
{
public static GamePanel gamePanel = new GamePanel();
private boolean running = false;
private int fps = 60;
private int frameCount = 0;
private int mouseXC;//X coordinate of where the mouse was clicked
private int mouseYC;//Y coordinate of where the mouse was clicked
private int mouseXR;//X coordinatte of where the mouse was released
private int mouseYR;//Y coordinatte of where the mouse was released
public Marble()
{
super("Fixed Timestep Game Loop Test");
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
JPanel p = new JPanel();
p.setLayout(new GridLayout(1,2));
cp.add(gamePanel, BorderLayout.CENTER);
cp.add(p, BorderLayout.SOUTH);
setSize(500, 500);
addMouseListener(this);
addMouseMotionListener(this);
}
public static void main(String[]args)
{
Marble mrb = new Marble();
mrb.setVisible(true);
}
//Starts a new thread and runs the game loop in it.
public void runGameLoop()
{
Thread loop = new Thread()
{
public void run()
{
gameLoop();
}
};
loop.start();
}
//Only run this in another Thread!
private void gameLoop()
{
//This value would probably be stored elsewhere.
final double GAME_HERTZ = 30.0;
//Calculate how many ns each frame should take for our target game hertz.
final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
//At the very most we will update the game this many times before a new render.
//If you're worried about visual hitches more than perfect timing, set this to 1.
final int MAX_UPDATES_BEFORE_RENDER = 5;
//We will need the last update time.
double lastUpdateTime = System.nanoTime();
//Store the last time we rendered.
double lastRenderTime = System.nanoTime();
//If we are able to get as high as this FPS, don't render again.
final double TARGET_FPS = 60;
final double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS;
//Simple way of finding FPS.
int lastSecondTime = (int) (lastUpdateTime / 1000000000);
while(running)
{
double now = System.nanoTime();
int updateCount = 0;
//Do as many game updates as we need to, potentially playing catchup.
while(now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER)
{
gamePanel.update();
lastUpdateTime += TIME_BETWEEN_UPDATES;
updateCount++;
}
//If for some reason an update takes forever, we don't want to do an insane number of catchups.
//If you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
if(now - lastUpdateTime > TIME_BETWEEN_UPDATES)
{
lastUpdateTime = now - TIME_BETWEEN_UPDATES;
}
//Render. To do so, we need to calculate interpolation for a smooth render.
float interpolation = Math.min(1.0f, (float) ((now - lastUpdateTime) / TIME_BETWEEN_UPDATES));
drawGame(interpolation);
lastRenderTime = now;
//Update the frames we got.
int thisSecond = (int) (lastUpdateTime / 1000000000);
}
//////if(gamePanel.ballXVel == 0 && gamePanel.ballYVel == 0)
//////{
////// running = !running;
//////}
}
private void drawGame(float interpolation)
{
gamePanel.setInterpolation(interpolation);
gamePanel.repaint();
}
public void mouseClicked(MouseEvent e)
{
}
public void mousePressed(MouseEvent e)
{
/**
* I want to make it so you have to wait until the marble is done rolling before you can click again
*/
mouseXC = (int) e.getPoint().getX();
mouseYC = (int) e.getPoint().getY();
}
public void mouseReleased(MouseEvent e)
{
mouseXR = (int) e.getPoint().getX();
mouseYR = (int) e.getPoint().getY();
gamePanel.ballXVel = (mouseXC - mouseXR)/2;
gamePanel.ballYVel = (mouseYC - mouseYR)/2;
//////////while(gamePanel.ballXVel != 0 && gamePanel.ballYVel != 0)
//////////{
running = !running;
runGameLoop();
////////}
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseExited(MouseEvent e)
{
}
public void mouseDragged(MouseEvent e)
{
}
public void mouseMoved(MouseEvent e)
{
}
}
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
public class GamePanel extends JPanel
{
float interpolation;
float ballX, ballY, lastBallX, lastBallY;
int ballWidth, ballHeight;
float ballXVel, ballYVel;
int lastDrawX, lastDrawY;
int ballSpeed = 10;
public GamePanel()
{
ballX = lastBallX = 100;
ballY = lastBallY = 100;
ballWidth = 25;
ballHeight = 25;
}
public void setInterpolation(float interp)
{
interpolation = interp;
}
public void update()
{
lastBallX = ballX;
lastBallY = ballY;
ballX += ballXVel;
ballY += ballYVel;
//Slowly decreases the ball's velocity
ballXVel *= 0.975;
ballYVel *= 0.975;
//Prints out the velocity
//////////if(ballXVel > 0.3 && ballYVel > 0.3)
//////////{
System.out.println(ballXVel);
System.out.println(ballYVel);
//////////}
//If the ball is at a slow enough velocity, it will stop moving alltogether
if(ballXVel > 0 && ballYVel > 0)//if x and y velocity are positive
{
if(ballXVel < 0.3 && ballYVel < 0.3)
{
ballXVel = 0;
ballYVel = 0;
}
}
if(ballXVel < 0 && ballYVel < 0)//if x and y velocity are negative
{
ballXVel *=-1;//Changes to a positive number so it is easier to check
ballYVel *=-1;//Changes to a positive number so it is easier to check
if(ballXVel < 0.3 && ballYVel < 0.3)
{
ballXVel = 0;
ballYVel = 0;
}
ballXVel *=-1;//Changes the velocity back if it doesn't get set to 0. Even if it does, 0*-1 is still 0
ballYVel *=-1;//Changes the velocity back if it doesn't get set to 0. Even if it does, 0*-1 is still 0
}
if(ballXVel < 0 && ballYVel > 0)//if x velocity is negative and y velocity is positive
{
ballXVel *=-1;//Changes to a positive number so it is easier to check
if(ballXVel < 0.3 && ballYVel < 0.3)
{
ballXVel = 0;
ballYVel = 0;
}
ballXVel *=-1;//Changes the velocity back if it doesn't get set to 0. Even if it does, 0*-1 is still 0
}
if(ballXVel > 0 && ballYVel < 0)//if x velocity is positive and y velocity is negative
{
ballYVel *=-1;//Changes to a positive number so it is easier to check
if(ballXVel < 0.3 && ballYVel < 0.3)
{
ballXVel = 0;
ballYVel = 0;
}
ballYVel *=-1;//Changes the velocity back if it doesn't get set to 0. Even if it does, 0*-1 is still 0
}
if(ballX + ballWidth/2 >= getWidth())
{
ballXVel *= -0.8;
ballX = getWidth() - ballWidth/2;
}
else if(ballX - ballWidth/2 <= 0)
{
ballXVel *= -0.8;
ballX = ballWidth/2;
}
if(ballY + ballHeight/2 >= getHeight())
{
ballYVel *= -0.8;
ballY = getHeight() - ballHeight/2;
}
else if(ballY - ballHeight/2 <= 0)
{
ballYVel *= -0.8;
ballY = ballHeight/2;
}
}
public void paintComponent(Graphics g)
{
//BS way of clearing out the old rectangle to save CPU.
g.setColor(getBackground());
g.fillRect(lastDrawX-1, lastDrawY-1, ballWidth+2, ballHeight+2);
g.setColor(Color.RED);
int drawX = (int) ((ballX - lastBallX) * interpolation + lastBallX - ballWidth/2);
int drawY = (int) ((ballY - lastBallY) * interpolation + lastBallY - ballHeight/2);
g.fillOval(drawX, drawY, ballWidth, ballHeight);
lastDrawX = drawX;
lastDrawY = drawY;
g.setColor(Color.BLACK);
}
}
The code you have commented:
//////if(gamePanel.ballXVel == 0 && gamePanel.ballYVel == 0)
//////{
////// running = !running;
//////}
Put it inside the while(running) loop like:
float EPSILON = 0.00000001f;
if(Math.abs(gamePanel.ballXVel - 0.0f) < EPSILON && Math.abs(gamePanel.ballYVel - 0.0f) < EPSILON)
{
running = false;
}
You have put it outside the while loop. The interpreter will never reach the code because running=true;
EDIT :
I tested the solution myself, it worked. See:
I suggest you change your gamePanel.update() method to return a boolean value, indicating if the marvel is still moving.
You can then use the result of that method to break out of the loop once the marvel has stopped.
In GamePanel#update() something like that.
final float epsilon = 0.00001f;
return (Math.abs(ballXVel) < epsilon) && (Math.abs(ballXVel) < epsilon);
And in Marble#gameLoop something like that:
running = !gamePanel.update();
Related
I am currently generating an isometric map which should allow some sprites to move randomly within its bounds. My sprites, or 'humans' do move within a specified constraint however it is not the correct boundaries I wish to set it to. Below is my code.
public class Human implements Entity {
private int[][] map;
public static final int TILE_WIDTH = 34;
public static final int TILE_HEIGHT = 34;
private int min = 100;
private int max = 200;
private Texture img;
// position variable
private Vector2 pos;
private float time;
public Human() {
img = new Texture(Gdx.files.internal("humanFF.png"));
// coordinates of human initial position
pos = new Vector2(9, 220);
// for locking movement if need be.
time = 2;
map = randomGenerator();
}
#Override
public void render(SpriteBatch batch) {
batch.draw(img, pos.x, pos.y);
}
#Override
public void update(float delta) {
time += delta;
Random rand = new Random();
int upperbound = 2;
double double_random = rand.nextDouble(upperbound);
// lock human, can only move once every 2 secs.
if (time > 0) {
move();
time = 0;
}
}
private void move() {
/** Calculation **/
for (int row = map.length - 1; row >= 0; row--) {
for (int col = map.length - 1; col >= 0; col--) {
float x = (col - row) * (TILE_WIDTH / 2f - 2);
float y = (col + row) * (TILE_HEIGHT / 4f);
}
}
// after calculation, mapWidth is 525 pixels.
int mapWidth = map.length * (TILE_WIDTH / 2 - 2);
// after calculation, mapHeight is 280 pixels.
int mapHeight = map.length * (TILE_HEIGHT / 4);
// Calculate the minimum and maximum x-coordinates.
float minX = 0;
float maxX = mapWidth - TILE_WIDTH;
if (maxX < 0) {
maxX = 0;
}
// max-x coordinate is 491.0. min-x coordinate is 0.0.
// Calculate the minimum and maximum y-coordinates.
float minY = 0;
float maxY = mapHeight - TILE_HEIGHT;
if (maxY < 0) {
maxY = 0;
}
// check the position of human against map boundaries
if (pos.x < minX) {
pos.x = minX;
} else if (pos.x > maxX) {
pos.x = maxX;
}
if (pos.y < minY) {
pos.y = minY;
} else if (pos.y > maxY) {
pos.y = maxY;
}
// min-y coordinate is 0.0, max-y coordinate is 246.0.
// a variable to store a random generated value between 100 and 200.
int a = (int) (Math.random() * (max - min + 1) + min);
float newX = pos.x;
float newY = pos.y;
// move up
if (a <= 125) {
newX -= 15;
newY += 8.5;
}
// move down
else if (a <= 150 && a > 125) {
newX += 15;
newY -= 8.5;
}
// move left
else if (a <= 175 && a > 150) {
newX -= 15;
newY -= 8.5;
}
// move right
else if (a <= 200 && a > 175) {
newX += 15;
newY += 8.5;
}
if (newX >= minX && newX <= maxX && newY >= minY && newY <= maxY) {
pos.x = newX;
pos.y = newY;
}
}
public int[][] randomGenerator() {
Random r = new Random();
int Size = r.nextInt(35, 36);
int[][] map = new int[Size][Size];
for(int row = 0; row < map.length; row++) {
for(int col = 0; col < map.length; col++) {
int Number = r.nextInt(10);
if(Number == 0) {
map[row][col] = 0;
} else if (Number == 1) {
map[row][col] = 1;
}
else if (Number == 2) {
map[row][col] = 2;
}
else if (Number == 3) {
map[row][col] = 3;
}
else if (Number == 4) {
map[row][col] = 4;
}
else if (Number == 5) {
map[row][col] = 5;
}
else if (Number < 8) {
map[row][col] = 6;
}
else {
map[row][col] = 7;
}
}
}
map[0][0] = 1;
return map;
}
}
Based on the above, I am generating a random value which defines the movement of my 'humans' thus adding or subtracting from their x and y positions. In my attempt to tackle the problem of these sprites moving outside of my isometric map, I tried to calculate my mapWidth, mapHeight, min-x, max-x, min-y and max-x and then checking the position of my human against these boundaries to determine their movement.
Although these sprites now move within a constraint, it is not same dimension as my isometric map, but are now constrained to a rectangular-shaped boundary. How do I modify my code so that the sprites only move within the isometric map I have generated?
Below is a photo for visualisation.
The coordinate system you use for Human enties is ordinary orthogonal, you don't map these coordinates to an isometric view but you -have to- map them onto the isometric background. Screen coordinate system is basically different from the isometric one. When you render you need to map coordinates so that
(0,0)-> left corner
(0,maxY)->top corner
(maxX,maxY)->right corner
(maxX,0)->bottom corner
See here for to and back(map screen touch events to the map etc) conversion LibGdx render a sprite on top of a isometric tile
Our restart button is only appearing by itself. Our two tanks disappear when we run the game. We haven't coded the function of the button, we're just trying to figure out how to make the button appear in the lower left corner
We moved the code into the game object class, but it does not change anything
This is our game object class. The button is at the bottom.
import java.awt.Graphics;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class GameObject {
// Every moving object in our game is a separate GameObject. However, since there are different types of moving objects (eg. players and
// projectiles), each with their own variables and functions, we don't want to instantiate GameObjects (otherwise, we wouldn't be able
// to distinguish between a player and a projectile). Instead, we want to create derived (also known as child) classes to implement the
// separate functionality, and instantiate the derived/child classes instead. The purpose of this GameObject "parent" class is to
// provide the derived/child classes with a set of useful functions for handling physics interactions (eg. collisions with the terrain
// or the walls of the arena) between different GameObjects (eg. player/player, player/projectile, or even projectile/projectile). Note:
// depending on the features you implement, you may not end up using all of the functions provided in this class.
// Every GameObject contains its own velocity, position, size, and mass information (necessary for moving and handling physics
// interactions). We store this information in the GameObject class instead of the derived/child classes because every type of
// GameObject has the same set of velocity, position, size, and mass variables.
public double vX; // Velocity in the x direction. Positive if moving right. Negative if moving left.
public double vY; // Velocity in the y direction. Positive if moving down. Negative if moving up. Notice how it's flipped from the usual
// coordinate system in math!
public double posX; // Position along the x direction. Ranges from 0 (left edge) to maximumX (see below).
public double posY; // Position along the y direction. Ranges from 0 (top edge) to maximumY (see below).
public int width; // Width of the bounding box of the GameObject.
public int height; // Height of the bounding box of the GameObject.
public int mass; // Used in realistic physics collision calculations. Ignore it if you don't want to implement that feature.
public double radius; // Used for circular GameObjects only.
public int maximumX; // Maximum x position for a GameObject, equal to the arena width subtracted by the game object's width.
public int maximumY; // Maximum y position for a GameObject, equal to the arena height subtracted by the game object's height.
// Constructor. All derived (ie. child) classes call this constructor in their own constructors. The resulting derived/child class
// object can then call the other functions in this class.
public GameObject(int arenaWidth, int arenaHeight, double vX, double vY, double posX, double posY, int width, int height, int mass) {
this.vX = vX;
this.vY = vY;
this.posX = posX;
this.posY = posY;
this.width = width;
this.height = height;
this.mass = mass;
this.maximumX = arenaWidth - width;
this.maximumY = arenaHeight - height;
radius = Math.min(width, height) / 2.0;
}
// Note: No need to change this function since we're going to override it in the the child classes.
public boolean move(Map map, double translateX, double translateY) {
return false;
}
// Check if the calling GameObject currently intersects with the obj GameObject.
public boolean currentlyIntersects(GameObject obj) {
return (posX + width >= obj.posX && posY + height >= obj.posY && obj.posX + obj.width >= posX && obj.posY + obj.height >= posY);
}
// Check if the calling GameObject will intersect with the obj GameObject, after both have moved according to their velocities. A note
// of caution: what might go wrong if either player moves too fast?
public boolean willIntersect(GameObject obj) {
double nextX = posX + vX;
double nextY = posY + vY;
double nextObjX = obj.posX + obj.vX;
double nextObjY = obj.posY + obj.vY;
return (nextX + width >= nextObjX && nextY + height >= nextObjY && nextObjX + obj.width >= nextX && nextObjY + obj.height >= nextY);
}
// Clip the calling GameObject to within the arena's x bounds, if it has moved outside the arena along the x direction.
public boolean xClip() {
if (posX < 0) {
posX = 0;
return true;
} else if (posX > maximumX) {
posX = maximumX;
return true;
}
return false;
}
// Clip the calling GameObject to within the arena's y bounds, if it has moved outside the arena along the y direction.
public boolean yClip() {
if (posY < 0) {
posY = 0;
return true;
} else if (posY > maximumY) {
posY = maximumY;
return true;
}
return false;
}
// If the calling GameObject will move outside the arena along either direction (after moving according to its velocity), this function
// tells you which of the four edges of the arena it hit. If the calling GameObject will stay within the bounds of the arena, this
// function returns null.
public Direction hitEdgeDirection() {
if (posX + vX < 0) {
return Direction.LEFT;
} else if (posX + vX > maximumX) {
return Direction.RIGHT;
}
if (posY + vY < 0) {
return Direction.UP;
} else if (posY + vY > maximumY) {
return Direction.DOWN;
} else {
return null;
}
}
// If the calling GameObject will intersect with the "other" GameObject (after both move according to their velocities), this function
// tells you which of the four sides of the calling GameObject that the "other" GameObject hit. If the calling GameObject will not
// intersect with the "other" GameObject, this function returns null. Note: this function is great for figuring out when and where two
// rectangles intersect, but is it a good choice for handling circle/rectangle or circle/circle intersections?
public Direction hitObjectDirection(GameObject other) {
if (this.willIntersect(other)) {
double dx = other.posX + other.width / 2.0 + other.vX - (posX + width / 2.0 + vX);
double dy = other.posY + other.height / 2.0 + other.vY - (posY + height / 2.0 + vY);
double theta = Math.acos(dx / (Math.sqrt(dx * dx + dy * dy)));
double diagTheta = Math.atan2(height / 2.0, width / 2.0);
if (theta <= diagTheta) {
return Direction.RIGHT;
} else if (theta <= Math.PI - diagTheta) {
if (dy > 0) {
return Direction.DOWN;
} else {
return Direction.UP;
}
} else {
return Direction.LEFT;
}
} else {
return null;
}
}
// Change the calling GameObject's velocity (to simulate a "bouncing" effect) based on which direction it intersected another GameObject
// or the edge of the arena. If the passed in direction is null, this function does nothing (why is this a good idea?). This function is
// best used with the hitEdgeDirection and hitObjectDirection functions above.
public void bounce(Direction d) {
if (d == null) {
return;
}
// Note: We probably should use a "switch" statement here instead. But for pedagogical purposes it's left as a simple if/else
// conditional.
if (d == Direction.UP) {
vY = Math.abs(vY);
} else if (d == Direction.DOWN) {
vY = -Math.abs(vY);
} else if (d == Direction.LEFT) {
vX = Math.abs(vX);
} else if (d == Direction.RIGHT) {
vX = -Math.abs(vX);
}
}
// TODO: (Challenge!) If you want to implement realistic sphere-sphere collisions that take into account the laws of physics, do so in
// the function below.
public boolean bounceWith(GameObject otherObj, Map map, long frames, double[] actualVelocities) {
return false;
}
// Calculate the distance from (pointX, pointY)---perhaps representing the center of a circle---to the closest point on a rectangle
// bounded by minX (left), maxX (right), minY (top), and maxY (bottom). If the point is inside the rectangle, this function returns 0.
public double pointToRectSqrDist(double minX, double maxX, double minY, double maxY, double pointX, double pointY) {
double dx = Math.max(Math.max(minX - pointX, 0), pointX - maxX);
double dy = Math.max(Math.max(minY - pointY, 0), pointY - maxY);
return dx * dx + dy * dy;
}
// Rotate the point (x, y) "degrees" degrees around (centerX, centerY) in counterclockwise fashion, and return the resulting point in an
// array of length 2. If the returned array is "result", then (result[0], result[1]) is the final point.
public double[] rotatePoint(double centerX, double centerY, double degrees, double x, double y) {
double s = Math.sin(Math.toRadians(degrees));
double c = Math.cos(Math.toRadians(degrees));
x -= centerX;
y -= centerY;
double xNew = x * c - y * s;
double yNew = x * s + y * c;
double[] result = new double[2];
result[0] = xNew + centerX;
result[1] = yNew + centerY;
return result;
}
// Note: No need to change this function since we're going to override it in the the child classes.
public void draw(Graphics g) {
}
public static void main(String []args){
JButton b= new JButton("Reset");
JFrame f = new JFrame();
f.setSize(1200,800);
f.setVisible(true);
f.getDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel p = new JPanel();
f.add(p);
p.add(b);
b.setSize(50,50);
b.setVisible(true);
b.setLocation(50, 50);
}
}
This is our arena class:
// TODO: Feel free to import any other libraries that you need.
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import javax.swing.*;
#SuppressWarnings("serial")
public class Arena extends JPanel {
public int arenaWidth;
public int arenaHeight;
public Player player1;
public Player player2;
Timer timer;
public static int INTERVAL = 35;
public long lastTick;
// TODO: Add other variables to keep track of the game state or other game objects (eg. the map) that will be in your game. Don't forget
// to instantiate them in reset()!
// Constructor. Called inside Game.java for setting up the Arena on game start.
public Arena() {
// Create a timer that calls the tick() function every INTERVAL milliseconds. Every call of the tick() function is a "frame".
timer = new Timer(INTERVAL, new ActionListener() {
public void actionPerformed(ActionEvent e) {
tick();
}
});
lastTick = System.currentTimeMillis();
timer.start();
setFocusable(true);
// TODO: To recognize key presses, you need to fill in the following.
addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent butthole) {
if (butthole.getKeyCode() == KeyEvent.VK_W) {
player1.isWPressed = true;
}
if (butthole.getKeyCode() == KeyEvent.VK_S) {
player1.isSPressed = true;
}
if (butthole.getKeyCode() == KeyEvent.VK_A) {
player1.isAPressed = true;
}
if (butthole.getKeyCode() == KeyEvent.VK_D) {
player1.isDPressed = true;
}
if (butthole.getKeyCode() == KeyEvent.VK_SPACE) {
player1.isSpacePressed = true;
}
if (butthole.getKeyCode() == KeyEvent.VK_UP) {
player2.isUpPressed = true;
}
if (butthole.getKeyCode() == KeyEvent.VK_DOWN) {
player2.isDownPressed = true;
}
if (butthole.getKeyCode() == KeyEvent.VK_LEFT) {
player2.isLeftPressed = true;
}
if (butthole.getKeyCode() == KeyEvent.VK_RIGHT) {
player2.isRightPressed = true;
}
if (butthole.getKeyCode() == KeyEvent.VK_ENTER) {
player2.isEnterPressed = true;
}
}
public void keyReleased(KeyEvent butthole) {
if (butthole.getKeyCode() == KeyEvent.VK_W) {
player1.isWPressed = false;
}
if (butthole.getKeyCode() == KeyEvent.VK_S) {
player1.isSPressed = false;
}
if (butthole.getKeyCode() == KeyEvent.VK_A) {
player1.isAPressed = false;
}
if (butthole.getKeyCode() == KeyEvent.VK_D) {
player1.isDPressed = false;
}
if (butthole.getKeyCode() == KeyEvent.VK_SPACE) {
player1.isSpacePressed = false;
}
if (butthole.getKeyCode() == KeyEvent.VK_UP) {
player2.isUpPressed = false;
}
if (butthole.getKeyCode() == KeyEvent.VK_DOWN) {
player2.isDownPressed = false;
}
if (butthole.getKeyCode() == KeyEvent.VK_LEFT) {
player2.isLeftPressed = false;
}
if (butthole.getKeyCode() == KeyEvent.VK_RIGHT) {
player2.isRightPressed = false;
}
if (butthole.getKeyCode() == KeyEvent.VK_ENTER) {
player2.isEnterPressed = false;
}
}
});
}
// Resets the game to its initial state.
public void reset() {
this.removeAll();
this.setBackground(Color.WHITE);
this.setOpaque(true);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
arenaWidth = (int) screenSize.getWidth();
arenaHeight = (int) screenSize.getHeight();
player1 = new Player(arenaWidth, arenaHeight, 100, 100, 1);
player2 = new Player(arenaWidth, arenaHeight, arenaWidth - Player.INIT_SIZE - 100, arenaHeight - Player.INIT_SIZE - 100, 2);
requestFocusInWindow();
}
// Function called once per "frame".
void tick() {
// While tick() should be called once every INTERVAL amount of time, there's no guarantee of that, particularly if you have a lot
// of background apps running. Thus, we need to calculate the time difference (timeDelta) between every two calls of the tick()
// function. Note: 1 divided by this difference is commonly known as the "frames per second", or fps.
long currentTime = System.currentTimeMillis();
long timeDelta = (currentTime - lastTick)/35;
lastTick = currentTime;
if ((player1.isWPressed && player1.isSPressed) ||
(!player1.isWPressed && !player1.isSPressed)) {
player1.vX = 0;
player1.vY = 0;
} else if (player1.isWPressed) {
//System.out.println("Up");
player1.vX = Math.cos(player1.RotationDegree*Math.PI/180)*player1.speed * timeDelta;
player1.vY = Math.sin(player1.RotationDegree*Math.PI/180)*player1.speed * timeDelta;
//player1.posY -= player1.speed*timeDelta;
// MOVE FORWARD
} else if (player1.isSPressed) {
//System.out.println("Down");
player1.vX = -Math.cos(player1.RotationDegree*Math.PI/180)*player1.speed * timeDelta;
player1.vY = -Math.sin(player1.RotationDegree*Math.PI/180)*player1.speed * timeDelta;
//player1.posY += player1.speed*timeDelta;
// MOVE BACKWARD;
}
if (player1.isAPressed) {
//System.out.println("Left");
player1.RotationDegree -= player1.rotateSpeed* timeDelta;
// MOVE BACKWARD;
}
if (player1.isDPressed) {
//System.out.parintln("Right");
player1.RotationDegree += player1.rotateSpeed* timeDelta;
// MOVE BACKWARD;
}
if(player1.RotationDegree > 360) {
player1.RotationDegree -= 360;
}
else if(player1.RotationDegree < 0) {
player1.RotationDegree += 360;
}
player1.move(null, player1.vX, player1.vY);
if ((player2.isUpPressed && player2.isDownPressed) ||
(!player2.isUpPressed && !player2.isDownPressed)) {
player2.vX = 0;
player2.vY = 0; }
else if (player2.isUpPressed) {
//System.out.println("Up");
player2.vX = Math.cos(player2.RotationDegree*Math.PI/180)*player2.speed* timeDelta;
player2.vY = Math.sin(player2.RotationDegree*Math.PI/180)*player2.speed* timeDelta;
//player2.posY -= player2.speed*timeDelta;
// MOVE FORWARD
}
else if (player2.isDownPressed) {
//System.out.println("Down");
player2.vX = -Math.cos(player2.RotationDegree*Math.PI/180)*player2.speed * timeDelta;
player2.vY = -Math.sin(player2.RotationDegree*Math.PI/180)*player2.speed * timeDelta;
//player2.posY += player2.speed*timeDelta;
// MOVE BACKWARD;
}
if (player2.isLeftPressed) {
//System.out.println("Left");
player2.RotationDegree -= player2.rotateSpeed*timeDelta;
// MOVE BACKWARD;
}
if (player2.isRightPressed) {
//System.out.println("Right");
player2.RotationDegree += player2.rotateSpeed*timeDelta;
// MOVE BACKWARD;
}
if(player2.RotationDegree > 360) {
player2.RotationDegree -= 360;
}
else if(player2.RotationDegree < 0) {
player2.RotationDegree += 360;
}
player2.move(null, player2.vX, player2.vY);
player1.currentReload -= timeDelta;
if (player1.currentReload <= 0)
;
{
if (player1.isSpacePressed) {
// create bullet and fire
BasicWeapon newBullet = new BasicWeapon(arenaWidth, arenaHeight, player1.posX + player1.radius, player1.posY + player1.radius, player1);
player1.bullets.add(newBullet);
player1.currentReload = player1.MaxReload;
}
}
ArrayList<PlayerProjectile> bulletsToDelete1 = new ArrayList<PlayerProjectile>();
for (int i = 0; i < player1.bullets.size(); i++) {
PlayerProjectile bulletToChange = player1.bullets.get(i);
bulletsToDelete1.add(bulletToChange);
}
player1.bullets.removeAll(bulletsToDelete1);
// TODO: Update the game state each frame. This can be broken into the following steps:
// Step 1: Handle the keys pressed during the last frame by both players and calculate their resulting velocities/orientations.
// Step 2: Move the players and detect/handle player/player collisions and player/terrain collisions.
// Step 3: Decide whether a bullet should be fired for each player and create new bullet(s) if so. Also, handle reload mechanics.
// Step 4: Move all bullets via their calculated velocities (up to bullet range). Handle bullet/player & bullet/terrain collisions.
// Step 5: Decide whether the game has ended. If so, stop the timer and print a message to the screen indicating who's the winner.
// Note: If you implement other features (eg. weapon swapping, damage counters...etc.), you might also need to add more steps above.
// Update the display: this function calls paintComponent as part of its execution.
repaint(); }
// TODO: Draw all of the objects in your game.
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
//AffineTransform at = AffineTransoform.getTranslateInstance();
player1.draw(g);
player2.draw(g);
}
// Returns the dimensions of the Arena (for properly resizing the JPanel on the screen).
#Override
public Dimension getPreferredSize() {
return new Dimension(arenaWidth, arenaHeight);
}
}
THIS IS OUR PLAYER CLASS
// TODO: Feel free to import any other libraries that you need.
//import java.*;
import java.awt.*;
import java.util.ArrayList;
//import javax.swing.*;
//import java.awt.event.*;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Player extends GameObject {
public static void main(String []args){
}
// TODO: Set these values as you see fit. However, which variables should you not set as negative numbers? Which variables should you
// not set as zero? Which v'ariables should you not set as a very large positive number? Why?
public static final int INIT_SIZE = 30;
public static final int INIT_MASS = 0;
public static final int INIT_DAMAGE = 0;
public static final double INIT_SPEED = 10;
public static final double INIT_ROTATE_SPEED = 10;
public static final int INIT_HEALTH = 100;
// Member variables of the player that you can change over the course of the game.
public int damage = INIT_DAMAGE;
public double speed = INIT_SPEED;
public double rotateSpeed = INIT_ROTATE_SPEED;
public int health = INIT_HEALTH;
public double orientation = 0;
public int id;
// TODO: You may need to set up extra variables to store the projectiles fired by this player, the reload status/time of this player,
// the key press/release status of this player, and any other player-related features you decide to implement. Make sure to update the
// constructor appropriately as well!
long currentReload = 0;
long MaxReload = BasicWeapon.INIT_RELOAD;
ArrayList<PlayerProjectile> bullets = new ArrayList<PlayerProjectile>();
double RotationDegree = 0;
boolean isWPressed = false;
boolean isSPressed = false;
boolean isAPressed = false;
boolean isDPressed = false;
boolean isSpacePressed = false;
boolean isEnterPressed = false;
boolean isUpPressed = false;
boolean isDownPressed = false;
boolean isLeftPressed = false;
boolean isRightPressed = false;
boolean isLeftCLickPressed = false;
// Constructor that calls the super (ie. parent) class's constructor and instantiates any other player-specific variables.
public Player(int arenaWidth, int arenaHeight, double startPosX, double startPosY, int id) {
super(arenaWidth, arenaHeight, 0, 0, startPosX, startPosY, INIT_SIZE, INIT_SIZE, INIT_MASS);
this.id = id;
}
// TODO: This function should move the player and handle any player-terrain interactions.
#Override
public boolean move(Map map, double translateX, double translateY) {
posX += translateX;
posY += translateY;
xClip();
yClip();
return false;
}
//UPDATE PLAYER POSTION HERE
//}
public void draw(Graphics g) {
// TODO: Draw the barrel(s) for the player here
double xChords[] = new double[4];
double YChords[] = new double[4];
xChords[0] = posX + 0.6 * width;
xChords[1] = posX + 0.6 * width;
xChords[2] = posX + 1.5 * width;
xChords[3] = posX + 1.5 * width;
YChords[0] = posY + 0.4 * height;
YChords[1] = posY + 0.5 * height;
YChords[2] = posY + 0.5 * height;
YChords[3] = posY + 0.4 * height;
double[] point0 = rotatePoint(posX + 0.5 * width, posY + 0.5 * height, RotationDegree, xChords[0], YChords[0]);
double[] point1 = rotatePoint(posX + 0.5 * width, posY + 0.5 * height, RotationDegree, xChords[1], YChords[1]);
double[] point2 = rotatePoint(posX + 0.5 * width, posY + 0.5 * height, RotationDegree, xChords[2], YChords[2]);
double[] point3 = rotatePoint(posX + 0.5 * width, posY + 0.5 * height, RotationDegree, xChords[3], YChords[3]);
int rotatedPointsX[] = new int[4];
int rotatedPointsY[] = new int[4];
rotatedPointsX[0] = (int)Math.round(point0[0]);
rotatedPointsX[1] = (int)Math.round(point1[0]);
rotatedPointsX[2] = (int)Math.round(point2[0]);
rotatedPointsX[3] = (int)Math.round(point3[0]);
rotatedPointsY[0] = (int)Math.round(point0[1]);
rotatedPointsY[1] = (int)Math.round(point1[1]);
rotatedPointsY[2] = (int)Math.round(point2[1]);
rotatedPointsY[3] = (int)Math.round(point3[1]);
g.drawPolygon(rotatedPointsX, rotatedPointsY, 4);
g.setColor(Color.BLACK);
g.fillPolygon(rotatedPointsX, rotatedPointsY, 4);
if (id == 1) {
g.setColor(new Color(255, 215, 0));
} else if (id == 2) {
g.setColor(Color.RED);
}
// Body
g.fillOval((int) posX, (int) posY, width, height);
g.setColor(Color.BLACK);
g.drawOval((int) posX, (int) posY, width, height);
// TODO: Draw the health bar for the player here.
}
}
Only the button appears, and player1 and player2 don't.
I think what you are trying to achieve is something like :
public static void main(String []args){
JButton button= new JButton("Reset");
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.add(button);
frame.add(panel, BorderLayout.NORTH);
frame.add(new Arena(), BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
For future question please do not post so much code. See: don't just copy in your entire program!
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
I'm working on animation of moving balls, which must collide with walls and with each other.
Half the work is done. Balls already can collide with walls.
But I don't know how to make a collision of balls with each other. I tried using the loop throughout the ArrayList collection, but the balls are removed incorrectly.
Maybe you can help me with this task.
MainClass.java
import java.awt.EventQueue;
import javax.swing.Timer;
public class MainClass
{
public static Timer t;
public static void main(String[] args)
{
UI myUI = new UI();
EventQueue.invokeLater(myUI);
t = new Timer(10, myUI);
t.start();
}
}
UI.java.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
public class UI extends JFrame implements Runnable, ActionListener
{
private static final long serialVersionUID = 1L;
#Override
public void run()
{
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(600, 500);
setLocationRelativeTo(null);
setTitle("Bouncing Balls!");
add(new DrawingSurface());
setVisible(true);
}
#Override
public void actionPerformed(ActionEvent e)
{
repaint();
}
}
DrawingSurface.java
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JPanel;
public class DrawingSurface extends JPanel
{
private static final long serialVersionUID = 1L;
static ArrayList<BouncingBall> bList = new ArrayList<BouncingBall>();
private final int numBalls = 2;
Random r = new Random();
int rangeMin = 1, rangeMax = 3;
double randomXSpeed;
double randomYSpeed;
// create a list of balls in the constructor. This way it happens only one time
// rather than EVERY time you re-draw...
public DrawingSurface()
{
for(int i = 0; i < numBalls ; i++)
{
randomXSpeed = rangeMin + (rangeMax - rangeMin) * r.nextDouble();
randomYSpeed = rangeMin + (rangeMax - rangeMin) * r.nextDouble();
// Notice I delegate ALL of the ball functionality to the BouncingBall class.
// I don't want give it anything to create a new ball.
// The only place the balls exist is in the ArrayList.
bList.add(new BouncingBall(r.nextInt(400), r.nextInt(300), randomXSpeed, randomYSpeed));
}
}
public void paintComponent(Graphics gContext)
{
// loop through the array list and tell each ball the size of the window
// and give it the graphics context so it can draw itself.
for(int i = 0; i < bList.size(); i++)
{
bList.get(i).updatePosition(getWidth(), getHeight(), (Graphics2D)gContext);
}
}
}
BouncingBall.java
import java.awt.Color;
import java.awt.Graphics2D;
import javax.swing.JPanel;
// a class that manages the position of randomly colored ball, and draws it.
public class BouncingBall extends JPanel
{
private static final long serialVersionUID = 1L;
public int x,y;
Double xMove;
Double yMove;
public final static int size = 20; //size of the ball
private Color c;
public BouncingBall(int width, int height, Double xSpeed, Double ySpeed)
{
x = width; //starting position
y = height; //starting position
xMove = xSpeed; //starting velocity
yMove = ySpeed; //starting velocity
//pick a random color
c = new Color((int)(Math.random()*255),(int)(Math.random()*255),(int)(Math.random()*255));
}
//pass in the height and width of the current window so we can tell where we should bounce
public void updatePosition(int width, int height, Graphics2D g)
{
//update the position
y += yMove;
x += xMove;
Double xSpeed = 0.0, ySpeed = 0.0;
//if the ball moves to the right edge of the window, turn around.
if(x > width - size)
{
x = width - size;
xMove *= -1;
if (xMove > 0) {
xSpeed = xMove + (Math.random() * (1));
}
if (xMove <= 0) {
xSpeed = xMove - (Math.random() * (1));
}
if (yMove > 0) {
ySpeed = yMove + (Math.random() * (1));
}
if (yMove <= 0) {
ySpeed = yMove - (Math.random() * (1));
}
DrawingSurface.bList.add(new BouncingBall(x, y, xSpeed, ySpeed));
c = new Color((int)(Math.random()*255),(int)(Math.random()*255),(int)(Math.random()*255));
}
//if the ball moves to the left edge of the window, turn around.
if(x < 1)
{
x = 1;
xMove *= -1;
if (xMove > 0) {
xSpeed = xMove + (Math.random() * (1));
}
if (xMove <= 0) {
xSpeed = xMove - (Math.random() * (1));
}
if (yMove > 0) {
ySpeed = yMove + (Math.random() * (1));
}
if (yMove <= 0) {
ySpeed = yMove - (Math.random() * (1));
}
DrawingSurface.bList.add(new BouncingBall(x, y, xSpeed, ySpeed));
c = new Color((int)(Math.random()*255),(int)(Math.random()*255),(int)(Math.random()*255));
}
//if the ball moves to the bottom of the screen, turn around.
if(y > height - size )
{
y = height - size;
yMove *= -1;
if (xMove > 0) {
xSpeed = xMove + (Math.random() * (1));
}
if (xMove <= 0) {
xSpeed = xMove - (Math.random() * (1));
}
if (yMove > 0) {
ySpeed = yMove + (Math.random() * (1));
}
if (yMove <= 0) {
ySpeed = yMove - (Math.random() * (1));
}
DrawingSurface.bList.add(new BouncingBall(x, y, xSpeed, ySpeed));
c = new Color((int)(Math.random()*255),(int)(Math.random()*255),(int)(Math.random()*255));
}
//if the ball moves to the top of the screen, turn around.
if(y < 1)
{
y = 1;
yMove *= -1;
if (xMove > 0) {
xSpeed = xMove + (Math.random() * (1));
}
if (xMove <= 0) {
xSpeed = xMove - (Math.random() * (1));
}
if (yMove > 0) {
ySpeed = yMove + (Math.random() * (1));
}
if (yMove <= 0) {
ySpeed = yMove - (Math.random() * (1));
}
DrawingSurface.bList.add(new BouncingBall(x, y, xSpeed, ySpeed));
c = new Color((int)(Math.random()*255),(int)(Math.random()*255),(int)(Math.random()*255));
}
g.setColor(c);
g.fillOval(x, y, size, size);
}
}
To detect the collision of each ball with one another, a simple way will be looping through the collection of balls with a pair of nested loops. So each ball will be checked against all other balls for collision:
//From your list of balls
ArrayList<BouncingBall> bList = new ArrayList<BouncingBall>();
for(BouncingBall b1 : bList)
for(BouncingBall b2 : bList)
if(b1.intersects(b2)){
//do whatever (such as bouncing off) when the balls collide
b1.flipDirection();
b2.flipDirection();
}
In order to use intersects() method, your BouncingBall class can extends to Rectangle class from Java. Alternatively, if you can't let BouncingBall class be extended to another class.
You can let them return the bounds:
class BouncingBall{
public Rectangle getBounds(){
return new Rectangle(x, y, width ,height);
}
}
Then you will still be able to make use of intersects() method:
//to check for collision using intersects() method
for(BouncingBall b1 : bList)
for(BouncingBall b2 : bList)
if(b1.getBounds().intersects(b2.getBounds())){
//do whatever (such as bouncing off) when the balls collide
b1.flipDirection();
b2.flipDirection();
}
Using nested loop to detect collision for a simple ball animation like this is sufficient. Unless you are making a game where every frame you need to check the collision for thousands or more entities, then you will need a different collision detection algorithm such as Quad-tree.
I have coded such program using the same algorithm before, and it works perfectly well:
I'm doing this bouncing ball problem and I have was given this formula: (velocity) vx = v0*cos(angle). and (x-position) x = v0*cos(angle)*t. However, I cannot get the ball to bounce properly.
The problem is that after the ball hits the right vertical wall, it starts to bounce inside certain range on the right-hand-side of the window. (y and vy shouldn't matter in this case.)
How can I fix this weird bouncing problem to make it bounce property in the x direction?
public class GamePanel2 extends JPanel implements KeyListener, ActionListener{
Timer tm = new Timer(60, this); //this refers to the ActionListener
public int score = 0;
public GamePanel2(){
addKeyListener(this);
setFocusable(true);
setBackground(Color.BLACK);
}
public int getScore() {
return score;
}
public double v0 = 100;
public double t = 0;
public double angle = Math.PI/2.5;
public double x = 0;
public double y = 0;
public double vx =0;
public double vy = 0;
public int move = 0;
public int paddlex =0;
public void paintComponent(Graphics g){
int h = getHeight();
int w = getWidth();
vx = v0*Math.cos(angle);
vy = v0*Math.sin(angle);
Graphics2D g2d = (Graphics2D)g;
g2d.translate(0.0,h);
g2d.scale(1.0, -1.0);
//ball
g2d.setColor(Color.GREEN);
g2d.fillOval((int)Math.round(x), (int)Math.round(y+6), 20, 20);
//paddle
g2d.setColor(Color.RED);
g2d.fillRect(paddlex + move, 0, 60, 6);
repaint();
}
//KeyListener methods
#Override
public void keyPressed(KeyEvent arg0) {
if(arg0.getKeyCode() == KeyEvent.VK_SPACE){
tm.start();
}
else if(arg0.getKeyCode()==KeyEvent.VK_ESCAPE){
tm.stop();
}
if(arg0.getKeyCode() == KeyEvent.VK_RIGHT){
move += 30;
}
//if pressed right key
if(arg0.getKeyCode() == KeyEvent.VK_LEFT){
move -= 30;
}
repaint();
}
#Override
public void keyReleased(KeyEvent arg0) {
}
#Override
public void keyTyped(KeyEvent arg0) {
}
#Override
public void actionPerformed(ActionEvent arg0) {
t = 0.2;
vy -= 9.8;
x += vx;
y += (vy)*t-(t*t*9.8)*0.5;
if( x<= 0){
vx = v0*Math.cos(angle);
}
if (x>=getWidth()-20){
vx =-(v0*Math.cos(angle));
}
repaint();
}
}
You're not even close. The differential equations of motion for a ball with gravity supplying the only force are
d^2x/dt^2 = -9.8 and d^2x/dt^2 = 0
You need to integrate these equations. For this purpose, you need to get rid of the second degree differentials by introducing a new variable:
dv_y/dt = -9.8 and dv_x/dt = 0
dy/dt = v_y dx/dt = v_x
With Euler forward differences (the simplest possible integration method), this becomes:
v_y[i+i] = v_y[i] + h * -9.8
y[i+1] = y[i] + h * v_y[i]
v_x[i+1] = v_x[i] + h * 0 // x-velocity is constant!
x[i+1] = x[i] + h * v_x[i]
When the ball encounters a vertical wall with a perfectly elastic collision, the x velocity instantly changes sign. When it hits the floor or ceiling, the y velocity changes sign.
Your formula provides only the initial values of v_x and v_y. All x and y values after are results of the above Euler equations. In pseudocode it will look something like this:
// Initialize the velocity components.
vx = v0 * cos(theta)
vy = v0 * sin(theta)
// Initialize the position of the ball.
x = R // in the corner of the first quadrant
y = R
// Choose a time increment.
h = < a very small number of seconds >
// Start the clock.
t = 0
while (t < END_OF_SIMULATION) {
draw_ball(x,y)
x = x + h * vx;
y = y + h * vy;
vy = vy - h * 9.8;
// Check for bounces
// Assumes box has corners (0,0), (W,H)
if ((vx < 0 and x < r) or (vx > 0 && x > W-r)) x = -x;
if ((vy < 0 and y < r) or (vy > 0 && y > H-r)) y = -y;
t = t + h
}
Note that that 9.8 means that the units are meters and seconds. You need to scale pixels in the Java window and use a timer to get a realistic result.
To roughly simulate lossy collision, you can steal some velocity on every bounce:
x = -<a number a bit less than 1.0> * x and
y = -<a number a bit less than 1.0> * y
With these, the ball will slow down a bit every time it hits a wall.
I can't find where you're changing the angle after detecting a bounce. I also don't see bounds checking for all four sides of the windows the ball is in.
There's a related bug you might run into where there's a double bounce in a corner that leaves the ball outside the window after all the calculations are done. Think about ways to handle that case.
I'm working with tutorial from this site - "Fixed timestep" section.
Here's the code - http://pastebin.com/QaHgcLaR
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class GameLoopTest extends JFrame implements ActionListener
{
private GamePanel gamePanel = new GamePanel();
private JButton startButton = new JButton("Start");
private JButton quitButton = new JButton("Quit");
private JButton pauseButton = new JButton("Pause");
private boolean running = false;
private boolean paused = false;
private int fps = 60;
private int frameCount = 0;
public GameLoopTest()
{
super("Fixed Timestep Game Loop Test");
Container cp = getContentPane();
cp.setLayout(new BorderLayout());
JPanel p = new JPanel();
p.setLayout(new GridLayout(1,2));
p.add(startButton);
p.add(pauseButton);
p.add(quitButton);
cp.add(gamePanel, BorderLayout.CENTER);
cp.add(p, BorderLayout.SOUTH);
setSize(500, 500);
startButton.addActionListener(this);
quitButton.addActionListener(this);
pauseButton.addActionListener(this);
}
public static void main(String[] args)
{
GameLoopTest glt = new GameLoopTest();
glt.setVisible(true);
}
public void actionPerformed(ActionEvent e)
{
Object s = e.getSource();
if (s == startButton)
{
running = !running;
if (running)
{
startButton.setText("Stop");
runGameLoop();
}
else
{
startButton.setText("Start");
}
}
else if (s == pauseButton)
{
paused = !paused;
if (paused)
{
pauseButton.setText("Unpause");
}
else
{
pauseButton.setText("Pause");
}
}
else if (s == quitButton)
{
System.exit(0);
}
}
//Starts a new thread and runs the game loop in it.
public void runGameLoop()
{
Thread loop = new Thread()
{
public void run()
{
gameLoop();
}
};
loop.start();
}
//Only run this in another Thread!
private void gameLoop()
{
//This value would probably be stored elsewhere.
final double GAME_HERTZ = 30.0;
//Calculate how many ns each frame should take for our target game hertz.
final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
//At the very most we will update the game this many times before a new render.
//If you're worried about visual hitches more than perfect timing, set this to 1.
final int MAX_UPDATES_BEFORE_RENDER = 5;
//We will need the last update time.
double lastUpdateTime = System.nanoTime();
//Store the last time we rendered.
double lastRenderTime = System.nanoTime();
//If we are able to get as high as this FPS, don't render again.
final double TARGET_FPS = 60;
final double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS;
//Simple way of finding FPS.
int lastSecondTime = (int) (lastUpdateTime / 1000000000);
while (running)
{
double now = System.nanoTime();
int updateCount = 0;
if (!paused)
{
//Do as many game updates as we need to, potentially playing catchup.
while( now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER )
{
updateGame();
lastUpdateTime += TIME_BETWEEN_UPDATES;
updateCount++;
}
//If for some reason an update takes forever, we don't want to do an insane number of catchups.
//If you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
if ( now - lastUpdateTime > TIME_BETWEEN_UPDATES)
{
lastUpdateTime = now - TIME_BETWEEN_UPDATES;
}
//Render. To do so, we need to calculate interpolation for a smooth render.
float interpolation = Math.min(1.0f, (float) ((now - lastUpdateTime) / TIME_BETWEEN_UPDATES) );
drawGame(interpolation);
lastRenderTime = now;
//Update the frames we got.
int thisSecond = (int) (lastUpdateTime / 1000000000);
if (thisSecond > lastSecondTime)
{
System.out.println("NEW SECOND " + thisSecond + " " + frameCount);
fps = frameCount;
frameCount = 0;
lastSecondTime = thisSecond;
}
//Yield until it has been at least the target time between renders. This saves the CPU from hogging.
while ( now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES)
{
Thread.yield();
//This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it.
//You can remove this line and it will still work (better), your CPU just climbs on certain OSes.
//FYI on some OS's this can cause pretty bad stuttering. Scroll down and have a look at different peoples' solutions to this.
try {Thread.sleep(1);} catch(Exception e) {}
now = System.nanoTime();
}
}
}
}
private void updateGame()
{
gamePanel.update();
}
private void drawGame(float interpolation)
{
gamePanel.setInterpolation(interpolation);
gamePanel.repaint();
}
private class GamePanel extends JPanel
{
float interpolation;
float ballX, ballY, lastBallX, lastBallY;
int ballWidth, ballHeight;
float ballXVel, ballYVel;
float ballSpeed;
int lastDrawX, lastDrawY;
public GamePanel()
{
ballX = lastBallX = 100;
ballY = lastBallY = 100;
ballWidth = 25;
ballHeight = 25;
ballSpeed = 25;
ballXVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
ballYVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
}
public void setInterpolation(float interp)
{
interpolation = interp;
}
public void update()
{
lastBallX = ballX;
lastBallY = ballY;
ballX += ballXVel;
ballY += ballYVel;
if (ballX + ballWidth/2 >= getWidth())
{
ballXVel *= -1;
ballX = getWidth() - ballWidth/2;
ballYVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
}
else if (ballX - ballWidth/2 <= 0)
{
ballXVel *= -1;
ballX = ballWidth/2;
}
if (ballY + ballHeight/2 >= getHeight())
{
ballYVel *= -1;
ballY = getHeight() - ballHeight/2;
ballXVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
}
else if (ballY - ballHeight/2 <= 0)
{
ballYVel *= -1;
ballY = ballHeight/2;
}
}
public void paintComponent(Graphics g)
{
//BS way of clearing out the old rectangle to save CPU.
g.setColor(getBackground());
g.fillRect(lastDrawX-1, lastDrawY-1, ballWidth+2, ballHeight+2);
g.fillRect(5, 0, 75, 30);
g.setColor(Color.RED);
int drawX = (int) ((ballX - lastBallX) * interpolation + lastBallX - ballWidth/2);
int drawY = (int) ((ballY - lastBallY) * interpolation + lastBallY - ballHeight/2);
g.fillOval(drawX, drawY, ballWidth, ballHeight);
lastDrawX = drawX;
lastDrawY = drawY;
g.setColor(Color.BLACK);
g.drawString("FPS: " + fps, 5, 10);
frameCount++;
}
}
private class Ball
{
float x, y, lastX, lastY;
int width, height;
float xVelocity, yVelocity;
float speed;
public Ball()
{
width = (int) (Math.random() * 50 + 10);
height = (int) (Math.random() * 50 + 10);
x = (float) (Math.random() * (gamePanel.getWidth() - width) + width/2);
y = (float) (Math.random() * (gamePanel.getHeight() - height) + height/2);
lastX = x;
lastY = y;
xVelocity = (float) Math.random() * speed*2 - speed;
yVelocity = (float) Math.random() * speed*2 - speed;
}
public void update()
{
lastX = x;
lastY = y;
x += xVelocity;
y += yVelocity;
if (x + width/2 >= gamePanel.getWidth())
{
xVelocity *= -1;
x = gamePanel.getWidth() - width/2;
yVelocity = (float) Math.random() * speed*2 - speed;
}
else if (x - width/2 <= 0)
{
xVelocity *= -1;
x = width/2;
}
if (y + height/2 >= gamePanel.getHeight())
{
yVelocity *= -1;
y = gamePanel.getHeight() - height/2;
xVelocity = (float) Math.random() * speed*2 - speed;
}
else if (y - height/2 <= 0)
{
yVelocity *= -1;
y = height/2;
}
}
public void draw(Graphics g)
{
}
}
}
After run this code, the ball has kind of lag, but there is still 60 FPS. After I move mouse over application's window and move it in random directions, the ball is moving smoothly. It happens even if window application isn't focused! What's wrong? Can it be fixed?
I'm using Ubuntu 13.04 with Oracle JDK7.
I've found that it happens with every application. Similar things happens even in LWJGL application, but effect of the "lag" is much less than in swing application.
17 sec video showing my problem
http://www.youtube.com/watch?v=J8SBjKncgRw
I had same problem under Kubuntu 13.04
I googled something which works for me: http://www.java-gaming.org/index.php?topic=19224.0
The basic idea is to put Toolkit.getDefaultToolkit().sync(); after drawing something. In your code it should be after drawGame(interpolation);.
The explanation seems to be that the window system manages the update intervals, so it is not Java's fault and only occures with some window mangers.
The repaints should be triggered from a Swing based Timer, which ensures that GUI updates are called on the EDT. See Concurrency in Swing for more details.
This appears to be a bug in the VM since Java 6. I had the same problem and found an ugly, but simple workaround: Create a Robot object and let it press a key or position the mouse in each cycle. The animation will then run smoothly. Example:
final Robot robot = new Robot();
javax.swing.Timer timer = new javax.swing.Timer(initialDelay, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
// update your image...
robot.keyPress(62);
}
});
See also: Java animation stutters when not moving mouse cursor
You could invoke setIgnoreRepaint(true) and use a BufferStrategy for drawing that particular component instead. A BufferStrategy allows you to perform the drawing whenever you want. You can also invoke paintComponent methods inside your drawing method.