В фонарях в обучении работе со Scene Graph используется несколько компонентов Verse для создания динамических и интерактивных готовых элементов. Это возможно за счёт программирования возможностей перемещения и взаимодействия непосредственно для компонента Verse.
Симуляция простого перемещения
В готовом элементе (фонарном столбе) для имитации перемещения фонаря используются два разных компонента. Оба этих компонента связаны с модулем Pivot, который выступает в качестве опорной точки для перемещения фонаря. Компонент keyframed_movement_component позволяет вам сначала создать анимацию на основе опорных кадров, после чего воспроизводить её для анимации модуля. При этом важно помнить, что этот компонент не может работать отдельно: для получения опорных кадров и запуска анимации необходимо прописать дополнительный код. Здесь нам пригодится пользовательский компонент unique_movement_component. Этот сценарий повторно используется в проекте для управления движением различных игровых объектов в сцене.
Более подробная информация об использовании анимаций перемещения объектов приведена в разделе Анимация движения объектов.
Давайте рассмотрим компонент simple_movement_component более подробно. Для начала откройте сценарий SimpleMovementComponent.verse в проводнике Verse в редакторе Visual Studio Code.
В анимациях используются различные режимы воспроизведения для определения того, что должна выполнять анимация после её завершения. Это определено в верхней части файла в enum movement_mode:
Однократное выполнение: объект прекращает перемещение после завершения анимации.
Цикл: объект повторно выполняет анимацию.
Пинг-понг: объект воспроизводит анимацию от начала до конца и обратно.
movement_mode<public> := enum:
OneShot
Loop
PingPongКласс basic_movement_component также определяет переменные, необходимые для создания анимации. У каждой из них имеется атрибут @editable, позволяющий редактировать их через окно «Структура»:
Keyframes: массив элементовkeyframed_movement_delta, используемый для создания анимации. Каждый из них отслеживает изменение преобразования в конкретном опорном кадре, длительность опорного кадра, а также тип используемого замедления перемещения.AutoPlay:логическаяпеременная, которая определяет, должен ли модуль воспроизводить анимацию в начале симуляции.MovementMode: поведение перемещения модуля.
# A Verse-authored component that can be added to entities
basic_movement_component<public> := class<final_super>(component):
@editable
var Keyframes<public>: []keyframed_movement_delta = array{}
@editable
var AutoPlay: logic = true
Код в функции OnSimulate() запускается в момент добавления компонента в сцену и в момент запуска игры. Сначала на короткий промежуток времени выполняется вызов функции Sleep(), чтобы проверить, что все модули и компоненты верно инициализированы. Затем в выражении if выполняется проверка, имеется ли у модуля компонент keyframed_movement_component.
Если это так, то выполняется вызов функции InitializeKeyFramedMovementComponent(), что позволяет задать компоненту нужные значения. Что делать, если у модуля нет компонента keyframed_movement_component? В этом случае вы можете создать его динамически в среде выполнения и добавить его к модулю с помощью функции AddComponents(). Таким способом в начале игры ваш модуль будет корректно настроен!
OnSimulate<override>()<suspends>:void =
Sleep (0.1)
if:
KeyframedMovementComponent := Entity.GetComponent[keyframed_movement_component]
then:
InitializeKeyframedMovementComponent(KeyframedMovementComponent)
else:
NewKeyFramedMovementComponent := keyframed_movement_component { Entity := Entity }
Entity.AddComponents of array { NewKeyFramedMovementComponent }
InitializeKeyframedMovementComponent(NewKeyFramedMovementComponent)Функция InitializeKeyframedMovementComponent() задаёт для компонента перемещения по опорным кадрам те значения, которые были заданы в редакторе. Сначала создаётся новая переменная keyframed_movement_playback_mode, которая позволит определить режим воспроизведения во время этой анимации. Инициализация выполняется со значением oneshot_keyframed_movement_playback_mode, что означает, что анимация будет воспроизводиться только один раз.
InitializeKeyframedMovementComponent(InKeyframedMovementComponent:keyframed_movement_component):void =
var PlaybackMode:keyframed_movement_playback_mode = oneshot_keyframed_movement_playback_mode{}Затем используется выражение case, где выполняется выбор PlaybackMode на основе значения MovementMode, заданного в редакторе. Каждое значение в enum MovementMode соответствует отдельному режиму воспроизведения, определённому в модуле SceneGraph/KeyframedMovement.
case (MovementMode):
movement_mode.OneShot =>
set PlaybackMode = oneshot_keyframed_movement_playback_mode{}
movement_mode.Loop =>
set PlaybackMode = loop_keyframed_movement_playback_mode{}
movement_mode.PingPong =>
set PlaybackMode = pingpong_keyframed_movement_playback_mode{}Наконец, функция задаёт опорные кадры и режим перемещения для компонента перемещения по опорным кадрам, после чего анимация готова к воспроизведению. Если для переменной AutoPlay установлено значение true, то анимация будет запускаться сразу с помощью вызова функции Play() компонента перемещения по опорным кадрам.
InKeyframedMovementComponent.SetKeyframes(Keyframes, PlaybackMode)
if:
AutoPlay?
then:
InKeyframedMovementComponent.Play()В редакторе компонент simple_movement_component добавляется к модулю Pivot, после чего задаются следующие значения. Теперь при запуске игры фонарь будет раскачиваться!
Полный сценарий
using { /Verse.org }
using { /Verse.org/Native }
using { /Verse.org/SceneGraph }
using { /Verse.org/Simulation }
using { /Verse.org/SceneGraph/KeyframedMovement }
movement_mode<public> := enum:
OneShot
Loop
Взаимодействие с фонарём
Фонарь раскачивается, однако чтобы игрок мог с ним взаимодействовать, нам понадобится кое-что ещё. Для этого нужно прикрепить Интерактивный компонент к модулю Lantern, а пользовательский компонент Verse lantern_interaction_component — к фонарному столбу, который позволяет включать и выключать фонарь.
Откройте скрипт LanternInteractionComponent.verse в проводнике Verse. В начале этого сценария выполняется импорт модуля LightPost, заданного в Assets.digest.verse. Здесь хранятся все ресурсы, связанные с фонарным столбом, к примеру, готовые элементы. Там же выполняется импорт модуля LightPost.Materials для доступа к материалам и сеткам фонарного столба и фонаря.
using { /Verse.org }
using { /Verse.org/Native }
using { /Verse.org/SceneGraph }
using { /Verse.org/Simulation }
LightPost := module:
Materials<public> := module:
Класс lantern_interaction_component начинает с определения переменной MaterialInstance, которая инициализируется в LightPost.Materials.MI_Lantern_01. Это экземпляр материала, где хранятся все переменные, связанные с материалом фонаря.
lantern_interaction_component<public> := class<final_super>(component):
var MaterialInstance:LightPost.Materials.MI_Lantern_01 = LightPost.Materials.MI_Lantern_01{}В функции OnBeginSimulation() сценарий начинает с поиска каждого компонента в его дочерних модулях с прикреплённым interactable_component. Поскольку компонент lantern_interaction_component прикреплён к фонарному столбу, возвращаться будет interactable_component модуля Lantern, поскольку он является дочерним элементом фонарного столба.
OnBeginSimulation<override>():void =
(super:)OnBeginSimulation()
InteractabeleComponents := Entity.FindDescendantComponents(interactable_component)Далее в выражении for сценарий перебирает все найденные интерактивные компоненты и выполняет подписку для их SucceededEvent на функцию OnInteractFinished(), определённую ниже в этом файле. Теперь, когда игрок завершит взаимодействие с фонарём, будет запущена функция OnInteractFinished().
InteractabeleComponents := Entity.FindDescendantComponents(interactable_component)
for (InteractableComponent : InteractabeleComponents):
InteractableComponent.SucceededEvent.Subscribe(OnInteractFinished)Затем функция OnBeginSimulation() повторно вызовет FindDescendantComponents, чтобы найти каждый модуль с прикреплённым компонентом LightPost.SM_Lightpost_Lantern_01. Это компонент сетки, прикреплённый к фонарю. Затем он присваивает компоненту сетки заданное ранее значение MaterialInstance, чтобы инициализация сетки фонаря выполнялась корректно.
MeshComponents := Entity.FindDescendantComponents(LightPost.SM_Lightpost_Lantern_01)
for (MeshComponent : MeshComponents):
set MeshComponent.M_Lantern = MaterialInstanceФункция OnInteractFinished() принимает агента или игрока в качестве инициатора взаимодействия. Эта функция просто вызывает функцию ToggleLight() для включения или выключения фонаря.
Сама же функция ToggleLight() содержит в себе всю необходимую логику, что и позволяет включать или выключать фонарь. Сначала в выражении if она проверяет, равен ли уровень свечения у MaterialInstance 0,0, что будет указывать на то, что свет не горит. Если это так, то для уровня свечения устанавливается значение 1,0. Затем в двух выражениях for она находит каждый light_component и particle_system_component в дочерних модулях и включает их, вызывая функции Enable() и Play().
ToggleLight():void =
if (MaterialInstance.Emissive_Multiply = 0.0):
set MaterialInstance.Emissive_Multiply = 1.0
for:
Light : Entity.FindDescendantComponents(light_component)
do:
Light.Enable()
for:
Если источник света уже включён, функция выполняет обратное в блоке else. Для уровней свечения материала она задаёт значение 0,0 и отключает все компоненты частиц и освещения у дочерних модулей.
else:
set MaterialInstance.Emissive_Multiply = 0.0
for:
Light : Entity.FindDescendantComponents(light_component)
do:
Light.Disable()
for:
Particle : Entity.FindDescendantComponents(particle_system_component)
Функции в этом сценарии делают фонарь динамическим за счёт включения или выключения нескольких компонентов при взаимодействии игрока. Следует помнить, что несмотря на то, что сам фонарь и включается, и выключается, компонент lantern_interaction_component предоставляется со стороны его родительского модуля (т. е. фонарного столба).
Также важно продумать, что может потребоваться для столба с несколькими источниками света, каждый из которых будет включаться и выключаться нажатием одной кнопки. Поскольку этот код работает таким образом, что сначала выполняется поиск дочерних модулей с определённым типом компонента, для реализации этого вам уже не потребуется дополнительный код Verse. Иными словами, если у каждого дочернего модуля фонаря будет свой компонент освещения или частиц, этого уже будет достаточно!
Полный сценарий
using { /Verse.org }
using { /Verse.org/Native }
using { /Verse.org/SceneGraph }
using { /Verse.org/Simulation }
LightPost := module:
Materials<public> := module: