Play an Animation on a Player's Character with Verse
Tutorial intermediate compiles

Play an Animation on a Player's Character with Verse

Play an Animation Directly on a Player's Character

You want your player to take a bow, throw a punch, or strike a victory pose — on their own character, the moment something happens in your game. Until recently that was a fight: you'd spawn a hidden NPC, swap meshes, or fake it with an Animated Mesh device standing in for the player. The /Fortnite.com/Animation/PlayAnimation module (added in the recent 41.x update) makes it a straight line — get an animation controller from any fort_character and call Play.

A player character mid-emote on a stage, a button panel facing them

The three-hop chain

Every use of this API is the same little chain: an agent gives you a character, the character gives you a controller, and the controller gives you an instance you can steer.

# The whole API in three hops: agent -> character -> controller -> instance.
if:
    Character := Presser.GetFortCharacter[]          # agent -> fort_character
    Controller := Character.GetPlayAnimationController[]  # -> play_animation_controller
then:
    Instance := Controller.Play(MyAnim)              # -> play_animation_instance

The two extension functions are both failable (<decides> in the digest), so you call them with brackets [] inside an ifGetFortCharacter[] (the player might not have a living character right now) and GetPlayAnimationController[]. The real signature, straight from Fortnite.digest.verse:

(InCharacter : fort_character).GetPlayAnimationController()<transacts><decides> : play_animation_controller

What you can animate

GetPlayAnimationController is an extension on fort_character — so it works on any character you can get one from: the player who pressed a button, every player in GetPlayers(), or an NPC you spawned. The clip itself is an animation_sequence asset (from /Verse.org/Assets) that you import in the editor and select on the device. You can't build one in code — its constructor is epic_internal.

A play / stop button device

This device plays an editor-chosen animation on whoever presses Play, and stops it with Stop. It is compile-verified in UEFN 5.8 — the whole project builds clean with it installed.

using { /Fortnite.com/Characters }
using { /Fortnite.com/Animation/PlayAnimation }
using { /Fortnite.com/Devices }
using { /Verse.org/Assets }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

play_emote_on_press_device := class(creative_device):

    # You CANNOT construct an animation_sequence in Verse (its constructor is
    # epic_internal). It is an editor-imported asset, so the slot is an OPTION:
    # pick the asset in the device's Details panel, unwrap it with `EmoteAnim?`.
    @editable
    EmoteAnim : ?animation_sequence = false

    @editable
    PlayButton : button_device = button_device{}

    @editable
    StopButton : button_device = button_device{}

    # Keep the latest instance so the Stop button can cancel it.
    var CurrentAnim : ?play_animation_instance = false

    OnBegin<override>()<suspends> : void =
        PlayButton.InteractedWithEvent.Subscribe(OnPlayPressed)
        StopButton.InteractedWithEvent.Subscribe(OnStopPressed)

    OnPlayPressed(Presser : agent) : void =
        # GetFortCharacter and GetPlayAnimationController both <decide>, so they
        # share one `if` with the option unwrap -- all three must succeed.
        if:
            Anim := EmoteAnim?
            Character := Presser.GetFortCharacter[]
            Controller := Character.GetPlayAnimationController[]
        then:
            # Play() returns the instance IMMEDIATELY -- it does not suspend.
            Instance := Controller.Play(
                Anim,
                ?PlayRate := 1.0,      # 1.0 = authored speed, 0.5 = slow-mo
                ?BlendInTime := 0.2,   # ease in over 0.2s
                ?BlendOutTime := 0.2)  # ease out over 0.2s
            set CurrentAnim = option{Instance}
            Print("Playing animation on the presser's character")

    OnStopPressed(Presser : agent) : void =
        if (Instance := CurrentAnim?):
            Instance.Stop()   # blends out and fires InterruptedEvent

Two details worth burning in:

  • The editable is ?animation_sequence, not animation_sequence. Because the constructor is epic_internal, animation_sequence{} does not compile (Invalid access of epic_internal class constructor). Declare the slot as an option defaulting to false, assign the asset in the Details panel, and unwrap with Anim?. This is the single most common mistake with this API.
  • Play() does not suspend. It returns a play_animation_instance immediately and the clip plays on its own. That's why OnPlayPressed doesn't need <suspends> — and why you keep the returned instance around if you want to stop it later.

The signatures (from the digest)

Play(AnimationSequence : animation_sequence, ?PlayRate : float, ?PlayCount : float,
     ?StartPositionSeconds : float, ?BlendInTime : float, ?BlendOutTime : float)
  : play_animation_instance

PlayAndAwait(AnimationSequence : animation_sequence, ?PlayRate : float, ?PlayCount : float,
     ?StartPositionSeconds : float, ?BlendInTime : float, ?BlendOutTime : float)<suspends>
  : play_animation_result

