Cascade - Devlog


Here is a breakdown of the thoughts and experiences that went into my game submission.

Theme

For the "Breaking the Loop" idea, I first considered the time-travel trope of interacting with your past to manipulate the present. While this works in movies where you can paper over logical holes, it's more difficult in a game with hard-coded logic. I did not have the time or mental wherewithal to figure that out. 

However, it led to the idea of having to deal with past versions of yourself. These versions would be destructive but "dumb" in that they would only mimic your past actions. You would have to find ways to change the level or set traps to destroy them or even use them to your advantage.

At first these were killer clones, and you had to turn off their cloning machine. But a few things bugged me:

  • why would there be cloning machines all over the place?
  • why do my clones want to kill me?
  • if they are copies of myself, why would they blindly follow what I did, even moving straight into hazards?

So eventually this changed into an after-images idea: the "astral phases" are not sentient beings, but copies made of exotic matter (or whatever sci-fi concept works). This feels more "loopy" to me to invoke the idea of resonance, periodicity, waves, etc. since it's a runaway natural phenomenon.

This also helps to justify making the copies look different from the player. Whereas a clone would be expected to be an indistinguishable copy of myself, the astral phase can be a spooky negative of the player.

I don't mean to give the impression that there is a lot of depth to the game's world. These are the ideas that automatically occurred to me while I was putting the game together. Writing this description of the theme actually took longer than generating the idea. I only want to explain what the intent was so you can look at the game and say "Ohhh that's what it's supposed to be."

Development

This is the first complete-ish game I have ever made. Every now and then I play around with Godot, but it's not something I am fluent with. Software development is orthogonal to my day job (I'm a lawyer).

Typically what happens is that I get an idea for a cool game mechanic. Then I fire up Godot and dive into implementing that particular mechanic. I don't have the eye for writing code in a modular or maintainable way. I just vomit out code to get the "cool" idea working. Only when it is working does it occur to me that I will need a larger game structure around the mechanic. By this point the code has spread out in a fungal way, and I have already forgotten what half of it does. Putting it into a coherent game structure is then like cleaning up, well, vomit.

Then I lose interest in the game until the next cool idea occurs to me.

And this game started the same way. :D

Making the Clones

Instead of learning from past mistakes, I jumped right into making the cool mechanic: the clones that mimic your past movements.

My first idea was to have a global array that would log the movements of the player. Each row would have two items: the action to take, which is a character corresponding to the action ("L", "R", "J" or "I" for idle), and the duration of the action. I use the player's actions instead of the player's path because the whole idea is that the copies will take a different path using the same actions. The player and each of the clones had a Timer. When the player did an action the time elapsed on the timer would be inserted into the array along with the corresponding action.

Don't do this

This was a bad idea. I noticed that the clones were not reliably following the player's path at all. The culprit seems to be that Godot's Timers are not reliable for times "lower than roughly 0.05 seconds". Since the player makes so many short erratic movements, this just doesn't work.

This approach had further flaws:

  • complexity - the Timer for each character had to be started and restarted precisely and there was an asymmetry in how the player's Timer worked vs the copies' Timers
  • incoherence - the timer concept does not match with what is going on. If a character jumps, that is instantaneous. But you cannot have a timer with 0 seconds. I had to manually check if a jump was next in the array and advance the array to the next move, again adding complexity
  • added overhead - since everyone was using timers, I had to put many redundant entries in the array since the player's timer would keep triggering

I am sure you real developers already know what would have been a better approach, which is just to use a shared clock. The improved version stores the time using get_ticks_usec() with the corresponding action as before. The clones then only need to apply an offset and check the same function to see if it is time to perform the next action.

This worked surprisingly well. I could start playing around with different puzzle pieces, and pretty soon saw it coming together the way I had imagined:

Unfortunately the seesaw did not make it into the final cut.

Leveling Up

"Well well well," a familiar voice said in my head, "this all works in your little toy level but how are you going to put this into an actual game?" Oh no - I had done it again. I had slapped together the different pieces and all of it was oriented to this sandbox level. None of it was made to work in a bigger game loop. My motivation evaporated. I went off to stew.

The issue that my head can't wrap around is how to implement the big meta game stuff to run the individual levels without repeating the big game logic over and over. Is this an inheritance thing or a composition thing? I've looked at game templates from others but it's always unclear how they intend to do this at scale.

