Developing Multiplatform Game with LibGDX, part 24: improving progression and rewards

game progress balancing, replayability

Our stats affect the game, but what happens if you unlock low-level character after you’ve cleared lots of game stages with your high-level one? The difficulty simply won’t be up to par. We have to keep current stage numbers separately for each character.

In our GameProgress class, get rid of currentLevel variable (and also let’s make the naming less confusing). Also, get rid of SAVE_KEY_CURRENT_LEVEL constant. The game levels will be called „game stages” in order to avoid confusion. Add a new save key, called

private static final String SAVE_KEY_PLAYER_STAGE = "playerstage";

Right beside our levels variable, create a new array, called „stages”; Initialize it right beside our levels. Adjust the Load function to set stages to zero by default. Here’s how my Load function looks like now:

public static void Load()
{
    levels = new int[CharacterRecord.CHARACTERS.length];
    stages = new int[CharacterRecord.CHARACTERS.length];

    Preferences prefs = Gdx.app.getPreferences(PROGRESS_SAVE_NAME);

    for (int i = 0; i < CharacterRecord.CHARACTERS.length; i++)
    {
        levels[i] = prefs.getInteger(SAVE_KEY_PLAYER_LEVEL + i, i == 0 ? 1 : 0);
        stages[i] = prefs.getInteger(SAVE_KEY_PLAYER_STAGE + i, 0);
    }
//...

Don’t forget about Save function:

for (int i = 0; i < CharacterRecord.CHARACTERS.length; i++)
{
    prefs.putInteger(SAVE_KEY_PLAYER_LEVEL + i, levels[i]);
    prefs.putInteger(SAVE_KEY_PLAYER_STAGE + i, stages[i]);
}

Now, let’s get rid of errors which we inevitably got after removing currentLevel variable.

public static int getEnemyLives()
{
    return 3 + currentLevel * 2;
}

Becomes

public static int getEnemyLives()
{
    return 3 + stages[currentCharacter] * 2;
}

Let’s also add getEnemyDamage function (it makes sense to increase the damage as player’s hp increases too, right?)

public static int getEnemyDamage()
{
    return 1 + stages[currentCharacter] / 10; // increase damage every 10 stages
}

The Reset function needs to be heavily adjusted.

public static void Reset() {
    currentLevel = 0;
}

 

Forget about it. After player dies, let’s simply reduce his current stage by 5 (so he does not have to do everything from the beginning). We also need to reset player’s hp. Important note: when a new character is chosen in CharacterSelectionScreen, we also must reset player’s hp (since each char can have separate max hp now). Let’s introduce extra Boolean parameter to Reset, called resetProgress. It is only going to be true when player dies, and not simply changes the character.

public static void Reset(boolean resetProgress) {
    if (resetProgress)
    {
        stages[currentCharacter] -= 5;
        if (stages[currentCharacter] < 0)
        {
            stages[currentCharacter] = 0; // don't let it go below zero!
        }
    }

    playerLives = getPlayerMaxHp();
}

Now, let’s arrange the new reset calls. Go to CharacterSelectionScreen and alter the code of our next and prev buttons.

nextBtn.addListener(new ClickListener() {
    public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
        GameProgress.currentCharacter += 1;
        if (GameProgress.currentCharacter == CharacterRecord.CHARACTERS.length)
        {
            GameProgress.currentCharacter = 0;
        }
        GameProgress.Reset(false);
        prepareUi();
    }
});
…
prevBtn.addListener(new ClickListener() {
    public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
        GameProgress.currentCharacter -= 1;
        if (GameProgress.currentCharacter < 0)
        {
            GameProgress.currentCharacter = CharacterRecord.CHARACTERS.length - 1;
        }
        GameProgress.Reset(false);
        prepareUi();
    }
});

And fix the GameLogic a bit: firstly, we cannot simply increase current level when the enemy has less than 0 lives (we removed the variable, remember?). Let’s make a separate function inside the GameProgress, named increaseStage(). Let’s also grant player some money on level completion. Replace:

GameProgress.currentLevel++; 

With

GameProgress.increaseStage(); 

increaseStage implementation inside our GameProgress:

public static void increaseStage() {
    currentGold += 1 + stages[currentCharacter] / 4; // increase gold gain every 4 levels
    stages[currentCharacter]++;
}

The values are taken somewhat randomly, so only testing will show if those values are good. Always be on lookout to see what’s going on with the game balance. Two last changes:

player.takeDamage(1);
if (player.getLives() <= 0)
{
    GameProgress.Reset();
}

Pass true to our Reset function (we want to reduce levels when player has lost the game) and also use the new enemy damage vs player.


player.takeDamage(GameProgress.getEnemyDamage());
if (player.getLives() <= 0)
{
    GameProgress.Reset(true);
}

That should do it. Not much to see, but the gameplay-wise this will change a lot.

Relevant git commit: https://github.com/vladimirslav/dodginghero/commit/49e8b0fe0fa156e17acd6d4ab01d798296f70f03

Leave a Reply

Your email address will not be published. Required fields are marked *