Eclipse Game Lags Too Much - java

I am making a game and tutorials for how to make it on youtube. Here is the link to the channel. I explain the first part of what I have and why I have it because I know that is helpful for filling you in.
Link to part 1(Then watch the rest of parts. #Chris, this is helpful for solving the problem so don't flag the post).
https://www.youtube.com/watch?v=IRn_ZGhJZ94
I noticed as I was testing out my code for part 4. before recording, the game lagged HORRIBLY. I have alot of code, and any help is appreciated.
Game class:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;
#SuppressWarnings("serial")
public class Game extends JPanel implements ActionListener{
Timer mainTimer;
Paddle paddle;
Ball ball;
int blockCount = 16;
static ArrayList<Block> blocks = new ArrayList<Block>();
public Game() {
setFocusable(true);
paddle = new Paddle(250, 300);
addKeyListener(new KeyAdapt(paddle));
ball = new Ball(275, 280);
mainTimer = new Timer(10, this);
mainTimer.start();
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/background.png");
g2d.drawImage(ic.getImage(), 0, 0, null);
paddle.draw(g2d);
ball.draw(g2d);
for(int i = 0; i < blockCount; i++) {
Block b = blocks.get(i);
b.draw(g2d);
}
}
#Override
public void actionPerformed(ActionEvent arg0) {
paddle.update();
ball.update();
for(int i = 0; i < blocks.size(); i++) {
Block b = blocks.get(i);
b.update();
}
repaint();
startGame();
}
public void addBlock(Block b) {
blocks.add(b);
}
public static void removeBlock(Block b) {
blocks.remove(b);
}
public static ArrayList<Block> getBlockList() {
return blocks;
}
public void startGame() {
for(int i = 0; i < blockCount; i++) {
addBlock(new Block(i*60 + 7, 20));
addBlock(new Block(i*60 + 7, 0));
}
}
}
Main class(The frame part):
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame("Game");
frame.setSize(500, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new Game());
frame.setResizable(false);
frame.setVisible(true);
}
}
Key Adapt class:
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
public class KeyAdapt extends KeyAdapter{
Paddle p;
public KeyAdapt(Paddle paddle) {
p = paddle;
}
public void keyPressed(KeyEvent e) {
p.keyPressed(e);
}
public void keyReleased(KeyEvent e) {
p.keyReleased(e);
}
}
Paddle class:
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import javax.swing.ImageIcon;
public class Paddle {
int velX;
int speed = 3;
static int x1, y1;
public Paddle(int x1, int y1) {
this.x1 = x1;
this.y1 = y1;
}
public void update() {
x1+=velX;
checkCollisions();
}
public void draw(Graphics2D g2d) {
g2d.drawImage(getPaddleImg(), x1, y1, null);
}
public static Image getPaddleImg() {
ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/paddle.png");
return ic.getImage();
}
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if(key==KeyEvent.VK_D) {
velX = speed;
} else if(key==KeyEvent.VK_A){
velX = -speed;
}
}
public void keyReleased(KeyEvent e) {
int key = e.getKeyCode();
if(key==KeyEvent.VK_D) {
velX = 0;
} else if(key==KeyEvent.VK_A){
velX = 0;
}
}
public void checkCollisions() {
if(getBounds().getX() + getBounds().getWidth() >= 500) {
x1 = 440;
} else if(getBounds().getX() <= 0) {
x1 = 0;
}
}
public static Rectangle getBounds() {
return new Rectangle(x1, y1 - 1, getPaddleImg().getWidth(null), getPaddleImg().getHeight(null));
}
}
Ball class:
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
public class Ball {
int velX;
int velY;
int speed = 3;
int x, y;
public Ball(int x, int y) {
this.x = x;
this.y = y;
}
public void update() {
x+=velX;
y+=velY;
checkCollisions();
}
public void draw(Graphics2D g2d) {
g2d.drawImage(getBallImg(), x, y, null);
}
public Image getBallImg() {
ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/ball.png");
return ic.getImage();
}
public void checkCollisions() {
for(int i = 0; i < Game.getBlockList().size(); i++) {
Block b = Game.getBlockList().get(i);
if(getBounds().intersects(b.getBounds()) && velX!=-speed) {
velY=speed;
velX =- speed;
Game.removeBlock(b);
}
else if(getBounds().intersects(b.getBounds())) {
velY=speed;
velX = speed;
Game.removeBlock(b);
}
}
if(getBounds().intersects(Paddle.getBounds())) {
velY = -speed;
} else if (getBounds().getY() <= 0 && velX!=speed) {
velY = speed;
velX =- speed;
}else if (getBounds().getY() <= 0 && velX!=-speed) {
velY = speed;
velX = speed;
} else if(getBounds().getY() >= 400) {
JOptionPane.showMessageDialog(null, "You Lost! :( ");
System.exit(0);
}
if(getBounds().getX() <= 0) {
velX = speed;
} else if(getBounds().getX() >= 500 - getBounds().getWidth()) {
velX = -speed;
}
}
public Rectangle getBounds() {
return new Rectangle(x, y, getBallImg().getWidth(null), getBallImg().getHeight(null));
}
}
Block class:
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import javax.swing.ImageIcon;
public class Block {
int x2, y2;
public Block(int x2, int y2) {
this.x2 = x2;
this.y2 = y2;
}
public void update() {
}
public void draw(Graphics2D g2d){
g2d.drawImage(getBlockImg(), x2, y2, null);
}
public static Image getBlockImg() {
ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/block.png");
return ic.getImage();
}
public Rectangle getBounds() {
return new Rectangle(x2, y2, getBlockImg().getWidth(null), getBlockImg().getHeight(null));
}
}
I also have a folder called Eclipse Game on my desktop and I refer to it in my code.
Again, I understand this is alot but any idea with making it lag less is helpful. Also, watching the tutorial (look at the beginning for the link) on making what I have finished so far will help make it less confusing for you to understand how the code works. The game seriously lags so much I cannot play.

There are multiple issues.
The first, as I already mentioned in my comment, is that you're calling startGame() inside your timer action listener:
#Override
public void actionPerformed(ActionEvent arg0) {
paddle.update();
ball.update();
for(int i = 0; i < blocks.size(); i++) {
Block b = blocks.get(i);
b.update();
}
repaint();
startGame();
}
This is adding 3,200 blocks every second to the game, so you don't want that. I think the simplest place to put startGame() is at the end of the game constructor:
public Game() {
setFocusable(true);
paddle = new Paddle(250, 300);
addKeyListener(new KeyAdapt(paddle));
ball = new Ball(275, 280);
mainTimer = new Timer(10, this);
mainTimer.start();
startGame();
}
The other really big problem is that you're constantly reloading the images all the time. For example, look at this snippet:
if(getBounds().intersects(b.getBounds()) && velX!=-speed) {
velY=speed;
velX =- speed;
Game.removeBlock(b);
}
else if(getBounds().intersects(b.getBounds())) {
velY=speed;
velX = speed;
Game.removeBlock(b);
}
That is 4 calls to getBounds(), and if we take a look at that:
return new Rectangle(x2, y2, getBlockImg().getWidth(null), getBlockImg().getHeight(null));
You are loading 2 images which in total is 4*2*blockCount images every 10ms, just for this one method. Instead of loading images all the time, do something like this:
class GameResources {
static Image ballImage;
static Image paddleImage;
static Image blockImage;
// call GameResources.loadResources() at the
// beginning of main() or something
static void loadResources() {
// load all 3 images once here and be done
ballImage = ...;
paddleImage = ...;
blockImage = ...;
}
Then finally, you have an issue with removing items from the list while iterating over it, Ball.checkCollisions:
for(int i = 0; i < Game.getBlockList().size(); i++) {
Block b = Game.getBlockList().get(i);
if(getBounds().intersects(b.getBounds()) && velX!=-speed) {
velY=speed;
velX =- speed;
// removeBlock changes blocks.size()
Game.removeBlock(b);
}
else if(getBounds().intersects(b.getBounds())) {
velY=speed;
velX = speed;
// removeBlock changes blocks.size()
Game.removeBlock(b);
}
}
Instead you need to do something like this:
Iterator<Block> iter = Game.getBlockList().iterator();
while (it.hasNext()) {
Block b = it.next();
if(getBounds().intersects(b.getBounds()) && velX!=-speed) {
velY=speed;
velX =- speed;
// safely removing
it.remove();
}
else if(getBounds().intersects(b.getBounds())) {
velY=speed;
velX = speed;
// safely removing
it.remove();
}
}
And another possible boundary issue in Game.paint:
// using blockCount after possibly
// removing items from the list
// vvvvvvvvvv
for(int i = 0; i < blockCount; i++) {
Block b = blocks.get(i);
b.draw(g2d);
}
For simple iterations like this, you should use for-each:
for(Block b : blocks) {
b.draw(g2d);
}
After all of that the game runs pretty smoothly, except for some type of issue with the key listener which I didn't have time to figure out. I might look at it again after dinner.
edit:
I noticed a lot of other small things, so here is the program fixed up a bit more with my comments.
Some of the classes aren't public anymore just because I had them all in one source file.
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JOptionPane;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyAdapter;
import java.util.ArrayList;
import java.util.Iterator;
import java.awt.Rectangle;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Dimension;
import java.net.URL;
import javax.imageio.ImageIO;
import java.io.IOException;
import java.io.File;
public class BlockGame {
public static void main(String[] args) {
// Swing program should always begin on the Swing
// thread with a call to invokeLater.
// See https://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
// change this to
// .loadImages();
GameResources.loadInternetImages();
} catch (IOException x) {
x.printStackTrace();
return;
}
JFrame frame = new JFrame("Game");
// frame.setSize(500, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// frame.add(new Game());
// Instead of calling setSize on the JFrame
// directly, set a preferred size on the game
// panel, then call pack() on the JFrame
Game game = new Game();
game.setPreferredSize(new Dimension(500, 400));
frame.add(game);
frame.pack();
frame.setResizable(false);
frame.setVisible(true);
// I started the game here instead
// of in the game loop, so the panel
// is visible and stuff beforehand.
game.startGame();
}
});
}
}
class Game extends JPanel implements ActionListener {
Timer mainTimer;
Paddle paddle;
Ball ball;
// I removed this because it's only ever
// used by startGame.
// int blockCount = 16;
// I changed this to an instance variable
// (not static) and passed the game in to
// update so the game objects can access
// it.
ArrayList<Block> blocks = new ArrayList<Block>();
public Game() {
setFocusable(true);
paddle = new Paddle(250, 300);
addKeyListener(new KeyAdapt(paddle));
ball = new Ball(275, 280);
mainTimer = new Timer(10, this);
// I moved this to the startGame() method
// mainTimer.start();
}
// Swing programs should override paintComponent
// instead of paint.
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// You should create a copy instead of
// directly using the graphics object which
// the component uses.
// This is so any changes you make to it
// don't affect the Swing paint routines.
Graphics2D g2d = (Graphics2D) g.create();
// ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/background.png");
// g2d.drawImage(ic.getImage(), 0, 0, null);
// Painting static resource.
g2d.drawImage(GameResources.backgroundImage, 0, 0, null);
paddle.draw(g2d);
ball.draw(g2d);
// This loop will throw an out of bounds
// exception once the first block is removed.
// vvvvvvvvvv
// for(int i = 0; i < blockCount; i++) {
// Block b = blocks.get(i);
// b.draw(g2d);
// }
// using for each
for (Block b : blocks) {
b.draw(g2d);
}
// Dispose the copied graphics when you're done.
g2d.dispose();
}
#Override
public void actionPerformed(ActionEvent arg0) {
paddle.update(this);
ball.update(this);
// for(int i = 0; i < blocks.size(); i++) {
// Block b = blocks.get(i);
// b.update();
// }
for (Block b : blocks) {
b.update(this);
}
repaint();
// I moved this to main
// startGame();
}
public void addBlock(Block b) {
blocks.add(b);
}
public void removeBlock(Block b) {
blocks.remove(b);
}
public ArrayList<Block> getBlockList() {
return blocks;
}
// I added this method so that the
// ball can access the paddle without
// static variables.
public Paddle getPaddle() {
return paddle;
}
public void startGame() {
// So the method won't be called twice
// and put the game in some unexpected
// state.
if (mainTimer.isRunning()) {
throw new IllegalStateException("game already started");
}
int initialBlockCount = 16;
for(int i = 0; i < initialBlockCount; i++) {
addBlock(new Block(i*60 + 7, 20));
addBlock(new Block(i*60 + 7, 0));
}
mainTimer.start();
}
}
// Generally speaking you should use
// Swing key bindings now, instead of
// key listeners.
//
// Key listeners have problems with
// the focus system: Swing components
// only send out key events when they
// have the focus.
//
// Key bindings don't have this issue.
//
// You can set up key bindings so they
// trigger any time the key is pressed
// in the focused window.
//
// https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html
//
class KeyAdapt extends KeyAdapter {
Paddle p;
public KeyAdapt(Paddle paddle) {
p = paddle;
}
public void keyPressed(KeyEvent e) {
p.keyPressed(e);
}
public void keyReleased(KeyEvent e) {
p.keyReleased(e);
}
}
class Paddle {
int velX;
int speed = 3;
// I changed these from static
// to instance variables.
int x1, y1;
// I added these variables to
// help with the key listener
// logic.
boolean leftPressed, rightPressed;
public Paddle(int x1, int y1) {
this.x1 = x1;
this.y1 = y1;
}
public void update(Game game) {
x1 += velX;
checkCollisions();
}
public void draw(Graphics2D g2d) {
g2d.drawImage(GameResources.paddleImage, x1, y1, null);
}
// public static Image getPaddleImg() {
// ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/paddle.png");
// return ic.getImage();
// }
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
// This logic is a little more robust
// because it handles cases where both
// keys are being held at the same time.
// Also see computeVelX().
if (key == KeyEvent.VK_D) {
leftPressed = true;
// velX = speed;
} else if (key == KeyEvent.VK_A) {
rightPressed = true;
// velX = -speed;
}
computeVelX();
}
public void keyReleased(KeyEvent e) {
int key = e.getKeyCode();
// This logic is a little more robust
// because it handles cases where both
// keys are being held at the same time.
// Also see computeVelX().
if (key == KeyEvent.VK_D) {
leftPressed = false;
// velX = 0;
} else if (key == KeyEvent.VK_A) {
rightPressed = false;
// velX = 0;
}
computeVelX();
}
public void computeVelX() {
// This way the keys will never
// "stick". If both keys are
// held at the same time, velX
// is just 0 until one of the
// keys is released.
velX = 0;
if (leftPressed) {
velX += speed;
}
if (rightPressed) {
velX -= speed;
}
}
public void checkCollisions() {
// I used a variable instead of calling
// getBounds() repeatedly.
Rectangle bounds = getBounds();
if (bounds.getX() + bounds.getWidth() >= 500) {
x1 = 440;
} else if (bounds.getX() <= 0) {
x1 = 0;
}
}
// I change this from static to an instance method.
public Rectangle getBounds() {
// return new Rectangle(x1, y1 - 1, getPaddleImg().getWidth(null), getPaddleImg().getHeight(null));
int width = GameResources.paddleImage.getWidth(null);
int height = GameResources.paddleImage.getHeight(null);
return new Rectangle(x1, y1 - 1, width, height);
}
}
class Ball {
int velX;
int velY;
int speed = 3;
int x, y;
public Ball(int x, int y) {
this.x = x;
this.y = y;
}
public void update(Game game) {
x += velX;
y += velY;
checkCollisions(game);
}
public void draw(Graphics2D g2d) {
// g2d.drawImage(getBallImg(), x, y, null);
g2d.drawImage(GameResources.ballImage, x, y, null);
}
// public Image getBallImg() {
// ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/ball.png");
// return ic.getImage();
// }
public void checkCollisions(Game game) {
// Using an iterator instead of looping with size()
// directly, because we want to remove items from
// the list while iterating.
// The problem with removing while iterating with
// size() is that once you remove an element, the
// list shifts all the other elements back by 1,
// so on the next iteration of the loop you end
// up skipping an item.
// (Say you remove the element at index 5. Then
// all the elements shift back, so that e.g. the
// element at index 6 is now at index 5. The variable
// i is incremented, so you end up skipping the element
// that was at index 6 before the removal.
Iterator<Block> iter = game.getBlockList().iterator();
Rectangle bounds = getBounds();
while (iter.hasNext()) {
Block b = iter.next();
Rectangle bBounds = b.getBounds();
if (bounds.intersects(bBounds) && velX != -speed) {
velY = speed;
velX =- speed;
// Game.removeBlock(b);
iter.remove();
} else if (bounds.intersects(bBounds)) {
velY = speed;
velX = speed;
// Game.removeBlock(b);
iter.remove();
}
}
//
Rectangle pBounds = game.getPaddle().getBounds();
if (bounds.intersects(pBounds)) {
velY = -speed;
} else if (bounds.getY() <= 0 && velX != speed) {
velY = speed;
velX =- speed;
} else if (bounds.getY() <= 0 && velX != -speed) {
velY = speed;
velX = speed;
} else if (bounds.getY() >= 400) {
JOptionPane.showMessageDialog(null, "You Lost! :( ");
System.exit(0);
}
if (bounds.getX() <= 0) {
velX = speed;
} else if(bounds.getX() >= 500 - bounds.getWidth()) {
velX = -speed;
}
}
public Rectangle getBounds() {
// return new Rectangle(x, y, getBallImg().getWidth(null), getBallImg().getHeight(null));
int width = GameResources.ballImage.getWidth(null);
int height = GameResources.ballImage.getHeight(null);
return new Rectangle(x, y, width, height);
}
}
class Block {
int x2, y2;
public Block(int x2, int y2) {
this.x2 = x2;
this.y2 = y2;
}
public void update(Game game) {
}
public void draw(Graphics2D g2d){
// g2d.drawImage(getBlockImg(), x2, y2, null);
g2d.drawImage(GameResources.blockImage, x2, y2, null);
}
// public static Image getBlockImg() {
// ImageIcon ic = new ImageIcon("C:/Users/Elliot/Desktop/Eclipse Game/block.png");
// return ic.getImage();
// }
public Rectangle getBounds() {
// return new Rectangle(x2, y2, getBlockImg().getWidth(null), getBlockImg().getHeight(null));
int width = GameResources.blockImage.getWidth(null);
int height = GameResources.blockImage.getHeight(null);
return new Rectangle(x2, y2, width, height);
}
}
class GameResources {
public static Image backgroundImage;
public static Image blockImage;
public static Image ballImage;
public static Image paddleImage;
public static void loadImages() throws IOException {
// Load images once here.
// I didn't test this method since I don't have the images, but it
// should work. ImageIO.read will give better error messages than
// using ImageIcon. ImageIcon.getImage() will just return null if
// there was a problem, which doesn't tell you what the problem
// actually was.
paddleImage =
ImageIO.read(new File("C:/Users/Elliot/Desktop/Eclipse Game/paddle.png"));
ballImage =
ImageIO.read(new File("C:/Users/Elliot/Desktop/Eclipse Game/ball.png"));
blockImage =
ImageIO.read(new File("C:/Users/Elliot/Desktop/Eclipse Game/block.png"));
backgroundImage =
ImageIO.read(new File("C:/Users/Elliot/Desktop/Eclipse Game/background.png"));
}
public static void loadInternetImages() throws IOException {
// These images are from
// http://stackoverflow.com/questions/19209650/example-images-for-code-and-mark-up-qas
paddleImage =
ImageIO.read(new URL("http://i.stack.imgur.com/gYxHm.png"));
ballImage =
ImageIO.read(new URL("http://i.stack.imgur.com/gJmeJ.png"));
blockImage =
ImageIO.read(new URL("http://i.stack.imgur.com/F0JHK.png"));
backgroundImage =
ImageIO.read(new URL("http://i.stack.imgur.com/P59NF.png"));
}
}

