На предыдущем шаге мы создали объекты, которые одновременно перемещаются, вращаются и изменяют свои размеры. Однако при анимации объектов окружения, которые перемещаются несколькими способами одновременно, нужно обратить внимание на некоторые важные моменты.
Поскольку контроллер анимации может одновременно воспроизводить только одну анимацию, не получится осуществлять перемещение, вращение и масштабирование в отдельных анимациях. Для этих анимаций также необходимо несколько опорных кадров, поскольку может потребоваться повернуть объект окружения несколько раз в одной анимации. Поскольку необходимо построить все опорные кадры заранее, также требуется рассчитать положение, вращение и масштаб в каждой точке трансформации объекта окружения. Что произойдёт, если объект окружения должен совершить нецелое число оборотов? Как вы будете обрабатывать ситуацию с половиной оборота?
В следующем разделе будет много математики, но, изучив его, вы сможете перемещать, вращать и масштабировать объекты окружения с несколькими промежуточными точками, а также создавать сложные, динамичные и, что самое важное, увлекательные испытания в стиле «платформера», чтобы построить полосу препятствий Fall Guys своей мечты.
Создание анимаций, которые обеспечивают перемещение, вращение и масштабирование
Выполните следующие шаги, чтобы собрать отдельные элементы воедино:
Создайте новый класс Verse с именем
animating_prop, наследующий отmovable_prop, с помощью проводника Verse. Добавьте спецификатор<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. Это вращение, применяемое кRootPropв каждом ключевом кадре.Verse# The additional rotation to apply to the RootProp per keyframe. @editable {ToolTip := AdditionalRotationTip} AdditionalRotation:rotation = rotation{}Редактируемая переменная типа
floatс названиемRotationRate. Это время в секундах, необходимое для совершения одного вращенияAdditionalRotation.Verse# The time it takes to make one AdditionalRotation in seconds. @editable {ToolTip := RotationRateTip} var RotationRate:float = 1.0Редактируемая переменная типа
logicс названиемUseEasePerKeyFrame. Определяет, будет ли в каждом опорном кадре применяться интерполяция типа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 = trueРедактируемый массив элементов
creative_propс названиемMoveTargets. Существуют различные объекты окружения творческого режима, к которым будет перемещаться корневой объект окружения.Verse# The Creative prop target for the RootProp to move toward. @editable {ToolTip := MoveTargetsTip} var MoveTargets:[]creative_prop = array{}Переменная типа
transformс названиемTargetTransform. Это целевое преобразование, в которое в данный момент переходит корневой объект окружения.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):Переопределите функцию
Move()в классеanimating_prop. Затем в выраженииforпереберём все элементыMoveTargetв массивеMoveTargets. Проверьте, является ли действительным каждый элемент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и новую переменную типаlogicс названиемUseEasePerKeyframe. Последняя переменная определяет, будет ли использоваться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()инициализируем следующие переменные:Массив переменных типа
keyframe_deltaс именемKeyframes. Это массив, возвращаемый в конце.Verse# The array of keyframes to return. var KeyFrames:[]keyframe_delta = array{}Переменная типа
floatс названиемTotalTime. Это общее время воспроизведения анимации к данному моменту.Verse# The total amount of time spent animating. var TotalTime:float = 0.0Две переменные типа
transformс названиямиStartTransformиEndTransform. Это начальное и конечное преобразования объекта окружения в начале и в конце каждого опорного кадра. Инициализируем обе переменные значением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 = OriginalTransformПеременная типа
rotationс названиемRotationToApply, инициализируемая значением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 = AdditionalRotationПеременная типа
floatс названиемAnimationTime. Это продолжительность каждого опорного кадра в секундах. Инициализируем её значением1.0 / RotationRate, так как RootProp должен совершать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 / RotationRateПеременная типа
floatс названиемTotalRotations. Это общее количество оборотов, которое нужно совершить на протяжении всей анимации, и инициализация происходит со значениемMoveDuration * RotationRate. Используется переменная типаfloat, а неint, потому что это необходимо для ситуаций, когда нужно сделать неполное вращение, например в конце анимации.Verse# The total number of rotations to make. TotalRotations:float = MoveDuration * RotationRateПеременная типа
floatс названиемTimePerRotation. Это время в секундах, необходимое для совершения одного вращения. Теперь функция должна выглядеть следующим образом: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 секунды. Это означает, что в конце анимации нужно будет сделать ещё половину вращения Также можно заметить, что время воспроизведения анимации совпадает с продолжительностью одного вращения. Это почти всегда так, за исключением случаев, когда нужно сделать меньше полного вращения. Несмотря на то что изначально эти переменные совпадают, нужно будет отслеживать их обе, чтобы затем производить некоторые расчёты.
Создание опорных кадров в цикле
Пора приступать к разработке! Выполните следующие шаги, чтобы организовать цикл, в котором создаются опорные кадры.
Добавьте выражение
loopв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результат вызоваLerp()междуOriginalTransformиTargetTransform. ФункцияLerp()принимает два значения и коэффициент линейной интерполяции в диапазоне от0,0до1,0. Затем она рассчитывает новое значение где-то между двумя базовыми значениями с учётом коэффициента линейной интерполяции. Чем ближе коэффициент линейной интерполяции к 1,0, тем ближе будет преобразование кTargetTransform, и наоборот.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))~~~Запишем в
Translationрезультат вызоваLerp()междуOriginalTransform.ScaleиTargetTransform.Scale. Помните, что это масштаб, до которого следует масштабировать объект окружения, а не величина, на которую его необходимо масштабировать. Готовый объект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_deltaс именемKeyFrame. Определим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Необходимо рассмотреть важный пограничный случай — что произойдёт, когда нужно сделать меньше полного вращения? Поскольку к
TimePerRotationдобавляетсяTotalTime, это означает, что в начале цикла значениеTotalTimeможет оказаться больше, чемMoveDuration. В этом случае необходимо обработать оставшееся время и совершить неполное вращение, установив более короткое время анимации, чтобы учесть разницу. Выполните следующие шаги, чтобы корректно обрабатывать такую ситуацию:В начале цикла, после обновления
TotalTime, начните выражениеif. Внутри него инициализируйте переменнуюLeftoverTimeи задайте для неё результат вычитанияTotalTimeиMoveDuration, проверив, не превышает ли он значение0,0. Это выражение присваивает значение переменнойLeftoverTimeтолько в том случае, если сравнение даёт результат true.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)/TimePerRotationЧтобы создать изменённое вращение на основе
RotationFraction, используемSlerp[]. Это версия функцииLerp(), которая выполняет сферическую интерполяцию и имеет аналогичные параметры. Она находит кратчайшее вращение между двумя разными положениями вращения и возвращает его на основе параметра линейной интерполяции. ВызовемSlerp[], выполнив интерполяцию междуIdentityRotation()иIdentityRotation(), повёрнутым с помощьюRotationToApply, используяRotationFractionв качестве параметра. Здесь мы используемIdentityRotation(), потому что вас интересует только то, какую часть оборота нужно совершить, а не конечный поворот, который должен быть установлен вEndTransform.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, чтобы узнать, насколько нужно сократить анимацию, и, наконец, установимTotalTimeравнымMoveDuration, поскольку это время не должно быть больше использовавшегося при расчётеEndTransform.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
В самом конце функции, после блока loop, вернём массив
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()инициализируем новый массив типаkeyframe_deltaс названиемKeyframes. Затем присвоим массивуKeyframesрезультат вызоваBuildMovingAnimationKeyframes(). ИспользуйтеRootProp.GetTransform()в качестве начального преобразования, поскольку положение объекта окружения будет меняться между вызовамиMove(). Инициализируем переменную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.OneShotВернувшись в
Move(), вызовемBuildAndPlayAnimation()предварительно установив преобразованиеTargetTransformравным преобразованию целей перемещения.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, и если да, установим 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()
Теперь нужно добавить ссылку на animating_prop в классе prop_animator. В prop_animator добавьте редактируемый массив animating_prop с названием MoveAndRotateProps. В OnBegin(), в другом выражении for, инициализируйте каждый объект окружения в MoveAndRotateProps, вызвав Setup(). Готовый класс 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 способен перемещать, поворачивать и масштабировать объекты окружения. Однако он по-прежнему зависит от класса moveable_prop, поскольку ему необходимо наследовать несколько функций, таких как ManageMovement(). Учитывая, что класс animating_prop способен выполнять перемещение всех трёх типов, может оказаться полезным перепроектировать animating_prop, включив в него всю логику moveable_prop, чтобы наш класс мог работать автономно. Ниже приведён пример перепроектирования, в котором классы animating_prop и moveable_prop объединены в одном файле. Он также включает класс устройства Verse prop_animator. Для выполнения кода по-прежнему требуется функционал из movement_behaviors, но такое перепроектирование сокращает количество необходимых файлов с пяти до двух. Этот код также приведён в раздел Полный код.
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 и Аэроплатформа (средняя) FG01. Назовите их ConstantMovingBar и TranslatingPlatform, а затем добавьте несколько объектов окружения Кнопка-луковица FG01, которые будут целями, к которым будет перемещаться каждый объект. Назовите их PlatformTarget. Поместите платформы и планку над пропастью и убедитесь, что цели размещены там, куда должны перемещаться платформы. В нашем примере вращающаяся планка перемещается из стороны в сторону, а платформа перемещается вперёд и назад.
Настройка вращающейся панели и движущейся платформы. Стрелки указывают направления перемещения каждого объекта окружения. Вращающаяся планка и платформа перемещаются вперёд и назад, а вращающаяся планка ещё и поворачивается во время перемещения.
Выберите свой prop_animator на панели Структура. Добавьте в массив AnimatingProps элемент, соответствующий вращающейся планке. Задайте следующие значения параметров:
| option | Значение | Пояснение |
|---|---|---|
Дополнительное вращение | 90,0 | Этот объект окружения всякий раз будет поворачиваться на 90 градусов. |
Скорость вращения | 1,5 | Этот объект окружения поворачивается каждые |
Использование замедления для каждого опорного кадра | ложь | В каждом опорном кадре будет использоваться линейный тип замедления для перемещения и вращения объекта окружения с постоянной скоростью. |
MoveTargets | 2 элемента, назначенные в качестве целей платформы. | Это цели, к которым должна перемещаться планка. |
RootProp | StronginMovingBar | Это анимируемый объект окружения. |
Добавьте в массив TranslatingProps ещё один элемент, соответствующий перемещающейся платформе. Назначьте MoveTargets в качестве целей платформы, а RootProp в качестве TranslatingPlatform.
Нажмите Запуск сеанса и попробуйте пройти свою полосу препятствий!
Самостоятельная работа
Вот и всё! Теперь у вас есть всё, что нужно, чтобы создать собственную полосу препятствий Fall Guys с помощью Verse!
Вы можете использовать этот код, чтобы анимировать объекты окружения творческого режима в любом из своих проектов, даже отличном от полосы препятствий Fall Guys!
Используя полученные знания, попробуйте сделать следующее:
Создавайте препятствия, вращающиеся в разных направлениях или случайным образом в разных опорных кадрах.
Создайте препятствия, которые активируются только тогда, когда игрок встаёт на них или приближается к ним на определённое расстояние.
Придумайте, как сделать так, чтобы платформы исчезали по истечении определённого времени или перемещали игрока в опасные места, если он остаётся на этих платформах слишком долго.
Полный код
Ниже приведён полный код, созданный в этом разделе, включая пример перепроектирования для animating_prop.
moveable_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):
rotation_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."