MoveTo is severely Buggy [Fixed Fortnite v25.0]

When you use MoveTo in a function that loops, the prop being moved snaps back to the start position for maybe 1 frame, once it reaches it’s destination, and I can’t seem to get it to not do that.

7 Likes

Glad I’m not the only one encountering this. Though I have to wonder why this issue isn’t more active… what are we doing wrong? Haha.

For what it’s worth, it’s only an issue if your MoveTo speed is lower than the average tickrate (i.e. 0.333333), at least in my experience. Yes, even if you have a queue of MoveTo commands in it’s own spawned task; it just goes whacko if you try to move anything at a rate quicker than the game tick rate.

I’ve since given up on trying to make my own NPC’s / Bots and such. Until they fix these timing/sync issues, UEFN is severely limited in what kinds of games can be made IMO.

This wouldn’t be an issue if we could cancel/interrupt a running task, though (along with the MoveTo command that’s running inside the Task, I mean) - then we could just “steer” our props when their direction is updated, for example. REALLY hope they give us more than the basically-useless “Await” function on Tasks. Also the ability to assign the Task to a class field, so we can cancel it from a different place.

That would make the “I want to update MoveTo faster than the tickrate” issue redundant, and it’s probably easier to do than making MoveTo “more optimized”. Plus it benefits all async stuff.

1 Like

From my testing, you can avoid this using the move_to_result enum.

By default, a MoveTo() call will return a move_to_result enum which contains the following.

You can then do something along the lines of testing that the move_to_result has the DestinationReached member, then call the MoveToAgain function recursively.

For example:

MoveNPC()<suspends> : void =
    Result : move_to_result = Prop.MoveTo(Position, Rotation, Time)
    if (Result = move_to_result.DestinationReached):
         MoveNPC()

Thanks for that info, I’m gonna try this out. I’ve actually tried using the Result before, and I found that sometimes the result was WillNotReach at random - my guess was because it was too small a unit/number or something like that. In these cases I tried to do a final TeleportTo after MoveTo, which helped a different type of jerkiness. So that, in combination with what you suggested, is what I’m going to look into right now. Thanks!

EDIT: It may have helped in my situation, but didn’t completely eliminate it. But now that I’m back fiddling with it, I noticed that the “ghosting” is actually ahead of my movement - like for one frame it is actually teleporting to the destination first, then frame after it start the actual MoveTo from it’s existing location…

…so I managed to fix the “ghost teleport frame” completely by telling my prop to TeleportTo its current position before every MoveTo command. But with a MoveTo speed of 0.1, and the fact that I’m sending it position updates every tick, still results in general performance issues. Choppy, but at least it’s not doing the ghost teleport frame thing now.

Again, this doesn’t happen if I set my MoveTo speed to > 0.34 (longer than 1 tick) - the performance is perfect.

So maybe we’re just asking too much of Verse? That is, assuming the OP is doing the same thing as me - recursive/repeated/looped MoveTo commands that are shorter than a tick.

I think what might be happening in my case is that I’m re-using vector3’s based on the props original location instead of copying the values. Either that or Verse itself has a problem with “not getting the actual latest variables”.

Or maybe I should be changing my code to have a or effect on a method somewhere, and that’s why the vector3’s seem to be “messing up sometimes”. I admit I don’t know how/when/why to use these two effects in practice.

I’ve spent a lot of time on this haha, now that it’s out in the open on the forums I’m keen to hear any input and share my own findings.

EDIT2: Turns out the pre-teleport didn’t solve it, after all, it just made it even less likely. Hmmm.

1 Like

So, do you guys have any better experience since the latest update? I have noticed that it’s better, but not completely fixed - the “flicker/teleport” type effect that the OP references is still present, but seems to be very rare now. Still, it happens… though I’m yet to try my previously-not-as-good approaches to this problem, maybe the issue is in my current movement code… will look into it when I have time, though I’m more focused on another project that’s doesn’t depend so much on this for now.

Thanks for the report! I have an example from another user and consulting the experts to determine the core issue.

