I'm using an image to draw a background for a Java app, a big one without repeat property. To do so, I used bufferedImage to read the resource, the image, and draw it in a Canvas. Well, it works almost fine. Except one thing, the app still opens even though the image are fallen behind.
I mean, I expected to see both the app and image were loaded at the same time. But it seems like the image has only been loaded after the app started, long enough to see the window frame background (gray color cause I'm using Windows 10).
So I tried my solution by setVisible(true); after the Canvas class has been initialized. The result is still the same.
One more exception that I don't know if it's relevant to the image. But after I closed the app, the console has thrown out an exception called 'Closed'. It like after I close my app, there is still a part of my coding running and try to read the image.
Here are my code:
App.class (main class in here)
import engine.MouseAction;
import graphic.Game;
import graphic.Window;
public class App {
MouseAction mouse;
Game game;
Window window;
App() {
init();
setup();
window.launch();//It's just window.setVisible(true);
}
private void init() {
mouse = new MouseAction();
game = new Game();
window = new Window(game, mouse);
}
private void setup() {
mouse.setWindow(window);
game.start();
}
public static void main(String[] args) {
new App();
}
}
Game.class
package graphic;
import java.awt.Canvas;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import engine.Config;
public class Game extends Canvas implements Runnable {
private static final long serialVersionUID = -545327428912119369L;
private Thread thread;
private boolean running = false;
public Game() {}
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();
}
}
private void update() {
}
private void render() {
BufferStrategy bs = getBufferStrategy();
if (bs == null) {
createBufferStrategy(3);
return;
}
Graphics2D g = (Graphics2D) bs.getDrawGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING , RenderingHints.VALUE_ANTIALIAS_ON);
try {
BufferedImage bg = ImageIO.read(getClass().getResource("/assets/img/bg.jpg"));
g.drawImage(bg, 0, 0, Config.SOL_16x10_small.width, Config.SOL_16x10_small.height, this);
bg.flush();
} catch (IOException e) {
e.printStackTrace();
}
g.dispose();
bs.show();
}
public void run() {
long lastTime = System.nanoTime();
final double amountOfTick = 60.0;
double ns = 1000000000 / amountOfTick; //It's nano second
double delta = 0;
while (running) {
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while (delta >= 1) {
update();
delta--;
}
render();
}
stop();
}
}
That is 2 classes I think it's relevant to my problem. And this code does load images but it loads very slow.
The problem is in your gameloop. You try to load your image file into memory on every iteration of your gameloop, just before you get your graphics context to actually draw it.
Try to load the image into memory beforehand, then simply call the drawing method of Graphics g to render it on every iteration.
You could load the image anywhere before you start your gameloop.
You could also create the bufferstrategy beforehand and then simply get the graphics context in your render() method, the way you do it already.
Ultimately saving the if(bs == null), this will not make any noticeable difference though.
Related
I am trying to create a 2D platform game, by following this tutorial. I have created an Images()-class which looks like this:
package Resources;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Objects;
public class Images {
public static BufferedImage[] tileBlocks;
public Images(){
tileBlocks = new BufferedImage[1];
try{
tileBlocks[0] = ImageIO.read(
// TODO: Fix the error caused by this try/catch
(Objects.requireNonNull(getClass().getResourceAsStream("TileBlocks/block_brick.png")))
);
} catch (IOException e) { e.printStackTrace(); }
}
}
And I created an instantation of it in my GamePanel()-class, which looks like this
package Main;
import GameState.GameStateManager;
import Resources.Images;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.Serial;
#SuppressWarnings("BusyWait")
public class GamePanel extends JPanel implements Runnable, KeyListener {
#Serial
private static final long serialVersionUID = 1L;
// Game thread
private boolean isRunning = false;
int FPS = 60; // Frames per second
long targetTime = 1000 / FPS; // Set target time
// Set the dimension of the game-window
public static final int WIDTH = 800;
public static final int HEIGHT = 600;
private GameStateManager gsm = new GameStateManager(); // Game state manager object
public GamePanel(){
setPreferredSize(new Dimension(WIDTH, HEIGHT));
addKeyListener(this);
setFocusable(true);
// TODO: Fix error (Reports instantiation of utility classes using the new keyword)
new Images();
start();
}
// Start the game (initialize)
private void start() {
isRunning = true;
Thread thread = new Thread(this); // Referring to Runnable
thread.start(); // Call to our game-loop
}
// Game-loop
public void run() {
long start, elapsed, wait; // Keep track of time
gsm = new GameStateManager(); // Initialize game state manager
while (isRunning) {
start = System.nanoTime(); // Start timer
update(); // Update the game-logic
repaint(); // Re-paint the board (built-in method from java.awt)
elapsed = System.nanoTime() - start;
// If everything runs in under the target time we wait (so it runs equally in all computers)
wait = targetTime - elapsed / 1000000; // Divide by 1 000 000 to get milli-seconds
if (wait <= 0) { wait = 5; } // Keep wait a positive value
try {
Thread.sleep(wait);
} catch (Exception e) { e.printStackTrace(); }
}
}
// Updating all game-logic
public void update() { gsm.update(); }// Call methods from game state manager in the game panel class
// Where we draw the graphics
public void paintComponent(Graphics g){
super.paintComponent(g); // Built-in method from java.awt
g.clearRect(0,0,WIDTH,HEIGHT); // Clear the screen before drawing
gsm.draw(g); // Call method from game state manager in the game panel class
}
public void keyPressed(KeyEvent e) {
gsm.keyPressed(e.getKeyCode()); // getKeyCode() turns KeyEvent into an integer
}
public void keyReleased(KeyEvent e) {
gsm.keyReleased(e.getKeyCode()); // getKeyCode() turns KeyEvent into an integer
}
public void keyTyped(KeyEvent e) { }
}
I get an error saying
Instantiation of utility class 'Images'
Inspection info: Reports instantiation of utility classes using the new keyword.
In utility classes, all fields and methods are static.
Instantiation of such classes is most likely unnecessary and indicates a mistake.
And when I try to run the game it doesn't launch, I only get a NullPointerException
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:208)
at inf112.skeleton.app/Resources.Images.<init>(Images.java:17)
at inf112.skeleton.app/Main.GamePanel.<init>(GamePanel.java:35)
at inf112.skeleton.app/Main.Game.game(Game.java:10)
at inf112.skeleton.app/Main.Game.main(Game.java:20)
But if I remove the the instantation, the game launches, but then it crashes with even more errors
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException:
Cannot load from object array because "Resources.Images.tileBlocks" is null
etc...
If I remove the Objects.requireNonNull from
tileBlocks[0] = ImageIO.read((getClass().getResourceAsStream("TileBlocks/block_brick.png"))
The game launches, but it crashes and I get another error saying
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException:
Cannot load from object array because "Resources.Images.tileBlocks" is null
What am I doing wrong here? Why is my list of tileBlocks null? How can I read the image and display it on the tileBlocks?
Put file "block_brick.png" in directory "TileBlocks" in classpath.
I'm trying to make a simple 2D game in Java.
As far as I know, my game should consist of two threads: "event dispatch thread" (for GUI operations) and "game thread" (for game loop).
I created an outline but could not find where to place the game loop.
in short, I'm trying to create a game loop without freezing my UI thread.
I would be grateful if you could give any information about the things I did wrong.
That's my game loop (You can also give tips to create a better game loop):
while(true) {
repaint();
try {
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("Forge and Attack");
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setUndecorated(true);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setFocusable(true);
frame.add(new MyPanel());
}
}
class MyPanel extends JPanel implements KeyListener, MouseListener {
public MyPanel() {
setBackground(Color.BLACK);
setOpaque(true);
addKeyListener(this);
addMouseListener(new MouseAdapter(){
public void mousePressed(MouseEvent e){
}
});
}
#Override
public void paint(Graphics g) {
}
}
I thought this an interesting topic to expand on... I have covered the questions you asked as well as showed the maybe a better or correct way of doing certain things like painting, and listening for keys pressed, as well as some others like separation of concerns and making the entire game more reusable/expandable.
1. Where to place the game loop?
So this isn't straight forward and can depend on each individuals coding style, but really all we seek to achieve here is to create the game loop and start it at an appropriate time. I believe code speaks a 1000 words (sometimes it might just be 1000 words :)), but below is some code which in the most minimally possible way (still producing a valid working example) shows where a game loop can be created/placed and used in the code, the code is heavily commented for clarity and understanding:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
public class MyGame {
private Scene scene;
private Sprite player;
private Thread gameLoop;
private boolean isRunning;
public MyGame() {
createAndShowUI();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(MyGame::new);
}
/**
* Here we will create our swing UI as well as initialise and setup our
* sprites, scene, and game loop and other buttons etc
*/
private void createAndShowUI() {
JFrame frame = new JFrame("MyGame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
player = new Sprite(/*ImageIO.read(getClass().getResourceAsStream("...."))*/);
this.scene = new Scene();
this.scene.add(player);
this.addKeyBindings();
this.setupGameLoop();
frame.add(scene);
frame.pack();
frame.setVisible(true);
// after setting the frame visible we start the game loop, this could be done in a button or wherever you want
this.isRunning = true;
this.gameLoop.start();
}
/**
* This method would actually create and setup the game loop The game loop
* will always be encapsulated in a Thread, Timer or some sort of construct
* that generates a separate thread in order to not block the UI
*/
private void setupGameLoop() {
// initialise the thread
gameLoop = new Thread(() -> {
// while the game "is running" and the isRunning boolean is set to true, loop forever
while (isRunning) {
// here we do 2 very important things which might later be expanded to 3:
// 1. We call Scene#update: this essentially will iterate all of our Sprites in our game and update their movments/position in the game via Sprite#update()
this.scene.update();
// TODO later on one might add a method like this.scene.checkCollisions in which you check if 2 sprites are interesecting and do something about it
// 2. We then call JPanel#repaint() which will cause JPanel#paintComponent to be called and thus we will iterate all of our sprites
// and invoke the Sprite#render method which will draw them to the screen
this.scene.repaint();
// here we throttle our game loop, because we are using a while loop this will execute as fast as it possible can, which might not be needed
// so here we call Thread#slepp so we can give the CPU some time to breathe :)
try {
Thread.sleep(15);
} catch (InterruptedException ex) {
}
}
});
}
private void addKeyBindings() {
// here we would use KeyBindings (https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html) and add them to our Scene/JPanel
// these would allow us to manipulate our Sprite objects using the keyboard below is 2 examples for using the A key to make our player/Sprite go left
// or the D key to make the player/Sprite go to the right
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "A pressed");
this.scene.getActionMap().put("A pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.LEFT = true;
}
});
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "A released");
this.scene.getActionMap().put("A released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.LEFT = false;
}
});
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "D pressed");
this.scene.getActionMap().put("D pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.RIGHT = true;
}
});
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "D released");
this.scene.getActionMap().put("D released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.RIGHT = false;
}
});
}
public class Scene extends JPanel {
private final ArrayList<Sprite> sprites;
public Scene() {
// we are using a game loop to repaint, so probably dont want swing randomly doing it for us
this.setIgnoreRepaint(true);
this.sprites = new ArrayList<>();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// this method gets called on Scene#repaint in our game loop and we then render each in our game
sprites.forEach((sprite) -> {
sprite.render(g2d);
});
}
#Override
public Dimension getPreferredSize() {
// because no components are added to the JPanel, we will have a default sizxe of 0,0 so we instead force the JPanel to a size we want
return new Dimension(500, 500);
}
public void add(Sprite go) {
this.sprites.add(go);
}
private void update() {
// this method gets called on Scene#update in our game loop and we then update the sprites movement and position our game
sprites.forEach((go) -> {
go.update();
});
}
}
public class Sprite {
private int x = 50, y = 50, speed = 5;
//private final BufferedImage image;
public boolean LEFT, RIGHT, UP, DOWN;
public Sprite(/*BufferedImage image*/) {
//this.image = image;
}
public void render(Graphics2D g2d) {
//g2d.drawImage(this.image, this.x, this.y, null);
g2d.fillRect(this.x, this.y, 100, 100);
}
public void update() {
if (LEFT) {
this.x -= this.speed;
}
if (RIGHT) {
this.x += this.speed;
}
if (UP) {
this.y -= this.speed;
}
if (DOWN) {
this.y += this.speed;
}
}
}
}
2. Tips to create a better game loop
This very much like the first point in my answer is very subjective to what you are trying to achieve and at what granularity will your problem be satisfied with. So instead of prescribing 1 type of game loop. Let us look at the various kinds we can have:
First what is a game loop?*
The game loop is the overall flow control for the entire game program. It’s a loop because the game keeps doing a series of actions over and over again until the user quits. Each iteration of the game loop is known as a frame. Most real-time games update several times per second: 30 and 60 are the two most common intervals. If a game runs at 60 FPS (frames per second), this means that the game loop completes 60 iterations every second.
a. While loop
This we have seen in the above example and is simply a while loop encapsulated inside a Thread with possibly a Thread#sleep call to help throttle CPU usage. This and the Swing Timer are probably the most basic you can use.
gameLoop = new Thread(() -> {
while (isRunning) {
this.scene.update();
this.scene.repaint();
try {
Thread.sleep(15);
} catch (InterruptedException ex) {
}
}
});
Pros:
Easy to implement
All updating and rendering, repainting is done in a separate thread from the EDT
Cons:
Cannot guarantee the same frame rate on various PCs, so movement of the game might look better/worse or slower/faster on various computers depending on the hardware
b. Swing Timer
Similar to the while loop, a Swing Timer can be used in which an action event is fired periodically, because it is fired periodically we can simply use an if statement to check if the game is running and then call our necessary methods
gameLoop = new Timer(15, (ActionEvent e) -> {
if (isRunning) {
MyGame.this.scene.update();
MyGame.this.scene.repaint();
}
});
Pros:
Easy to implement
Cons:
Runs on the EDT which is not necessary or wanted as we are not updating any Swing components but rather simply painting to it
Cannot guarantee the same frame rate on various PCs, so movement of the game might look better/worse or slower/faster on various computers depending on the hardware
c. Fixed time step*
This is a more complex game loop (but simpler than a variable time step game loop). This works on the premise that we want to achieve a specific FPS i.e. 30 or 60 frames per second, and thus we simply make sure we call our update and rendering methods that exact number of times per seconds.
Update methods do not accept a "time elapsed", as they assume each update is for a fixed time period. Calculations may be done as position += distancePerUpdate. The example includes an interpolation during render.
gameLoop = new Thread(() -> {
//This value would probably be stored elsewhere.
final double GAME_HERTZ = 60.0;
//Calculate how many ns each frame should take for our target game hertz.
final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
//If we are able to get as high as this FPS, don't render again.
final double TARGET_FPS = 60;
final double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS;
//At the very most we will update the game this many times before a new render.
//If you're worried about visual hitches more than perfect timing, set this to 1.
final int MAX_UPDATES_BEFORE_RENDER = 5;
//We will need the last update time.
double lastUpdateTime = System.nanoTime();
//Store the last time we rendered.
double lastRenderTime = System.nanoTime();
while (isRunning) {
double now = System.nanoTime();
int updateCount = 0;
//Do as many game updates as we need to, potentially playing catchup.
while (now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER) {
MyGame.this.scene.update();
lastUpdateTime += TIME_BETWEEN_UPDATES;
updateCount++;
}
//If for some reason an update takes forever, we don't want to do an insane number of catchups.
//If you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
if (now - lastUpdateTime > TIME_BETWEEN_UPDATES) {
lastUpdateTime = now - TIME_BETWEEN_UPDATES;
}
//Render. To do so, we need to calculate interpolation for a smooth render.
float interpolation = Math.min(1.0f, (float) ((now - lastUpdateTime) / TIME_BETWEEN_UPDATES));
MyGame.this.scene.render(interpolation);
lastRenderTime = now;
//Yield until it has been at least the target time between renders. This saves the CPU from hogging.
while (now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES) {
//allow the threading system to play threads that are waiting to run.
Thread.yield();
//This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it.
//You can remove this line and it will still work (better), your CPU just climbs on certain OSes.
//FYI on some OS's this can cause pretty bad stuttering. Scroll down and have a look at different peoples' solutions to this.
//On my OS it does not unpuase the game if i take this away
try {
Thread.sleep(1);
} catch (Exception e) {
}
now = System.nanoTime();
}
}
});
This loop will need other changes such to take place to allow for interpolation:
Scene:
public class Scene extends JPanel {
private float interpolation;
#Override
protected void paintComponent(Graphics g) {
...
sprites.forEach((sprite) -> {
sprite.render(g2d, this.interpolation);
});
}
public void render(float interpolation) {
this.interpolation = interpolation;
this.repaint();
}
}
Sprite:
public class Sprite {
public void render(Graphics2D g2d, float interpolation) {
g2d.fillRect((int) (this.x + interpolation), (int) (this.y + interpolation), 100, 100);
}
}
Pros:
predictable, deterministic FPS on various computers/hardware
clearer calculation code
Cons:
not synced to monitor v-sync (causes jittery graphics unless you interpolate) - this example interpolates
limited max frame rate (unless you interpolate) - this example interpolates
d. Variable time step*
Usually used when a physics system is being implemented, or whenever a record of elapsed time is needed, I.e. animations. Physics/animation updates are passed a "time elapsed since last update" argument and are hence framerate-dependent. This may mean doing calculations as position += distancePerSecond * timeElapsed.
gameLoop = new Thread(() -> {
// how many frames should be drawn in a second
final int FRAMES_PER_SECOND = 60;
// calculate how many nano seconds each frame should take for our target frames per second.
final long TIME_BETWEEN_UPDATES = 1000000000 / FRAMES_PER_SECOND;
// track number of frames
int frameCount;
// if you're worried about visual hitches more than perfect timing, set this to 1. else 5 should be okay
final int MAX_UPDATES_BETWEEN_RENDER = 1;
// we will need the last update time.
long lastUpdateTime = System.nanoTime();
// store the time we started this will be used for updating map and charcter animations
long currTime = System.currentTimeMillis();
while (isRunning) {
long now = System.nanoTime();
long elapsedTime = System.currentTimeMillis() - currTime;
currTime += elapsedTime;
int updateCount = 0;
// do as many game updates as we need to, potentially playing catchup.
while (now - lastUpdateTime >= TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BETWEEN_RENDER) {
MyGame.this.scene.update(elapsedTime);//Update the entity movements and collision checks etc (all has to do with updating the games status i.e call move() on Enitites)
lastUpdateTime += TIME_BETWEEN_UPDATES;
updateCount++;
}
// if for some reason an update takes forever, we don't want to do an insane number of catchups.
// if you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
if (now - lastUpdateTime >= TIME_BETWEEN_UPDATES) {
lastUpdateTime = now - TIME_BETWEEN_UPDATES;
}
MyGame.this.scene.repaint(); // draw call for rendering sprites etc
long lastRenderTime = now;
//Yield until it has been at least the target time between renders. This saves the CPU from hogging.
while (now - lastRenderTime < TIME_BETWEEN_UPDATES && now - lastUpdateTime < TIME_BETWEEN_UPDATES) {
Thread.yield();
now = System.nanoTime();
}
}
});
Scene:
public class Scene extends JPanel {
private void update(long elapsedTime) {
// this method gets called on Scene#update in our game loop and we then update the sprites movement and position our game
sprites.forEach((go) -> {
go.update(elapsedTime);
});
}
}
Sprite:
public class Sprite {
private float speed = 0.5f;
public void update(long elapsedTime) {
if (LEFT) {
this.x -= this.speed * elapsedTime;
}
if (RIGHT) {
this.x += this.speed * elapsedTime;
}
if (UP) {
this.y -= this.speed * elapsedTime;
}
if (DOWN) {
this.y += this.speed * elapsedTime;
}
}
}
Pros:
smooth
easier to to code
Cons:
non-deterministic, unpredictable at very small or large steps
I have made a class that displays a series of images to create an animation. I would like the animation to take place on a separate thread than the rest of the program. However, when I try to start the animation class, I get an error.
This is some of the animation class (There is more but is is irrelevant):
/**
* Starts a new thread to play the animation on
*/
public void start()
{
playing = true;
animateThread = (new Thread(() -> run()));
animateThread.start();
}
/**
* Will go through sprites and display the images
*/
public void run()
{
int index = 0;
while (playing)
{
if (index > sprites.length)
{
index = 0;
}
try
{
g.drawImage(sprites[index].getImage(), x, y, null);
animateThread.sleep(speed);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
index++;
}
}
I have also attempted at making the animation class Runnable, then making the whole object a new Thread but I received the same error.
This is the class which holds the JFrame and starts the animation (There is more but is is irrelevant):
public static void main(String[] args)
{
AnimationTester tester = new AnimationTester();
tester.frame.setResizable(false);
tester.frame.setTitle("Tester");
tester.frame.add(tester);
tester.frame.pack();
tester.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
tester.frame.setLocationRelativeTo(null);
tester.frame.setVisible(true);
//Make the window not have to be clicked on to get input (Set is as the main focus when it begins)
tester.requestFocusInWindow();
//Start the program
tester.start();
}
public void start()
{
createGraphics();
animation.start();
}
public void createGraphics()
{
BufferStrategy bs = getBufferStrategy();
//Checks to see if the BufferStrategy has already been created, it only needs to be created once
if (bs == null)
{
//Always do triple buffering (put 3 in the param)
createBufferStrategy(3);
return;
}
//Links the bufferStrategy and graphics, creating a graphics context
g = bs.getDrawGraphics();
try
{
animation = new Animation(ImageIO.read(getClass().getResource("/SpriteSheet.jpg")), 16, 2, 200, 250, 250, 2.0);
animation.addGraphics(g);
}
catch (IOException e)
{
e.printStackTrace();
}
}
That's not really how BufferStrategy works, instead of using createGraphics, you should be calling it to update the state and render it to the screen
So, in your Thread, it should be updating the state in some way and call some "render" method which would get the next page, render the state and push that state to the screen.
Take a closer look at BufferStrategy and BufferStrategy and BufferCapabilities for more details about how it's suppose to work
For example
So I have been looking at Notch's Metagun source code and I can't seem to figure out how he got to get the sprites animating. Right now all I am trying to do is loop through some of images of a character's walking animation. Here is the code, my output,so far,only shows the first image of walking which is the character standing still:
package animation;
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
public class SpriteAnimation extends JComponent{
/**
*
*/
private static final long serialVersionUID = 1L;
int frame=0;
public void paint(Graphics g){
try{
BufferedImage still = ImageIO.read(SpriteAnimation.class.getResource("still.png"));
BufferedImage walkRight = ImageIO.read(SpriteAnimation.class.getResource("right.png"));
BufferedImage midWalk = ImageIO.read(SpriteAnimation.class.getResource("mid.png"));
BufferedImage walkLeft = ImageIO.read(SpriteAnimation.class.getResource("left.png"));
BufferedImage[] states={still,walkRight,midWalk};
int frame=0;
do{
frame++;
if(frame>=3){
frame=0;
g.drawImage(states[frame],0,0,null);
}
}
while(true);
}catch(Exception e){
}
}
}
I don't know why the other guys went ahead and just tried to refactor your code without even mentioning the real issue here: if you're using Swing to make your animation, that loop there is a big no no. You're basically hogging the EDT and stalling the whole GUI by doing that.
You should rewrite your code so your SpriteAnimation draws only one frame each time the paint method is called, while the animation loop is managed externaly by some kind of timer.
Quick example:
public class SpriteAnimation extends JComponent{
private int currentFrame = 0;
private BufferedImage[] frames;
public SpriteAnimation(){
/**
* Load your frames
*/
}
public void paintComponent(Graphics g){
currentFrame++;
if(frame >= 3)
frame = 0;
// we pass this as the ImageObserver in case the images are
// loaded asynchronously
g.drawImage(frames[currentFrame], 0, 0, this);
}
}
And in your main method:
// Timer is a swing timer
Timer timer = new Timer(
100,
new ActionListener() {
public void actionPerformed(ActionEvent evt) {
// main frame is your main animation canvas (eg a JFrame)
mainFrame.repaint();
}
});
timer.start();
Swing Timer Documentation
The problem is here:
if(frame>=3){
frame=0;
g.drawImage(states[frame],0,0,null);
}
needs to be:
if(frame>=3){
frame=0;
}
g.drawImage(states[frame],0,0,null);
additionally, your states array is missing walkLeft:
BufferedImage[] states={still,walkRight,midWalk};
which also means that you probably want your condition to be frame > states.length on the above snippet.
NOTE: You should really go about using timers as suggested by #asermax's comment, but this should fix the bugs you had at least.
I've just starting delving into the wonders of Java ME but have become frustrated when trying to create a thread...
Below is the code which compiles absolutely fine. However, as soon as I install it on my G600 and run it, 'Java Game Error' pops up.
My method of putting it in a jar file and installing it works, as I have created a game with no threads and that works fine.
import java.util.Random;
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
import javax.microedition.midlet.*;
public class CanvasTest extends MIDlet {
Display display;
public CanvasTest() {
}
public void startApp() {
TestCanvas thecanvas = new TestCanvas();
display = Display.getDisplay(this);
display.setCurrent(thecanvas);
}
public void pauseApp() {}
public void destroyApp(boolean unconditional) {}
}
class TestCanvas extends GameCanvas implements Runnable {
Font font;
int width;
int height;
boolean running = true;
public TestCanvas() {
super(false);
setFullScreenMode(true);
width = getWidth();
height = getHeight();
Thread thisThread = new Thread(this);
thisThread.start();
}
public void paint(Graphics g) {
Random rand = new Random();
g.setColor(rand.nextInt(255), rand.nextInt(255), rand.nextInt(255));
g.fillRect(0, 0, width, height);
}
public void run() {
while(running) {
paint(getGraphics());
flushGraphics();
try {
Thread.sleep(50);
}
catch(InterruptedException ex) {}
}
}
};
Note: yes, this is not the game, it merely demonstrates the problem I am facing.
Thanks in advance!
Just a wild guess, but a general rule in Java is that you can't "touch" the UI out of the main thread. Well, this a little bit roughly explained, but there are many articles about the topic.
I suggest you to avoid calling UI methods like paint() or flushGraphics() from a separate Thread.
I hope it helps.
did you test it at emulator prior to phone? if not - why? if yes - how did it go?
regarding the code it looks OK to me except for the slippery two lines where you create and start thread from constructor. I'd rather move these two lines at the end of startApp
public void startApp() {
TestCanvas theCanvas= new TestCanvas();
display = Display.getDisplay(this);
display.setCurrent(theCanvas);
new Thread(theCanvas).start(); // add here and...
}
//...
public TestCanvas() {
super(false);
setFullScreenMode(true);
width = getWidth();
height = getHeight();
// ...and remove here
// Thread thisThread = new Thread(this);
// thisThread.start();
}