Coding the player

In this lesson, we'll add player movement, animation, and set it up to detect collisions.

To do so, we need to add some functionality that we can't get from a built-in node, so we'll add a script. Click the Player node and click the "Attach Script" button:

../../_images/add_script_button.png

スクリプト設定ウィンドウ内の設定はそのままで構いません。「作成」をクリックしてください:

注釈

C#スクリプトまたはその他の言語を作成する場合は、作成を実行する前に [言語] ドロップダウン メニューから言語を選択します。

../../_images/attach_node_window.png

注釈

If this is your first time encountering GDScript, please read スクリプト言語 before continuing.

まず、このオブジェクトに必要なメンバー変数を宣言します:

extends Area2D

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

最初の変数 speedexport キーワードを使用すると、インスペクタでその値を設定できるようになります。これは、値をノードの組み込みプロパティのように調整できるようにする場合に便利です。 Player ノードをクリックすると、インスペクタの "Script Variables" セクションにプロパティが表示されます。ここで値を変更すると、スクリプトに記述された値が上書きされることに注意してください。

警告

C#を使用している場合、新しいエクスポート変数またはシグナルを表示する場合は、プロジェクトアセンブリを(再)ビルドする必要があります。このビルドは、エディタウィンドウの下部にある「Mono」をクリックしてMonoパネルを表示し、「Build Project」ボタンをクリックして手動でトリガーできます。

../../_images/export_variable.png

_ready() 関数は、ノードがシーンツリーに入ると呼び出されます。これは、ゲームウィンドウのサイズを調べる良いタイミングです:

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

これで _process() 関数を使用して、プレイヤーが何をするかを定義できます。 _process() はフレームごとに呼び出されるため、これを使用してゲームの要素を更新しますが、これは頻繁に変更されることが予想されます。プレイヤーの場合、次のことを行う必要があります:

  • 入力をチェックします。

  • 指定した方向に移動します。

  • 適切なアニメーションを再生します。

First, we need to check for input - is the player pressing a key? For this game, we have 4 direction inputs to check. Input actions are defined in the Project Settings under "Input Map". Here, you can define custom events and assign different keys, mouse events, or other inputs to them. For this game, we will map the arrow keys to the four directions.

Click on Project -> Project Settings to open the project settings window and click on the Input Map tab at the top. Type "move_right" in the top bar and click the "Add" button to add the move_right action.

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

We need to assign a key to this action. Click the "+" icon on the right, then click the "Key" option in the drop-down menu. A dialog asks you to type in the desired key. Press the right arrow on your keyboard and click "Ok".

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

Repeat these steps to add three more mappings:

  1. move_left mapped to the left arrow key.

  2. move_up mapped to the up arrow key.

  3. And move_down mapped to the down arrow key.

Your input map tab should look like this:

../../_images/input-mapping-completed.png

Click the "Close" button to close the project settings.

注釈

We only mapped one key to each input action, but you can map multiple keys, joystick buttons, or mouse buttons to the same input action.

キーが押されているかどうかを 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
        $AnimatedSprite.play()
    else:
        $AnimatedSprite.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 AnimatedSprite.

ちなみに

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

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

移動方向がわかったので、プレイヤーの位置を更新します。clamp() を使用して、プレイヤーが画面を離れないようにします。Clampingの意味は長さに制限をかける事です。_process 関数の下部に追加して下さい (else までインデントしないように注意):

position += velocity * delta
position.x = clamp(position.x, 0, screen_size.x)
position.y = clamp(position.y, 0, screen_size.y)

ちなみに

_process() 関数の delta パラメータは フレームの長さ - 前のフレームが完了するまでに要した時間を参照します。この値を使うことで、動きの処理はフレームレートの変動の影響を受けなくなります。

Click "Play Scene" (F6, Cmd + R on macOS) and confirm you can move the player around the screen in all directions.

警告

「デバッガ」パネルで以下のようなエラーが発生した場合

null インスタンス上の ベース 'null インスタンス' の関数 'play' を呼び出そうとしています。

this likely means you spelled the name of the AnimatedSprite 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 AnimatedSprite 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:
    $AnimatedSprite.animation = "walk"
    $AnimatedSprite.flip_v = false
    # See the note below about boolean assignment.
    $AnimatedSprite.flip_h = velocity.x < 0
elif velocity.y != 0:
    $AnimatedSprite.animation = "up"
    $AnimatedSprite.flip_v = velocity.y > 0

注釈

上記のコードにおけるブール値の代入は、プログラマーがよく使う略式記法です。比較テスト (ブール値) とブール値の代入を行っていますが、両方とも同時に行うことができます。次のコードと、上記の1行でのブール値代入とを比べてみましょう。

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

もう一度シーンを再生して、それぞれの方向のアニメーションが正しいことを確認してください。

ちなみに

ここでよくある間違いは、アニメーションの名前を間違って入力してしまうことです。SpriteFramesパネルに表示されるアニメーションの名前は、コードの中で入力したものと一致していなければなりません。もしアニメーションの名前を "Walk" とした場合、コード中では大文字の "W" も使わなければなりません。

動きが正しく機能していることを確認したら、次の行を _ready() に追加して、ゲームの開始時にプレイヤーが非表示になるようにします:

hide()

コリジョン(衝突/当り判定)の準備

Player には敵に攻撃されたことを検知してもらいたいのですが、まだ敵を作っていません!Godotのシグナル機能を使って動作させるので、大丈夫です。

スクリプトの先頭で extends Area2d の後に、次の行を追加します:

signal hit

これは、プレイヤーが敵と衝突したときにプレイヤーが発信する(送り出す)"hit"と呼ばれるカスタムシグナルを定義します。衝突を検出するために Area2D を使用します。 Player ノードを選択し、インスペクタタブの横にある「ノード」タブをクリックすると、プレイヤーが発信するシグナルのリストが表示されます:

../../_images/player_signals.png

カスタムの「hit」シグナルもありますね! 敵は RigidBody2D ノードになるため、 body_entered(body: Node) シグナルが必要です。これは、ボディがプレイヤーに接触したときに発信されます。「接続」をクリックすると、「シグナルの接続」ウィンドウが現れます。これらの設定を変更する必要はないので、再度「接続」をクリックしてください。Godotはプレイヤーのスクリプトに自動的に関数を作成します。

../../_images/player_signal_connection.png

シグナルがこの関数に接続されていることを示す緑色のアイコンに注意してください。次のコードを関数に追加します:

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

敵がプレイヤーに当たるたびに、シグナルが発せられます。プレイヤーの衝突を無効にして、 hit シグナルを複数回トリガーしないようにする必要があります。

注釈

エリアのコリジョン形状を無効にすると、それがエンジンの衝突処理の途中だったときにエラーが発生する可能性があります。 set_deferred() を使用すると、安全にシェイプを無効にできるようになるまでGodotを待機させることができます。

最後のピースとなるのは、新しいゲームの開始時にPlayerをリセットするため、呼び出す関数を追加することです。

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

With the player working, we'll work on the enemy in the next lesson.