Death Chron dev log

Hey there Game Maker community!

I've been working with Game Maker Studio 1.4 and now 2.3 since 2016. Some of my Game Maker projects are :

- Sprite Sequence. Experimental weekly released project with over 30 entries in 2016. Released Chapter 1 on Kongregate in 2019.
- Project Zeta (Chapter 1 on Kongregate), Mega Man-like game (on hold)
- Super Ordinary Joe, a short HTML5 upgrade / platformer comedy game released on Kongregate.

I'm now working on Death Chron with Crescent Moon Games. I'm super happy to work with them on a retro 8-bit styled action platformer about driving mechs, which is perfectly in line with what I had already developed so far with Super Ordinary Joe and Project Zeta.

I'm starting this thread to share some of my hard earned wisdom with the community!

But let's start with some cool pictures :

SplashScreenA.png
Splash Screen

WorldMapA.png
World Map

SnowLevelA.png
Snow Level

SnowLevelB.png
Snow Level
 
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 :
entry01_lizardBehavior.gif

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 :

entry01_lizardFullCode.png

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.

entry01_stateObjectCreate.png
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"?
  1. First, "state_total" is reset to 0 every step.
  2. Then, "state_counter" is increased by one during the "End Step" event.
  3. 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 :
entry01_stateSetAnimBlurred.png
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!
 
Quick word on how I manage animations in my stateObject. I'm using spritesheets so I need to know a few values : at which frame to start, which to end, is the animation looping or not and what speed is it playing at?

I use the function below to set the values. By default it will loop at image_speed = 1 so I don't have to pass these values every time. If I pass only one value (start frame), it will set the end frame to be the same value which will play a still frame.

GML:
/// @description setAnim();
/// @param first_frame
/// @param [last_frame]:first_frame
/// @param [loop]:true
/// @param [spd]:1
function setAnim(first_frame)
{
    var last_frame = first_frame;
    var loop = true;
    var spd = 1;
   
    if(argument_count > 1)  last_frame = argument[1];
    if(argument_count > 2)  loop = argument[2];
    if(argument_count > 3)  spd = argument[3];
   
    anim_start = first_frame;
    anim_end = last_frame;
    anim_loop = loop;
    target_image_speed = spd;
}
Then to actually use these values I run this little function every step :
GML:
function manageAnimations()
{
    image_speed = target_image_speed;
    if(image_speed >= 0)
    {
        if(!anim_loop && floor(image_index) == anim_end)                image_index = anim_end;
        else if(image_index < anim_start || floor(image_index) > anim_end)    image_index = anim_start;
    }
    else
    {
        if(!anim_loop && floor(image_index) <= anim_start)                image_index = anim_start;
        else if(image_index < anim_start || floor(image_index) > anim_end)    image_index = anim_end;
    }
}
It will play the defined range of animations at defined speed, including negative speed. It handles looping or not (if it doesn't loop it will stay still on the last frame). I keep the image_speed in a "target_image_speed" var as the image_speed is set to 0 when pausing the stateObject.
 
Last edited:
Couldn't find a better way to share a gif than embedding a tweet!
As the tweet mentions, I'm going through a lot of content creation, building enemies and making temp graphics until the artist can hop back on it and make it pretty!

So here's a new boss fight. Basic behavior is there, the fight is already fun. My approach right now is to create content fast and dirty and come back later to polish and make better. So I'll certainly add another attack and / or behavior to this boss but for now I can consider it functional.

 
Working on new bosses. I'm not the artist on the project so the way I work is to make a crappy programmer art version as quickly as possible in order to nail down the behavior and the artist will go over to reskin it later.
It doesn't make for the most impressive shares but it allows me to move forward swiftly.

Here is the Ice Gorilla!
 
I like the look of this game a lot. The limited palette really helps sell it as retro. I'm kinda trying to do the same thing.

I noticed the screenshots are 240px tall. Does that create problems? My game is 270 tall since that divides by 1080 evenly. I wish I could do 240 but I don't think it would scale right.
 
Thanks for the comments! The parts that look good are by great pixel artist Thomas McCloskey, using a slightly modified NES palette. The rest is programmer art by me. :D Looking very much forward to having Thomas make it all look good!

Resolution is 432x240. It's not exactly 16:9 since I rounded width and height to closest 16px (tile size) but close enough. I didn't encountered problems with scaling.
 
Top