Related

Collision detection only working on top side of wall - Java

I have an assignment to create a 2D game, this game must use an abstract class Shape which is used to draw shapes. I decided to make a 2D platformer, but it's my first time doing something like this. I am trying to implement collision detection, and decided to create an offset of my player object to see if it collides with my rectangle object, and stop movement if it does. This is only working for the top side, however on the right, left and botton the player will move through the rectangle and I don't understand why. I expected the collision detection to work for all sides. I would like to know how I can change my collision detection to work for all sides of the rectangle.
Here's a gif showing what happens:
My App class:
package A2;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Rectangle2D;
import java.util.TreeSet;
import static java.awt.event.KeyEvent.*;
public class App extends JFrame {
private static final int GRAVITY = 10;
private static final int MAX_FALL_SPEED = 30;
public App() {
final Player player = new Player(200, 300);
final Rectangle rectangle = new Rectangle(100, 400);
JPanel mainPanel = new JPanel() {
public void paintComponent(Graphics g) {
super.paintComponent(g);
player.draw(g);
rectangle.draw(g);
}
};
mainPanel.addKeyListener(new controlHandler(this, player, rectangle));
mainPanel.setFocusable(true);
add(mainPanel);
setLayout(new GridLayout());
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setSize(600, 600);
}
class controlHandler implements ActionListener, KeyListener {
int keyCode;
App app;
Player player;
Rectangle rectangle;
TreeSet<Integer> keys = new TreeSet<>();
Timer time = new Timer(5, this);
boolean collision = false;
public controlHandler(App app, Player player, Rectangle rectangle) {
this.app = app;
this.player = player;
this.rectangle = rectangle;
time.start();
}
public void actionPerformed(ActionEvent e) {
player.x += player.xVelocity;
player.y += player.yVelocity;
Rectangle2D offset = player.getOffsetBounds();
if(offset.intersects(this.rectangle.wallObj.getBounds2D())){
collision = true;
player.xVelocity = 0;
player.yVelocity = 0;
}
else collision = false;
repaint();
}
public void keyPressed(KeyEvent e) {
keyCode = e.getKeyCode();
keys.add(keyCode);
switch (keyCode) {
case VK_UP:
moveUp();
break;
case VK_DOWN:
moveDown();
break;
case VK_LEFT:
moveLeft();
break;
case VK_RIGHT:
moveRight();
break;
}
}
public void keyReleased(KeyEvent e) {
released();
}
public void keyTyped(KeyEvent e) { }
public void moveUp() {
player.xVelocity = 0;
player.yVelocity = -2;
}
public void moveDown() {
if(!collision) {
player.xVelocity = 0;
player.yVelocity = 2;
}
}
public void moveLeft() {
player.xVelocity = -2;
player.yVelocity = 0;
}
public void moveRight() {
player.xVelocity = 2;
player.yVelocity = 0;
}
public void released() {
player.xVelocity = 0;
player.yVelocity = 0;
}
}
public static void main(String[] args) {
App app = new App();
app.setVisible(true);
}
}
abstract class Shape {
public double x;
public double y;
public void draw(Graphics g) { }
}
Player class:
package A2;
import java.awt.*;
import java.awt.geom.Rectangle2D;
public class Player extends Shape {
public double xVelocity;
public double yVelocity;
public double length = 60;
public double top;
public double right;
public double left;
public double bottom;
public Rectangle2D playerObj;
public Player(double x, double y) {
this.x = x;
this.y = y;
playerObj = new Rectangle2D.Double(x, y, length, length);
}
public Rectangle2D getOffsetBounds(){
return new Rectangle2D.Double(x + xVelocity , y + yVelocity , length, length);
}
public void draw(Graphics g) {
playerObj = new Rectangle2D.Double(x, y, length, length);
g.setColor(Color.BLACK);
Graphics2D g2 = (Graphics2D)g;
g2.fill(playerObj);
}
}
Rectangle class (will be a platform):
package A2;
import java.awt.*;
import java.awt.geom.Rectangle2D;
public class Rectangle extends Shape {
public double width = 400;
public double height = 30;
public double top;
public double right;
public double left;
public double bottom;
public Rectangle2D wallObj;
public Rectangle(double x, double y) {
this.x = x;
this.y = y;
wallObj = new Rectangle2D.Double(x, y, width, height);
}
public void draw(Graphics g) {
g.setColor(Color.ORANGE);
Graphics2D g2 = (Graphics2D)g;
g2.fill(wallObj);
}
}
So, based on my understanding, your collision detection process is basically to create a "virtual" instance of the object and determine if it will collide.
Your code is actually work, you can tell by the fact that the object "stops" moving once it collides, but what you're not doing, is reseting the objects position after it.
Let's look at the original code...
public void actionPerformed(ActionEvent e) {
player.x += player.xVelocity;
player.y += player.yVelocity;
Rectangle2D offset = player.getOffsetBounds();
if (offset.intersects(this.rectangle.wallObj.getBounds2D())) {
collision = true;
player.xVelocity = 0;
player.yVelocity = 0;
} else {
collision = false;
}
repaint();
}
Update the position of the object
Create a virtual instance of the object, which applies the velocity to the objects current position ... again?
Determine if the object collides
Stop the movement if any
... where do you reset the position of object so it's no longer colliding?!?
Instead, you shouldn't set the position of the object until AFTER you've determine if a collision has occurred or not, for example...
public void actionPerformed(ActionEvent e) {
Rectangle2D offset = player.getOffsetBounds();
if (offset.intersects(this.rectangle.wallObj.getBounds2D())) {
collision = true;
player.xVelocity = 0;
player.yVelocity = 0;
} else {
player.x += player.xVelocity;
player.y += player.yVelocity;
collision = false;
}
repaint();
}
The problem with this approach, though, is if the velocity is large enough, the object will appear not to collide, but stop a little before it, as the amount of change is larger than the remaining gap.
What you need to do, is once you've detected a collision is calculate the difference between the amount you want to move and the space available and move the object only by that amount instead

