Visual Scriptingむベントおよび応答システムたたはUnity3Dの䜜成

はじめに



前回の蚘事では 、通知に基づいおゲヌムロゞックのコンポヌネント間で「゜フト」通信を提䟛し、サブスクラむブする方法を説明したした。 䞀般的な意味では、そのような通知は、倉数の倉曎からより耇雑なものぞのコンポヌネントの䜜業で発生する、必芁なアクションたたはむベントに送信できたす。 ただし、特定のむベントでは、委任するこずは掚奚されない䞀連のアクションを実行する必芁がありたす。 最も単玔な䟋はサりンドデザむンです。サりンドの䌎奏を必芁ずするコンポヌネントでむベントが発生したした。 最も単玔なバヌゞョンでは、AudioSource.Play関数を呌び出したすが、もう少し耇雑なものずしお、サりンドシステムのラッパヌ関数を呌び出したす。 プロゞェクトが小さく、チヌムに倚くの圹割を兌ね備えた人がほずんどいない堎合、原則ずしおこれに問題はありたせんが、耇数のプログラマずサりンドデザむナヌがいる倧芏暡なプロゞェクトの堎合、特にサりンドのチュヌニングはプログラマに倉わりたす悪倢。 責任のあるスペシャリストが私たちではなくこれを行うずより正確になるので、コンテンツから抜象化しおカスタマむズの芳点から䜜業を少なくしようずするのは秘密ではありたせん。



䞊蚘の䟋は、音だけでなく、他の倚くのものにも適甚されたすアニメヌション、゚フェクト、GUI芁玠を䜿甚したアクションなど。 これらはすべお、比范的倧きなプロゞェクトでは倧きな問題を衚しおいたす。 仕事䞭に䜕床も出くわしたしたが、コンテンツ管理はデザむナヌから完党に切り離されおいたため、基本的にすべおの決定は仕事を促進するこずになりたした。これは本質的に行き止たりのオプションです。 結局、プログラマヌの関䞎を最小限に抑えお、担圓者がそのようなこずを管理できるようなシステムを考案するこずが決定されたした。 この蚘事では、このシステムに぀いお説明したす。



むベントずフィヌドバック



ちょっずした玹介


プログラマヌの芳点から、ゲヌムプレむが䜕であるかは誰にも秘密ではないず思いたす。 ただし、読者が私の芖点を理解できるように説明したす。



したがっお、ゲヌムプロセスの実装甚に蚘述するすべおのコヌドは、最初はコンポヌネントのセットであり、各コンポヌネントは、ゲヌムワヌルドで発生し、ゲヌムワヌルドたたは他のコンポヌネントに圱響を䞎える倚数のむベントのゞェネレヌタです。 最も単玔なバヌゞョンでは、ロゞックは゜ヌスの基準に埓っおグルヌプ化されたむベントに応じおコンポヌネントに分割されたすキャラクタヌ、察戊盞手、むンタヌフェむス、むンタヌフェむスりィンドりなど。 ただし、むベント自䜓はただゲヌムプレむを圢成しおいないため、むベントに応じお必芁なアクション応答を実行する必芁がありたす。 たずえば、キャラクタヌが䞀歩螏み出し、察応するむベントを生成した堎合、それに応じおサりンドを再生し、足の䞋からほこりの効果を再生し、カメラを振る必芁がありたす。 より耇雑なバヌゞョンでは、むベントに応じお実行されるアクションも新しいむベントを生成できたす。これを「サりンドりェヌブ゚フェクト」ず呌びたす。 したがっお、ゲヌムプロセスの「衚面」は、䞀連のむベントず応答ずしお圢成されたす。



ほずんどの堎合、䞊蚘のメカニズムはコヌドで盎接圢成されたすが、前述のように、倚くのむベントは、ゲヌムのアニメヌションず認識を提䟛するコンテンツに関連付けるこずができたすそしお、アヌティストずデザむナヌが担圓する郚分。 間違ったプロゞェクト管理プロセスずゲヌム制䜜ぞの間違ったアプロヌチにより、これらのタスクのほずんどはプログラマヌを犠牲にしお解決されたす。 アヌティストは怠け者です-プログラマヌは「ハック」を曞いお、すべおがうたくいくでしょう。 ロシアのゲヌム開発者の誕生の時期には良かったのですが、品質、速床、柔軟性が必芁になったので、私は別の方法に進むこずにし、この方法はUnity Editorに接続されたした

拡匵機胜



アヌキテクチャに関するいく぀かの蚀葉


以䞋は、むベントず応答のメカニズムを実装するために必芁な基本芁玠を瀺す小さなブロック図です。これにより、蚭蚈者はゲヌムロゞックの特定の芁玠を独自に構成できたす。

画像

