When gameover is true and I call the startGame method from the DOWN button KeyListener, it breaks my game and doesn't allow me to click the exit button on the JFrame and the paddle doesn't work anymore. Please help.
import javax.swing.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.Image;
public class AustinsBrickBreaker {
JFrame window;
DrawPanel panel;
public AustinsBrickBreaker() {
window = new JFrame("Brick Breaker");
panel = new DrawPanel();
window.setSize(800, 592);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.getContentPane().add(panel);
window.setLocationRelativeTo(null);
window.setVisible(true);
window.setResizable(false);
}
public void go() {
panel.startGame();
}
public static void main(String[] args) {
AustinsBrickBreaker game = new AustinsBrickBreaker();
game.go();
}
}
#SuppressWarnings("serial")
class DrawPanel extends JPanel implements KeyListener {
final int WIDTH = 800, HEIGHT = 592;
BufferedImage buffer;
public static Brick[][] bricks = new Brick[3][5];
Paddle paddle;
Ball ball;
int score = 0;
int lives = 3;
boolean gameover = false;
Image brickImage = Toolkit.getDefaultToolkit().getImage("brick.png");
Image brickImage2 = Toolkit.getDefaultToolkit().getImage("brick2.png");
Image brickImage3 = Toolkit.getDefaultToolkit().getImage("brick3.png");
Image brickImage4 = Toolkit.getDefaultToolkit().getImage("brick4.png");
Image brickImage5 = Toolkit.getDefaultToolkit().getImage("brick5.png");
Image paddleImage = Toolkit.getDefaultToolkit().getImage("paddle.png");
Image background = Toolkit.getDefaultToolkit().getImage("background.jpg");
Image ballImage = Toolkit.getDefaultToolkit().getImage("ball.png");
Image heartImage = Toolkit.getDefaultToolkit().getImage("heart.png");
public DrawPanel() {
setIgnoreRepaint(true);
addKeyListener(this);
setFocusable(true);
}
public void keyTyped(KeyEvent e) {}
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_LEFT) paddle.left = true;
if (key == KeyEvent.VK_RIGHT) paddle.right = true;
if (key == KeyEvent.VK_UP && gameover) {
gameover = false;
score = 0;
lives = 3;
startGame();
}
if (key == KeyEvent.VK_DOWN && gameover) System.exit(0);
}
public void keyReleased(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_LEFT) paddle.left = false;
if (key == KeyEvent.VK_RIGHT) paddle.right = false;
}
public int count() {
int count = 0;
for (int r = 0; r < DrawPanel.bricks.length; r++)
for (int c = 0; c < DrawPanel.bricks[r].length; c++)
if (!bricks[r][c].visible) count++;
else
break;
int returner = 0;
if (count == 15) returner = 1;
return returner;
}
public void initialize() {
buffer = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
for (int r = 0; r < DrawPanel.bricks.length; r++)
for (int c = 0; c < DrawPanel.bricks[r].length; c++)
DrawPanel.bricks[r][c] = new Brick(c * 150 + 50, r * 60 + 30, 100, 50);
ball = new Ball(390, 200, 20, 20, 10);
paddle = new Paddle(350, 510, 100, 20, 8);
}
public void updateMovement() {
paddle.move();
ball.move();
}
public void checkCollisions() {
if (paddle.x <= 20) paddle.x = 20;
if (paddle.x >= 679) paddle.x = 679;
if (ball.x < 21) {
ball.left = false;
ball.right = true;
}
if (ball.x > 761) {
ball.left = true;
ball.right = false;
}
if (ball.y < 21) {
ball.up = false;
ball.down = true;
}
if (paddle.getBounds().intersects(ball.getBounds())) ball.swap();
for (int r = 0; r < DrawPanel.bricks.length; r++)
for (int c = 0; c < DrawPanel.bricks[r].length; c++) {
if (ball.getBounds().intersects(DrawPanel.bricks[r][c].getBounds()) && !DrawPanel.bricks[r][c].collision) {
ball.swap();
bricks[r][c].collide();
score += 10;
}
}
}
public void drawBuffer() {
Graphics2D b = buffer.createGraphics();
b.drawImage(background, 0, 0, null);
if (!gameover) {
for (int l = 0; l < lives; l++)
b.drawImage(heartImage, 20 * l + 620, 535, null);
b.drawString("Score: " + score, 700, 550);
b.drawImage(paddleImage, paddle.getX(), paddle.getY(), null);
b.drawImage(ballImage, ball.getX(), ball.getY(), null);
for (int r = 0; r < DrawPanel.bricks.length; r++)
for (int c = 0; c < DrawPanel.bricks[r].length; c++) {
if (bricks[r][c].visible == true)
if (bricks[r][c].colour == 1)
b.drawImage(brickImage, DrawPanel.bricks[r][c].getX(), DrawPanel.bricks[r][c].getY(), null);
else if (bricks[r][c].colour == 2)
b.drawImage(brickImage2, DrawPanel.bricks[r][c].getX(), DrawPanel.bricks[r][c].getY(), null);
else if (bricks[r][c].colour == 3)
b.drawImage(brickImage3, DrawPanel.bricks[r][c].getX(), DrawPanel.bricks[r][c].getY(), null);
else if (bricks[r][c].colour == 4)
b.drawImage(brickImage4, DrawPanel.bricks[r][c].getX(), DrawPanel.bricks[r][c].getY(), null);
else if (bricks[r][c].colour == 5)
b.drawImage(brickImage5, DrawPanel.bricks[r][c].getX(), DrawPanel.bricks[r][c].getY(), null);
}
b.dispose();
} else {
b.drawString("G A M E O V E R !", 340, 300);
b.drawString("G A M E O V E R !", 341, 300);
b.drawString("G A M E O V E R !", 342, 300);
b.drawString("Press ↑ To Play Again!", 332, 320);
b.drawString("Press ↓ To Exit :(", 342, 340);
}
}
public void drawScreen() {
Graphics2D g = (Graphics2D) this.getGraphics();
g.drawImage(buffer, 0, 0, this);
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
public void startGame() {
initialize();
while (!gameover) {
try {
updateMovement();
checkCollisions();
drawBuffer();
drawScreen();
Thread.sleep(15);
if (ball.y > 562 && lives != -1) {
Thread.sleep(500);
lives -= 1;
ball.x = 390;
ball.y = 200;
ball.left = false;
ball.right = false;
paddle.x = 350;
}
if (lives == -1) {
Thread.sleep(500);
gameover = true;
drawBuffer();
drawScreen();
}
//Replace Bricks
if (count() == 1) {
Thread.sleep(500);
startGame();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/*--|--|--|--| GAME CLASSES |--|--|--|--*/
class Brick {
int x, y, width, height, colour;
boolean collision, visible;
public Brick(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.collision = false;
this.visible = true;
this.colour = (int) Math.ceil(Math.random() * 5);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public Rectangle getBounds() {
return new Rectangle(getX(), getY(), getWidth(), getHeight());
}
public void collide() {
if (collision == false) {
visible = false;
collision = true;
}
}
}
class Paddle {
int x, y, width, height, speed;
boolean left, right;
public Paddle(int x, int y, int w, int h, int s) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.speed = s;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public Rectangle getBounds() {
return new Rectangle(getX(), getY(), getWidth(), getHeight());
}
public void move() {
if (left) x -= speed;
if (right) x += speed;
}
}
class Ball {
int x, y, width, height, speed;
boolean up, down, left, right;
public Ball(int x, int y, int w, int h, int s) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.speed = s;
this.up = false;
this.down = true;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public Rectangle getBounds() {
return new Rectangle(getX(), getY(), getWidth(), getHeight());
}
public void move() {
if (up) y -= speed;
if (down) y += speed;
if (left) x -= (float) Math.ceil(Math.random() * 5);
if (right) x += (float) Math.ceil(Math.random() * 5);
}
public void swap() {
if (up) {
down = true;
up = false;
} else if (down) {
up = true;
down = false;
}
double r = Math.random();
if (r <= 0.5) {
left = false;
right = true;
} else if (r > 0.5) {
left = true;
right = false;
} else left = true;
}
}
Your code completely ignores Swing threading rules, and this is somehow allowed when first run, since when first run, the startGame() method is called in the main thread off of the Swing event thread. But when it is called a second time, it is then called on the event thread, and this time, all those sleep calls put the Swing event thread and your application to sleep. The solution: learn about Swing threading rules, and have your application obey these rules, including not calling Thread.sleep, or having forever loops called on the event thread.
see: Lesson: Concurrency in Swing.
This is not an answer, but a very long comment
First, getGraphics is NOT how custom painting works in Swing and you should never use it.
Start by taking a look at Painting in AWT and Swing and Performing Custom Painting for more details about how painting works in Swing.
Swing uses a passive rendering approaching, meaning that it's painting process can take place at any time, for any reason most without your interaction or knowledge, under your current approach, you could end up with intermediate flickering which be near impossible to diagnose or repeat.
If you want control over the painting (active painting), have a look at BufferStrategy and BufferStrategy and BufferCapabilities
Second, don't use KeyListener, there are a very limited number of circumstances I might consider using KeyListener, but this is not one of them and when you find yourself wanting to respond to key events, you should start with the key bindings API
Third, don't use Toolkit.getDefaultToolkit().getImage, but instead use ImageIO, it supports more images, it loads the image first before returning (rather than using a background thread) and throws an IOException when the image can't be loaded. See Reading/Loading an Image for more details
Fourth, you are violating the single thread rules of Swing. Basically, because the way the system works, main is called within what is called the "main" thread, but Swing runs in it's own thread (AKA The Event Dispatching Thread).
So when you first start, go is running in the "main" thread, but when you call start from your KeyListener, you're running within the EDT, meaning that you "game-loop" will block the EDT and nothing will ever paint again and the user won't be able to interact with your program.
See Concurrency in Swing for more details and How to Use Swing Timers for a possible solution
Related
I have been working on a flappy bird clone so I can get more practice programming. Everything in the game works, however the game has frame skips and lag drops, and I do not know how to make Java programs run more smoothly. Am I supposed to measure the amount of time a method takes and try to shorten that, or do I do something else? I have seen people explain how to program Java games, but there is hardly anything on improving the performance. Any advice would be helpful. Thank you.
Hazards class
package entity;
import java.util.ArrayList;
public class Hazards {
public ArrayList<Horizontal> hors;
public ArrayList<Vertical> verts;
public Hazards(int width, int height, int thickness) {
hors = new ArrayList<Horizontal>();
hors.add(new Horizontal(0, 0, width, thickness));
hors.add(new Horizontal(0, height-thickness, width, thickness));
verts = new ArrayList<Vertical>();
}
}
Horizontal class
package entity;
import java.awt.Rectangle;
public class Horizontal {
public int xPos, yPos, width, height;
public Rectangle bounds;
public Horizontal(int x, int y, int w, int h) {
this.xPos = x;
this.yPos = y;
this.width = w;
this.height = h;
this.bounds = new Rectangle(x, y, w, h);
}
public void updateBounds(int x, int y, int w, int h) {
this.xPos = x;
this.yPos = y;
this.width = w;
this.height = h;
this.bounds = new Rectangle(x, y, w, h);
}
}
Vertical class
package entity;
import java.awt.Rectangle;
import java.util.Random;
public class Vertical {
public int xPos, width, gapSize, gapPos;
public boolean scoredOn = false;
public Rectangle top, bottom;
public Vertical(int xPos, int width, int roofHeight, int floorHeight, int gapSize) {
this.xPos = xPos;
this.width = width;
this.gapPos = new Random().nextInt(floorHeight - gapSize) + roofHeight;
this.top = new Rectangle();
this.bottom = new Rectangle();
this.top.setBounds(xPos, 0, width, gapPos);
this.bottom.setBounds(xPos, gapPos + gapSize, width, floorHeight - roofHeight + top.height + gapSize);
}
}
Content class
package main;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.JPanel;
public class Content extends JPanel {
private static final long serialVersionUID = 1L;
private Engine e;
private Rectangle bounds;
public Content(Engine engine) {
e = engine;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.clearRect(0, 0, this.getWidth(), this.getHeight());
// Roof and floor
g.setColor(Color.black);
for (int x = 0; x < e.e.hors.size(); x++) {
bounds = e.e.hors.get(x).bounds;
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
}
// Pipes
g.setColor(Color.black);
for(int x = 0; x < e.e.verts.size(); x++) {
bounds = e.e.verts.get(x).top;
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
bounds = e.e.verts.get(x).bottom;
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
}
// Player
g.setColor(Color.black);
g.fillRect((int) e.p.xPos, (int) e.p.yPos, e.p.size, e.p.size);
// Score
g.setColor(Color.blue);
g.setFont(new Font("Monospaced", Font.PLAIN, 40));
g.drawString(Integer.toString(e.p.score), e.width/2, 80);
}
}
Engine class
package main;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import entity.Hazards;
import entity.Vertical;
import screen.TitleScreen;
public class Engine implements Runnable {
public JFrame f;
public String title = "Flappy Bird";
public int width = 500, height = 500;
public Content c;
public boolean running = false;
public boolean playing = false;
public Thread t;
public Player p;
public Hazards e;
public TitleScreen ts;
public JPanel mainPanel;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Engine e = new Engine();
e.execute();
}
});
}
public void execute() {
ts = new TitleScreen(width, height);
e = new Hazards(width, height, 30);
p = new Player(this);
c = new Content(this);
c.setPreferredSize(new Dimension(width, height));
c.setLayout(null);
c.addKeyListener(p);
mainPanel = new JPanel();
mainPanel.setPreferredSize(new Dimension(width, height));
mainPanel.setLayout(null);
f = new JFrame();
f.setTitle(title);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setResizable(false);
f.add(c);
// f.add(mainPanel);
f.pack();
f.createBufferStrategy(2);
f.setLayout(null);
f.setLocationRelativeTo(null);
f.setVisible(true);
c.requestFocus();
//ts.setScreen(mainPanel);
start();
}
public synchronized void start() {
if (running)
return;
running = true;
t = new Thread(this);
t.start();
}
public synchronized void stop() {
if (!running)
return;
running = false;
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void run() {
long lastime = System.nanoTime();
double AmountOfTicks = 60;
double ns = 1000000000 / AmountOfTicks;
double delta = 0;
int tick = 0;
while (running) {
long now = System.nanoTime();
delta += (now - lastime) / ns;
lastime = now;
if (delta >= 1) {
// Call all updates here
if (playing) {
p.updatePos();
tick++;
if (tick == 60) {
tick = 0;
p.distance += p.speed;
System.out.println(p.distance);
if ((p.distance % 4) == 0) {
System.out.println("Making new pipes-----------------------------------------------------");
e.verts.add(new Vertical(600, 10, 30, height - 30, 100));
}
}
for (int x = 0; x < e.verts.size(); x++) {
e.verts.get(x).top.x -= p.speed;
e.verts.get(x).bottom.x -= p.speed;
if(e.verts.get(x).top.x<-50) {
e.verts.remove(x);
System.out.println("removed a pipe");
}
if(p.xPos>e.verts.get(x).top.x && !e.verts.get(x).scoredOn) {
e.verts.get(x).scoredOn = true;
p.score++;
}
}
}
mainPanel.revalidate();
mainPanel.repaint();
f.revalidate();
f.repaint();
delta--;
}
}
}
}
Player class
package main;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Player implements KeyListener {
public int size = 10;
public double xPos = 50;
public double yPos = 240;
public double gravity = 3.4;
public double jumpForce = 16.6;
public double weight = 1;
public int speed = 2;
public int score = 0;
public int distance = 0;
public boolean jumping = false;
public double jumpTime = 10;
public int timed = 0;
public Rectangle bounds, temp, top, bottom;
public Engine en;
public Player(Engine engine) {
en = engine;
bounds = new Rectangle((int)xPos, (int)yPos, size, size);
}
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_W && en.playing) {
jumping = true;
} else if (e.getKeyCode() == KeyEvent.VK_SPACE) {
en.playing = !en.playing;
}
if(jumping) {
timed = 0;
jumpForce = 16.6;
}
}
public void keyReleased(KeyEvent e) {
}
public void updatePos() {
// collide with floor or ceiling
for(int x = 0; x < en.e.hors.size(); x++) {
temp = en.e.hors.get(x).bounds;
if(bounds.intersects(temp)) {
en.playing = false;
jumping = false;
timed = 0;
jumpForce = 0;
yPos = 240;
score = 0;
en.e.verts.clear();
distance = 0;
gravity = 3.8;
}
}
// collide with pipe
for(int x =0; x <en.e.verts.size();x++) {
top = en.e.verts.get(x).top;
bottom = en.e.verts.get(x).bottom;
if(bounds.intersects(top)||bounds.intersects(bottom)) {
en.playing = false;
jumping = false;
timed = 0;
jumpForce = 0;
yPos = 240;
score = 0;
gravity =3.4;
en.e.verts.clear();
distance = 0;
}
}
if (jumping && en.playing) {
gravity = 3.4;
yPos -= jumpForce;
jumpForce -= weight;
if (jumpForce == 0) {
jumping = false;
jumpForce = 16.6;
}
}
//if(!jumping && en.playing) {
gravity += 0.1;
//}
System.out.println(gravity);
yPos += gravity;
bounds.setBounds((int)xPos, (int)yPos, size, size);
}
}
EDIT
: it seems that in my move() method, java has to skip one of the 2 if statements
Keeping it simple. I'm trying to make a GUI in java that models the following class Truck behavior: the blue squares are supposed to run up their diagonales, which they do. But when they are supposed to bounce back once they reach the edge point of the square they just fly away. I've placed a condition to prevent this, but it never passes. I'll upload GUI and Drawable, but I don't think they are needed.
Class Truck:
package construction_site;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.plaf.ButtonUI;
public class Truck extends Thread implements Drawable {
private boolean isFull = false;
private int x, y;
private int capacity;
private Panel panel;
static int r = 20;
private int dx, dy;
private Site site;
private Building building;
public Truck(int x, int y, Panel panel, Building building, Site site) {
this.x = x;
this.y = y;
this.building = building;
this.panel = panel;
this.site = site;
this.start();
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
#Override
public void run() {
super.run();
while (true) {
move();
panel.repaint();
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setDirection(int i) {
if (i == 0) {
dx = -1;
dy = -1;
}
if (i == 1) {
dx = 1;
dy = -1;
}
if (i == 2) {
dx = -1;
dy = 1;
}
if (i == 3) {
dx = 1;
dy = 1;
}
}
private void move() {
if (site.truckOnSite(x, y)) {
site.loadTruck(this);
dx *= -1;
dy *= -1;
}
if (building.containsTruck(this)) {
building.unloadTruck(this);
dx *= -1;
dy *= -1;
}
x += dx;
y += dy;
}
public int getDx() {
return dx;
}
public void setFull(boolean isFull) {
this.isFull = isFull;
}
#Override
public void draw(Graphics g) {
g.setColor(Color.BLUE);
if (isFull)
g.fillRect(x - 10, y - 10, r, r);
else
g.drawRect(x - 10, y - 10, r, r);
}
}
Class Panel:
package construction_site;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.util.ArrayList;
import javax.swing.JPanel;
public class Panel extends JPanel {
private ArrayList<Drawable> drawables = new ArrayList<>();
private Site site;
private Building[] buildings = new Building[4];
private Truck[] trucks = new Truck[4];
private Van[] vans = new Van[4];
public Panel(int w, int h) {
setPreferredSize(new Dimension(w, h));
site = new Site(100, 75, 500, 500, this);
drawables.add(site);
buildings[0] = new Building(100, 75, this);
buildings[1] = new Building(100 + 500, 75, this);
buildings[2] = new Building(100, 75 + 500, this);
buildings[3] = new Building(100 + 500, 75 + 500, this);
for (Building b : buildings)
drawables.add(b);
trucks[0] = new Truck(100 + 250, 75 + 250, this, buildings[0], site);
trucks[1] = new Truck(100 + 250, 75 + 250, this, buildings[1], site);
trucks[2] = new Truck(100 + 250, 75 + 250, this, buildings[2], site);
trucks[3] = new Truck(100 + 250, 75 + 250, this, buildings[3], site);
for (int i = 0; i < 4; i++)
trucks[i].setDirection(i);
for (Truck t : trucks)
drawables.add(t);
}
#Override
public void paint(Graphics g) {
super.paint(g);
for (Drawable d : drawables)
d.draw(g);
g.setColor(Color.GREEN);
for (int i = 1; i <= 3; i++)
g.drawLine(buildings[0].getX(), buildings[0].getY(), buildings[i].getX(), buildings[i].getY());
g.drawLine(buildings[1].getX(), buildings[1].getY(), buildings[2].getX(), buildings[2].getY());
}
}
Class Building:
package construction_site;
import java.awt.Color;
import java.awt.Graphics;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Building extends Thread implements Drawable {
private Panel panel;
private boolean done = false;
private int x, y;
static int w = 100;
static int h = 100;
private ReentrantLock lock = new ReentrantLock();
private Condition insufficientMaterial = lock.newCondition();
private Condition insufficientMisc = lock.newCondition();
private Condition sufficient = lock.newCondition();
private int material = 0;
private int misc = 0;
private int spent = 0;
public Building(int x, int y, Panel panel) {
this.x = x;
this.y = y;
this.panel = panel;
this.start();
}
#Override
public void run() {
super.run();
lock.lock();
while(material < 100 )
try {
insufficientMaterial.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
spent += 150;
misc -= 50;
material -= 100;
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void unloadVan(Van v) {
lock.lock();
System.out.println("Truck got in");
if (material >= 100 && misc >=50) {
}
misc += 50;
v.setCapacity(0);
insufficientMisc.signalAll();
lock.unlock();
}
public void unloadTruck(Truck t) {
lock.lock();
material += 10000;
t.setCapacity(0);
t.setFull(false);
insufficientMaterial.signalAll();
lock.unlock();
}
public void setMaterial(int material) {
this.material = material;
}
public void setMisc(int misc) {
this.misc = misc;
}
public boolean containsVan(int x2, int y2) {
return ( Math.sqrt((x - x2)*(x - x2) + (y - y2)*(y - y2)) <= h/2 );
}
public boolean containsTruck(Truck t) {
return (x == t.getX() && y == t.getY());
}
public int getX() {
return x;
}
public int getY() {
return y;
}
#Override
public void draw(Graphics g) {
g.setColor(Color.BLACK);
g.drawRect(x - 50, y - 50, 100, 100);
}
}
Class Site:
package construction_site;
import java.awt.Color;
import java.awt.Graphics;
public class Site implements Drawable {
private int x, y, w, h;
private Panel panel;
public Site(int x, int y, int w, int h, Panel panel) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.panel = panel;
}
public boolean contains(int x2, int y2) {
return (x == x2 && y == 2);
}
public boolean truckOnSite(int x2, int y2) {
return ( x2 == x + w / 2 && y2 == y + h / 2);
}
public boolean vanOnSite(int x2, int y2) {
return ( (x2 == x && y2 == y + h / 2) || (x2 == x + w && y2 == y + h / 2) );
}
public void loadVan(Van v) {
v.setCapacity(5000);
}
public void loadTruck(Truck t) {
t.setCapacity(10000);
t.setFull(true);
}
#Override
public void draw(Graphics g) {
g.setColor(Color.GRAY);
g.fillRect(x, y, w, h);
}
}
As it turned out much later, it doesn't seem truncation errors were the problem at hand. It was a mistake at setting the trucks' starting point and the condition that decides when they're supposed to turn the other way. What looked like the truck bouncing off, was actually the oppsoite truck from the same diagonal passing through. A simple change in direction of my Truck's move() method did the trick.
public void setDirection(int i) {
if (i == 0) {
dx = -1;
dy = -1;
}
if (i == 1) {
dx = 1;
dy = -1;
}
if (i == 2) {
dx = -1;
dy = 1;
}
if (i == 3) {
dx = 1;
dy = 1;
}
}
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.
I'm trying to make a match 3 game. I am trying to create some visual aid to what is actually happening by first marking the gems that need to be deleted "black", and after that letting gravity do it's job. I'm struggling to do this, I called repaint(); after I marked them "black", but it doesn't seem to work. I also tried adding in revalidate(); as suggested in another question but that doesn't seem to fix the problem either. Here's the piece of code that's troubling me.
Trouble code:
public void deletePattern(Set<Gem> gemsToDelete){
for(Gem gem : gemsToDelete)
gem.setType(7);
repaint(); //This doesn't seem to work
doGravity();
switchedBack = true;
checkPattern();
}
I want to repaint the board before doGravity() and after the enhanced for loop. Could it be that I'm not using the thread correctly in the doGravity() method?
Here's the full code:
Board.java
package Game;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.LinkedHashSet;
import java.util.Set;
public class Board extends JPanel{
final int BOARDWIDTH = 8;
final int BOARDHEIGHT = 8;
private static final Color COLORS[] = { new Color(255, 0, 0), new Color(255, 128, 0), new Color(255, 255, 0), new Color(0, 255, 0), new Color(0, 255, 255), new Color(0, 0, 255), new Color(127, 0, 255), new Color(0, 0, 0), new Color(0, 0, 0), new Color(255, 255, 255)};
boolean isAlive, isPattern, switchedBack;
boolean isFirstSelected = false;
Gem[][] gems;
int fromX, fromY, toX, toY;
public Board() {
gems = new Gem[BOARDWIDTH][BOARDHEIGHT];
addMouseListener(new MouseInputAdapter());
}
int cellWidth() { return (int) getSize().getWidth() / BOARDWIDTH; }
int cellHeight() { return (int) getSize().getHeight() / BOARDHEIGHT; }
public void start(){
isPattern = switchedBack = false;
isAlive = true;
fillBoard();
checkPattern();
switchedBack = false;
}
public void paint(Graphics g) {
super.paint(g);
for (int x = 0; x < BOARDWIDTH; x++) {
for (int y = 0; y < BOARDHEIGHT; y++)
drawCell(g, x, y, gems[x][y]);
}
}
public void fillBoard(){
for (int x = 0; x < BOARDWIDTH; x++) {
for (int y = 0; y < BOARDHEIGHT; y++)
gems[x][y] = new Gem();
}
}
public void drawCell(Graphics g, int x, int y, Gem gem) {
x = x * cellWidth();
y = y * cellHeight();
g.setColor(COLORS[gem.getType()]);
g.fillRect(x, y, x + cellWidth(), y + cellHeight());
}
class MouseInputAdapter extends MouseAdapter { #Override public void mouseClicked(MouseEvent e) { selectGems(e); } }
public void selectGems(MouseEvent e){
int x = e.getX() / cellWidth();
int y = e.getY() / cellHeight();
if(!isFirstSelected) {
fromX = x;
fromY = y;
isFirstSelected = true;
}else{
toX = x;
toY = y;
if((Math.abs(fromX - toX) == 1 ^ Math.abs(fromY - toY) == 1) & (gems[fromX][fromY].getType() != gems[toX][toY].getType())) {
switchGems();
isFirstSelected = false;
}
}
}
public void switchGems(){
int tempType = gems[fromX][fromY].getType();
gems[fromX][fromY].setType(gems[toX][toY].getType());
gems[toX][toY].setType(tempType);
checkPattern();
switchedBack = false;
repaint();
}
public void checkPattern() {
Set<Gem> gemsToDelete = new LinkedHashSet<>();
isPattern = false;
for (int x = 0; x < BOARDWIDTH; x++) {
for (int y = 0; y < BOARDHEIGHT; y++) {
if (x + 2 < BOARDWIDTH && (gems[x][y].getType() == gems[x + 1][y].getType()) && (gems[x + 1][y].getType() == gems[x + 2][y].getType())) { //Checks for 3 horizontal gems in a row
isPattern = true;
gemsToDelete.add(gems[x][y]);
gemsToDelete.add(gems[x + 1][y]);
gemsToDelete.add(gems[x + 2][y]);
}
if (y + 2 < BOARDHEIGHT && (gems[x][y].getType() == gems[x][y + 1].getType()) && (gems[x][y + 1].getType() == gems[x][y + 2].getType())) { //Check for 3 vertical gems in a row
isPattern = true;
gemsToDelete.add(gems[x][y]);
gemsToDelete.add(gems[x][y + 1]);
gemsToDelete.add(gems[x][y + 2]);
}
}
}
if(!gemsToDelete.isEmpty())
deletePattern(gemsToDelete);
if(!isPattern && !switchedBack){
switchedBack = true;
switchGems();
}
}
public void deletePattern(Set<Gem> gemsToDelete){
for(Gem gem : gemsToDelete)
gem.setType(7);
repaint(); //This doesn't seem to work
doGravity();
switchedBack = true;
checkPattern();
}
public void doGravity(){
try{
Thread.sleep(1000);
}catch(InterruptedException e){e.printStackTrace();}
for (int y = 0; y < BOARDHEIGHT; y++) {
for (int x = 0; x < BOARDWIDTH; x++) {
if(gems[x][y].getType() == 7){
for (int i = y; i >= 0; i--) {
if(i == 0)
gems[x][i].setType(gems[x][i].genType());
else
gems[x][i].setType(gems[x][i-1].getType());
}
}
}
}
}
}
Gem.java
package Game;
public class Gem {
private int type;
public Gem(){
this.type = genType();
}
public int genType(){
return (int) (Math.random() * 7);
}
public void setType(int type){
this.type = type;
}
public int getType(){
return type;
}
}
Game.java
package Game;
import javax.swing.*;
public class Game extends JFrame{
public Game(){
Board board = new Board();
getContentPane().add(board);
board.start();
setTitle("Game");
setSize(600, 600);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args){
Game game = new Game();
game.setLocationRelativeTo(null);
game.setVisible(true);
}
}
Your code is initiated via a mouse click. Code invoked from a Swing listener is executed on the Event Dispatch Thread (EDT), which is also responsible for painting the GUI
The Thread.sleep() in your doGravaity() method causes the EDT to sleep, therefore the GUI can't repaint() itself until the whole looping code is finished, at which point it will just paint the final state of your animation.
Instead of sleeping, you need to use a Swing Timer to schedule animation. So basically, in the deletePattern() method you would start the Timer to do the gravity animation. This will free up the EDT to repaint itself and when the Timer fires you would animate your components one move and then do repaint() again. When the components are finished moving you stop the timer.
Read the section from the Swing tutorial on Concurrency for more information about the EDT.
Call this.invalidate() or this.postInvalidate() which then forces a repaint.
I know I have asked a question like this before, but none of the answers in the old question worked for me. I am trying to make a little single-player pong game (It is in a Java applet). I already have a moveBall() function, as you can see below. But I don't know where to call it. I can't call it in the paint() method because it is double buffered.
public class Main extends Applet implements KeyListener, MouseListener {
private Rectangle paddle;
private Rectangle ball;
private ArrayList<Integer> keysDown;
private Image dbImage;
private Graphics dbg;
public int time = 300000;
Random randomGenerator = new Random();
int speed = 10;
int level = 1; // change to 0 once start menu works
int xpos, ypos;
int ballx, bally;
int width = 1024;
int height = 768;
int paddleWidth = 96;
int ballSize = 16;
String version = "0.0.1";
public static final int START_X_POS = 160;
public static final int START_Y_POS = 160;
public static final int START_WIDTH = 256;
public static final int START_HEIGHT = 64;
boolean startClicked;
boolean falling = true;
public void init() {
setSize(width, height);
addKeyListener(this);
addMouseListener(this);
setBackground(Color.black);
Frame c = (Frame)getParent().getParent();
c.setTitle("Asteroid Attack - Version " + version);
keysDown = new ArrayList<Integer>();
paddle = new Rectangle(getWidth()/2-paddleWidth, getHeight()-96, paddleWidth, 12);
ball = new Rectangle(getWidth()/2-ballSize, 96, ballSize, ballSize);
}
public void update(Graphics g) {
dbImage = createImage(getSize().width, getSize().height);
dbg = dbImage.getGraphics ();
if (dbImage == null) {}
dbg.setColor(getBackground ());
dbg.fillRect(0, 0, getSize().width, getSize().height);
dbg.setColor(getForeground());
paint(dbg);
g.drawImage(dbImage, 0, 0, this);
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
if (level != 0) {
g2.setPaint(Color.gray);
g2.fill(paddle);
g2.setPaint(Color.darkGray);
g2.fill(ball);
moveBall();
}
}
public void moveBall() {
bally = ball.y;
ballx = ball.x;
if (bally < paddle.y-32 && falling) {
bally += 12;
}
if (bally < paddle.y-32 && falling && paddle.x <= ballx && paddle.getMaxX() >= ball.x) { // collides with paddle
falling = false;
}
else { // does not collide with paddle
}
ball.setLocation(ballx, bally);
}
#Override
public void keyPressed(KeyEvent e) {
if (!keysDown.contains(e.getKeyCode()))
keysDown.add(new Integer(e.getKeyCode()));
key();
}
#Override
public void keyReleased(KeyEvent e) {
keysDown.remove(new Integer(e.getKeyCode()));
}
public void key() {
if (level != 0) {
int x = paddle.x;
int y = paddle.y;
if (keysDown.contains(KeyEvent.VK_ESCAPE)) {System.exit(0);}
if (x > 0 && x+paddleWidth < this.getWidth()) {
if (keysDown.contains(KeyEvent.VK_LEFT)) {x -= speed;}
if (keysDown.contains(KeyEvent.VK_RIGHT)) {x += speed;}
}
else { // so paddle doesn't exit room
if (x <= 0) {x += 4;}
else {x -= 4;}
}
paddle.setLocation(x, y);
repaint();
}
}
#Override
public void keyTyped(KeyEvent e) {}
#Override
public void mouseClicked(MouseEvent me) {
if (level == 0) {
xpos = me.getX();
ypos = me.getY();
if (xpos >= START_X_POS && ypos >= START_Y_POS && xpos <= START_X_POS + START_WIDTH && ypos <= START_X_POS + START_HEIGHT ) {
level = 1;
}
}
}
#Override
public void mouseEntered(MouseEvent me) {}
#Override
public void mouseExited(MouseEvent me) {}
#Override
public void mouseReleased(MouseEvent me) {}
#Override
public void mousePressed(MouseEvent me) {}
}
Any help would be greatly appreciated!
You'll probably want a separate Thread to move the ball which keeps track of time while in a in a loop - that way if frame rates drop, or speed up, you can try ensure consistency in the ball movement speed.
e.g. here on using a thread in a applet http://www.realapplets.com/tutorial/threadexample.html