Developing Multiplatform Game with Libgdx, part 8: enemy attacks!

Heya!

In our previous lesson, we’ve created an enemy. Now, it is time we teach how to attack. Our enemy will be responsible for timing attacks and telling the logic that there should be a warning given to the player (so that he can dodge).

First, in our Enemy class, let’s make a float variable that will count time since last attack (name it timeSinceAttack). We don’t want our attacks to be too predictable, so we’re going to use another variable to determine how long should we wait until performing our next attack. Declare float variable nextAttackTime. It is going to use a constant value of 3 seconds and add some random time interval (up to two seconds).

Declare a private static final variable BASE_ATTACK_TIME, set it to 3.

Define a private void function resetAttackTimer(), which will regen the timers. Here’s how my Enemy class looks now:

public class Enemy extends Sprite {

    private static final float BASE_ATTACK_TIME = 3.0f;
    private float timeSinceAttack;
    private float nextAttackTime;

    public Enemy(Resources res)
    {
        set(res.enemy);
        resetAttackTimer();
    }

    public void draw(SpriteBatch batch, SizeEvaluator sizeEval)
    {
        setPosition(sizeEval.getEnemyX(this), sizeEval.getEnemyY(this));
        super.draw(batch);
    }

    private void resetAttackTimer()
    {
        timeSinceAttack = 0;
        nextAttackTime = BASE_ATTACK_TIME + MathUtils.random(2.0f);
    }
}

Add an update(float delta) function, which will be responsible for timing events and actually launching attacks. But the question appears: how will .our enemy class tell the logic that attack should happen? I generally want to avoid circular references ( http://programmers.stackexchange.com/questions/11856/whats-wrong-with-circular-references ),

So I’m going to make an interface (something like a listener), accept it in the enemy’s constructor and make our logic implement it.

public interface EnemyAttackListener
{
    void OnAttack(boolean[][] tiles);
}

It will contain one method declaration OnAttack that accepts two-dimensional Boolean array which by size mirrors battlefield tile amount. (It will also be 4×4). If value is true, that means that logic should create warning effect on the given tile.

Now create a variable that will store the reference to our listener and adjust constructor to accept one.

private EnemyAttackListener attackListener;

public Enemy(Resources res, EnemyAttackListener listener)
{
    attackListener = listener;

 

Now, the only thing that’s left is to create the actual Boolean array. We could initialize it on every attack, but it’s better to initialize it once for every enemy and reuse it on every attack (memory management on mobile devices, right?). Declare it in the Enemy class:

private boolean targetTiles[][];

 

Initialize those in Enemy constructor:

targetTiles = new boolean[GameLogic.MAX_BASE_X + 1][];
for (int i = 0; i <= GameLogic.MAX_BASE_X; i++)
{
    targetTiles[i] = new boolean[GameLogic.MAX_BASE_Y + 1];
}

First, we initialize the columns (Array that contains arrays), then, we initialize the rows (child arrays).

Great, now we’re ready to actually get to attacking process. In our update function, check the current time versus the time when we need to attack. If our time to attack has come, fill the tiles that we want attacked.

We’re actually going to diversify attacks later on, but for now let’s make a very basic one: pick two columns (non-consecutive), leave others untouched.

Here’s how our update function is going to look now:

public void update(float delta)
{
    timeSinceAttack += delta;

    if (timeSinceAttack > nextAttackTime)
    {
        int col1 = MathUtils.random(GameLogic.MAX_BASE_X);
        int col2 = 0;
        do {
            col2 = MathUtils.random(GameLogic.MAX_BASE_X);
        } while (col2 == col1);
        // not very effective, but guaranteed to get different results

        for (int x = 0; x <= GameLogic.MAX_BASE_X; x++)
        {
            for (int y = 0; y <= GameLogic.MAX_BASE_Y; y++)
            {
                targetTiles[x][y] = (col1 == x || col2 == x);
            }
        }

        attackListener.OnAttack(targetTiles);
        resetAttackTimer();
    }
}

Note that

targetTiles[x][y] = (col1 == x || col2 == x);

Is just a short way of writing:

if (x == col1 || x == col2)
{
    targetTiles[x][y] = true;
}
else
{
    targetTiles[x][y] = false;
}

You can use whatever type you like (if it helps you to understand the code better), but I prefer the shorter version.

Amazing, now let’s make our gamelogic implement attack listener:

public class GameLogic implements Enemy.EnemyAttackListener

Press Alt+Enter to implement the suggested method. We just simply need to go through the array that has been passed to us and check every tile. If the tile is set to true, we create an effect on that tile.

First thing: go to GameScreen constructor and remove the WarningEffect creationg line from there:

WarningEffect.Create(0, 0, sizeEvaluator, game.res, logic.getEffectEngine());

Delete it. Yes.

Now go back to GameLogic and do three things:

Remove SizeEvaluator from WarningEffect constructor. We don’t need it there. We need it on drawing only, similar to our Player and Enemy. Keep sizeEvaluator in WarningEffect’s draw method, but remove all other mentions and declaration of SizeEvaluator in our WarningEffect class. Adjust “draw” function to accept SizeEvaluator as second parameter. Do the same thing with our parent class, Effect.

public abstract void draw(SpriteBatch b, SizeEvaluator sizeEvaluator);

Add the same parameter to our EffectEngine.

public void draw(SpriteBatch batch, SizeEvaluator sizeEvaluator)
{
    for (int i = 0; i < effects.size(); i++)
    {
        effects.get(i).draw(batch, sizeEvaluator);
    }
}

Go back to GameLogic. In Gamelogic’s update method, let’s add the update call of our enemy:

public void update(float delta)
{
    effectEngine.update(delta);
    enemy.update(delta);
}

Yup, only that. Now, to an actual OnAttack method:

@Override
public void OnAttack(boolean[][] tiles) {
    for (int x = 0; x < tiles.length; x++)
    {
        for (int y = 0; y < tiles[x].length; y++)
        {
            if (tiles[x][y])
            {
                WarningEffect.Create(x, y, game.res, effectEngine);
            }
        }
    }
}

As discussed before, we go through all the tiles, and create the effect when necessary. The only thing left to do is to adjust the effect drawing call in our GameScreen.

Launch the game, see what we get.

I’ve actually noticed a bug: in our WarningEffect, we’re not assigning fieldX and fieldY variables in init function. Let’s fix this:

public void init(int x, int y, EffectEngine parent, Resources res)
{
    super.init(parent);
    resources = res;
    fieldX = x;
    fieldY = y;
}

Run the game! You should be getting the enemy attack warnings shown once in a while.

Final look

Relevant commit: https://github.com/vladimirslav/dodginghero/commit/1e86d0af762486ad9425e28c82e4e46a59033522

Leave a Reply

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