ゆべねこの足跡

IT系のはなしを残しておく場所です

UnityのC#でインターフェースを使う

本ブログ初のVRコンテンツに関する内容です。春休み中に設計に関する技術書を読んだので実践してみようと思い作ってみました。

読んだ本はこちらです

Adaptive Code ? C#実践開発手法 第2版

テストやリファクタリングの仕方からSOLID原則、依存性の注入など大規模な開発に不可欠な要素が学べる素晴らしい書籍となっておりました。特にインターフェースの有効性について学べたのが一番良かった点ですね。

というわけで、今回は「インターフェースを利用した抽象化をしてみる」という目的でVRコンテンツを作ってみました。

成果物

コントローラーの加速度を読み取って魔法を放出し敵を倒すゲームとしました。

クラス図

f:id:yubeshineko:20190406115208p:plain

今回が初めてなので正直なところ書き方がこれで合ってるかは分からないですが書いてみました。注目点はインターフェースや抽象クラスを使った抽象化です。将来的にどのポイントが拡張されるかを考えてそこを拡張できるように抽象化しました。例をあげるとプレイヤーの入力をIInputProviderインターフェースとして抽出してあげることでテスト入力がしやすくなったり違うコントローラーでも使えるようにしました(今回はテスト入力用のクラスは作りませんでしたが...)。Leap Motionを使った何かをやってみたかったのでいずれ暇があればハンドジェスチャで魔法を飛ばしたりできるようなIInputProviderインターフェースの実装をしてみたいと考えていたりもしてます。

他にはタグの代わりにインターフェースを使うということもやってみました。ダメージを与える対象のオブジェクトにIDamageApplicableインターフェースを実装したスクリプトをアタッチして与ダメージオブジェクトが被ダメージオブジェクトに衝突した際に与ダメージオブジェクトが衝突したオブジェクトからIDamageApplicableインターフェースを取得します。取得できたら被ダメージオブジェクトのApplyDamage()を実行といった形になります。開発の最初の段階でダメージを与える際のメソッドの引数はこうだよ!とインターフェースを指定しておくことで敵の種類が増えた時に引数が増えてしまい上位モジュール(今回の場合、Magicクラス)に変更が生じる...なんてことがなくなります。これはSOLID原則の「依存性反転の原則」, 「オープン/クローズド原則」を守るための工夫でもあります。今回の例で考えるとMagicオブジェクトがSkeletonオブジェクトに衝突した際にMagicクラスがGetComponent<IDamageApplicable>()を実行し、取得できたらSkeletonオブジェクトのApplyDamage()を実行するといった感じですね。

void OnTriggerEnter (Collider other)
{
    var enemy = other.gameObject.GetComponentInParent<IDamageApplicable> ();
    if (enemy != null)
    {
        enemy.ApplyDamage (damage);
    }
}

感想

今回初めてまともに設計してみたんですが、やっぱり最初は難しいですね。特に名前空間の名前決めや各クラスをどの名前空間に所属させるかなどは悩みどころでした。もしかしたらゲームのジャンルごとにある程度テンプレート的なものがあったりするのでしょうか? こればっかりは慣れるしかないのでしょうね。

それと、最初の段階でどこまで拡張できるようにするかをしっかり決めることも大事です。最初の段階ではIDamageApplicableインターフェースは以下のようになっていました。

namespace WCE.Damages
{
    public interface IDamageApplicable
    {
        void ApplyDamage(Attribute attribute);
    }
}

これは魔法の属性だけで敵に与えるダメージ計算をすることを想定したインターフェースでした。しかし、開発の途中で「プレイヤーがアイテムを拾って攻撃力が上がったら?」 なんてことを考えた結果、開発の途中でインターフェースを書き換えるという禁忌を犯すことになりました。これはSOLID原則の1つであるオープン/クローズド原則に違反する行為であり、あってはならない事態でした。今後はこのようなことがないよう初めの段階でキチンと設計をしておくようにしたいです。

あと、モチベーションが続くボリュームにすることが大事ですね。開発当初は敵の種類や魔法の属性をたくさん考えていたりプレイヤーのステータス上昇アイテムなどを考えていたのですが、途中で心がポッキリ折れてしまったので結局遊べるところまで持っていって終わろうということにしました。インターフェースで拡張ポイントは確保してあるのでまたやる気が出たら拡張することはできるんですけど当分はやれそうもありません。

それでも「インターフェースを使って抽象化をする」ことは実践できたので満足ではあります。