I checked it out in one of my projects after the update. It seems like rotation flicker/teleport is fixed. I’m still seeing major issues with translation, although sometimes it looks mostly ok now for a few seconds at a time. My code is calling MoveTo every 2 frames.

I just tested my project where I tried to keep it really simple and it still flickers. It doesn’t feel as common but it’s frequent enough that anything built on it will look unprofessional.
Here’s the code I’m using for reference:

    # move the droid to hover over the player's right shoulder
    # turn the droid to face in the player direction (camera, not avatar)
    MoveTowards()<suspends>:void=
        Print("MoveTowards started")
        loop:
            if (Player := PlayerManager.GetCharacter[0]):

                PropTransform := Prop.GetTransform()
                PropPosition := PropTransform.Translation

                PlayTransform := Player.GetTransform()
                PlayPosition := PlayTransform.Translation
                PlayRotation := PlayTransform.Rotation

                OffsetPosition := PlayPosition + PlayRotation.RotateVector(vector3{ X:=0.0, Y:=70.0, Z:=60.0 })

                # move smoothly
                SpawnTask := spawn{ Prop.MoveTo(OffsetPosition, PlayRotation, 0.5) }
                # block until move is complete
                SpawnTask.Await()
            Sleep(0.0)

NOTE: the 0.5 MoveTo duration is much longer than my preference, I’d like it to be 0.1 but thought the longer duration might cause fewer problems.
I’ll quickly try it without the Await and using the conditional block instead… this was a last resort after the conditional didn’t work for me previously.

EDIT: that made it significantly worse with the conditional (containing just a Print on the success & failure) but it’s actually very good when relying on the MoveTo blocking alone with no other code. It looks a little bit jerky, but it’s not jumping to the wrong place or rotation.
I saw one instance where it got ‘ahead of itself’ which I assume is because the Verse code is running server-side and the client has predictive movement smoothing. I don’t know if that is actually fixable so long as the code is running on a remote server.
Same code as above but with the simpler line replacing the spawn/Await:

        MoveResult := Prop.MoveTo(OffsetPosition, PlayRotation, 0.5)

(MoveResult is now ignored and can probably be removed)

EDIT2: further testing shows that those results were a fluke and are not consistently reproducible. It still jumps ahead or teleports to the final destination. I tested with duration of 0.1 (appallingly bad), 0.3334 (less bad, but still unusable), and reverted to my original 0.5 (particularly bad when rotating… sometimes).
I more and more have the feeling that we’re seeing network effects, which would explain why it is so hard to get any consistent results in tests.

I’d like to second @CosmicDanAU suggestion that the real fix would be a better command. I’m not sure that a cancelable MoveTo would be the ideal solution, although it wouldn’t hurt. For my purposes the ability to lock items together then move one of them on a path relative to the other would be excellent. I intend to explore the animation path system today to see if that might be possible already - assuming I can attach to the player character.

I’ve taken a quick look at this and may have found the issue with MoveTo().
I’m experimenting with a work around until a full fix is available.
Recommendations soon - hopefully tomorrow…

2 Likes

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

Some other observations:

You can cancel any async expression if it loses out as a subexpression in a race - i.e. some sibling subexpression finishes before it does.

race:
    SomeAsyncCall()
    # Ten second timeout
    # [Could be anything that completes faster than the expression above]
    Sleep(10.0)

If you want to be able to cancel it from several different locations just use an event:

CancelEvent := event{}
race:
    SomeAsyncCall()
    CancelEvent.Await()

Other code elsewhere:

# Trigger winning of the above `race`
CancelEvent.Signal()

The same event Signal() could be in many different locations and multiple different locations can wait on the same event Await().

event is also parametric - in that it can have a result sent through a Signal() and received as a result at each Await().

IntEvent := event(int){}

sync:
    block:
        # Returns an int when signalled
        Result := IntEvent.Await()
        DoStuffWithInt(Result)
    block:
        # Also awaiting the same event
        OtherStuffWithInt(IntEvent.Await())
        LaterStuff()
    IntEvent.Signal(CalcIntAnswer())

Note that the above is essentially identical to:

Prop.MoveTo(OffsetPosition, PlayRotation, 0.5)

