Unity3Dでスクリプト間の盞互䜜甚を敎理する方法

゚ントリヌ


平均的なUnity3Dプロゞェクトでさえ、非垞に迅速に倚皮倚様なスクリプトで満たされ、これらのスクリプトが盞互にどのように盞互䜜甚するかずいう疑問が生じたす。
この蚘事では、このような盞互䜜甚を単玔なものから高床なものたで䜓系化するためのいく぀かの異なるアプロヌチを提䟛し、各アプロヌチがもたらす問題を説明し、これらの問題を解決する方法を提案したす。

アプロヌチ1. Unity3D゚ディタヌによる割り圓お


プロゞェクトに2぀のスクリプトがあるずしたす。 最初のきしみはゲヌム内のポむントのスコアリングを担圓し、2番目のきしみはゲヌム画面で埗点したポむントの数を衚瀺するナヌザヌむンタヌフェむスを担圓したす。
䞡方のスクリプトマネヌゞャヌ、ScoresManagerずHUDManagerを呌び出したす。
それでは、スクリヌンメニュヌを担圓するマネヌゞャヌは、ポむントの獲埗を担圓するマネヌゞャヌから珟圚のポむント数をどのように受け取るこずができたすか
シヌンのオブゞェクトの階局階局には2぀のオブゞェクトがあり、1぀にScoresManagerスクリプトが割り圓おられ、もう1぀にHUDManagerスクリプトが割り圓おられおいるず想定されおいたす。
1぀のアプロヌチには、次の原則が含たれたす。
UIManagerスクリプトで、ScoresManagerタむプの倉数を定矩したす。

public class HUDManager : MonoBehaviour { public ScoresManager ScoresManager; } 

ただし、ScoresManager倉数はクラスのむンスタンスで初期化する必芁がありたす。 これを行うには、オブゞェクト階局でHUDManagerスクリプトが割り圓おられおいるオブゞェクトを遞択したす。オブゞェクト蚭定では、倀NoneのScoresManager倉数が衚瀺されたす。

画像

次に、階局りィンドりから、ScoresManagerスクリプトを含むオブゞェクトをNoneが曞き蟌たれおいる領域にドラッグし、宣蚀された倉数に割り圓おたす。

画像

その埌、HUDManagerコヌドからScoresManagerスクリプトにアクセスする機䌚がありたす。

 public class HUDManager : MonoBehaviour { public ScoresManager ScoresManager; public void Update () { ShowScores(ScoresManager.Scores); } } 

シンプルですが、ゲヌムはポむントを獲埗するだけでなく、HUDはプレヌダヌの珟圚の生掻、利甚可胜なプレヌダヌアクションのメニュヌ、レベル情報などを衚瀺できたす。 ゲヌムには、互いに情報を受信する必芁のある数十から数癟の異なるスクリプトを含めるこずができたす。
1぀のスクリプトで別のスクリプトからデヌタを取埗するには、1぀のスクリプトで倉数を蚘述し、゚ディタヌを䜿甚しお倉数を手動で割り圓おドラッグアンドドロップする必芁がありたす。 。
䜕かをリファクタリングしたい堎合は、スクリプトの名前を倉曎するず、名前が倉曎されたスクリプトに関連付けられたオブゞェクトの階局内のすべおの叀い初期化がリセットされ、再床割り圓おる必芁がありたす。
同時に、そのようなメカニズムは、プレハブテンプレヌトからのオブゞェクトの動的䜜成では機胜したせん。 プレハブがオブゞェクトの階局にあるマネヌゞャヌにアクセスする必芁がある堎合、階局からプレハブ自䜓に芁玠を割り圓おるこずはできたせんが、最初にプレハブからオブゞェクトを䜜成しおから、プログラムでマネヌゞャヌむンスタンスを新しく䜜成されたオブゞェクトの倉数に割り圓おる必芁がありたす。 䞍芁な䜜業、䞍芁なコヌド、远加の接続性。
次のアプロヌチは、これらすべおの問題を解決したす。

アプロヌチ2.シングルトン


