2D Walking Animation (edited - different approach)

Old post:

[quote]I have a problem. I can’t get this to animate right :confused:
Spritesheet:

http://www.javadaemon.com/charsheet.png

My game is a rough pokemon clone. The world is tiled.
The player movement is smooth and great, but the animation is not working and I can’t think of a way to implement it correctly.
Basicly, this functionality is my goal:
When just pressing once (moving one tile), the animation should be mid-layer on the spritesheet, downer layer, and when hitting the right position still (upper layer).
When holding down it should be mid-layer, upper layer, downer-layer, upper layer, and repeating (in order to get a nice walking animation).

I’ve tried my best here (a peice of my player-class):


public void update(long delta) {
        if (appearenceUpdate) {
            image = ImagesLoader.character[moveDir - 1][imageY];
            appearenceUpdate = false;
        }
        if (animUpdate) {
           switch (lastY) {
               case 1:
                   lastY = 2;
                   break;
               case 2:
                   lastY = 1;
                   break;
           }
           switch (imageY) {
               case 0:
                   imageY = lastY;
                   break;
               case 1:
                   imageY = 0;
                   break;
               case 2:
                   imageY = 0;
                   break;
           }
            image = ImagesLoader.character[moveDir - 1][imageY];
            animUpdate = false;
        }
        

        // Position logic
        if (isMoving) {
            if (map.targX != map.mapStartX
                    || map.targY != map.mapStartY) {
                long now = System.nanoTime();
                timeSinceLocUpdate += (long) (now - delta); //* 1000000;
                if (timeSinceLocUpdate > locFPS) { // Move 1 px
                    if (animPointX == map.mapStartX && animPointY == map.mapStartY) {
                        animUpdate = true;
                    }
                    move(moveDir);
                    timeSinceLocUpdate = 0;
                }           
            } else if (isKeyDown) {
                coordX = targX;
                coordY = targY;
                calcNewCoords(moveDir);
                animUpdate = true;
            } else {
                coordX = targX;
                coordY = targY;
                //appearenceUpdate = true;
                //imageY = 0;
                isMoving = false;
                gpTop.blockInput = false;
            }
        } else {
            //imageY = 0;
            gpTop.blockInput = false;
        }
    }

    public void calcNewCoords(int dir) {
        switch (dir) {
            case 1:
                targY = coordY - 1;
                targX = coordX;
                if (map.checkObstacle(targX, targY)) {
                    map.targX = map.mapStartX;
                    map.targY = map.mapStartY + map.tileHeight;
                    animPointX = map.targX;
                    animPointY = map.targY - (map.tileHeight / 2);
                } else {
                    targX = coordX;
                    targY = coordY;
                }
                break;
            case 2:
                targX = coordX + 1;
                targY = coordY;
                if (map.checkObstacle(targX, targY)) {
                    map.targX = map.mapStartX - map.tileWidth;
                    map.targY = map.mapStartY;
                    animPointY = map.targY;
                    animPointX = map.targX + (map.tileWidth / 2);
                } else {
                    targX = coordX;
                    targY = coordY;
                }
                break;
            case 3:
                targY = coordY + 1;
                targX = coordX;
                if (map.checkObstacle(targX, targY)) {
                    map.targY = map.mapStartY - map.tileHeight;
                    map.targX = map.mapStartX;
                    animPointX = map.targX;
                    animPointY = map.targY + (map.tileHeight / 2);
                } else {
                    targX = coordX;
                    targY = coordY;
                }
                break;
            case 4:
                targX = coordX - 1;
                targY = coordY;
                if (map.checkObstacle(targX, targY)) {
                    map.targX = map.mapStartX + map.tileWidth;
                    map.targY = map.mapStartY;
                    animPointY = map.targY;
                    animPointX = map.targX - (map.tileWidth / 2);
                } else {
                    targX = coordX;
                    targY = coordY;
                }
                break;
        }
    }

In player, targX and -Y is the coords to where my player is going.
map.targX and -Y is the pixel coords to where the map should stop.
animPointX and -Y is the pixel location of where the player-image should change state (upper-layer to downer-layer or vice-versa).
The appearenceUpdate boolean is a flag that is set when a movement-key is pressed. The animUpdate boolean, is a flag that is set when the image should change due to animation.