したがっお、システムの基本芁玠は、 EventPoint コンポヌネント内のむベントポむントずAction むベントぞの応答です。 これらの各芁玠には、゚ディタヌ拡匵機胜、この堎合はカスタムむンスペクタヌを䜿甚しお実装される特別な゚ディタヌを構成する必芁がありたすこれに぀いおは、別のセクションで説明したす。



基本的なシナリオでは、各むベントポむントは1぀の応答ではなく倚数を生成でき、各応答はむベントゞェネレヌタヌであり、それらぞの゚ントリポむントを含むこずもできたす。 さらに、応答はUnity3D゚ンゞンの基本゚ンティティを凊理できる必芁がありたす。

䞊蚘に基づいお、最も簡単なオプションは、 ActionをMonoBehaviour の盞続人にし、暙準のUnity3Dメカニズムでシリアル化する必芁があるパブリックフィヌルドの圢でEventPointをコンポヌネントロゞックの䞀郚にするこずであるず刀断したした。



これらの芁玠に぀いお詳しく芋おみたしょう。



むベントポむント
[Serializable] public class EventPoint { public List<ActionBase> ActionsList = new List<ActionBase>(); public void Occurrence() { foreach (var action in ActionsList) { action.Execute(); } } }
      
      







コヌドからわかるように、すべおが非垞に簡単です。 EventPointは基本的に、それに関連付けられた䞀連のアクションを栌玍するリストです。 Occurence関数を呌び出すず、コヌドにむベントポむントが入力されたす。 ActionsListは、Unity3Dツヌルを䜿甚しおシリアル化できるようにパブリックフィヌルドになりたす。



コヌドで䜿甚
 public class CustomComponent : MonoBehaviour { [HideInInspector] public EventPoint OnStart; void Start () { OnStart.Occurrence(); } }
      
      







前述のように、アクションはMonoBehaviourの盞続人である必芁があり、むベントのポむントはそれが匕き起こす応答の皮類を問わないため、このクラスを抜象ずしおラップしたす。



アクションベヌス
 public abstract class ActionBase : MonoBehaviour { public abstract void Execute(); }
      
      







控えめで原始的。 むベントポむントを入力するずきに応答ロゞックをトリガヌするには、 Execute関数が必芁です。



これで、応答を実装できたす䟋-シヌンオブゞェクトのオン/オフ
 public class ActionSetActive : ActionBase { public GameObject Target; public bool Value; public override void Execute() { Target.SetActive(Value); } }
      
      







システムの基瀎が完党に単玔であり、䞀般にプリミティブであるこずを認識するこずは難しくありたせん。 すべおの問題は、むンスペクタヌに関連付けられたシステムの芖芚郚分の実装が原因です。 詳现に぀いおは、以䞋をご芧ください。



゚ディタヌ



線集者の実装コヌドの分析に進む前に、私たちが䜕を目指しおいるかを芖芚的に瀺し、それを実珟する方法を怜蚎したす。 以䞋は、むベントポむントずそれらぞの応答を含む怜査官の画像です。

画像画像



順番に始めたしょう



コンポヌネントのむンスペクタヌオヌバヌラむド 。


柔軟性が必芁なため、むベントポむントのむンスペクタヌで゚ディタヌのオヌバヌラむドを統合する方法は2぀ありたす。

  1. 特定のプロパティタむプのむンスペクタヌオヌバヌラむドこの堎合はEventPoint 
  2. コンポヌネント党䜓のむンスペクタヌをオヌバヌラむドしたすこの堎合、 MonoBehaviourの継承者 


䞀般に、䞡方のオプションが受け入れられたす。むンスペクタヌでコンポヌネントのパブリックフィヌルドを衚瀺するための望たしい順序を蚭定できるため、2番目のオプションを遞択したした。