ゲヌムの䜜成に䜿甚される可胜性のあるスクリプトの簡略化された分類を適甚したす。 最初のタむプのスクリプト「scripts-managers」、2番目のタむプ「scripts-game-objects」。
他のいく぀かの䞻な違いは、「スクリプトマネヌゞャヌ」は垞にゲヌム内で単䞀のむンスタンスを持ちたすが、「スクリプトゲヌムオブゞェクト」は耇数のむンスタンスを持぀こずができるこずです。

䟋


原則ずしお、単䞀のコピヌには、ナヌザヌむンタヌフェむスの䞀般的なロゞック、音楜の再生、レベル完了条件の監芖、タスクシステムの管理、特殊効果の衚瀺などを担圓するスクリプトがありたす。
同時に、ゲヌムオブゞェクトのスクリプトは倚数のむンスタンスに存圚したす。「Angry Birds」の各鳥は、固有の状態を持぀鳥スクリプトのむンスタンスによっお制埡されたす。 戊略のどのナニットに぀いおも、ナニットスクリプトのむンスタンスが䜜成され、その䞭には珟圚のラむフ数、フィヌルド䞊の䜍眮、個人的な目暙が含たれたす。 5぀の異なるアむコンの動䜜は、この動䜜を担圓する同じスクリプトの異なるむンスタンスによっお提䟛されたす。
前のステップの䟋では、HUDManagerおよびScoresManagerスクリプトは垞に単䞀のむンスタンスに存圚したす。 盞互䜜甚のために、「シングルトン」パタヌンシングルトン、別名ロヌンを適甚したす。
ScoresManagerクラスでは、ScoresManager型の静的プロパティを蚘述したす。このプロパティには、ポむントマネヌゞャヌの単䞀のむンスタンスが栌玍されたす。

 public class ScoresManager : MonoBehaviour { public static ScoresManager Instance { get; private set; } public int Scores; } 

Unity3D環境を䜜成するクラスのむンスタンスでInstanceプロパティを初期化するこずは残りたす。 ScoresManagerはMonoBehaviourの継承者であるため、シヌン内のすべおのアクティブなスクリプトのラむフサむクルに参加し、スクリプトの初期化䞭にAwakeメ゜ッドが呌び出されたす。 このメ゜ッドでは、Instanceプロパティの初期化コヌドを配眮したす。

 public class ScoresManager : MonoBehaviour { public static ScoresManager Instance { get; private set; } public int Scores; public void Awake() { Instance = this; } } 

その埌、次のように他のスクリプトからScoresManagerを䜿甚できたす。

 public class HUDManager : MonoBehaviour { public void Update () { ShowScores(ScoresManager.Instance.Scores); } } 

これで、HUDManagerがScoresManagerタむプのフィヌルドを蚘述しおUnity3D゚ディタヌで割り圓おる必芁がなくなり、「スクリプトマネヌゞャヌ」は、Awake関数で初期化される静的むンスタンスプロパティを介しお自身ぞのアクセスを提䟛できたす。

長所


-スクリプトフィヌルドを説明し、Unity3D゚ディタヌで割り圓おる必芁はありたせん。
-コヌドを安党にリファクタリングできたす。䜕かが萜ちた堎合は、コンパむラが通知したす。
-Instanceプロパティを介しお、プレハブから他の「スクリプトマネヌゞャヌ」にアクセスできるようになりたした。

短所


-このアプロヌチでは、単䞀のコピヌに存圚する「スクリプトマネヌゞャヌ」のみにアクセスできたす。
-匷い぀ながり。
最埌の「マむナス」でより詳现に説明したす。
キャラクタヌナニットがいお、これらのキャラクタヌが死ぬダむこずができるゲヌムを開発したしょう。
どこかに、キャラクタヌが死んでいるかどうかを確認するコヌドの䞀郚がありたす。

 public class Unit : MonoBehaviour { public int LifePoints; public void TakeDamage(int damage) { LifePoints -= damage; if (LifePoints <= 0) Die(); } } 

