Методи за организиране на взаимодействие между скриптове в Unity3D
Въведение
Да предположим, че имаме два скрипта в проекта. Първият скрипт отговаря за отбелязването на точки в играта, а вторият за потребителския интерфейс, който показва броя отбелязани точки на екрана на играта. Нека извикаме и двата мениджъра на скриптове: ScoresManager и HUDManager. Как може мениджърът, отговарящ за екранното меню, да получи текущия резултат от мениджъра, отговарящ за точкуването? Предполага се, че има два обекта в йерархията на обектите (Hierarchy) на сцената, на единия от които е присвоен скриптът ScoresManager, а на другия е скриптът HUDManager. Един от подходите съдържа следния принцип: В скрипта UIManager ние дефинираме променлива от тип ScoresManager:
Но променливата ScoresManager все още трябва да бъде инициализирана с екземпляр на класа. За да направите това, изберете в йерархията на обекта обекта, към който е присвоен скриптът HUDManager и в настройките на обекта ще видим променливата ScoresManager със стойност None.

След това от прозореца на йерархията плъзнете обекта, съдържащ скрипта ScoresManager, в областта, където е написано None, и го присвоете на декларираната променлива:

След това имаме възможност за достъп до скрипта ScoresManager от кода на HUDManager, като по този начин:
Подход 2. „Сингълтони“
Нека приложим опростена класификация на възможните скриптове, които се използват при създаването на игра. Първият тип скриптове: "мениджърски скриптове", вторият: "скриптове-игра-обекти". Основната разлика между едното и другото е, че „скриптовете за мениджър“ винаги имат един екземпляр в играта, докато „скриптовете за обект на игра“ могат да имат повече от един екземпляр.
Като правило, в единичен случай има скриптове, отговорни за общата логика на потребителския интерфейс, завъзпроизвеждане на музика, за проследяване на условията за завършване на ниво, за управление на системата за мисии, за показване на специални ефекти и т.н. В същото време скриптовете на игрови обекти съществуват в голям брой екземпляри: всяка птица Angry Birds се контролира от екземпляр на скрипта на птицата със свое собствено уникално състояние; за всяка единица в стратегията се създава екземпляр от скрипта на единицата, съдържащ текущия брой животи, позиция на полето и лична цел; поведението на пет различни икони се осигурява от различни екземпляри на едни и същи скриптове, отговорни за това поведение. В примера от предишната стъпка скриптовете HUDManager и ScoresManager винаги съществуват в един екземпляр. За тяхното взаимодействие помежду си е приложим моделът Singleton. В класа ScoresManager ще опишем статично свойство от типа ScoresManager, което ще съхранява един екземпляр на мениджъра на резултатите:
Остава да се инициализира свойството Instance с екземпляр на класа, който Unity3D средата създава. Тъй като ScoresManager е наследник на MonoBehaviour, той участва в жизнения цикъл на всички активни скриптове в сцената и по време на инициализацията на скрипта се извиква методът Awake. В този метод поставяме кода за инициализация за свойството Instance:
След това можете да използвате ScoresManager от други скриптове, както следва:
- подходът предоставя достъп само до "мениджърски скриптове", които съществуват в един екземпляр. —силна свързаност. Нека се спрем на последния "минус" по-подробно. Да кажем, че разработваме игра, в която има герои (единици) и тези герои могат да умрат (умират). Някъде има част от кода, който проверява дали нашият герой е умрял:
Как играта може да реагира на смърттахарактер? Много различни реакции! Ето няколко опции: - трябва да премахнете героя от сцената на играта, така че да не се появява повече на нея. —в играта се присъждат точки за всеки мъртъв персонаж, трябва да ги натрупате и да актуализирате стойността на екрана. - специален панел показва всички герои в играта, където можем да изберем конкретен герой. Когато герой умре, трябва да актуализираме панела, или да премахнем героя от него, или да покажем, че е мъртъв. - Трябва да пуснете звуковия ефект от смъртта на героя. - трябва да изиграете визуалния ефект от смъртта на героя (експлозия, пръски кръв). —Системата за постижения на играта има постижение, което отчита общия брой убити герои за времето. Трябва да добавите към брояча на героя, който току-що е починал. — системата за анализ на играта изпраща факта за смъртта на персонаж на външен сървър, този факт е важен за нас, за да проследим напредъка на играча. Като се има предвид всичко по-горе, функцията Die може да изглежда така:
Оказва се, че героят след смъртта му трябва да изпрати този тъжен факт на всички компоненти, които се интересуват от него, той трябва да знае за съществуването на тези компоненти и трябва да знае, че те се интересуват от него. Не е ли твърде много знания за малка единица? Тъй като играта, логично, е много свързана структура, събитията, случващи се в други компоненти, са от интерес за трети страни, единицата не е нищо особено тук. Примери за такива събития (не всички): — Условието за преминаване на нивото зависи от броя отбелязани точки, ако съберете 1000 точки, преминавате нивото (LevelConditionManager е свързан с ScoresManager). - Когато достигнем 500 точки, достигаме важен етап от преминаването на нивото, трябва да изсвирим забавна мелодия и визуален ефект (ScoresManager е свързан с EffectsManager иМениджър на звука). - Когато герой възстанови здравето си, трябва да пуснете лечебния ефект върху снимката на героя в панела с герои (UnitsPanel е свързан с EffectsManager). —и така нататък. В резултат на такива връзки стигаме до картина, подобна на следната, където всеки познава всеки за всеки:

Примерът със смъртта на героя е малко преувеличен, не е необходимо често да се съобщава за смърт (или друго събитие) на шест различни компонента. Но опциите, когато при някакво събитие в играта функцията, в която се е случило събитието, информира 2-3 други компонента за него, се появяват през цялото време в кода. Следният подход се опитва да разреши този проблем.
Подход 3. Световно излъчване (Агрегатор на събития)
Добавете това събитие към „EventAggregator“:
Сега функцията Die от предишния пример с осем реда се преобразува във функция с един ред код. Не е необходимо да съобщаваме, че единица е умряла на всички заинтересовани компоненти и да знаем за тези заинтересовани. Просто публикуваме факта на събитието:
И всеки компонент, който се интересува от това събитие, може да реагира на него по следния начин (използвайки примера на мениджър, отговорен за броя отбелязани точки):

Когато казвам, че няма други промени в кода, разбира се, съм малко неискрен. Може да се окаже, че системата за постижения се интересува от събития, които просто не са били публикувани преди това в играта, защото никоя друга система не се е интересувала от нея преди. И в този случай ще трябва да решим какви нови събития да добавим към играта и кой ще ги публикува. Но в една идеална игра всички възможни събития вече съществуват и въздухът е изпълнен с тях максимално.
- трябва постоянно да описвате нови събития и да ги добавяте към света. — нарушение на функционалната атомарност.
Нека разгледаме последния минус по-подробно.
Заключение
Hardcore conf в C++. Каним само професионалисти.