I've been working on a 2d game, and I think I've been over-complicating how to make an animation only cycle through once. For example, if the user presses the 'a' key, the character's 'attack' variable is set to true, then the the game will continually render the animation sprite images to the screen in a loop until the key is released. The problem is, I want the attack animation to only happen once, whether the 'a' key is held down for .001 seconds or for 1 minute.
I am trying to solve this by raising a flag in the Animation's tick method when the last frame is hit. However when it comes to resetting this flag, I don't know how it would be done. This is a pretty broad question (with probably a lot of solutions), so let me know if I'm leaving out any details. Here is a sample of code with everything removed except for what pertains to 1 player action (attack, for this example):
Game class (w/ main()):
public class Game extends Canvas implements Runnable{
private static final long serialVersionUID = 1L;
public static final int WIDTH = 1200, HEIGHT = 600, SCALE = 1;
public static boolean running = false;
public Thread gameThread;
private BufferedImage p1AttackSpriteSheet;
private ImageManager im;
private static Player1 player1;
public void init(){
ImageLoader loader = new ImageLoader();
//Prepare sprite sheets (the loading takes some time...)
p1AttackSpriteSheet = loader.load("/player1/attackSpriteSheet.png");
SpriteSheet SS1 = new SpriteSheet(p1AttackSpriteSheet);
im = new ImageManager(SS1);
player1 = new Player1(-50, 200, im);
}
public synchronized void start() {
if(running)return;
running = true;
gameThread = new Thread(this);
gameThread.start();
}
public synchronized void stop() {
if(!running)return;
running = false;
try {
gameThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void run() {
// Credit for this run loop goes to MadProgrammer @ StackOverflow
init();
final long amountOfTicks = 60;
long ns = Math.round(1_000_000_000 / (double)amountOfTicks);
int frames = 0;
long frameStart = System.currentTimeMillis();
while (running) {
long startedAt = System.nanoTime();
tick();
render();
long completedAt = System.nanoTime();
long duration = completedAt - startedAt;
long frameEnd = System.currentTimeMillis();
if (frameEnd - frameStart >= 1000) {
System.out.println(frames); //Take this out for final version
frames = 0;
frameStart = System.currentTimeMillis();
} else {
frames++;
}
long rest = ns - duration;
if (rest > 0) {
rest = TimeUnit.MILLISECONDS.convert(rest, TimeUnit.NANOSECONDS);
try {
Thread.sleep(rest);
} catch (InterruptedException ex) {
}
}
}
stop();
}
public void tick() {
player1.tick();
}
public void render() {
BufferStrategy bs = this.getBufferStrategy();
if(bs == null)
{
createBufferStrategy(3); //Use 5 at most
return;
}
Graphics g = bs.getDrawGraphics();
//RENDER HERE
player1.render(g);
//END RENDER
g.dispose();
bs.show();
}
public static void main(String[] args)
{
JLabel controlKeyPanel;
Game game = new Game();
game.setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
JFrame frame = new JFrame("-");
frame.setResizable(false);
frame.setSize(WIDTH * SCALE, HEIGHT * SCALE);
frame.setLayout(new BorderLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(game);
frame.setVisible(true);
game.start();
}
public static Player1 getPlayer1() {
return player1;
}
public static Player2 getPlayer2() {
return player2;
}
KeyManager Class:
public class KeyManager implements KeyListener{
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if(keyCode == KeyEvent.VK_A) //P1 Attack
{
Game.getPlayer1().attack1 = true;
}
}
public void keyReleased(KeyEvent e) {
int keyCode = e.getKeyCode();
if(keyCode == KeyEvent.VK_C) //P1 Attack
{
Game.getPlayer1().attack1 = false;
}
}
}
Player1 class:
public class Player1 {
private ImageManager im;
public boolean attack1 = false;
private int x1, y1;
private Animation P1AttackLeftAnimation;
public Player1(int x, int y, ImageManager im) {
this.x1 = x;
this.y1 = y;
this.im = im;
P1AttackLeftAnimation = new Animation(im.player1AttackLeft, 20);
}
public void tick() {
P1AttackLeftAnimation.tick();
}
public void render(Graphics g) {
if(attack1)
{
P1AttackLeftAnimation.render(g, x1, y1, 400 * Game.SCALE, 400 * Game.SCALE);
}
}
}
Animation class:
public class Animation {
private BufferedImage[] images;
private int interval, index;
private long timer, now, lastTime;
public boolean cycleComplete = false;
public Animation(BufferedImage[] images, int interval) {
this.images = images;
this.interval = interval;
index = 0;
timer = 0;
now = 0;
lastTime = System.currentTimeMillis();
}
public void tick() {
now = System.currentTimeMillis();
timer += now - lastTime;
lastTime = now;
if(timer >= interval)
{
index++;
timer = 0;
if(index >= images.length)
{
cycleComplete = true;
index = 0;
}
}
}
public void render(Graphics g, int x, int y, int width, int height) {
g.drawImage(images[index], x, y, width, height, null);
}
}
ImageManager class:
public class ImageManager {
public BufferedImage [] player1AttackLeft;
public ImageManager(SpriteSheet ss1) {
player1AttackLeft = new BufferedImage[12];
//trust that the individual images are in the array
}
}