3.Playerのスクリプトの作成
前節では「Player」シーンを作成しました。ここでは「Player」にスクリプトを追加していきます。「Player」ノードにplayer.gdというスクリプトをアタッチして、次のような処理を追加していきます。
-
上下左右のキー操作による「Player」の操作
- キー操作のバインド
- キーの状態によるスクリプトの処理
- 移動によるアニメーションの切り替え
- 敵との当たり判定の準備
それでは順番に作業を進めていきましょう。
スクリプトの作成とアタッチ
「シーン」ドックの「Player」を右クリックして「スクリプトをアタッチ」を選択します。
「ノードにスクリプトをアタッチする」ダイアログが開きます。
「テンプレート」の横のチェックボックスをチェックしてください。右横の「空」の表示が「Node: Default」という表示に変わります。
パスがres://player.gdになっていますので、そのまま「作成」ボタンを押します。
「ファイルシステム」にplayer.gdが追加され、Scriptビューが表示されてplayer.gdが開いた状態になります。
このスクリプトの先頭行にはextends Area2Dと書いてあります。Area2Dを継承しているのが分かります。
また_readyと_processという2つの関数が自動で追加されています。それぞれの関数の機能は次の通りです。
-
_ready関数- ノードツリーに追加され、準備が完了したタイミングで呼び出される。
-
_process関数- フレームごとに呼び出される。引数の「delta」は、1つ前のフレームからの経過時間。
関数の中で何もおこなわない時は、Pythonと同様にpass文を書きます。
メンバー変数の追加
それでは、このplayer.gdにメンバー変数を追加しましょう。
1つはspeedでスピードを表します。この変数の宣言は@export var speedのように書き、400という値を代入します。
もう1つはscreen_sizeでスクリーンサイズを表します。この変数の宣言はvar screen_sizeのように書き、まだ値は代入しないでおきます。
extends Area2D @export var speed = 400 # プレイヤーが移動する速さ (pixels/sec) var screen_size # ゲームウィンドウのサイズ # Called when the node enters the scene tree for the first time. func _ready() -> void: pass # Replace with function body. # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta: float) -> void: pass
メンバー変数はextendsの行のあとに書きます。また、GDScriptのコーディング規約に従い、関数との間は2行の隙間を空けます。
このようにスクリプトを書き換えると、「インスペクター」ドックの「Area2D」の上に「player.gd」というグループが現れます。そして「player.gd」の下に「Speed」という項目が表示されます。
メンバー変数に@exportを付けることで、外部(インスペクター)から値を設定可能にできます。
この「インスペクター」から設定可能にしたメンバー変数について、少しだけ補足します。
1つ目は名前です。スクリプトでspeedと書いた変数は、「インスペクター」では「Speed」と表示されます。変数名をspeed_pixelsのように書くと、「インスペクター」では「Speed Pixels」と表示されます。
2つ目は優先順位です。「インスペクター」で設定した値の方が、スクリプト内で変数を宣言した時に代入した値よりも優先されます。
_ready関数の実装
次に_ready関数の中で、screen_sizeの値を初期化します。passの行を削除して、新しい処理に差し替えます。
extends Area2D @export var speed = 400 # プレイヤーが移動する速さ (pixels/sec) var screen_size # ゲームウィンドウのサイズ # Called when the node enters the scene tree for the first time. func _ready() -> void: screen_size = get_viewport_rect().size # Called every frame. 'delta' is the elapsed time since the previous frame. func _process(delta: float) -> void: pass
変更したところで、スクリプト中のsizeの上にマウスを載せてください。ポップアップで説明が出ます。Godot Engineのエディターでは、マウスオーバーで各種の説明が出ます。
ポップアップを見ると、sizeのに入っている値の型がVector2で、このVector2は数値2つを保持していることが分かります。また、2つの数値は横幅と高さであることも分かります。
また、sizeのあとに.を書くと、関数やプロパティのリストが表示されます。
「Ctrl」を押しながらスクリプトの一部をクリックすると、ローカルのドキュメントを開くことも可能です。
さらに詳しい情報を得たいときは、ビューの上方にある「オンラインドキュメントで開く」をクリックして、公式ドキュメントのページを確認できます。
同じようにget_viewport_rect関数にマウスを載せても、ポップアップで説明が出ます。「Ctrl+クリック」でドキュメントを開くことも可能です。こうした情報を活用してください。
入力の準備
スクリプトの作成を先に進む前に、少しだけ準備をおこないます。キー入力の設定をおこない、プログラムから利用できるようにします。
メニューの「プロジェクト」から「プロジェクト設定」を選びます。「プロジェクト設定」ダイアログが開きますので、上部の「インプットマップ」タブを選択します。
「新しいアクションの追加」入力欄に「move_right」と入力して、右側にある「追加」ボタンを押します。
この時点では、アクションはまだ割り当てられていません。追加された「move_right」の行の右端にある「+」ボタンを押します。「move_right のイベント設定」ダイアログが開きます。
このダイアログでは「入力を待機しています」と表示されています。キーボードの「→」キーを押してください。そうすると表示が変わります。
「OK」ボタンを押します。これで「move_right」のアクションに「→」キーの操作が割り当てられました。
同じように「move_left」を「←」キーに、「move_up」を「↑」キーに、「move_down」を「↓」キーに割り当てます。
ここまで設定できたら「閉じる」ボタンを押してプロジェクト設定を終了します。その他のキーやマウスの操作、ジョイパッドの操作を割り当てる時も、同じようなやり方で設定を追加できます。
_process関数の実装1
続いて_process関数の中身を書いていきます。先ほど割り当てたキーの設定を利用します。少し長くなりますので、_process関数の中身だけを書きます。
func _process(delta: float) -> void:
# キー入力による速度の設定
var velocity = Vector2.ZERO # Playreの移動ベクトル
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() # AnimatedSprite2Dを再生
else:
$AnimatedSprite2D.stop() # AnimatedSprite2Dを停止
# 位置を更新、画面外にはみ出さないようにする
position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)
# 表示するアニメーションの種類と向きを設定
if velocity.x != 0:
$AnimatedSprite2D.animation = "walk"
$AnimatedSprite2D.flip_v = false # 縦方向の反転なし
$AnimatedSprite2D.flip_h = velocity.x < 0 # 横方向の反転
elif velocity.y != 0:
$AnimatedSprite2D.animation = "up"
$AnimatedSprite2D.flip_v = velocity.y > 0 # 縦方向の反転
_process関数の実装2 キー入力
スクリプトが長いので分解して見ていきましょう。まずはキー入力による処理の部分です。
# キー入力による速度の設定
var velocity = Vector2.ZERO # Playreの移動ベクトル
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
最初にVector2.ZEROつまり(0, 0)のベクトルを用意します。そして左右の入力があった場合にはxの値を、上下の入力があった場合にはyの値を変更します。
_process関数の実装3 移動の分岐
次は、移動があるかないかで処理を分岐します。
# 移動しているか、していなかで処理を分岐 if velocity.length() > 0: # 移動しているなら、速度のベクトルを正規化したあと、係数を掛ける velocity = velocity.normalized() * speed $AnimatedSprite2D.play() # AnimatedSprite2Dを再生 else: $AnimatedSprite2D.stop() # AnimatedSprite2Dを停止
Vector2のlength関数は、ベクトルの大きさを表します。最初は0だったので、そこからxyの値が加減算されているとlength関数の戻り値は1以上になります。0以上かを判定することで、移動があったかを調べられます。
Vector2のnormalized関数は、ベクトルの大きさを1に正規化します。
たとえば右と上に1ずつ移動した場合は、ベクトルの大きさはルート2で1.414…になります。そうなると斜めに移動するときだけ速く移動してしまいます。そのためにnormalized関数で正規化をおこなったあと、係数のspeedを乗算します。
また移動があった場合は、AnimatedSprite2Dのplay関数でアニメーションを再生して、移動がなかった場合はstop関数でアニメーションを停止します。
ここで、新しい書き方が出てきました。$AnimatedSprite2Dはget_node("AnimatedSprite2D")のショートカットです。現在のノードから、配下のAnimatedSprite2Dノードを得るという意味になります。GDScriptでは頻出する書き方です。
たとえば、次のようなノード構造をしているとします。
┗━ root
┣━ Character
┃ ┣━ Sword
┃ ┗━ Backpack
┃ ┗━ Dagger
┣━ MyGame
┗━ Swamp
┣━ Alligator
┣━ Mosquito
┗━ Goblin
このとき、Characterノードから、各ノードを参照するには、次のように書きます。
get_node("Sword")
get_node("Backpack/Dagger")
get_node("../Swamp/Alligator")
get_node("/root/MyGame")
get_node関数では、相対パスや絶対パスでノードを得ることができます。
ノードの取得は、$Swordのように簡便に書けます。$Backpack/Daggerのように相対パスも書けます。
$NodeNameと書いた際に、存在しないノードだった場合はnullが返ります。
スクリプトを実行した際に、「デバッガー」パネルにAttempt to call function '~' in base 'null instance' on a null instanceのようなエラーが出た場合には、ノード名のスペルが間違っている可能性があります。
$NodeNameの名前は、シーンツリーに表示されている名前と一致させる必要があります。エラーが出た場合には見直してください。
$/root/MyGameのような書き方も可能です。$/root/~のように書く場合は、$/root/<グローバル変数>や$/root/<シーンのルート>のようなパスになります。少し特殊なので、print($/root/Main/Button)のように出力して、正しく取れているか確認するとよいです。
.や..が入る場合には、$../Swamp/Alligatorのような書き方はできません。この場合はクオーテーションを使い、$"../Swamp/Alligator"と書くことも可能です。こちらも、print関数で確認してから使うとよいでしょう。
_process関数の実装4 プレイヤーの位置の更新
次は、プレイヤーの位置の更新をおこないます。
# 位置を更新、画面外にはみ出さないようにする position += velocity * delta position = position.clamp(Vector2.ZERO, screen_size)
Node2DのTranformのpositionに対して、速度velocityと差分時間deltaを掛けた値を加算します。
ベクトル同士の計算では、各プロパティーを個別に計算する必要はありません。
また、position.clamp関数を利用して、positionの値が、原点から画面サイズの範囲内に収まるように補正します。
実行確認
ここまでのスクリプトを書いたら、一度実行してみましょう。「Ctrl+S」でプロジェクトを保存したあと、ウィンドウ右上のボタンが並んでいる場所から、「カチンコ内の▶」
(現在のシーンを実行)をクリックします。あるいは「F6」キーを押します。
確認したら、hide関数を追加して、プレイヤーを隠しましょう。スタートボタンを押すまでは非表示にするためです。
# Called when the node enters the scene tree for the first time. func _ready() -> void: screen_size = get_viewport_rect().size hide()
