SkillAgentSearch skills...

SubmarineMirageFrameworkForUnity

中規模インディーゲーム開発用、Unity製フレームワーク。 非同期処理を土台とし、販売レベルのゲーム必須機能の提供が目標。

Install / Use

/learn @FromSeabedOfReverie/SubmarineMirageFrameworkForUnity

README

Submarine Mirage Framework for Unity

Version 0.1

Logo.png

概要

Submarine Mirage Framework for Unityとは、Unityでのゲーム開発時に、不便さを補い、安定、迅速、堅牢な実装を行う為の、フレームワークである。
販売品質のゲームで、一般的、汎用的、必須な機能の、全体実装を目標とする。
しかし、現在開発中の為、 未実装機能 が沢山あり、不安定である。

対象

  • 中規模インディーゲーム向け
    数名規模のインディーゲーム開発スタジオで、それなりの中規模ゲーム開発での使用を想定している。
    (ストーリーがある中編アクションRPG、小規模のオープンワールドゲーム等。)
    また、プログラム実装に悩んだ際に、処理の参考となる。

  • 日本人向け
    Unity用の総合機能フレームワークは、オープンソースで多々あるが、プログラムのコメントが中国語や英語である事が多い。
    マニュアルを日本語化して読む程度ならまだしも、外国語のプログラムのコメントを見ながらの開発は辛いと思われる。
    そのような語学が堪能でない日本人にとって、扱い易いフレームワークである。
    逆に、語学堪能な日本人、外国人には、 GameFrameworkQFrameworkBDFramework.CoreKSFramework 等のフレームワークの方が出来が良い為、そちらをお勧めする。

  • 導入事例
    夢想海の水底より (インディーゲーム開発スタジオ)の開発環境に採用中である。

特徴

  • Unityのいい加減な機能を、補強
    Unityは、いい加減なゲーム開発をサポートしているが、販売品質程度のきちんと動作するゲーム開発を、ほぼ全くサポートしていない。
    Unityの問題ばかりの機能の内、ゲーム開発で頻繁に使われるであろう機能を、堅牢に、使い易く実装している。

  • フレームワークとして、全面実装
    フレームワークの為、特定の機能実装だけでなく、多種多様な機能を全体的に実装している。

  • 外部ライブラリ導入済の為、環境構築が簡単
    業界水準を鑑み、ゲーム開発企業で頻繁に使用される、多種多様な外部ライブラリを同封している。
    (ライセンス上、同封可能なライブラリのみ。)
    このフレームワークを導入するだけで、即、開発環境を構築可能である。

  • 見易いプログラム
    可読性を意識し、(日本語であるが)見易いコメント、分かり易い設計を重視している。
    プログラムが見易い事は、使い易いフレームワークの必須条件と考えられる。

対応

  • Unity動作環境

    • 2018.4.16f1~(以降)
      動作確認は、2018.4.16f1を使用している。
  • ビルド対応

    • Windows(Mono、IL2CPP)
    • Android(Mono、IL2CPP)

使用方法

導入

  1. フレームワークを導入
    Assets/ 内の書類を全て複製、移植する。

  2. Unity内のPlayerSettingsを変更
    ScriptingRuntimeVersionを.NET4.xEquivalentに設定する。
    ApiCompatibilityLevelを.NET4.xに設定する。

  3. Unity内でパッケージを導入
    PackageManager、Service等から、UnityAds、UnityIAPを導入する。

