1
\$\begingroup\$

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
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ That is a lot of code, you might consider narrow it only to relevant parts... (not many people are willing to read hundreds of lines of code) Also if the animation stops immediately it seems you are releasing the flag too soon. \$\endgroup\$
    – wondra
    Sep 21, 2014 at 21:44
  • \$\begingroup\$ =[ This was the narrowed/condensed version. I'll try to take out more. \$\endgroup\$ Sep 21, 2014 at 21:59

2 Answers 2

2
\$\begingroup\$

Your flag should be an indication of whether to start the animation, not whether to continue playing it. e.g.

if (IsKeyDown() && !pressed && !animation.IsPlaying())
{
  // record that we triggered the animation
  pressed = true;

  // start playing the animation
  animation.Start();
}
else if (!IsKeyDown())
{
  // we reset our pressed state since the user let go of the key
  pressed = false;
}

// play animation
if (animation.IsPlaying())
  animation.Update();

You can make this more generic by having functions like IsKeyPressed in addition to IsKeyDown, with the difference being that IsKeyPressed only returns true on the frame that the user pressed the key and returns false on all subsequent frames until the user releases the key and presses it again.

This can be generalized to:

// all keys starts out not being pressed
lastKeyState = false;
currentKeyState = false;

// at the beginning of your game loop, remember the state from the last frame, then query the new state
lastKeyState = currentKeyState;
currentKeyState = IsKeydown();

// now compare the two states to determine if the key was pressed this frame or released this frame
pressed = currentKeyState && !lastKeyState;
released = !currentKeyState && lastKeyState;
\$\endgroup\$
2
  • \$\begingroup\$ I don't doubt your solution one bit. The problem, is this would change too much about the structure of this game. Maybe another good way to look at this problem would be: How can I set the "attack" flag to false if holding down the corresponding control key is constantly setting it to true? \$\endgroup\$ Sep 23, 2014 at 23:37
  • \$\begingroup\$ @user3750325: there's no always a magic answer, sorry. You're either going to need to add one new flag (there's no way that's "changing the structure too much") or just refactor the input code (which can be a big change, yes, but up-front extra work that reduces total work in the long term is a very good thing). \$\endgroup\$ Sep 24, 2014 at 0:21
0
\$\begingroup\$

Well this is the way I did it (May not be the best solution): Requires two booleans, a and b. If the key is down, and a is false, then start the animation, and set a to true. Then, if the key isn't down, set a to false. That way the animation will cycle through once per click of the key. Depending on what engine you're using, you may have to do something to stop the animation from automatically repeating itself(usually something like animation.setLooping(false), or an if statement checking to see if it's on it's last frame, and stopping it)

Hope this was helpful, if not, I apologize.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks, this is more of the logic I was looking for. In my Animation's render I know it checks for the last frame. However, stopping it is the problem. The animation is continuously rendered as long as the key is held down. I might need a second to mess around here... (where does 'b' come into play, by the way?) \$\endgroup\$ Sep 24, 2014 at 1:49
  • \$\begingroup\$ @user3750325 Whoops, b doesn't come into play, sorry. I have no idea how to stop your animation, since every engine is different. Most will have a method you can call to stop it though. So an if statement checking if it's on its last frame, then stop it. \$\endgroup\$
    – drdavehere
    Sep 24, 2014 at 23:58

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .