Previous Tutorial
Next Tutorial
Posted on: 30-10-2012

Gravity Platformer Tutorial #4 : Animating our character

This tutorial will be a rather short one in which we animate our character.

Lets start off by looking at what our character has to be able to do, first he has to be able to walk. Secondly we want him to face the direction he is walking towards. Lets start off with the last thing, the facing the right direction.

So lets add a Facing to our Character class.

  1. We start off by creating a package for our enumerations (called "game.enums"). Enumerations are a great way of keeping things clear in our code for things like these.
  2. In that package we create a new Enum:

    1. package game.enums;
    2.  
    3. public enum Facing {
    4. LEFT, RIGHT
    5. }
  3. Now that we have defined the directions our characters can face, we can add this to our Character and Player classes.

  4. In the Character class we want to add two things, first we want our characters to have a Facing attribute:

    1. protected Facing facing;

    And secondly we want our characters to look to the right by default, so we add this to the constructor of Character:

    1. facing = Facing.RIGHT;
  5. Next up is updating the side we are facing, so lets go to our player class and change both the moveLeft and the moveRight functions to include facing = Facing.LEFT; and facing = Facing.RIGHT;

  6. The only thing remaining is editing the render function of Character. We could just add an if statement there, but what happens when we also get the gravity effects. We would get 4 different gravity situations with each 2 directions or character can face. Well that would be an ugly if statement wouldn't it?

    So what I decided to do, is to add an extra method to Character called setSprite.

    1. protected void setSprite(Image i){
    2. sprites = new HashMap<Facing,Image>();
    3. sprites.put(Facing.RIGHT, i);
    4. sprites.put(Facing.LEFT , i.getFlippedCopy(true, false));
    5. }

    We also have to add an additional attribute to character called "sprites" which is a HashMap. What a HashMap does is takes a key and a value, which makes it perfect for our needs.

    You can also see that we use a method from Image called getFlippedCopy. This method simply gives a horizontal or vertical flipped copy. In our case, we want a horizontally flipped copy for the left facing.

  7. Our next step is to remove the sprite attribute, we won't be using it anymore since we will get the right sprite from our hashmap of sprites. This will mean we have to alter our constructor to use the setSprite method. Don't forget to alter the constructor of Player as well.

    We also have to update our render method to use the hashmap:

    1. sprites.get(facing).draw(x-2, y-2);

If we would run our game now, we can see our character turning when we move him. But we don't have any animations yet!

  1. Lets remedy that problem by adding 2 additional attributes to Character:

    1. protected HashMap<Facing,Animation> movingAnimations;
    2. protected long lastTimeMoved;

    The reason I also added lastTimeMoved here is because we have to know if we are actually moving, our character does not need to making a moving animation if we are standing still.

  2. You might have already guessed it, but we have to edit the moveLeft and moveRight methods again to set the lastTimeMoved.

    1. public void moveLeft(int delta){
    2. x = x - (0.15f*delta);
    3. facing = Facing.LEFT;
    4. lastTimeMoved = System.currentTimeMillis();
    5. }
  3. Next up will be a method similar to the setSprite one, but this time for the moving animation.

    1. protected void setMovingAnimation(Image[] images, int frameDuration){
    2. movingAnimations = new HashMap<Facing,Animation>();
    3.  
    4. //we can just put the right facing in with the default images
    5. movingAnimations.put(Facing.RIGHT, new Animation(images,frameDuration));
    6.  
    7. Animation facingLeftAnimation = new Animation();
    8. for(Image i : images){
    9. facingLeftAnimation.addFrame(i.getFlippedCopy(true, false), frameDuration);
    10. }
    11. movingAnimations.put(Facing.LEFT, facingLeftAnimation);
    12.  
    13. }

    The way that Slick2D animations work is that they automatically update when we call any of the draw() methods. This means that we can give it a set of images, tell it how long we want it to display each image and it will do the rest of the work.

    So what we do here is basicly the same as in the setSprite class, we create an Animation object for the right facing case and then we create an Animation object for when our character will face left and we fill it with horizontally flipped images.

    A small illustration on how the Animation will behave if we tell it to have each frame rendered for 125 milliseconds:

  4. Next up is modifying the render method of our Character class to use animations when we can:

    1. public void render(){
    2.  
    3. //draw a moving animation if we have one and we moved within the last 150 miliseconds
    4. if(movingAnimations != null && lastTimeMoved+150 > System.currentTimeMillis()){
    5. movingAnimations.get(facing).draw(x-2,y-2);
    6. }else{
    7. sprites.get(facing).draw(x-2, y-2);
    8. }
    9. }

    As you can see, this keeps things nice and tidy. We could even add an idle animation if we want, but i'll leave that for later. You could try to add it yourself :)

  5. Our last step is to set the animation when we create our player:

    1. public Player(float x, float y) throws SlickException{
    2. super(x,y);
    3. setSprite(new Image("data/img/characters/player/player.png"));
    4.  
    5.  
    6. setMovingAnimation(new Image[]{new Image("data/img/characters/player/player_1.png"),new Image("data/img/characters/player/player_2.png"),
    7. new Image("data/img/characters/player/player_3.png"),new Image("data/img/characters/player/player_4.png")}
    8. ,125);
    9.  
    10. }

    And thats it, when we run the game now we can see our awesome animation when we walk.

    You can download the images for the player here:

Closing Notes

Something to keep in mind is that characters have multiple copies of the same image stored in the memory, although they are flipped around. I could also have chosen to rotate them when we needed to, but since the Animation class does not provide an easy way of doing this I chose to do the rotation and flipping on creation of a character. This might use a little more memory, but this is acceptable because we are not talking about big size textures, we can literally have a million player_1.png images and it will still only be about 50MB. So nothing to worry about :)

I hope that you have learned something in this tutorial, if you have any questions, feel free to ask them in the comment sections below. There are no dumb questions!

Next time we will be looking at simple collision detection.

Categories: Game Development, Java, Tutorial

Comments

Nekotripp said: (30-10-2012)
Please continue this series. You're doing a great service by offering a step-by-step walkthrough of Slick like this. Thank you 10 million times!
Frums said: (30-10-2012)
I will, thanks for reading!
Wotsit said: (31-10-2012)
How often do you release editions? Their really great so far.
Frums said: (31-10-2012)
I'm trying to get at least a few out each week. But it really depends on how long they take to create, the next one about collision detection is even going to be split up in 2 parts that I will release shortly after each other.
Ausitn said: (02-11-2012)
When will the next part be released? This is my favorite tutorial of them all, thanks.
Frums said: (02-11-2012)
I've just put up the first part, the next part will be following within a day :)
z3r0312 said: (08-11-2012)
Could you post the character.java, I'm having trouble figuring out exactly what you did. The sprites commands are causing errors.
Frums said: (09-11-2012)
Are you sure you followed all steps? You might have forgotten to add the attribute:
protected HashMap<Facing,Image> sprites;
Beyond Louie's said: (11-11-2012)
Ohhh, yeeeaaaahhh!!! Did it again. Nice one. I think z3r0312 may have stumbled on Step #7. That step was a little tricky for me too. I would suggest he makes sure to make these changes to Character and Player:
// sprite = new Image("data/img/placeholder_sprite.png");
setSprite(new Image("data/img/characters/player/player.png"));
Beyond Louie's said: (11-11-2012)
Formatting didn't work. I was trying to say: comment out the original sprite and replace it with the setSprite method.
mikey said: (01-12-2012)
Getting all caught up in making changes to both Character and Player, I ended up adding

protected Facing facing;

to both.

Even with everything else set up all correctly, that'll mess it up.

Just something else to watch out for!



What is the name of the website? (to counter the spam)