使い方

  • MonoBehaviourProcess
    順序立てた非同期処理を追加したMonoBehaviourである。

      using UniRx;
      using UniRx.Async;
      using SubmarineMirageFramework.Process;
      // MonoBehaviourProcessは、処理順序に規則を持たせたMonoBehaviour
      // UniRxで無理矢理組むような、リアクティブスパゲッティを防止できる
      public class TestMonoBehaviourProcess : MonoBehaviourProcess {
          // 疑似的コンストラクタ
          // 生成時に呼ばれるが、実際のコンストラクタは実行時以外にも呼ばれる為、これを使用
          protected override void Constructor() {
              // 読込処理を設定
              // 各種管理クラスの初期化後、最初に管理クラスから呼ばれる
              _loadEvent += async () => {
                  // AssetBundle、ServerData等、自身で完結する非同期処理を記述
                  await UniTask.Delay( 0 );
              };
              // 初期化処理を設定
              // _loadEvent実行後、管理クラスから呼ばれる
              _initializeEvent += async () => {
                  // 他オブジェクトの要素、管理クラスの要素等、読込済の他者の取得処理を記述
                  await UniTask.Delay( 0 );
              };
              // 更新処理を設定
              // _initializeEvent実行後、管理クラスから毎フレーム呼ばれる
              _updateEvent.Subscribe( _ => {
                  // 毎フレーム実行する処理を記述
              } );
              // 終了処理を設定
              // 破棄直前に管理クラスから呼ばれる
              _finalizeEvent += async () => {
                  // 破棄が必要な、非同期処理を記述
                  await UniTask.Delay( 0 );
              };
          }
      }
    
  • Singleton
    MonoBehaviourProcess と同様に動作する、ゲームオブジェクト化する必要の無い、基本的なシングルトンである。

      using System.Linq;
      using System.Collections.Generic;
      using UniRx;
      using UniRx.Async;
      using SubmarineMirageFramework.Singleton;
      using SubmarineMirageFramework.Process;
      // MonoBehaviourProcessと同じようにProcessサイクルを持つ、シンプルなシングルトン
      public class TestSingleton : Singleton<TestSingleton> {
          // 試験データの一覧
          public readonly List<string> _data = new List<string>();
          // コンストラクタで、読込時にデータ読込を設定
          public TestSingleton() {
              _loadEvent += async () => {
                  _data.Add( "TestData" );
                  await UniTask.Delay( 0 );
              };
          }
      }
      // シングルトンを呼ぶ為の、コンポーネントクラス
      public class UseSingleton : MonoBehaviourProcess {
          // このコンポーネントが使用する試験データ
          string _data;
          // コンストラクタで、初期化時に管理クラスからデータ取得、を設定
          protected override void Constructor() {
              // 予めシングルトンを使用し生成しないと、内部登録されず、読込処理が行われない
              var i = TestSingleton.s_instance;
              _initializeEvent += async () => {
                  _data = TestSingleton.s_instance._data.FirstOrDefault();
                  await UniTask.Delay( 0 );
              };
          }
      }
    
  • MainProcess
    ゲーム起動直後に、 Singleton の生成と処理順序を指定できる。

      using System.Linq;
      using UniRx.Async;
      using SubmarineMirageFramework.Process;
      // 実行時に、一番最初に処理されるクラス
      // Assets/SubmarineMirageFrameworkForUnity/Scripts/Main/MainProcess.csを、簡易的に記述している
      // 実際は、そこを編集する
      public class MainProcess {
          // 外部プラグインを初期化する
          static async UniTask InitializePlugin() {
              // ここで、様々な外部プラグインの初期化を記述する
              // ...省略...
              await UniTask.Delay( 0 );
          }
          // 処理を登録する
          static async UniTask RegisterProcesses() {
              // ここで、シングルトン等のProcess系クラスを初期化し、呼び出し順を確定させる
              // ...省略...
              await TestSingleton.WaitForCreation();    // シングルトン試験の生成と登録を行い、完了まで待機
              await UniTask.DelayFrame( 1 );
          }
      }
      // 登録済シングルトンを呼ぶ為の、コンポーネントクラス
      public class UseSingletonByRegister : MonoBehaviourProcess {
          // このコンポーネントが使用する試験データ
          string _data;
          // コンストラクタで、初期化時に管理クラスからデータ取得、を設定
          protected override void Constructor() {
              // ここでシングルトン未使用でも、生成済で内部登録済の為、読込処理が行われる
      //      var i = TestSingleton.s_instance;
              _initializeEvent += async () => {
                  _data = TestSingleton.s_instance._data.FirstOrDefault();
                  await UniTask.Delay( 0 );
              };
          }
      }
    

設計思想

