Improving the Vehicle Camera
Hey folks,
So production is still trucking along fine - in fact we're gearing up for a playtest soon! If you're interested to play the game & give some feedback, please fill in this google form.
Anyway, this devlog is going to get into some details for the MSSIRT's driving camera; as I've built up a small laundry list of behaviours that all come together to make it a smoother-feeling experience than the jam version.
Before getting into all this, here's what Unity code/math I'll be getting into:
- Quaternions, and specifically Quaternion.AngleAxis
- EulerAngles
- AnimationCurves
And for reference, here's what my base camera class looks like before we get into these extras:
public class PlayerDrivingCameraRotator : MonoBehaviour
{
[Header("Input")]
public InputActionReference lookAction;
private Vector2 _actionValueRaw;
private Vector2 _actionValueFollower;
private Quaternion _rootLocalRotation;
[Header("Settings")]
[Range(0,1)]
public float lookMoveTowardsLerp = 0.5f;
public float maxAngle = 45;
private Quaternion _desiredRotation;
private void Awake()
{
_rootLocalRotation = transform.localRotation;
}
private void Update()
{
if(!PlayerStateModel.Instance.canPlayerLookAround)
return;
// get the raw input value from InputSystem
_actionValueRaw =
lookAction.action.ReadValue<vector2>();
// lerp to the raw input value
_actionValueFollower = Vector2.Lerp(
_actionValueFollower,
_actionValueRaw,
lookMoveTowardsLerp );
// use that to get the player's desired aim
_desiredRotation =
Quaternion.AngleAxis(
_actionValueFollower.x * maxAngle,
Vector3.up )
* Quaternion.AngleAxis(
_actionValueFollower.y * maxAngle,
invertY ? Vector3.right : Vector3.left );
transform.localRotation = _desiredRotation;
}
}
Aligning to the Ground
A big source of muergh when driving around the open desert is the rock/rolling of the camera as you go over dunes.
It's not the worst thing in the world, but I think to being in a bus or something going over hills - you shift your body & head to try and keep yourself upright. So I could do the same for the player camera:
Much better! How this is done is with a straightforward lerp, & a consistent reference for what "flat" is. For the MSSIRT, I've setup the hierarchy in a way where it looks something like this:
Player Truck (rigidbody sits here, along with the core controller components)
-> Cockpit
-> Camera Root
-> <various cabin cameras>
-> <other Truck gubbins>
What this means is is that the LocalRotation of the Cockpit Cameras is always going to be based off the Truck rotation (as it should, it's not a wrong thing to do that imo). Now because the truck is free to move around the world, I can't just use Quaternion.identity as my "world flat" - I need the flattened facing of the truck. I also need that rotation to be in the local space of the Camera Root - that way I can just lerp from the Camera's localRotation to/from the Flattened localRotation.
public class FlattenRotation : MonoBehaviour
{
public Transform reference;
private void Update()
{
transform.eulerAngles =
Vector3.up
* reference.eulerAngles.y;
}
}
Easy! Just have this script point to the Player Truck transform, and only copy it's world Y euler. With that we now have our flat to lerp to:
[Header("Flat rotation reference")]
public Transform worldIdentityReference;
[Range(0,1)]
public float matchWorldRotationRatio = 0.4f;
private Quaternion _baseRotationLerper;
...
Update()
{
...
// 2 layers of lerping:
// 1 - Lerp from the current value to the new ratio value
// (this is to keep this smooth whilst driving around)
// 2 - then use Lerp as a way of easily getting a mid-point
// between the Cabin Rotation & our World Flat rotation
// (using the LocalRotation as it's more performant,
// as it doesn't need to work up the hierarchy)
_baseRotationLerper = Quaternion.Lerp(
_baseRotationLerper,
Quaternion.Lerp(
_rootLocalRotation,
worldIdentityReference.localRotation,
matchWorldRotationRatio ),
0.8f );
transform.localRotation = _desiredRotation
* _baseRotationLerper;
}
Some further steps from this that I found are:
- If the truck is airborne, don't do this flattening behaviour, otherwise you get a tumble-dryer kind of effect which sucks.
- It'll take some dialling in of the matchWorldRotationRatio to get this to feel right, based on how much your vehicle rotates.
Looking into a Turn
Ok, next up is a teeny bit easier to grasp - and more obvious in hindsight! When you're turning the truck, also angle the camera in that direction a wee bit.
There we go! I find that this makes it feel that little bit more natural, rather than being locked in place in the driver's seat.
Once again, this uses a Lerp, but also uses a "follower" to smoothly move to the desired steer angle. This is because if you just use the current steering angle, the camera will *snap* to it, rather than smoothly blend into the turn, which is what I'm looking for (not that that's a bad thing for all games - if you have a fever-pitched driving game, the snapping might be exactly what you want, in this case I need something subtle & smooth)
...
[Header("Turning")]
public float whenTurningAngleMultiplier = 0.03f;
[Range(0,1)]
public float turningFollowLerp = 0.5f;
private Quaternion _turningRotation;
private float _turningFollower;
...
Update()
{
// lerp to & create the Turning rotation
// in my case, currentTurnValue is a value from -45 to 45,
// which reduces as you speed up,
// (hence the need for a multiplier to reduce it)
_turningFollower = Mathf.Lerp(
_turningFollower,
PlayerRoot.VehicleEngine.currentTurnValue
* whenTurningAngleMultiplier,
turningFollowLerp );
// apply around the Y axis to look left/right
_turningRotation = Quaternion.AngleAxis(
_turningFollower,
Vector3.up );
// note the ordering of this:
// we use the input,
// to rotate the steering,
// to then rotate onto the base rotation
transform.localRotation = _desiredRotation
* _turningRotation
* _baseRotationLerper;
}
Leaning forwards when braking
Cool, so we now have a stabilising camera, it looks into the turn you want to do, now what?
Oh yeah, when you brake a 300,000kg vehicle, it'd probably make you heeaaaaaaaavvvveee forwards a bit. So let's implement that.
In this case, I needed to have a few components working together to get this done in a not-extremely-hacky way:
- A BrakingValue calculator component. Which takes the current Rigidbody velocity's magnitude, & based on the current braking 0-1 input value, produce a resulting "braking hardness" 0-1 value.
- An offset component, which takes that hardness, then shifts it's transform by a given amount.
- And finally our good ol' camera script, which will now add on another rotation axis.
In terms of achieving 1, I've resorted to the most OP of all Unity classes: the AnimationCurve:

