~2 мин чтения

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).

Применение:

  1. Добавьте Empty GameObject на сцену с подходящим Collider (например, BoxCollider размером с playable-area). Это шейп граничной области.
  2. На вашу CinemachineCamera добавьте компонент CinemachineConfiner3D.
  3. В поле Bounding Volume укажите тот Collider.

Cinemachine будет автоматически “прижимать” камеру внутрь зоны, плавно адаптируя положение.

// Динамически менять зону при переходе между комнатами
[SerializeField] private CinemachineConfiner3D confiner;
[SerializeField] private BoxCollider roomA, roomB;

public void EnterRoomB() {
    confiner.BoundingVolume = roomB;
    confiner.InvalidateBoundingShapeCache(); // пересчитать на новой геометрии
}
Confiner2D с polygon — для top-down платформера

В 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;
    }
}