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.
Related
I am trying to write a game where the player is a character, falling interminably through the Earth. However, I am getting stuck at the very beginning, programming the scrolling background. My current approach is to create a JFrame and add to it an object of a class that extends JFrame. In this second class, I open the background image in the constructor. Then, in the first class, I create a new thread for the second class, and alternate sleeping and moving the y-coordinate of the background. Back in the second class, this movement triggers a repaint, and the image is drawn twice, once at the y-coordinate, and once at the y-coordinate minus the height of the JPanel. This current code gives the desired affect at any given frame, but the movement is slow and uneven. I think it has something to do with the amount of repaint requests, but I am fairly new to graphics in java. My question is, can this be fixed so the image move steadily across the screen, or should I try a completely different approach? If my current method is fundamentally flawed and can not be fixed, could you provide some incite as to how I can produce a smooth moving image? Thank you in advance.
This is my current code:
//ScrollingImage.java
import javax.swing.JFrame;
public class ScrollingImage {
public static void main(String[] args){
JFrame holder = new JFrame("New Game");
Background background = new Background();
Thread thread = new Thread(background);
holder.setSize(400, 400);
holder.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
holder.add(background);
holder.setVisible(true);
thread.start();
while (true){
background.move();
try {
thread.sleep(100);
}
catch (InterruptedException e){}
}
}
}
and:
//Background.java
import javax.swing.JPanel;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.File;
public class Background extends JPanel implements Runnable {
private BufferedImage image;
private int topEdgeY;
public Background(){
try {
image = ImageIO.read(new File("background.png"));
}
catch (Exception e){}
topEdgeY = 0;
}
public void run(){
repaint();
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(image, 0, topEdgeY, getWidth(), getHeight(), this);
g.drawImage(image, 0, topEdgeY - getWidth(), getWidth(), getHeight(), this);
}
public void move(){
topEdgeY += 5;
if (topEdgeY == getWidth())
topEdgeY = 0;
repaint();
}
}
You need a proper game loop. Currently, your game sleeps 100ms after every iteration, that is (even if your updating and rendering takes no time) 10fps and will go even lower as your game doing more job.
Game loops is a huge topic and there are several good approaches for different situations like is it a multiplayer game or how much physics involved in the game but for your case a simple one will just be fine.
A good paper about game loops, hopefully this will makes you understand:
http://gameprogrammingpatterns.com/game-loop.html
And a pretty good example written in Java:
http://www.java-gaming.org/index.php?topic=24220.0
i'm using a label for a little sprite for some testing that i'm doing, but i want to move the sprite 10 pixels per keypress. Now, i can do that, but now i'm trying to make it move the 10 pixels smoothly, so i tried the next code:
for(int i = 0; i < 100; i++){
x++;
container.setLocation(x, y);
System.out.println(x);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Now the problem is that, the sprite only moves when the for cycle ends, but the console shows the X value changing for each iteration. Any thoughts/help?
Thanks!
I suggest you to take a look at how to animate a JComponent using Swing Timer class, instead of for loop. You can find various tutorials about how to use Swing Timer. Here, to briefly explain, you are blocking EDT(Event Dispatch Thread) which operates the graphical side of the Java. Whenever you want to make a constant and smooth flow in your animations, make sure that you never block the EDT.
EDIT: Here is the demonstration of the usage of Swing Timer Class:
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;
public class AnimationTrial extends JFrame {
private final int DELAY = 10;
private Timer timer;
private int x, y;
private JLabel label;
public static void main(String[] args) {
EventQueue.invokeLater( new Runnable () {
#Override
public void run() {
new AnimationTrial();
}
});
}
public AnimationTrial()
{
setSize(500, 500);
x = 50;
y = 50;
label = new JLabel("They see me movin' they hatin'!");
timer = new Timer( DELAY, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent arg0) {
x++;
label.setLocation(x, y);
}
});
timer.start();
getContentPane().add(label);
pack();
setVisible (true);
}
}
If you dont create new Thread, the user interface runs on the same thread as its method.
Therefore your for-cycle is fired after some action and thread cant do anything else until it ends.
Solution : Create your own class, pass the JLabel or the whole form as parameter in constructor, implement threading and run it as new thread.
I'd suggest you give a look to the Timing Framework, if you want to do something close to an animation in Swing. It could help you, depending on your general need.
If you want other sprites to move in sync with your sprite you can create a TimerTask and use scheduleAtFixedRate(). Your TimerTask would then be responsible for moving all sprites and redrawing everything that was part of the moving like the JPanel in the background and the sprites.
To make your code snippet work you would have to add redrawing of the Background and the sprite after setting the location but I would advise against that approach as it can easily lead to badly designed code where you create one God Class that does everything.
The TimerTask approach should also be more precise if the calculations need a bit time as it tries to have the same time between 2 calls where the approach with the sleeping thread can easily lead to different delays if the calculations are finished earlier or later.
I am making a Pokemon Style 2D java game using no libraries, just pure java, and I am working on and having issues getting a water tile to animate. I want the tile to update every half a second or so. I will post my main class, abstract tile class, water class, and screen class so that maybe you can figure out a way to so me how to animate tiles in my game.
P.S: Right now I am trying to animate a water tile. And all the sprites are for testing and will be changed later.
Code at DropBox: AnimatedTile, Main, Screen, Tile.
I posted my animation tutorial in other animation question although doesn't seem the other guy liked it so much. Maybe you'll find it more useful, I used java.awt only. Has a working example on how to animate images. In my method - all of the tiles for the image to be animated are contained in one long image, and by updating the X coordinate of the part of buffer to be loaded it is possible to scroll through the frames and achieve animation that way.
Enjoy:
https://sites.google.com/site/javagamescorner/home/animated-sprites
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
class AnimatedWater {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
final JPanel gui = new JPanel(new GridLayout(2,0,0,0));
final AnimatedTile[] tiles = new AnimatedTile[8];
for (int ii=0; ii<tiles.length; ii++) {
tiles[ii] = new AnimatedTile();
gui.add(new JLabel(new ImageIcon(tiles[ii])));
}
ActionListener listener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (int ii=0; ii<tiles.length; ii++) {
tiles[ii].paintImage();
gui.repaint();
}
}
};
Timer timer = new Timer(50, listener);
timer.start();
JOptionPane.showMessageDialog(null, gui);
timer.stop();
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}
class AnimatedTile extends BufferedImage {
GradientPaint[] frameGradient;
int frame = 0;
AnimatedTile() {
super(60,60,BufferedImage.TYPE_INT_RGB);
frameGradient = new GradientPaint[6];
for (int ii=0; ii<frameGradient.length; ii++) {
frameGradient[ii] = new GradientPaint(
0f,(float)ii,Color.BLUE,
0f,(float)ii+3,Color.CYAN,true);
}
}
public void paintImage() {
Graphics2D g = createGraphics();
if (frame==frameGradient.length-1) frame = 0;
else frame++;
g.setPaint(frameGradient[frame]);
g.fillRect(0, 0, getWidth(), getHeight());
g.dispose();
}
}
So I'm making a game where you can put bombs on the location of your character. Each bomb is associated with a GIF image when the bomb is displayed and eventually go BOOM (think about Bomberman).
The problem was, when i tried to paint more than one bomb on the screen, it was painted from the last frame of the GIF. Investigating, I found the method image.flush() to reset the GIF cicle but now the problem is that every time I paint a second bomb on the screen, the GIF cycle is reset for all previously bombs on screen.
Here is my constructor for each bomb:
public Tnt(int x, int y){
this.x = x;
this.y = y;
ImageIcon ii = new ImageIcon("src/main/resources/modelObjects/tnt.gif");
image = ii.getImage();
image.flush();
}
Every bomb i create enters an ArrayList (listTnt) and is removed after 6 secs, so i only paint the bombs already active.
Here is my method for drawing:
public void draw(Graphics2D g2d, JPanel board){
for(Tnt tnt: listTnt){
g2d.drawImage(tnt.getImage(), tnt.getX(), tnt.getY(), board);
}
}
EDIT: Seems that the problem was ImageIcon, since it reuses the image using Toolkit.getImage. Instead, Toolkit.createImage create a not reusable image.
Here is my new constructor for Tnt that worked perfectly:
public Tnt(int x, int y){
this.x = x;
this.y = y;
Toolkit t = Toolkit.getDefaultToolkit ();
image = t.createImage("src/main/resources/modelObjects/tnt.gif");
}
I dont even need image.flush() now. Thank you all.
The underlying Image is being reused amongst each ImageIcon.
Judging by the OpenJDK source code, it appears to be due to the fact that each simply requests the Image via Toolkit.getImage.
This method has a nifty caveat, however, which explains the issue at hand:
The underlying toolkit attempts to resolve multiple requests with the same filename to the same returned Image.
Instead, you should skip the ImageIcon step completely (since it's inappropriate to be using a Swing class unnecessarily in the first place), and instead call Toolkit.createImage, which states in the documentation:
The returned Image is a new object which will not be shared with any other caller of this method or its getImage variant.
Good luck.
As I did not know how to solve this, I tried #super_ solution and it works quite nicely. I share the code for anyone who wants an example. +1 to him
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class TestAnimatedGif {
private static final int IMAGE_COUNT = 9;
protected void initUI() {
JFrame frame = new JFrame(TestAnimatedGif.class.getSimpleName());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JPanel panel = new JPanel();
frame.add(panel);
frame.setSize(600, 400);
frame.setVisible(true);
final Timer t = new Timer(1000, null);
t.addActionListener(new ActionListener() {
int count = 0;
#Override
public void actionPerformed(ActionEvent e) {
if (count < IMAGE_COUNT) {
try {
JLabel image = new JLabel(new ImageIcon(Toolkit.getDefaultToolkit().createImage(
new URL("http://www.sitevip.net/gifs/bomba/BOMB-B_animado.gif"))));
panel.add(image);
count++;
panel.revalidate();
panel.repaint();
System.err.println("image added");
} catch (MalformedURLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
} else {
t.stop();
}
}
});
t.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestAnimatedGif().initUI();
}
});
}
}
I'm making a simple tower defense game in Swing and I've run into a performance problem when I try to put many sprites (more than 20) on screen.
The whole game takes place on a JPanel which has setIgnoreRepaint(true).
Here is the paintComponent method (con is the Controller):
public void paintComponent(Graphics g){
super.paintComponent(g);
//Draw grid
g.drawImage(background, 0, 0, null);
if (con != null){
//Draw towers
for (Tower t : con.getTowerList()){
t.paintTower(g);
}
//Draw targets
if (con.getTargets().size() != 0){
for (Target t : con.getTargets()){
t.paintTarget(g);
}
//Draw shots
for (Shot s : con.getShots()){
s.paintShot(g);
}
}
}
}
The Target class simple paints a BufferedImage at its current location. The getImage method doesn't create a new BufferedImage, it simply returns the Controller class's instance of it:
public void paintTarget(Graphics g){
g.drawImage(con.getImage("target"), getPosition().x - 20, getPosition().y - 20, null);
}
Each target runs a Swing Timer to calculate its position. This is the ActionListener it calls:
public void actionPerformed(ActionEvent e) {
if (!waypointReached()){
x += dx;
y += dy;
con.repaintArea((int)x - 25, (int)y - 25, 50, 50);
}
else{
moving = false;
mover.stop();
}
}
private boolean waypointReached(){
return Math.abs(x - currentWaypoint.x) <= speed && Math.abs(y - currentWaypoint.y) <= speed;
}
Other than that, repaint() is only called when placing a new tower.
How can I improve the performance?
Each target runs a Swing Timer to calculate its position. This is the ActionListener it calls:
This may be your problem - having each target/bullet (I assume?) responsible for keeping track of when to update itself and draw itself sounds like quite a bit of work. The more common approach is to have a loop along the lines of
while (gameIsRunning) {
int timeElapsed = timeSinceLastUpdate();
for (GameEntity e : entities) {
e.update(timeElapsed);
}
render(); // or simply repaint in your case, I guess
Thread.sleep(???); // You don't want to do this on the main Swing (EDT) thread though
}
Essentially, an object further up the chain has the responsibility to keep track of all entities in your game, tell them to update themselves, and render them.
I think what might be at fault here is your whole logic of the games setup (no offense intended), As stated in another answer you have different timers taking care of each entities movement, this is not good. I'd suggest taking a look at some gaming loop examples, and adjusting yours to this, you'll notice a great readability and performance improvement a few nice links:
http://www.java-gaming.org/index.php/topic,24220.0
http://www.cokeandcode.com/info/tut2d.html
http://entropyinteractive.com/2011/02/game-engine-design-the-game-loop/
I was initially wary of the too-many-timer theory. Instances of javax.swing.Timer use "a single, shared thread (created by the first Timer object that executes)." Dozens or even scores are perfectly fine, but hundreds typically start to become sluggish. Depending on period and duty cycle, the EventQueue eventually saturates. I agree with the others that you need to critically examine your design, but you may want to experiment with setCoalesce(). For reference, here's an sscce that you may like to profile.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Timer;
/**
* #see http://stackoverflow.com/a/11436660/230513
*/
public class TimerTest extends JPanel {
private static final int N = 25;
public TimerTest() {
super(new GridLayout(N, N));
for (int i = 0; i < N * N; i++) {
this.add(new TimedLabel());
}
}
private static class TimedLabel extends JLabel {
private static final Random r = new Random();
public TimedLabel() {
super("000", JLabel.CENTER);
// period 100 to 1000 ms; frequency 1 to 10 Hz.
Timer timer = new Timer(r.nextInt(900) + 100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
TimedLabel.this.setText(next());
}
});
timer.setCoalesce(true);
timer.start();
}
private String next() {
return String.valueOf(r.nextInt(900) + 100);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(640, 480);
}
private void display() {
JFrame f = new JFrame("TimerTet");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JScrollPane(this));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new TimerTest().display();
}
});
}
}
for painting in the Swing is better (in all cases >= Java5) use Swing Timer exclusivelly
this painting proccess required only one Swing Timer
example about bunch of Stars and one Swing Timer
Try to use one timer for all the targets.
If you have 20 targets then you will also have 20 timers running simultaneously (think about 1000 targets?). There is some expense and the most important thing is each of them is doing the similar job -- to calculate the position -- You don't need to split them. I guess it is a simple task, which will not take you a blink, even running 20 times.
If I got the point, What you want to do is trying to change the positions of all the targets at the same time. You can achieve this by changing all of them in one single method running in one thread.