Skip to content

Custom Components

vasili.kostin edited this page Aug 22, 2021 · 20 revisions

Разработка Custom компонентов

Общие сведения

Thing-Editor позволяет разработчику добавлять в проект свои собственные "строительные блоки" - Custom компоненты, которые он может использовать для построения игровых сцен.

Custom компонент - это javascript файл, расположенный в папке 'game-objects'. Примеры простых компонентов можно найти в тестовом проекте thing-project-example, который устанавливается вместе с Thing-Editor в инструкции по установке.

Thing-Editor загружает все Custom компоненты из папки game-objects при открытии проекта, и делает их доступными для добавления на сцену через окно Classes.

После редактирования исходного кода Custom компонента, необходимо нажать кнопку редактора Reload Custom Components, для того, чтобы изменения вступили в силу.

Thing-Editor имеет интеграцию с VSCode. Использование этого редактора является рекомендуемым для Thing-Editor, и наиболее удобным. Подробнее: Настройка VSCode для работы с Thing-Editor.

Исходный код каждого компонента Thing-Editor состоит из двух блоков:

  • javascript класс, унаследованный от одного из встроенных компонентов Thing-Editor, с описанием методов (функций класса), определяющий поведение компонента в игре.
  • Блок описания редактируемых свойств - определяющий какие свойства компонента и в каком виде будут доступны для редактирования в редакторе свойств.

В данном разделе будет рассмотрен только первый блок, относящийся к программированию компонентов, тогда как второй блок подробно описан в разделе Редактируемые свойства компонентов.

Предполагается что читатель данного раздела знаком с базовыми понятиями javascript разработки, в частности c Классами javascript, Методами классов.

Теги условной компиляции

Для того, чтобы обеспечить интеграцию компонентов с игровым редактором, компонентам необходимы специальные свойства, и методы обеспечивающие такую интеграцию. Однако, такие методы и свойства будут бесполезны в финальном билде игры, и могут замедлять игру, поэтому, в Thing-Editor используется условная компиляция кода, позволяющая автоматически "вырезать" части javascript кода, при сборке билда игры. Условная компиляция реализуется путем добавления в код специальных однострочных комментариев, начинающихся с тройного знака '/', которые никак не влияют на работоспособность не скомпилированного javascript кода, и являются маркерами областей кода, подлежащих удалению из релизного и отладочного билда игры. Такой подход позволяет не только выкинуть из билда часть кода метода, как по условию if c константным выражением false, но и выкинуть само объявление метода или отдельные параметры метода, используемые только в редакторе. Также, теги условной компиляции позволяют выбрасывать из билда код импорта внешних классов относящихся только к игровому редактору. Данный подход широко применяется в языке C++.

Удаление кода, отвечающего за интеграцию с игровым редактором:

/// #if EDITOR - тег, открывающий область подлежащую удалению вне редактора. /// #endif - тег, закрывающий область подлежащую удалению вне редактора.

Строки кода, заключенные между двумя этими тегами будут удаляться как при отладочном так и при релизном билде игры, которые запускаются вне игрового редактора.

Удаление кода, отвечающего за отладку игры:

/// #if DEBUG - тег, открывающий область подлежащую удалению в релизном билде. /// #endif - тег, закрывающий область подлежащую удалению в релизном билде (закрывающий тег не отличается от закрывающего тега предыдущей пары тегов, описанной выше).

Строки кода, заключенные между двумя этими тегами будут удаляться из релизного билда игры, но будут сохранены в отладочном билде. Данный тег полезен для оборачивания кода отладочных проверок, тестов, читов, которые работоспособны вне редактора, но необходимости в которых в релизном билде нет.

Обработка тегов условной компиляции при сборке игры, осуществляется webpack плагином ifdef-loader.

Создание Custom компонента

