Skip to main content Link Search Menu Expand Document (external link)

Theme: auto

Animations, Sounds, and Finishing Touches

The game is now playable, but it could use some polish. Specifically, you’re going to add sprite animations, sound effects/music, and a win condition.

Animations

The Lab04 directory includes a declaration of AnimatedSprite in AnimatedSprite.h/cpp. The AnimatedSprite class inherits from SpriteComponent. Much of the code is already implemented, but you’ll have to implement the Update function.

First, take a moment to understand the member data in AnimatedSprite.h:

  • mAnims map associates an animation name like “run” with a vector of corresponding textures. This way, you can support switching to different animations.
  • mAnimName is the name of the current animation, or empty if the animation hasn’t been set yet
  • mIsPaused is false if the animation is running, or true if it should be paused
  • mAnimTimer is used to track the amount of time elapsed in the current animation
  • mAnimFPS is the animation “frames per second”. It defaults to 10, which is what we want for this lab

Now you must implement AnimatedSprite::Update. The basic idea is:

  1. If mAnimName.empty(), don’t do anything (since we haven’t set an animation)
    1. If the animation is not paused (i.e. !mIsPaused), update mAnimTimer based on the animation FPS and delta time
    2. Do the wrapping of mAnimTimer to make sure it isn’t >= the number of animation frames (consult the slides if you don’t know what this means).
    3. Call SetTexture (which is inherited from SpriteComponent), passing in the correct texture based on the mAnimName and mAnimTimer

Steps (b) and (c) should ALWAYS happen, even if the animation is paused. This is because we want the animation texture to still get set properly even when the animation is paused.

AnimatedSprite Unit Tests

As with Lab 3, we have provided a handful of unit tests that validate your AnimatedSprite functionality. These will automatically run every time you push AnimatedSprite.cpp. Keep in mind you need to pass the unit tests to satisfy the spec for animation.

You can also run the tests locally if you clone the https://github.com/itp380-20243/tests-Lab04 repo and copy your AnimatedSprite.cpp file into that repo.

Goomba Animations

You’ll first test AnimatedSprite with Goomba. In Goomba.cpp, instead of creating a SpriteComponent, create an AnimatedSprite.

Then, add code like this to create an animation called "walk" :

std::vector<SDL_Texture*> walkAnim{
	GetGame()->GetTexture("Assets/Goomba/Walk0.png"),
	GetGame()->GetTexture("Assets/Goomba/Walk1.png")
};

// This assumes "asc" is the animated sprite component
asc->AddAnimation("walk", walkAnim);

Generally, you want to create all the animations you need in the constructor of the actor, so they are ready to be used when it’s time to play the animation.

Do not add the animation every time you want to play it. This is super inefficient. You only need to add the animation once for each AnimatedSprite. Once it’s added, you can change to that animation whenever you want to.

Add an additional animation to Goomba called "dead" that only contains the one "Assets/Goomba/Dead.png" texture from before.

To play an animation, you need to call the SetAnimation function passing in the name of the animation to play. This assumes that you previously added an animation with that name.

For example, this would set the current animation to "walk":

// This assumes "asc" is the animated sprite component
asc->SetAnimation("walk");

Now, set it up so that by default the Goomba uses the "walk" animation and when it gets stomped, change the animation to "dead".

When you run your game, you should notice that the goombas now play a walking animation that alternates between two frames. When a goomba gets stomped, it should still correctly show the dead texture.

Mario Animations

Now in the Player class, switch out the SpriteComponent for an AnimatedSprite.

Mario has several different animations. All of Mario’s textures are in "Assets/Mario/". For brevity, I will omit the directory from the list of animations and textures, but you still need to specify that when loading them. Here are the animations:

  • "idle"Idle.png
  • "dead"Dead.png
  • "jumpLeft"JumpLeft.png
  • "jumpRight"JumpRight.png
  • "runLeft"RunLeft0.png, RunLeft1.png, RunLeft2.png
  • "runRight"RunRight0.png, RunRight1.png, RunRight2.png

Set the player’s initial animation to "idle".

Now in PlayerMove, you’ll need to add code that switches between the various animations as needed. We recommend putting this in new function, and calling the function at the end of PlayerMove::Update.

Most of the animation decisions are straightforward but jumping is a little weird. This is because if you start jumping to the left but then let go of the left key, you still want Mario to face to the left and not be idle.

Here is the logic:

  • If Mario is on the ground and…
    • Moving to the right – "runRight"
    • Moving to the left – "runLeft"
    • Not moving left or right – "idle"
  • If Mario is in the air and…
    • Moving to the right – "jumpRight"
    • Moving to the left – "jumpLeft"
    • If Mario’s not moving right or left, then you decide what to play based on the current animation:
      • If the current anim is "runRight" or "jumpRight" or "idle""jumpRight"
      • Otherwise – "jumpLeft"

● If Mario is dead – "dead"

Keep in mind you can get the name of the current animation from an AnimatedSprite via GetAnimName.

Verify that all the Mario animations play when expected, especially the running/jumping ones.

Sound Effects and Music

For sounds, we’ll just use the basic SDL mixer functionality covered in lecture.

First, add an #include "SDL2/SDL_mixer.h" to Game.h.

Next, in Game::Initialize before the call to LoadData, add:

Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);

Likewise, in Game::Shutdown call Mix_CloseAudio (it takes no parameters).

Loading Sounds

In Game.h/cpp add the following function:

Mix_Chunk* GetSound(const std::string& filename);

This will use the same map technique we used for textures. GetSound either finds the sound in the map, or if not in the map it’ll load the sound file and add it to the map. To load a sound file into a Mix_Chunk*, use Mix_LoadWAV: https://wiki.libsdl.org/SDL_mixer/Mix_LoadWAV.

Unloading Sounds

In Game::UnloadData delete all the cached sounds with Mix_FreeChunk: https://wiki.libsdl.org/SDL_mixer/Mix_FreeChunk.

Sound Effects

You can play one-off sound effects using Mix_PlayChannel: https://wiki.libsdl.org/SDL_mixer/Mix_PlayChannel.

  • Pass -1 as the first argument to tell SDL to choose the next available channel.
  • The second argument is the Mix_Chunk* to play.
  • Set the third argument to 0 to tell it not to loop the sound.

Mix_PlayChannel returns an integer of the channel on which the sound’s being played, which lets you control the sound after it has started. But for one-off sound effects, you typically won’t want to save the return value.

Now hook up the following sound effects:

  • When the player jumps (a full jump, not half jumps) – "Assets/Sounds/Jump.wav"
  • When the player stomps an enemy – “Assets/Sounds/Stomp.wav”
  • When the player collides with the bottom side of the block – "Assets/Sounds/Bump.wav"

Verify the different sound effects you’ve added work as expected.

Music

You can also play music with Mix_PlayChannel. However, because you want the music to loop, you should pass in -1 for the third parameter, which says to infinitely loop the sound.

Now in Game::LoadData, play the "Assets/Sounds/Music.ogg" music file, and set it to looping. Make sure you save the return value of Mix_PlayChannel in a member variable, because we will want to stop the music when the player wins or loses.

Verify the music plays and keeps looping.

Winning/Losing

Remove the code that caps the player’s position at 448.0f. Instead, just let the player fall off screen, and once off-screen, make the player "die" (which does the same thing as when a goomba kills him).

When the player dies, stop playing the music with Mix_HaltChannel, passing in the channel number of the music you saved in your member variable. Then play the "Assets/Sounds/Dead.wav" as a one-off sound effect (don’t loop the death sound).

The player “wins” the game once their x-position is greater than 6368, which is where the flagpole is at. When the player wins, stop playing the music and play the "Assets/Sounds/StageClear.wav" sound (don’t loop it). Also, set the player to ActorState::Paused so he can no longer move.

Make sure the death sound and win sounds work. Your game should now play like in the video!

Once you’ve pushed your code, you should review the grading specifications to confirm you’ve satisfied them.