記事の要約
- Godotのシーンは派生できる
- この時CollisionShapeの上書きには派生元でResource→Local to Scene にチェックが必要
はじめに
コピペコードは後から修正が面倒くさいです。そんな時は基底クラスに抽象的な振る舞いを記述し、派生クラスで具体的な詳細を書きます。
Godotのノードでも同じ事が言えるでしょう。嬉しい事にGodotではシーン派生の機能があります。便利な機能を使わない手はありません。
例えばシューティンゲームで弾・波動砲・ホーミングミサイル・etcを実装する際、これらの基本的な振る舞いは全て同じです。敵に当たればダメージを与え、画面外に出たら自動的に消去するといった振る舞いです。
シーン構成
基底シーン(MovingObject)
Godotのノード実装に置きかえるとArea2Dを親としてCollisionShape、VisibleOnScreenNotifierを子としてもつシーンをイメージしてください。衝突範囲は円形のデフォルトである半径10としておきます。
以降このシーンをMovingObjectと呼びます。
この時、CollisionShapeのResource→Local to Sceneにチェックを入れてください。チェックを付けないと派生先で上書きした内容が基底シーンにも逆伝搬してしまい、これはとても困ります。
他のノードパラメータはこのひと手間を加えなくとも派生先で値のオーバーライドが可能ですが、Collisionの時はその形状等を(恐らくPathで示した先に)保持してるためでしょう。
派生シーン1(Bullet)
ゲーム内では実体(目に見えるスプライト)が必要ですからシーン派生をしてSprite2Dを子ノードに追加します。派生した直後はシーン名が"MovingObject"のままなので"Bullet"に書き換えます。
Bulletシーンでは衝突範囲となる円の半径を3ピクセルに変更しました。
派生シーン2(ChargeBullet)
新たに波動砲のシーンを作成します。MobvingObjectから派生して名前をChargeBulletと変更します。衝突範囲となる円の半径を7ピクセルに拡げ、衝突位置をオフセットで右側に移動させています。
画像は最大時のスプライト。先端部分の太い部分の中心に衝突判定を配置しています。
破壊力が落ちた時のスプライト。こちらのほうがCollisionの大きさと位置が見やすいでしょう。
アタッチするスクリプトの構成
MovingObject(Area2D)ノードには衝突と画面外への到達を受け取るイベントハンドラを実装します。衝突時に与える破壊力や振る舞いは弾の種類により異なるのでここでは実装しません。派生先シーンにアタッチするスクリプト側で記述します。OnAreaEnterd()は派生先でオーバーラードして欲しい事を意図してvirtualキーワードを付けています。基底クラス側はやる事が無いので実装はカラです。
private void _on_visible_on_screen_notifier_2d_screen_exited()
{
disappear();
}
private void _on_area_entered(Area2D area)
{
OnAreaEnterd(area);
}
protected virtual void OnAreaEnterd(Area2D area)
{
}
派生先のBulletクラスやChargeBulletクラスではOnAreaEnterd()をオーバーライドして個々の役割に応じた処理を記述します。
protected override void OnAreaEnterd(Area2D area)
{
EnemyBase enemy = area as EnemyBase;
if(enemy != null){
enemy.Damage(1);
disappear();
}
}
このようにGodotシーンの派生とクラスの派生を組み合わせる事で、コピペコードやコピペシーンのメンテナンスから(多少)開放されます。
動かしてみよう
派生シーンで衝突範囲を変更した事がわかるよう、波動砲の速度を落としています。
あとがき
今回の記事は技術書典でGodotの技術同人誌を入稿した後に、派生シーンでCollisonが原則通り動いてないのは何故だろう...?と試した結果の補完を兼ねています。