В Thing-Editor существует специальный инструмент (визард), для быстрого создания новых компонентов по шаблону. В конце работы визарда мы получим javascript файл, сохраненный в папку 'js/games-objects', и содержащий реализацию компонента с набором пустых методов. Данный файл сразу же откроется для редактирования в вашем текстовом редакторе по умолчанию, а созданный компонент будет доступен для добавления на сцену в Окне списка классов, либо при создании новой сцены, если вы создали компонент, унаследованный от Scene.

Для запуска визарда нужно нажать кнопку New в Окне списка классов.

Выбор шаблона для Custom компонента

Первым делом, необходимо решить, какой именно шаблон выбрать для создания нового компонента. Для выбора доступны следующие шаблоны:

  • Basic Game Object - простой игровой объект, содержащий три базовых метода init, update, onRemove, востребованных для большинства игровых объектов. В большинстве случаев вы будете использовать именно его.

  • Basic Scene - простая сцена, имеющая методы onShow, onHide, и другие методы, востребованные в большинстве игровых сцен. Рекомендуется для создания любой игровой сцены.

  • Full Game Object - игровой объект, отличающийся от Basic Game Object тем, что содержит все возможные методы, отвечающие за игровое поведение, и поведение во время редактирования сцены. Данный шаблон, обычно, представляет лишь учебный интерес, так как большинство методов, в нем перечисленные, редко находят применение, используются только во встроенных компонентах, и подлежат удалению при разработке обычного игрового объекта.

  • Full Scene - сцена, отличающийся от Basic Scene тем, что содержит все возможные методы, отвечающие за игровое поведение, и поведение во время редактирования сцены. Данный шаблон так же введен в образовательных целях, чтобы дать разработчику ознакомиться со всеми методами игровых сцен.

Название Custom компонента

Второй шаг визарда - ввод имени для нового компонента. Имя может содержать название подпапки, отделенное символом '/', в которую исходный javascript файл будет помещен. Конечное имя Компонента должно быть уникальным.

Выбор базового класса для Custom компонента

Любой Custom Компонент, создаваемый разработчиком, должен наследовать свойства и поведение существующего компонента. Либо встроенного, либо созданного разработчиком. Третий (заключительный) шаг визарда отвечает за выбор такого базового компонента, от которого свойства будут унаследованы. Обычно, игровые объекты удобно наследовать от DSprite, или MovieClip, но все зависит от функций, которые будет выполнять объект. Позднее, базовый объект можно будет изменить, путем редактирования его javascript кода.

Что дальше

Когда все три шага визарда выполнены, исходный файл вашего компонента будет создан, и открыт для редактирования, как и было обещано выше. Теперь вы можете добавить первый экземпляр своего игрового объекта на игровой экран (Подробнее: Добавление новых объектов на сцену).

Далее, вся работа заключается в написании игровой логики, отвечающей за поведение игрового объекта, взаимодействие с другими объектами и реакцию на действия игрока. Наполнении методов игрового объекта javascript кодом.

После того, как вы изменили исходный код компонента, необходимо перезагрузить его нажатием кнопки Reload Custom Components (Перезагрузка страницы редактора не требуется). В случае, если в исходном коде компонента обнаружится ошибка, будет отображено окно ошибки загрузки, с информацией о том в каком файле и на какой строке ошибка находится. Дальнейшая работа редактора будет заблокирована до исправления ошибки в коде, так как рабочий компонент необходим, для отображения игровой сцены. В некоторых случаях, исходный файл компонента может содержать ошибки, который не определяются загрузчиком Thing-Editor, и загрузка "зависает". В такой ситуации ошибка будет отображена в консоли браузера DeveloperTools.


В следующих разделах будут описаны методы, исполняемые в течении жизненного цикла игровых объектов. Вызов этих методов разработчиком, не имеет необходимости, так как они являются обработчиками событий, возникающих в Thing-Engine во время работы игры, либо при редактировании сцены во время разработки. Объявляя данные методы в исходном коде своего компонента, разработчик переопределяет соответствующий метод базового компонента, изменяя его поведение. Данный механизм является частью Объектно-Ориентированного Программирования javascript, подробнее о котором можно узнать в справочнике по javascript.

