第4回目の今回は、敵キャラクターの雛形となるシーンを作成し、それを継承する形で個別の敵キャラクターを一つ作っていく。そのあと、その敵キャラクターをレベルシーンに配置して、プレイヤーキャラクターに踏まれたら消えるところまで実装してみよう。

Memo:
過去のシリーズをまだご覧になっていない方は、そちらを先にご覧いただくことをおすすめします。
Godot で作るプラットフォーマー



敵キャラクターの雛形となるシーンを作ろう

一般的に、プラットフォーマーゲームには複数種類の敵キャラクターが存在する。今回のチュートリアルでは、まず 1 種類、敵キャラクターを作る。そして、次回以降 Part でさらに種類を増やしていこう。

だが、敵キャラクターを一種類ずつ、はじめから作り始めると、それなりに時間がかかる。全ての敵キャラクターに共通の部分を先に雛形のシーンとして作っておき、そのシーンを継承する形でそれぞれの敵キャラクターのシーンを作成すれば効率的だ。

ということで、まずは雛形のシーンから作っていこう。「シーン」メニュー>「新規シーン」を選択する。
新規シーンを追加

「ルートノードを生成」のオプションで「+その他のノード」を選択する。
その他のノードを選択

ここからは、先に必要なノードをシーンツリーに追加していこう。

ルートノードには「KinematicBody2D」クラスのノードを設定する。名前を「Enemy」に変更しておこう。続けてそのルートノードに「AnimatedSprite」ノード、および「CollisionShape2D」ノードを子ノードとして追加する。
AnimatedSpriteノードとCollisionShape2Dノードを選択

さらに「Enemy」ルートノードに「Area2D」クラスのノードを追加し、名前を「HitBox」に変更する。その「HitBox」ノードに「CollisionShape2D」ノードを追加する。この「HitBox」のコリジョン形状は、プレイヤーキャラクターに踏まれた時の衝突検出に使用するためのものだ。

最後に「Enemy」ノードに「VisibilityEnabler2D」ノードを追加する。このノードは、敵キャラクターがゲーム画面の範囲内に入った瞬間や、ゲーム画面内にいた敵キャラクターが画面外に消えた時にシグナルを発信できるので、敵キャラクターの動きの制御に後ほど利用する予定だ。

ここまでで、シーンツリーの構造はシーンドック上で以下のようになったはずだ。
シーンドックを確認

ここからは、インスペクターでいくつかノードのプロパティを編集していく。

「AnimatedSprite」ノードの「Frames」プロパティに「新規 SpriteFrames」を設定する。具体的なアニメーションは、「Enemy」シーンを継承したシーンでそれぞれ行っていく。あとは「Playing」プロパティをオンにしておこう。
AnimatedSpriteのプロパティ編集

「Enemy」ルートノード直下の「CollisionShape2D」ノードの「Shape」プロパティには「新規 CapsuleShape2D」を設定しておく。ただし、具体的なコリジョン形状の調整は「Enemy」シーンを継承したシーンで行う。
Enemy>CollisionShape2Dのプロパティ編集

次に「HitBox」ノードの子ノードの方の「CollisionShape2D」ノードの「Shape」プロパティには「新規 RectangleShape2D」を設定しておく。こちらも、コリジョン形状の調整は「Enemy」シーンを継承したシーンで行う。
HitBox>CollisionShape2Dのプロパティ編集

「VisibilityEnabler2D」ノードの「Rect」プロパティの値を「x: -16, y: -16, w: 32, h: 32」にしておこう。
Rectプロパティの編集

このピンクの四角い範囲がゲーム画面を出た時や入った時にノードがシグナルを発信することができる。
Rectの2Dワークスペース上の表示

では、ここまでできたら一度シーンを保存しておこう。シーンを保存するためのフォルダを「Enemies」という名前にして作成し、ファイル名を「Enemy.tscn」として保存しよう。ファイルパスは「res://Enemies/Enemy.tscn」になる。
Enemyシーンの保存



Enemy シーンにスクリプトをアタッチする

次に敵キャラクターの共通部分について、スクリプトの方も作成しておこう。

