David Newton (davidn) wrote,
David Newton
davidn

Crystal Towers 2: Anatomy of a Boss

Boss monsters are usually a bit of a headache to write. You have to think up a cocktail of attacks, weak points and strategies that are balanced to be difficult to cope with at first but possible to learn through experience, and then write the whole thing's behaviour, which can often get quite complex. As evidence that I'm still working on the game, this is the seventh of the bosses I've made, and at the moment it's called Beam Stack.



The general approach that I'm using for bosses in this game is treating them as loose sort of state machines, with one string called State which is the main thing that decides how the boss should behave at any time, along with a heap of other variables on the object that describe more of the details of those behaviours. By making at least one of the possible actions in each state being to change the contents of State to something else (and having what it changes to possibly dependent on other conditions), the boss can move between behaviours in sequence.

This particular boss has eighteen additional variables, which is the largest number that I've had on any of them so far:

HP
Time
MinPattern
MaxPattern
FiringInterval
FiringNumber
DecidingTime
DecidingGraceTime
FireThisNow
FireSpeed
FireDirection
ChanceForTop
AfterFireTime
DescendSpeed
SwitchChance
CenterChance
FireSequences
MaxFireSequences


As complex as this may seem at first, putting as many things as possible into into the object's variables actually makes things easier and more flexible, because you can instantly change the way that the boss works by moving numbers around - at the beginning, or during the course of the game so that it can change its behaviour as it goes on and surprise the player. Of the eighteen variables, HP and Time are the ones that are the simplest and used most globally - HP starts off at six hundred and is depleted slightly with each successful attack on the boss, and Time is used as a timer - when State is changed, it's set to zero, and then counts up by 1 every time a frame is drawn (50 times per second). Timed behaviours are done by checking that Time has reached a certain value.

This boss has eleven different possible states that it moves through to form its attack cycle.


Dormant: This is the state that all bosses start in. Nothing happens at all when in this state, until the player moves into the boss area, setting the State to Ready.

Ready: The boss is waiting to begin. Again, all bosses have this state, and I use it to fade out the level music and start up the boss theme as well as do some other visual effects sometimes. This particular one doesn't have any - after waiting around for a bit and waiting for Time to increase, it just goes on to Slowrise.

Slowrise: The slow rising out of the ground that the boss does at the start. Slowrise and Fastrise could really have been one state with another variable to determine the speed, but laying it out this way separates the beginning from the main cycle more. While in this state, the boss moves upwards by 1 pixel every frame, until it's completely out of the ground (detected by its Y position matching an invisible detector object near the top of the screen). After it's reached that point, it goes on to Deciding.

Deciding: This is the state the boss is in when you see the flashing of the lights up on the top panel, while it's randomly determining which pattern of lasers to fire. While the Time spent in this state is below DecidingTime, the object that represents those lights cycles rapidly between a random animation frame between the values of MinPattern to MaxPattern (which start at 0 and 3, giving four possibilities out of the 16 frames).

After DecidingTime has been reached, it then waits DecidingGraceTime before turning either the top or bottom lights at random (a 1 in ChanceForTop chance of picking the much more difficult-to-reach top light - this starts at the spectacularly unlikely 99999 but is brought down sharply to 3 or 2 later on) and starting the FireSequence.

FireSequence: Probably the most complex of the states, this is the one the boss is in when it's firing the coloured beams. The sequences are stored in an invisible list object, with fifteen lines that are like miniature programs in themselves, composed of numbers that represent beams to fire. 1, 2 and 3 represent individual beams, 4, 5, 6 and 7 are the patterns of two and three, 8 represents a pause and 9 indicates to stop. For example, alternating the top and bottom beams looks like "313139" in the list.

Without getting into the absolute minutiae of it, the boss looks at the list line picked by the state above, then continually copies the FiringNumberth number from it into FireThisNow with a pause of FiringInterval frames in between.

The mechanics of actually shooting aren't triggered by the main boss object itself - instead, a different part of the game loop detects when FireThisNow isn't equal to 0, creates the necessary beam objects and sets them going in direction FireDirection (0 = right, 16 = left) at speed FireSpeed to launch them towards the player, after which it sets FireThisNow back to 0 to avoid repeating the action.

