プレイヤーのコーディング¶
このパートでは、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;
using System;
public class Player : Area2D
{
[Export]
public int Speed = 400; // How fast the player will move (pixels/sec).
public Vector2 ScreenSize; // Size of the game window.
}
// A `player.gdns` file has already been created for you. Attach it to the Player node.
// Create two files `player.cpp` and `player.hpp` next to `entry.cpp` in `src`.
// This code goes in `player.hpp`. We also define the methods we'll be using here.
#ifndef PLAYER_H
#define PLAYER_H
#include <AnimatedSprite.hpp>
#include <Area2D.hpp>
#include <CollisionShape2D.hpp>
#include <Godot.hpp>
#include <Input.hpp>
class Player : public godot::Area2D {
GODOT_CLASS(Player, godot::Area2D)
godot::AnimatedSprite *_animated_sprite;
godot::CollisionShape2D *_collision_shape;
godot::Input *_input;
godot::Vector2 _screen_size; // Size of the game window.
public:
real_t speed = 400; // How fast the player will move (pixels/sec).
void _init() {}
void _ready();
void _process(const double p_delta);
void start(const godot::Vector2 p_position);
void _on_Player_body_entered(godot::Node2D *_body);
static void _register_methods();
};
#endif // PLAYER_H
最初の変数 speed
で export
キーワードを使用すると、インスペクタでその値を設定できるようになります。これは、値をノードの組み込みプロパティのように調整できるようにする場合に便利です。 Player
ノードをクリックすると、インスペクタの "Script Variables" セクションにプロパティが表示されます。ここで値を変更すると、スクリプトに記述された値よりも優先されることに注意してください。
警告
C#を使用している場合、新しいエクスポート変数またはシグナルを表示する場合は、プロジェクトアセンブリを(再)ビルドする必要があります。このビルドは、エディタウィンドウの下部にある「Mono」をクリックしてMonoパネルを表示し、「Build Project」ボタンをクリックして手動でトリガーできます。
_ready()
関数は、ノードがシーンツリーに入ると呼び出されます。これは、ゲームウィンドウのサイズを調べる良いタイミングです:
func _ready():
screen_size = get_viewport_rect().size
public override void _Ready()
{
ScreenSize = GetViewportRect().Size;
}
// This code goes in `player.cpp`.
#include "player.hpp"
void Player::_ready() {
_animated_sprite = get_node<godot::AnimatedSprite>("AnimatedSprite");
_collision_shape = get_node<godot::CollisionShape2D>("CollisionShape2D");
_input = godot::Input::get_singleton();
_screen_size = get_viewport_rect().size;
}
これで _process()
関数を使用して、プレイヤーが何をするかを定義できます。 _process()
はフレームごとに呼び出されるため、これを使用してゲームの要素を更新しますが、これは頻繁に変更されることが予想されます。プレイヤーの場合、次のことを行う必要があります:
入力をチェックします。
指定した方向に移動します。
適切なアニメーションを再生します。
まず、入力をチェックする必要があります - プレイヤーはキーを押しているでしょうか?このゲームでは、4方向の入力チェックをする必要があります。入力アクションは、プロジェクト設定の「インプットマップ」で定義されます。ここで、カスタムイベントを定義し、異なるキー、マウスイベント、またはその他の入力を割り当てることができます。このデモでは、キーボードの矢印キーを4方向に割り当てます。
「プロジェクト」→「プロジェクト設定」の順にクリックしてプロジェクト設定を開き、上部の「インプットマップ」タブをクリックします。一番上のバーに"move_right"と入力し、「追加」をクリックして move_right
アクションを追加します。
キー入力をこのアクションに設定する必要があります。右の"+"アイコンをクリックし、出てきたドロップダウンメニューの中から"Key"オプションを選択します。どのキーを利用したいか聞かれますので、キーボードの右矢印キーをクリックし、"Ok"を選択してください。
以上の手順を残り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
$AnimatedSprite.play()
else:
$AnimatedSprite.stop()
public override void _Process(float 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 animatedSprite = GetNode<AnimatedSprite>("AnimatedSprite");
if (velocity.Length() > 0)
{
velocity = velocity.Normalized() * Speed;
animatedSprite.Play();
}
else
{
animatedSprite.Stop();
}
}
// This code goes in `player.cpp`.
void Player::_process(const double p_delta) {
godot::Vector2 velocity(0, 0);
velocity.x = _input->get_action_strength("move_right") - _input->get_action_strength("move_left");
velocity.y = _input->get_action_strength("move_down") - _input->get_action_strength("move_up");
if (velocity.length() > 0) {
velocity = velocity.normalized() * speed;
_animated_sprite->play();
} else {
_animated_sprite->stop();
}
}
まず velocity
を (0, 0)
に設定することから始めます - デフォルトでは、プレイヤーは動いてはいけません。次に、各入力をチェックし、 velocity
から加算/減算して全体の方向を取得します。たとえば、 右
と 下
を同時に押した場合、結果の velocity
ベクトルは (1, 1)
になります。この場合、水平方向と垂直方向の動きを追加しているため、プレイヤーは水平方向に移動した場合よりも、斜め方向に速く移動します。
加速度を正規化すれば、これを防ぐことができます。つまり、速度の長さを 1
に設定し、それから希望の速度を乗算します。これは、これ以上速い対角移動がないことを意味します。
ちなみに
以前にベクトル演算を使用したことがない場合、あるいは忘れてしまった場合は、ベクトル演算でGodotのベクトル使用の説明をみることができます。これは知っておくと良いですが、このチュートリアルの残りの部分では必要ないでしょう。
また、AnimatedSpriteの play()
または stop()
を呼び出せるようにするため、プレイヤーが移動中かどうかも確認します。
ちなみに
$
は get_node()
の省略形です。したがって、上記のコードでは、$AnimatedSprite.play()
は get_node("AnimatedSprite").play()
と同じです。
GDScriptでは、$
は現在のノードからの相対パスにあるノードを返し、ノードが見つからない場合は null
を返します。 AnimatedSpriteは現在のノードの子であるため、$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)
Position += velocity * delta;
Position = new Vector2(
x: Mathf.Clamp(Position.x, 0, ScreenSize.x),
y: Mathf.Clamp(Position.y, 0, ScreenSize.y)
);
godot::Vector2 position = get_position();
position += velocity * (real_t)p_delta;
position.x = godot::Math::clamp(position.x, (real_t)0.0, _screen_size.x);
position.y = godot::Math::clamp(position.y, (real_t)0.0, _screen_size.y);
set_position(position);
ちなみに
_process() 関数の delta パラメーターは フレームの長さ - 前のフレームが完了するまでに要した時間を参照します。この値を使うことで、動きの処理はフレームレートの変動の影響を受けなくなります。
「シーンを実行」( F6 、macOSでは Cmd + R )をクリックして、画面上で全方向にプレイヤーを移動できることを確認してください。
警告
「デバッガ」パネルで以下のようなエラーが発生した場合
null インスタンス上の ベース 'null インスタンス' の関数 'play' を呼び出そうとしています。
この場合、AnimatedSpriteノード名のスペルが間違っている可能性があります。ノード名は大文字と小文字を区別するので、$NodeName
はシーンツリーに表示されている名前と一致させる必要があります。
アニメーションの選択¶
プレイヤーを移動できるようになったので、AnimatedSpriteが再生するアニメーションを方向に合わせて変更させる必要があります。今あるのは「walk」アニメーションで、プレイヤーは右方向へ歩きます。左への動きには flip_h
プロパティを使用して水平に反転させます。また「up」アニメーションもあり、これは flip_v
で垂直に反転させれば、下への動きになります。では、このコードを _process()
関数の最後に配置します:
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
if (velocity.x != 0)
{
animatedSprite.Animation = "walk";
animatedSprite.FlipV = false;
// See the note below about boolean assignment.
animatedSprite.FlipH = velocity.x < 0;
}
else if (velocity.y != 0)
{
animatedSprite.Animation = "up";
animatedSprite.FlipV = velocity.y > 0;
}
if (velocity.x != 0) {
_animated_sprite->set_animation("walk");
_animated_sprite->set_flip_v(false);
// See the note below about boolean assignment.
_animated_sprite->set_flip_h(velocity.x < 0);
} else if (velocity.y != 0) {
_animated_sprite->set_animation("up");
_animated_sprite->set_flip_v(velocity.y > 0);
}
注釈
上記のコードにおけるブール値の代入は、プログラマーがよく使う略式記法です。比較テスト (ブール値) とブール値の代入を行っていますが、両方とも同時に行うことができます。次のコードと、上記の1行でのブール値代入とを比べてみましょう。
if velocity.x < 0:
$AnimatedSprite.flip_h = true
else:
$AnimatedSprite.flip_h = false
if (velocity.x < 0)
{
animatedSprite.FlipH = true;
}
else
{
animatedSprite.FlipH = false;
}
もう一度シーンを再生して、それぞれの方向のアニメーションが正しいことを確認してください。
ちなみに
ここでよくある間違いは、アニメーションの名前を間違って入力してしまうことです。SpriteFramesパネルに表示されるアニメーションの名前は、コードの中で入力したものと一致していなければなりません。もしアニメーションの名前を "Walk"
とした場合、コード中では大文字の "W" も使わなければなりません。
動きが正しく機能していることを確認したら、次の行を _ready()
に追加して、ゲームの開始時にプレイヤーが非表示になるようにします:
hide()
Hide();
hide();
コリジョン(衝突/当り判定)の準備¶
Player
には敵に攻撃されたことを検知してもらいたいのですが、まだ敵を作っていません!Godotのシグナル機能を使って動作させるので、大丈夫です。
スクリプトの先頭で extends Area2d
の後に、次の行を追加します:
signal hit
// Don't forget to rebuild the project so the editor knows about the new signal.
[Signal]
public delegate void Hit();
// This code goes in `player.cpp`.
// We need to register the signal here, and while we're here, we can also
// register the other methods and register the speed property.
void Player::_register_methods() {
godot::register_method("_ready", &Player::_ready);
godot::register_method("_process", &Player::_process);
godot::register_method("start", &Player::start);
godot::register_method("_on_Player_body_entered", &Player::_on_Player_body_entered);
godot::register_property("speed", &Player::speed, (real_t)400.0);
// This below line is the signal.
godot::register_signal<Player>("hit", godot::Dictionary());
}
これは、プレイヤーが敵と衝突したときにプレイヤーが発信する(送り出す)"hit"と呼ばれるカスタムシグナルを定義します。衝突を検出するために Area2D
を使用します。 Player
ノードを選択し、インスペクタタブの横にある「ノード」タブをクリックすると、プレイヤーが発信するシグナルのリストが表示されます:
カスタムの「hit」シグナルもありますね! 敵は RigidBody2D
ノードになるため、 body_entered(body: Node)
シグナルが必要です。これは、ボディがプレイヤーに接触したときに発信されます。「接続」をクリックすると、「シグナルの接続」ウィンドウが現れます。これらの設定を変更する必要はないので、再度「接続」をクリックしてください。Godotはプレイヤーのスクリプトに自動的に関数を作成します。
シグナルがこの関数に接続されていることを示す緑色のアイコンに注意してください。次のコードを関数に追加します:
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)
public void OnPlayerBodyEntered(PhysicsBody2D body)
{
Hide(); // Player disappears after being hit.
EmitSignal(nameof(Hit));
// Must be deferred as we can't change physics properties on a physics callback.
GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred("disabled", true);
}
// This code goes in `player.cpp`.
void Player::_on_Player_body_entered(godot::Node2D *_body) {
hide(); // Player disappears after being hit.
emit_signal("hit");
// Must be deferred as we can't change physics properties on a physics callback.
_collision_shape->set_deferred("disabled", true);
}
敵がプレイヤーに当たるたびに、シグナルが発せられます。プレイヤーの衝突を無効にして、 hit
シグナルを複数回トリガーしないようにする必要があります。
注釈
エリアのコリジョン形状を無効にすると、それがエンジンの衝突処理の途中だったときにエラーが発生する可能性があります。 set_deferred()
を使用すると、安全にシェイプを無効にできるようになるまでGodotを待機させることができます。
最後のピースとなるのは、新しいゲームの開始時にPlayerをリセットするため、呼び出す関数を追加することです。
func start(pos):
position = pos
show()
$CollisionShape2D.disabled = false
public void Start(Vector2 pos)
{
Position = pos;
Show();
GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
}
// This code goes in `player.cpp`.
void Player::start(const godot::Vector2 p_position) {
set_position(p_position);
show();
_collision_shape->set_disabled(false);
}
playerが設定できたことを確認したら、次のパートで敵を設定しましょう。