シーンドックで「Enemy」ルートノードを選択して、スクリプトをアタッチする。この時、先ほどシーンを保存する時に作成した「Enemies」フォルダに保存するようにしよう。スクリプトのファイルパスは「res://Enemies/Enemy.gd」となるはずだ。
Enemy.gd スクリプトをアタッチ

では、スクリプトエディタが開いたら、まず敵キャラクターに共通するプロパティを用意しておこう。

extends KinematicBody2D


export var gravity: int
export var speed: int
var velocity = Vector2()
onready var sprite = $AnimatedSprite

gravityspeedの2つのプロパティは、敵キャラクターによって、違ったほうが面白そうなので、インスペクターで気軽に更新できるようにexportキーワードをつけた。この時点では値を指定せず、データ型をintとして指定したのみだ。

velocityはプレイヤーキャラクター同様、_physics_processメソッド内で更新させる予定のため、一旦Vector2()を値にしている。これはx, y座標が(0, 0)の状態だ。

spriteは「AnimatedSprite」ノードにアクセスするためのプロパティだ。子ノードが読み込まれてから定義する必要があるためonreadyキーワードを付けている。

次に敵キャラクターが踏まれたら消える仕組みも、全ての敵キャラクターで共通にしたい。これには「HitBox」ノードの子「CollisionShape2D」ノードを利用する。コリジョン形状は敵キャラクターの頭のてっぺんあたりに配置することを想定している。キャラクターがこれに衝突した時に「HitBox」ノードからシグナルを発信して、それをトリガーにして敵が消える処理を実行するようにしていく。

まずはシグナルの接続からだ。シーンドックで「HitBox」ノードを選択し、ノードドック>シグナルを開き、「body_entered(body: Node)」シグナルをダブルクリック、または選択して右下の「接続」をクリックする。
body_entered(body: Node)シグナルを接続

ダイアログにて「Enemy.gd」スクリプトに接続しよう。「スクリプトに接続:」欄で「Enemy」を選択して下部の「接続」をクリックすればOKだ。
Enemy.gdスクリプトにシグナルを接続

すると、以下のようにシグナルをトリガーにして実行される_on_HitBox_body_enteredメソッドが追加されたはずだ。しかし、メソッドの中身は今のところ空っぽ(passのみ)だ。

func _on_HitBox_body_entered(body):
	pass

このメソッドの中身をコーディングする前に、先にやっておきたいのが、ノードのグループ設定だ。以下の手順で「Player」シーンの「Player」ノードを新しくノードグループ「Players」を作成してそれに追加しよう。また直近で必要なのは「Player」ノードのグループだが、ついでに「Enemy」シーンの「Enemy」ノードも「Enemies」を作成して追加しておこう。

現在の「Enemy.tscn」シーンファイルは保存して、「Player.tscn」シーンファイルを開く。

シーンドックで「Player」ルートノードを選択する。
Enemy.gdスクリプトにシグナルを接続

ノードドック>「グループ」タブを選択し、入力欄に「Players」とグループ名を入力して「追加」をクリックする。
Enemy.gdスクリプトにシグナルを接続

これで「Player」ノードが「Players」ノードグループに追加された。
Enemy.gdスクリプトにシグナルを接続

今度は「Enemy.tscn」に切り替えて、同様の手順で「Enemy」ノードを「Enemies」ノードグループに追加しておく。
Enemy.gdスクリプトにシグナルを接続

それでは今作ったグループを利用してさっきの_on_HitBox_body_enteredメソッドの中身をコーディングしていこう。このメソッドの引数bodyは、「HitBox」ノードと衝突したノードを指す。このbodyがプレイヤーキャラクターだった場合は、敵キャラクター自身を消す、という内容のコードを記述する。

func _on_HitBox_body_entered(body):
	if body.is_in_group("Players"):
		print("Player entered in ", self.name)
		sprite.play("hit")
		yield(sprite, "animation_finished")
		queue_free()
		print(self.name, "died")

if body.is_in_group("Players"):で、『もしbodyが「Players」グループのノードだったら』という条件分岐になる。この条件に当てはまった場合に、以下を実行するプログラムになっている。

  • デバッグとして、print関数でプレイヤーキャラクターが「HitBox」に衝突したことを出力する。
  • 「AnimatedSprite」ノードの「hit」アニメーションを再生する(この名前のアニメーションを継承後のシーンで作成予定)
  • yieldにて、「AnimatedSprite」ノードのアニメーションが終了するまでこれ以下のコードの読み込みを一時停止する。
  • queue_freeメソッドを実行し、この「Enemy」ノード自身を消す。
  • もう一度デバッグとして、print関数で敵キャラクターが消えたことを出力する。