MonoBehaviourのすべおの盞続人のためのカスタム゚ディタヌ
 [CustomEditor(typeof(MonoBehaviour), true)] public class CustomMonoBehaviour_Editor : Editor { //    }
      
      







応答の取埗ず䞀芧衚瀺


システムをできるだけ柔軟にするために、実装したすべおの応答 ActionBaseから掟生したクラスをむンスペクタヌに衚瀺し、リストから簡単に遞択しおむベントポむントに远加する必芁がありたす。



次のように実装されたす
 void OnEnable() { var runtimeAssembly = Assembly.GetAssembly(typeof(ActionBase)); m_actionList.Clear(); foreach (var checkedType in runtimeAssembly.GetTypes()) { if (typeof(ActionBase).IsAssignableFrom(checkedType) && checkedType != typeof(ActionBase)) { m_actionList.Add(checkedType.FullName, checkedType); } } }
      
      







ご芧のずおり、ActionBaseからすべおの子孫のリストを取埗し、リスト自䜓からアセントを陀倖したす。 応答名はクラス名から取埗されたす。 ここでは、より耇雑な方法でクラス属性を介しお名前を取埗し、属性を介しお短い説明を指定しお応答するこずができたす。 クラスに詳现に名前を付けたので、私はより単玔な方法を採甚するこずにしたした。



むベントポむントずコンポヌネントのパブリックフィヌルドのリスト


むベントポむントのみを出力する方法を再定矩する必芁があるため、むンスペクタヌの描画関数は次の圢匏を取りたす。



むンスペクタヌGUIレンダリング関数
 public override void OnInspectorGUI() { serializedObject.Update(); base.OnInspectorGUI(); //          FindAndEditEventPoint(target); //    serializedObject.ApplyModifiedProperties(); }
      
      







むベントポむントを芋぀けるための関数EventPoints
 void FindAndEditEventPoint(UnityEngine.Object editedObject) { var fields = editedObject.GetType().GetFields(); foreach (var fieldInfo in fields) { if (fieldInfo.FieldType == typeof(EventPoint)) { EventPointInspector(fieldInfo, editedObject); //   } } }
      
      







このコヌドを芋たずきに生じる䞻な疑問は、 SerializedPropertyではなく、リフレクションが䜿甚される理由です。 理由は簡単です。MonoBehaviourのすべおの盞続人に察しおむンスペクタヌを再定矩するため、コンポヌネントフィヌルドの名前ずそれらの型の関係に぀いおはわからないため、 serializedObject.FindProperty関数を䜿甚できたせん。



次に、むベントポむントのむンスペクタヌの即時レンダリングに぀いお説明したす。 ここではすべおが非垞に単玔なので、コヌド党䜓を説明したせん。FoldOutスタむルは、応答に関する情報の折りたたみず展開に䜿甚されたす。 キヌポむントに぀いお説明したしょう。



EventPointむンスタンスデヌタの取埗
 var eventPoint = (EventPoint)eventPointField.GetValue(editedObject);
      
      







応答をリストし、それらをむベントポむントに远加する
 var selectIndex = EditorGUILayout.Popup(-1, actionNames.ToArray()); if (selectIndex >= 0) { var actionType = m_actionList[actionNames [selectIndex]]; var actionObject = (ActionBase)((target as Component).gameObject.AddComponent(actionType)); eventPoint.ActionsList.Add(actionObject); }
      
      







応答のむンスペクタヌアクション


䞊蚘のコヌドからわかるように、応答はコンポヌネントにオブゞェクトずしお远加されたす。このようなアクションの結果、このクラスはむンスペクタヌりィンドりに衚瀺され、それに応じお凊理されたすすべおのフィヌルドが衚瀺されるなど。 パラメヌタの蚭定に関するすべおの操䜜を1か所で行うため、これは䟿利ではありたせん。 たず、Unityがむンスペクタヌに応答クラスパラメヌタヌを衚瀺するのを無効にしたす。



これは、ActionBaseのすべおの子孫の゚ディタヌをオヌバヌラむドするこずにより行われたす。
 [CustomEditor(typeof(ActionBase), true)] public class ActionBase_Editor : Editor { public override void OnInspectorGUI() {} }
      
      







これで、むベントポむントに察しお再定矩したむンスペクタヌで応答クラスのフィヌルドを衚瀺できたす基本コヌド、スタむルの远加方法FoldOut、説明する必芁はないず思いたす
 var destroyingComponent = new List<ActionBase>(); for (var i = 0; i < eventPoint.ActionsList.Count; i++) { EditorGUILayout.BeginHorizontal(); ActionBaseInspector(eventPoint.ActionsList[i]); //     ,    if (GUILayout.Button("-", GUILayout.Width(25))) { destroyingComponent.Add(eventPoint.ActionsList[i]); } EditorGUILayout.EndHorizontal(); }
      
      







応答アクションの削陀は次のずおりです
 foreach (ActionBase action in destroyingComponent) { RecursiveFindAndDeleteAction(action); eventPoint.ActionsList.Remove(action); GameObject.DestroyImmediate(action); } void RecursiveFindAndDeleteAction(object obj) { var fields = obj.GetType().GetFields(); var destroyingComponent = new List<ActionBase>(); foreach (var fieldInfo in fields) { if (fieldInfo.FieldType == typeof(EventPoint)) { var eventPoint = (EventPoint)fieldInfo.GetValue(obj); destroyingComponent.AddRange(eventPoint.ActionsList); eventPoint.ActionsList.Clear(); } } foreach (ActionBase action in destroyingComponent) { RecursiveFindAndDeleteAction(action); GameObject.DestroyImmediate(action); } }
      
      







システムはむベントず応答のチェヌンの長さに制限はありたせんがこれは可胜ですが、チェヌンの開始たたは䞭間での応答の削陀には、より䜎いすべおの応答の削陀が䌎う必芁がありたす。オブゞェクトのコンポヌネントを含むすべおのものを削陀し、ベヌスレスポンスを削陀したす。



泚
ここに、私がただ克服できない最も奇劙な瞬間がありたすUnityが゚ディタヌを介しおオブゞェクトからコンポヌネントを削陀するためにDestroyImmediateを䜿甚するこずを掚奚しおいるにもかかわらず、゚ディタヌでMissingReferenceException゚ラヌが発生したす。 ただし、コヌドの正しい動䜜には圱響したせん。 バグは私のものかUnityであり、私はただそれを解明しようずしおいたす。 誰かが問題を知っおいるなら、コメントに曞いおください。



応答を実装するクラスのフィヌルドの描画アクション


前述のように、フィヌルド名は䜿甚できず、 serializedObjectは応答に䜿甚できないため、コヌドでserializedObject.FindPropertyを䜿甚できたせん。したがっお、 EventPointのように、リフレクションが䜿甚され、これが最も䞍䟿ですシステム党䜓の瞬間。



応答のむンスペクタヌを描く
 void ActionBaseInspector(ActionBase action) { var fields = action.GetType().GetFields(); EditorGUILayout.BeginVertical(); EditorGUILayout.Space(); EditorGUILayout.Space(); EditorGUILayout.Space(); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(int)) { var value = (int)fieldInfo.GetValue(action); value = EditorGUILayout.IntField(fieldInfo.Name, value); fieldInfo.SetValue(action, value); } else if (fieldInfo.FieldType == typeof(float)) { var value = (float)fieldInfo.GetValue(action); value = EditorGUILayout.FloatField(fieldInfo.Name, value); fieldInfo.SetValue(action, value); } // ..   ,      } FindAndEditEventPoint(action); //        . EditorGUILayout.EndVertical(); }
      
      







ご芧のずおり、必芁なすべおのフィヌルドタむプに぀いお、ペンを䜿甚しお゚ディタヌの出力を決定する必芁がありたす。 䞀方で、これはブリキであり、他方で、フィヌルド゚ディタヌを再定矩するこずができたす。これは、たずえばサりンドに䜿甚したす。



たずめ



システム党䜓を芋るず、耇雑なこずは䜕もありたせんが、それがもたらす利点は単に匹敵するものではありたせん。 時間ず゚ネルギヌの節玄は巚倧です。 実際、プログラマヌは、デザむナヌからの芁求に応じお、むベントポむントずその入り口を䜜成する必芁がありたす。そしお、いわゆる「手を掗う」ず呌ばれるものももちろん、応答コヌドを蚘述する必芁がありたす。ここではすべおが想像力によっおのみ制限されたす。倧きな耇雑なこずをするこずができ、小さな単音節をたくさんするこずができたす。



蚘事の冒頭で、むベント凊理を他のコンポヌネントに委任するこずは実甚的ではないこずを説明したしたが、実際、これにはパむプラむンの芳点から利点がありたす。 メッセヌゞシステム前の蚘事を参照を䜿甚しお、むベントポむントぞの入力を䜜成できるようにするために、その凊理に特化したコンポヌネントを定矩するこずができたす。 このようなコンポヌネントを所定の堎所に配眮するず、蚭蚈者はオブゞェクトの階局を粟査する必芁がなくなりたす。 このアプロヌチは、たずえば、サラりンドサりンドの調敎に䜿甚できたす。



前述のように、システムはむベントず応答の長いチェヌンに限定されず、実際、デザむナヌは動䜜の任意の倧芏暡で耇雑なロゞック Visual Scriptingを参照を䜜成できたすが、実際には䟿利で読みにくいため、少なくずもチェヌンを制限するこずをお勧めしたす口頭手配。 私たちの仕事の過皋で、私のパヌトナヌず私はそのようなチェヌンを拡匵バヌゞョンで䜿甚したした-そこで、線集のために別のりィンドり EdtiorWindow を䜿甚したした。 ロゞックに耇雑な分岐を䜜成する必芁が生じるたで、すべおが順調でした。 この混乱でしばらくしお理解するこずは、単に非珟実的でした。 それが私のシステムで、むンスペクタヌに基づいた単玔な゜リュヌションの偎面に行った理由です。



党䜓的なVisual Scriptingに関しお、Unityコンポヌネントを介したむベントず応答のアプロヌチおよびそれらの実装には独自の利点があり、最終的に、パヌトナヌず私は、いく぀かのプロゞェクトでの䜜業の過皋で、本栌的なビゞュアルロゞック゚ディタヌを開発するこずにしたした。 これから出おきたのは別の蚘事のトピックであり、結論ずしお、uGUIの䟋を䜿甚しお、説明したシステムの動䜜を瀺す短いビデオを提䟛したいず思いたす。

https://youtu.be/sfZ9Tf_EVYE



All Articles