The only thing extra is getting a task object - though it was awaited on immediately after that so they are the same.

8 Likes

You legend!

Thanks so much for your input here. I actually had that same thought re: race last night in bed, though you’ve provided some very useful nuggets here. Much appreciated, thank you! And for addressing my slightly off-topic vents too - big “aha” moments here, you legend!

Now all we really need next (in regards to prop movement) is a generic physics object or something, so we can apply force + directional/steer vector to a prop :wink: perhaps that’s best suited to “custom vehicles”, however - I’m sure it’s somewhere on the radar already.

@InsanePete - just an FYI, using Print can cause issues at times - best to avoid it when using performance-sensitive code. If you really need some console output, look into how to use a Logger as it doesn’t have the same issue. I don’t think this is a bug FWIW, since I’ve seen similar behavior in other systems in the past (the fact that printing to screen is a bit expensive).

2 Likes

That’s a great bit of intel right there, I was wondering about that from the digest headers. It would be great to see that explained in the docs soon.

I second getting some basic movement physics up in here, impulses, velocity, acceleration, etc. It would be really nice for any number of cases. I don’t think I’ve ever used a MoveTo over time function to move something before, it’s a pretty awkward way to move game objects.

1 Like

Hello, thank you for the solution, its much appreciated, could you explain the meaning of the second block inside the race you have created, I have created my own version of the script with a set time for each moveTo and for specific 2 props and the race never reaches the second block. I still get a few moveTo bugs tho.
here is the code I wrote:

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

    MoveToChar()<suspends>:void =
        loop:
            var targetX: float = movement_Device.getX()
            var targetZ: float = movement_Device.getZ()
            var CamXform : float = camera.GetTransform().Translation.X
            var camZform : float = camera.GetTransform().Translation.Z
            Arrived := race:
                block:
                    camera.MoveTo(vector3{X:= targetX, Y:=camera.GetTransform().Translation.Y, Z:= targetZ},camera.GetTransform().Rotation, 0.2)
                    true
                block:
                    Sleep(0.25)
                    set CamXform = camera.GetTransform().Translation.X
                    set camZform = camera.GetTransform().Translation.Z
                    false
            if(not Arrived?, camera.TeleportTo[vector3{X:= CamXform, Y:=camera.GetTransform().Translation.Y, Z:= camZform},camera.GetTransform().Rotation]):
                Sleep(0.0)
                Sleep(0.0)
            else:
                break

    FollowChar()<suspends>:void =
        loop:
            MoveToChar()

The second block in the race is to periodically stop long moves and retarget - assuming that a target might be moving. This only happens if the Sleep() in the second block takes less time than the MoveTo() in the first block. Whichever block takes less time wins the race and cancels any other sibling blocks of the race.

Ideally there would be a built in function that auto-updates as a target moves - the function I wrote above called MoveToPositional() does this (though written in a somewhat hacky way). The position should ideally be updated every frame update (0.0) - not every half a second (0.5) as I did in the hack. [Since the hack already has a two frame pause - doing it more frequently doesn’t necessarily look better.]

The OverTime parameter in MoveTo() (as I understand it - I didn’t write MoveTo()) represents how much time (in seconds) to do the full move from where the prop is currently to the target destination.

Using OverTime can be tricky to keep a constant movement rate for an object - especially the target adjusts its destination over time. The best way to use OverTime - as far as I can tell - is to calculate the distance between points and divide that by the desired speed.

It might be nice to have a version of MoveTo() that uses a Speed such as a MoveToBySpeed(). It seems to me that many people are using MoveTo() as if OverTime were a constant speed and not the full amount of time for the move.

You used 0.2 seconds for the OverTime in the MoveTo() which will always be less time than your second block Sleep(0.25) so the first block will always win the race and the second block will always lose.

2 Likes

The second block in the race is to periodically stop long moves and retarget - assuming that a target might be moving. This only happens if the Sleep() in the second block takes less time than the MoveTo() in the first block. Whichever block takes less time wins the race and cancels any other sibling blocks of the race.

This is really clever, and definitely a good example of race - particularly how you’re able to tell which block “won the race”.

