Related
I am trying to create a snake clone just as a practice. ive drawn the snake and added the movement patterns but the snake eats on it self when I press any key to move. but its not moving. the array retracts the reactacles on the starting point and does nothing.
here is my snake class I have removed my comments as they where more than the code and the posting system was not allowing me to post
Edit
If you need anything from the other classes please let me know. but I think my error is somewhere in here
EDIT 2
Added the entire code, you can just copy paste in inside a new project and you will reproduce my error.
public class Snake {
List<Point> sPoints;
int xDir,yDir;
boolean isMoving,addTail;
final int sSize = 20, startX = 150 , startY = 150;
public Snake(){
sPoints = new ArrayList<Point>();
xDir = 0;
yDir = 0;
isMoving = false;
addTail = false;
sPoints.add(new Point(startX,startY));
for(int i=1; i<sSize; i++) {
sPoints.add(new Point(startX - i * 4,startY));
}
}
public void draw(Graphics g){
g.setColor(Color.white);
for(Point p : sPoints) {
g.fillRect(p.getX(),p.getY(),4,4);
}
}
public void move(){
if (isMoving) {
Point temp = sPoints.get(0);
Point last = sPoints.get(sPoints.size() - 1);
Point newstart = new Point(temp.getX() + xDir * 4, temp.getY() + yDir * 4);
for (int i = sPoints.size() - 1; i >= 1; i--) {
sPoints.set(i, sPoints.get(i - 1));
}
sPoints.set(0, newstart);
}
}
public int getxDir() {
return xDir;
}
public void setxDir(int x) {
this.xDir = xDir;
}
public int getyDir() {
return yDir;
}
public void setyDir(int y) {
this.yDir = yDir;
}
public int getX(){
return sPoints.get(0).getX();
}
public int getY(){
return sPoints.get(0).getY();
}
public boolean isMoving() {
return isMoving;
}
public void setIsMoving(boolean b) {
isMoving = b;
}
}
The following is the point class. just some getters setters for the points ,for those i used the IntelliJ to auto generate them.. (again i removed comments )
public class Point {
private int x,y;
public Point() {
x = 0;
y = 0;
}
public Point(int x, int y) {
this.x =x;
this.y =y;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
and finally my main class called game.
in here what I do is create my applet give it background color. create my threat for the runnable. and also add the movement patterns for up/right/down/left...
and use several classes to update my drawing patterns so it can simulate movement by updating each of state of my rect list.
import java.applet.Applet;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Game extends Applet implements Runnable, KeyListener {
//setting up double buffering.
Graphics graphics;
Image img;
Thread thread;
Snake snake;
public void init() {
//setting the size of our Applet
this.resize(400,400);
//we gonna create the image just the same size as our applet.
img = createImage(400,400);
//this represents our offscreen image that we will draw
graphics = img.getGraphics();
this.addKeyListener(this);
snake = new Snake();
thread = new Thread(this);
thread.start();
}
public void paint(Graphics g) {
//Setting the background of our applet to black
graphics.setColor(Color.black);
//Fill rectangle 0 , 0 (starts from) for top left corner and then 400,400 to fill our entire background to black
graphics.fillRect(0,0,400,400);
snake.draw(graphics);
//painting the entire image
g.drawImage(img,0,0,null);
}
//Update will call on Paint(g)
public void update(Graphics g){
paint(g);
}
//Repaint will call on Paint(g)
public void repaint(Graphics g){
paint(g);
}
public void run() {
//infinite loop
for(;;) {
snake.move();
//drawing snake
this.repaint();
//Creating a time delay
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void keyTyped(KeyEvent keyEvent) {
}
public void keyPressed(KeyEvent keyEvent) {
if(!snake.isMoving()){ //this will allow the snake to start moving, but will disable LEFT for just the 1st move
if(keyEvent.getKeyCode() == KeyEvent.VK_UP || keyEvent.getKeyCode() == KeyEvent.VK_RIGHT ||
keyEvent.getKeyCode() == KeyEvent.VK_DOWN ) {
snake.setIsMoving(true);
}
}
//setting up Key mapping so when the user presses UP,RIGHT,DOWN,LEFT. the Snake will move accordingly
if(keyEvent.getKeyCode() == KeyEvent.VK_UP) {
if (snake.getyDir() != 1) {
snake.setyDir(-1);
snake.setxDir(0);
}
}
if(keyEvent.getKeyCode() == KeyEvent.VK_RIGHT) {
if (snake.getxDir() != -1) {
snake.setxDir(1);
snake.setyDir(0);
}
}
if(keyEvent.getKeyCode() == KeyEvent.VK_DOWN) {
if (snake.getyDir() != -1) {
snake.setyDir(1);
snake.setxDir(0);
}
}
if(keyEvent.getKeyCode() == KeyEvent.VK_LEFT) {
if (snake.getxDir() != 1) {
snake.setxDir(-1);
snake.setyDir(0);
}
}
}
public void keyReleased(KeyEvent keyEvent) {
}
}
Here is some opinion I have reading your code.
The reason your snake won't move is because your snake.setyDir() and
snake.setxDir() didn't take the input to overwrite xDir and yDir. They are assigning to itself.
There is a Point2D class ready for you in JDK
When moving the snake, you just need to remove the tail and add one
more block before the head. You can keep the body tight (according
to my common knowledge to snake).
Consider a L shape snake on the left, the bottom end is the head and it is currently heading right. To move the snake, remove the tail (green block) and add one more to the head according to its direction (red block). It final state become the snake on the right. LinkedList suit the needs.
If using two int (xDir and yDir) to control the snake direction
is confusing, you can help your self by creating a enum. Those -1,
0, 1 with x and y may confuse you.
Declare constant instead of magic number. e.g. the width of block 4,
image size 400
Is Snake.addTail unnecessary?
Attribute should has accessibility modifier
End result:
Game.java
import java.applet.Applet;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Arrays;
public class Game extends Applet implements Runnable, KeyListener {
private final int GAMEBOARD_WIDTH = 400;
// setting up double buffering.
private Graphics graphics;
private Image img;
private Thread thread;
private Snake snake;
public void init() {
// setting the size of our Applet
this.resize(GAMEBOARD_WIDTH, GAMEBOARD_WIDTH);
// we gonna create the image just the same size as our applet.
img = createImage(GAMEBOARD_WIDTH, GAMEBOARD_WIDTH);
// this represents our offscreen image that we will draw
graphics = img.getGraphics();
this.addKeyListener(this);
snake = new Snake();
thread = new Thread(this);
thread.start();
}
public void paint(Graphics g) {
// Setting the background of our applet to black
graphics.setColor(Color.BLACK);
// Fill rectangle 0 , 0 (starts from) for top left corner and then 400,400 to
// fill our entire background to black
graphics.fillRect(0, 0, GAMEBOARD_WIDTH, GAMEBOARD_WIDTH);
snake.draw(graphics);
// painting the entire image
g.drawImage(img, 0, 0, null);
}
// Update will call on Paint(g)
public void update(Graphics g) {
paint(g);
}
// Repaint will call on Paint(g)
public void repaint(Graphics g) {
paint(g);
}
public void run() {
// infinite loop
for (;;) {
snake.move();
// drawing snake
this.repaint();
// Creating a time delay
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void keyTyped(KeyEvent keyEvent) {
}
public void keyPressed(KeyEvent keyEvent) {
int keyCode = keyEvent.getKeyCode();
if (!snake.isMoving()) {
// this will allow the snake to start moving, but will disable LEFT for just the
// 1st move
if (matchKey(keyCode, KeyEvent.VK_UP, KeyEvent.VK_RIGHT, KeyEvent.VK_DOWN)) {
snake.setIsMoving(true);
}
}
// setting up Key mapping so when the user presses UP,RIGHT,DOWN,LEFT. the Snake
// will move accordingly
if (matchKey(keyCode, KeyEvent.VK_UP)) {
snake.setDirection(Direction.UP);
}
if (matchKey(keyCode, KeyEvent.VK_RIGHT)) {
snake.setDirection(Direction.RIGHT);
}
if (matchKey(keyCode, KeyEvent.VK_DOWN)) {
snake.setDirection(Direction.DOWN);
}
if (matchKey(keyCode, KeyEvent.VK_LEFT)) {
snake.setDirection(Direction.LEFT);
}
}
// return true if targetKey contains the provided keyCode
private boolean matchKey(int keyCode, int... targetKey) {
return Arrays.stream(targetKey).anyMatch(i -> i == keyCode);
}
public void keyReleased(KeyEvent keyEvent) {
}
}
Snake.java
import java.awt.Color;
import java.awt.Graphics;
import java.awt.geom.Point2D;
import java.util.LinkedList;
public class Snake {
private final int sSize = 20, startX = 150, startY = 150;
private final int BLOCK_WIDTH = 4;
private LinkedList<Point2D.Float> sPoints;
private boolean isMoving;
private Direction direction;
public Snake() {
sPoints = new LinkedList<Point2D.Float>();
isMoving = false;
sPoints.add(new Point2D.Float(startX, startY));
for (int i = 1; i < sSize; i++) {
sPoints.add(new Point2D.Float(startX - i * BLOCK_WIDTH, startY));
}
}
public void draw(Graphics g) {
g.setColor(Color.white);
for (Point2D p : sPoints) {
g.fillRect((int) p.getX(), (int) p.getY(), BLOCK_WIDTH, BLOCK_WIDTH);
}
}
public void move() {
if (isMoving) {
sPoints.removeLast();
steer(sPoints.getFirst());
}
}
private void steer(Point2D head) {
Point2D.Float newHead = new Point2D.Float();
switch (this.getDirection()) {
case UP:
newHead.setLocation(head.getX(), head.getY() - BLOCK_WIDTH);
break;
case DOWN:
newHead.setLocation(head.getX(), head.getY() + BLOCK_WIDTH);
break;
case LEFT:
newHead.setLocation(head.getX() - BLOCK_WIDTH, head.getY());
break;
case RIGHT:
newHead.setLocation(head.getX() + BLOCK_WIDTH, head.getY());
break;
}
this.sPoints.addFirst(newHead);
}
public int getX() {
return (int) sPoints.get(0).getX();
}
public int getY() {
return (int) sPoints.get(0).getY();
}
public boolean isMoving() {
return isMoving;
}
public void setIsMoving(boolean b) {
isMoving = b;
}
public Direction getDirection() {
return direction;
}
public void setDirection(Direction d) {
if (this.getDirection() == null) {
this.direction = d;
} else if (!this.getDirection().isOpposite(d)) {
this.direction = d;
}
}
}
Direction.java
public enum Direction {
UP(-1), DOWN(1), LEFT(-2), RIGHT(2);
int vector;
Direction(int i) {
this.vector = i;
}
public boolean isOpposite(Direction d) {
return this.vector + d.vector == 0;
}
}
Snack.java
import java.awt.EventQueue;
import javax.swing.JFrame;
public class Snake extends JFrame {
public Snake() {
initUI();
}
private void initUI() {
add(new Board());
setResizable(false);
pack();
setTitle("Snake");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame ex = new Snake();
ex.setVisible(true);
});
}
}
Board.java
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Board extends JPanel implements ActionListener {
private final int B_WIDTH = 300;
private final int B_HEIGHT = 300;
private final int DOT_SIZE = 10;
private final int ALL_DOTS = 900;
private final int RAND_POS = 29;
private final int DELAY = 140;
private final int x\[\] = new int\[ALL_DOTS\];
private final int y\[\] = new int\[ALL_DOTS\];
private int dots;
private int apple_x;
private int apple_y;
private boolean leftDirection = false;
private boolean rightDirection = true;
private boolean upDirection = false;
private boolean downDirection = false;
private boolean inGame = true;
private Timer timer;
private Image ball;
private Image apple;
private Image head;
public Board() {
initBoard();
}
private void initBoard() {
addKeyListener(new TAdapter());
setBackground(Color.black);
setFocusable(true);
setPreferredSize(new Dimension(B_WIDTH, B_HEIGHT));
loadImages();
initGame();
}
private void loadImages() {
ImageIcon iid = new ImageIcon("src/resources/dot.png");
ball = iid.getImage();
ImageIcon iia = new ImageIcon("src/resources/apple.png");
apple = iia.getImage();
ImageIcon iih = new ImageIcon("src/resources/head.png");
head = iih.getImage();
}
private void initGame() {
dots = 3;
for (int z = 0; z < dots; z++) {
x\[z\] = 50 - z * 10;
y\[z\] = 50;
}
locateApple();
timer = new Timer(DELAY, this);
timer.start();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
private void doDrawing(Graphics g) {
if (inGame) {
g.drawImage(apple, apple_x, apple_y, this);
for (int z = 0; z < dots; z++) {
if (z == 0) {
g.drawImage(head, x\[z\], y\[z\], this);
} else {
g.drawImage(ball, x\[z\], y\[z\], this);
}
}
Toolkit.getDefaultToolkit().sync();
} else {
gameOver(g);
}
}
private void gameOver(Graphics g) {
String msg = "Game Over";
Font small = new Font("Helvetica", Font.BOLD, 14);
FontMetrics metr = getFontMetrics(small);
g.setColor(Color.white);
g.setFont(small);
g.drawString(msg, (B_WIDTH - metr.stringWidth(msg)) / 2, B_HEIGHT / 2);
}
private void checkApple() {
if ((x\[0\] == apple_x) && (y\[0\] == apple_y)) {
dots++;
locateApple();
}
}
private void move() {
for (int z = dots; z > 0; z--) {
x\[z\] = x\[(z - 1)\];
y\[z\] = y\[(z - 1)\];
}
if (leftDirection) {
x\[0\] -= DOT_SIZE;
}
if (rightDirection) {
x\[0\] += DOT_SIZE;
}
if (upDirection) {
y\[0\] -= DOT_SIZE;
}
if (downDirection) {
y\[0\] += DOT_SIZE;
}
}
private void checkCollision() {
for (int z = dots; z > 0; z--) {
if ((z > 4) && (x\[0\] == x\[z\]) && (y\[0\] == y\[z\])) {
inGame = false;
}
}
if (y\[0\] >= B_HEIGHT) {
inGame = false;
}
if (y\[0\] < 0) {
inGame = false;
}
if (x\[0\] >= B_WIDTH) {
inGame = false;
}
if (x\[0\] < 0) {
inGame = false;
}
if (!inGame) {
timer.stop();
}
}
private void locateApple() {
int r = (int) (Math.random() * RAND_POS);
apple_x = ((r * DOT_SIZE));
r = (int) (Math.random() * RAND_POS);
apple_y = ((r * DOT_SIZE));
}
#Override
public void actionPerformed(ActionEvent e) {
if (inGame) {
checkApple();
checkCollision();
move();
}
repaint();
}
private class TAdapter extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if ((key == KeyEvent.VK_LEFT) && (!rightDirection)) {
leftDirection = true;
upDirection = false;
downDirection = false;
}
if ((key == KeyEvent.VK_RIGHT) && (!leftDirection)) {
rightDirection = true;
upDirection = false;
downDirection = false;
}
if ((key == KeyEvent.VK_UP) && (!downDirection)) {
upDirection = true;
rightDirection = false;
leftDirection = false;
}
if ((key == KeyEvent.VK_DOWN) && (!upDirection)) {
downDirection = true;
rightDirection = false;
leftDirection = false;
}
}
}
}
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
I’m writing a simple graphics editor, thus far I can draw some figures, move and enlarge them. I’m trying to allow user to change color of figure. After I right click in a shape, there appears a popup menu with colors to choose. But no matter what I do - the shape’s color doesn’t change. :/ I hope to get help, I spent a of of time on it but no idea how to solve it. :/
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.util.ArrayList;
public class PaintPanel extends JPanel implements MouseListener, MouseMotionListener, MouseWheelListener
{
public RadioMenu radio = new RadioMenu();
private ArrayList <Point2D.Double> points = new ArrayList<>();
private ArrayList <Shape> figures = new ArrayList<>();
private Color mainColor = Color.blue;
private Color bgColor = Color.white;
private Color special = Color.red;
private double scrollSpeed = 5;
private int pointsize = 4;
private int near = 15;
private int index;
private ColorMenu colorMenu = new ColorMenu();
public PaintPanel()
{
super();
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
setLayout(new BorderLayout());
setBackground(bgColor);
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
drawGraphics(g2d);
}
private void drawGraphics(Graphics2D g2d)
{
int i = 0;
for (Shape s : figures)
{
g2d.setColor(mainColor);
if (s instanceof MyEllipse2D)
{
g2d.setColor(((MyEllipse2D) s).color);
System.out.println(g2d.getColor());
}
else if (s instanceof MyRectangle2D)
{
g2d.setColor(((MyRectangle2D) s).color);
System.out.println(g2d.getColor());
}
else if (s instanceof MyPolygon2D)
{
g2d.setColor(((MyPolygon2D) s).color);
System.out.println(g2d.getColor());
}
if (g2d.getColor() != bgColor)
{
g2d.setColor(mainColor);
} else
{
g2d.setColor(mainColor);
g2d.draw(s);
}
++i;
}
i = 0;
for (Point2D.Double p : points)
{
if (i == 0)
{
g2d.setColor(special);
g2d.fillOval((int) p.getX(), (int) p.getY(), pointsize, pointsize);
g2d.setColor(mainColor);
} else
{
g2d.fillOval((int) p.getX(), (int) p.getY(), pointsize, pointsize);
}
++i;
}
}
#Override
public void mousePressed(MouseEvent e)
{
if(e.getButton()==MouseEvent.BUTTON1)
switch (radio.getChoice())
{
case "Okrag":
points.clear();
points.add(new Point2D.Double(e.getX(),e.getY()));
repaint();
break;
case "Prostokat":
points.clear();
points.add(new Point2D.Double(e.getX(),e.getY()));
repaint();
break;
case "Edycja":
index = isSelected(e);
break;
}
else if(e.getButton()==MouseEvent.BUTTON3 && radio.getChoice().equals("Edycja"))
{
index = isSelected(e);
if(index >= 0)
{
colorMenu.doPop(e);
}
}
}
#Override
public void mouseReleased(MouseEvent e)
{
if(e.getButton()==MouseEvent.BUTTON1)
switch (radio.getChoice())
{
case "Okrag":
points.add(new Point2D.Double(e.getX(),e.getY()));
figures.add(new MyEllipse2D(points.get(0),points.get(1),bgColor));
points.clear();
break;
case "Prostokat":
points.add(new Point2D.Double(e.getX(),e.getY()));
figures.add(new MyRectangle2D(points.get(0),points.get(1),bgColor));
points.clear();
break;
case "Wielokat":
if(points.size() != 0 && points.get(0).distance(e.getX(),e.getY())<=near)
{
figures.add(new MyPolygon2D(points,bgColor));
points.clear();
}
else
{
points.add(new Point2D.Double(e.getX(),e.getY()));
}
break;
case "Edycja":
points.clear();
}
repaint();
}
#Override
public void mouseDragged(MouseEvent e)
{
if(index>=0 && radio.getChoice().equals("Edycja"))
{
if (figures.get(index) instanceof MyEllipse2D)
{
((MyEllipse2D) figures.get(index)).move(new Point2D.Double(e.getX(),e.getY()));
}
else if (figures.get(index) instanceof MyRectangle2D)
{
((MyRectangle2D) figures.get(index)).move(new Point2D.Double(e.getX(),e.getY()));
}
else if(figures.get(index) instanceof MyPolygon2D)
{
((MyPolygon2D) figures.get(index)).move(new Point2D.Double(e.getX(),e.getY()));
}
repaint();
}
}
#Override
public void mouseWheelMoved(MouseWheelEvent e)
{
index = isSelected(e);
if(radio.getChoice().equals("Edycja"))
{
if (index>=0)
{
if (figures.get(index) instanceof MyEllipse2D)
{
((MyEllipse2D) figures.get(index)).scale(e.getPreciseWheelRotation(), scrollSpeed);
}
else if (figures.get(index) instanceof MyRectangle2D)
{
((MyRectangle2D) figures.get(index)).scale(e.getPreciseWheelRotation(), scrollSpeed);
}
else if(figures.get(index) instanceof MyPolygon2D)
{
((MyPolygon2D) figures.get(index)).scale(e.getPreciseWheelRotation(), scrollSpeed);
}
repaint();
}
}
}
private int isSelected(MouseEvent e)
{
int i;
for(i=figures.size()-1;i>=0;--i)
{
if(figures.get(i).contains(e.getPoint()))
{
return i;
}
}
return -1;
}
#Override
public void mouseClicked(MouseEvent e)
{
index = isSelected(e);
if(index >= 0 )
{
colorMenu.doPop(e);
if(e.getButton()==MouseEvent.BUTTON3 && radio.getChoice().equals("Edycja"))
{
colorMenu.doPop(e);
if(figures.get(index) instanceof MyEllipse2D)
((MyEllipse2D) figures.get(index)).color = colorMenu.color;
else if(figures.get(index) instanceof MyRectangle2D)
((MyRectangle2D) figures.get(index)).color = colorMenu.color;
else if(figures.get(index) instanceof MyPolygon2D)
((MyPolygon2D) figures.get(index)).color = colorMenu.color;
System.out.println(colorMenu.color);
//colorMenu.color = bgColor;
}
repaint();
}
}
#Override
public void mouseEntered(MouseEvent e)
{
}
#Override
public void mouseExited(MouseEvent e)
{
}
#Override
public void mouseMoved(MouseEvent e)
{
}
}
ColorMenu
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
public class ColorMenu extends JPopupMenu implements ActionListener
{
private ArrayList<JMenuItem> items = new ArrayList<JMenuItem>();
private ArrayList<Color> colors = new ArrayList<Color>();
public Color color;
public ColorMenu()
{
super();
colors.add(Color.black);
colors.add(Color.blue);
colors.add(Color.cyan);
colors.add(Color.gray);
colors.add(Color.green);
colors.add(Color.magenta);
colors.add(Color.orange);
colors.add(Color.red);
colors.add(Color.yellow);
colors.add(Color.white);
for (Color c : colors)
{
items.add(new JMenuItem(c.toString()));
}
for(JMenuItem i: items)
{
i.addActionListener(this);
add(i);
}
}
public void doPop(MouseEvent e)
{
show(e.getComponent(), e.getX(), e.getY());
}
#Override
public void actionPerformed(ActionEvent e)
{
Object source = e.getSource();
int j=0;
for(JMenuItem i: items)
{
if(i == source)
{
break;
}
++j;
}
this.color = colors.get(j);
}
}
MyRectangle2D
import java.awt.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
public class MyRectangle2D extends Rectangle2D.Double implements Shape
{
private Point2D.Double center;
private double width,hight;
public Color color;
public MyRectangle2D()
{}
public MyRectangle2D(Point2D.Double p1, Point2D.Double p2, Color color)
{
super();
this.color = color;
double x1 = p1.getX();
double y1 = p1.getY();
double x2 = p2.getX();
double y2 = p2.getY();
if(x1<=x2 && y1>=y2)
{
width=x2-x1;
hight=y1-y2;
setRect(x1,y2,width,hight);
}
else if(x1<=x2 && y1<=y2)
{
width=x2-x1;
hight=y2-y1;
setRect(x1,y1,width,hight);
}
else if (x1>=x2 && y1<=y2)
{
width=x1-x2;
hight=y2-y1;
setRect(x2,y1,width,hight);
}
else if(x1>=x2 && y1>=y2)
{
width=x1-x2;
hight=y1-y2;
setRect(x2,y2,width,hight);
}
center = new Point2D.Double(x1 + (x2-x1)/2,y1+(y2-y1)/2);
}
public void scale(double amount, double scale)
{
double change = -1*amount*scale;
width += change;
hight += change;
setRect(center.getX()-width/2,center.getY()-hight/2,width,hight);
}
public void move (Point2D.Double p)
{
center = p;
setRect();
}
private void setRect()
{
setRect(center.getX()-width/2,center.getY()-hight/2,width,hight);
}
}
MyPolygon2D
import java.awt.*;
import java.awt.geom.Point2D;
import java.util.ArrayList;
public class MyPolygon2D extends Polygon implements Shape
{
private ArrayList<MyVector> vectors = new ArrayList<>();
private Point2D.Double center;
private int size;
public Color color;
public MyPolygon2D()
{}
public MyPolygon2D(ArrayList<Point2D.Double> points, Color color)
{
super();
this.color = color;
size = points.size();
for(int i=0; i<size;++i)
{
addPoint((int)points.get(i).getX(),(int)points.get(i).getY());
}
center();
setVectors();
}
public void scale(double amount, double scale)
{
double change = -1*amount*scale;
for (int i=0;i<size;++i)
{
vectors.get(i).x *= (100.0+change)/100.0;
vectors.get(i).y *= (100.0+change)/100.0;
Point2D.Double curr = new Point2D.Double(center.getX()+vectors.get(i).x,center.getY()+vectors.get(i).y);
xpoints[i] = (int)curr.getX();
ypoints[i] = (int)curr.getY();
}
invalidate();
}
public void move (Point2D.Double p)
{
MyVector change = new MyVector(center,p);
center = p;
for(int i=0;i<size;++i)
{
xpoints[i] += (int)change.x;
ypoints[i] += (int)change.y;
}
invalidate();
}
public void setColor(Color color)
{
this.color = color;
}
public Color getColor()
{
return this.color;
}
#Override
public boolean contains(Point p)
{
int maxx=0, maxy=0, minx=Integer.MAX_VALUE, miny=Integer.MAX_VALUE;
for (int i=0;i<size;++i)
{
if(xpoints[i]>=maxx)
maxx = xpoints[i];
if(xpoints[i]<=minx)
minx = xpoints[i];
if(ypoints[i]>=maxy)
maxy = ypoints[i];
if(ypoints[i]<=miny)
miny = ypoints[i];
}
if(p.getX() <= maxx && p.getX() >= minx && p.getY() <= maxy && p.getY() >=miny)
return true;
else
return false;
}
private void setVectors()
{
for(int i=0; i<size;++i)
{
vectors.add(new MyVector(center,new Point2D.Double(xpoints[i],ypoints[i])));
}
}
private void center()
{
center = new Point2D.Double(getBounds2D().getX()+getBounds2D().getWidth()/2,getBounds2D().getY()+getBounds2D().getHeight()/2);
}
private class MyVector
{
public double x, y;
public MyVector(Point2D.Double p1, Point2D.Double p2)
{
x=p2.getX()-p1.getX();
y=p2.getY() - p1.getY();
}
}
}
MyEllipse2D
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
public class MyEllipse2D extends Ellipse2D.Double implements Shape
{
private double radius;
private Point2D.Double center;
public Color color;
public MyEllipse2D(Point2D.Double p1, Point2D.Double p2, Color color)
{
super();
this.color = color;
center = p1;
radius = (p1.distance(p2));
setFrame();
}
public void scale(double amount, double scale)
{
double change = -1*amount*scale;
radius += change;
setFrame();
}
public void move (Point2D.Double p)
{
center = p;
setFrame();
}
public void setColor(Color color)
{
this.color = color;
System.out.println(this.color);
}
public Color getColor()
{
return this.color;
}
private void setFrame()
{
setFrame(center.getX()-radius,center.getY()-radius,2*radius,2*radius);
}
}
RadioMenu
import javax.swing.*;
import java.awt.*;
public class RadioMenu extends JPanel
{
private int amount = 4;
private JRadioButton[] options = new JRadioButton[amount];
private ButtonGroup group = new ButtonGroup();
private String[] names = {"Okrag","Prostokat","Wielokat","Edycja"};
private Font font = new Font("Times New Roman",Font.BOLD,16);
public RadioMenu()
{
super();
setLayout(new GridLayout(1,amount));
for(int i=0;i<amount;++i)
{
if(i!=0)
options[i] = new JRadioButton(names[i],false);
else
options[i] = new JRadioButton(names[i],true);
group.add(options[i]);
add(options[i]);
options[i].setFont(font);
}
}
public String getChoice()
{
for(int i=0; i<amount; ++i)
{
if(options[i].isSelected())
return options[i].getText();
}
return "";
}
}
Frame
import javax.swing.*;
import java.awt.*;
public class Frame extends JFrame
{
private Dimension prefsize = new Dimension(800,600);
private Dimension minSize = new Dimension(400,200);
private Menu menu = new Menu();
private PaintPanel panel = new PaintPanel();
public Frame()
{
super();
setVisible(true);
setPreferredSize(prefsize);
setMinimumSize(minSize);
setLayout(new BorderLayout());
setDefaultCloseOperation(EXIT_ON_CLOSE);
add(panel,BorderLayout.CENTER);
JPanel upper = new JPanel();
upper.setLayout(new GridLayout(2,1));
upper.add(menu);
upper.add(panel.radio);
add(upper,BorderLayout.NORTH);
pack();
}
}
MyAplet
import javax.swing.*;
public class MyAplet extends JApplet
{
public void init()
{
Frame main = new Frame();
}
}
Menu
import javax.swing.*;
public class Menu extends JMenuBar
{
private JMenu info;
//private JMenuItem x;
public Menu()
{
super();
info = new JMenu("info");
//info.add(x);
add(info);
}
}
First, you should be displaying a JPopupMenu in response to only one type of event. You are currently calling colorMenu.doPop(e) in the mousePressed method and twice in the mouseClicked method.
To make sure you only display a JPopupMenu when you’re supposed to, you should use the JPopupMenu.isPopupTrigger method. This way, the menu will be displayed on mousePress or mouseClick, but never both. You do not want to display a JPopupMenu more than once in response to a single mouse action!
private int selectedIndex = -1;
// ...
#Override
public void mousePressed(MouseEvent e)
{
selectedIndex = isSelected(e);
if (colorMenu.isPopupTrigger(e)) {
colorMenu.doPop(e);
} else if (e.getButton() == MouseEvent.BUTTON1) {
// ...
#Override
public void mouseClicked(MouseEvent e)
{
if (selectedIndex >= 0 && colorMenu.isPopupTrigger(e) && radio.getChoice().equals("Edycja"))
{
colorMenu.doPop(e);
The next problem is that you’re trying to read colorMenu.color before the user has made a color selection. When you call colorMenu.doPopup(e), the menu is displayed and the method returns immediately. It does not wait for the user to make a selection and dismiss the menu. Your code is trying to immediately make use of colorMenu.color, but it hasn’t been set yet.
The only way to know when the user has selected a color is with an ActionListener. Your ColorMenu class has an ActionListener, but currently there is no way for the PaintPanel class to know when the ActionListener has been triggered.
You can give your ColorMenu class the capability of notifying listeners in other classes, using the inherited listenerList object:
public void addChangeListener(ChangeListener listener) {
listenerList.add(ChangeListener.class, listener);
}
public void removeChangeListener(ChangeListener listener) {
listenerList.remove(ChangeListener.class, listener);
}
public ChangeListener[] getChangeListeners() {
return listenerList.getListeners(ChangeListener.class);
}
protected void fireChangeListeners() {
ChangeEvent event = new ChangeEvent(this);
for (ChangeListener listener : getChangeListeners()) {
listener.stateChanged(event);
}
}
(ChangeListener and ChangeEvent are in the javax.swing.event package.)
This allows other classes to listen for a user selection. You want to change ColorMenu to actually notify those listeners:
#Override
public void actionPerformed(ActionEvent e)
{
// (other code) ...
this.color = colors.get(j);
fireChangeListeners();
}
Now, you can make PaintPanel listen for a user selection:
public PaintPanel()
{
super();
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
setLayout(new BorderLayout());
setBackground(bgColor);
colorMenu.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent event) {
if (selectedIndex >= 0)
{
Shape figure = figures.get(selectedIndex);
if (figure instanceof MyEllipse2D)
((MyEllipse2D) figure).color = colorMenu.color;
else if (figure instanceof MyRectangle2D)
((MyRectangle2D) figure).color = colorMenu.color;
else if (figure instanceof MyPolygon2D)
((MyPolygon2D) figure).color = colorMenu.color;
}
}
});
}
You should remove that color changing code from mouseClicked, as it will never be valid to call it there, since the user hasn’t selected a color yet:
#Override
public void mouseClicked(MouseEvent e)
{
if (selectedIndex >= 0 && colorMenu.isPopupTrigger(e) && radio.getChoice().equals("Edycja"))
{
colorMenu.doPop(e);
// Nothing else to do here, since the user has not selected a color yet.
}
}
The basic problem is, you need to know when the color is selected from the menu. The solution to the problem can be achieved in a number of different ways.
You could continue to use a MouseListener to monitor the mousePressed, mouseReleased and mouseClicked events, but you should be making using of the MouseEvent#isPopupTrigger property to determine when the popup should be displayed, as the triggers are different for different platforms.
Another solution would be to use JComponent#setComponentPopupMenu all the component to take care of the action itself. This, however, would require a slight change in design.
This approach places the onus back onto the component, rather then been separated into a different class, like you have the ColorMenu right now.
To start with, we need to know the selected shape, so, to the PaintPanel, we need to add a new property
private Shape selectedShape;
All this does is determines which shape was selected by the user.
Next we need someway to update the shape color. This can be achieved by simply adding a new method to the paintPanel method, which is called when the color is to be changed.
public void setShapeColor(Color color) {
//...
}
Next, we take advantage of the Action API and create a simple ColorAction.
This is self contained unit of work, which contains the information need to display it on a menu (or button) and which determines the actions to take when triggered.
public class ColorAction extends AbstractAction {
private Color color;
public ColorAction(String name, Color color) {
super(name);
this.color = color;
}
public Color getColor() {
return color;
}
#Override
public boolean isEnabled() {
return getSelectedShape() != null;
}
#Override
public void actionPerformed(ActionEvent e) {
setShapeColor(color);
}
}
In this case, this simple inner class to PaintPanel will call setShapeColor when triggered.
"But wait", I hear you call, "The colors can only be selected when a shape is selected!" Ah, we can control the Action through the isEnabled method, which will only return true when a shape is actually selected.
This might seem somewhat counterintuitive, but by not displaying a popup menu when nothing is displayed, it causes the user to think that no popup menu will be displayed ever, at least this way, we can display a popup menu with it's options disabled.
The next trick is trying to determine what is selected. Unfortunately, during my testing, the OS consumed the mousePressed event, which would have been most useful to make this determination, however, not all is lost.
The JComponent will call getPopupLocation just before the popup is made visible, passing in the MouseEvent which triggered it, we can take advantage of this to determine which shape would be selected
#Override
public Point getPopupLocation(MouseEvent event) {
selectedShape = null;
for (int i = figures.size() - 1; i >= 0; --i) {
if (figures.get(i).contains(event.getPoint())) {
selectedShape = figures.get(i);
}
}
return super.getPopupLocation(event);
}
For a more complete example...
public class PaintPanel extends JPanel implements MouseListener, MouseMotionListener, MouseWheelListener {
private ArrayList<Point2D.Double> points = new ArrayList<>();
private ArrayList<Shape> figures = new ArrayList<>();
private Shape selectedShape;
private Color mainColor = Color.blue;
private Color bgColor = Color.white;
private Color special = Color.red;
private double scrollSpeed = 5;
private int pointsize = 4;
private int near = 15;
private int index;
public PaintPanel() {
super();
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
setLayout(new BorderLayout());
setBackground(bgColor);
setComponentPopupMenu(makePopupMenu());
}
protected JPopupMenu makePopupMenu() {
JPopupMenu menu = new JPopupMenu();
menu.add(new ColorAction("Black", Color.black));
menu.add(new ColorAction("Blue", Color.blue));
menu.add(new ColorAction("Cyan", Color.cyan));
menu.add(new ColorAction("Grey", Color.gray));
menu.add(new ColorAction("Green", Color.green));
menu.add(new ColorAction("Megenta", Color.magenta));
menu.add(new ColorAction("Orange", Color.orange));
menu.add(new ColorAction("Red", Color.red));
menu.add(new ColorAction("Yellow", Color.yellow));
menu.add(new ColorAction("White", Color.white));
return menu;
}
public Shape getSelectedShape() {
return selectedShape;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public Point getPopupLocation(MouseEvent event) {
selectedShape = null;
for (int i = figures.size() - 1; i >= 0; --i) {
if (figures.get(i).contains(event.getPoint())) {
selectedShape = figures.get(i);
}
}
return super.getPopupLocation(event);
}
#Override
public void mouseClicked(MouseEvent e) {
//...
}
#Override
public void mousePressed(MouseEvent e) {
//...
}
#Override
public void mouseReleased(MouseEvent e) {
//...
}
#Override
public void mouseEntered(MouseEvent e) {
//...
}
#Override
public void mouseExited(MouseEvent e) {
//...
}
#Override
public void mouseDragged(MouseEvent e) {
//...
}
#Override
public void mouseMoved(MouseEvent e) {
//...
}
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
//...
}
public void setShapeColor(Color color) {
//...
}
public class ColorAction extends AbstractAction {
private Color color;
public ColorAction(String name, Color color) {
super(name);
this.color = color;
}
public Color getColor() {
return color;
}
#Override
public boolean isEnabled() {
return getSelectedShape() != null;
}
#Override
public void actionPerformed(ActionEvent e) {
setShapeColor(color);
}
}
}
There are a couple of other ways you might achieve this, but I'd be using these basic principles are the cornerstone of the design.
For example, you could have a "color model" which get's passed to the popup menu class, which would be updated when a color is selected
I'm developing a Java Game. I'm stuck at a point,where I need to restart the whole game again after GameOver. Here is the skeleton of my program:
package projectflappy;
import java.awt.*;
public final class TheGame extends JFrame implements MouseListener{
JPanel jp;
//declaration of the varibles
int x_width = 500;
int y_height = 500;
int count = 5 ;
Ellipse2D Ball;
int x_ball;
int y_ball;
int cord_xup1,cord_xdown1;
int cord_xup2,cord_xdown2;
int cord_xup3,cord_xdown3;
int cord_xup4,cord_xdown4;
int cord_xup5,cord_xdown5;
Boolean flag = true;
RoundRectangle2D up1,down1,up2,down2,up3,down3,up4,down4;
Font font = new Font("Matura MT Script Capitals",Font.ROMAN_BASELINE,40);
Font font1 = new Font("Matura MT Script Capitals",Font.ROMAN_BASELINE,20);
Font font3 = new Font("Matura MT Script Capitals",Font.ROMAN_BASELINE,20);
float das[] = {10.0f};
BasicStroke color = new BasicStroke(10,BasicStroke.CAP_ROUND,BasicStroke.JOIN_BEVEL,20.0f,das,0.0f);
GradientPaint gp2 = new GradientPaint(20, 0,
Color.DARK_GRAY, 0, 10, Color.GRAY, true);
GradientPaint gp3 = new GradientPaint(30, 0,
Color.BLACK, 0, 20, Color.GREEN, true);
Toolkit kit = Toolkit.getDefaultToolkit();
//Getting the "background.jpg" image we have in the folder
Image background = kit.getImage("D:\\College\\Programs\\ProjectFLAPPY\\src\\projectflappy\\1.png");
JLabel a = new JLabel("Get Ready ! Click to Start.");
JLabel retry = new JLabel(new ImageIcon("D:\\College\\Programs\\ProjectFLAPPY\\src\\projectflappy\\unnamed.png"));
int score = 0;
//constructor
public TheGame() throws IOException
{
super("Simple Drawing");
setSize(x_width, y_height);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
jp = new DrawingPanel();
add(jp);
addMouseListener(this);
}
ActionListener action = new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
update();
repaint();
}
};
Timer t = new Timer(50,action);
public void init()
{
x_ball = 30;
y_ball = 200;
cord_xup1 = 175; cord_xdown1 = 175;
cord_xup2 = 320; cord_xdown2 = 320;
cord_xup3 = 460; cord_xdown3 = 460;
cord_xup4 = 585; cord_xdown4 = 585;
cord_xup5 = 700; cord_xdown5 = 700;
retry.setVisible(false);
retry.setBounds(175,260,46,46);
a.setForeground(Color.YELLOW);
a.setFont(font1);
a.setVisible(true);
a.setBounds(105,200,300,100);
}
#Override
public void mouseClicked(MouseEvent e) {
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
a.setVisible(false);
if( flag == false)
{
t.stop();
}
else
{
t.start();
}
y_ball = y_ball - 40;
count--;
}
#Override
public void mousePressed(MouseEvent e) {
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void mouseReleased(MouseEvent e) {
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void mouseEntered(MouseEvent e) {
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void mouseExited(MouseEvent e) {
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
// for drawing on the panel
class DrawingPanel extends JPanel{
private static final long serialVersionUID = 1L;
public DrawingPanel() {
setPreferredSize(new Dimension(300, 300));
setLayout(null);
init();
add(a);
add(retry);
// addMouseListener(this);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D d = (Graphics2D)g;
d.drawImage(background, -270,-30, this);
Ball = new Ellipse2D.Double(x_ball,y_ball,30,30);
d.setColor(Color.green);
d.setFont(font3);
up1 = new RoundRectangle2D.Double(cord_xup1,-5,30,175,20,20);
down1 = new RoundRectangle2D.Double(cord_xdown1,310,30,155,20,20);
up2 = new RoundRectangle2D.Double(cord_xup2,-5,30,200,20,20);
down2 = new RoundRectangle2D.Double(cord_xdown2,310,30,175,20,20);
up3 = new RoundRectangle2D.Double(cord_xup3,-5,30,230,20,20);
down3 = new RoundRectangle2D.Double(cord_xdown3,350,30,135,20,20);
up4 = new RoundRectangle2D.Double(cord_xup4,-5,30,115,20,20);
down4 = new RoundRectangle2D.Double(cord_xdown4,240,30,115,20,20);
d.setPaint(gp2);
d.setStroke(color);
d.fill(up1);
d.fill(down1);
d.fill(up2);
d.fill(down2);
d.fill(up3);
d.fill(down3);
d.fill(up4);
d.fill(down4);
d.setPaint(gp3);
d.setStroke(color);
d.fill(Ball);
d.setColor(Color.BLACK);
d.setFont(font1);
d.drawString(""+score ,200,50);
if( Ball.intersects(up1.getBounds()) || Ball.intersects(down1.getBounds()) || Ball.intersects(up2.getBounds()) || Ball.intersects(down2.getBounds()) || Ball.intersects(up3.getBounds()) || Ball.intersects(down3.getBounds()) || Ball.intersects(up4.getBounds()) || Ball.intersects(down4.getBounds()))
{
t.stop();
flag = false;
d.setColor(Color.red);
d.setFont(font);
d.drawString("Game Over : "+score ,100,250);
retry.setVisible(true);
}
retry.addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent event) {
init(); //reset properties
}
//...
#Override
public void mousePressed(MouseEvent e) {
// throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void mouseReleased(MouseEvent e) {
// throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void mouseEntered(MouseEvent e) {
// throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void mouseExited(MouseEvent e) {
// throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
});
}
}
public void update()
{
cord_xdown1 -= 5;
cord_xup1 -= 5;
cord_xdown2 -= 5;
cord_xup2 -= 5;
cord_xdown3 -= 5;
cord_xup3 -= 5;
cord_xdown4 -= 5;
cord_xup4 -= 5;
cord_xdown5 -= 5;
cord_xup5 -= 5;
if( cord_xup1 <=-20)
{
cord_xup1 = 500;
cord_xdown1 = 500;
}
if( cord_xup2 <=-20)
{
cord_xup2 = 500;
cord_xdown2 = 500;
}
if( cord_xup3 <=-20)
{
cord_xup3 = 500;
cord_xdown3 = 500;
}
if( cord_xup4 <=-20)
{
cord_xup4 = 500;
cord_xdown4 = 500;
}
if( cord_xup5 <=-20)
{
cord_xup5 = 500;
cord_xdown5 = 500;
}
if(count >= 0)
{
y_ball = y_ball - 7;
count--;
if( y_ball == y_height)
{
t.stop();
}
}
else
{
y_ball = y_ball + 7;
if( y_ball == y_height-70)
{
t.stop();
}
}
if(cord_xdown1 == x_ball || cord_xdown2 == x_ball || cord_xdown3 == x_ball || cord_xdown4 == x_ball)
{
score = score+1;
}
}
public static void main(String[] args) throws IOException {
new TheGame();
}
}
here retry is a JLabel in which I'm using a MouseListener to do things.
When I run,the JPanel gets completely removed from the JFrame but the new JPanel really doesn't seem to work. But only one component i.e, a.setVisble(true) works.
This is the frame when the Players gets out.
This Frames when the Player clicks on the retry button.
The reason your new panel is not showing is due to the component hierarchy being invalid. You attempt to revalidate, but you did it before adding the panel. You need to do it AFTER you add a component to an already visible container. Check out invalidate():
This method is called automatically when any layout-related information changes (e.g. setting the bounds of the component, or adding the component to a container).
So you must validate after adding the component, not before. revalidate() invalidates then revalidates the component hierarchy.
The proper way to handle this would be to revert your game back to it's original form; just change everything back to how it was. No need to create a new panel.
You could create a method, init(), which sets your game to how it should be:
//Contains the properties that will change during gameplay
void init() {
retry.setVisible(false);
a.setForeground(Color.YELLOW);
//...
}
Which you can then call when you create the board (in the constructor) and when you press retry (in the listener):
public DrawingPanel() {
setPreferredSize(new Dimension(300, 300));
setPreferredSize(new Dimension(300, 300));
setLayout(null);
init(); //sets properties
a.setFont(font1);
a.setVisible(true);
a.setBounds(105,200,300,100);
add(a);
retry.setBounds(175,260,46,46);
retry.addMouseListener(new MouseListener() {
public void mouseClicked(MouseEvent event) {
init(); //reset properties
}
//...
});
add(retry);
}
You shouldn't add a listener to a component in your update() method, since update() will be called multiple times. Add it in your constructor.
If retry is a JButton, you should use an ActionListener. I wasn't sure, so I kept it as a mouse listener.
You should avoid using null layout (absolute positioning). Layout Managers position and size components using specific calculations to ensure your resulting GUI looks the same on all platforms. There are a few uses where absolute positioning is a viable option, as mentioned in the tutorials, but it's always best to prefer a Layout Manager. IMO, null layout is bad practice, and the only reason one would use it is if they didn't understand layout managers, which is a problem in itself.
To learn more about layout managers, check out the Visual Guide to Layout Managers trail. Not only does the JDK come bundled with layouts, but you can also create your own or use a third party layout, like MigLayout
EDIT:
Post Swing code to the Event Dispatch Thread. Swing event handlers (painting, listeners) are executed on the Event Dispatch Thread. To ensure the Swing code you write is in sync with the EDT, post any Swing code that isn't already being executed on the EDT to the EDT by using invokeLater or invokeAndWait.
Do not size your frame directly. Allow your frame to size based off the contents inside of it. Your DrawingPanel (the game canvas) should determine the size of the frame.
TheGame should not extend JFrame, since it's not a frame itself, rather than something contained within a frame. Having it extend JPanel would be a little easier on you (you won't be forced to create a new class to override the paint method). Although, TheGame shouldn't extend anything, it should HAVE these things (has-a relationship, not is-a). But since you're still a beginner, I don't wanna overwhelm you with a completely new design, so I considered TheGame to be the actual game canvas (where things will be draw; TheGame will extend JPanel), so you'll no longer need DrawingBoard.
As mentioned before, you should NOT add listeners (or do any task that is only needed once) in the paint method. Keep in mind that the paint method is for painting, not initializing or setting values. You should attempt to keep logic out of that method if possible.
Stay consistent. You use a JLabel for "Click to start!", yet you use drawString for "Game Over". Pick one or the other. This choice is really up to you. For this example, I chose to use drawString, since it's consistent with the rest of your rendering methods (how you paint the background, ball and obstacles)
DO NOT CREATE NEW OBJECTS IN YOUR PAINT METHOD. You're creating a ton of new objects every 50 milliseconds. This is NOT needed, and will harm performance critically. When you use the new keyword, you create a new object. Instead of creating a new object to change it (or revert it back), just change it's state.
Take advantage of Object Orientation. It'll help keep you organized, and allow you to easily manage and scale up your application. Don't shove a bunch of variables into one class to represent a ton of different things (cordx_up1, cordx_up2... it's definitely not scalable).
Look into some of the Adapter classes like MouseAdapter and KeyAdapter; they allow you to handle events without needing to declare methods you might not use.
Use access modifiers. If you aren't familiar with them, get to know them. It makes managing code a lot easier if you know where it can be used ahead of time.
Your paths point to a specific drive with a specific name. This should not be the case, since not everyone uses that drive and/or folder name. Package your images with your project, then refer to them locally.
With that said, you have a lot of studying to do.
What I did was create a Ball class and an Obstacle class, to get a little more organized. ball_x and ball_y are now inside the Ball class, as well as the gradient for it. The objects we create from this class will now have these properties (states). I create 1 ball object for your game.
Instead of creating new variables for each pole (cordx_up1), the Obstacle class has 2 RoundRectangle2D, top and bottom, which are the poles your ball is supposed to avoid. Each obstacle also has a gradient, which is used for both top and bottom. Now I can create 1 obstacle object for 2 aligned poles. You can change the starting x position of the obstacle (although I don't recommend allowing this; x should be set dynamically based on other obstacles' positions), as well as the size for top and bottom. I create 5 obstacle objects.
To keep your game labels organized (by color, message, location, font) while using drawString instead of JLabel, I created a GameLabel class.
I separated the main method into it's own class, named Launcher, which creates a JFrame and adds your game to it; all on the Event Dispatch Thread:
public class Launcher {
public static void main(String[] args) throws IOException {
EventQueue.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.add(new TheGame());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
});
}
}
Your Game class now extends JPanel, so you can override the paint method to render the game. I created 1 Ball and a LinkedList for your obstacles. I chose a LinkedList since inserting/removing from front/end is guaranteed constant time, meaning it'll take the same amount of time to remove/insert no matter how many obstacles are in the list. When the ball passes an obstacle, I remove it from the front of the list and add it to the back. The first obstacle in the list is always the next obstacle.
I saw how some Strings were being re-used, so I created final variables for them, which you can easily change. There's also the currentlyPlaying and isAlive booleans. currentlyPlaying is set to true when the user first clicks (to start the game), and set to false once the user has clicked to restart the game (after he lost).
readyToJump is the flag I use to forward mouse events to your update() method (technically your updatePlayerPostion() method, but it's still "centeralized" within your update() method). It's best to keep all your logic in one place. readyToJump = true would have been the only statement in your listener's method if you weren't relying on calling timer.start() in it. Since update() can't be called unless the timer has started, and mouseEvent starts the timer, we must still handle starting the game in your listener's method.
#SuppressWarnings("serial")
public final class TheGame extends JPanel implements MouseListener, ActionListener {
public static final int WIDTH = 500, HEIGHT = 500;
private final String START_SCORE = "0",
START_MESSAGE = "Get Ready ! Click to Start.",
BACKGROUND_URL = "/res/flappy.png";
private boolean currentlyPlaying, readyToJump, isAlive = true;
private int score;
private Timer timer;
private Image background;
private GameLabel messageLabel, scoreLabel;
private Collection<Obstacle> obstaclesInOrder;
private LinkedList<Obstacle> obstacles;
private Ball ball;
public TheGame() {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
addMouseListener(this);
timer = new Timer(50, this);
background = loadBackgroundImage();
messageLabel = new GameLabel(START_MESSAGE, 150, 240);
scoreLabel = new GameLabel(START_SCORE, 250, 60);
obstacles = new LinkedList<>();
obstacles.removeAll(obstacles);
obstaclesInOrder = Arrays.asList(new Obstacle(175, 20, 45), new Obstacle(320), new Obstacle(460), new Obstacle(585), new Obstacle(700));
obstacles.addAll(obstaclesInOrder);
ball = new Ball(30, 100);
}
#Override
public void mouseClicked(MouseEvent e) {
if (!currentlyPlaying) {
startGame();
} else if (!isAlive) {
reset();
}
readyToJump = true;
}
private void startGame() {
currentlyPlaying = true;
messageLabel.update("");
timer.start();
}
private void endGame() {
isAlive = false;
scoreLabel.update("");
messageLabel.update("Game Over. Your score was " + Integer.toString(score));
timer.stop();
}
private void reset() {
ball.reset();
for (Obstacle obstacle : obstacles)
obstacle.reset();
messageLabel.update(START_MESSAGE, 150, 240);
scoreLabel.update(START_SCORE, 250, 60);
obstacles.removeAll(obstacles);
obstacles.addAll(obstaclesInOrder);
score = 0;
isAlive = true;
currentlyPlaying = false;
repaint();
}
#Override
public void actionPerformed(ActionEvent ae) {
update();
repaint();
}
private void update() {
if (isAlive) {
updateBallPosition();
updateObstaclePositions();
if(ballOutOfBounds() || playerCollidedWithObstacle()) {
endGame();
} else if(ballPassedObstacle()) {
addToScore();
setupNextObstacle();
}
}
}
private void updateBallPosition() {
if (readyToJump) {
readyToJump = false;
ball.jump();
} else {
ball.fall();
}
}
private void updateObstaclePositions() {
for (Obstacle obstacle : obstacles) {
if (obstacle.getX() <= -obstacle.getWidth()) {
obstacle.moveToBack();
continue;
}
obstacle.moveForward();
}
}
private void addToScore() {
scoreLabel.update(Integer.toString(++score));
}
private void setupNextObstacle() {
obstacles.addLast(obstacles.removeFirst());
}
private boolean ballOutOfBounds() {
return ball.getY() >= HEIGHT || ball.getY() <= 0;
}
private boolean ballAtObstacle() {
Obstacle currentObstacle = obstacles.getFirst();
return ball.getX() + ball.getWidth() >= currentObstacle.getX() && ball.getX() <= currentObstacle.getX() + currentObstacle.getWidth();
}
private boolean ballPassedObstacle() {
Obstacle currentObstacle = obstacles.getFirst();
return ball.getX() >= (currentObstacle.getX() + currentObstacle.getWidth());
}
private boolean playerCollidedWithObstacle() {
boolean collided = false;
if(ballAtObstacle()) {
for (Obstacle obstacle : obstacles) {
RoundRectangle2D top = obstacle.getTop();
RoundRectangle2D bottom = obstacle.getBottom();
if (ball.intersects(top.getX(), top.getY(), top.getWidth(), top.getHeight()) || ball.intersects(bottom.getX(), bottom.getY(), bottom.getWidth(), bottom.getHeight())) {
collided = true;
}
}
}
return collided;
}
private Image loadBackgroundImage() {
Image background = null;
URL backgroundPath = getClass().getResource(BACKGROUND_URL);
if(backgroundPath == null) {
background = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
} else {
try {
background = ImageIO.read(backgroundPath);
} catch (IOException e) {
e.printStackTrace();
}
}
return background;
}
#Override
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
Graphics2D g = (Graphics2D) graphics;
g.drawImage(background, 0, 0, null);
ball.paint(g);
for (Obstacle obstacle : obstacles)
obstacle.paint(g);
scoreLabel.paint(g);
messageLabel.paint(g);
}
//...
}
loadBackgroundImage() loads and returns an image. If a problem occurs (image probably isn't there), it returns a black image.
(Most of) The game logic is in the update() method. Although it should all be in there, we can't due to a design flaw (from you using Timer which manages update(), and you start the timer in a listener, therefore we needed some logic in the listener). Your logic should be easy to read, and every execution step should be monitored and set by highest priority to lowest.
First, I check to make sure the ball hasn't collided with anything or gone out of bounds. If one of those things occur, I end the game.
If not, I check to see if the player has passed an obstacle. If the player passes an obstacle, I add to the score:
private void update() {
if (ballOutOfBounds() || playerCollidedWithObstacle()) {
endGame();
} else if (ballPassedObstacle()) {
addToScore();
setupNextObstacle();
}
updateBallPosition();
updateObstaclePositions();
}
I then finally update the positions.
To avoid constantly comparing the player's position and the obstacle's position (to see if the player has passed it), I created a boolean ballAtObstacle() method, which checks if the player is at an obstacle. Only then do I compare positions:
private boolean playerCollidedWithObstacle() {
boolean collided = false;
if (ballAtObstacle()) {
for (Obstacle obstacle : obstacles) {
RoundRectangle2D top = obstacle.getTop();
RoundRectangle2D bottom = obstacle.getBottom();
if (ball.intersects(top.getX(), top.getY(), top.getWidth(), top.getHeight()) || ball.intersects(bottom.getX(), bottom.getY(), bottom.getWidth(), bottom.getHeight())) {
collided = true;
}
}
}
return collided;
}
The ball.intersects method call is a bit messy. I did this so the shape didn't have to be specific, although you could declare the intersects method as boolean intersects(Shape shape).
Finally, I gave it a more flappy bird feel by increasing the fall speed as you fall. When you jump again, it goes back to normal. The longer it takes you to jump, the faster you'll fall. If you don't like this feature, and don't know how to remove it, let me know and I'll show you how.
The other classes involved:
GameLabel.java
public class GameLabel {
private String message;
private Font font;
private Color color;
private int x, y;
public GameLabel(String message, int x, int y, Color color, Font font) {
update(message, x, y, color, font);
}
public GameLabel(String message, int x, int y) {
this(message, x, y, Color.BLACK, new Font("Matura MT Script Capitals", Font.ROMAN_BASELINE, 20));
}
public GameLabel() {
this("", 0, 0);
}
public void setMessage(String message) {
this.message = message;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setColor(Color color) {
this.color = color;
}
public void setFont(Font font) {
this.font = font;
}
public final void update(String message, int x, int y, Color color, Font font) {
this.message = message;
this.x = x;
this.y = y;
this.color = color;
this.font = font;
}
public void update(String message, int x, int y) {
update(message, x, y, color, font);
}
public void update(String message) {
update(message, x, y);
}
public void paint(Graphics2D g) {
g.setFont(font);
g.setColor(color);
g.drawString(message, x, y);
}
public Font getFont() {
return font;
}
public Color getColor() {
return color;
}
public String getMessage() {
return message;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
Obstacle.java
public class Obstacle {
public static final int DEFAULT_TOP_HEIGHT = 175;
public static final int DEFAULT_BOTTOM_HEIGHT = 175;
public static final int DEFAULT_WIDTH = 30;
public static final int DEFAULT_ARCH_WIDTH = 20;
public static final int DEFAULT_ARCH_HEIGHT = 20;
public static final int DEFAULT_TOP_INSET = -5;
public static final int DEFAULT_BOTTOM_INSET = TheGame.HEIGHT + 5;
private RoundRectangle2D top, bottom;
private BasicStroke stroke;
private GradientPaint gradient;
private int initialX, x, width;
public Obstacle(int x, int width, int topHeight, int bottomHeight) {
this.x = initialX = x;
this.width = width;
top = new RoundRectangle2D.Double(x, DEFAULT_TOP_INSET, width, topHeight, DEFAULT_ARCH_WIDTH, DEFAULT_ARCH_HEIGHT);
bottom = new RoundRectangle2D.Double(x, DEFAULT_BOTTOM_INSET-bottomHeight, width, bottomHeight, DEFAULT_ARCH_WIDTH, DEFAULT_ARCH_HEIGHT);
stroke = new BasicStroke(10, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL, 20.0f, new float[] { 10.0f }, 0.0f);
gradient = new GradientPaint(20, 0, Color.DARK_GRAY, 0, 10, Color.GRAY, true);
}
public Obstacle(int x, int topHeight, int bottomHeight) {
this(x, DEFAULT_WIDTH, topHeight, bottomHeight);
}
public void reset() {
x = initialX;
top.setRoundRect(initialX, top.getY(), top.getWidth(), top.getHeight(), top.getArcWidth(), top.getArcHeight());
bottom.setRoundRect(initialX, bottom.getY(), bottom.getWidth(), bottom.getHeight(), bottom.getArcWidth(), bottom.getArcHeight());
}
public Obstacle(int x, int width) {
this(x, width, DEFAULT_TOP_HEIGHT, DEFAULT_BOTTOM_HEIGHT);
}
public Obstacle(int x) {
this(x, DEFAULT_WIDTH);
}
public void moveToBack() {
x = 600;
}
public void moveForward() {
x -= 5;
top.setRoundRect(x, top.getY(), top.getWidth(), top.getHeight(), top.getArcWidth(), top.getArcHeight());
bottom.setRoundRect(x, bottom.getY(), bottom.getWidth(), bottom.getHeight(), bottom.getArcWidth(), bottom.getArcHeight());
}
public RoundRectangle2D getTop() {
return top;
}
public RoundRectangle2D getBottom() {
return bottom;
}
public int getX() {
return x;
}
public int getWidth() {
return width;
}
public void paint(Graphics2D g) {
g.setPaint(gradient);
g.setStroke(stroke);
g.fill(top);
g.fill(bottom);
}
}
Ball.java
public class Ball {
public static final int DEFAULT_DROP_SPEED = 7;
private Ellipse2D ball;
private GradientPaint gradient;
private int initialY, x, y, width = 30, height = 30, dropSpeed = DEFAULT_DROP_SPEED;
public Ball(int x, int y) {
this.x = x;
this.y = initialY = y;
width = height = 30;
ball = new Ellipse2D.Double(x, y, width, height);
gradient = new GradientPaint(30, 0, Color.BLACK, 0, 20, Color.GREEN, true);
}
public void reset() {
y = initialY;
updateBall();
}
public void jump() {
dropSpeed = DEFAULT_DROP_SPEED;
y -= 40;
updateBall();
}
public void fall() {
y += dropSpeed++;
updateBall();
}
private void updateBall() {
ball.setFrame(x, y, width, height);
}
public boolean intersects(double x, double y, double w, double h) {
return ball.intersects(x, y, w, h);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getWidth() {
return width;
}
public int getHeight() {
return width;
}
public void paint(Graphics2D g) {
g.setPaint(gradient);
g.fill(ball);
}
public void moveForward() {
x += 7;
}
}
Reinitialize all the components by adding the initialization code to one method(resetAll) and call that method when u want to reinitailize
Below is an example:
import java.awt.*;
import java.awt.event.*;
public class ResetTest extends Frame{
Button b;
TextField tf;
Frame f;
Panel p;
public ResetTest(){
f=this; //intialize frame f to this to have access to our frame in event handler methods
resetAll(); //first time call to resetAll will initialize all the parameters of the frame
f.pack();
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent we){
System.exit(0);
}
});
this.setLayout(new FlowLayout(FlowLayout.CENTER,20,20));
this.setVisible(true);
}
/**
This method will be called on click of button to
reset all the parameters of the frame so that we
get fresh new frame, everything as it was before.
**/
public void resetAll(){
b=new Button("Reset");
p=new Panel(new FlowLayout(FlowLayout.CENTER,20,20));
p.add(b);
tf = new TextField("Edit And Reset");
p.add(tf);
b.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent ae){
remove(p); //remove the panel that contains all our components
resetAll(); // reset all the components
f.pack(); //refreshes the view of frame when everything is reset.
}
});
add(p);
}
}
class NewClass{
public static void main(String[] args) {
ResetTest fReset = new ResetTest();
}
}
Hello everyone I am trying to make a game where the user plays as some kind of character, and trys to collect coins while avoiding monsters that spawn. My program compiles with no error, but nothing is showing up when I run the applet. This could be because of the order of extension I have everything in but I am not sure. Any help would be greatly appreciated (this is for a final school project for my intro to Java class). Here is the code, I know it is long but it all pertains to the question at hand:
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.util.*;
public class Sprite extends JApplet
{
Image image;
int x, y;
boolean isVisible;
public Sprite()
{
isVisible = false;
image = null;
}
public Sprite(Image i)
{
isVisible = true;
image = i;
x = 10;
y = 10;
}
public void setImage(Image img)
{
image = img;
isVisible = true;
}
public void setLocation(int _x, int _y)
{
x = _x;
y = _y;
}
public Rectangle getDimensions()
{
return new Rectangle(x, y, image.getWidth(null), image.getHeight(null));
}
public boolean intersects(Sprite s)
{
return getDimensions().intersects(s.getDimensions());
}
public void setVisible(boolean vis)
{
isVisible = vis;
}
public void paintComponent(Graphics g)
{
if(isVisible)
{
g.drawImage(image, x, y, null);
}
}
}
class Coins extends Sprite
{
int amount;
public Coins(int amt)
{
amount = amt;
}
public int getAmount()
{
return amount;
}
public void setAmount(int amt)
{
amount = amt;
}
}
class AnimateSprite extends Sprite
{
int speed = 5;
int directionX = 1, directionY = 1;
int healthPoints = 100;
final boolean DEAD = false;
final boolean ALIVE = true;
public void moveUp()
{
y -= speed;
}
public void moveDown()
{
y += speed;
}
public void moveLeft()
{
x -= speed;
}
public void moveRight()
{
x += speed;
}
public int getHealthPoints()
{
return healthPoints;
}
public void setHealthPoints(int hp)
{
healthPoints = hp;
}
public boolean hit(int amt)
{
healthPoints -= amt;
if(healthPoints < 0)
return DEAD;
else
return ALIVE;
}
}
class Game extends AnimateSprite implements Runnable, KeyListener
{
AnimateSprite user;
AnimateSprite monster, troll;
Coins ten, twenty;
Thread thread;
Random r;
public void init()
{
r = new Random();
user = new AnimateSprite();
user.setImage(getImage(getCodeBase(), "player.gif"));
monster = new AnimateSprite();
monster.setImage(getImage(getCodeBase(), "monster.gif"));
troll = new AnimateSprite();
troll.setImage(getImage(getCodeBase(), "monster.gif"));
troll.setLocation(350, 350);
setupCoins();
setFocusable(true);
addKeyListener(this);
thread = new Thread(this);
thread.start();
}
public void setupCoins()
{
ten = new Coins(10);
twenty = new Coins(20);
ten.setLocation(400, 350);
twenty.setLocation(450, 50);
ten.setImage(getImage(getCodeBase(), "coins.gif"));
twenty.setImage(getImage(getCodeBase(), "coins.gif"));
}
public void keyPressed(KeyEvent ke) //Event handling
{
int key = ke.getKeyCode();
if(key == KeyEvent.VK_UP)
user.moveUp();
else if(key == KeyEvent.VK_DOWN)
user.moveDown();
else if(key == KeyEvent.VK_LEFT)
user.moveLeft();
else if(key == KeyEvent.VK_RIGHT)
user.moveRight();
}
public void keyReleased(KeyEvent ke) {}
public void keyTyped(KeyEvent ke) {}
public void update(Graphics g) {paint(g);}
public void paint(Graphics g)
{
g.clearRect(0, 0, this.getWidth(), this.getHeight());
ten.paintComponent(g);
twenty.paintComponent(g);
monster.setLocation(r.nextInt(10) - 5 + monster.x, r.nextInt(10 - 5 + monster.y));
monster.paintComponent(g);
user.paintComponent(g);
if(user.intersects(monster))
{
g.setFont(new Font("Serif", Font.BOLD, 26));
g.drawString("YOU HAVE DIED, YOU LOSE!", 20, 100); //Prints this when you lose
thread.interrupt(); //Stopping the thread if you die
}
}
public void run()
{
try //Try catch
{
while(true) //Only does this while when the boolean is true
{
repaint();
Thread.sleep(10); //Thread sleeps
}
} catch(Exception e) {} //Exception handling
}
}
Your order of inheritance seems odd, but its not whats causing the problem. Take a look at this website: http://www.dreamincode.net/forums/topic/28410-application-to-japplet-and-reverse/
Java Applets need to have init(), start(), stop(), and destroy(). You will need to put these methods in your Sprite class for the Applet to function.