After stewing a couple days I came up with an idea. Every level has some common elements: (a) the player, (b) the spawner, (c) the switch for the spawn, and (d) an exit portal. They are always present and function the same way. It is just a matter of resetting and repositioning them between levels. So the solution is that level scenes use what I called "stand-ins" for the universal elements. These are Marker2Ds with a Sprite2D attached. They can be dropped into a level so the game knows where to position them when the level is loaded. This way I can create levels without ever thinking about the larger game logic.

As you can see below, I only need to put in the stand-ins and then I can add the level-specific elements without ever thinking about the bigger game loop:


The universal elements are sitting just off-screen when the game starts. You just can't see them:


I assume there is a better way to do all this, but this works for me and my little non-developer brain.

Gameplay

I wanted the player to explore a bit how to solve the puzzles so I don't provide explanations of what elements do. I feel like the levels are so condensed that this is not asking too much. One thing that was not ideal is that I have to put the spawner switch far away to avoid that the player simply sprints forward to turn it off before the first copy is spawned. I am not sure if this felt contrived to the players.

As it is a platformer, the controls are important. Nevertheless, the character controller uses the stock script that Godot provides. I would eventually want to implement some kind of coyote time but this gets awkward since it would also need to be implemented for the clones. Coyote-time isn't too noticeable when you are controlling yourself but it can appear buggy if you watch an NPC using it.

Generally there is a lot to explore in how literally the clones should copy the player actions. Imagine the player is falling and presses "jump". Nothing will happen since he is in the air. But what if the clone is on the ground at the corresponding time: should it jump?

Something else, which you can see in the first gif above is that I originally had the player path shown. This was initially for debugging but I could imagine this being a helpful way to keep track of what happens to the clones. I was going to have a checkbox in the menu to toggle showing the player path, but that did not happen (see below).

The gameplay mechanic I am most unsure of is whether two clones should destroy each other when they touch. As you see in level 3, this makes it possible to complete a level without ever using traps. So potentially any level can be solved this way instead of the intended solution. I have to make some more levels to decide which direction I want to go.

Art

Art is hard. In comparison to programming it is tedious and overwhelmingly open-ended. While I would have liked to make my own assets I am too aware of my limitations in this department. I opted for pre-made assets and tried to use them generically enough that it did not come across as me trying to obscure the game's shortcomings with attractive graphics.

All the same, I am a little proud of the conveyor belt, which I created through much precision cropping and copying in MS Paint.

For the "astral phase" doppelgängers I was imagining some spooky Lovecraftian vibes, with lots of glowing and pulsating. But then I had a much easier idea:

Crispy perfection


Meanwhile in the Real World, Tragedy Strikes

I was not able to give the game as much energy as I had hoped. My wife and two of my kids were sick throughout the week, and I did not have the courage to tell my wife I needed to work on my crappy game instead of helping to clean up the (this time literal) vomit. Add to that a full-time job and - worst of all - the death of our beloved chicken Oskar. But damn it, I wanted to finish this thing!


Crash Landing

In the few hours before the deadline I still had some things to do. I needed to finish the menu, create a pause menu, and make the levels more uniform in how they looked. At that point they were different sizes and not consistent about what tiles were used. I peeked at the upload process for itch and saw this:

Probably not the best resolution for your game

Ah, I thought, 640 x 360 must be the best resolution to use on itch, since that is the default. So without any further thought I set about resizing my project to those dimensions and cramming the levels into that space.

In parallel, I was trying to get the UI working. I wanted a pause menu and checkboxes for toggling the player path and the annoying music. And it just did not work. I don't usually do anything with Godot's UI nodes, but I assumed callbacks and all that would work exactly the same way. They did not. Maybe I was in too much of a freakout mode to figure things out, but I ended up chopping off these pieces. Oh well.

I uploaded it and saw, a little dismayed, that it was so tiny looking. C'mon itch - I doubt anyone uses that resolution. Why make it the default when dummies like me are on here? Yes, obviously it's my fault for not taking the 2 seconds to check that it would look okay. Sorry to anyone who had to squint to see the game!

But hey, it got done, and I am happy that it's out there. I hope you enjoyed it. Thank you very much for all the feedback. And thank you to GoedWare for hosting the jam. You are wonderful.

Files

index.zip Play in browser
40 days ago

Leave a comment

Log in with itch.io to leave a comment.