В качестве примера, реализации логики Custom компонентов, лучше всего ознакомиться с исходным кодом встроенных компонентов, либо исходным кодом Custom компонентов существующих демонстрационных проектов.

Игровые методы Custom компонента

Описанные в данном разделе методы относятся к игровой логике, и работают как в игровом редакторе, при запуске сцены на выполнение, так и в финальном билде игры. Описание данных методов не включено в справочный раздел по методам компонентов, так как вызов этих методов непосредственно разработчиком не имеет смысла, и как уже говорилось выше, данные методы являются переопределяемыми обработчиками событий.

init()

Данный метод вызывается при создании объекта. В нем нужно разместить код, отвечающий за подготовку объекта к работе.

init метод базового класса, должен быть обязательно вызван.

init() {
  super.init() // вызов 'init' метода базового класса
}

Thing-Editor осуществляет автоматическую проверку на вызов базового init метода, для раннего выявления ошибок разработки.

В классах javascript есть понятие метода конструктора, используемого для инициализации объекта. Однако, необходимость методе init обусловлена тем, что в Thing-Engine используется Пулинг (многократное переиспользование одного и того же объекта при его создании/удалении) для всех экранных объектов. В то время как javascript конструктор будет вызван только однажды, при самом первом создании объекта, init вызывается каждый раз, когда объект возвращается из пула к повторному использованию.


update()

Метод, отвечающий за поведение/движение игрового объекта. Обновление игровой логики. Данный метод вызывается с частотой 60 раз в секунду, для объектов, находящихся на игровом экране в активном контейнере.

update() {
  super.update() // вызов 'update' метода базового класса
}

Вызов update метода базового класса, не обязателен, однако, отсутствие данного вызова 'отключит' базовое поведение компонента, и 'заморозит' все дочерние объекты, так как 'update' для дочерних объектов вызывается в 'update' методе базового для всех компонента Container. Такая особенность позволяет отключать обновление игровой логики для частей сцены, что используется, например, в компоненте Trigger для остановки дочерних объектов в неактивном, невидимом триггере, и для остановки всей сцены, при появлении модального объекта.


onRemove()

Данный метод срабатывает при уничтожении объекта, через вызов метода remove. После чего объект удаляется из дерева сцены и отправляется в пул объектов. onRemove метод базового класса, должен быть обязательно вызван.

onRemove() {
  super.onRemove() // вызов 'onRemove' метода базового класса
}

Thing-Editor осуществляет автоматическую проверку на вызов базового onRemove метода, для раннего выявления ошибок разработки.

Так как в Thing-Engine используется Пулинг для всех экранных объектов, при разработке компонентов необходимо следить за тем, чтобы в методе onRemove были очищены поля и ссылки на данные, которые могут повлиять на логику и поведение объекта в его "следующей жизни". Сохранение ссылок на экранные объекты - особо опасно, и может вызвать сложно отлавливаемые ошибки, поэтому, в Thing-Editor добавлена специальная валидация ссылок на экранные объекты, оставшихся в полях объекта после его удаления. При попытке обращения к такой ссылке в "следующей жизни", будет выброшено сообщение об ошибке обращения к устаревшей ссылке, которое помогает отследить неудаленные ссылки и добавить их очистку в метод onRemove.


_onRenderResize()

Данный метод срабатывает, при изменении размеров или ориентации игрового экрана. Добавление данного метода игровому объекту, имеет смысл только в проектах с динамическим размером экрана (См. настройку проекта dynamicStageSize) или проектах с автоматической ориентацией экрана (См. настройку проекта screenOrientation). При вызове этого метода, глобальные значения ширины и высоты игрового экрана (game.W, game.H), будут содержать новые размеры экрана, и глобальное свойство game.isPortrait будет содержать информацию о новой ориентации экрана.


