Minor tweaks
The gameplay is mostly done. One major flaw that remains is the lack of „restart”/”character selection” buttons after the game is over.
Add
eventListener.OnGameEnd(false);
function call in our GameLogic class, exactly once player’s hp reaches zero, right before GameProgress.Reset(true); call. Then modify our GameEventListener and make playerWon Boolean final.
public interface GameEventListener { void OnGameEnd(final boolean playerWon); }
Modify GameScreen’s OnGameEnd listener function to actually make use of playerWon variable. On our last action (where we get rid of the current screen. Here’s how it looks now:
new Action() { @Override public boolean act(float delta) { dispose(); if (playerWon) { game.setScreen(new GameScreen(game)); } else { game.setScreen(new CharacterSelectionScreen(game)); } return true; } }
After player gets defeated, he gets brought to CharacterSelectionScreen. That’s it. We no longer need to relaunch the game file after loss.
Few bugs:
- Let’s Tweak our “Start” button and call GameProgress.Reset(false) before the game starts: this will ensure that player gets his max hp (in case we made an upgrade and it raised the hp).
- After the player wins the stage – make sure to record his hp to currentLives. In our Player.markVictorious function, save the current player’s hp to GameProgress.playerLives:
public void markVictorious() { winTime = timeAlive; winning = true; GameProgress.playerLives = lives; }
Sound and Music introduction
Our final (and very important!) part is going to be about music. I’ve got two music/sound packs from opengameart.com:
- Attack sounds: http://opengameart.org/content/rpg-sound-pack by artisticdude
- Footsteps: http://opengameart.org/content/fantozzis-footsteps-grasssand-stone by Fantozzi (submitted by qubodup)
- Battle Music: http://opengameart.org/content/8-bit-music-pack-loopable#comment-55568 by CodeManu
Converted them to .ogg (smaller size) with http://audio.online-convert.com/convert-to-ogg tool.
I’ve converted the following files from attack sounds:
- swing.wav -> swing0.ogg
- swing2.wav -> swing1.ogg
- swing3.wav -> swing2.ogg
- coin.wav -> coin.ogg
- bite-small.wav -> heal.ogg
- fantozzi-sandl1 -> walk0.ogg
- fantozzi-sandl2 -> walk1.ogg
- Fantozzi-SandR1 -> walk2.ogg
Music is left-as is, in mp3, but renamed to music0..music5.ogg. In the directory android/assets/, create music folder and put sounds/music there. Finally, let’s get to coding! In our com.coldwild.dodginghero package, create a new class and call it SoundManager. It’s going to have static functions that controls menu music. Let’s introduce functions that load and unload sounds.
public class SoundManager { public static AssetManager assets = new AssetManager(); public static void LoadSounds() { for (int i = 0; i < 3; i++) { assets.load(Gdx.files.internal("music/swing" + i + ".ogg").path(), Sound.class); assets.load(Gdx.files.internal("music/walk" + i + ".ogg").path(), Sound.class); } assets.load("music/coin.ogg", Sound.class); assets.load("music/heal.ogg", Sound.class); assets.finishLoading(); } public static void ReleaseSounds() { assets.dispose(); } }
In our DodgingHero class, in our create function, after GameProgress.Load() call, add the call to LoadSounds function:
SoundManager.LoadSounds();
And
SoundManager.ReleaseSounds();
At the end of dispose() function. This will ensure the sounds are loaded at the start of the game and disposed at the end. The easiest ones to implement would be walk / attack sounds. Add those things to our SoundManager:
private static void playSoundRandomVolume(Sound sound, float min, float max) { if (sound != null) { sound.play(MathUtils.random(min, max)); } } public static void PlaySwingSound() { Sound s = assets.get("music/swing" + MathUtils.random(2) + ".ogg", Sound.class); playSoundRandomVolume(s, 0.90f, 1.0f); } public static void PlayWalkSound() { Sound s = assets.get("music/walk" + MathUtils.random(2) + ".ogg", Sound.class); playSoundRandomVolume(s, 0.45f, 0.55f); }
Pretty straightforward: pick the necessary (random!) sound, then play it at a reasonably random volume to make it even more randomized. Put the calls to the following functions:
Character.takeDamage()
public void takeDamage(int amnt) { SoundManager.PlaySwingSound(); timeOfDmgTaken = timeAlive; lives -= amnt; if (lives < 0) { lives = 0; } }
And in our GameScreen.AttemptMove
public void AttemptMove(int dx, int dy) { if (player.getLives() > 0 && enemy.getLives() > 0 && logic.CheckMove(player.getFieldX() + dx, player.getFieldY() + dy)) { logic.AssignPlayerPosition(player.getFieldX() + dx, player.getFieldY() + dy); SoundManager.PlayWalkSound(); } }
Try running the game! Whenever you move or your player gets hit – you’ll hear it. As you remember, we try to split game logic from representation. We won’t be calling soundmanager from our gamelogic, we’ll pass the bonus pickup / attack even to our gamescreen and it will handle it properly. First, add the functions for coin / health pickup:
public static void PlayCoinSound() { Sound coin = assets.get("music/coin.ogg", Sound.class); playSoundRandomVolume(coin, 0.9f, 1.0f); } public static void PlayHealSound() { Sound heal = assets.get("music/heal.ogg", Sound.class); playSoundRandomVolume(heal, 0.9f, 1.0f); }
In our GameEventListener interface, add a new function declaration:
void OnBonusPickup(byte bonusType);
We’ll call it when player picks a bonus, in our AssignPlayerPosition, right after the check:
if (currentBonus.getFieldX() == fx && currentBonus.getFieldY() == fy) { eventListener.OnBonusPickup(currentBonus.getBonusType());
The only thing left for GameScreen is to implement OnBonusPickup method.
@Override public void OnBonusPickup(byte bonusType) { if (bonusType == Bonus.BONUS_TYPE_COIN) { SoundManager.PlayCoinSound(); } else if (bonusType == Bonus.BONUS_TYPE_HEALTH) { SoundManager.PlayHealSound(); } }
Run the game, hear more sounds! Awesome. That concludes this lesson. In our next lesson, we’ll work on adding music.
Relevant git commit: https://github.com/vladimirslav/dodginghero/commit/e93739a981ad6da90c2fe69bfce876ae1be045c9