씬 그래프 튜토리얼의 랜턴은 Verse로 제작된 여러 컴포넌트를 사용하여 동적이고 상호작용하는 프리팹을 만드는데, 이는 Verse로 제작된 컴포넌트에 이동 및 상호작용 기능을 프로그래밍하기에 가능한 것입니다.
단순 이동 시뮬레이션
가로등 프리팹에는 두 개의 서로 다른 컴포넌트가 사용되어 랜턴 이동을 시뮬레이션합니다. 두 컴포넌트 모두 랜턴을 주위로 움직이게 하는 피벗 포인트를 제공하는 피벗(Pivot) 엔티티에 어태치됩니다. 가장 먼저 keyframed_movement_component를 사용하면 키프레임에서 애니메이션을 빌드한 후 재생하여 엔티티에 애니메이션을 적용할 수 있습니다. 하지만 이 컴포넌트는 독립적으로 작동하지 못하므로 키프레임을 제공하고 애니메이션을 시작하기 위한 다른 코드가 필요합니다. 바로 이런 경우에 커스텀 simple_movement_component를 사용합니다. 프로젝트 전반에서 이 스크립트를 재사용하여 여러 게임 오브젝트가 씬에서 움직이는 방식을 제어할 수 있습니다.
애니메이션을 사용하여 오브젝트를 움직이는 방법에 대한 자세한 내용은 사물 이동 애니메이션 적용하기를 확인해 보세요.
simple_movement_component를 자세히 살펴보겠습니다. 시작하려면 Visual Studio Code의 Verse 익스플로러(Verse Explorer)에서 SimpleMovementComponent.verse 스크립트를 엽니다.
애니메이션에서는 서로 다른 재생 모드를 사용하여 애니메이션 완료 시 애니메이션이 수행할 작업을 정의합니다. 이는 파일 상단의 movement_mode 열거형에 정의되어 있습니다.
원샷(One Shot) - 애니메이션이 종료되면 오브젝트가 이동을 멈춥니다.
루프(Loop) - 끝에 도달하면 오브젝트가 처음부터 애니메이션을 재시작합니다.
핑퐁(Ping Pong) - 오브젝트가 애니메이션을 역재생하여 처음부터 끝까지 진행됩니다.
movement_mode<public> := enum:
OneShot
Loop
PingPongbasic_movement_component 클래스는 애니메이션을 빌드하는 데 필요한 변수도 정의합니다. 각 변수에는 @editable 어트리뷰트가 있어 아웃라이너에서 편집할 수 있습니다.
Keyframes: 애니메이션을 빌드하는 데 사용되는keyframed_movement_delta의 배열입니다. 이러한 각 변수에서는 이 특정 키프레임에서 트랜스폼의 변화, 키프레임의 지속 시간 및 사용되는 이동 이징 타입을 추적합니다.AutoPlay: 엔티티가 시뮬레이션을 시작할 때 애니메이션이 적용되어야 하는지를 지시하는logic변수입니다.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 명령문을 사용하여 에디터에서 설정한 MovementMode 값에 따라 PlaybackMode를 설정합니다. 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가 피벗 엔티티에 추가되어 있고 다음 값으로 설정되어 있습니다. 이제 게임이 시작되면 랜턴이 앞뒤로 흔들리기 시작합니다!
전체 스크립트
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
랜턴과 상호작용하기
이제 랜턴이 앞뒤로 흔들릴 수 있지만, 플레이어가 랜턴과 상호작용하기 위해서는 다른 코드가 필요합니다. 이는 상호작용 가능 컴포넌트를 랜턴 엔티티에 어태치하고, 랜턴을 켜고 끌 수 있도록 해 주는 커스텀 Verse lantern_interaction_component를 가로등에 어태치하는 방식으로 구현합니다.
Verse 익스플로러에서 LanternInteractionComponent.verse 스크립트를 엽니다. 이 스크립트는 Assets.digest.verse에 정의된 LightPost 모듈을 임포트하는 것부터 시작합니다. 스크립트에 프리팹과 같이 가로등과 관련된 모든 에셋이 저장되어 있으며, 스크립트는 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가 반환됩니다.
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 = MaterialInstanceOnInteractFinished() 함수는 상호작용의 인스티게이터로 에이전트나 플레이어를 가져옵니다. 이 함수는 단순히 ToggleLight() 함수를 호출하여 랜턴을 켜고 끕니다.
ToggleLight() 함수는 랜턴 켜기/끄기와 관련하여 어려운 작업을 처리해 줍니다. 먼저 if 표현식에서 MaterialInstance의 이미시브 레벨이 0.0인지 확인합니다. 이 값은 라이트가 꺼져 있음을 나타냅니다. 값이 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: