Вы можете создавать собственные компоненты с помощью Verse, чтобы добавлять их в свои модули. С помощью пользовательских компонентов Verse вы можете создавать и удалять модули из сцены, добавлять и удалять компоненты модулей, создавать собственное поведение, например, циклически исчезающий модуль и т. д.
Создание нового компонента Verse
Вы можете создать новый компонент Verse из файла шаблона Verse.
Чтобы создать новый компонент Verse:
На панели Сведения вашего модуля выберите Добавить компонент > Новый компонент Verse.
Вы также можете создать новый компонент Verse, добавив новый файл Verse через проводник Verse.
Выберите Компонент Scene Graph из списка шаблонов кода Verse.
Для поля Название компонента задайте название вашего компонента Verse. В этом примере в качестве названия компонента используется
my_verse_component.Чтобы создать файл компонента Verse, нажмите Создать. Теперь, когда вы захотите добавить компонент к вашему модулю, этот компонент Verse будет в списке доступных компонентов.
Вы можете добавлять только один компонент указанного класса или подкласса. Например, в модуле может быть только один компонент mesh_component. Это распространяется на подклассы компонентов, то есть если вы добавите capsule_light_component в свой модуль, то добавить ещё и rect_light_component не получится, поскольку оба являются подклассами light_component. То же ограничение применяется к вашим пользовательским компонентам, создаваемым в Verse.
Время жизни компонента
По мере добавления в модули, добавления в сцену и запуска в симуляции компоненты выполняют серию функций времени жизни. Ваши Verse-компоненты должны переопределять эти методы для настройки и запуска симуляции.
При отключении компонент выполняет отключающую версию этих функций, и у вас появится возможность очистить любое сохраняющееся на компоненте состояние перед его удалением.
Состояния времени жизни компонентов бывают следующими:
Выполнена инициализация
AddedToScene
BeginSimulation
EndSimulation
RemovingFromScene
Отмена инициализации
Функции времени жизни компонентов отличаются от времени жизни устройств. Логика компонента запускается одновременно и в режиме редактирования, и в режиме игры. Любое добавляемое вами поведение будет сразу воспроизводиться при запуске сеанса. Если необходимо, чтобы ваш компонент запускался только при запуске игры, вы можете создать заготовки в функции OnBegin() устройства Verse.
Поиск модулей и компонентов в Verse
Существует несколько способов поиска модулей и компонентов в коде Verse. То, как вы структурируете модули и компоненты, влияет на то, как будут выполняться запросы и разрабатываться различные функции в коде Verse.
Поиск модулей и компонентов в Verse должен начинаться с модуля и возвращать модули, расположенные выше или ниже него в иерархии. Вы можете выполнить поиск его непосредственного родительского или дочернего элемента, а также всех его родительских и дочерних элементов.
Получение модулей с компонентом заданного типа
Для поиска всех модулей, в которых есть компонент определённого типа, достаточно вызвать модуль, по которому необходимо выполнить запрос. Если модуль, по которому выполняется поиск, является модулем симуляции, то будут возвращены все модули с компонентами заданного типа в сцене.
В следующем примере компонент Verse получает все модули с компонентом light_component. Для каждого из найденных модулей он создаёт экземпляр particle_sytem_component и прикрепляет его к ним. В данном примере BlowingParticles является генератором частиц Niagara, на который ссылается файл Assets.digest.Verse.
Компонент light_component является суперклассом для всех типов различных компонентов освещения, которые вы можете добавлять в модули. В запросе ниже LightComponent используется для поиска модулей с компонентом освещения любого типа.
# Runs when the component should start simulating in a running game.
OnBeginSimulation<override>():void =
# Run OnBeginSimulation from the parent class before
# running this component's OnBeginSimulation logic
(super:)OnBeginSimulation()
for:
LightComponent : Entity.GetSimulationEntity[].FindDescendantEntitiesWithComponent(light_component)
do:
# Create a particle system component and add it to the entity.
В следующем примере показано, как с помощью функции FindDescendantEntitiesWithComponent() выполнить поиск модулей с системой частиц, находящихся внутри модуля, к которому прикреплён компонент Verse. Таким же образом вы можете получить все родительские модули с определённым компонентом при помощи FindAncestorEntitiesWithComponent().
# Runs when the component should start simulating in a running game.
# Can be suspended throughout the lifetime of the component. Suspensions
# will be automatically canceled when the component is disposed or the
# game ends.
OnSimulate<override>()<suspends>:void=
# Get all entities that have particle system components nested under the entity this component is attached to.
ParticleSystemEntities := Entity.FindDescendantEntitiesWithComponent(particle_system_component)
# Get all entities with particle system components that are ancestors of this entity.
Если нужно выполнить поиск по всей сцене, достаточно получить модуль симуляции и выполнить поиск по нему. Это позволит начать поиск с самой вершины структуры модуля и найти все вложенные модули, соответствующие запросу. Для доступа к модулю симуляции вызовите функцию с неоднозначным результатом GetSimulationEntity[] на модуле, к которому присоединён компонент Verse.
# Runs when the component should start simulating in a running game.
# Can be suspended throughout the lifetime of the component. Suspensions
# will be automatically canceled when the component is disposed or the
# game ends.
OnSimulate<override>()<suspends>:void=
# Get the simulation entity.
if:
SimulationEntity := Entity.GetSimulationEntity[]
then:
Постоянный поиск по всему дереву модулей для компонента Verse может оказаться ресурсозатратным. Если ваше поведение зависит от структуры определённого модуля, любые даже небольшие изменения в его структуре могут привести к незапланированным изменениям в поведении (включая возникновение ошибок).
С другой стороны, в этом случае настройка самого компонента упрощается, поскольку всё, что вам понадобится, это добавить компонент Verse к правильной структуре модуля. Это те компромиссы, о которых следует помнить при создании модуля и разработке логики компонента Verse.
Способы работы с модулями и компонентами в Verse подробнее описаны в разделе, посвящённом SceneGraph. Ниже представлены некоторые распространённые способы запросов модулей и компонентов в коде.
Получение компонентов модуля
Для получения компонента определённого типа из модуля достаточно вызвать функцию GetComponent[]. Это подходит для случаев, когда необходимо создать пользовательскую логику, которая будет зависеть от поведения других компонентов. Например, можно использовать компонент sound_component для воспроизведения звука в зависимости от цвета света или получить компонент particle_system_component для применения эффектов в зависимости от того, находится или нет модуль в той или иной зоне.
В следующем примере компонент Verse заставляет платформу циклически появляться и исчезать. Для этого он получает компонент статичной сетки на модуле и отключает, а затем повторно включает по истечении заданного времени.
using { /Verse.org }
using { /Verse.org/Native }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SceneGraph }
# A Verse-authored component that can be added to entities.
# This component will make the entity appear and disappear on loop.
disappear_on_loop_component := class<final_super>(component):
# How long in seconds the entity should be hidden.
@editable
В другом примере компонент Verse получает компонент light_component модуля и меняет его цвет на тёмно-оранжевый. Компонент light_component является суперклассом для всех источников освещения, которые вы можете добавлять к модулям, поэтому в этом примере мы находим любой компонент, который является подклассом этого компонента.
# Runs when the component should start simulating in a running game.
# Can be suspended throughout the lifetime of the component. Suspensions
# will be automatically canceled when the component is disposed or the
# game ends.
OnSimulate<override>()<suspends>:void=
# Find any component on this entity that subclasses from light_component.
if:
LightComponent := Entity.GetComponent[light_component]
then:
Получение всех компонентов
Вы можете использовать функцию GetComponents(), чтобы вернуть все компоненты модуля. Поскольку ваш код не знает, какого типа это компоненты, вы можете использовать приведение типов для выполнения различных операций в зависимости от типа каждого компонента. В следующем примере мы получаем массив всех компонентов модуля и пытаемся привести их к типу enableable. Если приведение выполняется успешно, компонент реализует интерфейс enableable. Затем он отключает каждый компонент, реализующий этот интерфейс.
# Runs when the component should start simulating in a running game.
# Can be suspended throughout the lifetime of the component. Suspensions
# will be automatically canceled when the component is disposed or the
# game ends.
OnSimulate<override>()<suspends>:void=
# Get a list of all components on the entity.
ComponentList := Entity.GetComponents()
for:
Component:ComponentList
Поиск модулей по тегам игрового процесса
Вы можете добавить компонент тега к модулю, чтобы иметь возможность находить определённые модули в сцене, подобно тому, как добавляется тег игрового процесса к актору. Это может пригодиться, когда необходимо выбрать модули, с которыми вы хотите работать, а не полагаться на то, что может измениться: имеющиеся компоненты или расположение в сцене.
Это связано с тем, что наличие зависимости от того, что может в будущем измениться, может привести к нежелательному поведению в игре по мере изменения и добавления новых модулей в проект. Вы можете добавлять теги в редакторе, добавив к вашему модулю компонент tag_component и выбрав теги из выпадающего меню Теги, или в коде Verse при помощи функции AddTag().
В следующем примере модуль симуляции запрашивает все дочерние элементы, помеченные тегом my_tag в своём tag_component.
# Runs when the component should start simulating in a running game.
# Can be suspended throughout the lifetime of the component. Suspensions
# will be automatically cancelled when the component is disposed or the
# game ends.
OnSimulate<override>()<suspends>:void =
# Get the simulation entity.
if:
SimulationEntity := Entity.GetSimulationEntity[]
then:
Поиск модулей с наложением
Области столкновения — это области, которые представляют формы коллизий сеток. Вы можете использовать их для поиска пересекающихся объектов в пределах определённой формы, например, для нанесения урона объектам, если они находятся слишком близко к башне, или для определения момента, когда футбольный мяч входит в ворота. В Verse для поиска всех модулей в определённой области можно использовать функцию FindOverlapHits().
Эта область может быть либо самой модулем, заданным областью столкновения, таким как сфера или кубоид, либо точкой, из которой будет производиться симуляция модуля. По такому запросу возвращается список overlap_hit. Каждый overlap_hit предоставляет информацию о компоненте или области, которые пересекаются с исходной областью, при этом вы можете выполнять запросы по этим компонентам для поиска связанных с ними модулей.
В следующем примере создаётся сфера радиусом 256,0 единиц с центром на точке преобразования модуля. Затем она находит все пересечения в пределах сферы и возвращает список overlap_hit. Поскольку каждый overlap_hit является либо компонентом, либо областью, вы можете запросить либо TargetComponent, либо TargetVolume, чтобы узнать, к какому типу он относится. Если overlap_hit является компонентом, код получает модуль этого компонента. Наконец, он проверяет, есть ли у модуля компонент освещения. Если да, то цвет освещения становится синим. В случае достаточно большой области можно изменить цвет каждого источника освещения на острове всего несколькими строками кода!
# Runs when the component should start simulating in a running game.
# Can be suspended throughout the lifetime of the component. Suspensions
# will be automatically canceled when the component is disposed or the
# game ends.
OnSimulate<override>()<suspends>:void=
# Define a volume to find entities within.
# This is a sphere whose radius is 256.
CollisionSphere:collision_sphere = collision_sphere:
Radius := 256.0
Этот код симулирует пересекаемую область collision_sphere, обозначенную в виде круга радиусом 256,0 с центром на модуле кубоида, возвращая все компоненты или области, с которыми пересекается сфера. Затем он получает родительский модуль каждого пересекаемого компонента и окрашивает все его компоненты освещения в синий цвет. Поскольку куб находится внутри collision_sphere в начале пересечения, он будет включён в список overlap_hits и также станет синим. Обратите внимание: крайний правый красный конус находится за пределами collision_sphere, поэтому пересечения с ним не образуется, и он не будет становиться синим.
Поиск модулей с помощью поиска пересечений
Ещё один важный способ поиска модулей — это поиск пересечений. Поиск пересечений — это перемещение объекта на заданное расстояние вдоль определённого вектора. Например, можно создать блок, который двигается по платформе и сталкивает игроков в пропасть, или запустить ракету прямо вперёд, чтобы разрушить стену.
В Verse можно симулировать поиск пересечений, чтобы запросить коллизии между объектами, используя функцию FindSweepHits(). Эта функция использует вектор смещения для симуляции поиска пересечений по пути объекта. Вы можете выполнить поиск пересечений либо с родительским модулем, либо с заданной областью столкновения и указать начальное глобальное преобразование, с которого нужно начать движение.
Функция FindSweepHits() возвращает список результатов sweep_hit. Каждый результат sweep_hit предоставляет ту же информацию, что и overlap_hit, например, пересечение с компонентом или областью, а также исходную область или компонент, от которых выполняется поиск пересечений. При этом здесь дополнительно предоставляется информация о положении контакта, нормали, нормали грани и расстоянии вдоль траектории поиска пересечений, на котором и было обнаружено пересечение.
В следующем примере рассматривается модуль и вызов функции FindSweepHit(), передающей вектор длиной 1000.0 для значения «Вперёд». Затем код симулирует, какие столкновения могли бы возникнуть при перемещении модуля на 1000,0 единиц в положительном направлении «Вперёд», и возвращает список sweep_hit.
Поскольку каждый sweep_hit является либо компонентом, либо областью, вы можете запросить либо TargetComponent, либо TargetVolume, чтобы узнать, к какому типу он относится. Если пересечение возникает с компонентом, код получает родительский модуль этого компонента. Наконец, он проверяет, есть ли у модуля компонент освещения. Если да, то цвет освещения становится синим.
for:
# Simulate sweeping this entity 1000 units in the positive X direction, and return any components and volumes it overlaps with.
SweepHit : Entity.FindSweepHits(vector3{Left := 0.0, Up := 0.0, Forward := 1000.0}, Entity.GetGlobalTransform(), CollisionBox)
# Check that the overlap is a component, and if so get its parent entity.
# Then if the entity has a light component, change its color filter.
TargetComponent := SweepHit.TargetComponent
TargetEntity := TargetComponent.Entity
LightComponent := TargetEntity.GetComponent[light_component]
do:
Этот код симулирует поиск пересечений с модулем куба на расстоянии 1000,0 единиц в положительном направлении по оси X, возвращая все компоненты, с которыми обнаруживается пересечение. Затем он получает родительский модуль пересекаемого компонента и окрашивает все его компоненты освещения в синий цвет. Обратите внимание: список пересечений не включает в себя модуль, выполняющий поиск пересечений, поэтому сам куб не станет синим. Крайний правый красный конус также не станет синим, поскольку находится за пределами зоны поиска пересечений.
Следующий пример похож на предыдущий, за исключением того, что сначала создаётся область collision_box, а затем она используется для поиска пересечений на 1000.0 единиц в положительном направлении «Вперёд», начиная от центра модуля куба. Поскольку куб находится внутри collision_box в момент начала поиска пересечений, он будет включён в список sweep_hits и также станет синим.
# Define a volume to sweep over entities.
# This box is 1/4th the size of a standard 512x512 grid tile.
CollisionBox:collision_box = collision_box:
Extents := vector3:
Left := 128.0,
Up := 128.0,
Forward := 128.0
for:
# Simulate sweeping the CollisionBox 1000 units in the positive X direction, and return any components and volumes it overlaps with.
SweepHit : Entity.FindSweepHits(vector3{Left := 0.0, Up := 0.0, Forward := 1000.0}, Entity.GetGlobalTransform(), CollisionBox)
Этот код симулирует обработку области collision_box, обозначенной жёлтым квадратом, на 1000.0 единиц в положительном направлении «Вперёд», возвращая все компоненты, с которыми она пересекается. Затем он получает родительский модуль пересекаемого компонента и окрашивает все его компоненты освещения в синий цвет. Поскольку куб находится внутри collision_box в момент начала поиска пересечений, он будет включён в список sweep_hits и станет синим. Крайний правый красный модуль конуса не станет синим, поскольку он находится за пределами зоны поиска пересечений.
Создание и удаление модулей при помощи Verse
Для удаления модуля из сцены достаточно вызвать для него RemoveFromParent(). Вы можете добавить модуль в сцену (как новый, так и ранее удалённый) путём вызова функции AddEntities() для модуля, который будет выполнять роль родительского.
В следующем примере компонент Verse находит все модули с тегом my_tag. Он удаляет каждый найденный модуль из его родительского элемента, при этом модуль удаляется из сцены, а через пять секунд добавляет его обратно в родительский элемент, и модуль вновь появляется в сцене.
# Runs when the component should start simulating in a running game.
# Can be suspended throughout the lifetime of the component. Suspensions
# will be automatically cancelled when the component is disposed or the
# game ends.
OnSimulate<override>()<suspends>:void =
# Find all entities tagged and get their parent entity.
for:
TaggedEntity : Entity.GetSimulationEntity[].FindDescendantEntitiesWithTag(my_tag{})
Parent := TaggedEntity.GetParent[]
Таким же образом можно добавлять компоненты к модулю, вызывая функцию AddComponents() и передавая список добавляемых компонентов. Также можно удалить модуль, вызвав для него функцию RemoveFromParent(). Для удаления компонента из модуля достаточно вызвать на нем функцию RemoveFromEntity(). Удалённые модули и компоненты можно повторно добавить в сцену с помощью AddEntities() и AddComponents() соответственно. Обратите внимание: изменить родительский модуль удалённых компонентов, которые вы добавляете обратно в сцену, невозможно.
Заготовки
Готовые элементы, которые вы создаёте в своём проекте, представлены как классы Verse в файле Assets.digest.verse вашего проекта. Модули и компоненты, указанные в вашей заготовке, доступны в Verse с помощью вызовов GetEntities() и GetComponents().
Для создания экземпляров заготовок достаточно создать экземпляр класса заготовки и добавить их к модулю в сцене. В следующем примере компонент Verse в редакторе создаёт экземпляр заготовки с названием loop_disappearing_platform_prefab и добавляет его в сцену.
# Runs when the component should start simulating in a running game.
# Can be suspended throughout the lifetime of the component. Suspensions
# will be automatically cancelled when the component is disposed or the
# game ends.
OnSimulate<override>()<suspends>:void =
if:
SimulationEntity := Entity.GetSimulationEntity[]
then:
# Create an instance of the disappearing on loop platform from its prefab.
DisappearingPlatform:disappearing_platform_prefab = disappearing_platform_prefab{}
Советы и рекомендации
При создании собственных компонентов в Verse обратите внимание на следующие советы и рекомендации:
Компоненты Verse, зависящие от других компонентов, как правило, должны находиться в одном модуле.
Компоненты открывают доступ к событиям такта предварительной и последующей физики, которые происходят с каждым кадром. Это удобно, если вам нужно выполнить определённую логику до применения физических вычислений (например, изменить преобразование) или после, например, чтобы считать положения объектов после применения физических вычислений.
Если вам не нужны события предварительной и последующей физики, продолжайте использовать выражения Verse с одновременным выполнением для управления потоком времени на основе определённой логики. Более подробно об этом вы можете узнать в разделе «Контроль времени и конкурентность».
Функции времени жизни компонентов отличаются от времени жизни устройств. Логика компонента запускается одновременно и в режиме редактирования, и в режиме игры. Если необходимо, чтобы ваш компонент запускался только при запуске игры, вы можете создать заготовки в функции
OnBegin()устройства Verse.