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 HandleUpdate 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

The basic idea for HandleUpdate is:

  1. If mAnims.contains(mAnimName) is false, do nothing because that means either mAnimName is not set, or it’s an invalid animation.
  2. Otherwise…
    1. If the animation is not paused, update mAnimTimer based on the animation FPS and delta time
    2. Always do these, even if the animation is paused:
      1. Use Math::Fmod to make sure mAnimTimer is within a valid range (consult the slides if you’re not sure how).
      2. Call SetTexture (which is inherited from SpriteComponent), passing in the correct texture based on the mAnimName and mAnimTimer

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/tac380-20253/tests-Lab04 repo and copy these files to it:

  • Actor.h/cpp
  • AnimatedSprite.h/cpp
  • Component.h/cpp
  • Transform.h/cpp

Goomba Animations

You’ll first use 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");

Set it up so that by default the Goomba uses the "walk" animation. Then, 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 switch to the dead animation:

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. Put this in a separate function, and call this function at the end of PlayerMove::HandleUpdate.

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. It should look like this:

Sound Effects and Music

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

First, add an include for "SDL3_mixer/SDL_mixer.h" to Game.h.

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

Mix_OpenAudio(0, nullptr);

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

Loading Sounds

In Game.h/cpp add the following public 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/SDL2_mixer/Mix_LoadWAV.

Unloading Sounds

In Game::UnloadData delete all the sounds in the map with Mix_FreeChunk: https://wiki.libsdl.org/SDL2_mixer/Mix_FreeChunk, and then clear the map.

Sound Effects

You can play one-off sound effects using Mix_PlayChannel: https://wiki.libsdl.org/SDL2_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 Mario jumps (a full jump, not half jumps) – "Assets/Sounds/Jump.wav"
  • When Mario stomps an enemy – "Assets/Sounds/Stomp.wav"
  • When Mario hits his head on the bottom side of the block – "Assets/Sounds/Bump.wav". Make sure you can play this sound only once per frame, or it will seem spammy.

Verify the jump, stomp, and bump sounds play 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.

Losing the Game

If the player falls off the screen, make the player die (which does the same thing as when a goomba kills him).

When the player dies, stop playing the music. You can stop sounds with Mix_HaltChannel https://wiki.libsdl.org/SDL2_mixer/Mix_HaltChannel (this is why you saved the channel it was played on). Then play the "Assets/Sounds/Dead.wav" as a one-off sound effect (don’t loop the death sound).

When the player dies (regardless from a Goomba or from falling into a pit), it should sound like this:

Winning the Game

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 “is active” to false so it won’t update anymore.

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

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