We have the field where our character can walk. But what’s the point of walking if you can’t get anywhere? Or rather, if we cannot dodge the attacks of the enemy?
We’ll try to address this today. Let’s add the base marks on the field that will indicate where will the next enemy attack will go (essentially telling player to avoid these bases). We’ll just show a huge exclamation mark above the dangerous bases for now.
After exclamation mark passes, the player will be damaged if he stays on the marked fields. Even though it’s a graphical effect, we’ll need it to essentially affect the game logic (trigger an attack after it’s done showing).
In our graph package, let’s create „effects” package. Inside that package, create „EffectEngine” class. Create an empty public constructor and leave it as is for now. The effect engine will contain the list of all effects that are currently present in the game. It will also update them and issue drawing commands.
Let’s create an “Effect” abstract class in the same package that will implement Pool.Poolable.
To get back on this: if we’re making a game for android, it is good to reuse objects instead of creating them anew. Imagine if we have one effect in place, it vanishes and then the new one appears. If we reallocate the memory for it, it will take much more time to do this (especially with Android’s Garbage Collector). Instead, we’ll make a Pool of objects that we’re going to reuse.
For our base Effect class, we’ll just keep the most necessary things. To me, it usually comes down to two variables. Each effect should have its lifetime counted. We also need to know if the effect is alive or not (so that EffectEngine will know when to destroy it). Declare them this way:
public abstract class Effect implements Pool.Poolable { protected boolean isAlive; protected float timeAlive;
They need to be declared protected, as it means that they will be accessible by derived classes. Since those will be reinitialized often, the constructor won’t do much apart from setting the variables to initial values.
public Effect() { isAlive = false; timeAlive = 0; }
After this, let’s actually get to our Init function, which will reset the values to zero and will add the effect to the engine (will add it a bit later). The release function should also be there in case we’ll need extra disposal.
public void Init(EffectEngine parent) { isAlive = true; timeAlive = 0; } public void release() { }
Now to other functions: there is a need for draw() and update functions. Update will update the current effect (most of them will have the lifetime, update will be responsible for checking if our effect has expired). In our case, we simply adjust the timeAlive.
public void update(float delta) { timeAlive += delta; }
draw is self-explanatory. It will take the batch and draw the effect. Since we do not plan to instantiate the effect class, declare draw function abstract.
public abstract void draw(SpriteBatch b);
One more thing remains: let’s allow to access isAlive variable to check whether effect is alive by adding isAlive method:
public boolean isAlive() { return isAlive; }
Now, let’s get back to EffectEngine class. First thing we need to do: declare a List of Effects:
List<Effect> effects; public EffectEngine() { effects = new ArrayList<Effect>(); }
The whole interaction is going to happen with this list. Our EffectEngine essentially needs only four functions: draw(), update(), clear(), add(). Draw will be calling draw() on all existing effects, add will be used to add a new effect to the list, clear will clear all the effects (in case we exit the gamescreen) and update will do the necessary updates (and remove the dead effects from the list). Let’s start with add function, as it is going to be the simplest one out there:
public void add(Effect effect) { effects.add(effect); }
Nice and simple, just add a new effect to an existing list. The update function is going to be a bit trickier though. First, we’ll go through the effect list and call their ‘update’ functions. After that, it’s time to root out the dead (expired) effects.
public void update(float delta) { int i = 0; while (i < effects.size()) { effects.get(i).update(delta); if (effects.get(i).isAlive()) { i++; // just go forward in our list } else { effects.get(i).release(); effects.remove(i); } } }
First, we call the update function of every separate effect. After that, we check if the effect is alive. If it is, we simply proceed to the next effect in our list. But if it is not, we remove the effect with the current index (and do not increase our index, because the next element in the list moved to the current index).
The draw function is much simpler:
public void draw(SpriteBatch batch) { for (int i = 0; i < effects.size(); i++) { effects.get(i).draw(batch); } }
Go through all effects and draw them. And finally, clear function. The idea behind it is to mark the effects reusable (released) so that the next time (in case we restart the GameScreen) our effect pool won’t need to create any new elements, but reuse the old ones.
public void clear() { while (effects.size() > 0) { effects.get(0).release(); effects.remove(0); } }
Finally, go back to Effect class and modify it’s Init function: at the end of it, call parent.add(this);
Here’s how my code looks for Effect class:
public abstract class Effect implements Pool.Poolable { protected boolean isAlive; protected float timeAlive; public Effect() { isAlive = false; timeAlive = 0; } public void init(EffectEngine parent) { isAlive = true; timeAlive = 0; parent.add(this); } public boolean isAlive() { return isAlive; } public abstract void draw(SpriteBatch b); public void update(float delta) { timeAlive += delta; } public abstract void release(); }
And EffectEngine class:
public class EffectEngine { List<Effect> effects; public EffectEngine() { effects = new ArrayList<Effect>(); } public void add(Effect effect) { effects.add(effect); } public void update(float delta) { int i = 0; while (i < effects.size()) { effects.get(i).update(delta); if (effects.get(i).isAlive()) { i++; // just go forward in our list } else { effects.get(i).release(); effects.remove(i); } } } public void draw(SpriteBatch batch) { for (int i = 0; i < effects.size(); i++) { effects.get(i).draw(batch); } } public void clear() { while (effects.size() > 0) { effects.get(0).release(); effects.remove(0); } }
Adding the warning effect
Well, time to do a bit more preparations before we actually .Let’s draw the Warning sign effect. (16×16). Here’s mine:
Save it as “warning.png”
Now we’ll derive a new class (WarningEffect) which will show the warning on the field. In our effects package, create a new Java Class, name it “WarningEffect.” Make it extend the Effect class. Automatically implement the suggested methods (draw, release, reset). Create a constructor that won’t do anything but call super(), constructor of the parent. Here’s what we have now:
public class WarningEffect extends Effect { public WarningEffect() { } @Override public void draw(SpriteBatch b) { } @Override public void release() { } @Override public void reset() { } }
Here’s where it gets a bit tricky: since every type of effect requires it’s own pool, I usually make a static Pool in every separate class. And make a separate function that instantiates the object (either by creating a new one or by taking an unused one from the pool).
At the bottom of our WarningEffect class, add a new static variable warningPool:
static final Pool<WarningEffect> warningPool = new Pool<WarningEffect>() { @Override protected WarningEffect newObject() { return new WarningEffect(); } };
Essentially it will initialize a new WarningEffect if there are no released ones that are available for reinitialization. In our release() function, write
warningPool.free(this);
Which will indicate that the current object is available for reuse.
Now we need a Create function that will take care of returning a new effect instance and setting the base values to it.
static public WarningEffect Create(int fx, int fy, SizeEvaluator sz, Resources res, EffectEngine engine) { WarningEffect effect = warningPool.obtain(); effect.init(fx, fy, engine, sz, res); return effect; }
Now as you have probably noticed, there’s an error when you’re trying to call inti function. We absolutely need to pass the resources (will contain warning texture region) and SizeEvaluator (will allow us to draw the effect in a proper place).
Let’s add our own init method and add a few extra variables to handle that. Make a good habit of declaring variables and constants at the start of the class: that way you’ll be able to find them easier, it improves readability. Put these lines at the start of our class:
Resources resources; SizeEvaluator sizeEvaluator; public int fieldX; public int fieldY;
And then add our init method:
public void init(float x, float y, EffectEngine parent, SizeEvaluator sz, Resources res) { super.init(parent); fieldX = x; fieldY = y; sizeEvaluator = sz; resources = res; }
Let’s go back to Resources. Add a new TextureRegion declaration named warning.
public TextureRegion warning;
All that’s left is to add update() and draw() methods. Initialize it the same way you initialize any other Texture Region.
warning = gameSprites.findRegion("warning");
Now go back to WarningEffect class. In draw() method, add the actual drawing of a warning.
@Override public void draw(SpriteBatch b) { b.begin(); b.draw(resources.warning, sizeEvaluator.getBaseScreenX(fieldX), sizeEvaluator.getBaseScreenY(fieldY)); b.end(); }
The last thing we need to do here is to set the update function. At the start of the class, declare a constant:
private static final float WARNING_TIME = 2.0f;
Our warning will be shown for 2 seconds. After that, the player will get damaged if he did not get out of the way.
Let’s override the update function now:
@Override public void update(float delta) { super.update(delta); // pass the time change if (timeAlive > WARNING_TIME) { isAlive = false; } }
The backend part is done, let’s connect it to our logic and ensure it draws fine on our GameScreen. In our GameLogic, declare the EffectEngine variable and initialize it in constructor:
EffectEngine effectEngine; public GameLogic() { player = new Player( MathUtils.random(MAX_BASE_X), MathUtils.random(MAX_BASE_Y) ); effectEngine = new EffectEngine(); }
Add the update(float delta) method, and make sure effectEngine.update(delta) is called there:
public void update(float delta) { effectEngine.update(delta); }
Now in our GameScreen’s update function, make sure to call logic.update(delta):
private void update(float delta) { gameStage.act(delta); logic.update(delta); }
We also need a way to draw the effects. Make a getter inside GameLogic that would return a pointer to our EffectEngine.
public EffectEngine getEffectEngine() { return effectEngine; }
Now we need to call the draw from our GameScreen. In our render function, after drawBases() call, add:
drawBases(); logic.getEffectEngine().draw(batch);
Our Effect Engine seems to be done. The lesson is getting lengthy, so I think I’ll just add one effect for the show and call it a day.
In our GameScreen constructor, after logic has been initialized, add the following:
WarningEffect.Create(0, 0, sizeEvaluator, game.res, logic.getEffectEngine());
Run the game. The warning sign will show at the leftmost bottom tile. That should be enough for now, we’ll continue to add better warning effect changes and player damage in our next lesson.
Relevant commit: https://github.com/vladimirslav/dodginghero/commit/4a2ff247520baefbd2ba5895bd1bc41acff03b5c