UniRxは優れたライブラリだが、いい加減な使用で直ぐに複雑化し、リアクティブスパゲッティと呼ばれる難読プログラムとなる。
下記プログラムは、管理クラスのシングルトン初期化後に、ゲームオブジェクトがデータ取得後に初期化する、難読プログラムの悪しき例である。

    using System.Linq;
    using System.Collections.Generic;
    using UnityEngine;
    using UniRx;
    using UniRx.Triggers;
    // シーン配置済の管理処理のシングルトン
    public class TestUniRxManager : MonoBehaviour {
        // 自身のインスタンス
        public static TestUniRxManager s_instance { get; private set; }
        // 試験データの一覧
        public List<string> _data { get; private set; } = new List<string>();
        // 初期化
        void Start() {
            // 自身を保持し、自身のデータを設定し、他者のデータ登録を待機後、データを加工
            s_instance = this;
            _data.Add( "ManagerData" );
            var isInitialized = false;
            Observable.TimerFrame( 2 ).Subscribe( _ => {    // ※待機時間がいい加減
                _data = _data.Select( d => d + "Processed" ).ToList();
                isInitialized = true;
            } )
            .AddTo( gameObject );
            // 更新処理を登録し、初期化済の場合のみ、何らかの処理
            this.UpdateAsObservable().Where( _ => isInitialized ).Subscribe( _ => {
            } );
        }
    }
    // シーン配置済のゲームオブジェクト1
    public class UseUniRx1 : MonoBehaviour {
        // 管理クラスのデータ
        string _managerData;
        // 他者のデータ
        string _data;
        // 初期化
        void Start() {
            // シングルトン作成を待機後、自身のデータを登録し、他者のデータ登録と加工を待機後、データを設定
            var isInitialized = false;
            Observable.NextFrame().Subscribe( _ => {    // ※待機がいい加減で、複雑な入れ子になっている
                TestUniRxManager.s_instance._data.Add( "Data1" );
                Observable.TimerFrame( 2 ).Subscribe( __ => {
                    _managerData = TestUniRxManager.s_instance._data[0];
                    _data = TestUniRxManager.s_instance._data[2];
                    isInitialized = true;
                } )
                .AddTo( gameObject );
            } )
            .AddTo( gameObject );
            // 更新処理を登録し、初期化済の場合のみ、何らかの処理
            this.UpdateAsObservable().Where( _ => isInitialized ).Subscribe( _ => {
            } );
        }
    }
    // シーン配置済のゲームオブジェクト2
    public class UseUniRx2 : MonoBehaviour {
        // 管理クラスのデータ
        string _managerData;
        // 他者のデータ
        string _data;
        // 初期化
        void Start() {
            // シングルトン作成を待機後、自身のデータを登録し、他者のデータ登録と加工を待機後、データを設定
            var isInitialized = false;
            Observable.NextFrame().Subscribe( _ => {    // ※待機がいい加減で、複雑な入れ子になっている
                TestUniRxManager.s_instance._data.Add( "Data2" );
                Observable.TimerFrame( 2 ).Subscribe( __ => {
                    _managerData = TestUniRxManager.s_instance._data[0];
                    _data = TestUniRxManager.s_instance._data[1];
                    isInitialized = true;
                } )
                .AddTo( gameObject );
            } )
            .AddTo( gameObject );
            // 更新処理を登録し、初期化済の場合のみ、何らかの処理
            this.UpdateAsObservable().Where( _ => isInitialized ).Subscribe( _ => {
            } );
        }
    }

このような難読プログラムは、サーバー通信でAssetBundleを取得する等の、非同期処理が含まれる場合に、多く遭遇する。
原因は、Unityのコンポーネント設計が全て等価である事、Unityは非同期処理を考慮しない設計である事に、起因していると考えられる。
その為、当フレームワークでは、下記の遷移図に示す、非同期ゲームループを中心に据えた設計とし、管理クラス等の処理順序を規定している。
Flowchart.png
※完成予定図の為、現在未対応な機能も含まれる。

実装処理

フレームワークの配置フォルダ

Assets/SubmarineMirageFrameworkForUnity/ に、フレームワークが配置されている。
以降は、このフォルダ直下の説明を行う。

  • /Test/
    機能試験用の書類が存在する。
    当項目では、このフォルダ直下の説明を行う。

    • /ReadMe/
      当書類 README に記載のサンプルプログラムを纏めている。

    • /Sample/
      使用例の書類が纏められている。
      [Sample.unity](/Assets

Related Skills

View on GitHub
GitHub Stars47
CategoryDevelopment
Updated5mo ago
Forks2

Languages

C#

Security Score

77/100

Audited on Sep 25, 2025

No findings