浅いものから深いものまで、実装フレームワークをゆっくりと進化させます
2つのクラスの実装コードは全く同じで、クラス名や型が異なる場合や、継続的に拡張する必要がある場合(今後様々なイベントが追加される予定)のみ、ジェネリックス+継承を利用して抽出し、継承は拡張の問題を解決し、ジェネリックは一貫性のある実装コードと一貫性のないクラスの問題を解決します。これはリファクタリング手法です。
パフォーマンスとデータは分離する必要がある
ほとんどの場合、データは複数のシーン、インターフェイス、ゲーム オブジェクト間で共有する必要があります。このデータは空間的に共有する必要があるだけでなく、時間的にも共有する必要がある(保存する必要がある)ため、ここでは開発読者間のコンセンサスは、データ部分を抽出して、メンテナンスのために別の場所に置くことです。より一般的な開発アーキテクチャは、MVC 開発アーキテクチャを使用することです。最初に MVC 開発アーキテクチャを使用するのではなく、MVC の 1 つだけを使用します。コンセプトはモデル。
Model はデータを管理し、データを保存することです。データの管理とは、Model オブジェクトまたはクラスを通じてデータを追加、削除、変更、確認できることを意味し、場合によっては保存することもできます。
public class GameModel
{
public static int KillCount = 0;
public static int Gold = 0;
public static int Score = 0;
public static int BestScore = 0;
}
- ジェネリックス + 継承を使用したイベント ツール クラスの抽出
- 子ノードはイベントを使用して親ノードに通知することもできます (状況に応じて)
- プレゼンテーションと共有する必要があるデータの分離
- 正しいコードを正しい場所に配置する必要があります
データが共有されている場合はModelに入れますが、共有されていない場合は必要ありません。
ここで共有されるデータには、構成データ、保存が必要なデータ、複数の場所でアクセスする必要があるデータが含まれます。
設定データに関しては、ゲーム内のシーン、ゲームオブジェクト、プレハブも基本的に Yaml (json や xml に似たデータ形式) を使用してディスクに保存されるため、ゲーム実行前の一種の設定データとなります。
保存する必要があるデータは、時間次元から共有されるデータです。つまり、過去のある時点に保存されたデータにアクセスできるようになります。
再利用可能なバインド可能なプロパティ
using System;
namespace FrameworkDesign
{
public class BindableProperty<T> where T : IEquatable<T>
{
private T mValue;
public T Value
{
get => mValue;
set
{
if (!mValue.Equals(value))
{
mValue = value;
OnValueChanged?.Invoke(value);
}
}
}
public Action<T> OnValueChanged;
}
}
BidableProperty はデータ + データ変更イベントの組み合わせです。データを保存し、C# のプロパティとして機能するだけでなく、他の場所でデータ変更イベントを監視できるようになり、多くの定型コードが削減されます。
- パフォーマンス ロジックはイベントまたはデリゲートの使用に適しています
- プレゼンテーション ロジックにメソッド呼び出しを使用すると、コントローラーが肥大化して保守が困難になるなど、多くの問題が発生します。
- モデルとビューはボトムアップの関係にあります
- イベントまたはデリゲートを使用したボトムアップ
- トップダウンのメソッド呼び出し
コマンドモード
メソッドに記述されたロジックはオブジェクトを使用して実装され、このオブジェクトの実行メソッドは 1 つだけです。
まず、ICommand というインターフェイスを定義します。コードは次のとおりです。
namespace FrameworkDesign
{
public interface ICommand
{
void Execute();
}
}
インターフェースを実装します。
public struct AddCountCommand : ICommand
{
public void Execute()
{
CounterModel.Count.Value++;
}
}
コマンド モードは、論理的な呼び出しと実行が分離されていることを意味します。
スペース分割の方法は呼び出し元と実行先を2つのファイルに入れる方法です。
時間分離の方法は、呼び出し後、しばらくしてからコマンドが実行されることです。
コマンド モードには呼び出しと実行を分離する機能があるため、さまざまなデータ構造を使用してコマンド キューなどのコマンド呼び出しを整理したり、コマンド スタックを使用して元に戻す機能 (ctrl + z) を実装したりできます。
シングルトンの導入
- 静的クラスにはアクセス制限がありません。
- モジュールの拡張には静的を使用しますが、モジュールの認識は高くありません。
public class Singleton<T> where T : class
{
public static T Instance
{
get
{
if (mInstance == null)
{
// 通过反射获取构造
var ctors = typeof(T).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
// 获取无参非 public 的构造
var ctor = Array.Find(ctors, c => c.GetParameters().Length == 0);
if (ctor == null)
{
throw new Exception("Non-Public Constructor() not found in " + typeof(T));
}
mInstance = ctor.Invoke(null) as T;
}
return mInstance;
}
}
private static T mInstance;
}
モジュールの最適化 -- IOC コンテナの紹介
IOCコンテナは辞書として捉えることができ、Typeをキー、オブジェクト、つまりInstanceを値とする非常にシンプルな辞書です。
IOC コンテナには少なくとも 2 つのコア API があります。つまり、タイプに基づいてインスタンスを登録し、タイプに基づいてインスタンスを取得します。
シンプルなIOCコンテナを実装する
public class IOCContainer
{
/// <summary>
/// 实例
/// </summary>
public Dictionary<Type, object> mInstances = new Dictionary<Type, object>();
/// <summary>
/// 注册
/// </summary>
/// <param name="instance"></param>
/// <typeparam name="T"></typeparam>
public void Register<T>(T instance)
{
var key = typeof(T);
if (mInstances.ContainsKey(key))
{
mInstances[key] = instance;
}
else
{
mInstances.Add(key,instance);
}
}
/// <summary>
/// 获取
/// </summary>
public T Get<T>() where T : class
{
var key = typeof(T);
object retObj;
if(mInstances.TryGetValue(key,out retObj))
{
return retObj as T;
}
return null;
}
}
このコードを使用します。
我们先创建一个 CounterApp 类,用于注册全部模块,代码如下:
using FrameworkDesign;
namespace CounterApp
{
public class CounterApp
{
private static IOCContainer mContainer = null;
// 确保 Container 是有实例的
static void MakeSureContainer()
{
if (mContainer == null)
{
mContainer = new IOCContainer();
Init();
}
}
// 这里注册模块
private static void Init()
{
mContainer.Register(new CounterModel());
}
// 提供一个获取模块的 API
public static T Get<T>() where T : class
{
MakeSureContainer();
return mContainer.Get<T>();
}
}
}
接着我们把 CounterApp 类应用起来,代码如下:
using FrameworkDesign;
using UnityEngine;
using UnityEngine.UI;
namespace CounterApp
{
public class CounterViewController : MonoBehaviour
{
private CounterModel mCounterModel;
void Start()
{
// 获取
mCounterModel = CounterApp.Get<CounterModel>();
// 注册
mCounterModel.Count.OnValueChanged += OnCountChanged;
transform.Find("BtnAdd").GetComponent<Button>()
.onClick.AddListener(() =>
{
// 交互逻辑
new AddCountCommand()
.Execute();
});
transform.Find("BtnSub").GetComponent<Button>()
.onClick.AddListener(() =>
{
// 交互逻辑
new SubCountCommand()
.Execute();
});
OnCountChanged(mCounterModel.Count.Value);
}
// 表现逻辑
private void OnCountChanged(int newValue)
{
transform.Find("CountText").GetComponent<Text>().text = newValue.ToString();
}
private void OnDestroy()
{
// 注销
mCounterModel.Count.OnValueChanged -= OnCountChanged;
mCounterModel = null;
}
}
/// <summary>
/// 不需要是单例了
/// </summary>
public class CounterModel
{
public BindableProperty<int> Count = new BindableProperty<int>()
{
Value = 0
};
}
}
AddCountCommand.cs
using FrameworkDesign;
namespace CounterApp
{
public struct AddCountCommand : ICommand
{
public void Execute()
{
CounterApp.Get<CounterModel>().Count.Value++;
}
}
}
SubCountCommand.cs
using FrameworkDesign;
namespace CounterApp
{
public struct SubCountCommand : ICommand
{
public void Execute()
{
CounterApp.Get<CounterModel>().Count.Value--;
}
}
}
--dd、これが枠組みですかorz?
次のコードは簡単に繰り返すことができます
PiontGame.cs
namespace FrameworkDesign.Example
{
public class PointGame
{
private static IOCContainer mContainer = null;
// 确保 Container 是有实例的
static void MakeSureContainer()
{
if (mContainer == null)
{
mContainer = new IOCContainer();
Init();
}
}
// 这里注册模块
private static void Init()
{
mContainer.Register(new GameModel());
}
// 提供一个获取模块的 API
public static T Get<T>() where T : class
{
MakeSureContainer();
return mContainer.Get<T>();
}
}
}
最適化します。次のコードを使用して Architecture.cs という名前のクラスを作成します。
namespace FrameworkDesign
{
/// <summary>
/// 架构
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class Architecture<T> where T : Architecture<T>, new()
{
#region 类似单例模式 但是仅在内部课访问
private static T mArchitecture = null;
// 确保 Container 是有实例的
static void MakeSureArchitecture()
{
if (mArchitecture == null)
{
mArchitecture = new T();
mArchitecture.Init();
}
}
#endregion
private IOCContainer mContainer = new IOCContainer();
// 留给子类注册模块
protected abstract void Init();
// 提供一个注册模块的 API
public void Register<T>(T instance)
{
MakeSureArchitecture();
mArchitecture.mContainer.Register<T>(instance);
}
// 提供一个获取模块的 API
public static T Get<T>() where T : class
{
MakeSureArchitecture();
return mArchitecture.mContainer.Get<T>();
}
}
}