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.

Auto-generated description: A game development interface with a sprite and code editor is shown, from a software environment like Godot.

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:

  1. How do I get the platform starting point?
  2. How can I actually respawn the platform?

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.