Where the x axis is the speed of the truck, the Y axis is used for what the maximum brakin hardness can be - the end goal being that:
- when you're going slowly, or very fast, it's not that much of an effect,
- but around 20-30, the effect should be at it's fullest.
My thinking is (at least with my jank vehicle physics), around 20 is when you have a sweet spot of control & speed, so the braking is most noticeable? Dunno. But having this be driven by a curve let's it be immediately editable, rather than doing equations (*shriek*).
Anyway - we got our braking value, now to apply it, and the code for that is again, pretty straightforward:
...
[Header("Braking Lean")]
public float whenBrakingMaxAngle = 15;
private Quaternion _brakingRotation;
...
Update()
{
// apply around the X axis to lean forwards/backwards
_brakingRotation =
Quaternion.AngleAxis(
VehicleBrakingValue.BrakingHardness
* whenBrakingMaxAngle,
Vector3.right );
transform.localRotation =
_desiredRotation
* _turningRotation * _brakingRotation
* _baseRotationLerper;
}

End Result
With that, we have some pretty natural-feeling camera motion:
Nice 👌
I'll need to dial in the values some more of course, the braking is a little heavy, and feels like it could take longer to "undo"; but it always feels good to get in these more subtle effects to get a sense of space into a game.
(btw, if you liked what you see, add the full game to your collection & give me a follow for updates!)
See you all at the next devlog! (...whenever the heck that'll be)
Get A Day of Maintenance (Jam Version)
A Day of Maintenance (Jam Version)
A driving fix 'em up crane-operator game for 7DFPS
| Status | Released |
| Author | BigHandInSky |
| Genre | Puzzle |
| Tags | 3D, Atmospheric, crane, desert, Driving, fixing, maintenance, Open World, Sci-fi, Singleplayer |
| Languages | English |
| Accessibility | Color-blind friendly, Subtitles, High-contrast, Interactive tutorial |
More posts
- Site Update Teaser - The Sunken DishJun 07, 2021
- Validating Unity Builds the easier wayMay 15, 2021
- Working on the game's IntroductionApr 20, 2021
- Character TeaserMar 30, 2021
- Adding world propsFeb 21, 2021
- A helpful Ink-Knot PropertyDrawerFeb 07, 2021
- Things you should know before making a Vehicle in UnityJan 30, 2021
- Useful scripts for Quick 'n good UGUIJan 13, 2021
- A sneak peek at what's comingJan 01, 2021

Leave a comment
Log in with itch.io to leave a comment.