SMessage-Unity向けのシンプルで予測可能なイベントシステム

免責事項

この投稿は、 「Unityのシンプルなイベントシステム」という投稿のアイデアを発展させたものです。 私はこの問題に対する唯一の真のアプローチのふりをしてはおらず、一般的に私はハブラに住んでいるマストドンに関しては平凡なプログラマーです。 私は主にJavaを使用しているため、表面的にC#にも精通しています。 それにもかかわらず、運命は私をUnityに連れて行き、私は彼から取ったもののためにコミュニティに返済することができるいくつかのツールを持っていることに気付きました。 簡単に言えば、オープンソースへの貢献は小さくても、あなた自身のものであり、私は信じたいのです。



問題や結論などを読むのが面倒な人は、 github.github.com / erlioniel / unity - smessageの例でコードをすぐに見ることができます

そこに.unitypackageをダウンロードすることもできます:)



問題

Unityでプロジェクトを構築し始めたばかりで、すぐに何らかのイベントシステムが必要であるという結論に達しました。 宗教的な狂信者が何と言っても、GUIがゲームオブジェクトと密接に結びついている状況は、最悪の場合があります。 互いのオブジェクトのパスタ転送に基づいて構築されたプロジェクトのアーキテクチャは、スケーリングおよび変更を受けるのが非常に困難です。 したがって、イベントシステムが必要です。



別の質問は、イベントがまったく予測不可能な抽象化であるため、プログラムの振る舞いを追跡することが不可能になった場合に状況に到達するのにそれほど長くないため、狂信なしでここで何が必要かです。 しかし、タスクを少し単純化するために何ができますか?



パラメータ-最初のアプローチ

最初に参照する記事のコードを見ると、イベントにはパラメーターを含めることができないため、イベントシステムは非常に単純であることがわかります。 最初に、パラメーターの使用を許可する別のシステムを実装しました。



//  Callback.Add(Type.TURN_START, Refresh); //  Callback.Call(Type.TURN_START, TurnEntity());
      
      





ご覧のとおり、ENUM値はイベントキーとして使用されていたため、少し簡単になり(IDEから可能な値のリストをいつでも取得できました)、問題なくパラメーターを渡すこともできました。 それは一般的に初めて私に合った。



入力-2番目のアプローチ

イベントシステムの単純な実装の主な問題は、予測性が低く、IDEがコードを記述できないことです。 ある時点で、私は、複雑なイベントについては、通常はモデルに書き込まれるように引数を渡す順序を覚えていなければならないと考え始めました。 そして、ハンドラーの別のモデルカーストもすべて緊張していました。 一般に、大きくなりすぎたシステムは予測不可能な動作を開始し、それ自体をサポートするために古いコードの注意と知識を必要としました。



ある晩、ジェネリック医薬品を使用した魔術の後に、IDEがあらゆる状況で大いに役立つシステムが作成されました。 イベントクラスの名前付け規則(たとえば、SMessageプレフィックス...)を受け入れ、カーストなし、ハンドラーが最終クラスのオブジェクトをすぐに受け取る場合、イベントのリストは簡単に取得できます。これはすべて、古典的なC#デリゲートに基づいています。



マネージャーの仕事を自分で分析することをお勧めします。ここにリストをいくつか示します。 以下に使用例を示しますが、これはエンドユーザーにとって非常に興味深いものです。

SManager.cs
 public class SManager { private readonly Dictionary<Type, object> _handlers; // INSTANCE public SManager() { _handlers = new Dictionary<Type, object>(); } /// <summary> /// Just add new handler to selected event type /// </summary> /// <typeparam name="T">AbstractSMessage event</typeparam> /// <param name="value">Handler function</param> public void Add<T>(SCallback<T> value) where T : AbstractSMessage { var type = typeof (T); if (!_handlers.ContainsKey(type)) { _handlers.Add(type, new SCallbackWrapper<T>()); } ((SCallbackWrapper<T>) _handlers[type]).Add(value); } public void Remove<T>(SCallback<T> value) where T : AbstractSMessage { var type = typeof (T); if (_handlers.ContainsKey(type)) { ((SCallbackWrapper<T>) _handlers[type]).Remove(value); } } public void Call<T>(T message) where T : AbstractSMessage { var type = message.GetType(); if (_handlers.ContainsKey(type)) { ((SCallbackWrapper<T>) _handlers[type]).Call(message); } } // STATIC private static readonly SManager _instance = new SManager(); public static void SAdd<T>(SCallback<T> value) where T : AbstractSMessage { _instance.Add(value); } public static void SRemove<T>(SCallback<T> value) where T : AbstractSMessage { _instance.Remove(value); } public static void SCall<T>(T message) where T : AbstractSMessage { _instance.Call(message); } }
      
      



SCallbackWrapper.cs
  internal class SCallbackWrapper<T> where T : AbstractSMessage { private SCallback<T> _delegates; public void Add(SCallback<T> value) { _delegates += value; } public void Remove(SCallback<T> value) { _delegates -= value; } public void Call(T message) { if (_delegates != null) { _delegates(message); } } }
      
      





使用例

github-github.com/erlioniel/unity-smessage/tree/master/Assets/Scripts/Examplesで例を見つけることができます

しかし、ここでは、このシステムの使用方法の最も単純なケースを分析します。 たとえば、イベントマネージャのシングルトン実装を使用しますが、必要に応じて独自のインスタンスを作成する権利があります。 何らかのオブジェクトがクリックされたことを通知する新しいイベントを作成する必要があるとします。 イベントオブジェクトを作成します。



イベントモデルはイベントマーカーそのものなので、イベントごとに新しいクラスを作成する必要があります。 これはIDEヘルプの料金です:<AbstractSMessageは、何らかの種類のオブジェクトを格納できる基本クラスとして使用されます

  public class SMessageExample : AbstractSMessageValued<GameObject> { public SMessageExample (GameObject value) : base(value) { } }
      
      





オブジェクト自体で、このイベントを呼び出し、必要な引数をそこに渡す必要があります

  public class ExampleObject : MonoBehaviour { public void OnMouseDown () { SManager.SCall(new SMessageExample(gameObject)); } }
      
      





さて、最後に、このイベントを追跡する別のオブジェクトを作成します

  public class ExampleHandlerObject : MonoBehaviour { //     OnEnable public void OnEnable() { SManager.SAdd<SMessageExample>(OnMessage); } //      OnDisable public void OnDisable() { SManager.SRemove<SMessageExample>(OnMessage); } private void OnMessage (SMessageExample message) { Debug.Log("OnMouseDown for object "+message.Value.name); } }
      
      





すべてが非常にシンプルで明白ですが、さらに重要なことは、コンパイラ/ IDEがすべてをチェックし、作業を支援します。

PS私はコードをチェックしなかった、エラーがあるかもしれません:)



結論の代わりに

イベントシステムは非常に強力なツールであり、過小評価すべきではありません。 特にプロジェクトが中規模に成長した場合、コードの凝集度が高いと、一部のプログラマーに思われるほど良くありません。



コードが誰かに役立つことを願っています。 私はいくつかのコメントと提案を喜んでいます。



UPD:

抽象クラスAbstractSMessageValuedを追加し、記事の例を少し更新しました。



All Articles