이동하거나, 회전하거나, 스케일 조절되는 오브젝트보다 한 단계 위는 세 가지를 모두 동시에 적용하는 것입니다. 하지만 동시에 여러 방식으로 움직이는 사물의 애니메이션을 제작할 때는 몇 가지 중요한 과제를 고려해야 합니다.
애니메이션 컨트롤러는 한 번에 하나의 애니메이션만 재생할 수 있으므로 별도의 애니메이션에서 이동, 회전, 스케일 조절을 적용할 수 없습니다. 또한 애니메이션마다 사물을 여러 번 회전해야 할 수 있으므로 이러한 애니메이션에는 여러 키프레임이 필요합니다. 모든 키프레임을 미리 빌드해야 하기 때문에 사물의 여정에서 각 지점의 위치, 회전, 스케일도 미리 계산해야 합니다. 사물이 균일하게 회전하지 않는다면 어떻게 될까요? 180도 회전하려면 어떻게 해야 할까요?
이 다음 섹션에는 많은 수학이 연관되어 있습니다. 하지만 튜토리얼이 끝나면 사물을 여러 지점으로 이동, 회전, 스케일 조절할 수 있게 되며, 복잡하고 다이내믹하면서도 가장 중요한 점인 재미도 있는 플랫폼 도전을 제작하여 꿈의 폴가이즈 코스를 만들 수 있을 것입니다.
이동, 회전, 스케일 조절하는 애니메이션 만들기
모든 요소를 합치려면 다음 단계를 따릅니다.
Verse 익스플로러를 사용하여
movable_prop에서 상속되는 새 Verse 클래스animating_prop을 생성합니다. 이 클래스에<concrete>지정자를 추가하여 UEFN에 프로퍼티를 노출합니다.Verse# A prop that translates, rotates, and scales to a destination using animation. animating_prop<public> := class<concrete>(movable_prop):using { /Fortnite.com/Devices/CreativeAnimation }및using { /UnrealEngine.com/Temporary/SpatialMath }명령문을 파일 상단에 추가하여 해당 모듈을 임포트합니다. 이는 사물에 애니메이션을 적용하는 데 필요합니다. 이 섹션에서 사용된 툴팁도 여기에 포함됩니다.Verseusing { /Fortnite.com/Devices } using { /Fortnite.com/Devices/CreativeAnimation } using { /Verse.org/Simulation } using { /UnrealEngine.com/Temporary/SpatialMath } RotationRateTip<localizes>:message := "The time it takes to make one AdditionalRotation in seconds." UseEasePerKeyframeTip<localizes>:message := "Whether this prop should use the MoveEaseType for each keyframe. False will use the Linear ease type on each frame." # A prop that translates, rotates, and scales to a destination using animation. animating_prop<public> := class<concrete>(movable_prop):animating_prop클래스 정의 상단에 다음 필드를 추가합니다.AdditionalRotation으로 명명된 편집 가능한rotation을 추가합니다. 이는 키프레임당RootProp에 적용할 회전입니다.Verse# The additional rotation to apply to the RootProp per keyframe. @editable {ToolTip := AdditionalRotationTip} AdditionalRotation:rotation = rotation{}RotationRate로 명명된 편집 가능한float를 추가합니다. 이는 한 번의AdditionalRotation이 이루어지는 데 걸리는 시간(초)입니다.Verse# The time it takes to make one AdditionalRotation in seconds. @editable {ToolTip := RotationRateTip} var RotationRate:float = 1.0UseEasePerKeyFrame으로 명명된 편집 가능한logic을 추가합니다. 이는 각 키프레임의 보간에MoveEaseType을 사용하는지 여부를 나타냅니다. 이 예시에서 이 로직을false로 설정하면 각 프레임에 기본적으로 선형 보간 타입이 사용됩니다.Verse# Whether this prop should use the MoveEaseType per each frame of animation. # Setting this to false will use the linear MoveEaseType on each frame. @editable UseEasePerKeyframe:logic = trueMoveTargets로 명명된creative_prop의 편집 가능한 배열을 추가합니다. 루트 사물이 이동할 다양한 포크리 사물입니다.Verse# The Creative prop target for the RootProp to move toward. @editable {ToolTip := MoveTargetsTip} var MoveTargets:[]creative_prop = array{}이는
TargetTransform으로 명명된 변수transform을 추가합니다. 루트 사물이 현재 이 트랜스폼으로 이동 중입니다.Verse# The transform the prop is currently targeting. var TargetTransform:transform = transform{}
클래스 정의는 다음과 같습니다.
Verseusing { /Fortnite.com/Devices } using { /Fortnite.com/Devices/CreativeAnimation } using { /Verse.org/Simulation } using { /UnrealEngine.com/Temporary/SpatialMath } RotationRateTip<localizes>:message := "The time it takes to make one AdditionalRotation in seconds." UseEasePerKeyframeTip<localizes>:message := "Whether this prop should use the MoveEaseType for each keyframe. False will use the Linear ease type on each frame." # A prop that translates, rotates, and scales to a destination using animation. animating_prop<public> := class<concrete>(movable_prop):animating_prop클래스에서Move()함수를 오버라이드합니다. 그런 다음for표현식의MoveTargets배열에서 각MoveTarget을 반복작업합니다. 각MoveTarget이 유효한지 확인하고, 유효한 경우TargetTransform을MoveTarget의 트랜스폼으로 설정합니다.Verse# Move and rotate the RootProp toward the MoveTarget, or MoveTransform if one is set. Move<override>()<suspends>:void= # Move to each target in the MoveTargets array. for: MoveTarget:MoveTargets do: # Set the TargetTransformto the MoveTarget if the # MoveTarget is set. Otherwise set it to the MoveTransform.movement_behaviors파일로 돌아가서BuildMovingAnimationKeyframes()로 명명한 새 메서드를 추가합니다. 이 함수는 타깃 트랜스폼으로 이동 및 회전하는 사물에 애니메이션을 적용하는 키프레임 배열을 빌드 및 반환합니다. 이 함수는animating_prop에서MoveDuration,RotationRate,AdditionalRotation,OriginalTransform(사물의 시작 트랜스폼),TargetTransform,MoveEaseType이라는 몇 가지 파라미터를 받고,UseEasePerKeyframe이라는 새logic값을 받습니다. 이는 각 키프레임에MoveEaseType을 사용할지 여부를 결정합니다. 함수 시그니처는 다음과 같습니다.Verse# Builds an array of keyframes that animate movement and rotation from the OriginalTransform to the TargetTransform. BuildMovingAnimationKeyframes(MoveDuration:float, RotationRate:float, AdditionalRotation:rotation, OriginalTransform:transform, TargetTransform:transform,MoveEaseType:move_to_ease_type, UseEasePerKeyframe:logic):[]keyframe_delta=BuildMovingAnimationKeyframes()에서 다음 변수를 초기화합니다.Keyframes로 명명된keyframe_delta의 변수 배열을 초기화합니다. 종료 시 반환할 배열입니다.Verse# The array of keyframes to return. var KeyFrames:[]keyframe_delta = array{}TotalTime으로 명명된 변수float를 초기화합니다. 지금까지 애니메이션 적용에 소요된 총 시간입니다.Verse# The total amount of time spent animating. var TotalTime:float = 0.0StartTransform및EndTransform으로 명명된 두 개의transform변수를 초기화합니다. 각 키프레임 시작 및 종료 시 사물의 시작 및 종료 트랜스폼입니다. 둘 모두OriginalTransform으로 초기화합니다.Verse# The starting transform for building keyframes. This is the # transform of the RootProp at the start of each keyframe. var StartTransform:transform = OriginalTransform # The ending transform for building keyframes. This is the # transform of the RootProp at the end of each keyframe. var EndTransform:transform = OriginalTransformRotationToApply로 명명된 변수rotation을AdditionalRotation으로 초기화합니다. 각 키프레임에서 사물에 적용할 실제 회전입니다. 보통AdditionalRotation이지만, 부분 회전이 필요한 경우 이 값을 변경합니다.Verse# The actual rotation to apply to the RootProp. Usually this is the # AdditionalRotation, but will change in cases with fractional rotations. var RotationToApply:rotation = AdditionalRotationAnimationTime으로 명명된 변수float를 초기화합니다. 각 키프레임에 소요되는 시간(초)입니다. RootProp이 초당RotationRate회 회전해야 하므로1.0 / RotationRate로 초기화합니다.Verse# The time it takes for each keyframe of animation to complete. # This is initialized to 1.0/Rotation rate since the RootProp needs to make a # RotationRate number of rotations per second. var AnimationTime:float = 1.0 / RotationRateTotalRotations로 명명된float값을 초기화합니다. 이는 전체 애니메이션의 총 회전 수로,MoveDuration * RotationRate로 초기화합니다.int가 아닌float인 이유는 애니메이션 종료 시와 같이 360도로 회전할 필요가 없는 상황에 대응하기 위해서입니다.Verse# The total number of rotations to make. TotalRotations:float = MoveDuration * RotationRateTimePerRotation으로 명명된float값을 초기화합니다. 한 번의 회전을 완료하기까지 걸리는 시간(초)입니다. 함수는 이제 다음과 같습니다.Verse# Builds an array of keyframes that animate movement and rotation from the OriginalTransform to the TargetTransform. BuildMovingAnimationKeyframes(MoveDuration:float, RotationRate:float, AdditionalRotation:rotation, OriginalTransform:transform, TargetTransform:transform,MoveEaseType:move_to_ease_type, UseEasePerKeyframe:logic):[]keyframe_delta= # The array of keyframes to return. var KeyFrames:[]keyframe_delta = array{} # The total amount of time spent animating. var TotalTime:float = 0.0 # The starting transform for building keyframes. This is the
추적할 값이 많으므로 RotationRate를 2.5로, MoveDuration을 5.0으로 가정하여 계산해 보겠습니다.
Rotation Rate = 2.5 rotations/second
Move Duration = 5.0 seconds
Animation Time =
1.0 seconds/Rotation Rate =
1.0/2.5 = 0.4 seconds
Total Rotations =
Move Duration /Rotation Rate =
RotationRate는 2.5, MoveDuration은 5.0으로 설정한 상태에서 총 12.5회 회전하게 되면 각 회전마다 0.4초가 소요됩니다. 즉, 애니메이션 종료 시 추가로 180도 회전해야 합니다. 애니메이션 시간과 회전당 시간이 동일하다는 것도 알 수 있습니다. 이는 360도보다 작은 범위로 회전해야 할 때를 제외하면 거의 항상 그렇습니다. 초기에는 동일한 값이더라도, 일부 수학을 나중에 처리하려면 두 변수를 모두 추적해야 합니다.
루프에서 키프레임 빌드하기
이제 빌드할 차례입니다! 키프레임을 빌드하는 루프를 구성하려면 아래 단계를 따릅니다.
루프표현식을BuildMovingAnimationKeyframes()에 추가합니다. 루프의 각 반복작업에서 새 키프레임을 빌드하여 키프레임 배열에 추가합니다. 루프 시작 시TotalTime을TimePerRotation으로 업데이트합니다.Verse# Build each keyframe of animation and add it to the Keyframes array. # The loop breaks when the TotalTime goes past the MoveDuration. loop: # Add the TimePerRotation to the TotalTime. set TotalTime += TimePerRotation이 키프레임 종료 시 사물이 있어야 하는
EndTransform을 빌드합니다. 다음 파라미터를 사용하여EndTransform을 새 트랜스폼으로 설정합니다.Translation을OriginalTransform과TargetTransform사이의Lerp()호출 결과로 설정합니다.Lerp()함수는 두 개의 값과0.0과1.0사이의 선형보간 비율을 가져옵니다. 그런 다음 선형보간 비율을 기준으로 두 값 사이의 어딘가에 새 값을 생성합니다. 선형보간 비율이 1.0에 가까울수록 트랜스폼은TargetTransform에 가까워지며, 0.0에 가까울수록 반대의 효과를 보입니다.Verse# Build the ending transform for the RootProp to move to. set EndTransform = transform: # Use Lerp() to find how far between the StartingTransform and the TargetTransform the RootProp should translate. # Do the same for scale, and rotate the starting transform by the RotationToApply. # The Lerp Parameter is based on the total number of rotations since the RootProp should guarantee that it makes # at least that many rotations over the whole animation. Translation := Lerp(OriginalTransform.Translation, TargetTransform.Translation, (TotalTime * RotationRate) / (TotalRotations))Rotation을MakeShortestRotationBetween()의 결과로 설정하여 원본 트랜스폼의 회전과RotationToApply만큼 회전한 원본 트랜스폼을 전달합니다.VerseTranslation := Lerp(OriginalTransform.Translation, TargetTransform.Translation, (TotalTime * RotationRate) / (TotalRotations)) Rotation := MakeShortestRotationBetween(OriginalTransform.Rotation, OriginalTransform.Rotation.RotateBy(RotationToApply))~~~Scale을OriginalTransform.Scale과TargetTransform.Scale사이의Lerp()호출 결과 값으로 설정합니다. 이는 사물의 타깃 스케일이며 스케일 조절해야 하는 양이 아니라는 점에 유의하세요. 완성된EndTransform은 다음과 같습니다.Verse# Build the ending transform for the RootProp to move to. set EndTransform = transform: # Use Lerp() to find how far between the StartingTransform and the TargetTransform the RootProp should translate. # Do the same for scale, and find the shortest rotation between the original transform and a rotation to apply to it. Translation := Lerp(OriginalTransform.Translation, TargetTransform.Translation, LerpParameter) Rotation := MakeShortestRotationBetween(OriginalTransform.Rotation, OriginalTransform.Rotation.RotateBy(RotationToApply)) Scale := Lerp(OriginalTransform.Scale, TargetTransform.Scale, LerpParameter)
종료 트랜스폼이 설정되었으니, 키프레임을 빌드할 차례입니다. 이는
MoveToEase()함수에 진행한 과정과 대체로 동일합니다.KeyFrame으로 명명한 새keyframe_delta변수를 생성합니다.DeltaLocation을 종료 트랜스폼 이동에서 시작 트랜스폼 이동을 뺀 값으로 설정합니다.DeltaRotation을 종료 트랜스폼의 회전으로 설정합니다. 스케일 변경을 계산해야 하므로DeltaScale을 종료 트랜스폼 스케일을 시작 트랜스폼 스케일로 나눈 결과로 설정합니다.Time은AnimationTime이 되어야 하며,InterpolationType은if표현식의 결과가 되어야 합니다.UseEasePerKeyframe이 true인 경우MoveEaseType을 사용합니다. 그렇지 않으면 선형 타입을 사용합니다. 키프레임 표현식은 다음과 같습니다.Verse# Build the animation keyframe to animate the RootProp. Keyframe := keyframe_delta: DeltaLocation := EndTransform.Translation - StartTransform.Translation, DeltaRotation := EndTransform.Rotation, DeltaScale := EndTransform.Scale/StartTransform.Scale, Time := AnimationTime, # Use the MoveEaseType for interpolation if UseEasePerKeyframe is true, # otherwise use the Linear movement type. Interpolation := if:키프레임을 빌드했으니, 이제
Keyframes배열에 추가할 수 있습니다. 그런 다음StartTransform을EndTransform으로 설정하여 다음 키프레임을 위해 업데이트합니다. 마지막으로TotalTime이 이제MoveDuration보다 크면 루프를 중단합니다.Verse# Add the new keyframe to the KeyFrames array, and set the # StartTransform to the EndTransform. set Keyframes += array{Keyframe} set StartTransform = EndTransform # Break out of the loop if the TotalTime passes the MoveDuration. if: TotalTime >= MoveDuration then: break중요하게 고려해야 할 극단적인 사례가 하나 있습니다. 360도보다 작은 범위로 회전해야 하는 경우에는 어떻게 될까요?
TimePerRotation을TotalTime에 추가하면, 루프 시작 시TotalTime이MoveDuration보다 높을 수 있습니다. 이러한 상황에서는 남은 시간을 처리하고 360도보다 작은 범위로 회전해야 하며, 차이를 고려해 애니메이션 시간이 더 짧아야 합니다. 이 상황을 처리하려면 다음 단계를 따릅니다.루프의 시작으로 돌아가
TotalTime을 업데이트한 후if표현식을 시작합니다. 내부에서 변수LeftoverTime을 초기화하고TotalTime과MoveDuration을 뺀 결과와 같게 설정하여 값이0.0보다 큰지 확인합니다. 이 표현식은 비교가 true인 경우에만LeftoverTime을 할당합니다.Verseloop: # Add the TimePerRotation to the TotalTime. set TotalTime += TimePerRotation if: # If the TotalTime is greater than the MoveDuration, the final keyframe needs # to be shortened. This means making a fraction of a rotation. LeftoverTime := TotalTime - MoveDuration > 0.0회전해야 하는 범위를 파악하기 위해 새 변수
Roation Fraction을 초기화하고TimePerRotation에서LeftoverTime을 뺀 다음TimePerRotation으로 나눈 것과 같은 값으로 설정합니다.Verseif: # If the TotalTime is greater than the MoveDuration, the final keyframe needs # to be shortened. This means making a fraction of a rotation. LeftoverTime := TotalTime - MoveDuration > 0.0 # The fraction of a rotation to make. RotationFraction := (TimePerRotation - LeftoverTime)/TimePerRotationRotationFraction의 수정된 회전을 빌드하려면Slerp[]를 사용합니다. 이는Lerp()의 한 버전으로, 스피어 보간을 처리해 주며 비슷한 파라미터가 갖고 있습니다. 서로 다른 두 회전 간의 최단 회전을 찾아 선형보간 파라미터를 기반으로 회전을 반환합니다.Slerp[]를 호출하고RotationFraction을 선형보간 파라미터로 사용하여,IdentityRotation()과RotationToApply만큼 회전한IdentityRotation()사이를 보간합니다.EndTransform의 최종 회전이 아니라 부분 회전을 얼마나 적용해야 하는지에 초점이 맞춰져 있으므로 여기에서는IdentityRotation()을 사용합니다.Verseset TotalTime += TimePerRotation if: # If the TotalTime is greater than the MoveDuration, the final keyframe needs # to be shortened. This means making a fraction of a rotation. LeftoverTime := TotalTime - MoveDuration > 0.0 # The fraction of a rotation to make. RotationFraction := (TimePerRotation - LeftoverTime)/TimePerRotation # Make a modified fractional rotation by using Slerp(). The Slerp() function does spherical interpolation이러한 값이 구성된 상태에서,
RotationToApply를ModifiedRotation으로 설정하고AnimationTime에RotationFraction을 곱해 애니메이션을 얼마나 줄일지 파악합니다. 마지막으로EndTransform을 계산할 때 TotalTime이 MoveDuration을 초과하지 않도록 TotalTime을MoveDuration으로 설정합니다.VerseModifiedRotation := Slerp[IdentityRotation(), IdentityRotation().RotateBy(RotationToApply), RotationFraction] then: # Set the RotationToApply to the modified rotation, and multiply the animation time by # the RotationFraction to get the modified animation time. set RotationToApply = ModifiedRotation set AnimationTime = AnimationTime * RotationFraction # Since the TotalTime should not go past the MoveDuration, # set TotalTime to MoveDuration. set TotalTime = MoveDuration
함수 맨 끝에서 루프 이후
Keyframes배열을 반환합니다. 완성된BuildMovingAnimationKeyframes()함수는 다음과 같습니다.Verse# Builds an array of keyframes that animate movement and rotation from the OriginalTransform to the TargetTransform. BuildMovingAnimationKeyframes(MoveDuration:float, RotationRate:float, AdditionalRotation:rotation, OriginalTransform:transform, TargetTransform:transform,MoveEaseType:move_to_ease_type, UseEasePerKeyframe:logic):[]keyframe_delta= # The array of keyframes to return. var Keyframes:[]keyframe_delta = array{} # The total amount of time spent animating. var TotalTime:float = 0.0 # The starting transform for building keyframes. This is the이제 키프레임을 빌드할 로직을 정의했으니, 애니메이션을 적용할 차례입니다. 별도의 함수를 사용하여 애니메이션을 빌드하고 호출할 것입니다. 새 함수
BuildAndPlayAnimation()을animating_prop클래스에 추가합니다. 이 함수에<suspends>모디파이어를 추가하여 다른 비동기 함수를 호출하도록 허용합니다.Verse# Builds an animation from an array of keyframes, then calls MoveToEase() # to animate the prop. BuildAndPlayAnimation()<suspends>:void=BuildAndPlayAnimation()에서Keyframes로 명명된 새keyframe_delta배열을 초기화합니다. 그런 다음키프레임을BuildMovingAnimationKeyframes()호출 결과로 설정합니다.Move()호출 간에 사물의 위치가 변경되므로RootProp.GetTransform()을 시작 트랜스폼으로 사용합니다.animation_mode변수를animation_mode.OneShot으로 초기화하고MoveToEase()를 호출하여Keyframes배열 및AnimationMode를 전달합니다.완성된
BuildAndPlayAnimation()함수는 다음과 같습니다.Verse# Builds an animation from an array of keyframes, then calls MoveToEase() # to animate the prop. BuildAndPlayAnimation()<suspends>:void= var Keyframes:[]keyframe_delta = array{} # Build the animation, using the RootProp as the target transform. set Keyframes = BuildMovingAnimationKeyframes(MoveDuration, RotationRate, AdditionalRotation, RootProp.GetTransform(), TargetTransform, MoveEaseType, UseEasePerKeyframe) # Set the animation mode to OneShot. var AnimationMode:animation_mode := animation_mode.OneShotMove()로 돌아가TargetTransform을 이동 타깃 트랜스폼으로 설정한 후BuildAndPlayAnimation()을 호출합니다.Verse# Move to each target in the MoveTargets array. for: MoveTarget:MoveTargets do: # Set the TargetTransform to the MoveTarget if the # MoveTarget is set. Otherwise set it to the MoveTransform. if: MoveTarget.IsValid[] then: set TargetTransform = MoveTarget.GetTransform()
고려해야 할 사례가 하나 더 있습니다. 아무런 이동 타깃도 설정하지 않으면 어떻게 될까요? 이러한 상황에는 사물이 새 목적지로 이동하지 않고 제자리에서 계속 회전합니다. 이를 처리하려면 if 표현식을 Move() 함수의 상단에 추가합니다. if에서 MoveTargets.Length = 0인지 확인하고, 0이면 TargetTransform을 루트 사물의 트랜스폼으로 설정합니다. 그런 다음 BuildAndPlayAnimation()을 호출합니다. 이렇게 하면 이동 타깃을 설정하지 않았더라도 사물이 계속 이동합니다. 완성된 Move() 애니메이션은 다음과 같습니다.
# Move and rotate the RootProp toward the MoveTarget, or MoveTransform if one is set.
Move<override>()<suspends>:void=
# If there are no targets to move to, this prop will rotate in place.
if:
MoveTargets.Length = 0
then:
set TargetTransform = RootProp.GetTransform()
# Build and play the animation.
BuildAndPlayAnimation()
이제 prop_animator 클래스에서 animating_prop을 참조해야 합니다. prop_animator에서 MoveAndRotateProps로 명명한 animating_prop의 편집 가능한 배열을 추가합니다. OnBegin()의 또 다른 for 표현식에서 Setup()을 호출하여 MoveAndRotateProps의 각 사물을 초기화합니다. 완성된 prop_animator 클래스는 다음과 같습니다.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
TranslatingPropsTip<localizes>:message = "The props that translate (move) using animation."
RotatingPropsTip<localizes>:message = "The props that rotate using animation."
ScalingPropsTip<localizes>:message = "The props that scale using animation."
AnimatingPropsTip<localizes>:message = "The props that both move and rotate using animation."
# Coordinates moving props through animation by calling each movable_prop's Setup() method.
코드를 저장하고 컴파일합니다.
축하합니다, 모든 코드가 완성되었습니다! 이제 모든 것을 서로 연결할 차례입니다.
방금 생성한 animating_prop 클래스는 사물을 이동하고, 회전하고, 스케일 조절할 수 있습니다. 하지만 ManageMovement()와 같은 몇 가지 함수를 상속해야 하므로 moveable_prop 클래스에 계속 의존하게 됩니다. animating_prop 클래스가 세 가지 타입의 이동을 모두 수행할 수 있다는 점을 고려하면, 클래스가 독립적일 수 있도록 animating_prop의 모든 로직을 포함하기 위해 moveable_prop을 리팩터링하는 것이 유용할 수 있습니다. 예시 리팩터링이 여기 포함되어 있는데, animating_prop과 moveable_prop을 단일 파일로 병합해 줍니다. 예시에는 prop_animator Verse 장치 클래스도 포함되어 있습니다. 코드가 실행되기 위해서는 여전히 movement_behaviors의 기능이 필요하지만, 이 리팩터링을 통해 필요한 파일 수가 5개에서 2개로 줄어듭니다. 이 코드는 완성된 코드 섹션에도 포함되어 있습니다.
using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/CreativeAnimation }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
EasingCategory<localizes>:message := "These control the type of movement easing applied to the prop."
LogicCategory<localizes>:message := "These control different aspects of the prop's logic."
PropsCategory<localizes>:message := "These are the props that move associated with this device."
RotationCategory<localizes>:message := "These control how the prop rotates."
TimingCategory<localizes>:message := "These control the timing of parts of the prop's movmement."
장치에 사물 연결하기
에디터로 돌아가 회전하는 사물 섹션 뒤의 코스 섹션 하나를 삭제하여 최종 목표 앞에 틈새를 만듭니다. 레벨에 또 하나의 FG01 SpinningBar Double S와 FG01 Hover Platform M을 추가합니다. SpinningMovingBar와 TranslatingPlatform으로 명명한 다음, 각 사물이 이동할 타깃이 될 FG01 Button Bulb 사물을 몇 개 추가하고 PlatformTarget으로 명명합니다. 틈새 위로 플랫폼과 바를 배치하고, 플랫폼이 이동하도록 할 타깃을 배치합니다. 이 예시에서 회전 바는 양옆으로 움직이는 반면, 플랫폼은 앞뒤로 움직입니다.
회전 바 및 이동 플랫폼의 구성입니다. 화살표는 각 사물이 이동하는 방향을 나타냅니다. 회전 바 및 플랫폼은 모두 왕복 이동하며, 회전 바는 이동하면서 회전합니다.
아웃라이너(Outliner)에서 prop animator를 선택합니다. 회전 바의 AnimatingProps에 배열 엘리먼트를 추가합니다. 각 값을 다음과 같이 설정합니다.
| 옵션 | 값 | 설명 |
|---|---|---|
추가 회전(Additional Rotation) | 90.0 | 이 사물은 매번 90도 회전합니다. |
회전 속도(Rotation Rate) | 1.5 | 이 사물은 |
키프레임별 이즈 사용(Use Ease Per Keyframe) | False | 각 키프레임에서 선형 이징 타입을 사용하여 사물을 일정한 속도로 이동 및 회전합니다. |
MoveTargets | 2개의 엘리먼트로, 플랫폼 타깃에 할당됩니다. | 바가 이동할 타깃입니다. |
RootProp | SpinningMovingBar | 애니메이션을 적용할 사물입니다. |
이동 플랫폼의 TranslatingProps에 또 다른 배열 엘리먼트를 추가합니다. 플랫폼 타깃에 MoveTargets를, TranslatingPlatform에 RootProp을 할당합니다.
세션 시작(Launch Session)을 눌러 완성된 장애물 코스를 플레이테스트해 보세요!
직접 해보기
다 됐습니다! 이제 Verse로 나만의 폴가이즈 장애물 코스를 만드는 데 필요한 모든 것이 준비되었습니다.
여기의 코드를 사용하여 모든 경험에서, 심지어 폴가이즈 프로젝트가 아니어도 포크리 사물에 애니메이션을 적용할 수 있습니다.
학습한 내용을 활용하여 다음과 같은 작업을 해 보세요.
다양한 방향으로 회전하거나 여러 키프레임에 걸쳐 랜덤으로 회전하는 장애물을 만들어 봅니다.
플레이어가 위에 서거나 특정 거리 안으로 들어올 때만 활성화되는 장애물을 만들어 봅니다.
특정 지속 시간 이후 사라지거나, 너무 오래 머무르면 플레이어를 위험한 위치로 이동시키는 플랫폼을 만드는 방법을 찾아봅니다.
완성된 코드
이 섹션에서 작성한 완성된 코드입니다. animating_prop의 예시 리팩터링이 포함되어 있습니다.
movable_prop.verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
MoveDurationTip<localizes>:message = "The amount of time the prop takes to move to its destination."
MoveEaseTypeTip<localizes>:message = "The animation easing applied to the movement."
MoveEndDelayTip<localizes>:message = "The delay after the movement finishes."
MoveOnceAndStopTip<localizes>:message = "Whether the RootProp should stop in place after it finishes moving."
MoveStartDelayTip<localizes>:message = "The delay before the movement starts."
MoveTargetsTip<localizes>:message = "The array of CreativeProp to move toward. These targets can be children of the RootProp."
translating_prop.verse
using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/CreativeAnimation }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
MovePositionTip<localizes>:message = "The optional position to move to World Space. Use this if you do not want to set a MoveTarget."
# A prop that moves (translates) toward either a Creative prop target
# or a position in world space.
translating_prop<public> := class<concrete>(movable_prop):
rotating_prop.verse
using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/CreativeAnimation }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
AdditionalRotationTip<localizes>:message = "The rotation to apply to the RootProp."
ShouldRotateForeverTip<localizes>:message = "Whether the RootProp should rotate forever."
MatchRotationTargetTip<localizes>:message = "The optional prop whose rotation the RootProp should rotate to. Use this if you do not want to set an Additional Rotation."
# A prop that rotates by an additional rotation or rotates to match
scaling_prop.verse
using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/CreativeAnimation }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
MatchScaleTargetTip<localizes>:message = "The optional position to move to World Space. Use this if you do not want to set a MoveTarget."
# A prop that scales toward either a given scale or a Creative prop's scale.
scaling_prop<public> := class<concrete>(movable_prop):
# The array of vector3 targets for the RootProp to scale to.
animating_prop.verse
using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/CreativeAnimation }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
RotationRateTip<localizes>:message := "The time it takes to make one AdditionalRotation in seconds."
UseEasePerKeyframeTip<localizes>:message := "Whether this prop should use the MoveEaseType for each keyframe. False will use the Linear ease type on each frame."
# A prop that translates, rotates, and scales to a destination using animation.
animating_prop<public> := class<concrete>(movable_prop):
movement_behaviors.verse
# This file stores functions common to animating Creative props using keyframes.
# It also defines the move_to_ease_type enum to help in building animations.
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Fortnite.com/Characters}
using { /Fortnite.com/Devices/CreativeAnimation }
# Represents the different movement easing types.
move_to_ease_type<public> := enum {Linear, Ease, EaseIn, EaseOut, EaseInOut}
prop_animator.verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
TranslatingPropsTip<localizes>:message = "The props that translate (move) using animation."
RotatingPropsTip<localizes>:message = "The props that rotate using animation."
ScalingPropsTip<localizes>:message = "The props that scale using animation."
MoveAndRotatePropsTip<localizes>:message = "The props that both move and rotate using animation."
# Coordinates moving props through animation by calling each moveable_prop's Setup() method.
animating_props.verse(animating_prop의 예시 리팩터)
using { /Fortnite.com/Devices }
using { /Fortnite.com/Devices/CreativeAnimation }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
EasingCategory<localizes>:message := "These control the type of movement easing applied to the prop."
LogicCategory<localizes>:message := "These control different aspects of the prop's logic."
PropsCategory<localizes>:message := "These are the props that move associated with this device."
RotationCategory<localizes>:message := "These control how the prop rotates."
TimingCategory<localizes>:message := "These control the timing of parts of the prop's movmement."