Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

编写玩家代码

在这一课中,我们将添加玩家的动作、动画,并将其设置为检测碰撞。

现在我们需要添加一些内置节点所不具备的功能,因此要添加一个脚本。点击 Player 节点然后点击“附加脚本”按钮:

../../_images/add_script_button.webp

在脚本设置窗口中,你可以维持默认设置。点击“创建”即可:

备注

如果你要创建 C# 脚本或者其他语言的脚本,那就在创建之前在语言下拉菜单中选择语言。

../../_images/attach_node_window.webp

备注

如果这是你第一次接触 GDScript,请在继续之前阅读 脚本语言

首先声明该对象将需要的成员变量:

extends Area2D

@export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.

在第一个变量 speed 上使用 export 关键字,这样我们就可以在“检查器”中设置其值。对于希望能够像节点的内置属性一样进行调整的值,这可能很方便。点击 Player 节点,您将看到该属性现在显示在“检查器”的“Script Variables”(脚本变量)部分。请记住,如果您在此处更改值,它将覆盖脚本中所写的值。

警告

If you're using C#, you need to (re)build the project assemblies whenever you want to see new export variables or signals. This build can be manually triggered by clicking the "Build" button at the top right of the editor.

../../_images/build_dotnet.webp

A manual build can also be triggered from the MSBuild Panel. Click the word "MSBuild" at the bottom of the editor window to reveal the MSBuild Panel, then click the "Build" button.

../../_images/export_variable.webp

当节点进入场景树时,_ready() 函数被调用,这是查看游戏窗口大小的好时机:

func _ready():
    screen_size = get_viewport_rect().size

现在我们可以使用 _process() 函数定义玩家将执行的操作。_process() 在每一帧都被调用,因此我们将使用它来更新我们希望会经常变化的游戏元素。对于玩家而言,我们需要执行以下操作:

  • 检查输入。

  • 沿给定方向移动。

  • 播放合适的动画。

首先,我们需要检查输入——玩家是否正在按键?对于这个游戏,我们有 4 个方向的输入要检查。输入动作在项目设置中的“输入映射”下定义。在这里,您可以定义自定义事件,并为其分配不同的按键、鼠标事件、或者其他输入。对于此游戏,我们将把方向键映射给四个方向。

点击项目 -> 项目设置打开项目设置窗口,然后单击顶部的输入映射选项卡。在顶部栏中键入“move_right”,然后单击“添加”按钮以添加该 move_right 动作。

../../_images/input-mapping-add-action.webp

We need to assign a key to this action. Click the "+" icon on the right, to open the event manager window.

../../_images/input-mapping-add-key.webp

The "Listening for Input..." field should automatically be selected. Press the "right" key on your keyboard, and the menu should look like this now.

../../_images/input-mapping-event-configuration.webp

Select the "ok" button. The "right" key is now associated with the move_right action.

重复这些步骤以再添加三个映射:

  1. move_left 映射到左箭头键。

  2. move_up 映射到向上箭头键。

  3. move_down 映射到向下箭头键。

按键映射选项卡应该看起来类似这样:

../../_images/input-mapping-completed.webp

单击“关闭”按钮关闭项目设置。

备注

我们只将一个键映射到每个输入动作,但你可以将多个键、操纵杆按钮或鼠标按钮映射到同一个输入动作。

您可以使用 Input.is_action_pressed() 来检测是否按下了键, 如果按下会返回 true, 否则返回 false .

func _process(delta):
    var velocity = Vector2.ZERO # The player's movement vector.
    if Input.is_action_pressed("move_right"):
        velocity.x += 1
    if Input.is_action_pressed("move_left"):
        velocity.x -= 1
    if Input.is_action_pressed("move_down"):
        velocity.y += 1
    if Input.is_action_pressed("move_up"):
        velocity.y -= 1

    if velocity.length() > 0:
        velocity = velocity.normalized() * speed
        $AnimatedSprite2D.play()
    else:
        $AnimatedSprite2D.stop()

我们首先将 velocity 设置为 (0, 0)——默认情况下玩家不应该移动。然后我们检查每个输入并从 velocity 中进行加/减以获得总方向。例如,如果您同时按住 ,则生成的 velocity 向量将为 (1, 1)。此时,由于我们同时向水平和垂直两个方向进行移动,玩家斜向移动的速度将会比水平移动要更快

只要对速度进行归一化就可以防止这种情况,也就是将速度的长度设置为 1,然后乘以想要的速度。这样就不会有过快的斜向运动了。

小技巧

如果您以前从未使用过向量数学,或者需要复习,可以在 Godot 中的 向量数学 上查看向量用法的解释。最好了解一下,但对于本教程的其余部分而言,这不是必需的。

