Este tutorial es una versión avanzada específica de Scene Graph del tutorial Animación del movimiento de elementos. Si quieres obtener más información sobre el movimiento basado en animaciones en UEFN fuera de Scene Graph, echa un vistazo a ese tutorial y luego vuelve a este.
Las plataformas móviles son habituales en la mayoría de los juegos de plataformas y desafían al jugador a realizar saltos precisos entre objetivos para alcanzar la meta.
Existen varias maneras de mover elementos en UEFN. Puedes utilizar funciones como TeleportTo[] o MoveTo() para modificar directamente una transformación, o utilizar otro dispositivo como un colocador de elementos para mover un elemento en una ruta preestablecida. No obstante, existe otra opción útil en forma de animaciones.
Las animaciones presentan un par de ventajas sobre el movimiento de transformación del elemento. Las animaciones normalmente tienen un movimiento más suave que mover objetos con MoveTo() o TeleportTo(), ya que evitan la latencia de red que supone tener que llamar a estas funciones cada tic de juego.
Las animaciones también tienen colisiones más uniformes con los jugadores u otros objetos, y tienes un mayor nivel de control sobre dónde y cómo se mueve un objeto en comparación con el uso de un dispositivo Colocador de elementos. Puedes reproducir animaciones en bucle o reproducirlas hacia delante y hacia atrás con el modo ping-pong.
Las animaciones también te permiten elegir un tipo de interpolación. El tipo de interpolación determina el tipo de aceleración, o curva de animación, que seguirá la animación. Por ejemplo, el tipo de interpolación lineal reproduce la animación a una velocidad constante, mientras que el tipo de entrada lenta empieza despacio y luego se acelera hacia el final.
Al elegir el tipo de interpolación adecuado para tu animación, puedes especificar en diferentes puntos si el elemento debe ralentizarse, acelerarse o moverse linealmente. Si aplicas este componente para implementar estos comportamientos en las entidades de tu nivel, podrás crear plataformas móviles por las que puedan desplazarse tus jugadores.
En primer lugar, considera qué tipo de comportamientos debería tener tu plataforma. Debe empezar a animarse desde una posición inicial determinada y, a continuación, moverse a varios puntos. Cuando llegue al final de su movimiento, debería poder volver a su posición inicial o quedarse donde está.
Deben realizar estos movimientos durante un periodo determinado, y ser capaces de rotar y escalar adecuadamente en cada punto de su recorrido. Cada uno de estos comportamientos requiere un código específico para conseguirlo, pero si empiezas por una clase sencilla y vas construyendo, puedes iterar rápidamente sobre diferentes ideas. Sigue estos pasos para crear un componente de movimiento basado en animación.
Crea un nuevo componente de Verse llamado
animate_to_targets_componenty ábrelo en Visual Studio Code. Para obtener más información sobre cómo crear tus propios componentes de Verse, consulta Cómo crear tu propio componente de Verse. Añade instruccionesusingpara/Verse.org/SpatialMath,/Verse.org/SceneGraph/KeyframedMovement, y/UnrealEngine.com/Temporary/SpatialMath. Necesitarás funciones de cada uno de ellos más adelante.Verseusing { /Verse.org } using { /Verse.org/Native } using { /Verse.org/SceneGraph } using { /Verse.org/Simulation } using { /Verse.org/SpatialMath } using { /Verse.org/SceneGraph/KeyframedMovement } using { /UnrealEngine.com/Temporary/SpatialMath } # Place this component to an entity to move between preset targets. animate_to_targets_component<public> := class<final_super>(component):A continuación se incluyen las burbujas informativas utilizadas en esta sección del tutorial. Puedes copiarlos y pegarlos encima de la definición de tu clase
animate_to_targets_component.Verse# Editor Tool Tips DefaultSpeedTip<localizes><public>:message = "Default speed simulation entity moves during any segment that does not specify a speed." SpeedTip<localizes><public>:message = "Speed simulation entity moves during this segment." AnimationDurationTip<localizes><public>:message = "The duration of the animation segment in seconds." EasingFunctionTip<localizes><public>:message = "Movement cubic-bezier easing function during this segment." DefaultEasingFunctionTip<localizes><public>:message = "Default movement cubic-bezier easing function during any segment that does not specify an easing function." PlaybackModeTip<localizes><public>:message = "Animation playback mode\n\tOneshot: Animation plays once.\n\tPingPong: Animation plays in order and reverse order repeating.\n\tLoop: Animation repeats in a loop. Tip: To construct an animation in a closed loop, make the last segment starting position the same as the first segment starting position." TargetsTip<localizes><public>:message = "Entities that are targets for the parent entity." RandomizeTargetsTip<localizes><public>:message = "Randomize the order of the segments.\n\tNever: Always use the order specified.\n\tOnBegin: Only randomize the order on begin simulation.\n\tEveryIteration: Randomize the order of the segments every loop iteration." PauseTip<localizes><public>:message = "Duration simulation entity pauses at the beginning of this segment."Añade los siguientes campos a la definición de tu clase
animate_to_targets_component:Una variable float numérica y editable denominada
InitialPauseSeconds. Esta es la cantidad de tiempo que transcurre antes de que la entidad empiece a animarse. Elige el valor10.0para que la entidad espere diez segundos antes de empezar a animarse.
Verse# Amount of time to pause before the animation starts. @editable_number(float): ToolTip := SpeedTip MinValue := option{0.0} var InitialPauseSeconds<public>:float = 10.0Un
keyframed_movement_playback_modeeditable denominadoAnimationPlaybackMode. Este es el modo de animación para la animación de la entidad. Ajústalo enloop_keyframed_movement_playback_mode. Esto significa que, de forma predeterminada, cuando se complete la animación, la entidad hará un bucle y comenzará su animación de nuevo desde el principio.
Verse# The playback mode used by this animation. @editable: ToolTip := PlaybackModeTip var PlaybackMode<public>:keyframed_movement_playback_mode = loop_keyframed_movement_playback_mode{}Guarda el código y compílalo.
Cómo dividir animaciones en segmentos
Para construir animaciones en código, vas a utilizar fotogramas clave. Las animaciones se crean a partir de uno o varios fotogramas clave, y cada fotograma clave especifica los valores de un objeto en determinados puntos de la animación. Al construir una animación mediante fotogramas clave, puedes especificar múltiples puntos para que el elemento se mueva, gire o incluso se escale.
keyframed_movement_component utiliza keyframed_movement_delta como tipo de fotograma clave. Estos deltas de movimiento tienen tres valores:
Transform: especifica los cambios en la transformación relativos al fotograma clave anterior.Duración: la cantidad de tiempo en segundos que tarda el fotograma clave.Easing: la función de aceleración que se utilizará al reproducir este fotograma clave.
Como keyframed_movement_component toma una matriz de fotogramas clave y luego los reproduce, tienes que proporcionar todos los fotogramas clave a la vez para cada animación que quieras reproducir. dos formas:
Podrías compilar una matriz de varios fotogramas clave en el código, luego pasarla al componente de movimiento con fotogramas clave y reproducir una sola animación.
Puedes construir varias matrices que contengan un único fotograma clave y pasarlas individualmente al componente de movimiento con fotogramas clave para reproducir varias animaciones en secuencia.
Ambas opciones tienen sus ventajas y desventajas. Las matrices de fotogramas clave individuales te permiten realizar más fácilmente operaciones entre fotogramas clave, pero su gestión requiere más código. Construir todos los fotogramas clave a la vez facilita la gestión, pero es más difícil realizar operaciones mientras se reproduce la animación. Tanto el tutorial Animación del movimiento de los elementos como este cubren el primer enfoque, pero también se proporcionará una implementación del segundo enfoque.
Para compilar fotogramas clave individuales en el código, vas a definir una clase segment. Cada segmento representará un fotograma clave utilizado por el componente keyframed_movement_component que puedes compilar desde el editor. También podrás incluir datos adicionales, como el tiempo de espera entre cada fotograma clave. Sigue los pasos que se indican a continuación para construir tu clase de segmento.
Añade una nueva clase denominada
segmenta tu archivoanimate_to_targets_component.verse.Añade el especificador<concrete>para que esta clase pueda utilizarse como valor@editable.Verse# Defines a segment of animation, which includes a starting position, animation speed and duration, and easing function. # Each segment acts as a single animation, and multiple segments make up an animation sequence. segment<public> := class<concrete>:Añade los siguientes campos a la definición de tu clase de segmento:
Una opción de
entityeditable denominadaSegmentStartPosition. Esta entidad actuará como referencia para la posición en el mundo desde la que la entidad debe empezar a animarse.
Verse# An entity that represents the starting position of this entity during the animation segement. @editable: ToolTip := SourceTip SegmentStartPosition:?entity = falseUn
floateditable denominadoAnimationDuration. Este es el tiempo que tardará en reproducirse este segmento de animación. Ajústalo a2.0para que cada segmento de la animación tarde 2 segundos en reproducirse.
Verse# The duration of the animation segment. @editable: ToolTip := AnimationDurationTip AnimationDuration:float = 2.0Una opción editable de la función de aceleración denominada
EasingFunction. Esta es la función de aceleración que se utiliza durante este segmento de la animación.
Verse# The easing function to use during this segment of animation. @editable: ToolTip := EasingFunctionTip EasingFunction:?easing_function = falseCada función de aceleración se define mediante una curva cúbica de Bézier, que consta de cuatro números que crean el tipo de función de aceleración que utiliza la animación. Por ejemplo, los parámetros para una curva de entrada lenta hacen que la animación sea más lenta al principio y se acelere después.
Los parámetros para una curva lineal hacen que la animación se reproduzca a una velocidad constante. Puedes definir estos valores para crear tus propias curvas de animación personalizadas, pero no es necesario en este ejemplo, ya que utilizarás los definidos en el módulo
KeyframedMovement.Una opción de float editable denominada
PauseSeconds. Esta es la cantidad de tiempo en segundos para hacer una pausa antes de iniciar este segmento de animación. Imagínatelo como la cantidad de tiempo que una entidad se detiene antes de moverse desde cada punto de su ruta.
Verse# The number of seconds to pause before starting this animation segment. @editable: ToolTip := PauseTip PauseSeconds:?float = falseEn la definición de tu clase
animate_to_targets_component, añade los siguientes campos:Una matriz editable de
segmentcon el nombre Segments. Esto hará referencia a cada segmento de animación que compone la animación general que recorre tu entidad.
Verse# Segments of the keyframed movement animation. @editable: ToolTip := SegmentsTip var Segments<private>:[]segment = array{}Una
función de aceleracióneditable denominadaDefaultEasingFunction. Si un segmento de animación no especifica una función de aceleración, esta será la que se utilice por defecto. Ajústalo aease_in_out_cubic_bezier_easing_function.
Verse# Movement easing function between two targets @editable: ToolTip := DefaultEasingFunctionTip var DefaultEasingFunction<public>:easing_function = ease_in_out_cubic_bezier_easing_function{}Guarda el código y compílalo. En el editor, deberías ver aparecer la matriz
Segmentsen las entidades que tienen adjunto el componente animate_to_targets_.
Cómo crear fotogramas clave con código
Con tu clase de segmento completada, es hora de construir los fotogramas clave que definen tus segmentos. Crea fotogramas clave individualmente y añádelos a una matriz; luego pasa la matriz a tu keyframed_movement_component. Esto requerirá algunas operaciones matemáticas que definirás ahora.
Como las operaciones matemáticas son útiles en una gran variedad de situaciones, conviene colocar esa lógica en un archivo de utilidad para acceder a ella desde cualquiera de tus componentes de Verse. Para conocer más prácticas recomendadas de Verse a la hora de trabajar con entidades, consulta Cómo crear tu propio componente de Verse. Sigue estos pasos para crear tu archivo de utilidad:
Crea un nuevo módulo en tu archivo
animate_to_targets.versenombradoUtilidades. Esto almacenará la lógica común que utilizarás en todo tu proyecto.Verse# Module containing utility functions. Utilities<public> := module:Añade un nuevo alias de tipo
vector3nombradoVectorOnesa tu módulo Utilidades que cree unvector3dondeIzquierda,ArribayAdelanteestén configurados en1.0. Más adelante utilizarás este vector para simplificar algunas operaciones matemáticas, por lo que definir un alias de tipo para él significa que no tienes que escribirvector3{Left := 1.0, Up := 1.0, Forward := 1.0}repetidamente. Como importaste ambos/Verse.org/SpatialMathy/UnrealEngine.com/Temporary/SpatialMathmódulos, deberá especificar que se trata de un/Verse.org/SpatialMathvector3ya que ambos módulos incluyen una definición para él.Verse# Utility function for the identity of component-wise vector multiplication. VectorOnes<public>()<transacts>:(/Verse.org/SpatialMath:)vector3 = (/Verse.org/SpatialMath:)vector3{Left := 1.0, Up := 1.0, Forward := 1.0}Añade una nueva función denominada
GetDeltaTransform()a tu móduloUtilities. Esta función calculará la diferencia entre dos transformaciones y devolverá el delta. Añade el modificador<transacts>a esta función para permitir que se revierta. Especifique/Verse.org/SpatialMathcomo módulo para cadatransformar, ya que calcularás la diferencia entre las transformaciones de Entidad.Verse# Get the delta transform between two given transforms. GetDeltaTransform<public>(TransformOne:(/Verse.org/SpatialMath:)transform, TransformTwo:(/Verse.org/SpatialMath:)transform)<transacts>:(/Verse.org/SpatialMath:)transform=En
GetDeltaTransform, inicializa/Verse.org/SpatialMathcomo nuevatransformación. Establece latraslacióncomo la diferencia entre la traslación de cada transformación. Establece larotacióncomo el resultado de la llamada aMakeComponentWiseDeltaRotation(). Como esta función se encuentra en el módulo/UnrealEngine.com/Temporary/SpatialMathdeberás convertir de rotaciones de/Verse.org/SpatialMatha rotaciones de/UnrealEngine.com/Temporary/SpatialMath. Puedes hacerlo utilizando la funciónFromRotation(). Llama aMakeComponentWiseDeltaRotation()pasando la rotación de cada transformación después de convertirla conFromRotation(). A continuación, convierte el resultado de esta llamada de función medianteFromRotation()de nuevo para convertirlo de nuevo en/Verse.org/SpatialMathrotación. Por último, establece Scale en el resultado de añadirVectorOnesa la diferencia entre la primera y la segunda escala dividida por la primera escala. Esto garantiza que tu entidad se escala correctamente durante la animación. Tu funciónGetDeltaTransform()completa debería tener el siguiente aspecto:Verse# Get the delta transform between two given transforms. GetDeltaTransform<public>(TransformOne:(/Verse.org/SpatialMath:)transform, TransformTwo:(/Verse.org/SpatialMath:)transform)<transacts>:(/Verse.org/SpatialMath:)transform= (/Verse.org/SpatialMath:)transform: Translation := TransformTwo.Translation - TransformOne.Translation Rotation := FromRotation(MakeComponentWiseDeltaRotation( FromRotation(TransformTwo.Rotation), FromRotation(TransformOne.Rotation))) Scale := VectorOnes() + ((TransformTwo.Scale - TransformOne.Scale) / TransformOne.Scale)Por último, añade una función nombrada
TryGetvalueOrDefault()a tu móduloUtilidadesy añádele el modificador<transacts>. Esta función toma un valor deoptionde algún tipo y un valor predeterminado del mismo tipo y devuelve el valor predeterminado o el elemento dentro deValuesi existe. Esto es útil cuando quieres comprobar si un valor de una clase está realmente inicializado, y garantiza que devolverás algún valor en caso contrario. Dentro deTryGetValueOrDefault(), comprueba siValuecontiene un valor y devuélvelo. En caso contrario, devuelveDefault. El móduloUtilitiescompleto y la funciónTryGetValurOrDefault()deberían tener el siguiente aspecto:Verse# Module containing utility functions. Utilities<public> := module: # Utility function for the identity of component-wise vector multiplication. VectorOnes<public>()<transacts>:(/Verse.org/SpatialMath:)vector3 = (/Verse.org/SpatialMath:)vector3{Left := 1.0, Up := 1.0, Forward := 1.0} # Get the delta transform between two given transforms. GetDeltaTransform<public>(TransformOne:(/Verse.org/SpatialMath:)transform, TransformTwo:(/Verse.org/SpatialMath:)transform)<transacts>:(/Verse.org/SpatialMath:)transform=
Una vez definidas las matemáticas, ya puedes crear tus fotogramas clave a partir del código.
Sigue estos pasos para crear tus funciones de creación de fotogramas clave:
Añade una nueva función denominada
ConstuctKeyframe()a la definición de tu claseanimate_to_targets. Esta función toma una entidad de origen, una de destino, una función de aceleración opcional y una duración. Incluso devuelve una matriz dekeyframed_movements_delta.Verse# Construct a single keyframe that animates between the Source and Destination entity using the given easing function over a set duration. ConstructKeyframe<private>(Source:entity, Destination:entity, Easing:?easing_function, Duration:float)<transacts><decides>:[]keyframed_movement_delta=En
ConstructKeyframe(), primero obtén las transformaciones de las entidadesFuenteyDestinollamando aGetGlobalTransform().Verse# Construct a single keyframe which animates between the Source and Destination entity using the given easing function over a set duration. ConstructKeyframe<private>(Source:entity, Destination:entity, EasingFunction:easing_function, Duration:float)<transacts><decides>:[]keyframed_movement_delta= var SourceTransform:(/Verse.org/SpatialMath:)transform = Source.GetGlobalTransform() var DestinationTransform:(/Verse.org/SpatialMath:)transform = Destination.GetGlobalTransform()Inicializa una matriz con un único miembro de
keyframed_movement_delta. EstableceTransformen el resultado de llamar aGetDeltaTransform()pasando las transformaciones de origen y de destino, y estableceDurationeEasingen los valores pasados a esta función. Tu funciónConstructKeyframe()completa debería tener el siguiente aspecto:Verse# Construct a single keyframe which animates between the Source and Destination entity using the given easing function over a set duration. ConstructKeyframe<private>(Source:entity, Destination:entity, EasingFunction:easing_function, Duration:float)<transacts><decides>:[]keyframed_movement_delta= var SourceTransform:(/Verse.org/SpatialMath:)transform = Source.GetGlobalTransform() var DestinationTransform:(/Verse.org/SpatialMath:)transform = Destination.GetGlobalTransform() array: keyframed_movement_delta: Transform := Utilities.GetDeltaTransform(SourceTransform, DestinationTransform) Duration := Duration Easing := EasingFunction
Esta función construye fotogramas clave individuales, pero necesitarás más lógica para crear animaciones completas.
Añade una nueva función denominada
ConstructAndPlayAnimations()a la definición de tu claseanimate_to_targets. Esta función toma una matriz de segmentos y el modo de reproducción de la animación y los utiliza para crear y reproducir una animación completa. Añade el modificador<suspends>a esta función para permitir que se ejecute de forma asíncrona.Verse# Construct and play an animation from an array of animation segments. ConstructAndPlayAnimations<private>(InSegments:[]segment, AnimationPlayback:keyframed_movement_playback_mode)<suspends>:void=En
ConstructAndPlayAnimations(), define una nueva variablelogicdenominadaShouldBreakOute inicialízala afalse. Dados los tres modos de reproducción de movimiento con fotogramas clave, tendrás que gestionar cada uno individualmente. Para ello, utilizarás una expresiónlooppara construir continuamente animaciones y gestionar los modos de bucle continuo, pero el modo de una toma debe salir del bucle en la primera iteración. Comprueba si el modo de reproducción de la animación es de una sola toma y, en caso afirmativo, activaShouldBreakOut.Verse# Construct and play an animation from an array of animation segments. ConstructAndPlayAnimations<private>(InSegments:[]segment, AnimationPlayback:keyframed_movement_playback_mode)<suspends>:void= var ShouldBreakOut:logic = false # If this is a oneshot animation, break out of loop after it plays once. if (oneshot := oneshot_keyframed_movement_playback_mode[AnimationPlayback]): set ShouldBreakOut = trueA continuación, en una expresión
if, obtén elkeyframed_movement_componentde la entidad en una variableKeyframedMovementComponent. A continuación, obtén la transformación inicial de la animación en una variable denominadaStartingTransformobteniendo el primer elemento de la matrizInSegmentsy, a continuación, su transformación global.Verse# Position this entity in the correct starting position. if: KeyframedMovementComponent := Entity.GetComponent[keyframed_movement_component] StartingTransform := FirstSegment := InSegments[0].SegmentStartPosition?.GetGlobalTransform()Por último, coloca la entidad en su ubicación inicial estableciendo su transformación global en la transformación inicial, y desactívala durante los
InitialPauseSecondsantes de que se reproduzca la animación.Verse# Position this entity in the correct starting position. if: KeyframedMovementComponent := Entity.GetComponent[keyframed_movement_component] StartingTransform := FirstSegment := InSegments[0].SegmentStartPosition?.GetGlobalTransform() then: Entity.SetGlobalTransform(StartingTransform) # Sleep for initial pause. Sleep(InitialPauseSeconds)Ahora es el momento de construir la matriz de fotogramas clave a partir de la cual construirás la animación. Primero, inicializa una nueva matriz de variables de
keyframed_movement_deltadenominadaKeyframes. A continuación, en una expresiónfor, itera por cada segmento de la matrizInSegmentsobteniendo tanto el segmento como su índice en una variable local nombradaIndex.Verse# Build the array of keyframes to play the animation from. var Keyframes:[]keyframed_movement_delta = array{} for: Index -> Segment:InSegments SourceEntity := Segment.SegmentStartPosition? DestinationEntity := InSegments[Index + 1].SegmentStartPosition?Ahora obtén la función de aceleración utilizada para este fotograma clave en una variable local llamada
Easingllamando aTryGetValueOrDefault()y pasándoleSegment.EasingFunctionyDefaultEasingFunction. Además, obtén la duración del segmento de animación en una variable localDurationdeSegment.AnimationDuration. Con todos los valores establecidos, en una expresiónif, construye el fotograma clave pasando cada valor aConstructKeyframe[]y añade el resultado a la matriz defotogramas clave. Cuando hayas construido todos tus fotogramas clave, establece la matriz de fotogramas clave en el componente de movimiento con fotogramas clave llamando aSetKeyframes()pasándole la matrizKeyframesy elPlaybackMode.Verse# Build the array of keyframes to play the animation from. var Keyframes:[]keyframed_movement_delta = array{} for: Index -> Segment:InSegments SourceEntity := Segment.SegmentStartPosition? DestinationEntity := InSegments[Index + 1].SegmentStartPosition? do: Easing := Utilities.TryGetValueOrDefault(Segment.EasingFunction, DefaultEasingFunction) Duration := Segment.AnimationDuration # Construct each keyframe and add it to the array.Con tu matriz de fotogramas clave establecida, es hora de empezar a reproducirlos. Tu animación debe ejecutarse en loop, pero debe detenerse después de la primera iteración si el modo de animación está establecido en una sola toma. También tiene que gestionar la pausa en cada fotograma clave si el segmento tiene
PauseSeconds. Para controlarlo, configura una expresión debuclecon una expresiónforen su interior. En la expresiónfor, itera por cada fotograma clave de la matrizKeyframes, obteniendo además el índice de cada fotograma clave en una variableKeyframeIndex.VerseKeyframedMovementComponent.SetKeyframes(Keyframes, PlaybackMode) # Loop playing the animation from the keyframed_movement_component, pausing at each # keyframe for a specified duration. Will break out of the loop if the animation mode # is set to oneshot. loop: for(KeyframeIndex -> Keyframe:Keyframes):Dentro de la expresión
for, obtén el segmento asociado a este fotograma clave indexando en la matrizInSegmentsmedianteKeyframeIndex. Luego, si el segmento tienePauseSeconds, llama aSleep()para esa cantidad de tiempo. Después, llama aKeyframedMovementComponent.Play(), espera elKeyframeReachedEventy llama aKeyframedMovementComponent.Pause(). Lo que hace es pausar la animación en cada fotograma clave durante una cantidad de tiempo dePauseSeconds, antes de reproducirse y esperar al siguiente fotograma clave y volver a pausar.Por último, al final de la expresión loop, comprueba si ShouldBreakOut es true y, en caso afirmativo, sal del bucle.Verse# Loop playing the animation from the keyframed_movement_component, pausing at each # keyframe for a specified duration. Will break out of the loop if the animation mode # is set to oneshot. loop: for(KeyframeIndex -> Keyframe:Keyframes): if: SegmentReached := InSegments[KeyframeIndex] then: Sleep(SegmentReached.PauseSeconds) KeyframedMovementComponent.Play()Tu función
ContstructAndPlayAnimations()completa debería tener el siguiente aspecto:Verse# Construct and play an animation from an array of animation segments. ConstructAndPlayAnimations<private>(InSegments:[]segment, AnimationPlayback:keyframed_movement_playback_mode)<suspends>:void= var ShouldBreakOut:logic = false # If this is a oneshot animation, break out of loop after it plays once. if (oneshot := oneshot_keyframed_movement_playback_mode[AnimationPlayback]): set ShouldBreakOut = true # Position this entity in the correct starting position.
En el siguiente paso, crearás un prefabricado de tu entidad de animación y una instancia en el nivel.