公式オンラインドキュメント
コルーチン(yield関数による)

「AnimatedSprite」のアニメーションはこれから、「Enemy」シーンを継承した個別のシーンで作成することになる。その時に、踏まれて消える時の「hit」という名前のアニメーションを用意しておく必要があるので、覚えておこう。

さて、次に実装したいのは、敵キャラクターが画面外にいるときはその動作を停止し、画面内に入ったら動き出す、という仕組みだ。これには「VisibilityEnabler2D」ノードを利用する。このクラスのノードは、画面に表示されていない時は、ルートノードとそれ自身と同階層のノードの動きを停止してくれる。

公式オンラインドキュメント
VisibilityEnabler2D

ただし、動きというのは、基本的にノードが「RigidBody2D」クラスの場合のノードの動きやアニメーションの動きを指す。このチュートリアルでの敵キャラクターのノードは「KinematicBody2D」クラスであるため、移動に関する制御は別途行う必要がある。

そこで_readyメソッドで物理プロセスを停止させるようにする。

func _ready():
	set_physics_process(false)

このように引数をfalseにしてset_physics_processメソッドを実行させれば、物理プロセスは停止する。「Enemy」シーンを継承した個別の敵キャラクターシーンでは_physics_processメソッドでノードを移動させる予定のため、これでゲーム開始時点でノードの動きを止めることができるはずだ。

次に、画面に敵キャラクターが表示された時に動き出すようにする必要がある。これには「VisibilityEnabler2D」ノードのシグナルを利用する。「VisibilityEnabler2D」ノードを選択したら、ノードドック>「シグナル」タブにて「screen_entered()」シグナルをスクリプトに接続する。接続するスクリプトは当然「Enemy.gd」スクリプトだ。

生成されたメソッド_on_VisibilityEnabler2D_screen_enteredのブロック内に実行したいコードを記述しよう。

func _on_VisibilityEnabler2D_screen_entered():
	set_physics_process(true)

引数をtrueにして組み込みのset_physics_processメソッドを実行すると、停止していた物理プロセスが動き出す。これで、画面上に敵キャラクターが表示されたら、停止している物理プロセスが再開されようになった。

同様にして、画面上から敵キャラクターが消えたら、その物理プロセスが停止するようにも設定しておこう。これには「VisibilityEnabler2D」ノードの別のシグナルを利用する。今度は「VisibilityEnabler2D」ノードを選択して、ノードドック>「シグナル」タブにて「screen_exited()」シグナルを「Enemy.gd」スクリプトに接続しよう。

func _on_VisibilityEnabler2D_screen_exited():
	set_physics_process(false)

生成されたメソッド_on_VisibilityEnabler2D_screen_exitedのブロック内に引数をfalseにしてset_physics_processメソッドを記述しておけば、画面上から敵キャラクターが消えたら、物理プロセスが停止し、敵キャラクターの移動も停止するはずだ。

「Enemy.tscn」シーンと「Enemy.gd」スクリプトを保存し、個別の敵キャラクターのシーンを作成し、おかしなところはそこで修正をかけていこう。


Enemy シーンを継承して Mushroom シーンを作る

ここからは先に作成した「Enemy.tscn」シーンを継承する形で、個別の敵キャラクターを作っていく。

まずはゲームで最弱の敵キャラクターから作っていこう。スーパーマリオシリーズでいうところのクリボーのようなキャラクターだ。便宜上名前を「マッシュルーム」としておく。

まず「シーン」メニュー>「新しい継承シーン」を選択する。
シーン>新しい継承シーン

ルートノードの名前を「Mushroom」に変更しておこう。
Mushroomにルートノードの名前を変更

この時点で先にシーンを保存しておこう。この時、res://Enemies/ に「Mushroom」フォルダを作成して、その中に「Mushroom.tscn」のファイル名で保存しよう。つまりファイルパスは「res://Enemies/Mushroom/Mushroom.tscn」になる。
Mushroom.tscnとして保存



Mushroom シーンの各ノードの設定を更新する

