478 lines
16 KiB
C#
478 lines
16 KiB
C#
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();
|
||
}
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// 检查当前是否需要切相机
|
||
/// </summary>
|
||
/// <param name="targetCameraState"></param>
|
||
/// <returns>是就不用切相机</returns>
|
||
public bool EqualToCameraState(CameraEnum targetCameraState)
|
||
{
|
||
return _curCameraEnum == targetCameraState;
|
||
}
|
||
|
||
/// <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 会覆盖 DefaultBlend;immediate 时必须临时禁用,否则仍走 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
|
||
}
|
||
} |