How do I rotate a 2D rectangle in Java by pressing keys?

I am currently attempting to make my first simple java game. I've followed a certain Youtube tutorial up until this point but would like to add my own features, one of which is being able to rotate the player by pressing a certain key. I have been looking up on how to do this for a while now but after numerous failed attempts would be grateful if anyone could suggest how I should do this.
Here is my player class where I have tried rotating the player by implementing a KeyListener:
package topDownGame;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import javax.swing.Timer;
public class Player extends GameObject implements KeyListener{
private Handler handler;
private HUD hud;
public float rotation = 0;
public Player(int x, int y, ID id, Handler handler, HUD hud) {
super(x, y, id);
this.handler = handler;
this.hud = hud;
}
public void tick() {
x += velX;
y += velY;
x = Game.clamp(x, 0, Game.WIDTH - 38);
y = Game.clamp(y, 0, Game.HEIGHT - 67);
collision();
}
public void render(Graphics g) {
//g.setColor(Color.WHITE);
//g.fillRect(x, y, 32, 32);
Graphics2D g2d = (Graphics2D)g;
Rectangle r = new Rectangle(x, y, 32, 32);
Path2D.Double path = new Path2D.Double();
path.append(r, false);
AffineTransform t = new AffineTransform();
t.rotate(rotation);
path.transform(t);
g2d.setColor(Color.WHITE);
g2d.draw(path);
}
public void collision() {
for (int i = 0; i < handler.object.size(); i++) {
GameObject tempObject = handler.object.get(i);
if (tempObject.getId() == ID.BasicEnemy) {
if (getBounds().intersects(tempObject.getBounds())) {
hud.HEALTH -= 2;
}
}
}
}
public Rectangle getBounds() {
return new Rectangle(x, y, 32, 32);
}
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
for (int i = 0; i < handler.object.size(); i++) {
GameObject tempObject = handler.object.get(i);
if (tempObject.getId() == ID.Player) {
if (key == KeyEvent.VK_E) {
rotation = (float) (rotation + 0.1);
}
}
}
}
#Override
public void keyReleased(KeyEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void keyTyped(KeyEvent arg0) {
// TODO Auto-generated method stub
}
}
Below is some of my remaining code that may be important
Game class:
package topDownGame;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferStrategy;
public class Game extends Canvas implements Runnable{
/**
*
*/
private static final long serialVersionUID = 1744439430685015162L;
public static final int WIDTH = 640, HEIGHT = WIDTH / 12*9;
private boolean running = false;
private Thread thread;
private Handler handler;
public Game() {
handler = new Handler();
this.addKeyListener(new KeyInput(handler));
new Window(WIDTH, HEIGHT, "Game", this);
handler.addObject(new Player(200, 200, ID.Player, handler, hud));
}
public synchronized void start() {
running = true;
thread = new Thread(this);
thread.start();
}
public synchronized void stop() {
try{
running = false;
thread.join();
}catch(Exception e) {
e.printStackTrace();
}
}
public void run() {
this.requestFocus();
long lastTime = System.nanoTime();
double delta = 0.0;
double amountOfTicks = 60.0;
double ns = 1000000000/amountOfTicks;
long timer = System.currentTimeMillis();
int frames = 0;
while(running) {
long now = System.nanoTime();
delta += (now-lastTime)/ns;
lastTime = now;
while(delta >= 1) {
delta--;
tick();
}
if (running) {
frames++;
render();
}
if (System.currentTimeMillis() - timer > 1000) {
timer += 1000;
System.out.println("FPS: " + frames);
frames = 0;
}
}
stop();
}
public void tick() {
handler.tick();
}
public void render() {
BufferStrategy bs = this.getBufferStrategy();
if (bs == null) {
this.createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, WIDTH, HEIGHT);
handler.render(g);
g.dispose();
bs.show();
}
public static int clamp(int var, int min, int max) {
if (var <= min) {
var = min;
}
if (var >= max) {
var = max;
}
return var;
}
public static void main(String args[]) {
new Game();
}
}
Window class:
package topDownGame;
import java.awt.Canvas;
import java.awt.Dimension;
import javax.swing.JFrame;
public class Window extends Canvas{
/**
*
*/
private static final long serialVersionUID = -8646632868321067448L;
public Window(int width, int height, String title, Game game) {
JFrame jframe = new JFrame(title);
jframe.setMaximumSize(new Dimension(width, height));
jframe.setMinimumSize(new Dimension(width, height));
jframe.setPreferredSize(new Dimension(width, height));
jframe.setVisible(true);
jframe.setDefaultCloseOperation(jframe.EXIT_ON_CLOSE);
jframe.setResizable(false);
jframe.setLocationRelativeTo(null);
jframe.add(game);
game.start();
}
}
GameObject class:
package topDownGame;
import java.awt.Graphics;
import java.awt.Rectangle;
public abstract class GameObject {
protected int x, y;
protected ID id;
protected int velX, velY;
public GameObject(int x, int y, ID id) {
this.x = x;
this.y = y;
this.id = id;
}
public abstract void tick();
public abstract void render(Graphics g);
public abstract Rectangle getBounds();
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public ID getId() {
return id;
}
public void setId(ID id) {
this.id = id;
}
public int getVelX() {
return velX;
}
public void setVelX(int velX) {
this.velX = velX;
}
public int getVelY() {
return velY;
}
public void setVelY(int velY) {
this.velY = velY;
}
}
Handler class:
package topDownGame;
import java.awt.Graphics;
import java.util.LinkedList;
public class Handler {
LinkedList <GameObject> object = new LinkedList <GameObject>();
public void tick() {
for (int i = 0; i < object.size(); i++) {
GameObject tempObject = object.get(i);
tempObject.tick();
}
}
public void render(Graphics g) {
for (int i = 0; i < object.size(); i++) {
GameObject tempObject = object.get(i);
tempObject.render(g);
}
}
public void addObject(GameObject object) {
this.object.add(object);
}
public void removeObject(GameObject object) {
this.object.remove(object);
}
public void addObject(int x, int y, ID basicenemy) {
// TODO Auto-generated method stub
}
}
KeyInput class:
package topDownGame;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
public class KeyInput extends KeyAdapter{
private Handler handler;
public KeyInput(Handler handler) {
this.handler = handler;
}
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
for (int i = 0; i < handler.object.size(); i++) {
GameObject tempObject = handler.object.get(i);
if (tempObject.getId() == ID.Player) {
if (key == KeyEvent.VK_W) {
tempObject.setVelY(-5);
}
if (key == KeyEvent.VK_S) {
tempObject.setVelY(5);
}
if (key == KeyEvent.VK_A) {
tempObject.setVelX(-5);
}
if (key == KeyEvent.VK_D) {
tempObject.setVelX(5);
}
}
}
if (key == KeyEvent.VK_SPACE) {
System.exit(1);
}
}
public void keyReleased(KeyEvent e) {
int key = e.getKeyCode();
for (int i = 0; i < handler.object.size(); i++) {
GameObject tempObject = handler.object.get(i);
if (tempObject.getId() == ID.Player) {
if (key == KeyEvent.VK_W) {
tempObject.setVelY(0);
}
if (key == KeyEvent.VK_S) {
tempObject.setVelY(0);
}
if (key == KeyEvent.VK_A) {
tempObject.setVelX(0);
}
if (key == KeyEvent.VK_D) {
tempObject.setVelX(0);
}
}
}
}
}
ID enum:
package topDownGame;
public enum ID {
Player();
}
Okay, so I've dug through the example and the "basic" problem you're having is nothing is calling the Player's keyPressed/Released methods - the fact is, nothing should.
The intention is, the "input" should be decoupled from the entities and the entities should update their state based on the current state of the game engine.
So, the first thing I would do is "generalise" the available "input operations" which can occur (and to which the game model can respond)
public enum InputAction {
UP, DOWN, LEFT, RIGHT, ROTATE;
}
That's it. These are the inputs that the game supports and the entities can use. They are decoupled from "how" they might occur and just provide a means.
Now, to support this idea, we actually need someway to tell the entities that they should "update", this should be done just before they are rendered, but since we're trying to decouple these operations (so the objects could be updated more often then they are rendered for example), we need to provide a new method that performs this operation...
public abstract class GameObject {
//...
public void update() {
}
//...
}
(nb: technically this method could be abstract, as almost all the entities are doing to need to change in someway, but for simplicity, I've just made it a empty implementation)
Next, we need some way for the entities to respond to these input actions and some way to manage them, in your case the Handler is probably the best choice, as it provides a link between the entities and the other aspects of the system (like rendering and input control)
public class Handler {
//...
private Set<InputAction> inputActions = new HashSet<InputAction>();
public void render(Graphics g) {
for (int i = 0; i < object.size(); i++) {
GameObject tempObject = object.get(i);
tempObject.update();
tempObject.render(g);
}
}
public boolean is(InputAction action) {
return inputActions.contains(action);
}
public void set(InputAction action) {
inputActions.add(action);
}
public void remove(InputAction action) {
inputActions.remove(action);
}
//...
}
Okay, now the "input mechanism" can tell the Handler when the state has changed, based on it's implementation...
public class KeyInput extends KeyAdapter {
private Handler handler;
public KeyInput(Handler handler) {
this.handler = handler;
}
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_W) {
handler.set(InputAction.UP);
}
if (key == KeyEvent.VK_S) {
handler.set(InputAction.DOWN);
}
if (key == KeyEvent.VK_A) {
handler.set(InputAction.LEFT);
}
if (key == KeyEvent.VK_D) {
handler.set(InputAction.RIGHT);
}
if (key == KeyEvent.VK_E) {
handler.set(InputAction.ROTATE);
}
}
public void keyReleased(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_W) {
handler.remove(InputAction.UP);
}
if (key == KeyEvent.VK_S) {
handler.remove(InputAction.DOWN);
}
if (key == KeyEvent.VK_A) {
handler.remove(InputAction.LEFT);
}
if (key == KeyEvent.VK_D) {
handler.remove(InputAction.RIGHT);
}
if (key == KeyEvent.VK_E) {
handler.remove(InputAction.ROTATE);
}
}
}
(Yes, they could be if-else if statements, but I'm just modifying the existing code for brevity)
And finally, we need to update the Player object so it can "update" it's state based on the current "state" of the game engine...
public class Player extends GameObject {
private Handler handler;
public float rotation = 0;
public Player(int x, int y, ID id, Handler handler) {//, HUD hud) {
super(x, y, id);
this.handler = handler;
}
#Override
public void update() {
if (handler.is(InputAction.UP)) {
setVelY(-5);
} else if (handler.is(InputAction.DOWN)) {
setVelY(5);
} else {
setVelY(0);
}
if (handler.is(InputAction.LEFT)) {
setVelX(-5);
} else if (handler.is(InputAction.RIGHT)) {
setVelX(5);
} else {
setVelX(0);
}
if (handler.is(InputAction.ROTATE)) {
rotation += 0.1;
}
}
public void tick() {
x += velX;
y += velY;
x = Game.clamp(x, 0, Game.WIDTH - 38);
y = Game.clamp(y, 0, Game.HEIGHT - 67);
collision();
}
public void render(Graphics g) {
//g.setColor(Color.WHITE);
//g.fillRect(x, y, 32, 32);
Graphics2D g2d = (Graphics2D) g.create();
Rectangle r = new Rectangle(0, 0, 32, 32);
Path2D.Double path = new Path2D.Double();
path.append(r, false);
AffineTransform t = new AffineTransform();
t.translate(x, y);
t.rotate(rotation, 16, 16);
path.transform(t);
g2d.setColor(Color.WHITE);
g2d.draw(path);
g2d.dispose();
}
public void collision() {
for (int i = 0; i < handler.object.size(); i++) {
GameObject tempObject = handler.object.get(i);
// if (tempObject.getId() == ID.BasicEnemy) {
// if (getBounds().intersects(tempObject.getBounds())) {
// hud.HEALTH -= 2;
// }
// }
}
}
public Rectangle getBounds() {
return new Rectangle(x, y, 32, 32);
}
}
I want to take a closer look at the render method, as it's a little more complicated...
public void render(Graphics g) {
// 1...
Graphics2D g2d = (Graphics2D) g.create();
// 2...
Rectangle r = new Rectangle(0, 0, 32, 32);
Path2D.Double path = new Path2D.Double();
path.append(r, false);
AffineTransform t = new AffineTransform();
// 3...
t.translate(x, y);
// 4...
t.rotate(rotation, 16, 16);
path.transform(t);
g2d.setColor(Color.WHITE);
g2d.draw(path);
// 5...
g2d.dispose();
}
Okay:
Graphics is a shard concept, that means that EVERY entity that needs to be painted will get the same Graphics context, including any and all changes which have been made to it by previous entities. This "might" be desirable, but in general, you want to reduce the amount of "side effects" which might occur. So, we create a new copy of it first.
We create the Rectangle. Oddly, this is (now) a bad place to do it here, because it's state actually never changes. The Rectangle is always created at position 0x0 and has a size of 32x32 ... but wait, I want it to move and do stuff! I know, you will see "how" in ...
We translate the origin of the Graphics context to the position of the player ... this now makes the 0x0 position, the same as the players position 😱🤯. This is a neat cheat and means, as I stated above, you no longer need to create a Rectangle EVERY time render is called, which will further improve performance
We rotate the Graphics context around the centre point of the object (the object been 32x32 makes the centre point 16x16 - remember, the origin point is 0x0 ... do you see why that little change is SO important and useful 😉)
We dispose of the copy. This just releases any resources held by this copy, the action we've taken are still applied back to the original, but don't affect any operations which might occur after (so the origin point and rotation are the same as they were when render was first called).
Observations...
While working my way through the code, it become clear that the code was not well organised. One thing that really annoyed me was the fact that Game would create an instance of Window in order to display itself - this is actually a side effect and something that Game shouldn't be doing (it shouldn't care).
So, I took your main method and wrangled it to be...
public static void main(String args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame jframe = new JFrame("Game");
Game game = new Game();
jframe.setDefaultCloseOperation(jframe.EXIT_ON_CLOSE);
jframe.add(game);
jframe.pack();
jframe.setLocationRelativeTo(null);
jframe.setVisible(true);
game.start();
}
});
}
So, a number of small changes...
The instance of game is added to the frame almost straight away (this is important for another change I made)
The frame is "packed" around the component
The frame's location is set (so it appears in the middle of the screen), this is done AFTER packing the window, because the size of the frame isn't set until we pack it.
The frame is made visible - this stops the window from "jumping" to the centre of the screen
I also added...
#Override
public Dimension getPreferredSize() {
return new Dimension(WIDTH, HEIGHT);
}
to Game, this provides sizing hints to the container that Game is added to. It also means that when the JFrame is packed, the window will slightly larger then the content area, as the frame's borders are wrapped around it.
I would also recommend taking a look at the JavaDocs for BufferStrategy as it has an example of "how" it should be used.
To that end, I modified you render method accordingly...
public void render() {
BufferStrategy bs = this.getBufferStrategy();
if (bs == null) {
this.createBufferStrategy(3);
return;
}
do {
do {
Graphics g = bs.getDrawGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, getWidth(), getHeight());
handler.render(g);
g.dispose();
} while (bs.contentsRestored());
bs.show();
} while (bs.contentsLost());
}
One significant change I did make was g.fillRect(0, 0, getWidth(), getHeight()); - now it will fill the "actual" size of the component, not just the "desired" size ... I'm one of those people who hate (passionately) non-resizable windows ;)
If you're interested in seeing a slightly more complex solution, you could take a look at this example which presents a more "generic" and "decoupled" concept

