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
中使用 String
和 inputs
(毕竟,该函数是用来处理输入的)。并从那里开始与 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
传递给 move
。 move
将 dir
传递给 move_tween
的方式相同。
所以我一直在使用 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
中使用 String
和 inputs
(毕竟,该函数是用来处理输入的)。并从那里开始与 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
传递给 move
。 move
将 dir
传递给 move_tween
的方式相同。