问题

您需要一个以网格模式移动的 2D 角色。

解决方案

基于网格或图块的移动意味着角色的位置受到限制。他们只能站在一个特定的瓷砖上 - 永远不会在两个瓷砖之间。

角色设定

以下是我们将用于播放器的节点:

  • Area2D(“播放器”):使用Area2D意味着我们可以检测到重叠(用于拾取物体或与敌人碰撞)。
    • Sprite:您可以在此处使用精灵表(我们将在下面设置动画)。
    • CollisionShape2D:不要让hitbox太大。由于玩家将站在瓷砖的中心,因此重叠将从中心开始。
    • RayCast2D:用于检查是否可以在给定方向上移动。
    • Tween:用于从瓷砖到瓷砖的插值运动。
    • AnimationPlayer:用于播放角色的行走动画。

将一些输入操作添加到输入映射。在此示例中,我们将使用“上”、“下”、“左”和“右”。

基本动作

我们将从设置逐块移动开始,不使用任何动画或插值。

  1. extends Area2D
  2. var tile_size = 64
  3. var inputs = {"right": Vector2.RIGHT,
  4. "left": Vector2.LEFT,
  5. "up": Vector2.UP,
  6. "down": Vector2.DOWN}

tile_size应设置为匹配您的瓷砖的大小。在较大的项目中,这可以在实例化播放器时由您的主场景设置。在下面的示例中,我们使用 64x64 瓷砖。
字典将inputs输入动作名称映射到方向向量。确保您在此处和输入映射中的名称拼写相同(大写计数!)。

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

snapped()允许我们将位置“四舍五入”到最近的瓷砖增量,并添加半瓷砖数量确保玩家在瓷砖上居中。

  1. func _unhandled_input(event):
  2. for dir in inputs.keys():
  3. if event.is_action_pressed(dir):
  4. move(dir)
  5. func move(dir):
  6. position += inputs[dir] * tile_size

这是实际的运动代码。当输入事件发生时,我们检查四个方向以查看哪个匹配,然后将其传递给以move()更改位置。
基于网格的运动 - 图1

碰撞

现在我们可以添加一些障碍。你可以加StaticBody2Ds 手动添加一些障碍物(启用捕捉以确保它们与网格对齐)或使用 TileMap(定义了碰撞),如下例所示。
我们将使用RayCast2D以确定是否允许移动到下一个图块。

  1. onready var ray = $RayCast2D
  2. func move(dir):
  3. ray.cast_to = inputs[dir] * tile_size
  4. ray.force_raycast_update()
  5. if !ray.is_colliding():
  6. position += inputs[dir] * tile_size

更改光线投射的cast_to属性时,物理引擎不会重新计算其碰撞,直到下一个物理帧。force_raycast_update()让您立即更新光线的状态。如果它没有碰撞,那么我们允许移动。
基于网格的运动 - 图2
另一种常见的方法是使用 4 个单独的光线投射,每个方向一个。

动画运动

最后,我们可以在瓷砖之间插入位置,给运动带来平滑的感觉。我们将使用Tween节点为position属性设置动画。

  1. onready var tween = $Tween
  2. export var speed = 3

添加引用Tween节点和一个变量来设置我们的移动速度。

  1. func _unhandled_input(event):
  2. if tween.is_active():
  3. return
  4. for dir in inputs.keys():
  5. if event.is_action_pressed(dir):
  6. move(dir)

在补间运行时,我们将忽略任何输入。

  1. func move(dir):
  2. ray.cast_to = dir * tile_size
  3. ray.force_raycast_update()
  4. if !ray.is_colliding():
  5. # position += dir * tile_size
  6. move_tween(dir)

删除直接position更改并调用函数来激活补间:

  1. func move_tween(dir):
  2. tween.interpolate_property(self, "position",
  3. position, position + dir * tile_size,
  4. 1.0/speed, Tween.TRANS_SINE, Tween.EASE_IN_OUT)
  5. tween.start()

基于网格的运动 - 图3
尝试不同的补间过渡以获得不同的运动效果。
您可以下载此示例的完整项目:grid_based_movement.zip