Videos
- How do I get the platform starting point?
- How can I actually respawn the platform?
Made some more progress on that Godot game. I haven’t gotten any further with the first level of world 2, so I’ve been spending much of my time making mechanics. One of them was the slow moving “level 2” mechanic that I stole wholesale from Super Mario World. That mechanic, despite it being frustrating to speed-runners, was always slightly interesting to me. To have areas of a level become accessible or hazardous just due to a layer of it oscillate up and down, it promised to make for some interesting timing challenges. At least in theory.
I decided to put that theory to the test, and start work on one the later levels. And despite being a little skeptical about whether the mechanic could carry through a level on it’s own, I came up with one that I’m reasonably happy with. The mechanic is introduce slowly, and in a rather non-threatening way, proving the player the means to get to higher ground. This leads into the second half, which will be a long underground section which will ramp up the difficulty by introducing the risk of getting crushed or missing platforms.
To compliment this is a new enemy that rushes the player. The player cannot do anything to defeat this enemy: combat is not really a thing in this game. All they could do is evade it before the enemy gives up. I am reusing the same “green slime” sprite for this but I’m hoping that the differing animations provide some hints of how this enemy’s behaviour differs from that of the simpler one.
Finally, it was time to consider checkpoints. While the first few levels were too short to justify adding them in, this one is just that bit too long without one. And given the difficulty ramp-up in the second half, having the player go through the slower first half every time they died would probably lead to frustration. So checkpoints are now a thing. They’re not free — costing 5 coins to activate — and they are sometimes mandatory, blocking the player from progressing until they pay the toll. But I think their presence helps with eliminating the areas of the level that would just be boring to play through again and again.
So yeah, I’m quite happy with this level. And I’m also happy in realising that I’m not bound to building this game in the same progression that the player will experience it. It’s better sometimes to just work on the areas that you’re ready to. I mean, it’s sounds obvious to say that now. Not sure why it took me this long to actually do so.
Spent some time over the last few days working on that Godot game, mainly building new mechanics. This evening I started working on an interceptor, something that would jump out of the quicksand in order to disrupt the player’s jump. Here’s an example of how they look in the test bed:
And yeah, they’re pretty much a carbon-copy of the Podoboos from Mario. But I think there’s a reason they’re still making an appearance in games, years after their debut in Super Mario Brothers. They’re quite a versatile enemy, making jumping challenges a bit more interesting than just seeing whether the player the clear a gap. Plus they’re reasonably easy to make.
Another mechanic taken from Mario was a switch that revealed coins and tiles for a limited time. Hit it once and the child nodes of this “timed_limited_visible” scene are displayed and activated for 10 seconds, before they disappear again:
Much like the blue P switch this mechanic takes inspiration from, the switch can only be activated once. So it may be only useful for bonuses and areas the player can afford to miss.
I had to do some special handling for nested TileMap
nodes, since the player could still collide with them even when they’re hidden. How I solved this was nothing too spectacular: basically I just walk the child tree looking for TileMap
instances, and when encountering one, just enabling or disabling the first layer:
func _show_and_activate_children():
visible = true
process_mode = Node.PROCESS_MODE_INHERIT
for tm in find_children("*", "TileMap", false):
tm.set_layer_enabled(0, true)
func _hide_and_deactivate_children():
visible = false
process_mode = Node.PROCESS_MODE_DISABLED
for tm in find_children("*", "TileMap", false):
tm.set_layer_enabled(0, false)
Building these elements was fun, but the main problem is that I’m struggling to come up with a centrepiece mechanic for level 2-1, something that defines the level in some way. I have an idea for level 2-2 — this world is set in a desert so I’m hoping to introduce a thirst mechanic — but level 2-1 I’m hoping to keep relatively plain so as to avoid overwhelming the player with too many new things. The fear is to avoid making it little more than what the player encountered in world 1: a series of jumping puzzles over pits. Sure, that’s pretty much the entire game in a way, but some variety would be nice.
I’m hoping one of these mechanics could help here. I guess I’ll find one once I’ve start seriously building the level.
Started working on world 2, and one of the main mechanics of this world: quicksand. It won’t kill the player directly, but it will make it difficult for them to manoeuvre, and getting too low could cause death. Might be one of the more annoying mechanics in the game, but that’s kind of the point.
A bit more on Godot this evening, mainly working on pausing the game, and the end-of-level sequence. Have got something pretty close to what I was looking for: a very Mario-esc sequence where the player enters a castle, it start auto-walking the character, and the level stats show up and “spin” for a bit. Not too bad, although I may need to adjust the timing and camera a little to keep the stats from being unreadable.
Adventures In Godot: Respawning A Falling Platform
My taste of going through a Godot tutorial last week has got me wanting more, so I’ve set about building a game with it. Thanks to my limited art skills, I’m using the same asset pack that was used in the video, although I am planning to add a bit of my own here and there.
But it’s the new mechanics I enjoy working on, such as adding falling platforms. If you’ve played any platformer, you know what these look like: platforms that are suspended in air, until the player lands on them, at which point gravity takes a hold and they start falling, usually into a pit killing the player in the process:
The platforms are built as a CharacterBody2D
with an Area2D
that will detect when a player enters the collision shape. When they do, a script will run which will have the platform “slipping” for a second, before the gravity is turned on and the platform falls under it’s own weight. The whole thing is bundled as a reusable scene which I could drag into the level I’m working on.

