Cinemachine — Advanced Techniques
Confiner, Impulse, Composer, FreeLook, blending, state-driven cameras — a deep dive into Cinemachine 3.
In the camera chapter we went over the basics of Cinemachine 3. This chapter is a deep dive: 4 features you often need in a real project.
CinemachineConfiner — the camera won’t fly off the level
The most common problem with a follow camera is that it can end up inside a wall or show an out-of-bounds zone. The Confiner solves this:
- CinemachineConfiner2D — confines the camera to a 2D polygon (for 2D / top-down).
- CinemachineConfiner3D — confines it to a 3D Collider (BoxCollider, ConvexMesh).
Usage:
- Add an Empty GameObject to the scene with a suitable Collider (for example, a
BoxColliderthe size of the playable area). This is the shape of the bounding region. - Add the CinemachineConfiner3D component to your
CinemachineCamera. - In the
Bounding Volumefield, specify that Collider.
Cinemachine will automatically “push” the camera back inside the zone, smoothly adapting its position.
// Dynamically change the zone when moving between rooms
[SerializeField] private CinemachineConfiner3D confiner;
[SerializeField] private BoxCollider roomA, roomB;
public void EnterRoomB() {
confiner.BoundingVolume = roomB;
confiner.InvalidateBoundingShapeCache(); // recalculate for the new geometry
}
In 2D games (Hollow Knight-style), you place a PolygonCollider2D on each “room”, and Confiner2D automatically changes the bounds during a transition. Without jerks — Cinemachine blends between shapes.
CinemachineImpulse — camera shake from events
An impulse system: a single CinemachineImpulseSource component triggers an “explosion”, and all virtual cameras with a CinemachineImpulseListener receive noise that decays with distance and time.
Setup:
Explosion (GameObject)
└── CinemachineImpulseSource
impulse_definition: { signal_shape, duration, amplitude_gain, frequency_gain }
CinemachineCamera (player camera)
└── CinemachineImpulseListener
amplitude_gain: 1.0
use_2d_distance: false
Triggering the shake:
[SerializeField] private CinemachineImpulseSource impulseSource;
public void OnExplosion() {
impulseSource.GenerateImpulse(); // or GenerateImpulseAt(position, force)
}
Advantages over “just moving transform.position”:
- Falloff with distance — a distant explosion feels weaker.
- Auto-blending — several simultaneous impulses sum up naturally.
- Doesn’t break the existing follow logic — Impulse adds noise on top of the camera’s normal position.
CinemachineRotationComposer — precise aiming
The standard LookAt in Cinemachine is simple. RotationComposer is for fine framing: exactly
where on the screen should the target be?
Fields:
- Target Offset — an offset of the target in world space (for example, slightly above an NPC’s head).
- Lookahead Time — how many seconds to “run ahead” of the target’s movement (needed for dynamic action scenes).
- Damping —
(x, y, z)smoothing on each axis separately. - Screen Position — where you want to see the target on the screen (center vs 30% to the right, 20% lower).
- Dead Zone — the size of an invisible “box” in the center of the screen; while the target is inside it, the camera doesn’t move (useful against jitter).
- Soft Zone — a larger rectangle: beyond its bounds the camera reacts sharply.
[Screen]
┌──────────────────────────────┐
│ │
│ ┌─────────────────┐ │ ← Soft Zone (with damping)
│ │ ┌──────────┐ │ │
│ │ │Dead Zone │ │ │ ← Dead Zone (camera is asleep)
│ │ └──────────┘ │ │
│ └─────────────────┘ │
│ │
└──────────────────────────────┘
The larger the dead zone, the “calmer” the camera. Perfect for AAA games with fine-tuned cinematography.
CinemachineFreeLook — an orbital camera for action games
Previously (Cinemachine 2) there was a separate CinemachineFreeLook with three rings. In
Cinemachine 3, this functionality moved into CinemachineOrbitalFollow + InputAxisController.
Structure:
Player
└── CameraTarget (Node3D on the head)
CinemachineCamera (FreeLook)
├── CinemachineOrbitalFollow
│ orbits: Sphere | ThreeRing
│ horizontal_axis: Range -180..180
│ vertical_axis: Range -45..70
└── CinemachineInputAxisController
// binding mouse delta / right stick to horizontal_axis / vertical_axis
OrbitalFollow.OrbitMode = ThreeRing — three rings (top, mid, bottom) with different radii and
heights. The camera interpolates between them based on vertical input. The classic Dark Souls /
Sekiro camera.
// Change the distance (zoom) programmatically
[SerializeField] private CinemachineOrbitalFollow orbital;
public void Zoom(float delta) {
orbital.Radius = Mathf.Clamp(orbital.Radius + delta, 2f, 8f);
}
State-Driven Camera — switching by Animator state
A useful pattern: different cameras for different game states (Idle/Combat/Cutscene). You can change
Priority manually, but there’s a ready-made CinemachineStateDrivenCamera component:
StateDriven (Node)
├── CinemachineStateDrivenCamera
│ animator: Player.Animator
│ states:
│ - "Locomotion" → CinemachineCamera_FreeLook
│ - "Combat" → CinemachineCamera_OverShoulder
│ - "Cutscene" → CinemachineCamera_Dolly
When the Player’s Animator state changes, StateDrivenCamera automatically switches the priority. With a blend between camera shots, it looks cinematic without code.
Blending between cameras
CinemachineBrain (on the main Camera) has a Default Blend:
- Cut — instant (for smash cuts).
- EaseInOut / EaseIn / EaseOut — the classic easings.
- Linear — linear.
- Hard In/Out — sharp.
You can set a custom blend between specific pairs of cameras via a Custom Blends asset:
Custom Blends:
┌─────────────────────────────────────┐
│ From To Duration │
│ FreeLook → Combat 0.5s easy │
│ Combat → FreeLook 0.3s hard │
│ AnyCamera → Cutscene 1.5s ease │
└─────────────────────────────────────┘
Architecture advice
Don’t build a single “Mega Camera” with conditional logic. Build many simple virtual cameras and switch Priority through game events. This is both easier to debug and lets blends work correctly.
public class CameraManager : MonoBehaviour
{
[SerializeField] private CinemachineCamera locomotionCam;
[SerializeField] private CinemachineCamera combatCam;
[SerializeField] private CinemachineCamera dialogueCam;
public void EnterCombat() {
combatCam.Priority = 20;
locomotionCam.Priority = 10;
}
public void StartDialogue(Transform npc) {
dialogueCam.LookAt = npc;
dialogueCam.Priority = 30; // the highest — it wins
}
public void EndDialogue() {
dialogueCam.Priority = 0;
}
}