My ImagesLoader loads the spritesheet and slices it, making it available through coordinates via. a bufferedimage[][] (x, and y).

EDIT:
I’m beginning to think that the calcNewCoords methods does not calculate the animPoints correctly, even though it seems to me that they are :frowning:

EDIT 2:
Am I trying to do this the wrong way? :-\

Here is a video of the current functionality (just uploaded so it might be processing…):

…and here is a video of the functionality I want (just in case…):

Oh, and the keylistener:


private void processKey(KeyEvent e) {
        int keyCode = e.getKeyCode();
        player.appearenceUpdate = true;
        if (!blockInput) {
            if (keyCode == KeyEvent.VK_UP) {
                player.moveDir = 1;
                player.isKeyDown = true;
                blockInput = true;
                player.calcNewCoords(player.moveDir);
                player.isMoving = true;
            }
            if (keyCode == KeyEvent.VK_RIGHT) {
                player.moveDir = 2;
                player.isKeyDown = true;
                blockInput = true;
                player.calcNewCoords(player.moveDir);
                player.isMoving = true;
            }
            if (keyCode == KeyEvent.VK_DOWN) {
                player.moveDir = 3;
                player.isKeyDown = true;
                blockInput = true;
                player.calcNewCoords(player.moveDir);
                player.isMoving = true;
            }
            if (keyCode == KeyEvent.VK_LEFT) {
                player.moveDir = 4;
                player.isKeyDown = true;
                blockInput = true;
                player.calcNewCoords(player.moveDir);
                player.isMoving = true;
            }
            if (keyCode == KeyEvent.VK_X) {
                player.locFPS = 1000000 / 100;
            }
        }
    }  // end of processKey()

Any ideas? :-X
[/quote]
Since that didn’t work out too well, I tried to just play an animation of three images (y coordinate: 2,0,1), while walking.
This is what I got:


private long timeSinceLocUpdate;
    private long timeSinceAnimUpdate;
    public long locFPS = 1000000 / 9;
    public long animFPS = (locFPS * 16) / 3;

    public int animation[] = {1,0,2};
    private int counter = 0;

    public void update(long delta) {  
        long now = System.nanoTime();
        if (isMoving) {
            timeSinceAnimUpdate += (long) (now - delta);
            if (timeSinceAnimUpdate > animFPS) { // A third of the time moving has past
                counter++;
                if (2 < counter) {
                    counter = 2;
                }
                timeSinceAnimUpdate = 0;
            }
        } else {
            counter = 0;
        }

        // Position logic
        if (isMoving) {
            if (map.targX != map.mapStartX
                    || map.targY != map.mapStartY) {
                timeSinceLocUpdate += (long) (now - delta); //* 1000000;
                if (timeSinceLocUpdate > locFPS) { // Move 1 px
                    move(moveDir);
                    timeSinceLocUpdate = 0;
                }           
            } else if (isKeyDown) {
                coordX = targX;
                coordY = targY;
                calcNewCoords(moveDir);
            } else {
                coordX = targX;
                coordY = targY;
                appearenceUpdate = true;
                isMoving = false;
                gpTop.blockInput = false;
            }
        } else {
        }
    }

    public void draw(Graphics g) {
        x = (map.PWIDTH / 2) - (width / 2);
        y = (map.PHEIGHT / 2) - (height / 2);
        g.setColor(Color.ORANGE);
        if (isMoving) {
            g.drawImage(ImagesLoader.character[moveDir - 1][animation[counter]], x, y, null);
        } else {
            g.drawImage(ImagesLoader.character[moveDir - 1][0], x, y, null);
        }
    }

calcNewCoords now only handles location targets, and the keylistener now just sets the isMoving boolean, and the moveDir (the direction the sprite is facing).

However, this doesn’t work right: The animation isn’t “smooth”. The last frame (animation[2]) is usually a lot longer than the other two.
I think this implementation of animating the walking is easier to manage, and the problem is smaller.
I think what’s happening, is that the animation-counter quickly reaches 2, and stays there for the two last frames. To me, it looks like it should not.

I give up :frowning: How should this be implemented? I’m using Slick, if that could help my problem in any way? :-\

EDIT: I cheaphacked it… ???

