PSA: Your Minimum Move Distance May be Too High

Backstory

So I encountered a pretty horrible bug that occurred on release day for my last game–one that stops players from being able to play the game at all. What’s worse, I couldn’t even test the bug on my own machine, since it was kind of hardware-specific. After doing a ton of research, I couldn’t find any solutions online or anywhere, which is why I’m making this post now, for future reference in case anyone has the same issue. Furthermore, this post hopes to prevent this “bug” from happening in Unity games from now on.

The Problem

So the bug was actually pretty specific. When the framerate of my game was over ~150 FPS, the player character just refused to move. I phoned a friend to test the issue and confirmed that the framerate was what was causing the bug. I checked everything I could think of, thinking that maybe it was related to the Time.deltaTime variable. But none of my code changes helped.

Previous Solutions

At the time, I figured it was an issue with floating point precision. After all, with higher framerates, the Time.deltaTime variable shrinks down to a really small number. Maybe it was so low that the deltaTime variable was just coming in as 0. So I wrote and re-wrote code until everything was based on reading the inputs directly without additions and it still didn’t solve anything.

So my temporary solution was to just cap the framerate at 120FPS.

The Real Solution

So I finally ran some more tests yesterday and found that the issue was not with any of the code, but with the Character Controller itself. I ran a test scene at 500FPS and printed a log of the variables that fed into the Controller.Move() function and found that they were NOT 0. There was enough precision for Time.deltaTime (Which factored into the variable) to not be 0. More specifically, the Move() function was reading the player input and multiplying it by the Time.deltaTime. So if the Move() function wasn’t receiving a 0 vector, why would it refuse to move?

That’s when I found this little option right here:

It’s something I didn’t even think to consider, but this variable was the only thing standing in the way from fixing this bug I must’ve spent over 15 hours on. To reiterate, when the Move() function received a really low value due to high framerates and the deltaTime variable being so low, the character refused to move because the Minimum Move Distance on the Character Controller was higher than the input variable. So obviously, once I lowered the Minimum Move Distance to 1e-06, the issue was resolved.

Note that this only came up because my Motor code was in Update. (Actually, I’m using the old default Character Motor, which has an option for “Use Fixed Update” which I had turned off.) If your movement code is in FixedUpdate, I would assume you would multiply by the FixedDeltaTime instead, which normally never results in a variable lower than 0.001.

Thoughts

These kinds of bugs are scary, because this option was so obscure and I wouldn’t have even noticed a problem because I normally couldn’t run my game at a framerate that high. It makes me think that there might be other theoretical issues when framerates get even higher. Are there other things with a “minimum” setting that could break on higher framerates?

And the worst part is that my solution only delays the problem until the framerates get even higher. What if framerates get so high that the Move() input becomes less than 1e-06?! For future proofing especially, on future hardware we’ll be able to run games a lot faster than we can now. If there’s no Framecap option or VSync in a game, something that used to work would start to break 10 years from now. Sure, we can just keep lowering the Minimum Distance on and on, but how far is enough?

Anyways, I hope this helps you in making your games. If anyone else encounters this issue, this post should serve as a reference and guide for what to do. Good luck!

3 Likes

Exceeding some relatively small number of FPS is just wasting electricity.

FixedDeltaTime is exactly equal to whatever you set for your fixedupdate rate. It will never fluctuate. The Character controller bug is entirely down to the fact somewhere, you have bad timing. It can’t occur if you use correct delta timing because there would always be a correct amount of minimum move.

Yes I know, which is why I stated that unless games have a framecap or VSync option they will break. From now on I’ll always be including those options in my games. But I’m sure there are plenty of Unity games out there that don’t have these options and could potentially break if framerates get higher.

I had already explained that this issue occurs only because I have placed my Motor code in Update() instead of FixedUpdate:

I assume when you say “Correct delta timing” you mean to switch to FixedDeltaTime?

I’m aware that the default CharacterMotor option is to run the code in FixedUpdate(), however for my previous project I needed it to run in Update() because when my character was using climbing, the FixedUpdate resulted in the character’s velocity recalculating incorrectly. Not to mention the clear precision advantage in using framerate-bound input recognition instead of fixed recognition, especially for a platformer.