継承したシーンではルートノード以外のノードがグレーアウトして表示されているが、全く編集できないわけではないので安心していただきたい。各ノードの編集を順番に行っていく。


Mushroom ルートノードの Script Variables を編集する

まずはルートノードから編集しよう。

Script Variables とはスクリプト内で定義した変数(プロパティ)のことだ。継承元の「Enemy」シーンですでに定義しておいた「gravity」と「speed」の2つにはexportキーワードをつけておいたので、継承先のシーンでもインスペクターから簡単に値を変更できる。

シーンドックで「Mushroom」ルートノードを選択し、それぞれのプロパティを以下の値に変更しよう。

  • Gravity: 512
  • Speed: 32
    MushroomのScript Variablesの編集

AnimatedSprite ノードのアニメーションを編集する

続いて「AnimatedSprite」ノードの編集を行う。

シーンドックで「AnimatedSprite」ノードを選択したら、インスペクターから「Frames」プロパティで「SpriteFrames」をクリックし、メニューから 「ユニーク化」 を選択する。これにより、この「SpriteFrames」を編集しても、継承元の「Enemy」シーンには影響しない。その結果、今後作成予定の他の「Enemy」からの継承シーンにも影響しない。忘れがちだが、他のシーンへの影響を回避するために重要な操作なので早めの実施を心がけよう。

SpriteFramesをユニーク化

ここからはアニメーションを作成する。プレイヤーキャラクターでも一度やった作業なので、多少慣れた感じで進めていただけるだろう。

まずシーンドックで「AnimatedSprite」ノードを選択する。エディタ下部で「スプライトフレーム」パネルが表示されるので、そこで編集していく。

以下の3つのアニメーションを追加し、それぞれの速度を 24 FPS の設定にする。スプライトシートが「Asset」フォルダにあるので、それぞれに割り当てよう。

  • アニメーション名: hit / スプライトシート: res://Assets/Enemies/Mushroom/Hit.png
    Hitのアニメーション
  • アニメーション名: idle / スプライトシート: res://Assets/Enemies/Mushroom/Idle (32x32).png
    idleのアニメーション
  • アニメーション名: run / スプライトシート: res://Assets/Enemies/Mushroom/Run (32x32).png
    runのアニメーション

アニメーション「hit」だけはプレイヤーキャラクターに踏まれた時に一回だけ再生してノードを解放する(消す)予定のため、ループの設定をオフにしておこう。
hitのループをオフ

ところで、もし追加したスプライトフレームの画像にブラーがかかっている(ぼやけている)感じであれば、ファイルシステムで、該当のスプライトシートを選択した状態で、インポートドックにて「2D Pixel」プリセットを読み込み、その設定で「再インポート」するとピクセルアート独特のエッジが効いた見た目に修正される。
2D Pixel のプリセット

再インポート

最後に、それぞれのアニメーションをチェックしておこう。
アニメーションをチェック


CollisionShape2D ノードのコリジョン形状を設定する

「Mushroom」シーンには「CollisionShape2D」ノードが2つあるので、それぞれのコリジョン形状を順番に設定していく。

まずは「Mushroom」ルートノード直下の「CollisionShape2D」ノードのコリジョン形状からやっていこう。このコリジョン形状は、敵キャラクター自身が地面や壁、プレイヤーキャラクターなど他のオブジェクトと衝突したことを検知するために使用する。そのため、できるだけスプライトテクスチャのデザインに近い形状にするのが理想だ。

インスペクターから「Shape」プロパティの「CapsuleShape2D」をクリックし、また 「ユニーク化」 を選択して適用しておこう。継承元に影響を与えないためだ。

「CapsuleShape2D」のコリジョン形状は、デフォルトでは縦長の楕円形になっている。「Mushroom」のスプライトテクスチャは、比較的平べったいデザインだ。スプライトテクスチャのデザインにもよるが、横長のデザインの場合は、コリジョン形状を90°回転すると調整しやすいことが多い。
Rotation Degrees を 90 に設定

2Dワークスペースでドラッグ操作でコリジョン形状を調整する場合は、ツールバーの「スナップオプション」から「ピクセルスナップを使用」にチェックを入れておこう。

