ここぷろ!

私がプログラミングで学んだこと、行き詰った事などを書いていきます。たまに個人的なことも載せるかも

Unityゲーム開発で役立つインタフェース活用例


 こんにちは、ここあです。

 今回は、私がUnityでゲーム開発をする際によく使う、インタフェースの実装を書いていきたいと思います。

インタフェースとは

実装の強制

 簡潔に行ってしまえば、インタフェース機能の実装を強制することが出来る仕組みです。
 これは別の言い方をすると、インタフェースに定義された属性やメソッドは、確実に継承された先のクラスに実装されていることが保証されます

処理の窓口

 実装に関しては、インタフェースを引き継いだクラス側で自由に変更することができ、呼び出す側はその実装内容に関して気にすることなく使えます。
 この事から、インタフェースは処理の窓口という特性も持っています。


 ちなみに、クラスなどに、インタフェースを引き継ぐことを、継承ではなく、実装すると言います。

Update関数を自前の実装で回す

コールバック関数は重い

 UnityでC#を書く場合、多くの方がUpdate関数をそのまま各スクリプトに書いて実行しているのではないかと思います。
 少なくとも、初学者の方々はそうだと思います。実際、私がそうでした。

 それ自体、大きく問題にすることではありませんが、最適化を求めたり、処理の効率を気にするようになってくると少し話は変わってきます。


 Update関数に限らず、Unityの提供しているコールバック関数はちょくちょく重いです。


 弾幕シューティングの様に、大量のオブジェクトが出現する場合や、千個単位もしくはそれ以上のオブジェクト全てにUpdate関数が付いていると、処理効率を求めるうえで、あまり無視して良い処理量では無くなってきます。

 では、どうやって処理を軽くするか。


 単純明快。自前の関数を呼び出すことで、コールバック関数の重さを回避することが出来ます。

実装方法

 私の場合、次の様な実装の形式を取る事が多いです。

 f:id:holmes8901:20181225194100p:plain

 MasterManagerがインタフェースを経由して、それを実装した全てのScriptのUpdateを動かす形です。
 これのメリットは、先ほど述べた通り、コールバック関数を使わない。
 つまり、各スクリプトに継承されるMonoBehaviourを最小限に抑えることが出来る点です。

実装コード

using UnityEngine;

[DisallowMultipleComponent]
public class MasterManager : MonoBehaviour
{
    private IUpdateByFrame[]  m_updateByFrame;

    /// <summary>
    /// 各Managerの初期取得処理
    /// </summary>
    private void Awake()
    {
        m_masterObject = FindObjectOfInterface.FindObjectOfInterfaces<IUpdateByFrame>();
    }

    /// <summary>
    /// 各ManagerのUpdateByFrame関数をここから回す
    /// </summary>
    private void Update()
    {
       // 取得したMasterScene内オブジェクトのUpdateを回す
        foreach (var list in m_masterObject)
        {
            // インタフェースを継承しているか確認
            if (list is IUpdateByFrame update)
            {
                update.UpdateByFrame();
            }
        }
    }
}

FindObjectOfInterface.FindObjectOfInterfacesは自分で実装した、Interfaceを持ったオブジェクトを取得する為の拡張メソッドになります。
これについては、今回は説明しません。

Scene内オブジェクトの管理に空のインタフェース

各シーン内に存在するオブジェクト管理

 もうひとつよく行っているのが、空インタフェースの実装です。

 これをどう使うかというと、私は各シーン内に存在する管理クラスを捕まえてくるのに使います。

 私はマスターシーンを一つ用意して、そこに追加で各シーンをロードする方法を取っています。
 マスタシーンでIUpdateByFrameを実装したオブジェクトを取得する命令が間違って走ってしまった場合、全てのシーンからインタフェースを実装したオブジェクトを取得してしまいます。
 それを回避するために、使います。

実装方法

 今回の場合、次のようなアクセスの流れになります。

 f:id:holmes8901:20181225211553p:plain

 MasterManagerはIMasterObjを実装したMasterScene内の管理クラスを取得する。
 SceneManagerはInGameManagerを持っており、InGameManagerはIInGameObjを実装したInGameScene内の管理クラスを取得してくるといった形です。

 こうすることで、各シーンを束ねる管理クラスが、適切に自身の管理領域に存在するクラスを取得することが出来ます。

実装コード

 MasterScene上記で記載したIUpdateByFrameの使用と組み合わせると、次の様な実装になります。
 これは、各管理クラスが各シーン用インタフェースと、IUpdateByFrameを実装しているのが前提となります。

[DisallowMultipleComponent]
public class MasterManager : MonoBehaviour
{
    private IMasterObject[]  m_masterObject;

    /// <summary>
    /// 各Managerの初期取得処理
    /// </summary>
    private void Awake()
    {
        m_masterObject = FindObjectOfInterface.FindObjectOfInterfaces<IMasterObject>();
    }

    /// <summary>
    /// 各ManagerのUpdateByFrame関数をここから回す
    /// </summary>
    void Update()
    {
        // 取得したMasterScene内オブジェクトのUpdateを回す
        foreach (var list in m_masterObject)
        {
            // インタフェースを継承しているか確認
            if (list is IUpdateByFrame update)
            {
                update.UpdateByFrame();
            }
        }
    }
}

終わりに

 今回紹介した方法は、Interfaceの使用法としてあまりネットに情報が出てない気がしたので紹介しました。
 またこの方法は、本当に処理効率を考えるのであれば、キャストがそこそこ入る関係上、少し遅くなる可能性もあります。
 その辺の検証については、実仕様で問題が出ていなかったので、まだ行っていませんでした。

 組み方次第だとは思いますが、その点については留意しておいてください。

 また、今回の内容に関して、私の間違った認識やより良い方法があれば教えて頂けるとありがたいです。
 
 それでは、今回はこのあたりで。