Verse를 사용하여 엔티티에 추가할 자신만의 컴포넌트를 만들 수 있습니다. 커스텀 Verse 컴포넌트를 사용하여 씬에서 엔티티를 생성 및 제거하거나, 엔티티에서 컴포넌트를 추가 또는 제거하거나, 루프로 사라지는 엔티티 등 원하는 행동을 생성할 수 있습니다!
새 Verse 컴포넌트 만들기
Verse 템플릿 파일로 새 Verse 컴포넌트를 만들 수 있습니다.
새 Verse 컴포넌트를 생성하는 방법은 다음과 같습니다.
엔티티의 디테일(Details) 패널에서 + 컴포넌트(+ Component) > 새 Verse 컴포넌트(New Verse Component)를 선택합니다.
Verse 익스플로러를 통해 새 Verse 파일을 추가하여 새 Verse 컴포넌트를 만들 수도 있습니다.
Verse 코드 템플릿 목록에서 씬 그래프 컴포넌트(Scene Graph Component)를 선택합니다.
컴포넌트 이름(Component Name)을 Verse로 제작된 컴포넌트의 이름으로 설정합니다. 이 예시에서는 컴포넌트를
my_verse_component로 명명했습니다.생성(Create)을 클릭하면 Verse 컴포넌트 파일이 생성됩니다. 이제 엔티티에 컴포넌트를 추가하려고 할 때 Verse로 작성한 컴포넌트가 컴포넌트 목록에 나타납니다.
주어진 컴포넌트 클래스나 서브클래스는 하나만 추가할 수 있습니다. 예를 들어 하나의 엔티티는 하나의 mesh_component만 가질 수 있습니다. 이는 컴포넌트의 서브클래스에도 해당됩니다. 따라서 엔티티에 capsule_light_component를 추가하면 rect_light_component를 추가할 수 없는데, 둘 다 light_component의 서브클래스이기 때문입니다. 동일한 제한이 Verse에서 만든 커스텀 컴포넌트에도 적용됩니다.
컴포넌트 수명
컴포넌트는 엔티티에 추가되고, 씬에 추가되고, 시뮬레이션에서 실행되기 시작하면서 일련의 수명 함수를 통과합니다. Verse로 작성한 컴포넌트는 시뮬레이션 구성 및 실행을 위해 이러한 메서드를 오버라이드해야 합니다.
컴포넌트가 종료되면 이러한 함수의 종료 버전을 통과하여, 버려지기 전에 컴포넌트에 유지된 상태를 정리할 수 있습니다.
다음은 컴포넌트의 수명 상태입니다.
Initialized
AddedToScene
BeginSimulation
EndSimulation
RemovingFromScene
Uninitializing
컴포넌트 수명 함수는 장치 수명과 다릅니다. 컴포넌트 로직은 편집 모드와 플레이 모드에서 모두 실행됩니다. 추가한 모든 비헤이비어는 세션을 시작할 때 즉시 실행됩니다. 게임이 시작될 때만 컴포넌트 로직이 실행되도록 하려면 Verse 장치의 OnBegin() 함수에서 프리팹을 생성할 수 있습니다.
Verse로 엔티티 및 컴포넌트 쿼리하기
Verse 코드에서 엔티티와 컴포넌트를 찾는 방법은 여러 가지가 있습니다. 엔티티와 컴포넌트를 구조화하는 방식은 Verse 코드에서 함수 기능을 쿼리하고 개발하는 방식에 영향을 미칩니다.
Verse에서 엔티티와 컴포넌트를 쿼리하는 작업은 엔티티에서 시작해야 하고, 계층구조에서 위 또는 아래와 중첩되는 엔티티를 반환해야 합니다. 엔티티의 직접 부모 또는 자손은 물론 모든 조상 및 후손 엔티티에 대해 쿼리할 수 있습니다.
컴포넌트 타입으로 엔티티 얻기
쿼리할 엔티티에서 호출을 통해 특정 타입의 컴포넌트를 보유한 모든 엔티티를 찾을 수 있습니다. 쿼리하려는 엔티티가 시뮬레이션 엔티티인 경우, 씬에서 해당 타입의 컴포넌트를 보유한 모든 엔티티를 반환합니다.
다음 예시에서 Verse 컴포넌트는 light_component 컴포넌트가 어태치된 모든 엔티티를 얻고 찾는 엔티티마다 particle_sytem_component를 생성하여 어태치합니다. 여기에서 BlowingParticles는 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.
전체 씬에서 엔티티를 쿼리해야 하는 경우, 시뮬레이션 엔티티를 얻고 여기에서 쿼리를 수행하는 방법으로 가능합니다. 이 경우 엔티티 구조 맨 위에서 시작하여 쿼리에 일치하는 모든 중첩된 엔티티를 찾습니다. 시뮬레이션 엔티티에 액세스하려면 Verse 컴포넌트가 어태치된 엔티티에서 실패 가능 함수인 GetSimulationEntity[]를 호출합니다.
# 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 모듈을 참고하세요. 다음 내용은 코드에서 엔티티 및 컴포넌트를 쿼리하는 몇 가지 일반적인 방법에 대한 설명입니다.
엔티티의 컴포넌트 얻기
Verse를 사용하여 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를 엔티티에 추가하고 태그(Tags) 드롭다운에서 태그를 선택하거나, Verse 코드에서 AddTag() 함수를 사용하여 에디터에서 태그를 추가할 수 있습니다.
다음 예시에서는 tag_component에 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 =
# 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
이 코드는 큐브 엔티티를 중심으로 256.0 반경의 원으로 표시된 collision_sphere 오버랩 볼륨을 시뮬레이션하여 스피어가 오버랩되는 컴포넌트 또는 볼륨을 반환합니다. 이후 오버랩된 컴포넌트 각각의 부모 엔티티를 가져오고 모든 라이트 컴포넌트를 파란색으로 켭니다. 오버랩이 시작될 때 큐브가 collision_sphere 안에 있기 때문에 overlap_hits의 목록에 포함되고 파란색이 됩니다. 가장 오른쪽에 있는 빨간색 원뿔 엔티티는 collision_sphere 바깥에 있기 때문에 오버랩되지 않고 파란색이 되지도 않습니다.
스윕이 있는 엔티티 찾기
엔티티를 쿼리하는 또 다른 중요한 방법은 스윕을 통하는 것입니다. 스윕은 특정 벡터를 따라 지정된 거리로 오브젝트를 움직이는 것을 의미합니다. 플랫폼을 따라 블록을 이동해 플레이어를 틈새로 밀거나 미사일을 직선으로 발사해 벽을 파괴하는 것을 예시로 들 수 있습니다.
Verse에서 FindSweepHits() 함수를 사용하여 스윕을 시뮬레이션하고 오브젝트 간의 콜리전을 쿼리할 수 있습니다. 이 함수는 디스플레이스먼트 벡터를 가져와 오브젝트의 스윕을 시뮬레이션합니다. 부모 엔티티 또는 주어진 콜리전 볼륨과의 스윕을 수행하고 스윕을 시작할 시작 글로벌 트랜스폼을 지정할 수 있습니다.
FindSweepHits() 함수는 sweep_hit 목록을 반환합니다. 각각의 sweep_hit는 히트되는 컴포넌트 또는 볼륨, 스윕하는 소스 볼륨 또는 컴포넌트와 같이 overlap_hit와 동일한 정보를 제공합니다. 또한 접촉 위치, 노멀, 페이스 노멀, 스윕을 따라 히트가 발생하는 거리에 관한 정보도 제공해 줍니다.
다음 예시는 엔티티를 가져와 FindSweepHit()를 호출하고, 앞쪽(Forward) 값에 대해 길이가 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로 태그된 모든 엔티티를 찾습니다. 찾은 각 엔티티를 부모에게서 제거하여 씬에서 엔티티를 제거한 후, 5초 후 동일한 엔티티를 부모에 다시 추가하여 씬에 다시 생성합니다.
# 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()를 사용하여 다시 씬에 추가할 수 있습니다. 제거된 컴포넌트를 씬에 다시 추가하는 경우 컴포넌트의 부모 엔티티를 변경할 수는 없습니다.
Prefabs
프로젝트에서 생성한 프리팹은 프로젝트의 Assets.digest.verse 파일에서 Verse에 클래스로 노출됩니다. 프리팹에서 정의된 엔티티 및 컴포넌트는 프리팹의 GetEntities() 및 GetComponents() 호출을 통해 Verse에서 액세스할 수 있습니다.
프리팹 클래스를 인스턴스화하고 씬의 엔티티에 추가하는 방법으로 프리팹의 인스턴스를 생성할 수 있습니다. 다음 예시에서 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 동시성 표현식을 계속 사용하여 특정 로직을 기준으로 타임 플로를 제어해야 합니다. 자세한 내용은 타임 플로 및 동시성을 확인하세요.
컴포넌트 수명 함수는 장치 수명과 다릅니다. 컴포넌트 로직은 편집 모드와 플레이 모드에서 모두 실행됩니다. 게임 시작 시에만 컴포넌트 로직이 실행되도록 하려면 Verse 장치의
OnBegin()함수에서 프리팹을 생성할 수 있습니다.