Improbable Icon

How to have smooth movement?

v10-0-0

#1

I finally got my project working that started from the Blank Project. I have my player and 3 AI units that move around the scene.

The problem is that the AI units don’t move smoothly. They look a little better when I lower the send transform frequency wait to 0 frames but are still a little jerky.

Also, when another player spawns in, they do not move around smoothly. How is this accomplished in other Spatial games? The example Lerp code helps, but it still does not look fluid.


#2

For Worker controlled movement (AI’s etc) I have a simple Position Behaviour and Visualizer class; the Behaviour sends an update every few frames and in my Visualizer I lerp the position like this:

transform.position = Vector3.Lerp(transform.position, position.Data.position, Time.fixedDeltaTime);

Nothing special there. I have explicitly put this in the FixedUpdate and not Update as an experiment earlier on; I am not convinced it really matters :slight_smile:


#3

For client-side movement I do the same thing but with one exception: I also perform the action locally. So when someone pushes the forward button I AddForce both on the Client and Worker and have the worker send updates back to the client and let the client Lerp to that position.

This will compensate for minimal lag though a more sturdy lag prediction algorithm would be better (haven’t gotten around to writing that).

For my game this works well enough for now because it fits the atmosphere to Lerp around a little but for an FPS you will need to have proper lag prediction to prevent rubber banding issues on higher lag.


#4

It does, but not for animation smoothness but for physics. If you are interacting with the environment (raycasts, collisions) you definitely should move items in FixedUpdate, otherwise you might encounter weird behaviour. My understanding is that if you move object in Update it’s new position won’t be acknowledged by physics till next time FixedUpdate runs. Also imagine if you are running against a wall. If you move player in Update and his computer lags for 5 seconds, you’ll move his position instantly by 5 seconds worth of distance, which might skip colliding with the wall entirely - on the other hand if he lags and you move him in FixedUpdate - all the FixedUpdates will be called incrementally with physics doing it’s job in between them and you’ll move him in several smaller steps which should hit the wall properly. Once all the missed FixedUpdates were called, then you’ll get single Update.


#5

Thanks @kukki! I think you just helped me remember why I put it in FixedUpdate! I had read your exact explanation somewhere but obviously forgot again about it. Hero!


#6

In some of the example code I noticed they are using MovePosition. Do you guys use that in your games?


#7

I don’t but MovePosition sounds like a simple movement solution and applicable in some situations; I use steering functions for the movement of my units and my camera should not have physics and is set to be kinematic. And the MovePosition docs state the following:

If the rigidbody has isKinematic set false then it works differently. It works like transform.position=newPosition and teleports the object (rather than a smooth transition).


#8

Are yours set to isKinematic?

Thanks btw!


#9

Just checked it to be sure but my player is not set to Kinematic because I use AddForce to move it; which does require rigidbodies to work ‘normally’. But then again; I control movement using a controller and not by clicking on the ground (for the player; for units I do but those use steering functions and MovePosition is too simple for that).

If MovePosition works for you: use it. I don’t see a technical reason not too


#10

Its mainly my AI units that I am having trouble with. They are warping when they move and it looks quite unnatural.


#11

If they are warping then it sounds like your lerps or moveposition is invoked at the wrong moments. Something which can warp is if you try to use the lerp or moveposition on every update instead of anytime the location of your unit is updated.

I use an event listener (OnComponentUpdated) on my Position component and every time that is called I lerp the unit to its new location. On the worker side I update the Position component every 10 frames or so (for performance).

Do take care that updating the transform’s position is only done client side and changing the component Position (the real position) is only done on the worker. Does that help?


#12

That helps a lot actually!! Are you using AddForce on the worker or client or both?

I think I may be lerping the movement every frame regardless of change… going to check now.


#13

Well, I don’t use AddForce for my units (because they work using Lerps and/or MovePosition basically); AddForce is for the Controller/Keyboard movement of my Player/Camera. For that I apply AddForce both on the client and on the worker; and every few frames I re-set the position on the client with what the worker has to prevent cheating.


#14

It turns out that when I put the movement in Update() instead of FixedUpdate() it is a LOT smoother. Is there any way to keep it in FixedUpdate and still be smooth?


#15

The client-side visualizer of the movement needs to interpolate (in Update) between the sparse updates that are sent from the worker’s FixedUpdate. It’s possible to do it very simply, using draconigra’s lerping method.

The “correct” interpolation method is also a relatively simple algorithm, but you’ll need a deep understanding of what is really happening behind the scenes in order to implement it properly; here’s a good starting point: http://gafferongames.com/networked-physics/snapshots-and-interpolation/


#16

Ok, I have it in FixedUpdate() on the server and Update() on the client. I thought someone mentioned you need it in FixedUpdate() on the client though to see physics properly. Do you run the update in both?


#17

To start, just run the interpolation in Update on the client, and just send the data in FixedUpdate on the worker.

Eventually you’ll want to do some kind of client-side prediction to make it feel less latent, but just doing the interpolation in Update will make it seem smooth (provided the interpolation value is adjusted properly).