Godot/GDScript 网格运动补间

Godot/GDScript Grid Movement Tweening

所以我一直在使用 KidsCanCode 的 Godot tutorials/documentation 来帮助我为我正在从事的项目创建一个类似于 Pokemon 的基于网格的运动。出于所有意图和目的,我想创建一个尽可能接近早期手持口袋妖怪游戏中的运动系统。

在开始之前我想补充两件事;第一,我越来越喜欢 KidsCanCode 尝试教授基于网格的运动的方式,因此虽然其他编码方式可能更简单,例如可以在视频中找到的那些方式,例如这个 (https://www.youtube.com/watch?v=jSv5sGpnFso),我想硬着头皮坚持这种编码方法......当你阅读代码时你会明白我的意思。最后,我想补充一点,我之前有过这段代码!自从它上次运行以来我实际上没有对代码进行任何更改,但是,由于某种原因它似乎不再起作用,我不确定这是否是由于 Godot 更新所致,但希望有人可以帮助我那。

所以首先,这是我的player scene node tree。其中最重要的部分是 RayCast2D 和 Tween 节点。

这是我的主 Area2D Player 节点代码:

extends Area2D

const tile_size = 16
export var speed = 5

var inputs = { "ui_right": Vector2.RIGHT,
            "ui_left": Vector2.LEFT,
            "ui_up": Vector2.UP,
            "ui_down": Vector2.DOWN }

func _ready():
    position = position.snapped(Vector2.ONE * tile_size/2)

func _unhandled_input(event):
    if $Tween.is_active():
        return
    for dir in inputs.keys():
        if event.is_action_pressed(dir):
            move(inputs[dir])

func move(dir):
    $RayCast2D.cast_to = inputs[dir] * tile_size
    $RayCast2D.force_raycast_update()
    if !$RayCast2D.is_colliding():
        move_tween(dir)

func move_tween(dir):
    $Tween.interpolate_property(self, "position", position,
        position + inputs[dir] * tile_size, 1.0/speed, Tween.TRANS_SINE, Tween.EASE_IN_OUT)
    $Tween.start()

为了快速解释,func _ready(): 将玩家捕捉到网格。 func _unhandled_input(event): 然后检查是否出现 Tween,如果没有出现,则调用 func move(dir)。此函数对给定方向输入进行光线投射,强制进行光线投射更新,如果给定方向上没有静态物体,则调用 func move_tween(dir)。最后一个函数处理给定方向的补间插值并启动补间过程。差不多就是这样。再一次,这曾经工作得很好。

但是,现在当我尝试 运行 这个时,我得到一个错误“无效的获取索引'(0, 1)'(基于:'Dictionary')”其中“(0, 1)" 根据我在游戏 运行ning 时尝试移动的方向而变化。

在 Debugger dock 的 Stack Frames 下面,它在“22 - at function; move”$RayCast2D.cast_to = inputs[dir] * tile_size 和“19 - at function: _unhandled_input”move(inputs[dir]).

网站上的代码只有 (dir) 而不是 (inputs[dir])。但这样做只会给我另一个错误。如果比我聪明的人知道发生了什么,我将非常感谢任何和所有见解。谢谢!

了解问题

好吧,让我们看看。变量 inputs 有你的字典:

var inputs = { "ui_right": Vector2.RIGHT,
            "ui_left": Vector2.LEFT,
            "ui_up": Vector2.UP,
            "ui_down": Vector2.DOWN }

键是String,值是Vector2

因此,这里:

    for dir in inputs.keys():
        if event.is_action_pressed(dir):
            move(inputs[dir])

变量 dir 将成为 String。这正是 is_action_pressed 所需要的,所以这是正确的。

inputs[dir] 将成为 Vector2。这意味着在 move 你得到一个 Vector2 作为参数。

现在,在 move 你说::

func move(dir):
    $RayCast2D.cast_to = inputs[dir] * tile_size

但是记住你传递的参数是一个Vector2,而input的键都是String。所以这里失败了:inputs[dir].


类似问题预警

使用类型可以帮助您及早发现此类问题。遗憾的是,在 Godot 3.x 中无法指定 Dictionary.

的键和值

可以说您可以使用 C# 并使用 System.Collections.Generic 中的 .NET Dictionary<TKey,TValue>,这样您就可以指定键和值类型。然而,我们在这里不是在谈论那些字典。

你可以用 GDScript 告诉你的参数是 Vector2:

func move(displacement:Vector2):
    # …

String

func move(dir:String):
    # …

这样当你用错误的参数调用它们时,Godot 可以告诉你。


另一件事会有所帮助。虽然更多的是在纪律方面,就是要保持一致的名字。如果您使用的名称在您的系统中具有具体含义,它们将对您有所帮助。

例如,您这样调用 move

move(inputs[dir])

意思是你传的不叫dir※。但是你 move 定义如下:

func move(dir):
    # …

所以 move 期待你称之为 dir 的东西。当您输入对 move.

的调用时,您会看到

※:我会说你正在传递 inputs 的值之一,所以你传递的是一个 input。或者你可以称它们为 action,因为你在 is_action_pressed 中使用它们。同样,这将以对您有帮助的方式使用名称。


解决问题

我要解决这个问题的方法是仅在 _unhandled_input 中使用 Stringinputs(毕竟,该函数是用来处理输入的)。并从那里开始与 Vector2 合作。这意味着:

  • 如果将来您想要一个不来自其中一个输入的运动,其他方法也很有用。
  • 您没有在 Dictionary 中重复查找。

不可否认,目前这些对您的游戏来说并不是什么大问题。最终你做什么取决于你。然而,请考虑将此方法提交给您考虑。

这是代码(我添加了一些类型注释):

extends Area2D

const tile_size:float = 16
export var speed:float = 5

var inputs = { "ui_right": Vector2.RIGHT,
            "ui_left": Vector2.LEFT,
            "ui_up": Vector2.UP,
            "ui_down": Vector2.DOWN }

func _ready():
    position = position.snapped(Vector2.ONE * tile_size/2)

func _unhandled_input(event:InputEvent) -> void:
    if $Tween.is_active():
        return
    for dir in inputs.keys():
        if event.is_action_pressed(dir):
            move(inputs[dir])

func move(displacement:Vector2) -> void:
    $RayCast2D.cast_to = displacement * tile_size
    $RayCast2D.force_raycast_update()
    if !$RayCast2D.is_colliding():
        move_tween(displacement)

func move_tween(displacement:Vector2) -> void:
    $Tween.interpolate_property(self, "position", position,
        position + displacement * tile_size, 1.0/speed, Tween.TRANS_SINE, Tween.EASE_IN_OUT)
    $Tween.start()

或者你可以用String想出来的,每次都查字典。我相信这就是您的意图。像这样:

extends Area2D

const tile_size:float = 16
export var speed:float = 5

var inputs = { "ui_right": Vector2.RIGHT,
            "ui_left": Vector2.LEFT,
            "ui_up": Vector2.UP,
            "ui_down": Vector2.DOWN }

func _ready():
    position = position.snapped(Vector2.ONE * tile_size/2)

func _unhandled_input(event:InputEvent) -> void:
    if $Tween.is_active():
        return
    for dir in inputs.keys():
        if event.is_action_pressed(dir):
            move(dir)

func move(dir:String) -> void:
    $RayCast2D.cast_to = input[dir] * tile_size
    $RayCast2D.force_raycast_update()
    if !$RayCast2D.is_colliding():
        move_tween(dir)

func move_tween(dir:String) -> void:
    $Tween.interpolate_property(self, "position", position,
        position + input[dir] * tile_size, 1.0/speed, Tween.TRANS_SINE, Tween.EASE_IN_OUT)
    $Tween.start()

注意这里 _unhandled_input 正在将 dir 传递给 movemovedir 传递给 move_tween 的方式相同。