第4回目の今回は、敵キャラクターの雛形となるシーンを作成し、それを継承する形で個別の敵キャラクターを一つ作っていく。そのあと、その敵キャラクターをレベルシーンに配置して、プレイヤーキャラクターに踏まれたら消えるところまで実装してみよう。
Memo:
過去のシリーズをまだご覧になっていない方は、そちらを先にご覧いただくことをおすすめします。
Godot で作るプラットフォーマー
敵キャラクターの雛形となるシーンを作ろう
一般的に、プラットフォーマーゲームには複数種類の敵キャラクターが存在する。今回のチュートリアルでは、まず 1 種類、敵キャラクターを作る。そして、次回以降 Part でさらに種類を増やしていこう。
だが、敵キャラクターを一種類ずつ、はじめから作り始めると、それなりに時間がかかる。全ての敵キャラクターに共通の部分を先に雛形のシーンとして作っておき、そのシーンを継承する形でそれぞれの敵キャラクターのシーンを作成すれば効率的だ。
ということで、まずは雛形のシーンから作っていこう。「シーン」メニュー>「新規シーン」を選択する。
「ルートノードを生成」のオプションで「+その他のノード」を選択する。
ここからは、先に必要なノードをシーンツリーに追加していこう。
ルートノードには「KinematicBody2D」クラスのノードを設定する。名前を「Enemy」に変更しておこう。続けてそのルートノードに「AnimatedSprite」ノード、および「CollisionShape2D」ノードを子ノードとして追加する。
さらに「Enemy」ルートノードに「Area2D」クラスのノードを追加し、名前を「HitBox」に変更する。その「HitBox」ノードに「CollisionShape2D」ノードを追加する。この「HitBox」のコリジョン形状は、プレイヤーキャラクターに踏まれた時の衝突検出に使用するためのものだ。
最後に「Enemy」ノードに「VisibilityEnabler2D」ノードを追加する。このノードは、敵キャラクターがゲーム画面の範囲内に入った瞬間や、ゲーム画面内にいた敵キャラクターが画面外に消えた時にシグナルを発信できるので、敵キャラクターの動きの制御に後ほど利用する予定だ。
ここまでで、シーンツリーの構造はシーンドック上で以下のようになったはずだ。
ここからは、インスペクターでいくつかノードのプロパティを編集していく。
「AnimatedSprite」ノードの「Frames」プロパティに「新規 SpriteFrames」を設定する。具体的なアニメーションは、「Enemy」シーンを継承したシーンでそれぞれ行っていく。あとは「Playing」プロパティをオンにしておこう。
「Enemy」ルートノード直下の「CollisionShape2D」ノードの「Shape」プロパティには「新規 CapsuleShape2D」を設定しておく。ただし、具体的なコリジョン形状の調整は「Enemy」シーンを継承したシーンで行う。
次に「HitBox」ノードの子ノードの方の「CollisionShape2D」ノードの「Shape」プロパティには「新規 RectangleShape2D」を設定しておく。こちらも、コリジョン形状の調整は「Enemy」シーンを継承したシーンで行う。
「VisibilityEnabler2D」ノードの「Rect」プロパティの値を「x: -16, y: -16, w: 32, h: 32」にしておこう。
このピンクの四角い範囲がゲーム画面を出た時や入った時にノードがシグナルを発信することができる。
では、ここまでできたら一度シーンを保存しておこう。シーンを保存するためのフォルダを「Enemies」という名前にして作成し、ファイル名を「Enemy.tscn」として保存しよう。ファイルパスは「res://Enemies/Enemy.tscn」になる。
Enemy シーンにスクリプトをアタッチする
次に敵キャラクターの共通部分について、スクリプトの方も作成しておこう。
シーンドックで「Enemy」ルートノードを選択して、スクリプトをアタッチする。この時、先ほどシーンを保存する時に作成した「Enemies」フォルダに保存するようにしよう。スクリプトのファイルパスは「res://Enemies/Enemy.gd」となるはずだ。
では、スクリプトエディタが開いたら、まず敵キャラクターに共通するプロパティを用意しておこう。
extends KinematicBody2D
export var gravity: int
export var speed: int
var velocity = Vector2()
onready var sprite = $AnimatedSprite
gravity
、speed
の2つのプロパティは、敵キャラクターによって、違ったほうが面白そうなので、インスペクターで気軽に更新できるようにexport
キーワードをつけた。この時点では値を指定せず、データ型をint
として指定したのみだ。
velocity
はプレイヤーキャラクター同様、_physics_process
メソッド内で更新させる予定のため、一旦Vector2()
を値にしている。これはx, y座標が(0, 0)の状態だ。
sprite
は「AnimatedSprite」ノードにアクセスするためのプロパティだ。子ノードが読み込まれてから定義する必要があるためonready
キーワードを付けている。
次に敵キャラクターが踏まれたら消える仕組みも、全ての敵キャラクターで共通にしたい。これには「HitBox」ノードの子「CollisionShape2D」ノードを利用する。コリジョン形状は敵キャラクターの頭のてっぺんあたりに配置することを想定している。キャラクターがこれに衝突した時に「HitBox」ノードからシグナルを発信して、それをトリガーにして敵が消える処理を実行するようにしていく。
まずはシグナルの接続からだ。シーンドックで「HitBox」ノードを選択し、ノードドック>シグナルを開き、「body_entered(body: Node)」シグナルをダブルクリック、または選択して右下の「接続」をクリックする。
ダイアログにて「Enemy.gd」スクリプトに接続しよう。「スクリプトに接続:」欄で「Enemy」を選択して下部の「接続」をクリックすればOKだ。
すると、以下のようにシグナルをトリガーにして実行される_on_HitBox_body_entered
メソッドが追加されたはずだ。しかし、メソッドの中身は今のところ空っぽ(pass
のみ)だ。
func _on_HitBox_body_entered(body):
pass
このメソッドの中身をコーディングする前に、先にやっておきたいのが、ノードのグループ設定だ。以下の手順で「Player」シーンの「Player」ノードを新しくノードグループ「Players」を作成してそれに追加しよう。また直近で必要なのは「Player」ノードのグループだが、ついでに「Enemy」シーンの「Enemy」ノードも「Enemies」を作成して追加しておこう。
現在の「Enemy.tscn」シーンファイルは保存して、「Player.tscn」シーンファイルを開く。
シーンドックで「Player」ルートノードを選択する。
ノードドック>「グループ」タブを選択し、入力欄に「Players」とグループ名を入力して「追加」をクリックする。
これで「Player」ノードが「Players」ノードグループに追加された。
今度は「Enemy.tscn」に切り替えて、同様の手順で「Enemy」ノードを「Enemies」ノードグループに追加しておく。
それでは今作ったグループを利用してさっきの_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」に変更しておこう。
この時点で先にシーンを保存しておこう。この時、res://Enemies/ に「Mushroom」フォルダを作成して、その中に「Mushroom.tscn」のファイル名で保存しよう。つまりファイルパスは「res://Enemies/Mushroom/Mushroom.tscn」になる。
Mushroom シーンの各ノードの設定を更新する
継承したシーンではルートノード以外のノードがグレーアウトして表示されているが、全く編集できないわけではないので安心していただきたい。各ノードの編集を順番に行っていく。
Mushroom ルートノードの Script Variables を編集する
まずはルートノードから編集しよう。
Script Variables とはスクリプト内で定義した変数(プロパティ)のことだ。継承元の「Enemy」シーンですでに定義しておいた「gravity」と「speed」の2つにはexport
キーワードをつけておいたので、継承先のシーンでもインスペクターから簡単に値を変更できる。
シーンドックで「Mushroom」ルートノードを選択し、それぞれのプロパティを以下の値に変更しよう。
- Gravity: 512
- Speed: 32
AnimatedSprite ノードのアニメーションを編集する
続いて「AnimatedSprite」ノードの編集を行う。
シーンドックで「AnimatedSprite」ノードを選択したら、インスペクターから「Frames」プロパティで「SpriteFrames」をクリックし、メニューから 「ユニーク化」 を選択する。これにより、この「SpriteFrames」を編集しても、継承元の「Enemy」シーンには影響しない。その結果、今後作成予定の他の「Enemy」からの継承シーンにも影響しない。忘れがちだが、他のシーンへの影響を回避するために重要な操作なので早めの実施を心がけよう。
ここからはアニメーションを作成する。プレイヤーキャラクターでも一度やった作業なので、多少慣れた感じで進めていただけるだろう。
まずシーンドックで「AnimatedSprite」ノードを選択する。エディタ下部で「スプライトフレーム」パネルが表示されるので、そこで編集していく。
以下の3つのアニメーションを追加し、それぞれの速度を 24 FPS の設定にする。スプライトシートが「Asset」フォルダにあるので、それぞれに割り当てよう。
- アニメーション名: hit / スプライトシート: res://Assets/Enemies/Mushroom/Hit.png
- アニメーション名: idle / スプライトシート: res://Assets/Enemies/Mushroom/Idle (32x32).png
- アニメーション名: run / スプライトシート: res://Assets/Enemies/Mushroom/Run (32x32).png
アニメーション「hit」だけはプレイヤーキャラクターに踏まれた時に一回だけ再生してノードを解放する(消す)予定のため、ループの設定をオフにしておこう。
ところで、もし追加したスプライトフレームの画像にブラーがかかっている(ぼやけている)感じであれば、ファイルシステムで、該当のスプライトシートを選択した状態で、インポートドックにて「2D Pixel」プリセットを読み込み、その設定で「再インポート」するとピクセルアート独特のエッジが効いた見た目に修正される。
最後に、それぞれのアニメーションをチェックしておこう。
CollisionShape2D ノードのコリジョン形状を設定する
「Mushroom」シーンには「CollisionShape2D」ノードが2つあるので、それぞれのコリジョン形状を順番に設定していく。
まずは「Mushroom」ルートノード直下の「CollisionShape2D」ノードのコリジョン形状からやっていこう。このコリジョン形状は、敵キャラクター自身が地面や壁、プレイヤーキャラクターなど他のオブジェクトと衝突したことを検知するために使用する。そのため、できるだけスプライトテクスチャのデザインに近い形状にするのが理想だ。
インスペクターから「Shape」プロパティの「CapsuleShape2D」をクリックし、また 「ユニーク化」 を選択して適用しておこう。継承元に影響を与えないためだ。
「CapsuleShape2D」のコリジョン形状は、デフォルトでは縦長の楕円形になっている。「Mushroom」のスプライトテクスチャは、比較的平べったいデザインだ。スプライトテクスチャのデザインにもよるが、横長のデザインの場合は、コリジョン形状を90°回転すると調整しやすいことが多い。
2Dワークスペースでドラッグ操作でコリジョン形状を調整する場合は、ツールバーの「スナップオプション」から「ピクセルスナップを使用」にチェックを入れておこう。
だいたい以下のような形状になればOKだ。足が地面との衝突を正確に検知するため、テクスチャデザインの足の先にきっちり合わせておこう。頭のてっぺんには意図的に少しスペースを設けている。理由は、もう一つのコリジョン形状(プレイヤーキャラクターが敵キャラクターを踏んだことを判定するためのコリジョン形状)をそこに配置する予定だからだ。
このコリジョン形状の「Radius」と「Height」は以下の数値になっている。ドラッグ操作が煩わしい場合は、こちらに直接入力してもらっても構わない。
続いて、もう一つの「HitBox」ノードの子になっている「CollisionShape2D」ノードの方を編集していく。作業自体は大体同じだ。まず、シーンドックで該当の「CollisionShape2D」ノードを選択した状態で、インスペクターから「Shape」プロパティの「RectangleShape2D」を選択する。ここでも忘れずに 「ユニーク化」 を適用しておこう。
今回のコリジョン形状は大体以下のようになればOKだ。ポイントは、ルートノード直下の「CollisionShape2D」の幅より狭くし、それと重ならないようにしてスプライトテクスチャの頭のてっぺんに配置することだ。幅が広いと、踏まずともぶつかっただけでこの敵キャラクターを倒せてしまうし、頭のてっぺんのコリジョン形状が重なると、プレイヤーの方がぶつかられて、やられてしまうかもしれない。
このコリジョン形状の「Extents」プロパティは(8, 1)になっている。
これで、コリジョン形状の調整ができた。インスペクターで編集する内容はひとまずここまでだ。次はいよいよスクリプトの方を更新していく。
Enemy.gd を継承した Mushroom.gd スクリプトをアタッチする
現時点で「Mushroom」ノードには「Enemy.gd」スクリプトがアタッチされている。「Enemy」シーンを継承しているので当然なのだが、このスクリプトは全敵キャラクター共用のスクリプトなので、「Mushroom」シーンのためだけに更新するわけにはいかない。
ひとまず「Enemy.gd」はデタッチ(紐付け解除)しよう。シーンドックで「Mushroom」ノードを右クリック>「スクリプトをデタッチ」を選択する。これで簡単にアタッチされていたスクリプトが外れる。
次に「Mushroom」を選択して、新しいスクリプトをアタッチする。この時、デフォルトの設定のまま作成してしまうと「KinematicBody2D」クラスを継承したスクリプトになってしまう。「継承元」の右側にあるフォルダアイコンをクリックしてみよう。
すると、作成済みのスクリプトを選択できる。「res://Enemies/Enemy.gd」を選択して「開く」をクリックしてみよう。
これで「継承元」として「res://Enemies/Enemy.gd」が選択された状態になった。新しいスクリプトのファイルパスを「res://Enemies/Mushroom/Mushroom.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 の値をそれぞれ設定し、そのvelocity
をmove_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」がインスタンス化されて「Level1」の子ノードになった。
では「Mushroom」ノードを2Dワークスペース上の任意の場所に移動しよう。すぐに挙動が確認できるように、ゲーム開始時のキャラクターの位置に程よく近く、しかしゲームスタート時は画面に入らないような場所が最適だ。
ここで一度プロジェクトを実行してマッシュルームの動きを確認しておこう。
以下の動作について確認できた。
- マッシュルームがゲーム画面に入ってから動き出した
- 重力で下に高所から落下した
- 一定速度で「run」アニメーションを再生しながら前進した
- ゲーム画面外に出ると動きが止まった
- プレイヤーキャラクター(今回は壁の代わり)と衝突したら向きを反転した
- プレイヤーキャラクターに踏まれたら「hit」アニメーション再生後に消えた
Mushroom シーンを編集して動きに変化をつける
せっかく「AnimatedSprite」ノードで「idle」アニメーションを用意したので、これを使って、少しマッシュルームの動きに変化をつけてみよう。
敵キャラクターに少し複雑な動きをつけたい時、「Timer」クラスのノードを追加すると非常に便利だ。では「Mushroom.tscn」シーンに切り替えて、「Mushroom」ルートノードに「Timer」ノードを追加しよう。
次にインスペクターで「Timer」ノードのプロパティを以下のように設定しよう。
- Wait Time: 4
- Autostart: オン
これで4秒毎にタイムアウトするタイマーができた。そのタイムアウトのタイミングでプログラムを実行したいので、シグナルを接続しよう。「Timer」ノードを選択して、ノードドックの「シグナル」タブから「timeour()」シグナルを「Mushroom.gd」スクリプトに接続しよう。メソッド名などはデフォルトのままで良い。
接続できたら、「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」シーンに複数配置するところまでやって、敵キャラクターに関する手順を終える予定なのでお楽しみに。