Boss work is continuing nicely; the boss can now fire projectiles, can hurt Leilani on touch, and can be damaged by explosions! It can't actually die yet, though.
Plus I've put together a scripting system for controlling the boss's actions.
Today I'll talk about how the boss as a whole is pieced together.
Boss Code StructureIn my game an entity is an object in the game world. Typically something in the world just consists of a single entity - for example a normal enemy, or a coconut, or a collectable shell.
I've occasionally broken more complex things up into multiple entities. The main reason to do this is that each entity only supports a single hitbox, so using multiple entities allows multiple hitboxes. It can also just help to neaten the code up.
One example of this is that when I added Leilani's bubble powerup, I added the bubble as a separate entity. The bubble entity automatically tracks the Leilani entity's position, and Leilani tells the bubble when to turn on or off. The bubble entity then animates and plays sounds as appropriate. The bubble also has a bigger hitbox, and when it detects a collision that should pop the bubble, it reports that back to the Leilani entity.
The boss uses a similar thing on a bit of a larger scale. There are 3 main types of entity.
The
BossScript entity gives high-level orders to the
BossController entity, which executes those orders using a collection of
BossSection entities, each of which is a visible and collidable component of the boss.
BossScriptThis entity is invisible, and doesn't have any collision or anything. When it's created it's given a BossController entity to manage, and also a script to execute.
At its most basic the script can be thought of as just a series of actions to execute. For example...
- Drive to far side
- (Wait until finished driving)
- Turn around
- (Wait until finished turning)
- Fire a bomb
- (Wait 1.0 seconds)
- Fire a bomb
- (Wait 4.0 seconds)
- (Loop back to the start)
In reality the script is more complicated than this, but that's not something to cover in this post.
Some actions can be executed by the BossScript entity itself - for example, "Wait 1.0 seconds". In the case of "Wait until finished turning" the BossScript will query the BossController to check when an action has finished. And actions such as "Drive to far side" are passed to the BossController for it to execute.
This script is stored in a file, the most useful thing about this is that I can edit the file, quickly restart the current level and see the behaviour changes in the boss instantly! No compilation of code or restarting of the game necessary.
BossControllerThis is a physical entity in the world, which has collision. The hitbox is big enough to represent the whole boss, and this hitbox is used when the boss moves around the world (for example to check if the boss is on the floor or hitting a wall). The hitbox doesn't collide with other objects, such as Leilani - that kind of interactive collision is left to the BossSection entities.
The BossController is invisible. Sprites and animations are responsibilities of the BossSection entities to do.
The most important thing the BossController does is implementing the actions that the BossScript tells it. So if it's told to drive to a point, it accelerates, moves towards the target position, and stops there, as described in recent posts. It can be told to jump, turn around, or to fire a projectile.
It also does the suspension and tilting-under-acceleration animation calculations. Then it passes the resulting positions / rotations through to its BossSection entities.
The layout of the sections is read from a file, similarly to the script, so it can be reloaded instantly by restarting the level. There's a simple tree structure which is used to give the idea of "parenting" the sections to each other purely for the purpose of calculating their positions. For example:
Body: sprite "Body", position 0,-0.2, tiltable, collidable
\_ Turret: sprite "Turret", type Turret, position 0,-1.8, collidable
\_ Lid: sprite "Lid", position 0,-1.5
LeftTrack: sprite "Track", type Track, position -1.8,0
RightTrack: sprite "Track", type Track, position 1.8,0
The turret is a child of the body. When the tank accelerates, it's the body that tilts/rotates due to the acceleration, and the turret only rotates because it's attached to the body.
BossSectionTypically each section has a single sprite, and a single hitbox (the orange box). It can also provide solid collision (the blue box) to prevent Leilani from running through it while invincible.
The sections are responsible for handling collision, so if one touches Leilani, it hurts her. If an explosion hits the section, it reports back up to the BossController that the boss should take damage.
There are also some special types of sections such as:
Turret: capable of firing projectiles, it plays unique animations related to this.
Track: as the tank moves, this animates to show the tank tracks rotating.
SummaryWell that was a brief overview of the general idea of how the boss is constructed!
Creating a boss seems to be a good test of a game's technical gameplay systems, as it's way more complicated than most other things in the game. I originally intended on supporting multiple hitboxes in a single entity, for the purpose of doing things like this. It would be quite impractical to tack that functionality on at this point however, and would overcomplicate the general process of checking for collisions. Cobbling multiple simpler entities together to behave as a cohesive whole is simpler on the whole, more flexible, and helps to divide code up into more sensibly sized chunks.
I'm leaning on the data driven aspect of the boss quite heavily (the script file and the section layout file) and it already feels like it's paying off. The convenience of seeing changes instantly can never be overexaggerated. The faster your turnaround time is for iterating and testing ideas, the better!
The rest of October will be spent putting together a proper script for the boss so it can be fought, and maybe giving it some sort of basic 'death' state / sequence. I'm probably going to roll the boss work over into November rather than stopping at the end of October, as I'm not feeling burnt out on it yet, and there are many more aspects of the boss that need to be worked on.