だいたい以下のような形状になればOKだ。足が地面との衝突を正確に検知するため、テクスチャデザインの足の先にきっちり合わせておこう。頭のてっぺんには意図的に少しスペースを設けている。理由は、もう一つのコリジョン形状(プレイヤーキャラクターが敵キャラクターを踏んだことを判定するためのコリジョン形状)をそこに配置する予定だからだ。
CollisionShape2Dのコリジョン形状
このコリジョン形状の「Radius」と「Height」は以下の数値になっている。ドラッグ操作が煩わしい場合は、こちらに直接入力してもらっても構わない。
CollisionShape2Dのコリジョン形状のプロパティ

続いて、もう一つの「HitBox」ノードの子になっている「CollisionShape2D」ノードの方を編集していく。作業自体は大体同じだ。まず、シーンドックで該当の「CollisionShape2D」ノードを選択した状態で、インスペクターから「Shape」プロパティの「RectangleShape2D」を選択する。ここでも忘れずに 「ユニーク化」 を適用しておこう。

今回のコリジョン形状は大体以下のようになればOKだ。ポイントは、ルートノード直下の「CollisionShape2D」の幅より狭くし、それと重ならないようにしてスプライトテクスチャの頭のてっぺんに配置することだ。幅が広いと、踏まずともぶつかっただけでこの敵キャラクターを倒せてしまうし、頭のてっぺんのコリジョン形状が重なると、プレイヤーの方がぶつかられて、やられてしまうかもしれない。
CollisionShape2Dのコリジョン形状その2

このコリジョン形状の「Extents」プロパティは(8, 1)になっている。
CollisionShape2Dのコリジョン形状のプロパティその2

これで、コリジョン形状の調整ができた。インスペクターで編集する内容はひとまずここまでだ。次はいよいよスクリプトの方を更新していく。


Enemy.gd を継承した Mushroom.gd スクリプトをアタッチする

現時点で「Mushroom」ノードには「Enemy.gd」スクリプトがアタッチされている。「Enemy」シーンを継承しているので当然なのだが、このスクリプトは全敵キャラクター共用のスクリプトなので、「Mushroom」シーンのためだけに更新するわけにはいかない。

ひとまず「Enemy.gd」はデタッチ(紐付け解除)しよう。シーンドックで「Mushroom」ノードを右クリック>「スクリプトをデタッチ」を選択する。これで簡単にアタッチされていたスクリプトが外れる。

次に「Mushroom」を選択して、新しいスクリプトをアタッチする。この時、デフォルトの設定のまま作成してしまうと「KinematicBody2D」クラスを継承したスクリプトになってしまう。「継承元」の右側にあるフォルダアイコンをクリックしてみよう。
デフォルトのスクリプトアタッチ設定

すると、作成済みのスクリプトを選択できる。「res://Enemies/Enemy.gd」を選択して「開く」をクリックしてみよう。
継承元にEnemy.gdを選択して開く

これで「継承元」として「res://Enemies/Enemy.gd」が選択された状態になった。新しいスクリプトのファイルパスを「res://Enemies/Mushroom/Mushroom.gd」として「作成」をクリックしよう。
継承元にEnemy.gdを選択して開く

スクリプトエディタに切り替わったら、一行目にextends "res://Enemies/Enemy.gd"という記述があることに気がつくだろう。これは先に作成した「Enemy」シーン用の「Enemy.gd」スクリプトを継承していることを示している。

extends "res://Enemies/Enemy.gd"


func _ready():
	pass

このように、自作のスクリプトも簡単に継承することができる。シーンを継承するときはスクリプトも別途継承できることを覚えておこう。



Mushroom.gd スクリプトを編集する

まずは_readyメソッドから編集する。マッシュルームの移動時は「AnimatedSprite」ノードの「run」アニメーションを再生したいので、以下ように編集した。

func _ready():
	sprite.play("run") 

どのアニメーションを最初に再生するかはインスペクターで設定しておけるが、デバッグをしていると、他のアニメーションの再生中に終了して、そのアニメーションの設定がそのまま残ることがある。毎回ゲーム開始時に「run」アニメーションを再生するように_readyメソッド内でアニメーションを指定した。

では次に「Mushroom」の移動をスクリプトで制御していこう。例によって今回も_physics_processメソッドを利用する。

func _physics_process(delta):