Using OverTime can be tricky to keep a constant movement rate for an object - especially the target adjusts its destination over time. The best way to use OverTime - as far as I can tell - is to calculate the distance between points and divide that by the desired speed.

Yeah, that’s correct, if you want your prop to have a fixed speed regardless of distance. Time = Distance/Speed. Reminds me of high-school Physics, hehe: Speed Distance Time - GCSE Maths - Steps, Examples & Worksheet

Something I’ve found interesting in your example code is the fact that you get the player position only once, i.e. if (Target := PlayerAsPositional[0]) and then Prop.MoveToPositional(Target, Offset) in the infinite loop in another method. Forgive me if this is mentioned in the Verse documentation, but I did not know until now that Verse objects are passed by reference, rather than by value (or something similar, not sure if that’s a precise definition here). Very cool, the more I learn the more I realize how beastly Verse is! :nerd_face:

Really hope you see more nuggets of wisdom from you in future Conan, thanks so much!

1 Like

Hi everyone, let me contribute to the prop animation conversation.

I’ve spent a bit longer than a whole month on toying around with prop animations and have run many different experiments and approaches to both mitigate the prop ghosting issue and get actually rid of it.


I’ve eventually came up with an okay-ish workaround solution, totally different from what has been presented above by Conan. I will not disclose the approach and strategy behind it as I would like duplicate bug reports of related issues to push the priority for an actcutal fix so that we (myself included) don’t have to rely on workarounds. I can share as much that my workaround has much more heavy lifting behind the scenes, but it’s far from being perfect. It’s not perfectly smooth as there is still some jumping but that’s unrelated to the ghosting glitches I previously mentioned.

This video only shows the strategy I came up with applied, but not how it’s actually done. You can observe that the chained animations are much smoother already.


That out of the way, let’s get to the science.

What is MoveTo operation and how does it work?

The MoveTo operation is just a ‘wrapper’ method on the creative_prop class. Under the hood, the operation makes use of an animation_controller instance associated to the same prop. As eveyone should have noticed by now, MoveTo has a <suspends> effect as it awaits the completion of the animation.

Here’s what the operation approximately does under the hood:

  • stop any in-flight animations if needed
  • get the current transform
  • compute a single keyframe_delta between the current transform and the destination transform with linear interpolation
  • set the animation_controller instance with that keyframe_delta and animation_mode.OneShot
  • play the animation
  • a suspending strategy for awaiting the completion or cancellation of the animation, this could be either done though keyframe related observation, though movement completion or state changes; otherwise you can make animations non-async or in other words fire and forget

These are the basics behind the MoveTo operation. If you ever wanted to do more complex animations with different time curves, you will quickly realize that you’d need to drive the animation_controller yourself instead of using MoveTo.

That said, the issue is not with MoveTo but acutally with the animation_controller itself, but I’ll get to that.

Conan mentioned above that if the prop hasn’t reached the destionated position and if you fire another MoveTo, the prop might glitch to the previous location.

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

This in my eyes is not a bug, but actually the expected behavior. I’m not saying that I like it or anything, it is however the expected documented behavior. Let me explain further.

The docs for the MoveTo operation state the following:

Move the creative_prop to the specified Transform over the specified time. If an animation is currently playing on the creative_prop it will be stopped and put into the AnimationNotSet state.

That is interesting, but it’s not true. Furhtermore, the second part regarding the AnimationNotSet does not make much sense as we don’t want to stop the entire prop from being animated with a subsequential call to MoveTo, but rather retarget it to a different destinated transfrom mid-flight.

Let’s inspect the docs from the animation_controller next:

# Stops playback and resets the animation to the first keyframe. 
# Also resets the prop transform. Calling this method is valid while the animation is in the `Playing` or `Paused` states.
Stop<public>():void = external {}

The AnimationController.Stop() method explictly states that the prop will be reset to the first keyframe (not the keyframe_delta) and to the origin transform. This explains why the MoveTo operation behaves like it does. If you call another MoveTo while the previous call to MoveTo has not finished yet, you will cause the underlying animation_controller to Stop which will reset the prop.

