Search code examples
godotgdscript

Godot: How to move KinematicBody2D node from one scene to another


I want to move my Player node (KinematicBody2D) from one scene to another. My code successfully moves Node2D node, but fails with KinematicBody2D node.

I have two Location scenes (inheriting from base Location scene) with the following structure:

FirstLocation (inherits Location)
  - YSort
    - Player

In the base Location scene I have two methods:

func add_player(player: Player):
    get_node("YSort").add_child(player)

func remove_player() -> Player:
    var player = get_node("YSort/Player")
    get_node("YSort").remove_child(player)
    return player

In GameWorld scene I store the possible locations inside a dictionary and the moving of the player happens inside change_location() function:

onready var _locations = {
    Location.FOO: $CurrentLocation,
    Location.BAR: load("res://locations/Bar.tscn").instance(),
}

onready var _current_location = $CurrentLocation

func change_location(location: int):
    var player = _current_location.remove_player()

    remove_child(_current_location)
    _current_location = _locations[location]
    add_child(_current_location)

    _current_location.add_player(player)
  • The switching of the location works
  • The moving of the player also works in case the player is plain Node2D.

But when Player is KinematicBody2D then the game simply crashes, giving me no hint as to what's causing the problem.

The code works without crashing when I comment out the last line:

    _current_location.add_player(player)

...but of course then the player simply doesn't get added to the other scene.

  • I verified that the player does get removed from the scene.
  • I tested with a dummy scene (which only contains KinematicBody2D as root node and a simple Sprite as a single child) instead of my actual more complex Player scene to make sure it's not related to any other code I might have in my Player scene. Node2D as root works, KinematicBody2D crashes. Must have been a fluke. Tested again and now both work, so there must be something different in my Player object.
  • I tried adding the Player node as a direct child of Location node (not having a YSort node in the middle) - nope, still crashes.
  • I tried setting the position/global_position of player before/after adding it to new scene - no difference.
  • I'm able to create new Player scene instance and add it to new location.

What might it be in KinematicBody2D that prevents me from moving it from scene to scene?


Found a solution, but I don't know why it works.

I was performing the location change as a response to Area2D.body_entered signal. I triggered my own signal "location_change" and then called the change_location() function in another part of the code in response to it.

Adding a tiny timeout (0.00000001 seconds) before doing the location change solved the issue. However I have no idea why, and I'm pretty sure adding timeouts is not a proper way to solve this problem.


Solution

  • I'm having trouble visualizing the situation. However, given that the problem happens when removing and adding a physics body, and that "body_entered" was involved, and that the solution you found was adding a time out…

    Sounds like the issue was removing the physics body while Godot was still resolving physics response. And then the solution was to wait until the next frame.

    You could wait until the next graphics frame with this:

    yield(get_tree(), "idle_frame")
    

    Or until the next physics frame with this:

    yield(get_tree(), "physics_frame")
    

    Which would be better than some arbitrary small timeout.


    Alternatively, you could also make the signal connection deferred.

    If you are connecting from the UI, there will be an "advanced" toggle in the "Connect a Signal to a Method" dialog. Enabling "advanced" toggle will reveal some extra including making the connection deferred.

    If you are connecting form code, you can accomplish the same thing by passing the CONNECT_DEFERRED flag.