Методи організації взаємодії між скриптами в Unity3D

Вступ

Нехай у нас у проекті є два скрипти. Перший скрип відповідає за нарахування очок у грі, а другий за інтерфейс користувача, який, відображає кількість набраних очок на екрані гри. Назвемо обидва скрипти менеджерами: ScoresManager і HUDManager. Яким чином менеджеру, що відповідає за меню екрана можна отримати поточну кількість очок від менеджера, що відповідає за нарахування очок? Передбачається, що в ієрархії об'єктів (Hierarchy) сцени існують два об'єкти, на один з яких призначений скрипт ScoresManager, а на інший скрипт HUDManager. Один із підходів, містить наступний принцип: У скрипті UIManager визначаємо змінну типу ScoresManager:

Але змінну ScoresManager необхідно ще ініціалізувати екземпляром класу. Для цього виберемо в ієрархії об'єктів об'єкт, на який призначено скрипт HUDManager і в налаштуваннях об'єкта побачимо змінну ScoresManager зі значенням None.

методи

Далі, з вікна ієрархії перетягуємо об'єкт, що містить скрипт ScoresManager в область, де написано None і призначаємо його оголошеною змінною:

взаємодії

Після чого, у нас з'являється можливість з коду HUDManager звертатися до скрипту ScoresManager, таким чином:

Підхід 2. «Сінглтони»

Застосуємо спрощену класифікацію можливих скриптів, що використовуються під час створення гри. Перший тип скриптів: "скрипти-менеджери", другий: "скрипти-ігрові-об'єкти". Основна відмінність одних від інших у тому, що «скрипти-менеджери» завжди мають єдиний екземпляр у грі, тоді як «скрипти-ігрові-об'єкти» можуть мати кількість екземплярів більше одиниці.

Як правило, в єдиному екземплярі існують скрипти, які відповідають за загальну логіку інтерфейсу користувача, запрогравання музики, за відстеження умов завершення рівня, за керування системою завдань, за відображення спецефектів тощо. В той же час, скрипти ігрових об'єктів існують у великій кількості екземплярів: кожна пташка з «Angry Birds» управляється екземпляром скрипта пташки зі своїм унікальним станом; для будь-якого юніту у стратегії створюється екземпляр скрипту юніту, що містить його поточну кількість життів, позицію на полі та особисту мету; поведінка п'яти різних іконок забезпечується різними екземплярами тих самих скриптів, відповідальних за цю поведінку. У прикладі з попереднього кроку скрипти HUDManager і ScoresManager завжди існують в єдиному екземплярі. Для їх взаємодії один з одним застосуємо патерн "синглтон" (Singleton, він же одинак). У класі ScoresManager опишемо статичну властивість типу ScoresManager, в якій зберігатиметься єдиний екземпляр менеджера очок:

Залишилося ініціалізувати властивість Instance екземпляром класу, який створює середовище Unity3D. Так як ScoresManager спадкоємець MonoBehaviour, то він бере участь у життєвому циклі всіх активних скриптів у сцені і під час ініціалізації скрипту у нього викликається метод Awake. У цей спосіб ми і помістимо код ініціалізації властивості Instance:

Після чого, використовувати ScoresManager з інших скриптів можна наступним чином:

— підхід забезпечує доступ лише до «скриптів-менеджерів», які існують у єдиному екземплярі. - сильна пов'язаність. На останньому «мінусі» зупинимося докладніше. Нехай ми розробляємо гру, в якій є персонажі (unit), і ці персонажі можуть гинути (die). Десь знаходиться ділянка коду, яка перевіряє, чи не загинув наш персонаж:

Як гра може відреагувати на смертьперсонажа? Безліч різноманітних реакцій! Наведу кілька варіантів: треба видалити персонажа зі сцени гри, щоб він більше не відображався на ній. - у грі нараховуються очки за кожного загиблого персонажа, потрібно їх нарахувати та оновити значення на екрані. - на спеціальній панелі відображаються всі персонажі в грі, де ми можемо вибрати конкретного персонажа. При смерті персонажа нам потрібно оновити панель, або прибрати персонажа з неї, або відобразити що він мертвий. - Треба програти звуковий ефект смерті персонажа. - потрібно програти візуальний ефект смерті персонажа (вибух, бризки крові). - система досягнень гри має досягнення, яке вважає загальну кількість убитих персонажів за весь час. Потрібно додати до лічильника щойно померлого персонажа. - система аналітики гри відправляє на зовнішній сервер факт смерті персонажа, нам цей факт важливий для відстеження прогресу гравця. Враховуючи все вищезгадане, функція Die може виглядати так:

Виходить, що персонаж після своєї смерті повинен розіслати всім компонентам, які в ній зацікавлені цей сумний факт, він повинен знати про існування цих компонентів і повинен знати, що вони їм цікавляться. Чи не дуже багато знань для маленького юніта? Оскільки гра, за логікою, дуже пов'язана структура, то й події, що відбуваються в інших компонентах, цікавлять треті, юніт тут нічим не особливий. Приклади таких подій (далеко не всі): — Умова проходження рівня залежить від кількості набраних очок, набрали 1000 очок – пройшли рівень (LevelConditionManager пов'язаний із ScoresManager). — Коли набираємо 500 очок, досягаємо важливу стадію проходження рівня, потрібно програти веселу мелодію та візуальний ефект (ScoresManager пов'язаний з EffectsManager іSoundsManager). - Коли персонаж відновлює здоров'я, потрібно програти ефект лікування над картинкою персонажа в панелі персонажа (UnitsPanel пов'язаний з EffectsManager). — і так далі. У результаті таких зв'язків ми приходимо до картини схожої на наступну, де всі всі знають:

взаємодії

Приклад зі смертю персонажа трохи перебільшений, повідомляти про смерть (чи іншу подію) шести різних компонентів не так часто доводиться. Але варіанти, коли при якійсь події в грі, функція, в якій відбулася подія, повідомляє про це 2-3 іншим компонентам зустрічається часто-густо по всьому коду. Наступний підхід намагається вирішувати цю проблему.

Підхід 3. Світовий ефір (Event Aggregator)

Додаємо цю подію в «EventAggregator»:

Тепер, функція Die з попереднього прикладу з вісьмома рядками перетворюється на функцію з одним рядком коду. Нам немає необхідності повідомляти про те, що юніт помер усім заінтересованим компонентам і знати про цих заінтересованих. Ми просто публікуємо факт здійснення події:

А будь-який компонент, якому цікава ця подія, може відреагувати на нього наступним чином (на прикладі менеджера, який відповідає за кількість набраних очок):

unity3d

Говорячи, що ніякий інший код не змінюється, я звичайно трохи лукавлю. Може виявитися так, що систему досягнень цікавлять події, які раніше просто не публікувалися у грі, бо жодну іншу систему до цього не цікавили. І в цьому випадку, нам потрібно буде вирішити якісь нові події додати в гру і хто їх публікуватиме. Але в ідеальній грі вже всі можливі події є і ефір наповнений ними на повну.

— потрібно постійно описувати нові події та додавати їх у світ. - порушення функціональної атомарності.

Останній мінус розглянемо детальніше

Висновок

Хардкорна конфа за С++. Ми запрошуємо лише профі.