Yeah, it seems like you’re thinking about it the wrong way. Try to go more object oriented. In reality, you’ve got three separate things:

  • The sprite sheet and the logic of which frame is where
  • The animation, which knows which frames get swapped after the others and how long the delay is between frames
  • The game logic, which decides when to show which animation

Usually for me this translates to my Entity class having an AnimationSet. An AnimationSet is a HashMap of Animation’s with a String as a key, so I can say entity.setAnimation(“WalkUp”) and it will start playing that one. The AnimationSet has logic for priorities and whatnot so that certain Animation’s automatically play by default (like an Idle animation) and others can’t be interrupted (like a Death animation). Each Animation has an array of String’s that reference Sprites stored in memory, so I can just draw a different image depending on what the current frame is. You seem to understand how a Sprite sheet / atlas works, so I won’t bother explaining that part.

Does all that make sense?

[quote=“Eli Delventhal,post:3,topic:36207”]
About here I jumped off… I’m using slick now, so basicly I can have my enities have a hashmap of animations (as in the objects in Slick), and play them according to the state of the entity (walking different directions and dying)?

I’m not sure if Slick lets me move it around once started… Else I’ll have to make it myself… I think I know how to go about this, except for one thing: How do I know when to update the picture in the animation?
Should I just let it play to the end? If so, what about holding down the key for continous walking?
Replay, to the end?

Hi Mads,

It seems like we are working on a similar sort of thing.

I had a look at Slick, but decided to only use Java2D instead of any extra libraries. My animations are controlled with 2 integers for the direction which the character is facing(dirx, diry, where -1, 0 and 1 are values). This will help determine which image set to render.

My images are seperated instead of being on an image set, but there will just be a separate bit of programming for you to do. Whenever the actionListioner() is called by your timer, another method will alter another variable which changes the image currently being displayed. Here is a code representation, please keep in mind this only represents a single direction:


Image[] imgArr = new Image[3];//An array of 3 images, the images for your animation.
int currentImage=0;//An integer representing the current image.

public void changeImg()//A method to change the image to be displayed
{
currentImage++;//Increase the current image variable

if(currentImage==3)//If the current image is '3', which cant be used to get the image later, set it to 0.
currentImage=0;
}

public Image getImage()
{
return imgArr[currentImage];
}

This way, you can change the image in the paint() method of your program. Hope this helps a bit! :slight_smile:

Oh, and one other thing, it may be useful to use the ‘Sprite’ class found in the tools directory of your Slick download. It represents a single sprite in your program, but you could make an AnimatedSprite class which extends Sprite and impliments the above code or something somehow… Just an idea

Hi N15MO :smiley:

I actually used that technique, but for me it played too fast over the first image, and then coughed when reaching the end before planned. I also had an event where the sprite reached the position, where I would set the image back to the “standing” image. A combination of that worked really bad.
However, I think just setting the “standing” image, when the animation is done will fix that. The problems are just going to fall on me when I reach a point, where I’d like to implement NPC’s that are able to walk. I also have the pseudocode, but I doubt it’ll actually work :-X
Oh, and do you mind explaining why you use two integers, for the direction?
I like to just have one integer/short/byte going from 1 through 4. North, east, south, and west.

Just out of interrest, can I see what you’ve done so far? :stuck_out_tongue:

I’m not sure how Slick works, but you update the animation by calling tick() on it every timestep. That will then increment a counter, and when it reaches the delay for that frame it moves on to the next one.

Hi Mads :slight_smile:

I see youre predicament… Hmmmm i really havent done much to be able to help u with anything, but when i do i will post it here and show u. Because i am still pretty new to this forum, i cant past any links to websites, but please go ahead and type “pacman java 2d game tutorial” in Google and look at the one from Zetcode. It is what i am basing my animation around, also maybe look at their tutorial on animation itself and moving sprites, i think there will be valuable information in there to help u :slight_smile:

Concerning the two integer variables, becasue its 2d we’re working in, u may want a character to to so something really strange like strafe or side step, and u cannot account for that with one variable… but if you only intend to have the character going in the usual up/down/left/right sort of fashion, it can suffice too :slight_smile: when i have some spare time this week, ill take a look at your code and maybe help out a bit if i can :slight_smile:

I plan to have my project finished in the next 2/3 weeks before university starts