MoveTo is severely Buggy [Fixed Fortnite v25.0]


UPDATE: Fixed June 6, 2023 in UEFN as of Fortnite v25.0

MoveTo() now works as expected and a hack to prevent snapping is no longer necessary.

You can switch the call to MoveToPositionalExpected() from MoveToPositional() in FollowPositional() to see nice smooth movement without visual hitches.


If MoveTo() is canceled it seems to have a glitch where it may reset a prop to its starting position.

We’ve identified the likely cause of the MoveTo() glitch and are working on a fix for a future update. [I’ll update this thread.]

In the meantime here is an example that mostly works around the issue. There is still a little bit of jerkiness (due to a two frame pause) though it is fairly useable.

The work around hack:

  • call MoveTo() with whatever target and time (potentially using distance of object to target) makes sense
  • note the transform of the moving object before its MoveTo() is canceled
  • after MoveTo() is canceled (with a race) call TeleportTo() with the saved transform
  • wait two frames (Sleep(0.0) twice)
  • recalc move to target and call MoveTo() again
  • repeat

It describes a prop_follow_device that can be placed in the world and it will create the prop asset that is associated with it. I used PPID_CP_Birthday_Balloons_4b0b09c7.

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Fortnite.com/Game }
using { /Fortnite.com/Characters }


#===========================================================
# Creates a prop and moves it alongside player
prop_follow_device := class(creative_device):

    @editable
    var PropAsset : creative_prop_asset = DefaultCreativePropAsset

    OnBegin<override>()<suspends>:void=
        Sleep(2.0)

        # Assume zeroeth player
        if (Target := PlayerAsPositional[0]):
            Print("Creating prop...")

            # Three meters in front of player
            TargetXform  := Target.GetTransform()
            PropSpawnPos := TargetXform.Translation + TargetXform.Rotation.GetLocalForward() * 300.0
            PropInfo     := SpawnProp(PropAsset, PropSpawnPos, IdentityRotation())

            if (Prop := PropInfo(0)?):
                Print("Following prop is active!")
                # Follow 2 meters to the right
                Right := vector3{X:=0.0, Y:=1.0, Z:=0.0}
                Prop.FollowPositional(Target, Right * 200.0)
            else:
                Print("Invalid spawn info for prop!")

    #===========================================================
    # Gets the specified player index as a positional object
    PlayerAsPositional(PlayerIdx : int)<transacts><decides>:positional =
        GetPlayspace().GetPlayers()[PlayerIdx].GetFortCharacter[]

    #===========================================================
    # Have this prop follow the specified positional target at the specified offset
    # [Note that this is an "extension function" that allows calling it as if it were a member method.]
    (Prop : creative_prop).FollowPositional(Target : positional, Offset : vector3)<suspends>:void =
        loop:
            Print("Moving...")
            Prop.MoveToPositional(Target, Offset)
            Print("Arrived. Waiting...")
            # Wait a bit until moving again - could just be 0.0 or passed in
            Sleep(1.0)

    #===========================================================
    # Move to specified offset of positional target adjusting over time as needed
    # *** Partly corrects glitch in MoveTo() that occasionally resets position
    (Prop : creative_prop).MoveToPositional(Target : positional, Offset : vector3)<suspends>:void =
        TargetRotation := IdentityRotation()  # [Could specify a target rotation or make relative to Target]
        
        loop:
            TargetXform := Target.GetTransform()
            TargetPos := TargetXform.Translation
            var PropXform : transform = transform{}

            Arrived := race:
                block:
                    MovePos := TargetPos + TargetXform.Rotation.RotateVector(Offset)
                    # The farther apart the longer the time to get there [could pass in adjustment]
                    TimeToMove := Distance(TargetPos, Prop.GetTransform().Translation) * 0.005
                    # If MoveTo() is canceled before arrival (such as with the nesting `race` it seems
                    # to reset to the prop's original position
                    Prop.MoveTo(MovePos, TargetRotation, TimeToMove)
                    # The MoveTo() arrival criteria is quite picky so it may never complete
                    true
                block:
                    # Update move to location occasionally [could pass in value]
                    Sleep(0.5)
                    # Note current prop xform before MoveTo is canceled by race
                    set PropXform = Prop.GetTransform()
                    false

            if:
                not Arrived?
                # Teleport to position prior to adjustment - fixes occasional glitching of MoveTo()
                Prop.TeleportTo[PropXform]
            then:
                # Wait 2 update frames before calling MoveTo again - just 1 doesn't seem to always be enough
                Sleep(0.0)
                Sleep(0.0)
            else:
                # Arrived
                break


    #===========================================================
    # Move to specified offset of positional target adjusting over time as needed
    # - uses code that would be expected if MoveTo() did not have a glitch
    (Prop : creative_prop).MoveToPositionalExpected(Target : positional, Offset : vector3)<suspends>:void =
        TargetRotation := IdentityRotation()  # [Could specify a target rotation or make relative to Target]
        
        loop:
            TargetXform := Target.GetTransform()
            TargetPos := TargetXform.Translation

            Arrived := race:
                block:
                    MovePos := TargetPos + TargetXform.Rotation.RotateVector(Offset)
                    # The farther apart the longer the time to get there [could pass in adjustment]
                    TimeToMove := Distance(TargetPos, Prop.GetTransform().Translation) * 0.005
                    Prop.MoveTo(MovePos, TargetRotation, TimeToMove)
                    true   # Arrived
                block:
                    # Update move to location occasionally [could pass in value]
                    Sleep(0.5)
                    false  # Not arrived

            if (Arrived?):
                break
9 Likes