6章 : 当たり判定の機能を使ってみよう
5章では敵機と敵弾の作成までを行いました。しかしこのままでは当たり判定がないただのオブジェクトになってしまいます。
本章では敵機と敵弾に当たり判定をつけていきます。
当たり判定仕組み
「当たり判定」とは読んで字の如く、オブジェクト同士が当たっているかどうか判定するものです。当たり判定の実装方法には色々な手法が考えられますが、今回は簡単のために「円同士の当たり判定をピクセル単位で取る」という方法を扱います。
例えば下のような2つの円のオブジェクトを考え、2つの円の中心座標を考えてみましょう。2つの円の半径をa, bとおき、2つの円のx座標の差をd、y座標の差をeとおくと、中学校で習うような「三平方の定理」よりd²+e²<(a+b)²ならば2つの円は「ぶつかっている」ということになりますね。
当たり判定実装
当たり判定の仕組みは上で説明したとおりですが、自分で一から実装するのは面倒なのでAltseed2ではこの当たり判定をまとめたクラスであるCircleCollider
が用意されています。
早速このCircleCollider
を使って当たり判定を実装していきましょう。
今回は自機、自弾、敵機、敵弾の4つに当たり判定をつけるので基底クラスとしてCollidableObject
クラスを用意してそれを継承していくような実装にします。
継承については以下を参照してください。
CollidableObject
のコードは以下のようになります。
using System.Collections.Generic;
using Altseed2;
namespace Tutorial
{
// 衝突可能なオブジェクト(円形)
public class CollidableObject : SpriteNode
{
// コライダのコレクション
public static HashSet<CollidableObject> objects = new HashSet<CollidableObject>();
// コライダ
protected CircleCollider collider = new CircleCollider();
// OnUpdate内で衝突判定を調査するかどうか
protected bool doSurvey;
// 所属するメインノードへの参照
public MainNode mainNode;
// コンストラクタ
public CollidableObject(MainNode mainNode, Vector2F position)
{
// メインノードへの参照を設定
this.mainNode = mainNode;
// コライダの座標を設定
collider.Position = position;
// 座標を設定
Position = position;
}
// エンジンに追加された時に実行
protected override void OnAdded()
{
// コライダのコレクションに自身を追加
objects.Add(this);
}
// エンジンから削除された時に実行
protected override void OnRemoved()
{
// コライダのコレクションから自身を削除
objects.Remove(this);
}
// フレーム毎に実行
protected override void OnUpdate()
{
// フラグが成立時に衝突判定を実行
if (doSurvey)
{
Survey();
}
// コライダの座標を更新
collider.Position = Position;
}
// 衝突時に実行
private void CollideWith(CollidableObject obj)
{
// nullだったら終了
if (obj == null)
{
return;
}
// 衝突対象がSurveyを実行しないオブジェクトだった場合,相手のOnCollisionも実行
if (!obj.doSurvey)
{
obj.OnCollision(this);
}
// 自身のOnCollisiionを実行
OnCollision(obj);
}
// 衝突時に実行される内容をオーバーライドして設定できる
protected virtual void OnCollision(CollidableObject obj)
{
}
// 画面外に出た時自身を消去
protected void RemoveMyselfIfOutOfWindow()
{
var halfSize = Texture.Size / 2;
if (Position.X < -halfSize.X
|| Position.X > Engine.WindowSize.X + halfSize.X
|| Position.Y < -halfSize.Y
|| Position.Y > Engine.WindowSize.Y + halfSize.Y)
{
// 自身を削除
Parent?.RemoveChildNode(this);
}
}
// 衝突判定を調査する
private void Survey()
{
// objects内の全オブジェクトを検索し,衝突が確認されたオブジェクト間でCollideWithを実行
foreach (var obj in objects)
if (collider.GetIsCollidedWith(obj.collider))
CollideWith(obj);
}
}
}
これまでSpriteNode
を継承していたため、CollidableObject
でもSpriteNode
を継承しています。
また、Enemy
クラスとBullet
クラスに定義していたRemoveMyselfIfOutOfWindow
関数ですが同じ処理が二か所にあって冗長です。基本的に同じ処理は一か所にまとめた方が良いのでそれぞれの親クラスになるCollidableObject
でこの関数を定義することにします。
変数とコンストラクタを解説していきます。
// コライダのコレクション
public static HashSet<CollidableObject> objects = new HashSet<CollidableObject>();
// コライダ
protected CircleCollider collider = new CircleCollider();
// OnUpdate内で衝突判定を調査するかどうか
protected bool doSurvey;
// 所属するメインノードへの参照
public MainNode mainNode;
// コンストラクタ
public CollidableObject(MainNode mainNode, Vector2F position)
{
// メインノードへの参照を設定
this.mainNode = mainNode;
// コライダの座標を設定
collider.Position = position;
// 座標を設定
Position = position;
}
それぞれのコライダとの当たり判定をとるためにコライダのコレクションを保存しておく必要があります。コライダオブジェクトは変数objects
に保存します。
この変数objects
の宣言にはstatic
というキーワードが使われています。これは静的メンバーと呼ばれるもので、すべてのインスタンスから共有されるような変数を宣言できます。詳しい説明は以下のリンク先を参照してください。
変数collider
はコライダの本体で、先ほど言ったようにAltseed2で用意されたCircleCollider
を使用します。
doSurvey
とmainNode
はコメントにある通りです。
コンストラクタのcollider.Position = position
はコライダの位置設定でPosition = position
は本体の描画されているオブジェクトの位置設定であることに注意してください。
次にOnUpdate
です。
// フレーム毎に実行
protected override void OnUpdate()
{
// フラグが成立時に衝突判定を実行
if (doSurvey)
{
Survey();
}
// コライダの座標を更新
collider.Position = Position;
}
先ほど定義したdoSurvey
のフラグがtrue
の場合後述するSurvey
関数が呼ばれて当たり判定が開始します。
オブジェクトが動いてもコライダの位置は変わらないのでコライダとオブジェクトの位置と同期させるためにcollider.Position = Position;
としています。
さらにOnAdded
関数とOnRemoved
関数があると思います。こちらはエンジンにオブジェクトが追加されたタイミングと消去されたタイミングで呼ばれます。この時変数objects
に追加と削除をしてエンジンに追加されているオブジェクトのみをコレクションに残しておきます。
Survey関数です。
// 衝突判定を調査する
private void Survey()
{
// objects内の全オブジェクトを検索し,衝突が確認されたオブジェクト間でCollideWithを実行
foreach (var obj in objects)
if (collider.GetIsCollidedWith(obj.collider))
CollideWith(obj);
}
コライダ間の衝突はGetIsCollidedWith
関数で取ることができます。この関数は衝突している場合true
を返すのでif文で衝突した場合にCollideWith
が呼ばれます。CollideWith
には衝突した場合の処理を書いていきます。
ここで、foreach
とはfor
文の拡張で、
foreach (var obj in objects)
というのは、 「 objects
の要素をとりだして、 obj
と名前を付ける」 ことをobjects
の全要素について行ってくれます。
今回はforeach
文を使ってobjects
から取り出したCollidableObject
であるobj
のコライダobj.collider
と自身のコライダcollider
の間での当たり判定を取っています。
また、var
というキーワードがあります。これは型推論と呼ばれるもので、名前の通り変数の宣言の際に型を推論してくれるというものです。なので上のforeach
文は
foreach (CollidableObject obj in objects)
if (collider.GetIsCollidedWith(obj.collider))
CollideWith(obj);
このようにしても大丈夫です。ただ、var
を使ったほうが記述が短くて楽です。
foreach
とvar
についての詳しい解説を以下に載せておきます。
C# によるプログラミング入門 : foreach
C# によるプログラミング入門 : 型推論
CollideWith
関数では衝突時の処理を書いていきます。
// 衝突時に実行
private void CollideWith(CollidableObject obj)
{
// nullだったら終了
if (obj == null)
{
return;
}
// 衝突対象がSurveyを実行しないオブジェクトだった場合,相手のOnCollisionも実行
if (!obj.doSurvey)
{
obj.OnCollision(this);
}
// 自身のOnCollisiionを実行
OnCollision(obj);
}
ここで,Survey
を実行しないオブジェクト(=doSurvey
がfalse
)に対してOnCollision
を呼び出しています。
何故,全てのCollidableObject
に対してSurvey
を実行させず,doSurvey
のような面倒な処理を挟むのかというのが気になるかと思います。
後々説明しますが,doSurvey
フラグは自機や敵ではtrue
,自機弾や敵弾ではfalse
にします。
もし仮に全てのCollidableObject
にてSurvey
を走らせるとなると,衝突判定が計算される回数はobjects
に登録されているCollidableObject
の2乗に相当します。
弾というオブジェクトは,自機や敵の個数に比べて大量に画面上に出現する機会が多いです。その為,弾幕シューティングを作ったときなどは処理が重くなることがあります。
それを避けるためにdoSurvey
というフラグを用いてSurvey
を実行する回数を最小限に留める事が出来ます。
プログラミングを行う際はこのようにパフォーマンスを意識するという事も大事です(最初のうちは動くこと重視,慣れてきたら意識すると良いです)。
オブジェクトにより当たった時の処理は異なるので継承先でOnCollision
関数をオーバーライドさせて処理を継承先に委託するようにしています。
OnCollision
関数にあるキーワードvirtual
は仮想メソッドと呼ばれるものでこれをつけることで継承先で関数のオーバーライドができます。
// 衝突時に実行される内容をオーバーライドして設定できる
protected virtual void OnCollision(CollidableObject obj)
{
}
当たり判定を持つクラスへの切り替え
ざっとですが基本的なコライダの使い方を解説しました。
次は継承先での処理を作っていきましょう。
まずは各クラスをCollidableObject
から継承させるようにします。
Player
クラス
- public class Player : SpriteNode
+ public class Player : CollidableObject
Enemy
クラス
- public class Enemy : SpriteNode
+ public class Enemy : CollidableObject
Bullet
クラス
- class Bullet : SpriteNode
+ class Bullet : CollidableObject
さらにコンストラクタも書き換えていきます。
冒頭でも少し触れましたがEnemy
クラスとBullet
クラスのRemoveMyselfIfOutOfWindow
関数は親クラスであるCollidableObject
に移したのでついでに削除しましょう。
また,CollidableObject
のOnUpdate
に処理を書いたので,base.OnUpdate
を呼び出しましょう。
Player
クラス
using Altseed2;
namespace Tutorial
{
// プレイヤーのクラス
public class Player : CollidableObject
{
// コンストラクタ
- public Player(Vector2F position)
+ public Player(MainNode mainNode, Vector2F position) : base(mainNode, position)
{
- // 座標を設定
- Position = position;
+ // 衝突判定を行うように設定
+ doSurvey = true;
// テクスチャを読み込む
Texture = Texture2D.LoadStrict("Resources/Player.png");
// 中心座標を設定
CenterPosition = ContentSize / 2;
+ // コライダの半径を設定
+ collider.Radius = Texture.Size.Y / 2;
}
// フレーム毎に実行
protected override void OnUpdate()
{
// 移動を実行
Move();
// ショットを実行
Shot();
+ // CollidableObjectのOnUpdate呼び出し
+ base.OnUpdate();
}
...略...
}
}
Enemy
クラス
using Altseed2;
namespace Tutorial
{
// 敵の基礎となるクラス
public class Enemy : CollidableObject
{
// 倒された時に加算されるスコアの値
protected int score;
// プレイヤーへの参照
protected Player player;
// コンストラクタ
- public Enemy(Player player, Vector2F position)
+ public Enemy(Player player, Vector2F position) : base(player.mainNode, position)
{
+ // 衝突判定を行うように設定
+ doSurvey = true;
- // 座標を設定
- Position = position;
// プレイヤーへの参照を設定
this.player = player;
}
// フレーム毎に実行
protected override void OnUpdate()
{
// CollidableObjectのOnUpdateを実行
base.OnUpdate();
+ // 画面外に出たら自身を削除
+ RemoveMyselfIfOutOfWindow();
}
- private void RemoveMyselfIfOutOfWindow()
- {
- var halfSize = Texture.Size / 2;
- if (Position.X < -halfSize.X
- || Position.X > Engine.WindowSize.X + halfSize.X
- || Position.Y < -halfSize.Y
- || Position.Y > Engine.WindowSize.Y + halfSize.Y)
- {
- // 自身を削除
- Parent?.RemoveChildNode(this);
- }
- }
}
}
Bullet
クラス
using Altseed2;
namespace Tutorial
{
// 弾のクラス
public class Bullet : CollidableObject
{
// フレーム毎に進む距離
private Vector2F velocity;
// コンストラクタ
- public Bullet(Vector2F position, Vector2F velocity)
+ public Bullet(MainNode mainNode, Vector2F position, Vector2F velocity) : base(mainNode, position)
{
+ // 衝突判定を行わないように設定
+ doSurvey = false;
- // 座標を設定
- Position = position;
// 弾速を設定
this.velocity = velocity;
// 表示位置をプレイヤーや敵より奥に設定
ZOrder--;
}
// フレーム毎に実行
protected override void OnUpdate()
{
// 座標を速度分進める
Position += velocity;
+ // CollidableObjectのOnUpdateを呼び出す
+ base.OnUpdate();
// 画面外に出たら自身を削除
RemoveMyselfIfOutOfWindow();
}
- private void RemoveMyselfIfOutOfWindow()
- {
- var halfSize = Texture.Size / 2;
- if (Position.X < -halfSize.X
- || Position.X > Engine.WindowSize.X + halfSize.X
- || Position.Y < -halfSize.Y
- || Position.Y > Engine.WindowSize.Y + halfSize.Y)
- {
- // 画面外に出たら自身を削除
- Parent?.RemoveChildNode(this);
- }
- }
}
}
ここでコンストラクタの後ろにbase
というキーワードが出てきました。これは親クラスのコンストラクタ呼び出しという意味です。今回だとColliderObject
のコンストラクタを呼び出します。ColliderObject
のコンストラクタではMainNode
とposition
が必要なためbase
の後の引数で受け渡します。base
についての詳しい解説はこちらを参照してください。
また、ColliderObject
のコンストラクタで座標を設定する処理があるので子クラスでは座標を設定するコードは消しています。
Bullet
の修正に併せてEnemyBullet
とPlayerBullet
のコードも修正していきましょう。
これらはコンストラクタの引数変更と半径を設定させるだけで大丈夫です
EnemyBullet
クラス
+ public EnemyBullet(MainNode mainNode, Vector2F position, Vector2F velocity) : base(mainNode, position, velocity)
- public EnemyBullet(Vector2F position, Vector2F velocity) : base(position, velocity)
{
// テクスチャを読み込む
Texture = Texture2D.LoadStrict("Resources/Bullet_Red.png");
// 中心座標を設定
CenterPosition = ContentSize / 2;
+ // 半径を設定
+ collider.Radius = Texture.Size.X / 2;
}
PlayerBullet
クラス
+ public PlayerBullet(MainNode mainNode, Vector2F position) : base(mainNode, position, new Vector2F(10f, 0.0f))
- public PlayerBullet(Vector2F position) : base(position, new Vector2F(10f, 0.0f))
{
// テクスチャを読み込む
Texture = Texture2D.LoadStrict("Resources/Bullet_Blue.png");
// 中心座標を設定
CenterPosition = ContentSize / 2;
+ // 半径を設定
+ collider.Radius = Texture.Size.X / 2;
}
次にEnemy
クラスの変更に併せてその派生クラスであるChaseEnemy
クラスとRadialShotEnemy
クラスとStraightShotEnemy
クラスとMeteor
クラスを書き換えていきます。先ほどEnemyBullet
クラスの引数を変更したのでShot
関数のEnemyBullet
を生成するコードもついでに書き換えましょう。
ChaseEnemy
クラス
public ChaseEnemy(Player player, Vector2F position, float speed) : base(player, position)
{
// テクスチャを読み込む
Texture = Texture2D.LoadStrict("Resources/UFO.png");
// 中心座標を設定
CenterPosition = ConentSize / 2;
+ // 半径を設定
+ collider.Radius = Texture.Size.X / 2;
// 移動速度を設定
this.speed = speed;
// 自身が倒された時に加算されるスコアを設定
score = 10;
}
RadialShotEnemy
クラス
public RadialShotEnemy(Player player, Vector2F position, int shotAmount) : base(player, position)
{
// 撃ち出すショットの個数を設定
this.shotAmount = shotAmount;
// テクスチャを読み込む
Texture = Texture2D.LoadStrict("Resources/UFO.png");
// 中心座標を設定
CenterPosition = ContentSize / 2;
+ // 半径を設定
+ collider.Radius = Texture.Size.X / 2;
// スコアを設定
score = 30;
}
// 弾を撃つ
private void Shot(Vector2F velocity)
{
// 敵弾を画面に追加
+ Parent.AddChildNode(new EnemyBullet(mainNode, Position, velocity));
- Parent.AddChildNode(new EnemyBullet(Position, velocity));
}
StraightShotEnemy
クラス
public StraightShotEnemy(Player player, Vector2F position) : base(player, position)
{
// テクスチャを読み込む
Texture = Texture2D.LoadStrict("Resources/UFO.png");
// 中心座標を設定
CenterPosition = ContentSize / 2;
+ // 半径を設定
+ collider.Radius = Texture.Size.X / 2;
// 倒された時に加算されるスコアを設定
score = 20;
}
// 弾を撃つ
private void Shot(Vector2F velocity)
{
// 敵弾を画面に追加
+ Parent.AddChildNode(new EnemyBullet(mainNode, Position, velocity));
- Parent.AddChildNode(new EnemyBullet(Position, velocity));
}
Meteor
クラス
public Meteor(Player player, Vector2F position, Vector2F velocity) : base(player, position)
{
// 速度の設定
this.velocity = velocity;
// テクスチャの設定
Texture = Texture2D.LoadStrict("Resources/Meteor.png");
// 中心座標の設定
CenterPosition = ContentSize / 2;
+ // 半径の設定
+ collider.Radius = Texture.Size.X / 2;
// スコアの設定
score = 1;
}
さらにPlayer
クラスでPlayerBullet
を使用していたのでこちらも修正が必要になります。
Player
クラスの Shot
関数
// ショット
private void Shot()
{
// Zキーでショットを放つ
if (Engine.Keyboard.GetKeyState(Key.Z) == ButtonState.Push)
{
+ Parent.AddChildNode(new PlayerBullet(mainNode, Position));
- Parent.AddChildNode(new PlayerBullet(Position));
}
}
最後にMainNode
クラスを修正します。まず、Player
クラスの呼び出しの変更が必要です。さらにもう一つ変更が必要です。もしMainNode
が消去されてもコライダがコレクションに残っている場合、使われていないコライダがコレクションに保存され続けるとになります。この場合、ゲームのリトライなどを行うとリトライ前のコライダが残ってしまい、バグなどを引き起こす恐れがあるのでコライダの消去を行います。HashSet
クラスの中身消去はClear
関数でできます。
MainNode
クラス
protected override void OnAdded()
{
// ================================================================
// 省略
// ================================================================
// プレイヤーを設定
+ player = new Player(this, new Vector2F(100, 360));
- player = new Player(new Vector2F(100, 360));
// ================================================================
// 省略
// ================================================================
}
+ // エンジンから削除されたときに実行
+ protected override void OnRemoved()
+ {
+ // 衝突判定を全てリセット
+ CollidableObject.objects.Clear();
+ }
これでひと段落と思いきや、衝突時の処理をまだ書いていないので衝突してもまだ何も起こりません。次にそれぞれのクラスで衝突した時の処理を書いていきたいところですが、先に衝突したときのエフェクトを作りましょう。
Effectを作成するクラス
衝突したときに出すエフェクトであるDeathEffect
クラスを作ります。
DeathEffect
のコードは以下のようになります。
using Altseed2;
namespace Tutorial
{
// 死亡時エフェクト
public class DeathEffect : SpriteNode
{
// カウンタ
private int count = 0;
// コンストラクタ
public DeathEffect(Vector2F position)
{
// 座標を設定
Position = position;
// テクスチャを読み込む
Texture = Texture2D.LoadStrict("Resources/Explosion.png");
// 中心座標を設定
CenterPosition = new Vector2F(32f, 32f);
// 表示位置をプレイヤーや敵よりも手前に設定
ZOrder++;
// テクスチャの描画範囲を設定
Src = new RectF(new Vector2F(), new Vector2F(Texture.Size.X / 9, Texture.Size.Y));
}
// フレーム毎に実行
protected override void OnUpdate()
{
// 表示されるテクスチャのサイズを取得
var size = new Vector2F(Texture.Size.X / 9, Texture.Size.Y);
// 表示されるテクスチャの左上の座標を計算する
var pos = new Vector2F(size.X * (count / 2 % 9), size.Y);
// 描画範囲を設定
Src = new RectF(pos, size);
// カウントを進める
count++;
// カウントが18以上で自身を削除
if (count >= 18)
{
Parent.RemoveChildNode(this);
}
}
}
}
どのように実装されているかというと以下のような画像の一部を表示して表示位置をずらしてあげることでアニメーションのような効果を出しています。
コード中で使われているSrc
について説明します。今まではTexture
に設定された画像全てを描画していましたが、今回は画像の一部だけを切り取って描画する必要があります。
そこで、Src
を使用します。Src
はSpriteNode
クラスの持つフィールドで、Src
に値を設定すると画像の中で指定された範囲のみが描画されるようになります。Src
に値を設定する方法ですが、第一引数は表示したい範囲の左上の座標を、第二引数はその座標から表示する範囲を指定します
// 描画範囲を設定
Src = new RectF(pos, size);
設定する変数size
とpos
はcount
という毎フレーム1ずつ増える整数の変数を作って計算します。今回は2フレームごとに画像をずらすような式にしてあります。上に示した画像は爆破の画像が横に9枚並んでいるもので、2フレーム×9=18なのでcount
が18になった場合Parent.RemoveChildNode(this);
により自身を削除してエフェクトの再生を終了します。
// 表示されるテクスチャのサイズを取得
var size = new Vector2F(Texture.Size.X / 9, Texture.Size.Y);
// 表示されるテクスチャの左上の座標を計算する
var pos = new Vector2F(size.X * (count / 2 % 9), size.Y);
// 描画範囲を設定
Src = new RectF(pos, size);
// カウントを進める
count++;
// カウントが18以上で自身を削除
if (count >= 18)
{
Parent.RemoveChildNode(this);
}
爆破のエフェクトが完成したので衝突時の処理を書いていきましょう。
OnCollideの実装
CollidableObject
では衝突したときにOnCollide
関数を呼び出すように実装しましたね。なので子クラスでOnCollide
関数の中身を記述してあげればよいです。
衝突時に処理するクラスはPlayer
とEnemy
とPlayerBullet
とEnemyBullet
の4クラスになります。
これら4つのOnCollide
関数を以下に載せます。
Player
クラス
// 衝突時に実行
protected override void OnCollision(CollidableObject obj)
{
// 衝突対象が敵か敵の弾だったら
if (obj is Enemy || obj is EnemyBullet)
{
// 自身を親から削除
Parent.RemoveChildNode(this);
}
}
PlayerBullet
クラス
// 衝突時に実行
protected override void OnCollision(CollidableObject obj)
{
// 衝突対象が敵だったら自身を削除
if (obj is Enemy)
{
Parent?.RemoveChildNode(this);
}
}
Enemy
クラス
// 衝突時に実行
protected override void OnCollision(CollidableObject obj)
{
// 衝突対象が自機弾だったら
if (obj is PlayerBullet)
{
// 死亡時エフェクトを再生
Parent.AddChildNode(new DeathEffect(Position));
// 自身を削除
Parent.RemoveChildNode(this);
}
}
EnemyBullet
クラス
// 衝突時に実行
protected override void OnCollision(CollidableObject obj)
{
// 衝突対象がプレイヤーだったらBulletのOnCollisionを実行して削除
if (obj is Player)
{
Parent?.RemoveChildNode(this);
}
}
ここでis
というキーワードがありますね。これはis演算子と呼ばれるものです。このis
というのは変数obj
がどの型を継承しているのか判断するために使えます。一例ですが、if (obj is Enemy)
と書けば、obj
がEnemy
クラスか、その派生クラスの時に処理をすることができます。
余談ですが似たような機能にas演算子というものがあります。こちらは戻り値がbool
ではなく型変換したものになります。
is演算子とas演算子について詳しく知りたい方は以下を参照してください。
Meteorクラス改変
今、Meteor
クラスの衝突処理はEnemy
クラスのOnCollide
が呼ばれるので、Player
の弾に当たると消滅します。これで完成してもよいのですが、Meteor
というからには岩石で硬いはずなのでPlayer
の弾ごとき3回くらいまでなら耐えると思います。 そのように改変しましょう。
Meteor
クラス
using Altseed2;
namespace Tutorial
{
// 隕石
public class Meteor : Enemy
{
// フレーム毎の移動速度
private Vector2F velocity;
+ // HP
+ private int HP = 3;
// コンストラクタ、OnUpdate略
+ protected override void OnCollision(CollidableObject obj)
+ {
+ // 衝突したのが自機弾だったら
+ if (obj is PlayerBullet)
+ {
+ // HPを1減らす
+ HP--;
+ // HPが0になったらEnemyクラスのOnCollisionを呼び出して削除
+ if (HP == 0)
+ {
+ base.OnCollision(obj);
+ }
+ }
+ }
+ }
}
HPというフィールドを追加して、プレイヤーの弾に当たる度にHPを1減らしていき、HPが0になったら消滅するというシンプルな処理です。
このようにEnemy
クラスでオーバーライドしたOnCollision
をさらにオーバーライドすると親クラスのOnCollision
は呼ばれなくなり、子クラスの処理に切り替わります。
長い工程を経て衝突判定と衝突時の処理が完成しました。実行してみると敵に衝突したり、自弾が敵に衝突したときにエフェクトが出て画面から消えることが確認できると思います。
まとめ
今回は衝突判定をつけてみました。ようやくゲームらしさが増してきましたね。
Altseed2
ではCircleCollider
以外にも多角形のコライダであるPolygonCollier
クラスや四角形のコライダであるRectangleCollider
クラスがあります。もし、厳密さが必要な形のオブジェクトに衝突判定をつけたい場合はそちらを使ってみてください。
次章では音を鳴らしてみます。