Personally I don’t understand the design decision behind this, but let’s assume there’s a very good reason for that.

Conan correctly found that TeleportTo does seem to help here, but it only mitigates parts of the issues, not all of them, so let’s move to TeleportTo next (no pun intended).

What is TeleportTo operation and what effects does it have?

Let’s say the TeleportTo method is special as it somehow resets the animation_controller to AnimationNotSet state and applies the passed transform values. Through my observations it does not itself use the animation_controller but it does affect its state. There’s not much to say here besides that we should remember the state change to AnimationNotSet.


Let’s check out some examples next:

  1. animation_controller state of a prop that has not been animated yet or has been reset equals AnimationNotSet:

  1. Native MoveTo states when animating until the end: AnimationNotSetStoppedPlayingStopped

    The animation always seem to start with a Stopped state if it was in AnimationNotSet state before, unless it was already in Stopped state. This is very a strange behavior. And last but not least, the last Stopped state does not teleport the prop to it’s origin.

  1. Calling native MoveTo while the prop is being animated and has no finished yet has interesting results:

    • The prop will be reset to the origin, because of the AnimationController.Stop() behavior.
    • The prop will not reach the destinated transform value because of the way keyframe_delta is being recomputed.
      • The new keyframe_delta is computed from the intermediate location towards the destination, which is obviously shorter in distance.
      • Then the prop is reset to the privious origin location due to Stop call.
      • The prop will travel only a shorter distance towards our desired destination but will not reach it.

    At first this is a bit mind bending, but it can be fairly explained after one understand how animation_controller behaves.

  1. Calling native MoveTo then TeleportTo mid-flight:
    • The animation will be reseted and stopped, but the prop will jump back a bit, as the recorded current mid-flight transfrom will be out of sync.

  1. A better approach is by using Pause method: MoveToPauseTeleportTo
    • It’s theoreically much better to pause the current animation.
    • Additionally one should await until the animation_controller reports that no animation is playing through one of its keyframe related signals.
      Record the current transform of the prop at that point of time and then execute TeleportTo.

This seems like almost the perfect solution, but even that suffers from ghosting effects still. There’s also another synchronization restriction which will almost always cause problems with such animation, but I’ll get to that point later on.

Here’s a custom MoveTo that gracefully implements the previous strategy. As you can observe in the following video, calling it multiple times will not reset the prop to the origin position, but simply retarget the animation.

However even this is still suffering from ghosting glitches as you can observe next:

One can clearly see that in many ghosting instances the prop seems to teleport to or at least towards its final destination transform before its reset to its actual animating position!

  1. Calling Stop on an animating prop:
  • Calling Pause then Stop will reset the prop to the origin location:

  • So does calling just Stop:

  1. Prop ghosting in action:

    The following prop is properly chanined between each animation and does not override or retarget the animation. The player position is queued with a delay and then dequeed when the previous animation has finished its execution. As you can clearly observe the prop animation is highly unstable which renders many possible applications completely useless and unimplementable.

Restriction on the simulation and animation_controller:

The simulation ticks with 30Hz (fps if you want). This is a very hard restriction as it cannot be possibly worked around.

  • Feeding an animation_controller with 60 keyframes for a one second animation will result only in half of the keyframes being reported.
  • It’s also not possible to run two animation_controller side by side with a single tick delay where each has 30 keyframes. Don’t get that wrong, it’s possible, but many reported keyframes will be synchronized to the same tick (which btw. you can get by fetching the simulation elapsed time).
  • I even tried using a cinematic sequence with lots of different configuration to somehow produce 60Hz signals.

All these attempts failed. You must be aware that the animation_controller will either drop some of your keyframes (not tested) or at least not report them explicitly. In case of the former behavior this implies that no keyframe deltas that are faster than a single simulation tick are actually not allowed, but nothing like this is publicly documented. By the way I also observed a properly configured animation_controller with 60 keyframes with 2 seconds animation duration which produced 59 frames callbacks as some intermediate keyframe was dropped.

