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.