マッシュルームは、最弱キャラでただ速くもないスピードで前進したり止まったりするだけのキャラクターとする。

まずは基本として以下を満たすようにコーディングしていこう。

  • 毎フレームspeedプロパティの値だけ左方向に前進させる
  • 高所から落ちる際は重力で加速しながら落下する
func _physics_process(delta):
	velocity.x = -speed 
	velocity.y += gravity * delta
	
	velocity = move_and_slide(velocity, Vector2.UP)

このようなコードになる。velocityの x, y の値をそれぞれ設定し、そのvelocitymove_and_slideメソッドの第一引数に代入することで、ノードの移動を制御する最低限のコードができた。

さらに以下を満たすようにコードを追加しよう。

  • 壁にぶつかったら進行方向を左右反転する
func _physics_process(delta):
	if is_on_wall():
		speed *= -1
		sprite.flip_h = !sprite.flip_h
	
	velocity.x = -speed
	velocity.y += gravity * delta
	
	velocity = move_and_slide(velocity, Vector2.UP)

is_on_wallメソッドは壁に接していれば true を返す。つまり冒頭に追加した if 構文は『もしマッシュルームが壁に衝突したら』という条件分岐になる。

この条件を満たした場合、speedプロパティに -1 を乗算することで x 軸の反対方向に進むようにしている。sprite.flip_hプロパティが true の場合は、『水平方向に「AnimatedSprite」を反転させる』という意味になる。sprite.flip_hプロパティの値に!sprite.flip_hと指定することで、現在のsprite.flip_hの値とは逆の値(現在 true なら false)を設定する、というコードになる(!は not と同様の意味)。

さてこれでマッシュルームの基本的な動きが出来上がったので、Level1 シーンにインスタンスをノードとして追加してみよう。忘れずに「Mushroom」シーンを保存しておこう。



Level1 シーンに Mushroom シーンのインスタンスノードを追加する

ここまで作成してきた「Mushroom」シーンを「Level1」シーンのノードとしてインスタンス化して追加しよう。

まずはシーンドックで「Level1」ルートノードを選択したら、チェーンのアイコンをクリックする。
シーンからインスタンスにしてノードに追加

保存済みのシーンのファイルがリストアップされるので、「Mushroom.tscn」ファイルを選択して「開く」をクリックする。もしここで大量のファイルがある場合は上部の検索バーから検索も可能だ。
Mushroom.tscnを選択して開く

「Mushroom.tscn」がインスタンス化されて「Level1」の子ノードになった。
Mushroom.tscnを選択して開く

では「Mushroom」ノードを2Dワークスペース上の任意の場所に移動しよう。すぐに挙動が確認できるように、ゲーム開始時のキャラクターの位置に程よく近く、しかしゲームスタート時は画面に入らないような場所が最適だ。
2Dワークスペースにマッシュルームを設置

ここで一度プロジェクトを実行してマッシュルームの動きを確認しておこう。
デバッグ:マッシュルームの動きを確認

以下の動作について確認できた。

  • マッシュルームがゲーム画面に入ってから動き出した
  • 重力で下に高所から落下した
  • 一定速度で「run」アニメーションを再生しながら前進した
  • ゲーム画面外に出ると動きが止まった
  • プレイヤーキャラクター(今回は壁の代わり)と衝突したら向きを反転した
  • プレイヤーキャラクターに踏まれたら「hit」アニメーション再生後に消えた


Mushroom シーンを編集して動きに変化をつける

せっかく「AnimatedSprite」ノードで「idle」アニメーションを用意したので、これを使って、少しマッシュルームの動きに変化をつけてみよう。

敵キャラクターに少し複雑な動きをつけたい時、「Timer」クラスのノードを追加すると非常に便利だ。では「Mushroom.tscn」シーンに切り替えて、「Mushroom」ルートノードに「Timer」ノードを追加しよう。
Timerノードを追加

次にインスペクターで「Timer」ノードのプロパティを以下のように設定しよう。

  • Wait Time: 4
  • Autostart: オン
    Timerノードのプロパティをインスペクターで編集

これで4秒毎にタイムアウトするタイマーができた。そのタイムアウトのタイミングでプログラムを実行したいので、シグナルを接続しよう。「Timer」ノードを選択して、ノードドックの「シグナル」タブから「timeour()」シグナルを「Mushroom.gd」スクリプトに接続しよう。メソッド名などはデフォルトのままで良い。
Timerノードのtimeoutシグナルを接続