ゲヌムはキャラクタヌの死にどのように察応できたすか 倚くの異なる反応 いく぀かのオプションを玹介したす。
-キャラクタヌをゲヌムシヌンから削陀しお、キャラクタヌが登堎しないようにする必芁がありたす。
-ゲヌムでは、死んだキャラクタヌごずにポむントが付䞎されたす。それらを獲埗し、画面䞊の倀を曎新する必芁がありたす。
-特別なパネルには、ゲヌム内のすべおのキャラクタヌが衚瀺され、特定のキャラクタヌを遞択できたす。 キャラクタヌが死んだずき、パネルを曎新するか、パネルからキャラクタヌを削陀するか、パネルが死んでいるこずを衚瀺する必芁がありたす。
-キャラクタヌの死の効果音を再生する必芁がありたす。
-キャラクタヌの死の芖芚効果爆発、血しぶきを再生する必芁がありたす。
-ゲヌムのアチヌブメントシステムには、垞にキルされたキャラクタヌの総数をカりントするアチヌブメントがありたす。 死んだばかりのキャラクタヌをカりンタヌに远加する必芁がありたす。
-ゲヌム分析システムは、キャラクタヌの死の事実を倖郚サヌバヌに送信したす。この事実は、プレむダヌの進行状況を远跡するために重芁です。
䞊蚘のすべおを考慮するず、Die関数は次のようになりたす。

 private void Die() { DeleteFromScene(); ScoresManager.Instance.OnUnitDied(this); LevelConditionManager.Instance.OnUnitDied(this); UnitsPanel.Instance.RemoveUnit(this); SoundsManager.Instance.PlayUnitDieSound(); EffectsManager.Instance.PlaySmallExplosion(); AchivementsManager.Instance.OnUnitDied(this); AnaliticsManager.Instance.SendUnitDiedEvent(this); } 

圌の死埌のキャラクタヌは、この悲しい事実に興味を持っおいるすべおのコンポヌネントに送信する必芁があり、これらのコンポヌネントの存圚を知り、圌らが圌に興味を持っおいるこずを知っおいる必芁がありたす。 小さなナニットには知識が倚すぎたすか
ゲヌムは論理的に非垞に関連性の高い構造であるため、他のコンポヌネントで発生するむベントは3番目の関心事であり、このナニットは特別なものではありたせん。
そのようなむベントの䟋決しおすべおではない
-レベルを枡すための条件は、埗点、1000ポむントを獲埗したポむントの数に䟝存したす-レベルを枡したしたLevelConditionManagerはScoresManagerに関連付けられおいたす。
-500ポむントを集めるず、レベルを枡す重芁な段階に到達したす。楜しいメロディず芖芚効果を再生する必芁がありたすScoresManagerはEffectsManagerずSoundsManagerに関連付けられおいたす。
-キャラクタヌが健康を回埩したら、キャラクタヌのパネルでキャラクタヌの写真に癒し効果をかける必芁がありたすUnitsPanelはEffectsManagerに関連付けられおいたす。
-など。
このような接続の結果、次のような写真になりたす。誰もがすべおのこずを知っおいたす。

画像

キャラクタヌの死を䌎う䟋は少し誇匵されおおり、死たたは他のむベントを6぀の異なるコンポヌネントに報告する必芁はあたりありたせん。 しかし、オプションは、ゲヌム内のあるむベントで、むベントが発生した機胜がこの2〜3個のコンポヌネントに぀いお通知するずきに、コヌド党䜓に枡っお芋぀かるこずです。
次のアプロヌチは、この問題を解決しようずしたす。

アプロヌチ3. World Aetherむベントアグリゲヌタヌ


特別なコンポヌネント「EventAggregator」を導入したす。その䞻な機胜は、ゲヌムで発生するむベントのリストを保存するこずです。
ゲヌム内のむベントは、他のコンポヌネントに自分自身にサブスクラむブし、このむベントの発生の事実を公開する機䌚を提䟛する機胜です。 むベント機胜の実装は、開発者にずっお奜みに応じお行うこずができたす。暙準蚀語゜リュヌションを䜿甚するか、独自の実装を䜜成できたす。
過去の䟋のむベントの単玔な実装䟋ナニットの死に぀いお

 public class UnitDiedEvent { private readonly List<Action<Unit>> _callbacks = new List<Action<Unit>>(); public void Subscribe(Action<Unit> callback) { _callbacks.Add(callback); } public void Publish(Unit unit) { foreach (Action<Unit> callback in _callbacks) callback(unit); } } 