Due to these simulation synchronization limitations it’s virtually impossible to chain two sequential animations which both do not synchronize their duration with the simulation tick speed. An animation has to perfectly align it’s completion date with a future tick or otherwise there will always be some animation stutter.



  • In my opinion the animation_controller should have a Stop overloaded method which would allow us to stop the animation at its current position without peforming a teleportation to the origin.
    We also need a way to reset the animation_controller directly without relying on TeleportTo, especially bacause TeleportTo is for some reason a failable method. The documentation is not clear when it can ever fail.

    Futhermore the animation_controller_state has this tiny bit that is very confusing to me:

    No animation has been successfully set via `animation_controller.SetAnimation`, or that animation has been cleared by a failed call to `animation_controller.SetAnimation`.
    AnimationNotSet
    

    What is “a failed call to SetAnimation”? The public SetAnimation method is not failable. It might potentially fail internally, but that documented bit is not very helpful here.

  • Why does the animation_controller change to Stopped state before starting an animation?

  • We also need ways to make those operations relative to their parents. If you teleport a child prop’s transform, it will be based on the world coordinate space instead of being relative to its parent prop.

  • I’ve talked to someone experienced with Unreal Engine and they mentioned that this whole issue looks a bit like a “networking replication” problem. Basically the client and the server are both out of sync and the animation is replicated on both end.

    For example a cinematic sequence device has a toggle which allows to disable such default behavior and let the sequence be played on the client side based on proximity.

    If that’s the case here, we need some form of “opt-out” solution for prop animations as well.

  • Last but not least, as we’re touching prop manipulation related APIs. Could we possibly get APIs for “attaching” and “detaching” prop actors from one another?!
    Generally being able to traverse prop hierarchy to find children props, parent and root props or sibling props on prop trees would be amazing.

11 Likes

I have found something that may be related, or even the root cause for movement.

Verse seems to regularly lose synchronization with the position of world objects, particularly the player.

If you have a “tick loop”, simply printing the position of the player now vs the last “tick” (or even just the Distance between PlayerPos vs PlayerPosLastFrame), you’ll observe that sometimes verse seems to “miss a tick”, reporting TWO movement steps in one tick - and then, later, sometimes on the next immediate tick but often several ticks later, a tick will be skipped, resulting in zero actual position change that tick.

Calculating delta times (via GetSystemTime) does not have any significant fluctuation, however, so the Verse simulation itself is not “lagging” - but it seems like Verse’s idea of a “tick” (server frame) does not exactly correspond to the game loop.

In short, if you have a “Sleep(0.0)” loop in your Verse code, it does NOT guarantee a “tick every frame” loop - sometimes it gets a double tick, followed by a skipped tick at a random time later.

[EDITED: Went on a bit of a rant]

Given that measuring the position of something in the world every frame is desyncing - along with the fantastic info given to us by @Knight_Breaker above - I believe the issue lies in the Verse simulation itself. Not MoveTo, not even the animation_controller - but something far deeper, like a float-rounding issue or the accumulation of nanosecond differences, or some other type of unintended internal latency…

FWIW, if I were a Dev at Epic and was banging my head trying to find the cause, I would probably start the arduous investigation of floating-point determinism. I say this in complete ignorance of the voodoo magic that is net code in UEFN, mind you :cowboy_hat_face:

The player object is passed by reference as Target (as the positional interface type) to MoveToPositional() and then its transform is updated every half second in the loop.

As I say, ideally there would be a MoveTo() equivalent that updated its target without starting and stopping a separate MoveTo() command (which is also slightly glitchy). :grin:

I have seen a number of people post a variation of this observation. I wrote Sleep() and it is supposed to wait precisely one update frame when 0.0. If this is not the case then there is likely something wrong. :thinking:

It depends on an internal Unreal event system and the Blueprint Virtual Machine. It also used to be several different functions that waited - instantly, per frame update, per specified seconds and infinitely - and now all rolled into Sleep(). So any of those aspects or something entirely different could be doing something unexpected. I’ll dig further into that and see if anything is amiss… It might be possible to simplify or make more efficient (and fix if there is an issue) by making Sleep() more Verse specific.

4 Likes