SnakeGame how to make the tail follow the head?

I am making a snake game, and I am stuck at where making the tails follow the head. And I heard using an add and remove on the head and tails could make that happen, but I have no idea where to start with that.
Here's my code so far:
Screen.java
public class Screen extends JPanel implements ActionListener, KeyListener {
public static final JLabel statusbar = new JLabel("Default");
public static final int WIDTH = 800, HEIGHT = 800;
Timer t = new Timer(100, this);
int x = 400;
int y = 400;
int size = 5; //increase size if eat
private boolean right = false, left = false, up = false, down = false;
int head = 0;
private LinkedList<BodyPart> snake = new LinkedList<BodyPart>();
private BodyPart b;
public Screen(){
initSnake();
t.start();
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
}
public void update(){
}
public void direction(){
if(right) x+=10;
if(left) x-=10;
if(up) y-=10;
if(down) y+=10;
}
public void trackOutBound(){
if(x < 0 || x > 800 || y < 0 || y > 800) {
x = 400;
y = 400;
}
}
public void initSnake(){
if(snake.size() == 0){
b = new BodyPart(x, y);
for(int i = 0; i < size; i++) {
snake.add(b);
}
System.out.println(snake);
}
}
public static void main(String[] args) {
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(new Color(10, 50, 0));
g.fillRect(0, 0, WIDTH, HEIGHT);
g.setColor(Color.BLACK);
for(int i = 0; i < WIDTH / 10; i++) {
g.drawLine(i * 10, 0, i * 10, HEIGHT);
}
for(int i = 0; i < HEIGHT / 10; i++) {
g.drawLine(0, i * 10, WIDTH, i * 10);
}
int tempx = 0, tempy = 0;
int temp = 0;
for(int i = 0; i < size; i++){
if(i == head) {
snake.get(i).x = x;
snake.get(i).y = y;
snake.get(i).draw(g);
g.setColor(Color.blue);
g.fillRect(x, y, 10, 10);
g.setColor(Color.white);
g.drawRect(x, y, 10, 10);
} else if(i > 0 && up) {
snake.get(i).x = x;
snake.get(i).y = y + temp;
snake.get(i).draw(g);
} else if(i > 0 && down) {
snake.get(i).x = x;
snake.get(i).y = y - temp;
snake.get(i).draw(g);
} else if(i > 0 && left) {
snake.get(i).x = x + temp;
snake.get(i).y = y;
snake.get(i).draw(g);
} else if(i > 0 && right) {
snake.get(i).x = x - temp;
snake.get(i).y = y;
snake.get(i).draw(g);
}
temp += 10;
}
/*
if(snake.size() == 5){
snake.add(b);
size += 1;
}
*/
}
#Override
public void actionPerformed(ActionEvent e) {
direction();
trackOutBound();
repaint();
// System.out.println(snake);
statusbar.setText("(" + x + " , " + y + ")");
}
#Override
public void keyTyped(KeyEvent e) {}
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if(key == KeyEvent.VK_RIGHT && !left) {
up = false;
down = false;
right = true;
}
if(key == KeyEvent.VK_LEFT && !right) {
up = false;
down = false;
left = true;
}
if(key == KeyEvent.VK_UP && !down) {
left = false;
right = false;
up = true;
}
if(key == KeyEvent.VK_DOWN && !up) {
left = false;
right = false;
down = true;
}
}
#Override
public void keyReleased(KeyEvent e) {}
}
BodyPart.java
public class BodyPart {
int x;
int y;
public BodyPart(int x, int y) {
this.x = x;
this.y = y;
}
public void draw(Graphics g) {
this.x = x;
this.y = y;
g.setColor(Color.red);
g.fillRect(x, y, 10, 10);
g.setColor(Color.white);
g.drawRect(x, y, 10, 10);
}
}
Frame.java
public class Frame extends JPanel {
private static JLabel statusbar = new JLabel("Default");
public void statusbar(){
statusbar = Screen.statusbar;
}
public static void main(String[] args) {
JFrame f = new JFrame();
Screen s = new Screen();
f.add(s);
f.add(statusbar, BorderLayout.SOUTH);
f.setSize(800, 800);
f.setVisible(true);
f.setLocationRelativeTo(null);
f.setResizable(false);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Now this code would only make the tails flip to the horizontal or vertical, is it possible to make the tails follow the head by using this code? or I need to change my code?
Thank you
The basic idea is, you need some kind of List which contains ALL the points of the snake. Conceptually, the List would contain virtual coordinates, that is 1x1 would represent a coordinate in virtual space, which presented a place on a virtual board (which would have some wide and height).
You could then translate that to the screen, so this would allow each part of the snake to be larger then a single pixel. So, if each part was 5x5 pixels, then 1x1 would actually be 5x5 in the screen.
Each time the snake moves, you add a new value to the head and remove the last value from tail (assuming it's not growing). When you needed to paint the snake, you would simply iterate over the List, painting each point of the snake.
The following is a simple example, which uses a LinkedList, which pushes a new Point onto the List, making a new head, and removing the last element (the tail) on each cycle.
Which basically boils down to...
snakeBody.removeLast();
snakeBody.push(new Point(xPos, yPos));
As a runnable concept
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.LinkedList;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Snake {
public static void main(String[] args) {
new Snake();
}
public Snake() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class TestPane extends JPanel {
public enum Direction {
UP, DOWN, LEFT, RIGHT
}
private int xPos, yPos;
private Direction direction = Direction.UP;
private LinkedList<Point> snakeBody = new LinkedList<>();
public TestPane() {
xPos = 100;
yPos = 100;
for (int index = 0; index < 50; index++) {
snakeBody.add(new Point(xPos, yPos));
}
bindKeyStrokeTo("up.pressed", KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), new MoveAction(Direction.UP));
bindKeyStrokeTo("down.pressed", KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), new MoveAction(Direction.DOWN));
bindKeyStrokeTo("left.pressed", KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), new MoveAction(Direction.LEFT));
bindKeyStrokeTo("right.pressed", KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), new MoveAction(Direction.RIGHT));
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
switch (direction) {
case UP:
yPos--;
break;
case DOWN:
yPos++;
break;
case LEFT:
xPos--;
break;
case RIGHT:
xPos++;
break;
}
if (yPos < 0) {
yPos--;
} else if (yPos > getHeight() - 1) {
yPos = getHeight() - 1;
}
if (xPos < 0) {
xPos--;
} else if (xPos > getWidth() - 1) {
xPos = getWidth() - 1;
}
snakeBody.removeLast();
snakeBody.push(new Point(xPos, yPos));
repaint();
}
});
timer.start();
}
public void bindKeyStrokeTo(String name, KeyStroke keyStroke, Action action) {
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(keyStroke, name);
am.put(name, action);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
for (Point p : snakeBody) {
g2d.drawLine(p.x, p.y, p.x, p.y);
}
g2d.dispose();
}
public class MoveAction extends AbstractAction {
private Direction moveIn;
public MoveAction(Direction direction) {
this.moveIn = direction;
}
#Override
public void actionPerformed(ActionEvent e) {
direction = this.moveIn;
}
}
}
}
Now, this has no collision detection or other functionality, but you can move the snake around and it will follow itself
For snake style movement, you can, from the tail to the head, move each BodyPart position to the position of the BodyPart ahead of it. For the head there is no part ahead so you have to write decision code whether to simply move the same direction as the part before it or a new direction based on input. Then update the screen.