If FireThisNow is 9, then instead of creating any objects it detects that the firing sequence has stopped and puts the boss into AfterFire instead.

AfterFire: A small pause here determined by AfterFireTime, before the boss reduces the number of FireSequences by one and then looks at the result to decide what to do next. If it's zero, then it sets it back to the value of MaxFireSequences (which is always 2) and then goes to Descending. If not, then it loops back to Deciding to set up another random fire sequence. When the boss rises up the first time it only fires one sequence, as FireSequences begins at 1 - every time after that it will be set to 2.

Descending: Pretty much the opposite of the rising states, the object sinks slowly into the ground (DescendSpeed pixels per frame) until its Y position matches that of a detector. When it does, it goes on to MoveInGround.

MoveInGround: When the boss is hidden in the ground, the weak-point lights are turned off, then a couple of decisions are made. First, tactics are changed according to the amount of health that it has left, as I explain below. As implied by its name, the next decision is where to move to - there's a 1 in SwitchChance likelihood that it'll change the side that it appears on (which starts off as 1, a certainty), and a 1 in CenterChance possibility that it will appear in the centre of the screen instead of either side. Unless it's already in the centre, in which case it'll just pick the left or the right side at random. Once it's moved, FireDirection is also set depending on what side it's on, so that the lasers are always fired towards the centre of the area. After a small wait while it makes the decision, Fastrise is the next step.

Fastrise: Much the same as the slower variety above, except it happens much faster, as you'd expect. When it reaches the height of the detector at the top, it will go all the way back to Deciding if on the left or right side, and to FireZeMissiles if it's in the centre.

FireZeMissiles: (Well, nobody usually sees the state names except me.) This state is the rarest one - it appears at 1:30 in the video, where six guided missiles are fired from the boss's sides (the sequence is always the same and is done by creating objects at certain positions when Time becomes 40, 60, 80, etc). This demonstrates another tactic for boss writing - splitting the duties up between various different objects that have their own independent behaviours, launching them and leaving them to go off and do what they like, and making the difficulty come from the player having to cope with several things at once. After the firing sequence is over, the boss goes into Descending and leaves the missiles to function independently.

The missiles are a bit more significant than the laser beams, but they still follow simple rules: they streak along and always gradually turn towards the player object if they're not already facing it, speeding up gradually the longer they exist for. They're destroyed on contact with an obstacle backdrop or with the player (in which case they'll cause 8 points of damage).

Dying: And finally, this is another state that all bosses have - no matter what state it's in at the time, this one is jumped to when its health hits zero. All movement stops, a sequence of explosions is gone through by creating some invisible objects that crawl over the boss and plant other objects behind them, and after Time rises to about three hundred it flashes the screen and lets the Level Ender object (the big jewel) drift to the centre of the screen to be picked up.

And to keep all of that interesting, some of the variables are changed during the course of the level so that the behaviour changes slightly. This particular boss changes by checking the value of its health when it's in the MoveInGround state - if it's below certain thresholds, it will change MinPattern and MaxPattern to make the selected sequence more likely to be difficult (or select from all fifteen possibilities at the end), decrease the FiringInterval and increase the FireSpeed, or modify the ChanceForTop, SwitchChance and CenterChance variables to make the patterns less predictable and potentially more difficult for the player. If I feel like making the behaviour even more varied, all I have to do is tell it to change other variables as well - for example, increasing the MaxFireSequences so that it stays up to fire longer, or making it certain to alternately go into the centre and do the missile attack by reducing CenterChance to 1.

And all of that goes together to form the thing that you see in the video. After I first set it up it took me ages to get past it the first time, but now I can do it fairly reliably - if other people find the same thing, then that's just the right difficulty of boss I'm aiming for.
Tags: coding, crystal towers 2
Subscribe

  • LJ 18th anniversary

    #mylivejournal #lj18 #happybirthday Thanks, Livejournal. You've helped me record a truly incredible stretch of my life, from unsurely…

  • What's My Fruit? - Episode 12

    I discovered a new fruit at Russo's! This one doesn't really look a whole lot like anything, and if you live in a remote place with houses built…

  • Vulkan

    I’ve finished Vulkan! Enough to call it a beta, anyway. Venture down into the Vulkan power station, a promising side project by UAC that as usual…

  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 7 comments