Hold on the wreck before moving the player
Cutting straight from the crash to a base three hundred metres away read as a bug rather than as a death, and the one moment in a campaign worth actually looking at was the one moment the player was not allowed to see it. The run now ends five seconds after the car does. Control goes; the camera leaves the bumper and climbs away to sixty metres, turning slowly, until the street, whatever was hit and whoever is standing around watching are all in frame. The point of the shot is not the car - by then the car is a fact - it is *where* this happened, which is the thing you have to drive back out to. The world keeps running underneath it. A death cam over a frozen world is a screenshot, not a moment. What stops is the driver: no throttle, no board, no overhaul, and no quietly finishing the job by virtue of having stopped moving on top of the target. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+44
-2
@@ -286,10 +286,52 @@ const camTarget = new THREE.Vector3();
|
||||
const camDesired = new THREE.Vector3();
|
||||
const CHASE_OFFSET = new THREE.Vector3(0, 3.4, -8.5);
|
||||
|
||||
/** Smoothed chase camera. Frame-rate independent damping. */
|
||||
export function updateCamera(view: SceneView, dt: number, speed: number): void {
|
||||
/**
|
||||
* Where the camera starts and ends up while pulling off a wreck.
|
||||
*
|
||||
* It leaves from just behind the car and climbs away until the whole scene is
|
||||
* below it: the street, whatever was hit, and whoever happens to be standing
|
||||
* around watching. The point is not the car — by then the car is a fact — it is
|
||||
* *where* this happened, which is the thing you have to drive back out to.
|
||||
*/
|
||||
const WAKE_FROM = { radius: 9, height: 3.5 };
|
||||
const WAKE_TO = { radius: 26, height: 62 };
|
||||
|
||||
/** Smoothstep. Eases both ends, so the pull-away neither jerks nor drifts. */
|
||||
const ease = (t: number) => t * t * (3 - 2 * t);
|
||||
|
||||
/**
|
||||
* Smoothed chase camera. Frame-rate independent damping.
|
||||
*
|
||||
* @param wake When set, the camera stops chasing and climbs slowly away from
|
||||
* the car, looking down at where the run ended. Used after the car is written
|
||||
* off: a hard cut from a crash to a base three hundred metres away reads as a
|
||||
* bug rather than as a death. `angle` turns the view a little as it goes so
|
||||
* the shot is not a straight retreat; `progress` runs 0 to 1 over the hold.
|
||||
*/
|
||||
export function updateCamera(
|
||||
view: SceneView,
|
||||
dt: number,
|
||||
speed: number,
|
||||
wake: { angle: number; progress: number } | null = null,
|
||||
): void {
|
||||
const { camera, car } = view;
|
||||
|
||||
if (wake) {
|
||||
const t = ease(Math.min(1, Math.max(0, wake.progress)));
|
||||
const radius = WAKE_FROM.radius + (WAKE_TO.radius - WAKE_FROM.radius) * t;
|
||||
const height = WAKE_FROM.height + (WAKE_TO.height - WAKE_FROM.height) * t;
|
||||
camDesired
|
||||
.set(Math.sin(wake.angle) * radius, height, Math.cos(wake.angle) * radius)
|
||||
.add(car.position);
|
||||
// Barely damped: the easing above already shapes the move, and heavy
|
||||
// smoothing on top of it turns the climb into a lag.
|
||||
camera.position.lerp(camDesired, 1 - Math.exp(-6 * dt));
|
||||
camTarget.copy(car.position);
|
||||
camera.lookAt(camTarget);
|
||||
return;
|
||||
}
|
||||
|
||||
camDesired.copy(CHASE_OFFSET);
|
||||
// Pull back a little at speed for a sense of pace.
|
||||
camDesired.z -= Math.min(Math.abs(speed) * 0.09, 3);
|
||||
|
||||
Reference in New Issue
Block a user