接続できたら、「Mushroom.gd」スクリプトに_on_Timer_timeoutメソッドが追加されただろうか。

func _on_Timer_timeout():
	pass

先に、コードをメンテナンスしやすくするために、以下のように変数timerとして「Timer」ノードの参照を定義しておこう。

onready var timer = $Timer

そして_on_Timer_timeoutメソッドの中身を以下のように更新しよう。

func _on_Timer_timeout():
	if is_on_floor():
		timer.stop()
		set_physics_process(false)
		sprite.play("idle")
		yield(get_tree().create_timer(2), "timeout")
		sprite.play("run")
		set_physics_process(true)
		timer.start()

実装したいのは「マッシュルームは最弱なのですぐに息が切れてしまう」という感じの動きだ。息切れを表現するのに「AnimatedSprite」の「idle」アニメーションを利用する。
空中で息が切れるわけにはいかないので、if 構文でis_on_floorメソッドで true が帰ってきた場合(マッシュルームが地面に接している場合)のみ、それ以下のコードが実行されるようにした。

if 条件式が true だった場合に、以下の内容を実行するコードになっている。これらの動きが 4 秒ごとに発生する。

  • 自動でカウントダウンし続ける「Timer」ノードのstopメソッドにより、タイマーのカウントダウンを停止する。
  • set_physics_processメソッドで引数に false を渡すことで物理プロセスを停止する。これにより「Mushroom.gd」スクリプト内の_physics_processメソッドが停止するのでマッシュルームは前進しない。
  • 「AnimatedSprite」の「idle」アニメーションを再生する。
  • yieldにより、一時的な別のタイマーを作成し、待ち時間を2秒にし、これがタイムアウトするまで待つ。つまり「idle」アニメーションを 2 秒続ける。
  • 一時的なタイマーがタイムアウトしたら、「AnimatedSprite」ノードの「run」アニメーションを再び再生させる。
  • set_physics_processメソッドで引数に true を渡すことで、停止していた物理プロセスを再開する。これでマッシュルームがまた前進を始める。
  • 「Timer」ノードのstartメソッドにより、カウントダウンを再開する。

ではプロジェクトを実行して動きを確認してみよう。
マッシュルームの息切れの動きを確認

見事に息が切れている感じを表現できた。



Part 4 で編集したスクリプトのコード

最後に今回の Part 4 で編集したスクリプトのコードを共有しておくので、必要に応じて確認してほしい。

Enemy.gd の全コード
extends KinematicBody2D # Added @ Part 4


export var gravity: int
export var speed: int
var velocity = Vector2()
onready var sprite = $AnimatedSprite


func _ready():
	set_physics_process(false)
	

func _on_HitBox_body_entered(body):
	if body.is_in_group("Players"):
		print("Player entered in ", self.name)
		sprite.play("hit")
		yield(sprite, "animation_finished")
		queue_free()
		print(self.name, "died")


func _on_VisibilityEnabler2D_screen_entered():
	set_physics_process(true)


func _on_VisibilityEnabler2D_screen_exited():
	set_physics_process(false)
Mushroom.gd の全コード
extends "res://Enemies/Enemy.gd" # Added @ Part 4


onready var timer = $Timer


func _ready():
	sprite.play("run") # Set default animation


func _physics_process(delta):
	if is_on_wall():
		speed *= -1
		sprite.flip_h = !sprite.flip_h
	velocity.x = -speed
	velocity.y += gravity * delta
	velocity = move_and_slide(velocity, Vector2.UP)


func _on_Timer_timeout():
	if is_on_floor():
		timer.stop()
		set_physics_process(false)
		sprite.play("idle")
		yield(get_tree().create_timer(2), "timeout")
		sprite.play("run")
		set_physics_process(true)
		timer.start()


おわりに

以上で Part 4 は完了だ。今回はマッシュルームしか作れなかった割に、なかなかボリューミーな回となった。

次回は、もう少し別の動きをする敵キャラクターを数種類追加して、「Level1」シーンに複数配置するところまでやって、敵キャラクターに関する手順を終える予定なのでお楽しみに。