I got the basics of this working reasonably quickly, yet today, I had a devil of a time going beyond that. The issue was that I wanted the platform to respawn after it fell off the map, so that the player wouldn’t get soft-locked at at an area where the platform was needed to escape. After a false-start trying to reposition the platform at it’s starting point after it fell, I figured it was just easier to respawn the platform when the old one was removed. To do this I had to solve two problems:
Getting The Starting Point
A Node2D
object has the property global_position, which returns the position of the object based on the world coordinates. However, it seems like this position is not correct when the _init function of the attached script is called. I suspect this is because this function is called before the platform is added to the scene tree, when the final world coordinates are known.
Fortunately, there exists the _ready
notification, which is invoked when the node is added to the scene tree. After some experimentation, I managed to confirm that global_position
properly was correct. So tracking the starting point is a simple as storing that value in a variable:
var init_global_position = null
func _ready():
init_global_position = global_position
Another option is to use the _enter_tree()
notification. From the documentation, it looks like either would probably work here, with the only difference being the order in which this notification is invoked on parents and children (_enter_tree
is called by the parent first, whereas _ready
is called by the children first).
Respawning The Platform
The next trick was finding out how to respawn the platform. The usual technique for doing so, based on the results of my web searching, is to load the platform scene, instantiate a new instance of it, and added it to the scene tree.
@onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
func respawn():
var dup = FallingPlatform.instantiate()
add_child(dup)
Many of the examples I’ve seen online added the new scene node as a child of the current node. This wouldn’t work for me as I wanted to free the current node at the same time, and doing so would free the newly instantiated child. The fix for this was easy enough: I just added the new node as a child of the current scene.
@onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
func respawn():
var dup = FallingPlatform.instantiate()
get_tree().current_scene.add_child(dup)
queue_free()
I still had to reposition the new node to the spawn point. Fortunately the global_position
property is also settable, so it was simply a matter of setting that property before adding it to the tree (this is so that it’s correct when the newly instantiated node receives the _ready
notification).
@onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
func respawn():
var dup = FallingPlatform.instantiate()
dup.global_position = init_global_position
get_tree().current_scene.add_child(dup)
queue_free()
This spawned the platform at the desired positioned, but there was a huge problem: when the player jumped on the newly spawn platform, it wouldn’t fall. The Area2D
connection was not invoking the script to turn on the gravity:
It took me a while to figured out what was going on, but I came to the conclusion that the packed scene was loading properly, but without the script attached. Turns out a Script is a resource separate from the scene, and can be loaded and attached to an object via the set_script method:
@onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
@onready var FallingPlatformScript = preload("res://scripts/falling_platform.gd")
func respawn():
var dup = FallingPlatform.instantiate()
dup.set_script(FallingPlatformScript)
dup.global_position = init_global_position
get_tree().current_scene.add_child(dup)
queue_free()
Finally, after figuring all this out, I was able to spawn a new falling platform, have it positioned at the starting position of the old platform, and react to the player standing on it.
The time it took to work this out is actually a little surprising. I was expecting others to run into the same problem I was facing, where they were trying to instantiate a scene only to have the scripts not do anything. Yet it took me 45 minutes of web searching through Stack Overflow and forum posts that didn’t solve my problem. It was only after a bit of experimentation and print-debugging on my own that I realised that I actually had to attached the script after instantiating the node.
To be fair, I will attribute some of this to not understanding the problem at first: I actually thought the Area2D
wasn’t actually being instantiated at all. Yet not one of the Stack Overflow answers or forum post floated the possibility that the script wasn’t being loaded alongside the scene. This does suggest to me that my approach may not be optimal. There does exist a “Local to Scene” switch in the script inspector that could help, although turning it on doesn’t seem to do much. But surely there must be some way to instantiate the script alongside the scene.
Anyway, that’s for later. For now, I’m happy that I’ve got something that works.
It lives!
Prototyped a game I had in mind, sort of a 2D Sokoban-like thing, where you control a robot with a retractable pushing arm that is to push gems to a “receiver” tile. Not entirely sure if it’s fun enough to actually build.
Used PixiJS to build it. Not a bad framework.
So sorry to hear about the loss of @merlinmann's pet lizard, which I just learnt is a central bearded dragon (they're good looking lizards). I didn't include it in the clip but he had some really nice things to say about it.
Request for any open-source projects that want to put banner ads on their site: please consider hard-coding the height of your banner to prevent the ad from reflowing the page. Otherwise, it may have an impact on the experience of those reading your docs.
Oh, that’s nice. Looks like Obsidian allows you to set the starting ordinal for numbered lists.
This was something I wish vanilla Markdown had for a while, so it’s good to see at least one Markdown editor embracing this.
Just heard the name for John's new app. Must say I kinda like it. It grows on you. No spoilers (except in the clip), but I do appreciate that it follows a similar vein to the crazy names I came up with. It just does it so much better.
More fun today working on Blogging Tools. Finished a feature for uploading larger videos to object storage so they can be added to a post using the standard video tag, as opposed to an embedded video player. If you see the screencast below, that means it’s working.
That’s it! I’m never going to use a framework that uses Webpack or installs more than 5 Node dev dependencies. Why? Because every time I check it out to work on it, all these dependencies break, and I’m left to spend hours updating them.
Never again! 😡
I wonder if we could convince Ben to order another run of Stratechery mugs shaped like the one he drinks from. I really like my Stratechery mug — it's one I often use — yet the mug he describes here is intriguing.
In other building-small-things-for-myself news, I spent a bit of time this morning on the image processor for Blogging Tools. The big new change was adding support for working with multiple source images, instead of just one. This made way for a new “Phone Shot” processor, which arranges multiple screenshots of phone apps in a row, while also downscaling them and proving some quick crops and distribution options.
This should reduce the vertical size of mobile app screenshots I post here, something that’s been bothering me a little.
Agree with @manton here. I used to be quite religious about Test Driven Development, but I'm less so nowadays. For the simple stuff, it works great; but for anything larger, you know little about how your going to build something until after you build it. Only then should you lock it in with tests.
Listening to this part of HV got me wondering if the secret to punctual trains is just a whole lot of them. You’re less likely to do something to delay a train — like hold the doors open — if you know the next one’s only a few minutes away, and will arrive on time. One builds on the other.
Just to add one point of anecdata to Ben’s experience: I’ve never been happy with the performance of the mobile radio of Pixel phones. You can be in the middle of the CBD and still experience weird dropouts while using mobile data. It’s quite frustrating.
While on my walk, I stopped briefly to clear a stone from my shoe and this purple swamphen (or pūkeko for our Kiwi friends) came up quite close to me. Not sure why. Looking for food maybe?