SmoothDamp Example
I made this so that I would have an easy example to point to when I talk about how powerful adding a tiny bit of smoothing to even the most basic of character controllers can be in making your game feel 500% better.
By default, every form of smoothing is turned off, and the ship flies by mapping input to movement 1:1. This is the most basic kind of movement you can write, and unless you have a very good reason, you should almost never do this.
What is SmoothDamp
It's maybe not the most correct name, but it's what I call it, and have been calling it for years. Essentially, when people want to smooth something, the most obvious and tempting solution is to do something like this:
position = lerp(position, targetPosition, deltaTime);
This is very bad and you should almost never do this!
Why? It's framerate dependent. Yes, it will create smooth movement, but the speed at which this thing moves will change depending on the framerate. At high framerates, the object will move quickly. At slow framerates it can lag quite significantly behind the target position.
What's the solution? A framerate independent smoothing formula.
float Damp(float a, float b, float lambda, float dt) { return Mathf.Lerp(a, b, 1 - Mathf.Exp(-lambda * dt)) }
I'm not going to attempt to explain why this works. Instead go read Rory Driscoll's article on the subject matter. It does a much better job of explaining this problem and the solution than I can.
This function is magical. I use it in basically everything. It's one of the first things I copy/paste into a new project, no matter the language, framework, or engine. It is powerful stuff and sprinkling it around your code here and there can make your game feel 30 times more polished with minimal effort.
Flying the demo
The demo is designed to be played with a gamepad, but it can also be played with a keyboard. If anything, the keyboard exposes the problems of what no damping looks like even more.
Use the number keys to enable/disable the different kinds of motion and damping.
Camera = Handling
From one of my favorite GDC talks of all time, something I want to specifically point out here is how much having even just a tiny amount of movement in the camera adds to making a game feel better. A rigidly locked third person camera will make pretty much any game feel bad, no matter how good your movement or physics are.
Even the most simple and basic movement will make your game feel leagues better to play. It's hard to overstate how important the camera is to your game.
If you use Unity, Cinemachine has gotten really good in recent years, making it very very easy to set up a great looking camera. Unless you have highly specialized requirements for cameras, I highly recommend trying out Cinemachine first before you try writing your own cameras. If you have very typical use cases, e.g. a Warthog game, then you can set up a great feeling shippable camera in only a few minutes.
Source Code
I've made the source code for this project available on GitHub.
For the record, I probably wouldn't write a spaceship controller like this. Instead, I'd use physics because I like to solve all my problems by complicating them with physics and PID controllers. However, for a very simple game where collision detection/resolution isn't that important, I do think it's pretty workable and have used solutions like this in the past. The best game I've ever made actually used a controller that was near identical to this.
Comments
Log in with itch.io to leave a comment.
Great stuff!
Hello. This was fun and interesting to play with.
Freya Holmér did a great presentation on frame-rate independent lerp: “Lerp smoothing is broken” ( https://www.youtube.com/watch?v=LSNQuFEDOyQ ).
It is nice to see it in action.
Her equation is slightly different: (a - b) * Mathf.Pow(r, deltaTime) + b
Or for exponential decay: (a - b) * Mathf.Exp(-decay * deltaTime) + b