下から叩くと画面を揺らし、画面上にいる敵キャラを全部倒す、というブロックをUnity2Dで再現してみました!一気に敵を一掃できる爽快感溢れるアクションになりますが、これを上手くやるためにはカメラを揺らすだけでなく、ポリモーフィズムと継承を上手く活用する必要があります。
敵キャラをまとめて処理
敵キャラといっても、様々なキャラがいます。マリオで言えばクリボー、ノコノコをはじめとした多くのキャラクタがおり、多様な動きをしています。
クラスを横断する一括処理を実現させるにはポリモーフィズム を使います。いろいろな敵キャラを"ひとまとめ"にし、一括して処理する必要があるからです。
つまり、"衝撃波で攻撃する"という処理を各キャラに行うわけですが、、ひとつのアクション対してのそれぞれ違ったリアクションを取らせる、ということになります。それをシンプルにコーディングするには、継承とポリモーフィズムによる抽象化が必要です。
継承とポリモーフィズム
継承とはコードの量を減らすためのアイディアで、ポリモーフィズム は複雑な処理を人間的な感覚でシンプルに書き表すためのテクニックです。
敵キャラクターという概念があり、そこから様々な具体的なキャラクターに派生します。各々のキャラクタは違いがあるといえど、基本的なパラメータ、動作を共通しています。体力があるかもしれませんし、移動するでしょうし、プレイヤーへの攻撃動作もあるかもしれません。そうした共通部分をスーパークラス、基底クラスで定義します。
それを継承した各々の具体的なキャラの独自性の定義は、そのキャラのクラスですれば良いわけです。そうすれば、各敵キャラに共通する部分は省略することが出来ます。
ポリモーフィズムとは、要は共有されたメソッドの中身をそのクラスにあった内容にする、実装を変えるということです。つまり、移動する、Move()というメソッドがあったとして、あるキャラは歩いて移動するかもしれませんし、他のキャラは飛び跳ねる、または飛ぶ。魚系のキャラはそれが泳ぐ、ということになるかもしれません。今回の例だと、ボール型と花型の敵は動かないですが。
また、"プレイヤーに踏まれる"という動作などについては、クリボーはペッタンコになってしまいますし、ノコノコは殻にひっこみます。トゲゾーは何も起きず逆にマリオがやられてしまいます。つまり、同じ動作に対して、違った反応、結果が定義されていることになります。
ポリモーフィズム(多態性)とは、違った形態を持たせるということです。
Unity上での継承
Unityで継承する際には、UpdateなどのUnity固有のメソッドも各クラスに継承させる必要があります。この時、protectedを使います。親子関係にあるクラス間のみアクセス可能、となります。そして、継承クラスのUpdate内で、base.Update()とすることで、基底クラスのUpdateを実行します。
↓このクラスで定義した、プレイヤーがブロックを叩いた時の処理は継承されたクラス内のUpdate内にbase.Update()と記述することで引き継がれます。
衝撃波の出るブロックは原型となるBoxControllerクラスから派生しています。アイテムが出るボックスもありますし、いろいろあるボックスのうちのひとつです。
どんなボックスでもプレイヤーがしたからジャンプで叩くと飛び跳ねるという動作を共有しています。継承してしまえば、各クラスごとにこのコードを書く必要がなくなります。繰り返しますが、継承とは余分な、重複するコードを無くし、読まなければいけないコードの量を減らすためのテクニックです。
↓はShockBoxControllerのスクリプトですが、base.Update()があります。
BoxController内のvirtual Updateでの処理が実行されることになります。
Unity上でのポリモーフィズム
基底クラスでvirtualとして定義したメソッドを継承クラスでoverrideすることで、そのクラス独自の動作を出来るようにします。今回で言えば、ブロックを叩いた後に、敵がやられるわけですが、"どうやられるか"という反応をそれぞれ違ったものにするということです。
あるキャラは吹っ飛ぶかもしれませんし、あるキャラはバラバラになるかもしれません。まったく効かず、微動だにしないキャラもいるかもしれません。今回、衝撃波によるダメージを"DamagedDeath"というメソッドとして定義していますが、させたい動作をそれぞれコーディングすればいいわけです。
Enemyクラスでは中身がありませんが、地を這う敵キャラは"飛び跳ねて下に落ちる"という処理をしています。花の敵キャラは"逆さまになって、まっすぐ下に落ちる"ようにしています。
死亡判定されると、まず回転させて、下に移動させるという処理で、動きを作っています。
まとめて処理する!(アップキャスト)
さて、では各クラスでoverrideとして定義した"DamagedDeath"メソッドをどう活用すれば良いか!それには各キャラをおおざっぱに"敵キャラ"として認知する必要があります。
UnityにはPhysics2Dに、ある地点の周辺のコライダーを集めるOverlapBoxAllメソッドがあります。つまり、これを使って画面上の敵キャラのコライダーを集めます。
このコライダーを使って、ボックスのクラス内にEnemyクラスのインスタンス を作ります。つまり、いろんな敵キャラを具体的な各々のクラスのインスタンス として、生成するのではなく、漠然とした"敵キャラ"のインスタンス として生成するということで、柔軟性を持たせます。これこそ"抽象化"になります。詳細を無視して、概念的に物事を捉えるということです。
つまり、様々なキャラをひとまとめに出来、大らかな同タイプのオブジェクト群として扱えるようになります。
enemy.DamagedDeath();
なので、そうして集めた"敵キャラ"の集合に対して、ボックスは「衝撃波出すので、各々リアクション取ってね!」とお願いします。つまり、"DamagedDeath"が実行されます。つまり、各クラスで実装された異なる処理がなされるということになります。
カメラを揺らす!
今回の件で更に重要なのは、視覚効果であるカメラの揺れです。これは何にでも使えるアイディアですよね。爆弾が爆発した時にも使えそうですし、いろいろ使い道があります。注意としては、やりすぎるとプレイヤーが酔ってしまうということですね。特に縦揺れはキツイ。
変数など。動く幅、スピードなどの変数を宣言。
やることは多いので、揺れを起動するメソッドをShockActivationとして定義。叩いた時点でのカメラの位置の検出、ブロックを光らせるなど、様々なヘルパーメソッドを実行します。
これには動く足場の件で学んだことを生かしています。まぁ、サイン波に沿ってカメラを一定時間揺らす、というだけのことなんですけどね。
ブロックを叩いた瞬間のカメラの位置を中央"Center"として、そこから左右に揺れ動く、という処理にしています。叩いた瞬間を最大の揺れにして、時間経過ごとに揺れとスピードが弱まっていく、という処理にしています。それぞれ、speed, widthにしていますが、毎フレームごとにdeltaTime分を引いていく、という処理です。
cam.transform.position = center +
new Vector3("横揺れ", "縦揺れ", 0);
縦揺れも加えていますが、揺れの周期も幅も横揺れよりも弱くしています。
より、リアルに。
揺れている!という感じを更に感じさせるにはどうすればいいのか。プレイヤーが一定時間動けなくなるのがいいなと思いました。実際、そういうゲームは多いですよね、地面が揺れてるとプレイヤーは動けなくなる、みたいな。ロックマン のガッツマンとかもそうでしたね。
これは単純にブロック側のスクリプトがプレイヤーキャラ操作のスクリプト を一定時間、非アクティブにすることで実現しています。コレをやると、画面が揺れている間、プレイヤは動けなくなるので、あぁスゴイ揺れなんだなーという演出になります。FindGameObjectWithTagでプレイヤーとカメラのオブジェクトを探して、GetComponentから直接、オンオフにするのが楽ですが、少し長いのでStopPlayer()というヘルパーメソッドを作りました。
より、白熱した体験を!
今回の動作を再現して思ったのは、やっぱゲームに爽快感を与えるには、こういう派手な仕掛けが必要なんだな!ということです。このアイディアの初出って、初代マリオブラザーズなんでしょうか?POWブロックを再現しました。
こういう見た目が派手なものが大事ですよね、加えて敵キャラを一掃出来るのが楽しい。やっぱ、ゲームはやってて楽しくないと意味ないですよ!!