bullets creation in a simple game

I am creating a simple game where shapes fall and the player shoots them, but I am having problems creating bullet at every click of the mouse. I have tried various logic with no help, so am just going to put the code up here so you guys can take a look at it and help me out.
The bullet I created is not been created on every click just one is created and it moves on every click which is wrong........I want one bullet to be created per click.
// My main class: mousework2
import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.awt.geom.*;
public class mousework2 extends JFrame
{
public static int Width = 300;
public static int Height = 400;
private JPanel p1;
private Image pixMage,gunMage;
public mousework2()
{
super("shoot-em-up");
this.setSize(Width, Height);
this.setResizable(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Dimension pos = Toolkit.getDefaultToolkit().getScreenSize();
int x = (pos.width - Width) / 2;
int y = (pos.height - Height) / 2;
this.setLocation(x, y);
p1 = new CreateImage();
this.add(p1);
this.getContentPane();
Thread t = new recMove(this);
t.start();
}
class recMove extends Thread
{
JFrame b;
public recMove(JFrame b)
{
this.b = b;
}
public void run()
{
while (true) {
b.repaint();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
}
}
}
class CreateImage extends JPanel implements MouseListener
{
ArrayList<fallShape> rect = new ArrayList<fallShape>();
int x_pos = mousework.Width / 2;
int y_pos = mousework.Height - 50;
int bx_pos = mousework.Width / 2;
int by_pos = mousework.Height;
int y_speed = -10;
boolean clicked;
public CreateImage()
{
for (int i = 0; i < 10; i++) {
rect.add(new fallShape(15, 15, rect));
}
Toolkit picx = Toolkit.getDefaultToolkit();
gunMage = picx.getImage("gunner.jpg");
gunMage = gunMage.getScaledInstance(200, -1, Image.SCALE_SMOOTH);
Toolkit pic = Toolkit.getDefaultToolkit();
pixMage = pic.getImage("ballfall3.jpg");
pixMage = pixMage.getScaledInstance(200, -1, Image.SCALE_SMOOTH);
addMouseListener(this);
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseMoved(MouseEvent e)
{
x_pos = e.getX() - 5;
}
});
}
public void mousePressed(MouseEvent e)
{
if (e.getButton() == 1) {
clicked = true;
}
}
public void mouseReleased(MouseEvent e)
{
if (e.getButton() == 1) {
clicked = false;
}
}
public void mouseExited(MouseEvent e)
{
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseClicked(MouseEvent e)
{
}
public void paint(Graphics g)
{
super.paint(g);
g.drawImage(pixMage, 0, 0, Width, Height, null);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.drawImage(gunMage,x_pos,y_pos,10,20,null);
if (clicked) {
by_pos += y_speed;
Shape bullet = new Rectangle2D.Float(bx_pos, by_pos, 3, 10);
g2.setColor(Color.BLACK);
g2.fill(bullet);
g2.draw(bullet);
}
g2.setColor(Color.RED);
for (fallShape b : rect) {
b.move();
g2.fill(b);
}
}
}
public static void main(String[] args)
{
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run()
{
new mousework2().setVisible(true);
}
});
}
}
And:
// My falling shapes class: fallShape
import java.awt.geom.*;
import java.util.*;
public class fallShape extends Rectangle2D.Float
{
public int x_speed, y_speed;
public int l, b;
public int height = mousework.Height;
public int width = mousework.Width;
public ArrayList<fallShape> fall;
public fallShape(int breadth, int length, ArrayList<fallShape> fall)
{
super((int) (Math.random() * (mousework.Width - 20) + 1), 0, breadth, length);
this.b = breadth;
this.l = length;
this.x_speed = (int) Math.random() * (10) + 1;
this.y_speed = (int) Math.random() * (10) + 1;
this.fall = fall;
}
public void move()
{
Rectangle2D rec = new Rectangle2D.Float(super.x, super.y, b, l);
for (fallShape f : fall) {
if (f != this && f.intersects(rec)) {
int rxspeed = x_speed;
int ryspeed = y_speed;
x_speed = f.x_speed;
y_speed = f.y_speed;
f.x_speed = rxspeed;
f.y_speed = ryspeed;
}
}
if (super.x < 0) {
super.x =+ super.x;
//super.y =+ super.y;
x_speed = Math.abs(x_speed);
}
if (super.x> mousework.Width - 30) {
super.x =+ super.x;
super.y =+ super.y;
x_speed =- Math.abs(x_speed);
}
if (super.y < 0) {
super.y = 0;
y_speed = Math.abs(y_speed);
}
super.x += x_speed;
super.y += y_speed;
}
}
if(clicked){
by_pos+=y_speed;
This code only draws the bullet when the mouse is down. This is because you are setting clicked to false in your mouseReleased method:
public void mouseReleased(MouseEvent e){
if(e.getButton()==1)
clicked=false;
}
If you were to remove the body of the mouseReleased method, your bullet would move properly.
However, say you wanted to have more than just one bullet. Currently, your paint method only draws one bullet at a time. To draw multiple bullets, you would need to create a list of the coordinates of the bullets, and add a new coordinate pair to the list whenever you click. Then, in the paint method, just update each position in a for loop.
ArrayList<Integer> by_poss = new ArrayList<>();
by_poss is the list of all the y-positions of your bullets.
public void mousePressed(MouseEvent e){
if(e.getButton() == 1)
by_poss.add(mousework.Height);
}
The mousePressed method adds a new "bullet", in the form of a y-position, to the coordinates.
public void mouseReleased(MouseEvent e){
//do nothing
}
Nothing needs to happen in the mouseReleased method.
//update the bullets
public void paint(Graphics g){
...
g2.setColor(Color.BLACK);
Shape bullet;
for(int i = 0; i < by_poss.size(); i++){
by_poss.set(i, by_poss.get(i) + y_speed); //move the bullet
bullet = new Rectangle2D.Float(bx_pos, by_poss.get(i), 3, 10);
g2.fill(bullet);
g2.draw(bullet);
}
...
}
The for loop in your paint method draws all the bullets, one by one, usin g the y-positions from the by_poss list.

creating many objects with collision properties. JAVA

I have a simple program with three rectangles: one that can move with the push of the arrow keys, and two that are already moving back and forth on their own.
When the 'player' rectangle and top red collide, the player driven rectangle gets put back to (0,0). When I try to collide the player rectangle with the bottom red rectangle, it does not have those collision properties and I have no idea why.
What am I missing?
import java.awt.*;//needed for graphics
import javax.swing.*;//needed for JFrame window
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class javaapplication23 extends JFrame implements KeyListener, ActionListener {
public static int x = 0;
public static int y = 0;
public static int x2 = 100;
public static int y2 = 100;
public javaapplication23() {//constructor for JPanel
add(new JP());
}//close Jpanel Contructor
public static void main(String[] args) {
javaapplication23 w = new javaapplication23();
w.setTitle("MIKE IS AWESOME");
w.setSize(Toolkit.getDefaultToolkit().getScreenSize());
w.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
w.setVisible(true);
w.addKeyListener(w);
}
public class JP extends JPanel {//start JPanel CLass
public JP() {
Container c = getContentPane();
c.setBackground(Color.white);//backgraund color can be changed
}
public void paint(Graphics g) {//opens paint method
super.paint(g);
player(g, x, y);
g.setColor(Color.RED);
enemylevel1(g, x2, y2);
Rectangle enemyblocks = new Rectangle(x2, y2, 25, 25);
Rectangle player = new Rectangle(x, y, 25, 25);
enemyblocks.contains(x2, y2);
player.contains(x, y);
if (player.getBounds().intersects(enemyblocks.getBounds())) {
x = 0;
y = 0;
}
pause(1);
repaint();
}//close paint method
}//close JPanel Class
public static void pause(int time) {
try //opens an exception handling statement
{
Thread.sleep(time);
} catch (InterruptedException e) {
} //captures the exception
}
public void actionPerformed(ActionEvent e) {
}
public void keyTyped(KeyEvent e) {
}
public void keyReleased(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == e.VK_RIGHT) {
x += 20;//global variable controlling right movement
repaint();
}
if (e.getKeyCode() == e.VK_LEFT) {
x -= 20;//global variable controlling left movement
repaint();
}
if (e.getKeyCode() == e.VK_UP) {
y -= 20;//global variable controlling up movement
repaint();
}
if (e.getKeyCode() == e.VK_DOWN) {
y += 20;//global variable controlling down movement
repaint();
}
}
public void player(Graphics g, int x, int y) {
g.fillRect(x, y, 30, 30);
}
public void enemylevel1(Graphics g, int x, int y) {
g.fillRect(x2, y2, 25, 25);
g.fillRect(x2, y2 + 100, 25, 25);
if (x2 < 200 && y2 == 100) {
x2 += 1;
}
if (x2 == 200 && y2 >= 100) {
y2 += 1;
}
if (x2 <= 200 && y2 >= 101) {
x2 -= 1;
}
if (x2 == 100 && y2 <= 101) {
y2 -= 1;
}
pause(10);
repaint();
}
}
Start by having a look at Working with Geometry, this will allow you to reduce much of the code complexity.
Basically, a enemy is just a Rectangle, Graphics2D can paint these without to much of an issue. What you need to do is create an instance which can also update it's position based on your needs
public class Enemy extends Rectangle {
private int xDelta;
public Enemy(int x, int y) {
super(x, y, 20, 20);
if (x == 0) {
xDelta = 1;
} else {
xDelta = -1;
}
}
public void update(Rectangle bounds) {
x += xDelta;
if (x < bounds.x) {
x = bounds.x;
xDelta *= -1;
} else if (x > bounds.x + bounds.width - width) {
x = bounds.x + bounds.width - width;
xDelta *= -1;
}
}
}
So, this creates a single unit of work, which is isolated from everything else and carries it's own logic with it. This makes updating it, painting and generally working with much simpler.
Next, you need to create a List of these
public class Bounce extends JPanel implements KeyListener, ActionListener {
private List<Enemy> enemies;
//...
public Bounce() {
enemies = new ArrayList<>(5);
int y = 100;
for (int index = 0; index < 5; index++) {
int x = (index % 2 == 0) ? 0 : 200;
Enemy enemy = new Enemy(x, y);
enemies.add(enemy);
y += 60;
}
This creates a List of Enemys which are distributed evenly within the container.
Now, we need to paint them....
#Override
protected void paintComponent(Graphics g) {//opens paint method
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g.create();
g2d.setColor(Color.RED);
for (Enemy enemy : enemies) {
g2d.fill(enemy);
}
}//close paint method
nb: General convention suggests that you should override paintComponent when you want to perform custom painting
But they don't move, that kind of sucks. So we need a way to, on a regular bases, update the position of the enemies...
First, we create a simple method which we can call to update the enemies, remember, they are capable of updating themselves, we just need to tell them when
public void updateState() {
Rectangle bounds = new Rectangle(20, 20, 200, 200);
for (Enemy enemy : enemies) {
enemy.update(bounds);
}
}
Remember, the Enemy is self contained, it knows how to update itself based on the constraints you have provided.
And now, we need to call this method on a regular bases...
javax.swing.Timer timer = new javax.swing.Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
updateState();
repaint();
}
});
timer.start();
Okay, this will schedule a callback every 40 milliseconds which will allow us to call the updateState method and repaint the component. This is neat because it won't block the Event Dispatching Thread (making our program look like it's hung) but which notifies us within the context of the EDT, making it safe to update the UI from within - WIN/WIN :)
Take a look at Concurrency in Swing and How to use Swing Timers for more details.
Okay, but that doesn't solve the collision...
The player is also a Rectangle, so why not use the same concept we have with the enemies...
public class Bounce extends JPanel implements KeyListener, ActionListener {
private List<Enemy> enemies;
private Rectangle player;
//...
public Bounce() {
player = new Rectangle(0, 0, 30, 30);
enemies = new ArrayList<>(5);
//...
}
#Override
protected void paintComponent(Graphics g) {//opens paint method
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
drawPlayer(g2d);
g2d.setColor(Color.RED);
for (Enemy enemy : enemies) {
g2d.fill(enemy);
if (player.intersects(enemy)) {
player.x = 0;
player.y = 0;
}
}
}//close paint method
public void drawPlayer(Graphics2D g) {
g.fill(player);
}
Which ends up with something like...
This allows you to add/remove enemies as you want and also change the way in which the enemies move, simply and easily
An my "awesome" test code...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Bounce extends JPanel implements KeyListener, ActionListener {
private List<Enemy> enemies;
private Rectangle player;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new Bounce());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public Bounce() {
player = new Rectangle(0, 0, 30, 30);
enemies = new ArrayList<>(5);
int y = 100;
for (int index = 0; index < 5; index++) {
int x = (index % 2 == 0) ? 0 : 200;
Enemy enemy = new Enemy(x, y);
enemies.add(enemy);
y += 60;
}
setBackground(Color.white);//backgraund color can be changed
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
updateState();
repaint();
}
});
timer.start();
setFocusable(true);
requestFocusInWindow();
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
requestFocusInWindow();
}
});
addKeyListener(this);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(240, 400);
}
#Override
protected void paintComponent(Graphics g) {//opens paint method
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
drawPlayer(g2d);
g2d.setColor(Color.RED);
for (Enemy enemy : enemies) {
g2d.fill(enemy);
if (player.intersects(enemy)) {
player.x = 0;
player.y = 0;
}
}
}//close paint method
public void actionPerformed(ActionEvent e) {
}
public void keyTyped(KeyEvent e) {
}
public void keyReleased(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == e.VK_RIGHT) {
player.x += 20;//global variable controlling right movement
}
if (e.getKeyCode() == e.VK_LEFT) {
player.x -= 20;//global variable controlling left movement
}
if (e.getKeyCode() == e.VK_UP) {
player.y -= 20;//global variable controlling up movement
}
if (e.getKeyCode() == e.VK_DOWN) {
player.y += 20;//global variable controlling down movement
}
}
public void drawPlayer(Graphics2D g) {
g.fill(player);
}
public void updateState() {
Rectangle bounds = new Rectangle(20, 20, 200, 200);
for (Enemy enemy : enemies) {
enemy.update(bounds);
}
}
public class Enemy extends Rectangle {
private int xDelta;
public Enemy(int x, int y) {
super(x, y, 20, 20);
if (x == 0) {
xDelta = 1;
} else {
xDelta = -1;
}
}
public void update(Rectangle bounds) {
x += xDelta;
if (x < bounds.x) {
x = bounds.x;
xDelta *= -1;
} else if (x > bounds.x + bounds.width - width) {
x = bounds.x + bounds.width - width;
xDelta *= -1;
}
}
}
}

Categories

Resources