Home > front end >  Calling get_position on KinematicBody2D causes a get_global_transform error
Calling get_position on KinematicBody2D causes a get_global_transform error

Time:09-17

Summary

I'm trying write a unit test using Gut for my player in but calling get_position() on the player (a KinematicBody2D) is causing the following error:

ERROR: get_global_transform: Condition "!is_inside_tree()" is true. Returned: get_transform()
   At: scene/2d/canvas_item.cpp:467.
ERROR: body_test_motion: Condition "!body->get_space()" is true. Returned: false
   At: servers/physics_2d/physics_2d_server_sw.cpp:1046.
ERROR: get_global_transform: Condition "!is_inside_tree()" is true. Returned: get_transform()
   At: scene/2d/canvas_item.cpp:467.
ERROR: body_test_ray_separation: Condition "!body->get_space()" is true. Returned: false
   At: servers/physics_2d/physics_2d_server_sw.cpp:1058.

Expectation

I was expecting that after _physics_process() I would be able to get the updated position of my player but it returns Vector2(0, 0).

Resources

This is my first time using GDScript so I have been using a mixture of the following resources in an attempt to write this first test:

Failed solutions

I think I may need to make the player a child of something else but I'm unsure how to do this within my test.

I tried searching the Gut issues on how to use add_child() within a test but I couldn't find an answer.

I also tried making the player a child of an Area2D but that didn't seem to work either:

var player := Player.new(input)
var area := Area2D.new()
area.add_child(player)

Code

This is the code that is causing the error when calling gut from the command line:

test/unit/actors/test_player.gd

extends "res://addons/gut/test.gd"

func test_can_create_player() -> void:
    var input = MockInput.new()
    var player := Player.new(input)
    
    assert_not_null(player)

func test_can_move_player_up() -> void:
    var input = MockInput.new()
    input.press("ui_up")

    var player := Player.new(input)

    simulate(player, 1, .1)
    assert_eq(player.get_position(), Vector2(200, 0))

test/mock_input.gd

class_name MockInput

var _pressed: Dictionary = {}

func press(key: String) -> void:
    _pressed[key] = true

func release(key: String) -> void:
    _pressed[key] = false

func is_action_pressed(key: String) -> bool:
    if _pressed.has(key):
        return _pressed.get(key)

    return false

entities/actors/player.gd

extends KinematicBody2D

class_name Player

export var speed: int = 200

var _input: Object = Input
var _velocity: Vector2 = Vector2()

func _init(input: Object = Input):
    _input = input

func _physics_process(_delta: float) -> void:
    _velocity = Vector2()
    
    if _input.is_action_pressed("ui_right"):
        _velocity.x  = 1
    if _input.is_action_pressed("ui_left"):
        _velocity.x -= 1
    if _input.is_action_pressed("ui_down"):
        _velocity.y  = 1
    if _input.is_action_pressed("ui_up"):
        _velocity.y -= 1

    _velocity = _velocity.normalized() * speed
    _velocity = move_and_slide(_velocity)

CodePudding user response:

The error is telling you !is_inside_tree(). The problem is not -only- that the node does not have a parent. The problem is that it is not inside a SceneTree.

You can create your own instance of SceneTree. You can even extend it. For example, this would work:

var node := KinematicBody2D.new()
    
var tree := SceneTree.new()
tree.get_root().add_child(node)
yield(tree, "idle_frame")
print(node.get_global_transform())

Now, about Gut… Your tests will be running inside a SceneTree. Thus, you can add the nodes you want to test as children. Just remember to do proper teardown (remove the nodes). Because Gut will not do it for you, and if children nodes linger from one test to another, then they aren't units anymore.

Addendum: You can use add_child_autofree to add a child, and also queue it for freeing right after the test ends.


The rest of the answer is me going over how Gut runs tests to confirm they run inside a SceneTree.

The file gut_cmdln.gd is actually an extended SceneTree. It will load gut.gd (here) and add it as a node to itself (here).

In turn, gut.gd is in charge of running the tests, which is handled by _test_the_scripts. What I want you to look is this:

        var test_script = the_script.get_new()
        var script_result = null
        _setup_script(test_script)

There the_script is a TestScript object. The get_new call will return an instance. And _setup_script

func _setup_script(test_script):
    test_script.gut = self
    test_script.set_logger(_lgr)
    add_child(test_script)
    _test_script_objects.append(test_script)

Will add it as child (of gud.gd).

Afterwards _test_the_scripts will go into a loop (here) and call _run_test or _run_parameterized_test (which calls _run_test) to actually run the test. And _run_test will use call on the instance to run the test method (with extra steps).

Huzza! Your tests will be running in a custom SceneTree.

CodePudding user response:

I found something that worked for my purposes (I'm a little bit annoyed that I missed this the first time).

As I suspected, the player needs to be a child of another Node.

In the test/unit/actors/test_player.gd after the player variable has been defined it can be added as a child of the test itself using:

self.add_child_autofree(player)

With the complete unit test as the following:

func test_can_move_player_up() -> void:
    var input = MockInput.new()
    input.press("ui_up")

    var player := Player.new(input)
    self.add_child_autofree(player)

    simulate(player, 1, 0)
    assert_eq(player.get_position().round(), Vector2(0, -30))
  • Related