このむベントを「EventAggregator」に远加したす。

 public class EventAggregator { public static UnitDiedEvent UnitDied; } 

これで、前の8行の䟋のDie関数が単䞀行関数に倉換されたす。 関心のあるすべおのコンポヌネントに察しおナニットが死亡したこずを報告する必芁はありたせん。 むベントの事実を公開するだけです。

 private void Die() { EventAggregator.UnitDied.Publish(this); } 

たた、このむベントに関心のあるコンポヌネントは、次のように応答できたすたずえば、埗点の数を担圓するマネヌゞャヌ。

 public class ScoresManager : MonoBehaviour { public int Scores; public void Awake() { EventAggregator.UnitDied.Subscribe(OnUnitDied); } private void OnUnitDied(Unit unit) { Scores += CalculateScores(unit); } } 

Awake関数では、マネヌゞャヌはむベントにサブスクラむブし、このむベントの凊理を担圓するデリゲヌトを枡したす。 むベントハンドラヌ自䜓は、死亡したナニットのむンスタンスをパラメヌタヌずしお受け取り、このナニットのタむプに応じおポむント数を远加したす。
同様に、ナニットの死亡むベントに関心がある他のすべおのコンポヌネントは、むベントが発生したずきにサブスクラむブしお凊理できたす。
その結果、各コンポヌネントが互いを知っおいる堎合、コンポヌネント間の接続の図は、コンポヌネントがゲヌムで発生するむベントそれらが関心のあるむベントに぀いおのみを知っおいるずきに図に倉わりたすが、これらのむベントがどこから来たかは気にしたせん。 新しいグラフは次のようになりたす。

画像

私は別の解釈が倧奜きです。「EventAggregator」長方圢がすべおの方向に䌞び、それ自䜓の内偎にある他のすべおの長方圢をキャプチャしお、䞖界の境界になったず想像しおください。 私の頭の䞭で、この図では、EventAggregatorはたったくありたせん。 「EventAggregator」は単なるゲヌムの䞖界であり、䞀皮の「ゲヌムブロヌドキャスト」であり、ゲヌムのさたざたな郚分が「Hey people そのような郚隊は死にたした」そしお誰もが攟送を聞いおおり、聞いた出来事のどれかが圌らに興味があるなら、圌らはそれに反応したす。 したがっお、接続はなく、各コンポヌネントは独立しおいたす。
私がコンポヌネントであり、䜕らかのむベントの発行を担圓しおいる堎合、私はこれが死んだこず、これがレベルを獲埗したこず、シェルが戊車に圓たったこずを叫んでいたす。 そしお、誰かがそれを気にするかどうかは気にしたせん。 おそらくこのむベントを誰も聞いおいないか、他の䜕癟ものオブゞェクトがサブスクラむブされおいる可胜性がありたす。 私は、むベントの著者ずしお、1グラムに぀いおは気にしたせん。それらに぀いおは䜕も知りたせんし、知りたくありたせん。
このアプロヌチにより、叀い機胜を倉曎せずに新しい機胜を簡単に導入できたす。 完成したゲヌムにアチヌブメントシステムを远加するこずにしたずしたす。 達成システムの新しいコンポヌネントを䜜成し、関心のあるすべおのむベントにサブスクラむブしたす。 他のコヌドの倉曎はありたせん。 他のコンポヌネントに目を通す必芁はありたせん。それらからアチヌブメントシステムを呌び出しお、圌女に蚀っおください、私のむベントをカりントしおください。 さらに、䞖界でむベントを公開するすべおの人は、達成システムに぀いおも、その存圚の事実に぀いおも䜕も知りたせん。

発蚀


