在 KinematicBody2D 上调用 get_position 会导致 get_global_transform 错误

Calling get_position on KinematicBody2D causes a get_global_transform error

总结

我正在尝试使用 Gut for my player in but calling get_position() on the player (a KinematicBody2D) 编写单元测试导致以下错误:

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.

预期

我原以为在 _physics_process() 之后我可以得到我的 player 的更新 position 但它 returns Vector2(0, 0).

资源

这是我第一次使用 GDScript,所以我一直在混合使用以下资源来尝试编写第一个测试:

失败的解决方案

我想我可能需要让 player 成为其他东西的 child,但我不确定如何在我的测试中做到这一点。

我尝试在 Gut issues 中搜索如何在测试中使用 add_child(),但找不到答案。

我也试过将 player 变成 Area2D 的 child 但这似乎也不起作用:

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

代码

这是从命令行调用 gut 时导致错误的代码:

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)

我发现了一些对我有用的东西(我第一次错过这个有点生气)。

如我所料,player 需要是另一个 Node 的 child。

test/unit/actors/test_player.gd 中定义 player 变量后,可以将其添加为测试本身的 child 使用:

self.add_child_autofree(player)

完整的单元测试如下:

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))

错误告诉你 !is_inside_tree()。问题不仅仅在于节点没有 parent。问题是它不在 SceneTree.

您可以创建自己的 SceneTree 实例。您甚至可以扩展它。例如,这会起作用:

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

现在,关于 Gut……您的测试将 运行 在 SceneTree 内进行。因此,您可以将要测试的节点添加为 children。请记住进行适当的拆卸(删除节点)。因为 Gut 不会为你做这件事,如果 children 节点从一个测试到另一个测试徘徊,那么它们就不再是单元了。

附录:你可以使用add_child_autofree添加一个child,并在测试结束后立即将其排队等待释放。


答案的其余部分是我回顾 Gut 运行s 如何测试以确认它们 运行 在 SceneTree 中。

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

反过来,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)

那里the_script是一个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)

将其添加为 child(共 gud.gd)。

之后 _test_the_scripts 将进入循环(here) and call _run_test or _run_parameterized_test(调用 _run_test)以实际 运行 测试。并且 _run_test 将使用 call 在实例上 运行 测试方法(有额外的步骤)。

万岁!您的测试将在自定义 SceneTree.

中 运行ning