Files
aibis-dream/Assets/Scripts/Framework/OtherKit/CameraKit.cs
T

484 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections;
using System.Collections.Generic;
using AibisDream.Kit;
using AibisDream.Utility;
using Cinemachine;
using DG.Tweening;
using DG.Tweening.Core;
using DG.Tweening.Plugins.Options;
using UnityEngine;
using UnityEngine.Rendering.Universal;
using Yarn.Unity;
namespace AibisDream.Framework
{
public class CameraKit : Singleton<CameraKit>
{
private const int LowPriority = 1;
private const int HighPriority = 10;
#region 相机组件
public static Camera UICamera => UIManager.Instance.UICamera;
private CinemachineBrain _orthoCameraBrain;
private readonly Dictionary<CameraEnum, ICinemachineCamera> _virtualCameraDict = new();
private readonly Dictionary<CameraEnum, CamState> _camInitStateDict = new();
private CameraEnum _curCameraEnum;
private CamState _curInitState;
private Camera _orthoMainCamera;
private Tween _shakeTween;
private struct CamState
{
public Vector3 pos;
public float initOrthoSize;
}
#endregion
private CameraKit()
{
}
public override void OnSingletonInit()
{
// 获取正交相机
_orthoMainCamera = Camera.main;
_orthoCameraBrain = _orthoMainCamera?.GetComponent<CinemachineBrain>();
_curCameraEnum = CameraEnum.Default;
}
#region 相机信息注册
public void RegisterUICamera(Camera overlayCamera)
{
var mainCameraData = _orthoMainCamera.GetUniversalAdditionalCameraData();
if (mainCameraData != null)
{
if (!mainCameraData.cameraStack.Contains(overlayCamera))
{
mainCameraData.cameraStack.Insert(0, overlayCamera);
}
}
else
{
Debug.LogError("Main Camera does not have UniversalAdditionalCameraData.");
}
}
public void UnRegisterUICamera(Camera overlayCamera)
{
// 获取 Main Camera 的 UniversalAdditionalCameraData
var mainCameraData = _orthoMainCamera.GetUniversalAdditionalCameraData();
if (mainCameraData != null)
{
// 检查 Overlay Camera 是否在 Stack 中
if (mainCameraData.cameraStack.Contains(overlayCamera))
{
// 将 Overlay Camera 从 Stack 移除
mainCameraData.cameraStack.Remove(overlayCamera);
Debug.Log("Overlay Camera removed from Main Camera stack.");
}
}
else
{
Debug.LogError("Main Camera does not have UniversalAdditionalCameraData.");
}
}
public void RegisterCamera(CameraEnum camType, ICinemachineCamera virtualCamera)
{
if (_virtualCameraDict.TryGetValue(camType, out var existing) && existing != virtualCamera)
{
var existingName = existing?.VirtualCameraGameObject != null
? existing.VirtualCameraGameObject.name
: "unknown";
var newName = virtualCamera?.VirtualCameraGameObject != null
? virtualCamera.VirtualCameraGameObject.name
: "unknown";
Debug.LogWarning(
$"RegisterCamera: {camType} 已被 \"{existingName}\" 注册,将被 \"{newName}\" 覆盖");
}
_virtualCameraDict[camType] = virtualCamera;
var vcam = virtualCamera.VirtualCameraGameObject.GetComponent<CinemachineVirtualCamera>();
if (vcam == null)
{
Debug.LogWarning($"RegisterCamera: {camType} 的 GameObject 上未找到 CinemachineVirtualCamera 组件");
return;
}
var initState = new CamState
{
pos = virtualCamera.VirtualCameraGameObject.transform.localPosition,
initOrthoSize = vcam.m_Lens.OrthographicSize
};
_camInitStateDict[camType] = initState;
}
public void UnRegisterCamera(CameraEnum camType)
{
_virtualCameraDict.Remove(camType);
_camInitStateDict.Remove(camType);
}
public void RemoveAllCam()
{
_virtualCameraDict.Clear();
_camInitStateDict.Clear();
// 场景卸载后虚拟相机已销毁,但 _curCameraEnum 若保留会导致读档时误判「已在目标机位」而跳过切换,画面卡在诊室。
_curCameraEnum = CameraEnum.Default;
}
#endregion
/// <summary>
/// 检查当前是否需要切相机
/// </summary>
/// <param name="targetCameraState"></param>
/// <returns>是就不用切相机</returns>
public bool EqualToCameraState(CameraEnum targetCameraState)
{
return _curCameraEnum == targetCameraState;
}
public CameraEnum CurrentCameraEnum => _curCameraEnum;
public int RegisteredCameraCount => _virtualCameraDict.Count;
/// <summary>
/// 切换相机
/// </summary>
/// <param name="camType">目标相机</param>
/// <param name="immediate">true 则跳过过渡动画立即切换(适用于读档等场景)</param>
public IEnumerator SwitchCamera(CameraEnum camType, bool immediate = false)
{
var switchDuration = SwitchCameraSync(camType, immediate);
if (switchDuration > 0f)
{
yield return new WaitForSeconds(switchDuration);
}
else
{
// 至少等待两帧,确保相机切换完成
yield return null;
yield return null;
}
}
private float SwitchCameraSync(CameraEnum camType, bool immediate = false)
{
if (_curCameraEnum == camType)
{
return 0;
}
if (!_virtualCameraDict.ContainsKey(camType))
{
Debug.LogWarning($"SwitchCamera: {camType} 相机未注册");
return 0;
}
var activeBrain = _orthoCameraBrain;
CinemachineBlendDefinition originalBlend = default;
CinemachineBlenderSettings savedCustomBlends = null;
if (immediate)
{
originalBlend = activeBrain.m_DefaultBlend;
activeBrain.m_DefaultBlend = new CinemachineBlendDefinition(
CinemachineBlendDefinition.Style.Cut, 0f);
// Custom Blends 会覆盖 DefaultBlendimmediate 时必须临时禁用,否则仍走 1s 缓动。
savedCustomBlends = activeBrain.m_CustomBlends;
activeBrain.m_CustomBlends = null;
}
_virtualCameraDict.TryGetValue(_curCameraEnum, out var fromVCam);
var toVCam = _virtualCameraDict[camType];
// 设置虚拟相机优先级
foreach (var camPair in _virtualCameraDict)
{
camPair.Value.Priority = camPair.Key == camType ? HighPriority : LowPriority;
}
_curInitState = _camInitStateDict[camType];
var lastCamEnum = _curCameraEnum;
_curCameraEnum = camType;
if (immediate)
{
ResetVirCam(lastCamEnum);
activeBrain.ManualUpdate();
var blend = originalBlend;
var customBlends = savedCustomBlends;
ActionKit.DelayFrame(2, () =>
{
activeBrain.m_DefaultBlend = blend;
activeBrain.m_CustomBlends = customBlends;
}).StartGlobal();
return 0f;
}
// CinemachineBrain.LookupBlend 在 2.x 中是 private,这里重写其等价逻辑:
// Custom Blends 资产里有匹配项就用它,否则回退到 Default Blend。
var blendTime = ResolveBlendTime(activeBrain, fromVCam, toVCam);
ActionKit.Delay(blendTime, () => ResetVirCam(lastCamEnum)).StartGlobal();
return blendTime;
}
private static float ResolveBlendTime(
CinemachineBrain brain,
ICinemachineCamera fromCam,
ICinemachineCamera toCam)
{
var blend = brain.m_DefaultBlend;
if (brain.m_CustomBlends != null)
{
var fromName = fromCam != null ? fromCam.Name : string.Empty;
var toName = toCam != null ? toCam.Name : string.Empty;
blend = brain.m_CustomBlends.GetBlendForVirtualCameras(fromName, toName, blend);
}
if (CinemachineCore.GetBlendOverride != null)
{
blend = CinemachineCore.GetBlendOverride(fromCam, toCam, blend, brain);
}
return blend.BlendTime;
}
public void ResetCamera()
{
_curCameraEnum = CameraEnum.Default;
RemoveAllCam();
}
private void ResetVirCam(CameraEnum cameraEnum)
{
if (cameraEnum == CameraEnum.Default)
{
return;
}
if (!_virtualCameraDict.TryGetValue(cameraEnum, out var virCam))
{
return;
}
var camState = _camInitStateDict[cameraEnum];
virCam.VirtualCameraGameObject.transform.localPosition = camState.pos;
var camObj = virCam.VirtualCameraGameObject.GetComponent<CinemachineVirtualCamera>();
camObj.m_Lens.OrthographicSize = camState.initOrthoSize;
}
private Tween TransCamera(Vector3 targetPos, float targetOrthographicSize, float duration)
{
// 寻找现在正在激活的相机
if (_orthoCameraBrain == null)
{
Debug.LogWarning("TransCamera: CinemachineBrain is null.");
return null;
}
var curCamera = _orthoCameraBrain.ActiveVirtualCamera;
if (curCamera == null)
{
Debug.LogWarning("TransCamera: No active virtual camera found.");
return null;
}
var sq = DOTween.Sequence();
// 移动
sq.Join(curCamera.DOLocalMove(targetPos, duration));
// 缩放
sq.Join(curCamera.DOOrthoSize(targetOrthographicSize, duration));
return sq;
}
private Tween ReturnInitCamera(float duration)
{
return TransCamera(_curInitState.pos, _curInitState.initOrthoSize, duration);
}
public IEnumerator TransCameraAsync(Vector3 targetPos, float targetOrthographicSize, float duration)
{
var tween = TransCamera(targetPos, targetOrthographicSize, duration);
if (tween != null)
{
yield return tween.WaitForCompletion();
}
}
public IEnumerator ReturnInitCameraAsync(float duration)
{
var tween = ReturnInitCamera(duration);
if (tween != null)
{
yield return tween.WaitForCompletion();
}
}
public IEnumerator ShakeCamera(float duration, float strength, int vibrate)
{
if (_orthoCameraBrain == null)
{
Debug.LogWarning("ShakeCamera: CinemachineBrain is null.");
yield break;
}
var curCamera = _orthoCameraBrain.ActiveVirtualCamera;
if (curCamera == null)
{
Debug.LogWarning("ShakeCamera: No active virtual camera found.");
yield break;
}
var transform = curCamera.VirtualCameraGameObject.transform;
// 停止并清理之前的震动,防止多个 tween 叠加导致无法回归原点
if (_shakeTween != null && _shakeTween.IsActive())
{
_shakeTween.Kill();
if (_camInitStateDict.TryGetValue(_curCameraEnum, out var camState))
{
transform.localPosition = camState.pos;
}
}
_shakeTween = transform
.DOShakePosition(duration, strength, vibrate, 90, false, false)
.OnComplete(() => _shakeTween = null);
yield return _shakeTween.WaitForCompletion();
}
/// <summary>
/// 获取当前激活的相机
/// </summary>
public Camera GetCurrentCamera()
{
return _orthoMainCamera;
}
/// <summary>
/// 获取鼠标在世界中的位置
/// </summary>
/// <param name="mousePoint">鼠标界面位置</param>
/// <returns>世界位置</returns>
public static Vector3 GetMouseWorldPos(Vector3 mousePoint)
{
var cam = Instance._orthoMainCamera;
if (cam == null) return Vector3.zero;
var pos = cam.ScreenToWorldPoint(mousePoint);
return new Vector3(pos.x, pos.y, 0);
}
public static Vector3 GetScreenPos(Vector3 worldPos)
{
var cam = Instance._orthoMainCamera;
if (cam == null) return Vector3.zero;
worldPos.z = 0;
return cam.WorldToScreenPoint(worldPos);
}
}
public static class CinemachineExtension
{
public static TweenerCore<Vector3, Vector3, VectorOptions> DOLocalMove(this ICinemachineCamera camera,
Vector3 targetPos, float duration)
{
var transform = camera.VirtualCameraGameObject.gameObject.transform;
return transform.DOLocalMove(targetPos, duration);
}
public static TweenerCore<float, float, FloatOptions> DOOrthoSize(this ICinemachineCamera camera,
float endValue, float duration)
{
var virtualCamera = camera.VirtualCameraGameObject.GetComponent<CinemachineVirtualCamera>();
var currentOrthoSize = virtualCamera.m_Lens.OrthographicSize;
return DOTween.To(() => currentOrthoSize, x => currentOrthoSize = x, endValue, duration)
.OnUpdate(() => virtualCamera.m_Lens.OrthographicSize = currentOrthoSize);
}
}
public static class CameraCommand
{
[YarnCommand("return_init_camera")]
public static IEnumerator ReturnInitCamera(float duration = 1f)
{
return CameraKit.Instance.ReturnInitCameraAsync(duration);
}
[YarnCommand("trans_camera")]
public static IEnumerator TransCameraAsync(float x, float y, float targetOrthographicSize, float duration)
{
float z = CameraKit.Instance.GetCurrentCamera()?.transform.position.z ?? -10f;
Vector3 targetPos = new Vector3(x, y, z);
return CameraKit.Instance.TransCameraAsync(targetPos, targetOrthographicSize, duration);
}
[YarnCommand("shake_camera")]
public static IEnumerator ShakeCamera(float duration, float strength, int vibrate = 10)
{
return CameraKit.Instance.ShakeCamera(duration, strength, vibrate);
}
[YarnCommand("switch_camera")]
public static IEnumerator SwitchCamera(string targetStateStr, bool immediate = false)
{
if (!Enum.TryParse<CameraEnum>(targetStateStr, out var targetState))
{
Debug.LogWarning($"SwitchCamera: \"{targetStateStr}\" 不在 CameraEnum 枚举中");
yield break;
}
yield return CameraKit.Instance.SwitchCamera(targetState, immediate);
}
}
public enum CameraEnum
{
Default,
Clinic,
CoolingMachine,
BodyModule,
BodyModuleCenter,
Memory,
Logic,
Eye,
UF,
Expression,
EyeDeep,
ExpressionDeep,
Emo,
EmoDeep,
Op,
CutEmo,
CutMemory,
CutLogic,
CutEmoDeep,
CutMemoryDeep,
CutLogicDeep,
BreakCamera,
DreamCamera,
Subway,
SubwayTV,
Language,
LanguageDeep,
AnalysisMode,
AnalysisModeDeep,
SaleCamera,
SaleDeepCamera
}
}