プレイヤーのコーディング
このパートでは、playerの動きやアニメーション、衝突判定を設定します。
そのために、組み込みノードからは得られない機能を追加する必要があるので、スクリプトを追加します。 Player ノードをクリックし、「スクリプトをアタッチ」ボタンをクリックします:
スクリプト設定ウィンドウ内の設定はそのままで構いません。「作成」をクリックしてください:
注釈
C#スクリプトまたはその他の言語を作成する場合は、作成を実行する前に [言語] ドロップダウン メニューから言語を選択します。
注釈
はじめてGDScriptを扱う場合は、 スクリプト言語 を読んでから先に進んでください。
まず、このオブジェクトに必要なメンバー変数を宣言します:
extends Area2D
@export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.
using Godot;
public partial class Player : Area2D
{
[Export]
public int Speed { get; set; } = 400; // How fast the player will move (pixels/sec).
public Vector2 ScreenSize; // Size of the game window.
}
最初の変数 speed で export キーワードを使用すると、インスペクタでその値を設定できるようになります。これは、値をノードの組み込みプロパティと同じように調整できるようにする場合に便利です。 Player ノードをクリックすると、インスペクタにスクリプトと同じ名前のセクションが追加され、プロパティが表示されます。ここで値を変更すると、スクリプトに記述された値よりもこちらが優先されることに注意してください。
警告
C# を使用している場合は、新しいエクスポート変数またはシグナルを確認したい場合は、常にプロジェクト アセンブリを (再) ビルドする必要があります。 このビルドは、エディターの右上にある「ビルド」ボタンをクリックすることで手動でトリガーできます。
作成した player.gd スクリプトは既に _ready() と _process() 関数を含んでいるはずです。もし前述の手順で、テンプレート [Node: Default] を選択していなかった場合は、以下のレッスンを進めながら適宜これらの関数を作成して下さい。
_ready() 関数は、ノードがシーンツリーに入ると呼び出されます。これは、ゲームウィンドウのサイズを調べる良いタイミングです:
func _ready():
screen_size = get_viewport_rect().size
public override void _Ready()
{
ScreenSize = GetViewportRect().Size;
}
これで _process() 関数を使用して、プレイヤーが何をするかを定義できます。 _process() はフレームごとに呼び出されるため、これを使用してゲームの要素を更新しますが、これは頻繁に変更されることが予想されます。プレイヤーの場合、次のことを行う必要があります:
入力をチェックします。
指定した方向に移動します。
適切なアニメーションを再生します。
まず、入力をチェックする必要があります - プレイヤーはキーを押しているでしょうか?このゲームでは、4方向の入力チェックをする必要があります。入力アクションは、プロジェクト設定の「インプットマップ」で定義されます。ここで、カスタムイベントを定義し、異なるキー、マウスイベント、またはその他の入力を割り当てることができます。このデモでは、キーボードの矢印キーを4方向に割り当てます。
「プロジェクト」->「プロジェクト設定」 の順にクリックしてプロジェクト設定を開き、上部の「インプットマップ」タブをクリックします。上のバーに "move_right" と入力し、[+追加] をクリックして move_right アクションを追加します。
このアクションにキーを割り当てる必要があります。右側の "+"アイコンをクリックして、イベントの設定を開きます。
「入力を待機しています...」というフィールドが自動的に選択されるはずです。キーボードの "→" キーを押すと、メニューがこのように表示されます。
"OK" ボタンをクリックします。これで "→" キーが move_right アクションに関連付けられました。
以上の手順を残り3方向分設定します。
move_leftを左矢印キーに設定します。move_upを上矢印キーに設定します。そして
move_downを下矢印キーに設定します。
インプットマップは次のようになります:
「閉じる」ボタンを押してプロジェクト設定を終了します。
注釈
今回は一つのキーのみを各アクションに設定しましたが、複数のキーやジョイスティックボタン、マウスボタンを同じアクションに設定することができます。
キーが押されているかどうかを 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()
public override void _Process(double delta)
{
var velocity = Vector2.Zero; // The player's movement vector.
if (Input.IsActionPressed("move_right"))
{
velocity.X += 1;
}
if (Input.IsActionPressed("move_left"))
{
velocity.X -= 1;
}
if (Input.IsActionPressed("move_down"))
{
velocity.Y += 1;
}
if (Input.IsActionPressed("move_up"))
{
velocity.Y -= 1;
}
var animatedSprite2D = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
if (velocity.Length() > 0)
{
velocity = velocity.Normalized() * Speed;
animatedSprite2D.Play();
}
else
{
animatedSprite2D.Stop();
}
}
まず velocity を (0, 0) に設定することから始めます - デフォルトでは、プレイヤーは動いてはいけません。次に、各入力をチェックし、 velocity から加算/減算して総合的な方向を取得します。たとえば、 右 と 下 を同時に押した場合、結果の velocity ベクトルは (1, 1) になります。これは、水平方向と垂直方向の動きを加算しているためで、結果としてプレイヤーは水平方向に移動した場合よりも、斜め方向に速く移動してしまいます。
加速度 (velocity) を正規化することで、これを防ぐことができます。つまり、速度の長さを 1 に設定し、それから希望の速度を乗算します。これは、これ以上速い斜め方向の移動がないことを意味します。
Tip
以前にベクトル演算を使用したことがない場合、あるいは忘れてしまった場合は、ベクトル演算でGodotのベクトル使用の説明をみることができます。これは知っておくと良いですが、このチュートリアルの残りの部分では必要ないでしょう。
また、AnimatedSprite2Dの play() または stop() を呼び出せるようにするため、プレイヤーが移動中かどうかも確認します。
Tip
$ は get_node() の省略形です。したがって、上記のコードでは、$AnimatedSprite2D.play() は get_node("AnimatedSprite2D").play() と同じです。
GDScriptでは、$ は現在のノードからの相対パスにあるノードを返し、ノードが見つからない場合は null を返します。 AnimatedSprite2Dは現在のノードの子であるため、$AnimatedSprite2D を使用できます。
移動方向がわかったので、プレイヤーの位置を更新します。clamp() を使用して、プレイヤーが画面を離れないようにします。値を クランプする ことは、値を特定の範囲に制限することを意味します。 _process 関数の末尾に追加して下さい ( else までインデントしないように気を付ける事):
position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)
Position += velocity * (float)delta;
Position = new Vector2(
x: Mathf.Clamp(Position.X, 0, ScreenSize.X),
y: Mathf.Clamp(Position.Y, 0, ScreenSize.Y)
);
Tip
_process() 関数の delta パラメーターは フレームの長さ - 前のフレームが完了するまでに要した時間を参照します。この値を使うことで、動きの処理はフレームレートの変動の影響を受けなくなります。
「現在のシーンを実行」( F6 、macOSでは Cmd + R )をクリックして、画面上で全方向にプレイヤーを移動できることを確認してください。
警告
「デバッガー」パネルで以下のようなエラーが発生した場合
Attempt to call function '~関数名~' in base 'null instance' on a null instance
この場合、AnimatedSprite2Dのノード名のスペルが間違っている可能性があります。ノード名は大文字と小文字を区別するので、$NodeName はシーンツリーに表示されている名前と一致させる必要があります。
アニメーションの選択
プレイヤーを移動できるようになったので、AnimatedSprite2Dが再生するアニメーションを方向に合わせて変更させる必要があります。今あるのは「walk」アニメーションで、プレイヤーは右方向へ歩きます。左への動きには flip_h プロパティを使用して水平に反転させます。また「up」アニメーションもあり、これは flip_v で垂直に反転させれば、下への動きになります。では、このコードを _process() 関数の最後に配置します:
if velocity.x != 0:
$AnimatedSprite2D.animation = "walk"
$AnimatedSprite2D.flip_v = false
# See the note below about the following 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.Animation = "walk";
animatedSprite2D.FlipV = false;
// See the note below about the following boolean assignment.
animatedSprite2D.FlipH = velocity.X < 0;
}
else if (velocity.Y != 0)
{
animatedSprite2D.Animation = "up";
animatedSprite2D.FlipV = velocity.Y > 0;
}
注釈
上記のコードにおける理論値 (boolean) の代入は、プログラマーがよく使う略式記法です。比較テストと理論値の代入をおこなっていますが、両方を同時におこなうことができます。次のコードと、上記の1行での理論値代入とを比べてみましょう。
if velocity.x < 0:
$AnimatedSprite2D.flip_h = true
else:
$AnimatedSprite2D.flip_h = false
if (velocity.X < 0)
{
animatedSprite2D.FlipH = true;
}
else
{
animatedSprite2D.FlipH = false;
}
もう一度シーンを再生して、それぞれの方向のアニメーションが正しいことを確認してください。
Tip
ここでよくある間違いは、アニメーションの名前を間違って入力してしまうことです。SpriteFramesパネルに表示されるアニメーションの名前は、コードの中で入力したものと一致していなければなりません。もしアニメーションの名前を "Walk" とした場合、コード中では大文字の "W" も使わなければなりません。
動きが正しく機能していることを確認したら、次の行を _ready() に追加して、ゲームの開始時にプレイヤーが非表示になるようにします:
hide()
Hide();
コリジョン(衝突/当り判定)の準備
Player には敵に攻撃されたことを検知してもらいたいのですが、まだ敵を作っていません!でも大丈夫。 Godotのシグナル機能を使って動作させます。
スクリプトの先頭に以下のコードを追加します。GDScriptを使用している場合は、 extends Area2D の後に追加してください。C#を使用している場合は、 public partial class Player : Area2D の後に追加してください。:
signal hit
// Don't forget to rebuild the project so the editor knows about the new signal.
[Signal]
public delegate void HitEventHandler();
これは、プレイヤーが敵と衝突したときにプレイヤーが発信する (送り出す) "hit" と名付けた自作のシグナルを定義します。衝突を検出するために Area2D を使用します。 Player ノードを選択し、インスペクタタブの横にある「ノード」タブをクリックすると、プレイヤーが発信するシグナルのリストが表示されます:
自作した「hit」シグナルもある点に注意しましょう!敵は RigidBody2D ノードになるため、 body_entered(body: Node2D) シグナルが必要です。このシグナルは、本体がプレイヤーに接触したときに発信されます。「接続…」をクリックすると、「メソッドにシグナルを接続」ウィンドウが表示されます。
Godotは、スクリプト内にその正確な名前で関数を直接作成します。 デフォルト設定をすぐに変更する必要はありません。
警告
もし外部テキストエディタ(例えばVisual Studio Code)を使っている場合は、バグのため現在はGodotはそのように動作できません。外部エディタに移動しても、新規関数は現れません。
この場合は、プレーヤースクリプト内に自前で関数を記述する必要があります。
緑のアイコンは、シグナルがこの関数に接続していることを示します。これは関数が存在していることを意味せず、シグナルがその名前を持つ関数に接続しようとするだけです。このため、関数名が正しく一致していることを多重チェックして下さい!
続いて、関数に次のコードを追加します:
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)
// We also specified this function name in PascalCase in the editor's connection window.
private void OnBodyEntered(Node2D body)
{
Hide(); // Player disappears after being hit.
EmitSignal(SignalName.Hit);
// Must be deferred as we can't change physics properties on a physics callback.
GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred(CollisionShape2D.PropertyName.Disabled, true);
}
敵がプレイヤーに当たるたびに、シグナルが発せられます。プレイヤーの衝突を無効にして、 hit シグナルを複数回トリガーしないようにする必要があります。
注釈
エリアのコリジョン形状を無効にすると、それがエンジンの衝突処理の途中だったときにエラーが発生する可能性があります。 set_deferred() を使用すると、安全にシェイプを無効にできるようになるまでGodotを待機させることができます。
最後のピースとなるのは、新しいゲームの開始時にPlayerをリセットするため、呼び出す関数を追加することです。
func start(pos):
position = pos
show()
$CollisionShape2D.disabled = false
public void Start(Vector2 position)
{
Position = position;
Show();
GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
}
playerが設定できたことを確認したら、次のパートで敵を設定しましょう。