The shared camera rig
Open the live demo · Read the source · View on GitHub
A platformer's chasing view and a racing game's both ease towards where they
should be, both carry a landing's knock or an explosion's shake as it fades,
and both have to stay out of the walls. CameraRig is that part, written
once; what differs between the two games is only how each works out where
the rig should be looking.
Step 1: The six numbers a chase needs #
RigTuning is a base class a game extends with its own tuning. This page's
is a short chase behind a fixed point.
/// The six numbers `CameraRig` needs to place itself, gathered under a name
/// this page's own view uses.
final class _ChaseTuning extends RigTuning {
const _ChaseTuning()
: super(
distance: 4.0,
height: 1.5,
aimHeight: 1.0,
lag: 8.0,
nearClearance: 0.3,
minDistance: 1.0,
);
}
Step 2: Build the rig #
CameraRig needs a collision world to stay out of, even an empty one.
_rig = CameraRig(world: CollisionWorld());
Step 3: Place it every frame #
place eases the rig's eye towards a desired position and a desired target,
and folds in any pending knock or shake. Turning on "Kick the rig" adds both
a downward knock and a short shake, the way a hard landing would.
static const _tuning = _ChaseTuning();
void _place(double dt) {
if (shaken) {
_rig.kick(Vector3(0, -0.4, 0));
_rig.shake(0.05, seconds: 0.3);
shaken = false;
}
final desiredEye = Vector3(0, _tuning.height, _tuning.distance);
_rig.place(
desiredEye: desiredEye,
desiredTarget: _target,
lag: _tuning.lag,
dt: dt,
);
}
Step 4: Let it settle #
// Placed enough times with nothing disturbing it, the rig settles to
// within a few millimetres of the free eye it is chasing.
for (var i = 0; i < 240; i++) {
_place(1 / 60);
}
final settledGap = (_rig.eye - _rig.freeEye).length;
Left alone with nothing kicking or shaking it, the rig's shown position converges on the same free eye it is chasing, because there is nothing to pull it away from that and nothing in its way.