onLanguageChanged()

Данный метод срабатывает при изменении текущего языка игры через вызов глобального метода L.setCurrentLanguage(). Метод onLanguageChanged необходим объектам, которые создают динамические текстовые сообщения из текстовых ресурсов, получаемых по ключу из метода L. Например, встроенный компонент Text, имеет данный метод и обновляет текст заданный через свойство translatableText.


_onDisableByTrigger()

Данный метод срабатывает в момент, когда родительский компонент Trigger, если таковой имеется в цепочке родителей объекта, переходит в неактивное состояние. Может быть полезен при разработке интерактивных (кликабельных) объектов.

Методы времени редактирования сцены

Методы, описанные в данном разделе, отвечают только за поведение объекта во время редактирования сцены в игровом редакторе. Объявления данных методов следует заключать в теги условной компиляции, так как в релизном или отладочном билде игры (вне игрового редактора) они не используются.

__EDITOR_onCreate()

Данный метод вызывается после того, как новый экземпляр данного компонента добавлен на сцену через окно Classes. Он не вызывается при добавлении на сцену префаба, или вставке объекта из буфера обмена.


__beforeDestroy()

Данный метод вызывается во время удаления объекта со сцены, как во время редактирования сцены, так и в то время, когда сцена запущена на выполнение. Метод onRemove же, является методом игровой логики, и вызывается только при удалении объекта, когда сцена запущена.


__onSelect()

Срабатывает при выделении объекта в дереве сцены.


__onUnselect()

Срабатывает при снятии выделения с объекта.


__onChildSelected()

Срабатывает при выделении любого дочернего объекта.


__beforeSerialization()

Вызывается перед сохранением сцены в файл (перед сериализацией сцены). Позволяет очистить данные, или дочернюю иерархию, которые не должны сохраняться в файл.

Сериализация - процесс преобразования сцены в набор данных, пригодных для хранения в файле, с возможностью последующего восстановления состояния сцены из этих данных. Так как механизм отмены действий undo/redo, использует сериализацию, то методы связанные с сериализацией вызываются после любого редактирования сцены, а методы связанный с десериализацией вызываются при любой отмене или повторе действия клавишами Ctrl + Z, Ctrl + Y.


__afterSerialization()

Вызывается после сохранения сцены в файл (после сериализации сцены).


__beforeDeserialization()

Вызывается перед загрузкой сцены из файла (перед десериализацией сцены).


__afterDeserialization()

Вызывается после загрузки сцены из файла (после десериализации сцены).


__goToPreviewMode()

Вызывается при нажатии в редакторе свойств кнопки "Предпросмотр", дескриптор которой генерируется методом makePreviewModeButton.


__exitPreviewMode()

Вызывается при выходе из режима предпросмотра. Подробнее о кнопке перехода в режим предпросмотра: makePreviewModeButton.

Игровые методы Custom сцены


Сцена, в дополнение к методам, описанный выше, имеет несколько методов, присущих только для сцен. Эти методы управляют временем жизни сцены, и взаимодействием с игроком. Да

onShow()

Метод срабатывает при появлении сцены на экране.

Если в проекте используются звуки с отложенной загрузкой, то для таких звуков, если планируется их использование в данной сцене, следует вызвать предзагрузку (метод Lib.preloadSound).

Сцена может появиться на экране как впервые, так и повторно, при ее возврате из стека сцен. Между вызовом метода onShow, и первым вызовом метода update сцены, может пройти неопределенное время, в случае, если на сцене имеются объекты, требующие подгрузки ресурсов, либо текущий фейдер game.currentFader имеет присоединенные к нему SceneLinkedPromise объекты.


onHide()

Метод срабатывает при скрытии сцены с экрана. Сцена может скрываться как при ее закрытии (уничтожении), так и при уходе в стек сцен, когда другая сцена открывается поверх текущей.


