Cinemachine — продвинутые техники
Confiner, Impulse, Composer, FreeLook, blending, state-driven cameras — углубление в Cinemachine 3.
В главе про камеру мы прошли базы Cinemachine 3. Эта глава — углубление: 4 фичи, которые часто нужны в реальном проекте.
CinemachineConfiner — камера не вылетит за уровень
Самая частая проблема следящей камеры — она может оказаться внутри стены или показать out-of-bounds zone. Confiner решает это:
- CinemachineConfiner2D — ограничивает камеру 2D-полигоном (для 2D / top-down).
- CinemachineConfiner3D — ограничивает 3D-Collider (BoxCollider, ConvexMesh).
Применение:
- Добавьте Empty GameObject на сцену с подходящим Collider (например,
BoxColliderразмером с playable-area). Это шейп граничной области. - На вашу
CinemachineCameraдобавьте компонент CinemachineConfiner3D. - В поле
Bounding Volumeукажите тот Collider.
Cinemachine будет автоматически “прижимать” камеру внутрь зоны, плавно адаптируя положение.
// Динамически менять зону при переходе между комнатами
[SerializeField] private CinemachineConfiner3D confiner;
[SerializeField] private BoxCollider roomA, roomB;
public void EnterRoomB() {
confiner.BoundingVolume = roomB;
confiner.InvalidateBoundingShapeCache(); // пересчитать на новой геометрии
}
В 2D-играх (Hollow Knight-style) расставляете PolygonCollider2D на каждую “комнату”, и Confiner2D автоматически меняет границы при переходе. Без рывков — Cinemachine блендит между shapes.
CinemachineImpulse — тряска камеры от событий
Импульсная система: один компонент CinemachineImpulseSource инициирует “взрыв”, а все виртуальные камеры с CinemachineImpulseListener получают шум, который затухает по расстоянию и времени.
Установка:
Explosion (GameObject)
└── CinemachineImpulseSource
impulse_definition: { signal_shape, duration, amplitude_gain, frequency_gain }
CinemachineCamera (player camera)
└── CinemachineImpulseListener
amplitude_gain: 1.0
use_2d_distance: false
Триггер тряски:
[SerializeField] private CinemachineImpulseSource impulseSource;
public void OnExplosion() {
impulseSource.GenerateImpulse(); // или GenerateImpulseAt(position, force)
}
Преимущества над “просто двигать transform.position”:
- Спадение по расстоянию — далёкий взрыв чувствуется слабее.
- Auto-blending — несколько одновременных импульсов суммируются естественно.
- Не ломает существующую follow-логику — Impulse добавляет шум поверх обычной позиции камеры.
CinemachineRotationComposer — точное прицеливание
Стандартный LookAt в Cinemachine — простой. RotationComposer — для тонкого framing: где
именно на экране должна быть цель?
Поля:
- Target Offset — смещение target в world space (например, чуть выше головы NPC).
- Lookahead Time — на сколько секунд “забегать” перед movement target’а (нужен для динамичных action-сцен).
- Damping —
(x, y, z)плавности на каждой оси по отдельности. - Screen Position — куда вы хотите видеть target на экране (центр vs правее 30%, ниже 20%).
- Dead Zone — размер невидимой “коробки” в центре экрана; пока цель в ней, камера не двигается (полезно против дрожаний).
- Soft Zone — больший прямоугольник: за его пределами камера резко реагирует.
[Screen]
┌──────────────────────────────┐
│ │
│ ┌─────────────────┐ │ ← Soft Zone (с damping)
│ │ ┌──────────┐ │ │
│ │ │Dead Zone │ │ │ ← Dead Zone (камера спит)
│ │ └──────────┘ │ │
│ └─────────────────┘ │
│ │
└──────────────────────────────┘
Чем больше dead zone — тем “спокойнее” камера. Идеально для AAA-игр с тонкой настройкой кинематографичности.
CinemachineFreeLook — orbital camera для action-игр
Раньше (Cinemachine 2) был отдельный CinemachineFreeLook с тремя кольцами. В Cinemachine 3 эта
функциональность вошла в CinemachineOrbitalFollow + InputAxisController.
Структура:
Player
└── CameraTarget (Node3D на голове)
CinemachineCamera (FreeLook)
├── CinemachineOrbitalFollow
│ orbits: Sphere | ThreeRing
│ horizontal_axis: Range -180..180
│ vertical_axis: Range -45..70
└── CinemachineInputAxisController
// привязка mouse delta / right stick к horizontal_axis / vertical_axis
OrbitalFollow.OrbitMode = ThreeRing — три кольца (top, mid, bottom) с разными радиусами и
высотами. Камера интерполируется между ними по vertical input. Классическая Dark Souls / Sekiro
камера.
// Программно поменять distance (zoom)
[SerializeField] private CinemachineOrbitalFollow orbital;
public void Zoom(float delta) {
orbital.Radius = Mathf.Clamp(orbital.Radius + delta, 2f, 8f);
}
State-Driven Camera — переключение по Animator-state
Полезный паттерн: разные камеры для разных game-state’ов (Idle/Combat/Cutscene). Можно вручную
менять Priority, но есть готовый компонент CinemachineStateDrivenCamera:
StateDriven (Node)
├── CinemachineStateDrivenCamera
│ animator: Player.Animator
│ states:
│ - "Locomotion" → CinemachineCamera_FreeLook
│ - "Combat" → CinemachineCamera_OverShoulder
│ - "Cutscene" → CinemachineCamera_Dolly
Когда Animator-state Player’а меняется, StateDrivenCamera автоматически переключает priority. С блендом между camera-shots — выглядит cinematic без кода.
Blending между камерами
В CinemachineBrain (на главной Camera) есть Default Blend:
- Cut — мгновенный (для smash cuts).
- EaseInOut / EaseIn / EaseOut — классические easings.
- Linear — линейный.
- Hard In/Out — резкий.
Можно задать custom blend между конкретными парами камер через Custom Blends ассет:
Custom Blends:
┌─────────────────────────────────────┐
│ From To Duration │
│ FreeLook → Combat 0.5s easy │
│ Combat → FreeLook 0.3s hard │
│ AnyCamera → Cutscene 1.5s ease │
└─────────────────────────────────────┘
Совет по архитектуре
Не делайте одну “Mega Camera” с условной логикой. Делайте много простых виртуальных камер и переключайте Priority через события игры. Это и отлаживается легче, и blendы работают корректно.
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; // самый высокий — победит
}
public void EndDialogue() {
dialogueCam.Priority = 0;
}
}