VanguardReMaster/client/Assets/Vanguard/Runtime/Battle/CameraDirector.cs

144 lines
3.9 KiB
C#

using UnityEngine;
namespace Vanguard.Battle
{
/// <summary>
/// Единственный владелец кинематографичного движения камеры.
/// Снимок исходного pose берётся один раз на цепочку и
/// восстанавливается идемпотентно.
/// </summary>
public sealed class CameraDirector
{
private struct Pose
{
public Vector3 Position;
public Quaternion Rotation;
public float FieldOfView;
}
private readonly Camera _camera;
private Pose _origin;
private Pose _from;
private Pose _to;
private float _duration;
private float _elapsed;
private bool _moving;
private bool _hasOrigin;
public CameraDirector(Camera camera)
{
_camera = camera;
}
public bool IsActive => _hasOrigin && _camera != null;
public bool IsMoving => _moving;
public void BeginChain()
{
if (_camera == null || _hasOrigin)
return;
_origin = Capture();
_hasOrigin = true;
}
public void ZoomTo(
Vector3 target,
float duration,
float fieldOfView = 34f)
{
if (_camera == null)
return;
BeginChain();
var current = Capture();
var targetPosition = target
+ (Vector3.back * 3.4f)
+ (Vector3.up * 2.6f);
var targetRotation = Quaternion.LookRotation(
target - targetPosition,
Vector3.up);
AnimateTo(
new Pose
{
Position = targetPosition,
Rotation = targetRotation,
FieldOfView = fieldOfView,
},
duration);
}
public void ReturnToOrigin(float duration)
{
if (!_hasOrigin)
return;
AnimateTo(_origin, duration);
}
public void Restore()
{
if (_camera == null || !_hasOrigin)
return;
_moving = false;
Apply(_origin);
_hasOrigin = false;
}
public void Tick(float deltaTime)
{
if (!_moving || _camera == null)
return;
_elapsed += deltaTime;
var t = Mathf.Clamp01(_elapsed / Mathf.Max(0.01f, _duration));
var eased = t * t * (3f - (2f * t));
Apply(new Pose
{
Position = Vector3.Lerp(_from.Position, _to.Position, eased),
Rotation = Quaternion.Slerp(
_from.Rotation,
_to.Rotation,
eased),
FieldOfView = Mathf.Lerp(
_from.FieldOfView,
_to.FieldOfView,
eased),
});
if (t >= 1f)
_moving = false;
}
private void AnimateTo(Pose target, float duration)
{
if (_camera == null)
return;
_from = Capture();
_to = target;
_duration = Mathf.Max(0.01f, duration);
_elapsed = 0f;
_moving = true;
}
private Pose Capture() => new Pose
{
Position = _camera.transform.position,
Rotation = _camera.transform.rotation,
FieldOfView = _camera.fieldOfView,
};
private void Apply(Pose pose)
{
if (_camera == null)
return;
_camera.transform.position = pose.Position;
_camera.transform.rotation = pose.Rotation;
_camera.fieldOfView = pose.FieldOfView;
}
}
}