他のコヌドは倉わらないず蚀っお、もちろん私は少し䞍誠実です。 アチヌブメントシステムは、以前はゲヌムに公開されおいなかったむベントに関心があるこずが刀明する堎合がありたす。 この堎合、どの新しいむベントをゲヌムに远加し、誰がそれらを公開するかを決定する必芁がありたす。 しかし、理想的なゲヌムでは、考えられるすべおのむベントがすでにそこにあり、゚ヌテルはそれらでいっぱいになりたす。

長所


-コンポヌネントは接続されおいたせん。むベントを公開するだけで十分であり、興味のある人は関係ありたせん。
-コンポヌネントは接続されおいたせん。必芁なむベントをサブスクラむブするだけです。
-既存の機胜を倉曎せずに個々のモゞュヌルを远加できたす。

短所


-新しいむベントを絶えず説明し、䞖界に远加する必芁がありたす。
-機胜原子性の違反。

最埌のマむナスを詳现に怜蚎する


MethodAメ゜ッドが呌び出されるObjectAオブゞェクトがあるずしたす。 MethodAメ゜ッドは3぀のステップで構成され、これらのステップを順次実行する他の3぀のメ゜ッドMethodA1、MethodA2、およびMethodA3自䜓を呌び出したす。 2番目のメ゜ッドMethodA2では、むベントが公開されたす。 そしお、ここで次のこずが起こりたすこのむベントにサブスクラむブしおいるすべおの人は、それを凊理し始め、独自の䜕らかのロゞックを実行したす。 このロゞックでは、他のむベントの発行も発生する可胜性があり、その凊理は新しいむベントの発行などにも぀ながる可胜性がありたす。 堎合によっおは、出版物や反応のツリヌが非垞に倧きくなるこずがありたす。 このような長いチェヌンは、デバッグが非垞に困難です。
ただし、ここで発生する可胜性のある最悪の問題は、チェヌンのブランチの1぀が「ObjectA」に戻り、他のMethodBメ゜ッドを呌び出しおむベントの凊理を開始する堎合です。 MethodAメ゜ッドは、2番目のステップで䞭断され、無効な状態が含たれおいるため、MethodAメ゜ッドはただすべおのステップを完了しおいないこずがわかりたすステップ1および2では、オブゞェクトの状態を倉曎したしたが、ステップ3からの最埌の倉曎はただ完了しおいたせん完了ず同時に、同じオブゞェクトで「MethodB」の実行が開始され、この無効な状態になりたす。 そのような状況ぱラヌを匕き起こし、キャッチするのが非垞に難しく、ロゞックがこれを行う必芁がなく、回避したい远加の耇雑さを導入する堎合、メ゜ッド呌び出しずむベントの発行の順序を制埡する必芁があるずいう事実に぀ながりたす。

解決策


説明した問題を解決するこずは難しくありたせん。むベントに遅延反応の機胜を远加するだけです。 このような機胜の簡単な実装ずしお、発生したむベントを远加するリポゞトリを䜜成できたす。 むベントが発生しおも、すぐに実行するのではなく、単に自宅のどこかに保存したす。 たた、ゲヌム内の䞀郚のコンポヌネントの機胜実行の切り替え時にたずえば、Updateメ゜ッドで、発生したむベントの存圚を確認し、そのようなむベントがある堎合は凊理を実行したす。
したがっお、MethodAメ゜ッドの実行䞭、その䞭断は発生せず、すべおの関係者は公開されたむベントを特別なリポゞトリに自分自身に曞き留めたす。 そしお、キュヌが関心のあるサブスクラむバヌに到達した埌にのみ、ストレヌゞからむベントを取埗しお凊理したす。 この時点で、MethodA党䜓が完了し、ObjectAは有効な状態になりたす。

おわりに


コンピュヌタヌゲヌムは、互いに密接に盞互䜜甚する倚数のコンポヌネントを持぀耇雑な構造です。 この盞互䜜甚を敎理するための倚くのメカニズムを考えるこずができたすが、私はむベントに基づいお説明したメカニズムを奜みたす。 誰かがそれを気に入っおくれお、私の蚘事が明確になり、圹に立぀こずを願っおいたす。

Source: https://habr.com/ru/post/J212055/


All Articles