Related
I am working on a 2D platformer in Java for an assignment. The assignment specifies I must use an abstract shape class to draw shapes. The problem I am having is getting my collision detection to work with multiple Rectangle Objects which I am using as platforms - I store these in a list. At the moment my collision detection will move my player regardless of which platform I have collided with, so if I collide with a platform from the right it will move me to the top of that platform because it's still checking another platform below me, and will assume I've hit that, therefore moving me to the top of the platform. I was wondering how I can change this so that my collision detection also detects which platform I have collided with and collide relative to that platform (as opposed to every platform).
Here's what is currently happening:
Expected Output:
My player should stop where it is when colliding with platforms.
My App class:
package A2;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
public class App extends JFrame {
public App() {
final Player player = new Player(200, 100);
final ArrayList<Rectangle> platforms = new ArrayList<>();
platforms.add(new Rectangle(100, 500, 400 ,10));
platforms.add(new Rectangle(500, 100, 10 ,400));
JPanel mainPanel = new JPanel() {
public void paintComponent(Graphics g) {
super.paintComponent(g);
player.draw(g);
for(Rectangle r : platforms){
r.draw(g);
}
}
};
mainPanel.addKeyListener(new InputControl(this, player, platforms));
mainPanel.setFocusable(true);
add(mainPanel);
setLayout(new GridLayout());
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setSize(600, 600);
}
public static void main(String[] args) {
App app = new App();
app.setVisible(true);
}
}
abstract class Shape {
public void draw(Graphics g) { }
}
My input handling class:
package A2;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import static java.awt.event.KeyEvent.*;
public class InputControl implements ActionListener, KeyListener {
App app;
Player player;
Timer time = new Timer(5, this);
ArrayList<Rectangle> platforms;
ArrayList<Integer> keyList = new ArrayList<>();
boolean keyReleased = false;
boolean keyPressed = false;
boolean[] keyUp = new boolean[256];
boolean[] keyDown = new boolean[256];
boolean topCollision = false;
boolean rightCollision = false;
boolean leftCollision = false;
boolean botCollision = false;
boolean onGround = false;
private static final double GRAVITY = 2;
private static final int MAX_FALL_SPEED = 5;
double Xoverlap = 0;
double Yoverlap = 0;
boolean collision = false;
public InputControl(App app, Player player, ArrayList<Rectangle> platforms) {
this.app = app;
this.player = player;
this.platforms = platforms;
time.start();
}
public void gravity(){
if(player.yVelocity > MAX_FALL_SPEED){
player.yVelocity = MAX_FALL_SPEED;
}
player.yVelocity += GRAVITY;
}
public void momentum(){}
public void actionPerformed(ActionEvent e) {
Rectangle2D offset = player.getOffsetBounds();
for(int i = 0; i < platforms.size(); i++) {
Rectangle r = platforms.get(i);
//If collision
if (offset.intersects(r.obj.getBounds2D())) {
//Collision on Y axis
if (offset.getX() + offset.getWidth() > r.obj.getX() &&
offset.getX() < r.obj.getX() + r.obj.getWidth() &&
offset.getY() + offset.getHeight() > r.obj.getY() &&
offset.getY() + player.yVelocity < r.obj.getY() + r.obj.getHeight()) {
player.y -= (offset.getY() + offset.getHeight()) - r.obj.getY();
}
//Collision on X axis
if (offset.getX() + offset.getWidth() + player.xVelocity > r.obj.getX() &&
offset.getX() + player.xVelocity < r.obj.getX() + r.obj.getWidth() &&
offset.getY() + offset.getHeight() > r.obj.getY() &&
offset.getY() < r.obj.getY() + r.obj.getHeight()) {
player.x -= (offset.getX() + offset.getHeight()) - r.obj.getX();
}
}
else {
player.x += player.xVelocity;
player.y += player.yVelocity;
}
}
player.x += player.xVelocity;
player.y += player.yVelocity;
app.repaint();
}
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() >= 0 && e.getKeyCode() < 256) {
keyDown[e.getKeyCode()] = true;
keyUp[e.getKeyCode()] = false;
keyPressed = true;
keyReleased = false;
}
if (keyDown[VK_UP]) {
player.yVelocity = -1;
}
if (keyDown[VK_RIGHT]){
player.xVelocity = 1;
}
if (keyDown[VK_LEFT]) {
player.xVelocity = -1;
}
if (keyDown[VK_DOWN]) {
player.yVelocity = 1;
}
}
public void keyReleased(KeyEvent e) {
if(e.getKeyCode() >= 0 && e.getKeyCode() < 256) {
keyDown[e.getKeyCode()] = false;
keyUp[e.getKeyCode()] = true;
keyPressed = false;
keyReleased = true;
}
if(keyUp[VK_RIGHT] || keyUp[VK_LEFT]){
player.xVelocity = 0;
}
if(keyUp[VK_UP]){
player.yVelocity = 0;
}
}
public void keyTyped(KeyEvent e) { }
}
My Player class:
package A2;
import java.awt.*;
import java.awt.geom.Rectangle2D;
public class Player extends Shape {
public double xVelocity;
public double yVelocity;
public double x;
public double y;
public double width = 60;
public double height = 60;
public double weight = 5;
public Rectangle2D obj;
public Player(double x, double y) {
this.x = x;
this.y = y;
obj = new Rectangle2D.Double(x, y, width, height);
}
public Rectangle2D getOffsetBounds(){
return new Rectangle2D.Double( x, y, width, height);
}
public void draw(Graphics g) {
Color c =new Color(1f,0f,0f,0.2f );
obj = new Rectangle2D.Double(x, y, width, height);
g.setColor(Color.BLACK);
Graphics2D g2 = (Graphics2D)g;
g2.fill(obj);
g.drawString("(" + String.valueOf(x) + ", " + String.valueOf(y) + ")", 10, 20);
g.drawString("X Speed: " + String.valueOf(xVelocity) + " Y Speed: " + String.valueOf(yVelocity) + ")", 10, 35);
g.setColor(c);
}
}
My Rectangle class:
package A2;
import java.awt.*;
import java.awt.geom.Rectangle2D;
public class Rectangle extends Shape {
public double width;
public double height;
public double x;
public double y;
boolean hasCollided = false;
public Rectangle2D obj;
public Rectangle(double x, double y, double width, double height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
obj = new Rectangle2D.Double(x, y, width, height);
}
public void draw(Graphics g) {
g.setColor(Color.ORANGE);
Graphics2D g2 = (Graphics2D)g;
g2.fill(obj);
}
}
First, define ColorRectangle class that extends Shape and provide logic for drawing:
// extends java.awt.Shape
abstract class ColorRectangle extends Rectangle {
private static final long serialVersionUID = -3626687047605407698L;
private final Color color;
protected ColorRectangle(int x, int y, int width, int height, Color color) {
super(x, y, width, height);
this.color = color;
}
public void draw(Graphics2D g) {
g.setColor(color);
g.fillRect(x, y, width, height);
}
}
Second, define separate class for represent Player and Platform:
public final class Player extends ColorRectangle {
private static final long serialVersionUID = -3909362955417024742L;
public Player(int width, int height, Color color) {
super(0, 0, width, height, color);
}
}
public final class Platform extends ColorRectangle {
private static final long serialVersionUID = 6602359551348037628L;
public Platform(int x, int y, int width, int height, Color color) {
super(x, y, width, height, color);
}
public boolean intersects(Player player, int offsX, int offsY) {
return intersects(player.x + offsX, player.y + offsY, player.width, player.height);
}
}
Third, define class that contains logic of demo application. Actually, you could split logic for demo and logic for board (including actiona and key listeners), but these classes are very simple, so in given case, no need to split it.
public class CollisionDetectionDemo extends JFrame {
public CollisionDetectionDemo() {
setLayout(new GridLayout());
add(new MainPanel());
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setSize(600, 600);
}
private static class MainPanel extends JPanel implements ActionListener, KeyListener {
private static final long serialVersionUID = 8771401446680969350L;
private static final int OFFS = 5;
private final Player player = new Player(60, 60, Color.BLACK);
private final List<Platform> platforms = Arrays.asList(
new Platform(100, 500, 400, 10, Color.ORANGE),
new Platform(500, 100, 10, 410, Color.RED),
new Platform(150, 300, 100, 10, Color.BLUE),
new Platform(150, 100, 100, 10, Color.GREEN));
private MainPanel() {
player.x = 200;
player.y = 200;
new Timer(5, this).start();
setFocusable(true);
addKeyListener(this);
}
private void drawPlayerPosition(Graphics g) {
g.setColor(Color.BLACK);
g.drawString(String.format("(%d, %d)", player.x, player.y), 10, 20);
}
private void movePlayer(int offsX, int offsY) {
if (!intersects(player, offsX, offsY)) {
player.x += offsX;
player.y += offsY;
}
}
private boolean intersects(Player player, int offsX, int offsY) {
for (Platform platform : platforms)
if (platform.intersects(player, offsX, offsY))
return true;
return false;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Color color = g.getColor();
drawPlayerPosition(g);
player.draw((Graphics2D)g);
platforms.forEach(platform -> platform.draw((Graphics2D)g));
g.setColor(color);
}
#Override
public void actionPerformed(ActionEvent event) {
repaint();
}
#Override
public void keyTyped(KeyEvent event) {
}
#Override
public void keyPressed(KeyEvent event) {
if (event.getKeyCode() == VK_UP)
movePlayer(0, -OFFS);
else if (event.getKeyCode() == VK_DOWN)
movePlayer(0, OFFS);
else if (event.getKeyCode() == VK_LEFT)
movePlayer(-OFFS, 0);
else if (event.getKeyCode() == VK_RIGHT)
movePlayer(OFFS, 0);
}
#Override
public void keyReleased(KeyEvent event) {
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new CollisionDetectionDemo().setVisible(true));
}
}
The 20 x 20 box is not being displayed(including motion listener and no errors). Another class is the window which sets up the JFrame and game start(). Here is the code below(with a package called "javagame9" .
public class Game extends Canvas implements Runnable {
private static final long serialVersionUID = -2713820159854096116L;
public static final int WIDTH = 640, HEIGHT = 700;
private Thread thread;
private boolean running = false;
public static boolean paused = false;
public Game() {
this.addMouseMotionListener(new Mouse());
new Window(WIDTH, HEIGHT, "A Game", this);
}
public synchronized void start() {
thread = new Thread(this);
thread.start();
running = true;
}
public synchronized void stop() {
try {
thread.join();
running = false;
} catch (Exception e) {
e.printStackTrace();
}
}
public void run() {
this.requestFocus();
long LastTime = System.nanoTime();
double amountOfTicks = 60.0;
double ns = 1000000000 / amountOfTicks;
double delta = 0;
long timer = System.currentTimeMillis();
int frames = 0;
while (running) {
long now = System.nanoTime();
delta += (now - LastTime) / ns;
LastTime = now;
while (delta >= 1) {
tick();
delta--;
}
if (running)
render();
frames++;
if (System.currentTimeMillis() - timer > 1000) {
timer += 1000;
System.out.println("FPS: " + frames);
frames = 0;
}
}
stop();
}
private void tick() {
}
private void render() {
}
public static void main(String args[]) {
new Game();
}
}
public class Mouse extends Canvas implements MouseMotionListener {
private static final long serialVersionUID = 7986961236445581989L;
private Image dbImage; //Mouse - class
private Graphics dbg;
int mx, my;
boolean mouseDragged;
public void paint(Graphics g) {
dbImage = createImage(getWidth(), getHeight());
dbg = dbImage.getGraphics();
paintComponent(dbg);
g.drawImage(dbImage, 0, 0, this);
}
public void paintComponent(Graphics g) {
if (mouseDragged) {
g.setColor(Color.DARK_GRAY);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.LIGHT_GRAY);
g.fillRect(mx, my, 20, 20);
} else {
g.setColor(Color.LIGHT_GRAY);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.DARK_GRAY);
g.fillRect(mx, my, 20, 20);
}
repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
mx = e.getX() - 10;
my = e.getY() - 10;
mouseDragged = true;
e.consume();
}
public void mouseMoved(MouseEvent e) {
mx = e.getX() - 10;
my = e.getY() - 10;
mouseDragged = false;
e.consume();
}
}
The problem is that you have 2 Canvas: Game and Mouse.
Assuming that the Window class is the following (found it here):
public class Window extends Canvas {
public Window(int width, int height, String title, Game game) {
JFrame frame = new JFrame(title);
frame.setPreferredSize(new Dimension(width, height));
frame.setMaximumSize(new Dimension(width, height));
frame.setMinimumSize(new Dimension(width, height));
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.add(game);
frame.setVisible(true);
game.start();
}
}
we see that the Canvas being added to the JFrame is the Game one, so it will be the one visible.
But you are only painting on the Mouse canvas, therefore you are not going to see anything.
You could move the paint logic from Mouse to Game, and use Mouse only for the MouseMotionListener functionality.
Modified Game class (differences highlighted by comments):
public class Game extends Canvas implements Runnable {
private static final long serialVersionUID = -2713820159854096116L;
public static final int WIDTH = 640, HEIGHT = 700;
private Thread thread;
private boolean running = false;
public static boolean paused = false;
// fields previously in Mouse moved here:
private Image dbImage;
private Graphics dbg;
// mouse field so we can reuse it
private Mouse mouse;
public Game() {
// we create an instance of mouse and use it as MouseMotionListener
mouse = new Mouse();
this.addMouseMotionListener(mouse);
new Window(WIDTH, HEIGHT, "A Game", this);
}
public synchronized void start() {
thread = new Thread(this);
thread.start();
running = true;
}
public synchronized void stop() {
try {
thread.join();
running = false;
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void run() {
this.requestFocus();
long LastTime = System.nanoTime();
double amountOfTicks = 60.0;
double ns = 1000000000 / amountOfTicks;
double delta = 0;
long timer = System.currentTimeMillis();
int frames = 0;
while (running) {
long now = System.nanoTime();
delta += (now - LastTime) / ns;
LastTime = now;
while (delta >= 1) {
tick();
delta--;
}
if (running)
render();
frames++;
if (System.currentTimeMillis() - timer > 1000) {
timer += 1000;
System.out.println("FPS: " + frames);
frames = 0;
}
}
stop();
}
private void tick() {
}
private void render() {
}
public static void main(String args[]) {
new Game();
}
// paint methods previously in Mouse moved here:
#Override
public void paint(Graphics g) {
dbImage = createImage(getWidth(), getHeight());
dbg = dbImage.getGraphics();
paintComponent(dbg);
g.drawImage(dbImage, 0, 0, this);
}
public void paintComponent(Graphics g) {
if (mouse.mouseDragged) {
g.setColor(Color.DARK_GRAY);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.LIGHT_GRAY);
g.fillRect(mouse.mx, mouse.my, 20, 20);
}
else {
g.setColor(Color.LIGHT_GRAY);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.DARK_GRAY);
g.fillRect(mouse.mx, mouse.my, 20, 20);
}
repaint();
}
}
Now from the Mouse class you can remove all the methods/fields used for the painting, also it no longer extends Canvas:
public class Mouse implements MouseMotionListener {
private static final long serialVersionUID = 7986961236445581989L;
int mx, my;
boolean mouseDragged;
#Override
public void mouseDragged(MouseEvent e) {
mx = e.getX() - 10;
my = e.getY() - 10;
mouseDragged = true;
e.consume();
}
#Override
public void mouseMoved(MouseEvent e) {
mx = e.getX() - 10;
my = e.getY() - 10;
mouseDragged = false;
e.consume();
}
}
Using these classes you should now be able to see what you are painting.
I have a large program that I will post some classes of and hopefully you guys can find the problem. Basically, sometimes when I start it, it creates the game just fine, and others the background is up a few pixels to the north and west directions leaving very unsightly whitespace. I cannot seem to find the missing piece of code that decides whether not it does this. It honestly feel like some kind of rendering glitch on my machine. At any rate, I have put a background getX and getY method in for debugging and have noticed that whether the background is fully stretched to the screen(its a custom background so the pixel height and width match perfectly), or its up and to the left, the background still reads that it is displaying at (0,0). I will post all the methods from the main thread to the creating of the background in the menu. I will leave notes indicating the path it takes through this code that gets it to creating the background. Thank you for your help and I will check in regularly for edits and more information.
EDIT: added background.java
EDIT2: added pictures explaining problem
Menu.java *ignore the FileIO code, the main point is the creation of a new GamePanel()
public class Menu {
private static File file;
public static void main(String[] args) throws IOException {
file = new File("saves.txt");
if(file.exists()){
FileIO.run();
FileIO.profileChoose();
}
else{
FileIO.profileCreate();
FileIO.run();
}
JFrame window = new JFrame("Jolly Jackpot Land");
window.setContentPane(new GamePanel());
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setResizable(false);
window.pack();
window.setLocationRelativeTo(null);
window.setVisible(true);
}
}
Next is the GamePanel.java
public class GamePanel extends JPanel implements Runnable, KeyListener {
// ID
private static final long serialVersionUID = 1L;
// Dimensions
public static final int WIDTH = 320;
public static final int HEIGHT = 240;
public static final int SCALE = 2;
// Thread
private Thread thread;
private boolean running;
private int FPS = 30;
private long targetTime = 1000 / FPS;
// Image
private BufferedImage image;
private Graphics2D g;
// Game State Manager
private GameStateManager gsm;
public GamePanel() {
super();
setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
setFocusable(true);
requestFocus();
}
public void addNotify() {
super.addNotify();
if (thread == null) {
thread = new Thread(this);
addKeyListener(this);
thread.start();
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
}
private void init() {
image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) image.getGraphics();
running = true;
gsm = new GameStateManager();
}
#Override
public void run() {
init();
long start;
long elapsed;
long wait;
// Game Loop
while (running) {
start = System.nanoTime();
update();
draw();
drawToScreen();
elapsed = System.nanoTime() - start;
wait = targetTime - (elapsed / 1000000);
if (wait < 0) {
wait = 5;
}
try {
Thread.sleep(wait);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void update() {
gsm.update();
}
private void draw() {
gsm.draw(g);
}
private void drawToScreen() {
Graphics g2 = getGraphics();
g2.drawImage(image, 0, 0, WIDTH * SCALE, HEIGHT * SCALE, null);
g2.dispose();
}
#Override
public void keyPressed(KeyEvent k) {
gsm.keyPressed(k.getKeyCode());
}
#Override
public void keyReleased(KeyEvent k) {
}
#Override
public void keyTyped(KeyEvent arg0) {
}
}
This calls for the creation of a new GameStateManager object in its init() method and the class for that is here.
GameStateManager.java
public class GameStateManager {
private ArrayList<GameState> gameStates;
private int currentState;
public static final int MENUSTATE = 0;
public static final int SLOTGAMESTATE = 1;
public static final int DICEGAMESTATE = 2;
public static final int ROULETTEGAMESTATE = 3;
public static final int LEADERBOARDSTATE = 4;
public static final int SETTINGSSTATE = 5;
public static final int HELPSTATE = 6;
public GameStateManager() {
gameStates = new ArrayList<GameState>();
currentState = 0;
gameStates.add(new MenuState(this));
gameStates.add(new SlotGameState(this));
gameStates.add(new DiceGameState(this));
gameStates.add(new RouletteGameState(this));
gameStates.add(new LeaderboardState(this));
gameStates.add(new SettingsState(this));
gameStates.add(new HelpState(this));
}
public void setState(int state){
currentState = state;
gameStates.get(currentState).init();
currentState = 0;
}
public int getState() {
return currentState;
}
public void update() {
gameStates.get(currentState).init();
}
public void draw(java.awt.Graphics2D g){
gameStates.get(currentState).draw(g);
}
public void keyPressed(int k){
gameStates.get(currentState).keyPressed(k);
}
public void keyReleased(int k) {
gameStates.get(currentState).keyReleased(k);
}
}
GameState is an abstract class I have so its not worth posting, it only contains init(), draw(), etc. This next class is the last and final class and is called because GameStateMananger starts at MENUSTATE or 0, and when GSM is initialized it initializes its current state, thus taking us to the class MenuState
MenuState.java
public class MenuState extends GameState {
private Background bg;
public FontMetrics fontMetrics;
private int choice = 0;
private String[] options = { "Slot Machine", "Dice Toss", "Roulette Wheel", "Leaderboards", "Settings", "Help",
"Quit" };
private Color titleColor;
private Font titleFont;
private Font font;
public MenuState(GameStateManager gsm) {
this.gsm = gsm;
try {
bg = new Background("/Backgrounds/happybg.png");
titleColor = Color.WHITE;
titleFont = new Font("Georgia", Font.PLAIN, 28);
} catch (Exception e) {
e.printStackTrace();
}
font = new Font("Arial", Font.PLAIN, 12);
}
#Override
public void init() {
}
#Override
public void update() {
}
#Override
public void draw(Graphics2D g) {
Canvas c = new Canvas();
fontMetrics = c.getFontMetrics(font);
// Draw BG
bg.draw(g);
// Draw title
g.setColor(titleColor);
g.setFont(titleFont);
String title = "Jolly Jackpot Land!";
g.drawString(title, 36, 60);
g.setFont(font);
for (int i = 0; i < options.length; i++) {
if (i == choice)
g.setColor(Color.RED);
else
g.setColor(Color.WHITE);
g.drawString(options[i], 30, 120 + i * 15);
}
g.setColor(Color.WHITE);
g.setFont(new Font("Arial", Font.PLAIN, 10));
g.drawString("v1.1", 165, 235);
Object[] a = { ("Name: " + Player.getName()), ("Gil: " + Player.getGil()),
("Personal Best: " + Player.getPersonalBest()), ("Winnings: " + Player.getWinnings()),
("Wins: " + Player.getWins()), ("Losses: " + Player.getLosses()),
("Win/Loss Ratio: " + String.format("%.2f", Player.getRatio()) + "%") };
g.setFont(font);
if (Player.getName() != null) {
for (int x = 0; x < a.length; x++) {
g.drawString(a[x].toString(), GamePanel.WIDTH - fontMetrics.stringWidth(a[x].toString()) - 30,
120 + x * 15);
}
}
}
private void select() {
if (choice == 0) {
// Slots
gsm.setState(GameStateManager.SLOTGAMESTATE);
}
if (choice == 1) {
// Dice
gsm.setState(GameStateManager.DICEGAMESTATE);
}
if (choice == 2) {
// Roulette
gsm.setState(GameStateManager.ROULETTEGAMESTATE);
}
if (choice == 3) {
// Leaderboards
gsm.setState(GameStateManager.LEADERBOARDSTATE);
}
if (choice == 4) {
// Settings
gsm.setState(GameStateManager.SETTINGSSTATE);
}
if (choice == 5) {
// Help
gsm.setState(GameStateManager.HELPSTATE);
}
if (choice == 6) {
// Quit
System.exit(0);
}
}
#Override
public void keyPressed(int k) {
if (k == KeyEvent.VK_ENTER) {
select();
}
if (k == KeyEvent.VK_UP) {
choice--;
if (choice == -1) {
choice = options.length - 1;
}
}
if (k == KeyEvent.VK_DOWN) {
choice++;
if (choice == options.length) {
choice = 0;
}
}
}
#Override
public void keyReleased(int k) {
}
}
Background.java
public class Background {
private BufferedImage image;
private double x;
private double y;
public Background(String s) {
try {
image = ImageIO.read(getClass().getResourceAsStream(s));
} catch (Exception e) {
e.printStackTrace();
}
}
public void setPosition(double x, double y) {
this.setX(x);
this.setY(y);
}
public void draw(Graphics2D g) {
g.drawImage(image, 0, 0, null);
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
}
This is where it waits for input in the game loop basically. I know this is a lot of code, but a lot of it is skimming till a method call takes you to the next class. I just can't figure out why it only happens sometimes, if it was consistent I could debug it. Any help would be extremely appreciated.
These are both from clicking the .jar of the above program, exact same .jar, exact same source code, different result. I am bewildered.
So I'm making very simple game. Game is about some guy who jumps on rocks(circles) and rocks sometimes get covered by water and when they are, you cant stand on them otherwise you'll fall into water and drown. I'm stuck in part where i need to make those rocks disappear(be covered by water). So I need at randomized time make them disappear, for random seconds(not too long) make them "invisible" and then again they need to show up. I'm still kinda beginner and I would appreciate any kind of answer but if you could explain it to me I'd be thrilled.
Here is my code:
My main class
package com.pitcher654.main;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyListener;
import java.awt.image.BufferStrategy;
import java.util.Random;
import com.pitcher654.main.Game.STATE;
public class Game extends Canvas implements Runnable{
private static final long serialVersionUID = -7800496711589684767L;
public static final int WIDTH = 640, HEIGHT = WIDTH / 12 * 9;
private Thread thread;
private boolean running = false;
private Random r;
private Handler handler;
//private HUD hud;
private Menu menu;
public enum STATE {
Menu,
Help,
Game
};
public STATE gameState = STATE.Menu;
public Game() {
handler = new Handler();
menu = new Menu(this, handler);
this.addKeyListener(new KeyInput(handler));
this.addMouseListener(menu);
new Window(WIDTH, HEIGHT, "My game", this);
//hud = new HUD();
r = new Random();
if(gameState == STATE.Game) {
//handler.addObject(new Player(100, 200, ID.Player));
}
//handler.addObject(new Player(100, 200, ID.Player));
//handler.addObject(new BasicEnemy(100, 200, ID.BasicEnemy));
}
public synchronized void start() {
thread = new Thread(this);
thread.start();
running = true;
}
public synchronized void stop() {
try {
thread.join();
running = false;
}catch(Exception ex) { ex.printStackTrace(); }
}
public void run()
{
this.requestFocus();
long lastTime = System.nanoTime();
double amountOfTicks = 60.0;
double ns = 1000000000 / amountOfTicks;
double delta = 0;
long timer = System.currentTimeMillis();
int frames = 0;
while(running)
{
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while(delta >=1)
{
tick();
delta--;
}
if(running)
render();
frames++;
if(System.currentTimeMillis() - timer > 1000)
{
timer += 1000;
//System.out.println("FPS: "+ frames);
frames = 0;
}
}
stop();
}
private void tick() {
handler.tick();
//hud.tick();
if(gameState == STATE.Game) {
}else if(gameState == STATE.Menu) {
menu.tick();
}
}
private void render() {
BufferStrategy bs = this.getBufferStrategy();
if(bs == null) {
this.createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
g.setColor(new Color(87, 124, 212));
g.fillRect(0, 0, WIDTH, HEIGHT);
if(gameState == STATE.Game) {
g.setColor(new Color(209, 155, 29));
for(int i = 0; i < 5; i++) {
g.fillOval(80 + (100 * i), 325, 70, 20);
}
}else if(gameState == STATE.Menu || gameState == STATE.Help){
menu.render(g);
}
handler.render(g);
if(gameState == STATE.Game) {
}
//hud.render(g);
g.dispose();
bs.show();
}
public static int clamp(int var, int min, int max) {
if(var >= max)
return var = max;
else if(var <= max)
return var = min;
else
return var;
}
public static void main(String[] args) {
new Game();
}
}
My Player class where I create my player:
package com.pitcher654.main;
import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;
import com.pitcher654.main.Game.STATE;
public class Player extends GameObject {
Random r = new Random();
public Player(int x, int y, ID id) {
super(x, y, id);
//velX = r.nextInt(5) + 1;
//velY = r.nextInt(5);
}
public void tick() {
x += velX;
y += velY;
//System.out.println(x);
if(x == 500) x = 500;
}
public void render(Graphics g) {
if(id == ID.Player) g.setColor(Color.white);
if(id == ID.Player2) g.setColor(Color.blue);
g.fillRect(x, y, 32, 32);
g.drawLine(x + 15, y, x + 15, y + 100);
g.drawLine(x + 15, y + 100, x, y + 135);
g.drawLine(x + 15, y + 100, x + 33, y + 135);
g.drawLine(x + 15, y + 70, x - 35, y + 30);
g.drawLine(x + 15, y + 70, x + 65, y + 30);
/*if(game.gameState == STATE.Menu) {
g.setColor(new Color(87, 124, 212));
g.fillRect(0, 0, Game.WIDTH, Game.HEIGHT);
}*/
}
}
And my game Object class:
package com.pitcher654.main;
import java.awt.Graphics;
public abstract class GameObject {
protected static 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 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;
}
public void setID(ID id) {
this.id = id;
}
public ID getID() {
return id;
}
public void setVelX(int velX) {
this.velX = velX;
}
public void setVelY(int velY) {
this.velY = velY;
}
public int getVelX() {
return velX;
}
public int getVelY() {
return velY;
}
}
If you need any other class, just tell me I'll post it.
You should store a status of every stone in the game.
So if you have given number of stones (5), create a constant field with this number. Then create an array of ### boolean values where you'll save status of each stone.
Then create an array of "times" when the stones will change their visibility.
private static final int NUM_STONES = 5; // you can change the # of the stones here
private boolean[] visible = new int[NUM_STONES];
private long[] changeTimes = new long[NUM_STONES];
In your game's init method initialize the values.
for(int i=0; i<NUM_STONES; i++){
visible[i] = true; // each stone will be visible
changeTimes[i] = System.currentTimeMillis() + r.nextInt(10000); // every stone will disappear in less than 10 seconds
}
In your update method (I suppose tick() ) update visibility statuses.
long now = System.currentTimeMillis();
for(int i=0; i<NUM_STONES; i++){
if(now < changeTimes[i]){ // if the time has come
if(visible[i]) changeTimes[i] = now + r.nextInt(5000); // every stone will be invisible up to five seconds
else changeTimes[i] = now + r.nextInt(10000); // every stone will be visible again up to 10 seconds
visible[i] = !visible[i]; // switch the visibility state
}
}
And finally add the condition to the render method:
if(gameState == STATE.Game) {
for(int i = 0; i < NUM_STONES; i++) {
if(visible[i] g.setColor(new Color(209, 155, 29));
else g.setColor(new Color(107, 155, 170));
g.fillOval(80 + (100 * i), 325, 70, 20);
}
}
That should be it.
Next thing you should do is extracting magic numbers into constants, like I showed you with the NUM_STONES. And also don't create a new instance of the Color class every time you render a stone and create instances of the colors just like I've written before.
Also notice that some stones will disappear (and appear again) for a very short time - you can add few seconds to the changeTimes[i] in your update method to ensure that each stone will be (in)visible at least for this amount of time.
I have the following program, it's a SubKiller game and I have one question. How do I make the boat launch the bomb anytime I press the down key? So far I have made to launch it multiple times. I can launch the first bomb but when I'm trying to launch the second one, the first one disappears and it doesn't continue it's way. I am stuck on this matter for almost two days, help me please.
I am going to provide you the SSCCE code.
This is the class called SubKillerPanel, basically everything is here, the boat is here, the bomb is here, the submarine is here.
import java.awt.event.*;
import javax.swing.*;
import java.awt.Graphics;
import java.awt.Color;
public class SubKillerPanel extends JPanel {
private static final long serialVersionUID = 1L;
private Timer timer;
private int width, height;
private Boat boat;
private Bomb bomb;
private Submarine sub;
public SubKillerPanel() {
setBackground(Color.WHITE);
ActionListener action = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (boat != null) {
boat.updateForNewFrame();
bomb.updateForNewFrame();
sub.updateForNewFrame();
}
repaint();
}
};
timer = new Timer( 20, action );
addMouseListener( new MouseAdapter() {
public void mousePressed(MouseEvent evt) {
requestFocus();
}
}
);
addFocusListener( new FocusListener() {
public void focusGained(FocusEvent evt) {
timer.start();
repaint();
}
public void focusLost(FocusEvent evt) {
timer.stop();
repaint();
}
}
);
addKeyListener( new KeyAdapter() {
public void keyPressed(KeyEvent evt) {
int code = evt.getKeyCode(); // ce tasta a fost apasata
if (code == KeyEvent.VK_LEFT) {
boat.centerX -= 15;
}
else
if (code == KeyEvent.VK_RIGHT) {
boat.centerX += 15;
}
else
if (code == KeyEvent.VK_DOWN) {
if (bomb.isFalling == false)
bomb.isFalling = true;
bomb.centerX = boat.centerX;
bomb.centerY = boat.centerY;
}
}
}
);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (boat == null) {
width = getWidth();
height = getHeight();
boat = new Boat();
sub = new Submarine();
bomb = new Bomb();
}
if (hasFocus())
g.setColor(Color.CYAN);
else {
g.setColor(Color.RED);
g.drawString("CLICK TO ACTIVATE", 20, 30);
g.setColor(Color.GRAY);
}
g.drawRect(0,0,width-1,height-1);
g.drawRect(1,1,width-3,height-3);
g.drawRect(2,2,width-5,height-5);
boat.draw(g);
sub.draw(g);
bomb.draw(g);
}
private class Boat {
int centerX, centerY;
Boat() {
centerX = width/2;
centerY = 80;
}
void updateForNewFrame() {
if (centerX < 0)
centerX = 0;
else
if (centerX > width)
centerX = width;
}
void draw(Graphics g) {
g.setColor(Color.blue);
g.fillRoundRect(centerX - 40, centerY - 20, 80, 40, 20, 20);
}
}
private class Bomb {
int centerX, centerY;
boolean isFalling;
Bomb() {
isFalling = false;
}
void updateForNewFrame() {
if (isFalling) {
if (centerY > height) {
isFalling = false;
}
else
if (Math.abs(centerX - sub.centerX) <= 36 && Math.abs(centerY - sub.centerY) <= 21) {
sub.isExploding = true;
sub.explosionFrameNumber = 1;
isFalling = false; // Bomba reapare in barca
}
else {
centerY += 10;
}
}
}
void draw(Graphics g) {
if ( !isFalling ) {
centerX = boat.centerX;
centerY = boat.centerY + 23;
}
g.setColor(Color.RED);
g.fillOval(centerX - 8, centerY - 8, 16, 16);
}
}
private class Submarine {
int centerX, centerY;
boolean isMovingLeft;
boolean isExploding;
int explosionFrameNumber;
Submarine() {
centerX = (int)(width*Math.random());
centerY = height - 40;
isExploding = false;
isMovingLeft = (Math.random() < 0.5);
}
void updateForNewFrame() {
if (isExploding) {
explosionFrameNumber++;
if (explosionFrameNumber == 30) {
centerX = (int)(width*Math.random());
centerY = height - 40;
isExploding = false;
isMovingLeft = (Math.random() < 0.5);
}
}
else {
if (Math.random() < 0.04) {
isMovingLeft = ! isMovingLeft;
}
if (isMovingLeft) {
centerX -= 5;
if (centerX <= 0) {
centerX = 0;
isMovingLeft = false;
}
}
else {
centerX += 5;
if (centerX > width) {
centerX = width;
isMovingLeft = true;
}
}
}
}
void draw(Graphics g) {
g.setColor(Color.BLACK);
g.fillOval(centerX - 30, centerY - 15, 60, 30);
if (isExploding) {
g.setColor(Color.YELLOW);
g.fillOval(centerX - 4*explosionFrameNumber, centerY - 2*explosionFrameNumber,
8*explosionFrameNumber, 4*explosionFrameNumber);
g.setColor(Color.RED);
g.fillOval(centerX - 2*explosionFrameNumber, centerY - explosionFrameNumber/2,
4*explosionFrameNumber, explosionFrameNumber);
}
}
}
}
The SubKiller class, where the main method is located.
import javax.swing.JFrame;
public class SubKiller {
public static void main(String[] args) {
JFrame window = new JFrame("Sub Killer Game");
SubKillerPanel content = new SubKillerPanel();
window.setContentPane(content);
window.setSize(700, 700);
window.setLocation(0,0);
window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
window.setResizable(false);
window.setVisible(true);
}
}
You only have a single Bomb being tracked by the graphics at any time. You should build a collection of Bombs and instantiate a new one whenever the down key is pressed, then iterate through all the collection of Bombs and draw them as needed.
So, instead of private Bomb bomb;
You would have private List<Bomb> bombs;
Afterwards, anywhere you update the single bomb you can use a for loop to go through the list of bombs and have them all update, and then if they are no longer being drawn, remove them from the list.