The optional params are your control panel: ?PlayRate retimes the clip (0.5 slow-mo, 2.0 fast), ?PlayCount repeats it, ?StartPositionSeconds starts partway in, and ?BlendInTime / ?BlendOutTime ease the transition so it doesn't pop.

Driving the instance

Play() hands you a play_animation_instance — a remote control for that one playback.

# The instance returned by Play() is a remote control for that one playback.
Instance := Controller.Play(MyAnim)

Instance.Stop()                       # stop now (blends out)
State := Instance.GetState()          # play_animation_state: Playing, BlendingIn,
                                      # BlendingOut, Completed, Stopped, ...
if (Instance.IsPlaying[]):            # <decides>: succeeds while it is running
    Print("still going")

# React to lifecycle events without polling:
Instance.CompletedEvent.Subscribe(OnDone)
Instance.InterruptedEvent.Subscribe(OnCut)

GetState() returns a play_animation_state (Playing, BlendingIn, BlendingOut, Completed, Interrupted, Stopped, Error). IsPlaying[] is the failable shortcut for "is it still running?". And the CompletedEvent / InterruptedEvent / BlendedInEvent / BlendingOutEvent listenables let you react to the lifecycle without polling.

Waiting for it to finish

When you want code to run after the animation — a cutscene beat, a follow-up emote — use PlayAndAwait. It suspends until the clip ends and returns a play_animation_result telling you whether it Completed, was Interrupted, or hit an Error. Because it suspends, call it from a <suspends> function (here, one we spawn so the button handler stays non-suspending):

# Play() returns instantly. PlayAndAwait() SUSPENDS until the clip ends and
# returns a play_animation_result enum telling you HOW it ended.
PlayThenReact(Presser : agent)<suspends> : void =
    if:
        Sequence := MyAnim?
        Character := Presser.GetFortCharacter[]
        Controller := Character.GetPlayAnimationController[]
    then:
        Result := Controller.PlayAndAwait(Sequence, ?PlayCount := 1.0)
        case (Result):
            play_animation_result.Completed   => Print("Finished cleanly")
            play_animation_result.Interrupted => Print("Something interrupted it")
            play_animation_result.Error       => Print("Playback errored")

You can get the same "wait" from a plain Play() too: keep the instance and call Instance.Await() — it also <suspends> and returns the same play_animation_result.

How this replaces the old hacks

  • No more proxy NPC. You no longer spawn an invisible character just to host an animation — GetPlayAnimationController works on the real player's fort_character.
  • No more Animated Mesh stand-in for player emotes. That device is still great for scenery (a dancing statue, a swinging gate), but for animating the player you now go straight to the character.
  • You get a handle. The old workarounds were fire-and-forget. play_animation_instance gives you Stop, GetState, Await, and lifecycle events — real control over a single playback.

Recap

  • The chain is agent → GetFortCharacter[]GetPlayAnimationController[]Play(...); both getters are <decides>, so use [] inside an if.
  • Play() returns immediately; PlayAndAwait() <suspends> and returns a play_animation_result.
  • Declare the editable clip as ?animation_sequence = false — you can't construct one in Verse.
  • The returned play_animation_instance gives you Stop, GetState, IsPlaying[], Await, and CompletedEvent / InterruptedEvent.
  • Tune playback with ?PlayRate, ?PlayCount, ?StartPositionSeconds, ?BlendInTime, ?BlendOutTime.

References

  • https://dev.epicgames.com/documentation/fortnite/verse-api/fortnitedotcom/animation/playanimation
  • https://dev.epicgames.com/documentation/en-us/uefn/verse-api/fortnitedotcom/animation/playanimation/play_animation_controller
  • https://dev.epicgames.com/documentation/en-us/uefn/verse-api/fortnitedotcom/animation/playanimation/play_animation_controller/play
  • https://dev.epicgames.com/documentation/en-us/uefn/verse-api/fortnitedotcom/animation/playanimation/play_animation_controller/playandawait
  • https://dev.epicgames.com/documentation/en-us/uefn/verse-api/fortnitedotcom/animation/playanimation/getplayanimationcontroller

Verse source files

Check your understanding

4 quick questions. Pick an answer to see if you've got it.

Score 70%+ to master this lesson and earn XP
  1. 1.

  2. 2.

  3. 3.

  4. 4.

Source
Verse Island

© Biloxi Studios Inc. — original Verse Island content.

Sources & licensing

Original tutorial generated by Verse Island from the indexed Verse/UEFN knowledge base, with references to the Epic Games sources above. Code is validated against the knowledge base.

Comments

    Sign in to vote, comment, or suggest an edit. Sign in