I'm trying to understand how BufferStrategy is working. I've made a simple app that is drawing some Sprite objects over and over again every 60 frames per second. I can see the images on the canvas but they are flickering for some reason. Can you tell me why? If you don't want to read all the code, just focus on the Paint method and the main game loop.
public abstract class Frame extends JFrame implements KeyListener {
private static final long serialVersionUID = 1L;
//------------------------Variables------------------------//
private boolean initialized = false;
private boolean fullScreen = false;
public boolean running = true;
private GraphicsDevice vc;
private BufferStrategy strategy;
private Graphics2D g2d;
private int timer = 0;
//------------------------Variables------------------------//
public final void __init__() {
this.addKeyListener(this); //Adding key listener.
this.setVisible(true);
this.setIgnoreRepaint(true);
this.createBufferStrategy(2);
this.strategy = this.getBufferStrategy();
this.setResizable(false);
this.initialized = true; //Initialized.
}
//Create a window.
public final void set_mode(int width, int height, boolean fullScreen) throws NotInitializedException {
//Frame not initialized.
if (!this.initialized) {
throw new NotInitializedException("Frame not initialized!");
} else {
//--------------------Variables--------------------//
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
//--------------------Variables--------------------//
//Setting vc equal to the default graphics device of the system.
this.vc = env.getDefaultScreenDevice();
//Full Screen.
if (fullScreen) {
this.fullScreen = fullScreen;
//Creating the display mode.
DisplayMode mode = new DisplayMode(width, height, 32, DisplayMode.REFRESH_RATE_UNKNOWN);
//If display settings are allow to change display mode.
if (this.vc.isDisplayChangeSupported()) {
this.vc.setDisplayMode(mode); //Change to the new mode.
}
//Set the screen to full screen.
this.vc.setFullScreenWindow(this);
} //Not full screen.
else {
this.setSize(width, height);
this.addWindowListener(new WindowHandler(this));
}
}
}
//This mehod is been called from Sprite.draw() method.
public void paint(Sprite sprite) {
do {
do {
this.g2d = (Graphics2D) this.strategy.getDrawGraphics();
g2d.drawImage(sprite.getImage(), sprite.getX(), sprite.getY(), sprite.getWidth(), sprite.getHeight(), null);
this.g2d.dispose();
} while (this.strategy.contentsRestored());
this.strategy.show();
} while (this.strategy.contentsLost());
}
public final int tick(int fps) {
int ms = 1000 / fps;
timer += 1;
//Try to sleep.
try {
Thread.sleep(ms);
} //Thread interrupted.
catch (Exception e) {
System.out.println(e.getMessage());
System.exit(0);
}
return timer;
}
public class MyApp extends Frame {
public static String BG_PATH = "C:/Users/admin/Desktop/game/bg.jpg";
public static String PL_PATH = "C:/Users/admin/Desktop/game/player.png";
public static String EN_PATH = "C:/Users/admin/Desktop/game/enemy.png";
private int speed = 20;
private boolean left = false;
private boolean right = false;
#Override
public void keyPressed(KeyEvent arg0) {
// TODO Auto-generated method stub
if (arg0.getKeyCode() == KeyEvent.VK_LEFT) {
this.left = true;
} else if (arg0.getKeyCode() == KeyEvent.VK_RIGHT) {
this.right = true;
} else if (arg0.getKeyCode() == KeyEvent.VK_ESCAPE) {
this.close();
}
}
#Override
public void keyReleased(KeyEvent arg0) {
// TODO Auto-generated method stub
if (arg0.getKeyCode() == KeyEvent.VK_LEFT) {
this.left = false;
} else if (arg0.getKeyCode() == KeyEvent.VK_RIGHT) {
this.right = false;
}
}
#Override
public void keyTyped(KeyEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void onWindowClose() {
// TODO Auto-generated method stub
}
//This method starts the game.
public void startApp() {
this.__init__(); //initialize the frame.
Sprite bg = new Sprite(this, Picture.load(BG_PATH), "bg"); //Create a new sprite obj
this.set_mode(bg.getWidth() - 500, bg.getHeight() - 100, false); //Create the window.
Sprite player = new Sprite(this, Picture.load(PL_PATH), "player");
player.setX(bg.getWidth() / 3);
player.setY(bg.getHeight() / 2);
//Game Main Loop
while (this.running) {
bg.draw();
player.draw();
player.moveHorizontal(left, right, speed); //Dont worry about this line.
this.tick(50);
}
}
}
}
You have a few issues that I can clearly spot.
First off, you must understand that drawing in Swing/Awt is not known for it's speed, it's actually known for the exact opposite. The fact is, even though you're telling your game to run at 60fps, it probably can't do it. Thus the flickering. Essentially, your application is caught within a "drawing-data race", and it's always slightly behind. Try something real quick; set Thead.Sleep() to 10 or 30. I feel as though that might solve your problem entirely.
If not, consider the second problem. You're calling this.strategy.show(); inside the player.Draw(); function, when it needs to be the last thing that you do in your draw call. In other words:
//Game Main Loop
while (this.running) {
bg.draw(); // DON'T SWAP BUFFERS!
player.draw(); // DON'T SWAP BUFFERS!
// draw other entities
player.moveHorizontal(left, right, speed); //Dont worry about this line.
this.tick(50);
this.strategy.show(); // This needs to be the very last thing you do. You're swapping buffers here, which only needs to be done once per frame.
}
My guess is you're also swapping your buffer during the bg.Draw(); function as well, and that is actually why your screen is flickering. So those are two things right there. Try lowering the frames per second down to something that Java can actually handle, and don't swap your buffer until the VERY end of your drawing routine.
Some other recommendations:
Use direct variable access instead of getters and setters. There is overhead when calling "player.getX()". When you could just call "player.x".
There is no future in Java 2D game development. Swing/AWT(which you're using) is entirely dead. If you want to make a game, and be serious about it, take the time to learn OpenGL(In your case it would be Lwjgl as a wrapper).
Related
So I have been looking to update one of my panels in a my client code with data that comes from a server in Indonesia. The delay is rather long (2-8) sec and Im noticing that my UI is freezing during the time it takes for the response to return from the server.
The response will be used to draw some points on a map (not yet implemented).
I have been looking all over the net to find out how to do it and I have come across:
InvokeLater.
SwingWroker.
ScheduledThreadPoolExecutor.
Making the JPanel a runnable to run in its own thread(seems like best option).
http://www.java2s.com/Tutorial/Java/0160__Thread/CreateathreadtoupdateSwing.htm
But tbh most of the data i find is out dated (more than 5 years old).
Here is the JPanel class i want to update based on a server query:
public class MapPanel extends JPanel implements Pointable, Runnable {
private static final long serialVersionUID = 1L;
private List<Shape> shapes = new LinkedList<>();
private State mapPanelState;
public Shape selected;
private BufferedImage image;
public MapPanel() {
Commander.getInstance().addShapeContainer(this);
mapPanelState = NoState.getInstance();
MouseHandler mouseHandler = new MouseHandler(this);
KeyListener keyListener = new KeyListener();
readImage();
this.addMouseListener(mouseHandler);
this.addMouseMotionListener(mouseHandler);
this.addKeyListener(keyListener);
this.setBackground(Color.white);
this.setFocusable(true);
this.requestFocusInWindow();
}
private void readImage(){
try {
image = ImageIO.read(new File("/MapCoordProject/earthmap1.jpg"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void setState(State state) {
mapPanelState = state;
}
public List<Shape> getShapes() { return shapes; }
public void setShapes(List<Shape> shapes) {
this.shapes = shapes;
}
public Shape getLastShape(){ return shapes.get(shapes.size()-1); }
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0, null);
for (Shape shape : shapes)
shape.draw((Graphics2D) g);
}
public void select(Point point) {
for (Shape shape : shapes) {
if (shape.intersects(point)) {
selected = shape;
}
}
}
#Override
public Dimension getPreferredSize() {
if (image == null) {
return super.getPreferredSize();
} else {
int w = image.getWidth();
int h = image.getHeight();
return new Dimension(w, h);
}
}
public void pointerDown(Point point) {
mapPanelState.pointerDown(point, this);
}
public void pointerUp(Point point) {
mapPanelState.pointerUp(point, this);
selected = null;
}
public void pointerMoved(Point point, boolean pointerDown) {
mapPanelState.pointerMoved(point, pointerDown, this);
}
#Override
public void run() {
}
}
I want a method that updates the "Shapes" array in a separate thread to stop everything from freezing.
Any suggestions?
Making your JPanel implement Runnable is not the best solution. There is no reason to expose a run() method to other classes.
Instead, create a private void method that takes no arguments. A method reference that refers to that method can act as a Runnable, since it will have the same arguments and return type. You can then pass it to a Thread constructor.
public MapPanel() {
// ...
readImage();
new Thread(this::readShapes, "Reading shapes").start();
// ...
}
private void readShapes() {
try {
List<Shape> newShapes = new ArrayList<>();
URL server = new URL("https://example.com/indonesia/data");
try (InputStream dataSource = server.openStream()) {
while ( /* ... */ ) {
Shape shape = /* ... */;
newShapes.add(shape);
}
}
EventQueue.invokeLater(() -> setShapes(newShapes));
} catch (IOException e) {
e.printStackTrace();
EventQueue.invokeLater(() -> {
JOptionPane.showMessageDialog(getTopLevelContainer(),
"Unable to retrieve data:\n" + e, "Load Error",
JOptionPane.ERROR_MESSAGE);
});
}
}
Notice that calls to methods involving Swing objects are always wrapped in a call to EventQueue.invokeLater, to make sure they run on the correct thread.
It is possible to improve this by creating a progress dialog that shows while the data is being loaded, but that would make this answer much longer and would require more knowledge about the Indonesian API you’re calling.
Mouse.java
package game.input;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
// holds information about mouse events.
// eg, presses of buttons.
public class Mouse extends MouseAdapter {
// the position of the mouse.
public static int x, y;
// Is the mouse pressed.
public static boolean pressed;
// Is the mouse held.
public static boolean held;
// Is the mouse hovered over the window.
public static boolean focused;
// Is the mouse being dragged.
public static boolean dragging;
// no mouse wheel support.
#Override
public void mouseWheelMoved(MouseWheelEvent event) {}
#Override
public void mouseDragged(MouseEvent event) {
x = event.getX();
y = event.getY();
dragging = true;
}
#Override
public void mouseMoved(MouseEvent event) {
x = event.getX();
y = event.getY();
}
#Override
public void mouseEntered(MouseEvent event) {
focused = true;
}
#Override
public void mouseExited(MouseEvent event) {
focused = false;
}
#Override
public void mousePressed(MouseEvent event) {
held = true;
}
#Override
public void mouseReleased(MouseEvent event) {
held = false;
dragging = false;
pressed = true;
}
}
Keyboard.java
package game.input;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
// holds information about key events.
public class Keyboard extends KeyAdapter {
// which keys are being held down.
private static boolean[] heldKeys;
// which keys are being clicked
private static boolean[] clickedKeys;
// size of arrays.
private final int size;
public Keyboard() {
// there are 255 valid key codes.
// plus one for the array size.
size = 256;
clickedKeys = new boolean[size];
heldKeys = new boolean[size];
}
// when the key is pressed.
#Override
public void keyPressed(KeyEvent event) {
// catches out of bounds error.
if(event.getKeyCode() > size)
return;
// key is being held.
heldKeys[event.getKeyCode()] = true;
}
#Override
public void keyReleased(KeyEvent event) {
// catches out of bounds error.
if(event.getKeyCode() > size)
return;
// key is let go.
heldKeys[event.getKeyCode()] = false;
// when key is let go, it gets interpreted as it being pressed.
clickedKeys[event.getKeyCode()] = true;
}
// returns whether or not the key is held.
public static boolean keyHeld(Key key) {
if(heldKeys != null)
return heldKeys[key.keyCode];
return false;
}
// returns whether or not the key is clicked.
public static boolean keyClicked(Key key) {
if(clickedKeys != null)
return clickedKeys[key.keyCode];
return false;
}
// resets key input.
public static void resetKeys() {
if(clickedKeys != null)
for(int i = 0; i < clickedKeys.length; i++)
clickedKeys[i] = false;
}
public enum Key {
// movement keys.
LEFT(37), UP(38), RIGHT(39), DOWN(40),
// x key.
A(88),
// z key.
B(90),
// enter key.
START(10);
private int keyCode;
private Key(int keyCode) {
this.keyCode = keyCode;
}
};
}
Game.java
package game;
import java.awt.Graphics2D;
import game.input.Keyboard;
import game.input.Mouse;
import game.room.Room;
import userInterface.containers.BB_Window;
// creates a new game
public final class Game {
// the window that the game resides in.
private static BB_Window window;
// the current room that is drawn to the window.
private static Room room;
private static GameLoop gameLoop;
// game constructor cannot be called.
private Game() {}
// inits the game.
// ie, adds input to the game (key and mouse).
public static void init(BB_Window window) {
if(gameLoop != null)
return;
// creates mouse and keyboard listeners.
Mouse mouse = new Mouse();
Keyboard keyboard = new Keyboard();
// adds input listeners to the window.
window.getJFrame().addKeyListener(keyboard);
window.getCanvas().addMouseListener(mouse);
window.getCanvas().addMouseMotionListener(mouse);
// init game loop
gameLoop = new GameLoop();
// init window
Game.window = window;
gameLoop.start();
}
// updates the current room and resets input.
protected static void update() {
// if room doesn't exist, don't update it.
if(room == null)
return;
// updates current room.
Game.room.update();
// resets mouse input.
Mouse.pressed = false;
// resets key input.
Keyboard.resetKeys();
// if a mouse or key button is clicked,
// then it would have to be reset to false here.
}
// renders the current room.
protected static void render() {
// if room doesn't exist, don't render it.
if(room == null)
return;
// creates graphics object from the window canvas.
Graphics2D graphics = (Graphics2D) window.getCanvas().getBufferStrategy().getDrawGraphics();
// creates the screen for next drawing.
graphics.clearRect(0, 0, window.getWidth(), window.getHeight());
// renders the current room.
Game.room.render(graphics);
// shows the buffer.
window.getCanvas().getBufferStrategy().show();
// removes graphics object.
graphics.dispose();
}
// sets the current room to a new one.
public static void setRoom(Room newRoom) {
newRoom.init();
Game.room = newRoom;
}
// returns the current room.
public static Room getRoom() {
return Game.room;
}
// returns width of window.
public static int getWindowWidth() {
return window.getWidth();
}
// returns height of window.
public static int getWindowHeight() {
return window.getHeight();
}
// stops the game loop.
public static void stop() {
gameLoop.stop();
}
}
GameLoop.java
package game;
public class GameLoop implements Runnable {
// the thread that the game runs on.
private Thread thread;
// is the game running.
private boolean running;
// starts the game loop.
// inits the thread and calls its start method.
public void start() {
// you can't start the game if it is started.
if(running)
return;
// starts the game.
running = true;
// creates thread.
thread = new Thread(this);
// starts the game.
// ie, calls thread.run();
thread.start();
}
// stops the game loop.
// interrupts the thread and terminates the currently running JVM.
public void stop() {
// you can't end the game if it is ended.
if(!running)
return;
// ends the game.
running = false;
// interrupts the thread.
// ie, ends the thread.
// this will always end the thread,
// because running is set to false.
thread.interrupt();
// ends the program.
System.exit(0);
}
// this is the game loop.
#Override
public void run() {
// holds information about each frames elapsed time.
double start, previous = System.nanoTime() / 1_000_000_000.0;
// time.
double actualFrameTime, realTime = 0;
// should the game be rendered.
boolean render;
// fps
final int FPS = 60;
final double DESIRED_FRAME_TIME = 1.0 / FPS;
// while the game is running
while(running) {
// calculates the elapsed time of the frame.
// converts from nano seconds to seconds
// by dividing by one billion.
start = System.nanoTime() / 1_000_000_000.0;
actualFrameTime = start - previous;
previous = start;
// the game time is updated by the elapsed frame time.
realTime += actualFrameTime;
// resets it to back to false.
render = false;
// if time surpasses desired frame time, game should update.
while(realTime >= DESIRED_FRAME_TIME && realTime != 0) {
realTime -= DESIRED_FRAME_TIME;
Game.update();
// if the game is updated, the game should render.
// if the game is not updated, the game doesn't render.
render = true;
}
if(render)
Game.render();
// sleep if game should not render.
// reduces cpu usage by a lot.
else
try {
// sleep for one millisecond.
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
BB_Window.java
package userInterface.containers;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;
// the JFrame that the game will reside in.
public final class BB_Window {
private JFrame window;
private Canvas canvas;
private Dimension windowDimension;
// constructs the canvas, window, and buffer.
public BB_Window(String title, int width, int height) {
// creates dimension.
windowDimension = new Dimension(width, height);
// creates a canvas with a bunch of defaults.
canvas = new Canvas();
// sets a non-changeable size.
canvas.setPreferredSize(windowDimension);
canvas.setMinimumSize(windowDimension);
canvas.setMaximumSize(windowDimension);
// cannot be focused for event listeners.
canvas.setFocusable(false);
// creates window with a bunch of defaults.
window = new JFrame();
window.getContentPane().add(canvas);
window.setTitle(title);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setResizable(false);
window.pack();
window.setVisible(true);
// center of screen.
window.setLocationRelativeTo(null);
BufferStrategy bufferStrategy = canvas.getBufferStrategy();
if(bufferStrategy == null)
canvas.createBufferStrategy(3);
}
// returns the frame.
public JFrame getJFrame() {
return window;
}
// returns the window width.
public int getWidth() {
return windowDimension.width;
}
// returns the window height.
public int getHeight() {
return windowDimension.height;
}
// returns the canvas.
public Canvas getCanvas() {
return canvas;
}
}
Room.java
package game.room;
import java.awt.Graphics2D;
// future functionality might be added,
// which is why this class is abstract and not interface.
// represents a room/location in your Game
// eg, a town, a house, a forest, and a cave are all examples of rooms.
public abstract class Room {
public abstract void init();
public abstract void update();
public abstract void render(Graphics2D graphics);
}
I feel that these files are the only ones needed to understand how my game library functions.
However, I notice that whenever I test out my game library, there is a very noticeable stutter that occurs every few seconds, and lasts for a few seconds. This is very annoying. However, what is more annoying is that this blocky/laggy movement is more noticeable on my computer than on other computers. What is going on with my computer for this to be occurring? How do I fix this? Here is an example of how my game library works.
Game.setRoom(new Room() {
private int x, y;
#Override
public void init() {
x = 0;
y = 0;
}
#Override
public void update() {
x++;
y++;
}
#Override
public void render(Graphics2D graphics) {
graphics.fillRect(x, y, 100, 100);
}});
Game.init(new BB_Window("Test", 640, 640));
This example program draws a rectangle that moves diagonally down the screen.
However, sometimes, the rectangle seems to "skip" pixels and moves more than it should.
I tried recording my screen to show exactly what is going on, but for some reason the stutter is not showing up in the video.
I tried enabling hardware acceleration by doing
System.setProperty("sun.java2d.opengl", "true");
but that didn't do anything.
My computer isn't bad at all, so why is the game running more smoother on other computers than on mine?
And what can I do to fix this in my game library?
Thank you in advance.
I am trying to to set up a KeyListener to respond to my keystrokes. I have already setup a mouselistener but for some reason I am un-able to get the keylistener to respond to any keystrokes.
I have created a class that implements KeyListener and overridden the functions. I then created an instance of the new class and added the handler to the JPanel and JFrame. Still no dice
public class main_program extends JFrame {
private int mX_cord, mY_cord,prior_selected_vertex, current_selected_vertex;
private int verticies_to_edge1, verticies_to_edge2;
private int radius =10;
private boolean vertex_selected1 = false, vertex_selected2 = false, edge_ready = false,delete_vertex_ready = false;
private Edge tempEdge = null;
private ArrayList<Integer> vertex_xcord = new ArrayList<Integer>();
private ArrayList<Integer> vertex_ycord = new ArrayList<Integer>();
private ArrayList<Edge> edge = new ArrayList<Edge>();
HandlerMouse handler = new HandlerMouse();
HandlerKey keyhand = new HandlerKey();
private JPanel masterPanel;
private JTextArea masterTextArea;
private JScrollPane masterScrollPane;
private Point point1, point2;
Graphics g;
public main_program(){
setTitle("Graph");
setSize(600, 400);
setDefaultCloseOperation(new JFrame().EXIT_ON_CLOSE);
//this must be set for custom layout of components
setLayout(null);
masterPanel = new JPanel();
masterPanel.setSize(600,300);
masterPanel.setLocation(0, 0);
masterPanel.setBackground(Color.WHITE);
masterPanel.addMouseListener(handler);
masterPanel.addMouseMotionListener(handler);
masterPanel.addKeyListener(keyhand);
masterTextArea = new JTextArea();
masterTextArea.setBackground(Color.green);
masterScrollPane = new JScrollPane();
masterScrollPane.add(masterTextArea);
masterScrollPane.setSize(600, 100);
masterScrollPane.setLocation(0, 300);
masterScrollPane.addMouseListener(handler);
masterScrollPane.addMouseListener(handler);
masterScrollPane.addKeyListener(keyhand);
add(masterPanel);
add(masterScrollPane);
setLocationRelativeTo(null);
setVisible(true);
}
public void paint(Graphics g){
super.paint(g);
for(int i = 0 ; i < vertex_xcord.size(); i++){
g.fillOval(vertex_xcord.get(i), vertex_ycord.get(i), radius, radius);
}
for(int i = 0 ; i<edge.size(); i++){
tempEdge = edge.get(i);
g.drawLine(vertex_xcord.get(tempEdge.vertex1), vertex_ycord.get(tempEdge.vertex1), vertex_xcord.get(tempEdge.vertex2), vertex_ycord.get(tempEdge.vertex2));
}
//g.fillOval(mX_cord, mY_cord, radius, radius);
//repaint();
}
private class HandlerKey implements KeyListener{
public void keyPressed(KeyEvent evt){
System.out.println("key pressed");
if(evt.getKeyCode() == KeyEvent.VK_ENTER && edge_ready){
edge.add(new Edge(prior_selected_vertex, current_selected_vertex));
edge_ready = false;
repaint();
}
}
public void keyReleased(KeyEvent evt){
System.out.println("key rel");
}
public void keyTyped(KeyEvent evt){
System.out.println("key type");
}
}
private class HandlerMouse implements MouseListener, MouseMotionListener{
public void mouseClicked(MouseEvent evt){
mX_cord = evt.getX()-5;
mY_cord = evt.getY()+15;
if( evt.getClickCount() == 1){
//mX_cord = evt.getX();
//mY_cord = evt.getY();
vertex_xcord.add(mX_cord);
vertex_ycord.add(mY_cord);
repaint();
}
else{
for(int i = 0 ; i < vertex_xcord.size(); i++){
if(Math.abs(vertex_xcord.get(i) - mX_cord ) < 15 && Math.abs(vertex_ycord.get(i) - mY_cord ) < 15 ){
if(vertex_selected1 == false){
prior_selected_vertex = i;
vertex_selected1 = true;
}
else{
current_selected_vertex = i;
vertex_selected2 = true;
}
System.out.println("YOU HAVE SELECTED A VERTEX: " + i);
break;
}
}
}
if(vertex_selected2 == true){
edge_ready = true;
verticies_to_edge1 = prior_selected_vertex;
verticies_to_edge2 = current_selected_vertex ;
vertex_selected1 = vertex_selected2 = false;
System.out.println("Ready for edge!");
}
else{
delete_vertex_ready = true;
}
}
public void mouseEntered(MouseEvent arg0)
{
}
public void mouseExited(MouseEvent arg0)
{
}
public void mousePressed(MouseEvent evt)
{
}
public void mouseReleased(MouseEvent arg0)
{
}
public void mouseDragged(MouseEvent e)
{
}
public void mouseMoved(MouseEvent e)
{
}
}
class Edge {
int vertex1, vertex2;
public Edge(int v1, int v2){
vertex1 = v1;
vertex2 = v2;
}
}
public static void main(String[] args){
main_program circle = new main_program();
}
}
You've got several problems with that program, and it suggests that you'd do well to read many of the Swing Q&A's on this site, because these problems (and your main problem) are quite common, and solutions are often posted.
As to your main problem, the problem again is very common: KeyListeners only work if the listened to component has focus. If it loses focus or is not focusable, then you're out of luck. The best solution is often not to use a KeyListener but rather the higher level and more flexible Key Bindings. Google will find the tutorials for you for this.
As for other problems with your code:
You're using null layout, a layout that leads to inflexible GUI's that are very difficult to upgrade and enhance, and that look terrible on all but your current platform and screen resolution. Solution: study and use the layout managers.
You're drawing directly into a JFrame's paint(Graphics g) method, which has risks as you risk messing up the painting of any and all of the JFrame's constituents by doing this. Much better to draw in a JPanel's paintComponent(Graphics g) method, and gain the benefit of Swing (rather than AWT) graphics including automatic double buffering.
Your class has a Graphics variable, g, which suggests that you're contemplating using a stored Graphics object either from a component or from the JVM. This increases the risk of your program throwing a NullPointerException when that Graphics object no longer exists, either that or drawing with it and but not seeing any effect. Solution: draw inside the painting method (again better with a JComponent's paintComponent method) only, or with the Graphics object from a BufferedImage.
You're adding a MouseListener directly to a JPanel but are drawing in a different component, the JFrame, and by doing so without taking insets into account, are placing points in the wrong location. Use the MouseListener on the same component that you're drawing on.
Again, please have a look around here, as I think you'll find a lot of treasures on this site that can be used to enhance your program.
my app currently crashes when you press the home or back button saying "Unfortunately, Rocks has crashed". I have narrowed it down to the line of code that reads:
// draws the canvas on the panel
this.gamePanel.render(canvas);
in this context:
package com.background.rocks;
import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;
public class MainThread extends Thread {
private static final String TAG = MainThread.class.getSimpleName();
// desired fps
private final static int MAX_FPS = 50;
// maximum number of frames to be skipped
private final static int MAX_FRAME_SKIPS = 5;
// the frame period
private final static int FRAME_PERIOD = 1000 / MAX_FPS;
// Surface holder that can access the physical surface
private SurfaceHolder surfaceHolder;
// The actual view that handles inputs
// and draws to the surface
private Graphics gamePanel;
// flag to hold game state
private boolean running;
public void setRunning(boolean running) {
this.running = running;
}
public MainThread(SurfaceHolder surfaceHolder, Graphics gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
#Override
public void run() {
Canvas canvas;
Log.d(TAG, "Starting game loop");
long beginTime; // the time when the cycle begun
long timeDiff; // the time it took for the cycle to execute
int sleepTime; // ms to sleep (<0 if we're behind)
int framesSkipped; // number of frames being skipped
sleepTime = 0;
while (running) {
canvas = null;
// try locking the canvas for exclusive pixel editing
// in the surface
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
beginTime = System.currentTimeMillis();
framesSkipped = 0; // resetting the frames skipped
// update game state
this.gamePanel.update();
// render state to the screen
// draws the canvas on the panel
this.gamePanel.render(canvas);
// calculate how long did the cycle take
timeDiff = System.currentTimeMillis() - beginTime;
// calculate sleep time
sleepTime = (int) (FRAME_PERIOD - timeDiff);
if (sleepTime > 0) {
// if sleepTime > 0 we're OK
try {
// send the thread to sleep for a short period
// very useful for battery saving
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
}
}
while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
// we need to catch up
this.gamePanel.update(); // update without rendering
sleepTime += FRAME_PERIOD; // add frame period to check if in next frame
framesSkipped++;
}
}
} finally {
// in case of an exception the surface is not left in
// an inconsistent state
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
} // end finally
}
}
}
But I'm new to using canvas so I don't know how to fix it. It creates a NullPointerException so I'm assuming it's to do with when resuming the app or pausing the app the canvas isn't stored properly.
Edit: Graphics class (without imports):
package com.background.rocks;
public class Graphics extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = Graphics.class.getSimpleName();
private MainThread thread;
private Player player;
private ArrayList<Rock> rocks = new ArrayList<Rock>();
private Random random = new Random();
private CountDownTimer countdown;
public Graphics(Context context) {
super(context);
// adding the callback (this) to the surface holder to intercept events
getHolder().addCallback(this);
// create shape and load bitmap
player = new Player(BitmapFactory.decodeResource(getResources(), R.drawable.player_orange), 540, 1500);
// create the game loop thread
thread = new MainThread(getHolder(), this);
// make the GamePanel focusable so it can handle events
setFocusable(true);
timer();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// at this point the surface is created and
// we can safely start the game loop
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "Surface is being destroyed");
// tell the thread to shut down and wait for it to finish
// this is a clean shutdown
boolean retry = true;
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
// try again shutting down the thread
}
}
Log.d(TAG, "Thread was shut down cleanly");
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// delegating event handling to the shape
player.handleActionDown((int) event.getX(), (int) event.getY());
// check if in the lower part of the screen we exit
if (event.getY() > getHeight() - 50) {
thread.setRunning(false);
((Activity) getContext()).finish();
} else {
Log.d(TAG, "Coords: x=" + event.getX() + ",y=" + event.getY());
}
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
// the gestures
if (player.isTouched()) {
// the shape was picked up and is being dragged
player.setX((int) event.getX());
player.setY((int) event.getY());
}
}
if (event.getAction() == MotionEvent.ACTION_UP) {
// touch was released
if (player.isTouched()) {
player.setTouched(false);
}
}
return true;
}
**public void render(Canvas canvas) {
if (canvas != null) {
canvas.drawColor(Color.WHITE);
player.draw(canvas);
Rock[] rockArray = rocks.toArray(new Rock[0]);
for (Rock rock : rockArray) {
rock.draw(canvas);
}
}
}**
/**
* This is the game update method. It iterates through all the objects and
* calls their update method if they have one or calls specific engine's
* update method.
*/
public void update() {
Rock[] rockArray = rocks.toArray(new Rock[0]);
for (Rock rock : rocks) {
rock.update();
}
}
public void timer() {
if (countdown != null) {
countdown.cancel();
}
countdown = new CountDownTimer(30000, 800) {
public void onTick(long millisUntilFinished) {
rocks.add(new Rock(BitmapFactory.decodeResource(getResources(), R.drawable.rock), random.nextInt(1080 - 1) + 1, 0));
}
public void onFinish() {
countdown.start();
}
}.start();
}
}
Edit 2: So I managed to change my Render method in Graphics and so the app doesn't crash now when you press home or back, however when I try and resume the app I get a black screen. I'm assuming because it hasn't loaded the canvas back in, but I'm not sure how to do this, any help?
Start running the thread once the surface gets created. i.e. when you get a callback surfaceCreated(), start the thread.
Code Snippet
public void surfaceCreated(SurfaceHolder holder) {
thread.setRunning(true);
thread.start();
}
This might be too late but what I did to fix it was I put a n if statement to test whether the canvas is still their before rendering and updating. Should look like this:
If (canvas != null){
this.gamePanel.render (canvas);
this.gamePanel.update ();
}
I think I followed the same tutorial as you, you will probobly encounter another issue after this on is solved. Im still looking for an answer to that issue. Hope this helps!
It seems like this applet will only draw and update DRAWS when the window is resized or minimized. So the applet will not repaint all the time, but only when manipulating the window.
Am I doing something wrong here?
I am following the gameloop presented here: http://www3.ntu.edu.sg/home/ehchua/programming/java/J8d_Game_Framework.html
The code is here:
package newapplet;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GameApplet extends JApplet { // main class for the game as a Swing application
// Define constants for the game
static final int CANVAS_WIDTH = 493; // width and height of the game screen
static final int CANVAS_HEIGHT = 411;
static final int UPDATE_RATE = 4; // number of game update per second
static final long UPDATE_PERIOD = 1000000000L / UPDATE_RATE; // nanoseconds
static int DRAWS = 0;
// ......
// Enumeration for the states of the game.
public enum gameState {
INITIALIZED, CONNECTING, PLAYING, DISCONNECTED
}
private gameState state;
// Define instance variables for the game objects
// ......
// ......
// Handle for the custom drawing panel
private GameCanvas canvas;
// Constructor to initialize the UI components and game objects
public GameApplet() {
// Initialize the game objects
gameInit();
// UI components
canvas = new GameCanvas();
canvas.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));
this.setContentPane(canvas);
// Other UI components such as button, score board, if any.
// ......
this.setVisible(true);
}
// All the game related codes here
// Initialize all the game objects, run only once in the constructor of the main class.
public void gameInit() {
// ......
state = gameState.INITIALIZED;
}
// Shutdown the game, clean up code that runs only once.
public void gameShutdown() {
// ......
state = gameState.DISCONNECTED;
}
// To start and re-start the game.
public void gameStart() {
// Create a new thread
Thread gameThread = new Thread() {
// Override run() to provide the running behavior of this thread.
#Override
public void run() {
gameLoop();
}
};
// Start the thread. start() calls run(), which in turn calls gameLoop().
gameThread.start();
}
// Run the game loop here.
private void gameLoop() {
// Regenerate the game objects for a new game
// ......
//state = State.PLAYING;
// Game loop
long beginTime, timeTaken, timeLeft;
while (true) {
beginTime = System.nanoTime();
if (state == gameState.DISCONNECTED) break; // break the loop to finish the current play
if (state == gameState.PLAYING) {
// Update the state and position of all the game objects,
// detect collisions and provide responses.
gameUpdate();
}
// Refresh the display
repaint();
// Delay timer to provide the necessary delay to meet the target rate
timeTaken = System.nanoTime() - beginTime;
timeLeft = (UPDATE_PERIOD - timeTaken) / 1000000L; // in milliseconds
if (timeLeft < 10) timeLeft = 10; // set a minimum
try {
// Provides the necessary delay and also yields control so that other thread can do work.
Thread.sleep(timeLeft);
} catch (InterruptedException ex) { }
}
}
// Update the state and position of all the game objects,
// detect collisions and provide responses.
public void gameUpdate() {
}
// Refresh the display. Called back via rapaint(), which invoke the paintComponent().
private void gameDraw(Graphics2D g2d) {
switch (state) {
case INITIALIZED:
g2d.setColor (Color.red);
g2d.drawString ("init",20,20);
break;
case PLAYING:
g2d.setColor (Color.red);
g2d.drawString ("play",20,20);
break;
case CONNECTING:
g2d.setColor (Color.red);
g2d.drawString ("connecting",20,20);
break;
case DISCONNECTED:
g2d.setColor (Color.red);
g2d.drawString ("disconnect",20,20);
break;
}
g2d.setColor (Color.GREEN);
g2d.drawString ("Re-paint: " + DRAWS,30,30);
this.DRAWS++;
// ......
}
// Process a key-pressed event. Update the current state.
public void gameKeyPressed(int keyCode) {
switch (keyCode) {
case KeyEvent.VK_UP:
// ......
break;
case KeyEvent.VK_DOWN:
// ......
break;
case KeyEvent.VK_LEFT:
// ......
break;
case KeyEvent.VK_RIGHT:
// ......
break;
}
}
// Process a key-released event.
public void gameKeyReleased(int keyCode) { }
// Process a key-typed event.
public void gameKeyTyped(char keyChar) { }
// Other methods
// ......
// Custom drawing panel, written as an inner class.
class GameCanvas extends JPanel implements KeyListener {
// Constructor
public GameCanvas() {
setFocusable(true); // so that can receive key-events
requestFocus();
addKeyListener(this);
}
// Override paintComponent to do custom drawing.
// Called back by repaint().
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
super.paintComponent(g2d); // paint background
setBackground(Color.BLACK); // may use an image for background
// Draw the game objects
gameDraw(g2d);
}
// KeyEvent handlers
#Override
public void keyPressed(KeyEvent e) {
gameKeyPressed(e.getKeyCode());
}
#Override
public void keyReleased(KeyEvent e) {
gameKeyReleased(e.getKeyCode());
}
#Override
public void keyTyped(KeyEvent e) {
gameKeyTyped(e.getKeyChar());
}
}
// main
public static void main(String[] args) {
// Use the event dispatch thread to build the UI for thread-safety.
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new GameApplet();
}
});
}
}
Glancing at that code quickly I can see that you are calling repaint() outside of the Event Dispatch Thread, which can cause issues like the one you are seeing. javax.swing.SwingUtilties.invokeAndWait(Runnable r) will allow you to put that repaint() call on the EDT.
http://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html
See this code for a few corrections.
// <applet code='GameApplet' width=400 height=50></applet>
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GameApplet extends JApplet { // main class for the game as a Swing application
// Define constants for the game
static final int CANVAS_WIDTH = 493; // width and height of the game screen
static final int CANVAS_HEIGHT = 411;
static final int UPDATE_RATE = 4; // number of game update per second
static final long UPDATE_PERIOD = 1000000000L / UPDATE_RATE; // nanoseconds
Timer timer;
static int DRAWS = 0;
// ......
// Enumeration for the states of the game.
public enum gameState {
INITIALIZED, CONNECTING, PLAYING, DISCONNECTED
}
private gameState state;
// Define instance variables for the game objects
// ......
// ......
// Handle for the custom drawing panel
private GameCanvas canvas;
// Constructor to initialize the UI components and game objects
public GameApplet() {
// Initialize the game objects
gameInit();
// UI components
canvas = new GameCanvas();
// set the size of the applet in HTML, not the content pane!
canvas.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));
this.setContentPane(canvas);
// Other UI components such as button, score board, if any.
// ......
//this.setVisible(true);
}
// All the game related codes here
// Initialize all the game objects, run only once in the constructor of the main class.
public void gameInit() {
// ......
state = gameState.INITIALIZED;
gameStart();
}
// Shutdown the game, clean up code that runs only once.
public void gameShutdown() {
// ......
state = gameState.DISCONNECTED;
}
#Override
public void destroy() {
timer.stop();
}
// To start and re-start the game.
public void gameStart() {
// Create a new thread
//Thread gameThread = new Thread() {
// Override run() to provide the running behavior of this thread.
// #Override
// public void run() {
gameLoop();
// }
//};
// Start the thread. start() calls run(), which in turn calls gameLoop().
//gameThread.start();
}
// Run the game loop here.
private void gameLoop() {
// Regenerate the game objects for a new game
// ......
//state = State.PLAYING;
// Game loop
ActionListener al = new ActionListener() {
long beginTime, timeTaken, timeLeft;
public void actionPerformed(ActionEvent ae) {
beginTime = System.nanoTime();
if (state == gameState.DISCONNECTED) {
//break; // break the loop to finish the current play
System.out.println("do SOMETHING here..");
}
if (state == gameState.PLAYING) {
// Update the state and position of all the game objects,
// detect collisions and provide responses.
gameUpdate();
}
// Refresh the display
repaint();
// Delay timer to provide the necessary delay to meet the target rate
timeTaken = System.nanoTime() - beginTime;
timeLeft = (UPDATE_PERIOD - timeTaken) / 1000000L; // in milliseconds
if (timeLeft < 10) timeLeft = 10; // set a minimum
}
};
timer = new Timer(40,al);
timer.start();
}
// Update the state and position of all the game objects,
// detect collisions and provide responses.
public void gameUpdate() {
}
// Refresh the display. Called back via rapaint(), which invoke the paintComponent().
private void gameDraw(Graphics2D g2d) {
switch (state) {
case INITIALIZED:
g2d.setColor (Color.red);
g2d.drawString ("init",20,20);
break;
case PLAYING:
g2d.setColor (Color.red);
g2d.drawString ("play",20,20);
break;
case CONNECTING:
g2d.setColor (Color.red);
g2d.drawString ("connecting",20,20);
break;
case DISCONNECTED:
g2d.setColor (Color.red);
g2d.drawString ("disconnect",20,20);
break;
}
g2d.setColor (Color.GREEN);
g2d.drawString ("Re-paint: " + DRAWS,30,30);
this.DRAWS++;
// ......
}
// Process a key-pressed event. Update the current state.
public void gameKeyPressed(int keyCode) {
switch (keyCode) {
case KeyEvent.VK_UP:
// ......
break;
case KeyEvent.VK_DOWN:
// ......
break;
case KeyEvent.VK_LEFT:
// ......
break;
case KeyEvent.VK_RIGHT:
// ......
break;
}
}
// Process a key-released event.
public void gameKeyReleased(int keyCode) { }
// Process a key-typed event.
public void gameKeyTyped(char keyChar) { }
// Other methods
// ......
// Custom drawing panel, written as an inner class.
class GameCanvas extends JPanel implements KeyListener {
// Constructor
public GameCanvas() {
setFocusable(true); // so that can receive key-events
requestFocus();
addKeyListener(this);
}
// Override paintComponent to do custom drawing.
// Called back by repaint().
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
super.paintComponent(g2d); // paint background
setBackground(Color.BLACK); // may use an image for background
// Draw the game objects
gameDraw(g2d);
}
// KeyEvent handlers
#Override
public void keyPressed(KeyEvent e) {
gameKeyPressed(e.getKeyCode());
}
#Override
public void keyReleased(KeyEvent e) {
gameKeyReleased(e.getKeyCode());
}
#Override
public void keyTyped(KeyEvent e) {
gameKeyTyped(e.getKeyChar());
}
}
}
Note that the addition of the single line comment at the top of the source means that (once compiled) it can be launched from the command line using..
> appletviewer GameApplet.java