First thing I want to talk about and is a little object I call "stateObject" that I use extensively which helps me manage the object's state, obviously, but also animations, movements and behavior scripting.
So let's say I want to come up with a simple enemy like this little lizard here :
First I need to have it reference stateObject as a parent. stateObject has a few variables the lizard inherits that I can tweak from the variables window : in this case "flag_gravity" is set to false so it doesn't fall from the ceiling, then I can set his "hp" and "spd" to my liking.
Then I just have to add this to the step event :
Functions that starts with "state" are played in sequence, which means I can add pauses (counted in steps) in-between my function calls and thus control timings very precisely.
The lizard's object is set to start as "state == states.normal". The only other state that is important to add for enemies is the "die" state which handles the behavior when its hp reaches zero.
Here is what happens with the "normal" state above is this :
- on first step, set sprite sheet animation references (set to play frames 1 to 3 in loop at image_speed 1; this is his walking animation)
- patrol for 60 steps (won't go in details here, but it uses a customizable "spd" variable and other flags that can be set on child object)
- after patrol period is done, set hsp, vsp to 0, 0. Skip right away to next action.
- set animation reference to frame 0, standing still frame (the function handles to set image_speed to 0 if only 1 value is passed)
- pause for 20 steps
- set anim to frame 4 (mouth opened to make its attack)
- tiny 2 steps pause..
- create instance of the bullet, passing x, y, depth and dir. The bullet will handle itself from there.
- (then I set the anim frame to 4 again but it's a mistake, I don't need to)
- short 16 steps pause
- set frame back to 0
- 30 steps pause...
- repeat from start.
How I handle this to happen in sequence is so simple I'm probably dumb for not thinking of a better way. But it makes everything so easily readable and writable I'm very happy with it. Let's look at how it works.
Above is the create event for the stateObject. It does a few things :
- Keep the original x, y and dir in memory for easily restoring object to initial state
- Initialize dir (direction), vsp (vertical speed) and hsp (horizontal speed)
- Set the state to a customizable "starting_state" (found in variables)
- More interestingly for us here, inititialize the "state_counter" and "state_total"
- "doStep" is used to "pause" the object. Child objects should manage this.
- It does a few more things regarding collisions and animation but we'll look at it in a further entry.
Now what's so cool about "state_counter" and "state_total"?
- First, "state_total" is reset to 0 every step.
- Then, "state_counter" is increased by one during the "End Step" event.
- Each "state function" adds a duration (in steps) to the "state_total" (most are just defaulted to 1) that they can check against to see if "it's their turn" to be called during any particular step. State functions mostly follow this template :
Stuff only happen when the "state_counter" has reached a certain point. If I want a function to be called then skip directly to the next one without waiting a step, I just increment the "state_counter" at the end of the function (as seen above).
Adding a pause is ridiculously simple :
GML:
function statePause(duration) {
state_total += duration;
}
To repeat the sequence from the start, I simply reset the state_counter to zero (actually -1 because end step takes care of incrementing up to 0).
GML:
function stateRepeat() {
var c_min = state_total;
if(state_counter >= c_min)
state_counter = -1;
}
Which is what I do whenever I change state :
GML:
function stateBecome(newState) {
var c_min = state_total;
if(state_counter >= c_min){
state = newState;
state_counter = -1;
}
}
I can script as many different states I need for an object to build any kind of simple or complex behavior. I have several functions I can use, some more specific, like the patrol behavior function or one to detect and react to the player, but in general I get away doing what I need with a handful of simple things : setting animation frames, setting H and V speeds, adding pauses, creating instances and executing scripts.
Cheers!