Dev Log  Dynamic Stunt Camera System

Built out a full cinematic chase camera for the stunt driver today. The goal was to make the camera feel like it's physically reacting to the car's forces, not just trailing behind, but participating in the driving. Seven composing effects, all reading real Chaos physics data, all tunable per-vehicle.

What it does

Speed-reactive distance and FOV. The camera pulls back and the FOV widens as the car speeds up. At idle it sits tight and close; at top speed it's pulled out wide. Sells velocity without any input from the player.

G-force brake pull-in. Instead of reading brake input, the camera calculates real longitudinal G-force (delta velocity over delta time, divided by gravity). When you slam the brakes  or hit a wall  the camera snaps inward fast and the FOV tightens. The interp speed itself is dynamic: snappy on the punch, smooth on the recovery. Hitting a wall throws the camera forward in a way that genuinely sells the impact.

Drift roll. When the car's lateral velocity spikes during a turn or drift, the camera banks opposite the slide  like a motorcycle leaning into a corner. Driven by actual sideways momentum, not steering input, so it captures real drifts (sliding without much steering) instead of just wheel angle.

Look-ahead offset. Same lateral velocity signal, but instead of rolling, shifts the camera sideways in the direction of the slide. This lets you see into the corner during a drift instead of staring at the side of your car. Composed with the roll, one banks, one shifts, and they feel cooperative.

Suspension dip on landings. Reads ‘NormalizedSuspensionLength’ from each wheel via the Chaos Vehicle Movement Component, takes the minimum of any wheel in contact, and maps that to a downward camera Z-offset. Hard landings dip the camera HARD; soft landings barely move it. Lopsided ramp landings (clipping one corner of a jump) hit the camera as hard as flat ones, which is exactly what we want for a stunt game. When airborne, all wheels lose contact and the camera lifts slightly for a floaty feel.

FOV punch on burnout launch. When the burnout-launch torque kick fires, the FOV instantly punches +15° wider, then smoothly settles back to normal over about a second. Captures the impulse of the launch, not the duration  short or long burnout, you get the same punctuation. Trigger is event-based, so when we add a real boost button later it's literally one node call to reuse the same effect.

Speed-scaled camera shake + landing impact shake. Two Perlin noise shakes layered together: a continuous one that scales subtly with speed (idle rumble at the low end, road buzz at top speed) and a one-shot punchy oscillation that fires on hard landings. The landing detection is a relative threshold  "did suspension compress more than 0.05 from resting at touchdown"  instead of an absolute number, because suspension stiffness varies wildly per vehicle and absolute thresholds didn't generalize.

What makes it good

Everything reads real physics. No input-axis dependencies, no "if drift button pressed." If the car's velocity goes sideways, the camera responds. If the suspension compresses, the camera dips. If G-force spikes, the camera reacts. This means crashes, collisions, ramp launches, and physics-driven moments all automatically trigger the appropriate camera response with no event hooks needed.

Everything also composes additively. The seven effects don't fight: they each contribute one specific change (a distance offset, a roll angle, a Y-offset, a Z-offset, an FOV delta, a shake scale) to a single final camera state per frame. You can be braking hard while drifting around a corner while landing from a jump and the camera handles it gracefully. Every effect adds its piece, the FInterpTo nodes smooth the composite, and it lands somewhere visually coherent.

All tuning values are exposed as instance-editable variables, so dialing in the feel for a specific vehicle is just sliders in the inspector. No recompiles, no code changes.

Technical notes

  • Logic lives in the Vehicle Pawn's Event Tick, with the actor's Tick Group set to ‘TG_PostPhysics’. This is critical  reading Chaos data on pre-physics gives you the last frame's state and the camera feels laggy.

  • Cleaner than the original tutorial recommendation: I used the native ‘GetForwardSpeed ‘node on the Chaos Vehicle Movement Component instead of computing speed from ‘GetPhysicsLinearVelocity’ and a Dot Product. Same result, one node instead of three.

  • Frequently-used computed values (normalized speed, current Z offset, current boost FOV, look-ahead Y) got promoted to member variables instead of being routed via long wires across the graph. Way cleaner, way more readable.

  • Sequence node off Event Tick: Then 0 runs all camera logic, Then 1 updates last-frame state (LastForwardSpeed, LastFrameAirborne) at frame end so next frame's deltas calculate correctly.

  • EndPlay calls Stop Camera Shake on the active shake instance; without this, the shake outlives the vehicle on destruction (because PlayerCameraManager owns it, not the Pawn). Subtle bug, but it would show up on respawn/level-transition every time.

What's next

The whole system could be refactored into a UDataAsset (CameraTuningProfile) so each vehicle type gets its own preset  sport car, muscle car, offroad  without touching the logic. Tagged it as next-session work.

Also still on the polish list: replay/photo mode shake disable, dynamic profile blending for slow-mo moments, and proper SpringArm collision setup (currently disabled because the City Sample chassis convex blocks the camera channel  fixable with a custom probe channel later).

The most surprising thing about today's work: how much of the camera feels comes from signal choice. Reading lateral velocity instead of steering input is the entire difference between a camera that knows you're drifting and one that doesn't. Reading G-force instead of brake input is the entire difference between a crash camera that sells impact and one that just shrugs at walls. The wiring is the boring part of the architecture decision about what signal drives what effect is where the cinematography lives.



Note for next session

Save this as a TODO for whenever you come back to camera polish:

Refactor camera tuning variables into a UStruct

  • Create FCameraTuningProfile struct with all Cam_* variables grouped logically:

  • Distance/FOV: BaseFOV, MaxSpeedFOV, BaseArmLength, MaxSpeedArmLength, TopSpeed_CmS

  • Brake: BrakePullInAmount, BrakePullInFOV, HardBrakeG, NormalInterpSpeed, BrakeInterpSpeed

  • Roll/Look-ahead: MaxRoll, MaxLateralVelocity, RollInterpSpeed, MaxLookAhead, LookAheadInterpSpeed

  • Suspension: RestingSuspension, MaxCompressionDip, AirborneLift, DipInterpSpeed, RecoverInterpSpeed, AirLiftInterpSpeed, HardLandingDelta

  • Boost: BoostFOVAmount, BoostSettleSpeed

  • Add a single CameraProfile variable of that struct type to the vehicle

  • Replace all individual variable reads with struct member reads

  • Bonus: make it a DataAsset instead of a struct, then each vehicle (sport, muscle, offroad) gets its own preset DataAsset → instant camera personality per vehicle without code changes

  • Internal state vars (LastForwardSpeed, IsAirborne, LastFrameAirborne, MinSusLength, CurrentZOffset, CurrentBoostFOV, LookCornerAhead, CurrentNormalizedSpeed, ActiveShakeInstance) stay as regular member variables  those aren't tuning, they're runtime state

Previous
Previous

Dev Log: Real Streets

Next
Next

Dev Log — Perforce Server Setup for UE5