We also check whether the player is moving so we can call play() or stop() on the AnimatedSprite2D.

小技巧

$ is shorthand for get_node(). So in the code above, $AnimatedSprite2D.play() is the same as get_node("AnimatedSprite2D").play().

In GDScript, $ returns the node at the relative path from the current node, or returns null if the node is not found. Since AnimatedSprite2D is a child of the current node, we can use $AnimatedSprite2D.

现在我们有了一个运动方向,我们可以更新玩家的位置了。我们也可以使用 clamp() 来防止它离开屏幕。 clamp 一个值意味着将其限制在给定范围内。将以下内容添加到 _process 函数的底部:

position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)

小技巧

_process() 函数的 delta 参数是 帧长度 ——完成上一帧所花费的时间. 使用这个值的话, 可以保证你的移动不会被帧率的变化所影响.

点击“运行场景”(F6,macOS 上为 Cmd + R)并确认您能够在屏幕中沿任一方向移动玩家。

警告

如果在“调试器”面板中出现错误

Attempt to call function 'play' in base 'null instance' on a null instance(尝试调用空实例在基类“空实例”上的“play”函数)

this likely means you spelled the name of the AnimatedSprite2D node wrong. Node names are case-sensitive and $NodeName must match the name you see in the scene tree.

选择动画

Now that the player can move, we need to change which animation the AnimatedSprite2D is playing based on its direction. We have the "walk" animation, which shows the player walking to the right. This animation should be flipped horizontally using the flip_h property for left movement. We also have the "up" animation, which should be flipped vertically with flip_v for downward movement. Let's place this code at the end of the _process() function:

if velocity.x != 0:
    $AnimatedSprite2D.animation = "walk"
    $AnimatedSprite2D.flip_v = false
    # See the note below about boolean assignment.
    $AnimatedSprite2D.flip_h = velocity.x < 0
elif velocity.y != 0:
    $AnimatedSprite2D.animation = "up"
    $AnimatedSprite2D.flip_v = velocity.y > 0

备注

上面代码中的布尔赋值是程序员常用的缩写. 在做布尔比较同时, 同时可 一个布尔值. 参考这段代码与上面的单行布尔赋值:

if velocity.x < 0:
    $AnimatedSprite2D.flip_h = true
else:
    $AnimatedSprite2D.flip_h = false

再次播放场景并检查每个方向上的动画是否正确.

小技巧

这里一个常见错误是打错了动画的名字。“动画帧”面板中的动画名称必须与在代码中键入的内容匹配。如果你将动画命名成了 "Walk",就必须在代码中也使用大写的“W”。

当您确定移动正常工作时, 请将此行添加到 _ready() 中,在游戏开始时隐藏玩家:

hide()

准备碰撞

我们希望 Player 能够检测到何时被敌人击中, 但是我们还没有任何敌人!没关系, 因为我们将使用Godot的 信号 功能来使其正常工作.

Add the following at the top of the script. If you're using GDScript, add it after extends Area2D. If you're using C#, add it after public partial class Player : Area2D:

signal hit

这定义了一个叫作“hit”的自定义信号,当玩家与敌人碰撞时,我们会让他发出这个信号。我们将使用 Area2D 来检测碰撞。选中 Player 节点,然后点击“检查器”选项卡旁边的“节点”选项卡,就可以查看玩家可以发出的信号列表:

../../_images/player_signals.webp

Notice our custom "hit" signal is there as well! Since our enemies are going to be RigidBody2D nodes, we want the body_entered(body: Node2D) signal. This signal will be emitted when a body contacts the player. Click "Connect.." and the "Connect a Signal" window appears. We don't need to change any of these settings so click "Connect" again. Godot will automatically create a function in your player's script.

../../_images/player_signal_connection.webp

请注意函数名旁的绿色图标, 这表示信号已经连接到这个函数. 将以下代码添加到函数体中:

func _on_body_entered(body):
    hide() # Player disappears after being hit.
    hit.emit()
    # Must be deferred as we can't change physics properties on a physics callback.
    $CollisionShape2D.set_deferred("disabled", true)

敌人每次击中 玩家时都会发出一个信号。我们需要禁用玩家的碰撞检测,确保我们不会多次触发 hit 信号。

备注

如果在引擎的碰撞处理过程中禁用区域的碰撞形状可能会导致错误。使用 set_deferred() 告诉 Godot 等待可以安全地禁用形状时再这样做。

最后再为玩家添加一个函数,用于在开始新游戏时调用来重置玩家。

func start(pos):
    position = pos
    show()
    $CollisionShape2D.disabled = false

在玩家部分的工作完成后,我们将在下一课中研究敌人。