onMouseDown(gameMouse, pixiEvent)

Метод срабатывает при нажатии игроком кнопки мыши или сенсорного экрана. gameMouse - ссылка на объект game.mouse, содержащий упрощенную информацию о текущем состоянии игрового курсора на сцене. pixiEvent - исходное PIXI событие PIXI.interaction.InteractionEvent.


onMouseMove(gameMouse, pixiEvent)

Метод срабатывает при движении игрового курсора по сцене. Аргументы, передаваемые в данный метод, совпадают с аргументами метода onMouseDown.


onMouseUp(gameMouse, pixiEvent)

Метод срабатывает при отпускании игроком кнопки мыши или сенсорного экрана. Аргументы, передаваемые в данный метод, совпадают с аргументами метода onMouseDown.

Пулинг экранных объектов

В целях оптимизации расхода памяти, в Thing-Engine используется Пулинг (повторное использование) для всех экранных объектов. При удалении объекта с экрана, он складывается в специальное хранилище - Пул. В следующий раз, когда в игре необходимо создать новый объект определенного типа, первым делом проверяется, нет ли в пуле готового объекта такого типа, и если такой объект имеется, то именно он используется как вновь созданный. Все редактируемые свойства такого объекта будут приведены к тем значениям, которые ему были заданы в редакторе свойств, либо к значениям по умолчанию. Однако, по мимо редактируемых полей, разработчик может создавать объекту произвольные свойства из javascript. За очисткой таких свойств необходимо следить разработчику самостоятельно, инициализируя их в методе init, либо очищая в методе onRemove.

Для отслеживания ошибок, связанных с неочищенными данными, в Thing-Editor существует механизм защиты доступа к устаревшим ссылкам. При создании объекта, проверяются все его свойства на наличие ссылок на экранные объекты, оставшиеся неочищенными при его прошлом использовании. Такие ссылки подменяются на специальный объект, выкидывающий ошибку при попытке обращения к любому из его свойств.

Расширенные данные объекта

В игровом редакторе, каждый объект сцены требует хранения дополнительной информации об объекте, необходимой при редактировании сцены. Данная информация вынесена за пределы объекта, чтобы не пересекаться с игровыми свойствами объекта. Для получения этих данных используется глобальный метод:

__getNodeExtendData ( displayObject ) : Object

Данный метод возвращает объект, в котором хранятся поля расширенных данных. Это объект имеет следующие зарезервированные Thing-Editor свойства:

childsExpanded : Boolean

Имеет значение true, если в дереве сцены его дочерние объекты развернуты в данный момент. Свойство доступно как для чтения так и для записи.


isSelected : Boolean

Имеет значение true, если объект выделен. Это свойство доступно только для чтения.


hidePropsEditor : Boolean

Установка этому полю значения true, скрывает редактор свойств для данного объекта, однако объект может быть передвинут или повернут через контрольные элементы окна Viewport. Данное поле используется, например, для скрытия свойств вершин при редактировании многоугольника в компоненте Shape.


rotatorLocked : Boolean

Установка этому полю значения true, скрывает контрольный элемент вращения в окне Viewport для данного объекта.


hidden : Boolean

Установка этому полю значения true, делает объект невидимым в дереве сцены. Используется для скрытия служебных объектов, генерируемых компонентами как объекты предпросмотра.


noSerialize : Boolean

Установка этому полю значения true, отключает сохранение объекта в файл при сериализации. Используется для предотвращения сохранения в файл служебных объектов, генерируемых компонентами как объекты предпросмотра. Устанавливать данный флаг не требуется если флаг hidden уже установлен.


isPreviewObject : Boolean

Имеет значение true, если данный объект является префабом, открытым в данный момент для редактирования. Подробнее: Редактирование префабов.


<- Предыдущая страница Следующая страница ->

Связанные темы:

Компоненты

Редактируемые поля

PIXI.js

ifdef-loader

Clone this wiki locally