Developing Multiplatform Game with Libgdx, part 11: bonus display and enemy lives

Winning The Game

Some people really don’t know when to stop. I mean, as if implementing the game losing process was not enough? Sigh. Fine, we’re going to allow our player to win.

I’m actually excited. This means that today we’re getting a first „real” prototype of the game 🙂 Usually at this stage you can see how do things feel and if it’s worth continuing to work on it.

Let’s start small. Add health value to our enemy. We’ll need to take it down before we win. Go to our „Enemy” class. Same as our player, add variable to count lives there;

private int lives;

Great! Now initialize it in our constructor. I’m setting it to 10, but you can pick however many you want. The point is not to make the game too annoying to play, so the enemies definitely should be beatable, but not very easily (leave some challenge!).

private static final int DEFAULT_ENEMY_LIVES = 10;
…
public Enemy(Resources res, EnemyAttackListener listener)
{
    lives = DEFAULT_ENEMY_LIVES;
…

Good! The only thing left to do here is to make a getter.

public int getLives()
{
    return lives;
}

This part should be good. Let’s show the enemy lives on our gamescreen. Go to GameScreen class and in our DrawUI function, add the line which will draw enemy lives in the top right corner. Use our good-old DrawShadowed function for that.

DrawShadowed("ENEMY:" + logic.getEnemy().getLives(),
        0,
        gameStage.getViewport().getScreenY() + gameStage.getHeight() - 3, // add some vertical padding
        gameStage.getWidth() - 5,
        Align.right,
        Color.WHITE);

Run the game. You’ll see that enemy lives are written on the right corner now.

Enemy Lives Displayed On The Right Top Corner

Enemy Lives Displayed On The Right Top Corner

Cool, huh?

Spawning Bonuses and Attacking

Now let’s find a way how we can make them go down. Since our mechanics involve stepping on the field and dodging attacks, why not add some pickups on the same tiles that would trigger the damage on an enemy? Or even heal the player?

First, add the graphics for those pickups. I’m going to use two pictures that my girlfriend Helen drew for me (https://twitter.com/ElenaNazaire). Those are two icons, named attack.png and health.png

attack health

Declare and load them up in our Resources class!

public Sprite attackBonus;
public Sprite healthBonus;

public Resources()
{
…
    attackBonus = gameSprites.createSprite("attack");
    healthBonus = gameSprites.createSprite("health");
}

Next, in our objects package, create a new class, name it “Bonus”; As usual, let’s think first what we want from our Bonuses.

– Same as with our effects, we should strive to reuse the bonuses instead of reinitializing them.
– We want two bonus types: one that takes enemy health away, the other gives health to our player.
– How do we detect that player gets the bonus?

Make Bonus extend Sprite (after all, the bonus needs to be displayed?) and, same as effect, implement Poolable. Let’s add bonus type and fieldX and fieldY as a private variables. Here’s what we have now:

public class Bonus extends Sprite implements Pool.Poolable {

    private byte bonusType;
    private int fieldX;
    private int fieldY;

    @Override
    public void reset() {

    }
}

Now make an empty constructor.

public Bonus()
{
    
}

Good. It will be called when our pool cannot locate any free unused bonuses. Now define two type constants:

public static byte BONUS_TYPE_ATTACK = 0;
public static byte BONUS_TYPE_HEALTH = 1;

Now, define setup function, which will assign the proper values.

public void setup(int fx, int fy, byte bType, Resources res)
{
    fieldX = fx;
    fieldY = fy;
    bonusType = bType;
    if (bType == BONUS_TYPE_ATTACK)
    {
        set(res.attackBonus);
    }
    else if (bType == BONUS_TYPE_HEALTH)
    {
        set(res.healthBonus);
    }
}

Should be self-explanatory, but the last conditions assign our bonus pictures. Now, similar to our WarningEffect, let’s make a bonus pool.

static final Pool<Bonus> bonusPool = new Pool<Bonus>() {
    @Override
    protected Bonus newObject() {
        return new Bonus();
    }
};

It will store unused bonuses (or create new if there are no free ones). Speaking about creating and releasing bonuses, let’s make a functions that do this:

public void release() {
    bonusPool.free(this);
}

static public Bonus Create(int fx,
                           int fy,
                           byte bType,
                           Resources res)
{
    Bonus bonus = bonusPool.obtain();
    bonus.setup(fx, fy, bType, res);
    return bonus;
}

Let’s not forget the drawing process too. Just cheat (the only time you should) and copy it from the Player.

public void draw(SpriteBatch batch, SizeEvaluator sizeEval)
{
    setPosition(sizeEval.getBaseScreenX(fieldX), sizeEval.getBaseScreenY(fieldY));
    super.draw(batch);
}

Finally, don’t forget the fieldX, fieldY and bonusType getters.


public int getFieldX()
{
    return fieldX;
}

public int getFieldY()
{
    return fieldY;
}

public byte getBonusType()
{
    return bonusType;
}

This should be enough for the bonus implementation (for now). Let’s get to the game logic. Since our field is small and player moves relatively rarely (maybe a few times per second max), we can make a list of bonuses and go through the list every time when player makes a move to check whether there was a collision with bonus and something should be done.

Go to our GameLogic class. Declare the bonus list, elapsed game time and the time of the last bonus spawn:

ArrayList<Bonus> bonuses;
float gameTime;
float lastBonusSpawnTime;

Initialize those variables it in the GameLogic constructor.

bonuses = new ArrayList<Bonus>();
lastBonusSpawnTime = 0;
gameTime = 0;

How often do we want to spawn bonuses? Let’s make it two seconds for experimentation purposes. Make a constant to show that:

private static final float BONUS_SPAWN_INTERVAL = 2.0f;

Great. Now to the actual spawn process. We should do this in our update function. First, we want to increase the game time, no matter what happens. Since update passes the delta value, we just add this to our gameTime and in the end we have the total game time. Then, we compare the gametime to the last spawn time and if it is greater than the bonus spawn interval, we spawn a new bonus (magic number, let’s cap our bonus amount to three. So, don’t spawn a bonus if we already have three bonuses). Yes, we need a separate function for that. Here’s how my update function looks now:

public void update(float delta)
{
    gameTime += delta;
    effectEngine.update(delta);
    enemy.update(delta);
    if (lastBonusSpawnTime + BONUS_SPAWN_INTERVAL < gameTime &&
            bonuses.size() < 3)
    {
        SpawnRandomBonus();
    }
}

Now, let’s get to SpawnRandomBonus function. We need to ensure that our new coordinates do not overlap with player or other bonuses. Also, let’s not generate the bonus on the same column and row where player is standing. That leaves us with plenty of space (9 tiles, to be exact).

private void SpawnRandomBonus()
{
    int fx = 0;
    int fy = 0;
    boolean targetNonEmpty = true;
    do {
        fx = MathUtils.random(MAX_BASE_X);
        fy = MathUtils.random(MAX_BASE_Y);
        targetNonEmpty = player.getFieldX() == fx || fy == player.getFieldY();

        for (int i = 0; i < bonuses.size() && targetNonEmpty; i++)
        {
            if (bonuses.get(i).getFieldX() == fx &&
                    bonuses.get(i).getFieldY() == fy)
            {
                targetNonEmpty = true;
            }
        }
    } while (targetNonEmpty);

    bonuses.add(Bonus.Create(fx, fy,
            MathUtils.random(3) == 0 ? Bonus.BONUS_TYPE_HEALTH : Bonus.BONUS_TYPE_ATTACK,
            game.res));
    lastBonusSpawnTime = gameTime;
}

The function might look tricky, but let’s go through it:

First, we generate new coordinates for the bonus. We check if those are equal to players coordinates. Then we go through already existing bonuses and if we find the bonus that has the same coordinates – we stop searching. We simply generate the new coorindates.

After acceptable coordinates have been found, we add the new bonus to the list. We don’t want health bonuses to spawn too often, so we generate a number from 0 to 3. If the number is 0, we spawn a health bonus. Otherwise, we spawn an attack bonus (which will damage our enemy).

Finally, we set our last bonus spawn time to current game time.

I feel that this tutorial is getting long, so let’s no program the logic of the bonuses, but only draw them. Create a bonus list getter in GameLogic:

public ArrayList<Bonus> getBonuses()
{
    return bonuses;
}

Then, in our GameScreen, right before we draw our player, let’s draw our bonuses. Here’s how the last part of the code looks:

batch.begin();
for (Bonus bonus : logic.getBonuses())
{
    bonus.draw(batch, sizeEvaluator);
}
player.draw(batch, sizeEvaluator);
enemy.draw(batch, sizeEvaluator);
batch.end();

Run the game. The bonuses should spawn now and be correctly displayed!

Bonuses are now displayed!

Bonuses are now displayed!

This concludes the lesson. In our next lesson, we are going to work on the actual logic (picking up bonuses and damaging the enemy).

Relevant commit: https://github.com/vladimirslav/dodginghero/commit/a439ec0c706ba5fd1110186cc9bd860bb97fe27b

Leave a Reply

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