Also do keep in mind that the documentation example for CharacterController.Move() uses Update() and Time.deltaTime. It even says “It is recommended that you make only one call to Move or SimpleMove per frame,” which implies usage in Update.

But Character Controller can and often SHOULD be used in Update, not fixedupdate. Your problem wasn’t that. It was you didn’t understand what fixed delta was returning.

You’re basically letting your maths go out of control and blaming what update loop you put it in. You should get a handle on that or your other work might suffer.

Sorry? Where exactly did I claim that FixedDelta returned anything other than the time between FixedUpdates? I never used FixedDeltaTime in my code. When I said “multiply by the FixedDeltaTime instead, which normally never results in a variable lower than 0.001” I meant that normally your Physics Timestep is larger than .001 which never results in a product less than .001 (Because you can assume an input magnitude of 1.) Of course, taking speed into account that raises the value but as the post explains even with that it’s been lowered to the point of going below the minimum.

The problem did stem from the fact that the Motor code was in Update(). I already explained two times that if the code was in FixedUpdate() then I would be using FixedDelta and this issue would not happen. How is it possible to not have this issue while running the code in Update() with regular deltaTime?

EDIT: To be perfectly, absolutely clear: My original code was similar to the example provided in the Unity Documentation for Character Motor–Code runs in Update, using regular deltaTime. I’ve known what deltaTime and fixedDeltaTime returns for like 6 years now; the problem came from the fact that I forgot there was a minimum vector cap on the Character Controller. And I can bet you that plenty of people have forgotten about this option as well.

The way it stands right now, Unity’s example for CharacterMotor.Move will encounter this issue on higher framerates.

3 Likes

To be safe, I’ve always put the minimum move distance to 0. The docs say the value should be left at 0 in most situations.

That’s a good idea, but you’d need to be careful with it. When joysticks are involved sometimes the “resting” position is actually not 0, which is probably part of the reason why the minimum move option exists. Now, Unity’s Input manager will solve this with axis deadzones, but if you were to ever build your own inputs without deadzones what you would have is the player moving slightly even when no controller stick is being held. Here’s the link to that documentation page. It also says the option exists to reduce jitter.

This extremely old post just saved me after 3 days of trying to figure out why my character won’t move when a linux dedicated server is authoritative. Without rendering graphics on the server my move speed was lower than min move distance.

2 Likes

Glad to hear it! Just to update everyone on my modern practices, nowadays I always set minimum move distance to 0 on every character controller I use. To solve the joystick issue mentioned in my last post here, I rely on deadzones and the stability offered by the new Input System as well as my own input smoothing techniques.

This has been the best solution for me in terms of reliability as hardware gets even better to safeguard against higher framerates that could happen in the future. In addition, I try to provide framerate limiting options which can preserve the original behavior. Hope that helps!

Same here. Glad to have finally found the cause for what was such an annoying issue lol. I thought I was going crazy since the issue was happening with Unity’s starter assets controller as well. Though for me, setting it to 0 didn’t work exactly and instead of being locked from moving my character would now just very slowly slide forward for a bit when the movement input had stopped. This was probably due to how I was handling acceleration/deceleration (I already had deadzones and everything in place so it wasn’t that) but I’m not sure.

Anyways, if anyone else comes across this with the same sliding issue after setting it to 0, it was an easy fix of just setting MinMoveDistance to a super low value if there was any movement input and if the CharacterController’s velocity.magnitude was above 0.25f, then setting the MinMoveDistance to 0 if the reverse was true.

Good lord, I spent a week on this. Glad to see my suspicion being correct.

On my PC and Laptop the CharacterController worked fine in the editor, as well as in a build on my laptop.
The build on my PC though, didnt work.

I capped the time.deltatime to 0.00101f. and it works… pfffff…

Thanks OP, much obliged!

For a detailed explanation of what’s going on:
Schrödinger’s bug

2 Likes