using System;
using DG.Tweening;
using Framework;
using PhxhSDK;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.EventSystems;
[ExecuteAlways]
public class CameraController : MonoBehaviour
{
#region 固定值
///
/// 相机固定高度
///
private const float CameraPositionStartY = 27f;
///
/// 正交相机固定旋转值
///
private readonly Vector3 cameraStartRotationO = new Vector3(37.5f, -10f, 0f);
///
/// 透视相机固定旋转值
///
private readonly Vector3 cameraStartRotationP = new Vector3(45, 10, 0);
#endregion
private const float CameraStartSize = 12;
private float cameraStartFOV = 30;
private float _lastFOV = 30;
private Vector3 _lastPos = new Vector3(0, 0, 0);
//之前的缩放是6次滚轮从最小放到最大,故每一次缩放的值为1/6f
private const float ZoomStep = 1 / 12f;
[Tooltip("控制缩放响应曲线")] public float zoomSensitivity = 2.0f;
[Tooltip("缩放速度与当前缩放比例的关系系数")] public float zoomScaleFactor = 0.75f;
[Tooltip("是否使用平滑缩放效果")] public bool smoothZoom = true;
[ShowIf("smoothZoom")] public float zoomSmoothTime = 0.2f;
public float leftMargin = 0.5f;
public float rightMargin = 0.5f;
public float topMargin = 0.5f;
public float bottomMargin = 0.5f;
[Header("Camera Movement Settings")] public float moveSpeedX = 0.19f;
public float moveSpeedY = 0.3f;
public float unitPerPixel = 0.01f;
[Tooltip("是否使用平滑移动效果")] public bool smoothPanning = false;
[ShowIf("smoothPanning")] [Tooltip("平移平滑时间,值越小响应越快")]
public float panSmoothTime = 0.08f;
[ShowIf("smoothPanning")] [Tooltip("惯性系数,控制释放后继续滑动的距离")] [Range(0, 1)]
public float panInertia = 0.8f;
[ShowIf("smoothPanning")] [Tooltip("最大平移速度限制")]
public float maxPanSpeed = 3000f;
[Tooltip("锁定相机时跟随目标的平滑时间")] public float followSmoothTime = 0.15f;
[Tooltip("锁定相机时跟随目标的最大速度")] public float followMaxSpeed = 3000f;
public float CameraMinSize = 6;
public float CameraMaxSize = 12;
public BoxCollider boxBounds;
public float CameraMinFOV = 15;
public float CameraMaxFOV = 30;
public float NearClipPlane = 0.03f;
public float FarClipPlane = 150;
private bool cannotMove = false;
private Transform cameraTarget;
private Vector3 cameraTargetOffest;
private bool _isScaleChange;
private Camera mainCamera;
public Camera MainCamera
{
get { return mainCamera; }
}
private bool isIgnoreZoomLimit;
public Bounds CameraBounds { get; set; }
private bool isOrthographic => mainCamera.orthographic;
//Camera拖动控制
public static bool FingerMoveEnable = true;
private float targetZoomScale;
private float zoomVelocity;
private Vector3 targetPanPosition;
private Vector3 panVelocity;
private Vector3 followVelocity;
private Vector2 lastDragDelta;
private float lastDragTime;
//size,将透视和正交相机的fov和orthographic size整合为统一数值
private float Size
{
get
{
if (isOrthographic)
return mainCamera.orthographicSize;
else
{
var fov = mainCamera.fieldOfView;
//这里乘(CameraMaxSize - CameraMinSize)/(CameraMaxFOV - CameraMinFOV)是将fov的区间变得和orthographic size区间一致
return fov * (CameraMaxSize - CameraMinSize) / (CameraMaxFOV - CameraMinFOV);
}
}
}
//缩放百分比,0表示放到最小,1表示放到最大
private float ZoomScale
{
get
{
if (isOrthographic)
return (CameraMaxSize - mainCamera.orthographicSize) / (CameraMaxSize - CameraMinSize);
else
return (CameraMaxFOV - mainCamera.fieldOfView) / (CameraMaxFOV - CameraMinFOV);
}
set
{
var t = Mathf.Clamp(value, 0, 1);
if (Math.Abs(ZoomScale - t) < float.Epsilon)
return;
if (isOrthographic)
mainCamera.orthographicSize = Mathf.Lerp(CameraMaxSize, CameraMinSize, t);
else
mainCamera.fieldOfView = Mathf.Lerp(CameraMaxFOV, CameraMinFOV, t);
}
}
private void Awake()
{
if (BusyWrapper.isBusy)
return;
mainCamera = GetComponent();
targetZoomScale = ZoomScale;
targetPanPosition = transform.position;
var useOrthographic = Application.isPlaying && CameraServiceLocator.CameraService.UseOrthographicMode;
if (useOrthographic)
{
mainCamera.orthographic = true;
mainCamera.orthographicSize = CameraStartSize;
}
else
{
mainCamera.orthographic = false;
mainCamera.fieldOfView = cameraStartFOV;
}
mainCamera.nearClipPlane = NearClipPlane;
mainCamera.farClipPlane = FarClipPlane;
CameraBounds = boxBounds != null ? boxBounds.bounds : CameraBounds;
if (!Application.isPlaying)
{
var rotEuler = useOrthographic ? cameraStartRotationO : cameraStartRotationP;
transform.rotation = Quaternion.Euler(rotEuler);
var currentPos = transform.position;
transform.position = new Vector3(currentPos.x, CameraPositionStartY, currentPos.z);
}
AdjustSelf();
}
private void OnEnable()
{
if (BusyWrapper.isBusy)
return;
if (Application.isPlaying)
{
//InputManager.Instance.OnFingerMove += OnFingerMove2;
EventManager.Instance.Register(EventManager.EventName.Level_OnDrag, _OnDragMove);
EventManager.Instance.Register(EventManager.EventName.Level_OnEndDrag, OnEndDrag);
InputManager.Instance.OnFingersMove += OnFingersMove;
InputManager.Instance.OnFingerUp += _OnFingerUp;
}
}
private void OnDisable()
{
if (BusyWrapper.isBusy)
return;
if (Application.isPlaying)
{
if (InputManager.Instance == null) return;
//InputManager.Instance.OnFingerMove -= OnFingerMove2;
EventManager.Instance.Unregister(EventManager.EventName.Level_OnDrag, _OnDragMove);
EventManager.Instance.Unregister(EventManager.EventName.Level_OnEndDrag, OnEndDrag);
InputManager.Instance.OnFingersMove -= OnFingersMove;
InputManager.Instance.OnFingerUp -= _OnFingerUp;
}
}
private void _OnDragMove(PointerEventData pointerData)
{
if (!FingerMoveEnable || cannotMove)
return;
Vector3 newPosition;
var startPos = pointerData.pressPosition;
#if UNITY_EDITOR
Vector2 delta = pointerData.delta;
#else
Vector2 delta = pointerData.delta * 0.5f; // 移动设备拖动速度减慢,临时处理
#endif
// 记录拖动速度数据,用于惯性计算
lastDragDelta = delta;
lastDragTime = Time.time;
CameraManager.IsMainCameraMoving = true;
var oldRay = mainCamera.ScreenPointToRay(startPos);
var newRay = mainCamera.ScreenPointToRay(startPos + delta);
var layerMask = 1 << LayerMask.NameToLayer("Ground");
if (Physics.Raycast(oldRay, out var oldHit, 1000, layerMask) &&
Physics.Raycast(newRay, out var newHit, 1000, layerMask))
{
var offset = oldHit.point - newHit.point;
if (smoothPanning)
{
// 在平滑模式下设置目标位置
targetPanPosition = transform.position + offset;
}
else
{
// 立即移动
transform.Translate(offset, Space.World);
AdjustSelf();
}
}
else
{
Vector2 touchDeltaPosition = -delta;
float ratio = Size * unitPerPixel;
Vector2 cameraMove = new(touchDeltaPosition.x * moveSpeedX * ratio,
touchDeltaPosition.y * moveSpeedY * ratio);
var cameraTransform = transform;
var moveY = cameraTransform.up;
moveY.y = 0;
moveY = moveY.normalized * cameraMove.y;
var moveX = cameraTransform.right;
moveX.y = 0;
moveX = moveX.normalized * cameraMove.x;
if (smoothPanning)
{
// 在平滑模式下设置目标位置
targetPanPosition = transform.position + moveX + moveY;
}
else
{
// 立即移动
transform.Translate(moveX + moveY, Space.World);
AdjustSelf();
}
}
}
// 处理拖动结束时的惯性
public void OnEndDrag(PointerEventData pointerData)
{
if (cannotMove)
{
CameraManager.IsMainCameraMoving = false;
return;
}
if (smoothPanning && panInertia > 0 && Time.time - lastDragTime < 0.1f)
{
// 计算惯性位移
Vector2 touchDeltaPosition = -lastDragDelta * panInertia;
float ratio = Size * unitPerPixel;
Vector2 inertiaMove = new(touchDeltaPosition.x * moveSpeedX * ratio,
touchDeltaPosition.y * moveSpeedY * ratio);
var cameraTransform = transform;
var moveY = cameraTransform.up;
moveY.y = 0;
moveY = moveY.normalized * inertiaMove.y;
var moveX = cameraTransform.right;
moveX.y = 0;
moveX = moveX.normalized * inertiaMove.x;
// 应用惯性
targetPanPosition += moveX + moveY;
}
CameraManager.IsMainCameraMoving = false;
}
private void OnFingersMove(float delta)
{
if (!FingerMoveEnable)
{
return;
}
if (CameraServiceLocator.CameraService.IsGuiding) // 引导中禁止缩放
return;
// 缩放系数随当前缩放程度变化,缩放越小,缩放速度越慢,提供更精确的控制
float currentZoomFactor = ZoomStep * zoomSensitivity;
CameraManager.IsMainCameraMoving = true;
// DebugUtil.LogP($"OnFingersMove");
// 根据当前缩放比例调整缩放速度
if (zoomScaleFactor > 0)
{
float scaleModifier = Mathf.Lerp(0.5f, 1.5f, ZoomScale * zoomScaleFactor);
currentZoomFactor *= scaleModifier;
}
//delta小于0是缩小
var zoomDelta = delta < 0 ? -currentZoomFactor : currentZoomFactor;
//修改目标缩放值
_isScaleChange = true;
if (smoothZoom)
{
targetZoomScale = Mathf.Clamp01(targetZoomScale + zoomDelta);
}
else
{
//直接缩放,无平滑效果
ZoomScale += zoomDelta;
AdjustSelf();
}
}
private void _OnFingerUp(Vector2 screenPos)
{
CameraManager.IsMainCameraMoving = false;
}
private void LateUpdate()
{
if (BusyWrapper.isBusy)
return;
// 处理平滑缩放
bool isScaled = Math.Abs(ZoomScale - targetZoomScale) > 0.01f;
bool isFovChange = Math.Abs(_lastFOV - mainCamera.fieldOfView) > 0.01f;
if (smoothZoom && isScaled)
{
// 使用SmoothDamp实现平滑缩放效果
float newZoomScale = Mathf.SmoothDamp(ZoomScale, targetZoomScale, ref zoomVelocity, zoomSmoothTime);
ZoomScale = newZoomScale;
AdjustSelf();
}
// 处理平滑平移(当相机允许手动移动时才生效)
if (!cannotMove && smoothPanning && Vector3.Distance(transform.position, targetPanPosition) > 0.01f)
{
// 限制最大速度
Vector3 newPosition = Vector3.SmoothDamp(transform.position, targetPanPosition,
ref panVelocity, panSmoothTime, maxPanSpeed);
transform.position = newPosition;
AdjustSelf();
}
// 当关卡禁止玩家滑动镜头时,相机会平滑跟随指定目标(可带偏移)
if (cannotMove && cameraTarget != null)
{
var targetPos = cameraTarget.position + cameraTargetOffest;
// 计算以目标为视野中心的理想相机位置
var desiredPos = CalculateCell2CameraPosition(targetPos);
if (followSmoothTime > 0f)
{
var newPos = Vector3.SmoothDamp(transform.position, desiredPos, ref followVelocity,
followSmoothTime, followMaxSpeed);
transform.position = newPos;
}
else
{
transform.position = desiredPos;
}
AdjustSelf();
}
if (_lastPos != mainCamera.transform.position)
{
_lastPos = mainCamera.transform.position;
CameraManager.IsMainCameraMoving = true;
return;
}
else
{
CameraManager.IsMainCameraMoving = false;
}
if (_isScaleChange && !isScaled)
{
//DebugUtil.LogP($"--- Update _isScaleChange:{_isScaleChange}, isScaled{isScaled}");
CameraManager.IsMainCameraMoving = false;
_isScaleChange = false;
}
else if (isFovChange)
{
_lastFOV = mainCamera.fieldOfView;
// DebugUtil.LogP($"--- Update _isScaleChange:{_isScaleChange}, isScaled{isScaled}");
// DebugUtil.LogP($"==== Update isFovChange, isScaled{isScaled}");
CameraManager.IsMainCameraMoving = true;
EventManager.Instance.Send(EventManager.EventName.Camera_FovChanged);
}
else
{
CameraManager.IsMainCameraMoving = false;
}
}
public void KeepCenter(Vector3 centerPos)
{
var cacheTrans = mainCamera.transform;
var oldPos = cacheTrans.position;
oldPos.x = centerPos.x - 5f;
oldPos.z = centerPos.z - 25f;
// cacheTrans.position = oldPos;
CameraManager.IsMainCameraMoving = true;
EventManager.Instance.Send(EventManager.EventName.CameraMoveStateChange);
cacheTrans.DOMove(oldPos, 0.3f).OnUpdate(AdjustSelf).OnComplete(() =>
{
CameraManager.IsMainCameraMoving = false;
EventManager.Instance.Send(EventManager.EventName.CameraMoveStateChange);
});
}
///
/// 营地相机保持中心位置
///
///
public void CampKeepCenter(Vector3 centerPos)
{
Transform cacheTrans = mainCamera.transform;
Vector3 newPos = cacheTrans.position;
newPos.x = centerPos.x + 17.5f;
newPos.z = centerPos.z + 30.5f;
cacheTrans.DOMove(newPos, 0.3f).OnUpdate(AdjustSelf); //.OnComplete(() => { targetZoomScale = 1f; });
targetZoomScale = 1f;
}
///
/// 耀斑选关保持中心位置
///
///
public void FlareLevelKeepCenter(Vector3 centerPos)
{
Vector3 newPos = new(centerPos.x + 2f, mainCamera.transform.position.y, centerPos.z + 15f);
CameraManager.IsMainCameraMoving = true;
EventManager.Instance.Send(EventManager.EventName.CameraMoveStateChange);
mainCamera.transform.DOMove(newPos, 0.3f).OnUpdate(AdjustSelf).OnComplete(() =>
{
CameraManager.IsMainCameraMoving = false;
EventManager.Instance.Send(EventManager.EventName.CameraMoveStateChange);
});
}
///
/// 军团战保持中心位置
///
///
public void LegionPveKeepCenter(Vector3 centerPos, float maxDistance)
{
Transform cacheTrans = mainCamera.transform;
Vector3 newPos = cacheTrans.position;
newPos.x = centerPos.x - 5f;
newPos.z = centerPos.z - 25f;
cacheTrans.DOMove(newPos, 0.3f).OnUpdate(AdjustSelf);
if (maxDistance > 200f)
targetZoomScale = 0f;
else
targetZoomScale = 1f - maxDistance / 200f;
}
///
/// 营地还原相机fov
///
public void CampRevertFov()
{
targetZoomScale = 0f;
}
Vector3 CalculateHitHorizontalPosition(Vector3 startPosition, Vector3 dir, float y)
{
if (dir.y == 0)
return Vector3.zero;
var p1 = startPosition; // 直线上一点
var v = dir; // 直线的方向向量
float x, z;
// 已知直线上一点 p1(x1, y1, z1) 和 直线的方向向量 v(m, n, p)
// 根据点向式
// (x - x1) / m = (y - y1) / n = (z - z1) / p
// 有:
// (x - p1.x) / v.x = (y - p1.y) / v.y = (z - p1.z) / v.z
// 有:
// x = p1.x + v.x * (y - p1.y) / v.y
// z = p1.z + v.z * (y - p1.y) / v.y
// 与平面 y = y 的交点坐标为
x = p1.x + v.x * (y - p1.y) / v.y;
z = p1.z + v.z * (y - p1.y) / v.y;
return new Vector3(x, y, z);
}
public void InitializeCameraPosition(Vector3 targetPosition, MapData.CameraInfo cameraInfo = null,
float rightM = 0.5f, float leftM = 0.5f, float topM = 0.5f, float bottomM = 0.5f)
{
var useOrthographic = Application.isPlaying && CameraServiceLocator.CameraService.UseOrthographicMode;
mainCamera.transform.position = CalculateCell2CameraPosition(targetPosition);
// 进入关卡初始化相机边距
if (cameraInfo != null)
{
SetCameraFOVSize(cameraInfo.initialFOV);
}
rightMargin = rightM;
leftMargin = leftM;
topMargin = topM;
bottomMargin = bottomM;
var rotEuler = useOrthographic ? cameraStartRotationO : cameraStartRotationP;
mainCamera.transform.rotation = Quaternion.Euler(rotEuler);
var currentPos = transform.position;
transform.position = new Vector3(currentPos.x, CameraPositionStartY, currentPos.z);
AdjustSelf();
}
///
/// 设置相机是否移动参数
///
/// 是否可以移动
/// 移动跟随物体
/// 偏移位置
public void SetCameraMoveParam(bool notMove = false, Transform target = null, Vector3 offest = default)
{
cannotMove = notMove;
cameraTarget = target;
cameraTargetOffest = offest;
// 锁定相机并跟随目标时,重置跟随速度,让后续 SmoothDamp 从当前帧开始平滑追踪
if (cannotMove && cameraTarget != null)
{
followVelocity = Vector3.zero;
}
}
private Vector3 CalculateCell2CameraPosition(Vector3 targetPosition)
{
var cameraTransform = mainCamera.transform;
var startPosition = cameraTransform.position;
var v = cameraTransform.forward;
float x, z;
z = targetPosition.z - v.z * (targetPosition.y - startPosition.y) / v.y;
x = targetPosition.x - v.x * (targetPosition.y - startPosition.y) / v.y;
return new Vector3(x, startPosition.y, z);
}
public void AdjustSelf()
{
mainCamera.transform.position = AdjustPosition(Size, out var isInsideBounds);
}
public void SetCameraFOVSize(float initialFOV)
{
if (mainCamera is null) return;
var fov = Mathf.Clamp(initialFOV, CameraMinFOV, CameraMaxFOV);
if (!mainCamera.orthographic)
{
cameraStartFOV = fov;
mainCamera.fieldOfView = fov;
targetZoomScale = ZoomScale;
}
}
private Vector3 _startPos;
private float _cacheZoomScale;
///
/// 记录相机初始视角
///
public void RecordStartPos()
{
_startPos = mainCamera.transform.position;
_cacheZoomScale = targetZoomScale;
}
///
/// 恢复相机视角 用于进入战斗
///
///
public void ResetToStartPos(float during = 0.3f)
{
if (mainCamera is null) return;
targetZoomScale = _cacheZoomScale;
// 如果当前相机处于锁定跟随状态,并且有跟随目标
if (cannotMove && cameraTarget != null)
{
var targetPos = cameraTarget.position + cameraTargetOffest;
var desiredPos = CalculateCell2CameraPosition(targetPos);
mainCamera.transform.DOMove(desiredPos, during)
.SetEase(Ease.InOutSine)
.OnUpdate(AdjustSelf)
.OnComplete(() =>
{
// 火车抖动:后续持续跟随仍然保持无平滑
followSmoothTime = 0;
});
}
else
{
mainCamera.transform.DOMove(_startPos, during).OnUpdate(AdjustSelf);
// .OnComplete(() => { targetZoomScale = 1f; });
}
}
private Vector3 AdjustPosition(float size, out bool IsInsideBounds)
{
var position = transform.position;
IsInsideBounds = true;
var center = CalculateHitHorizontalPosition(position, transform.forward, 0);
var left = center.x - leftMargin * size;
var right = center.x + rightMargin * size;
var top = center.z + topMargin * size;
var bottom = center.z - bottomMargin * size;
if (right - left < CameraBounds.max.x - CameraBounds.min.x)
{
if (left < CameraBounds.min.x)
{
center.x = CameraBounds.min.x + leftMargin * size;
IsInsideBounds = false;
}
else if (right > CameraBounds.max.x)
{
center.x = CameraBounds.max.x - rightMargin * size;
IsInsideBounds = false;
}
}
else
{
center.x = CameraBounds.center.x;
IsInsideBounds = false;
}
if (top - bottom < CameraBounds.max.z - CameraBounds.min.z)
{
if (bottom < CameraBounds.min.z)
{
center.z = CameraBounds.min.z + bottomMargin * size;
IsInsideBounds = false;
}
else if (top > CameraBounds.max.z)
{
center.z = CameraBounds.max.z - topMargin * size;
IsInsideBounds = false;
}
}
else
{
center.z = CameraBounds.center.z;
IsInsideBounds = false;
}
position = CalculateHitHorizontalPosition(center, transform.forward, position.y);
return position;
}
private void OnDrawGizmos()
{
if (BusyWrapper.isBusy)
return;
if (mainCamera != null)
{
Gizmos.color = Color.red;
var cameraTransform = transform;
var position = cameraTransform.position;
var size = Size;
var center = CalculateHitHorizontalPosition(position, cameraTransform.forward, 0);
var left = center.x - leftMargin * size;
var right = center.x + rightMargin * size;
var top = center.z + topMargin * size;
var bottom = center.z - bottomMargin * size;
Gizmos.DrawLineList(new ReadOnlySpan(new[]
{
new Vector3(left, 0.5f, top), new Vector3(left, 0.5f, bottom),
new Vector3(left, 0.5f, bottom), new Vector3(right, 0.5f, bottom),
new Vector3(right, 0.5f, bottom), new Vector3(right, 0.5f, top),
new Vector3(right, 0.5f, top), new Vector3(left, 0.5f, top),
}
)
);
CameraBounds = boxBounds == null ? CameraBounds : boxBounds.bounds;
Gizmos.DrawWireCube(CameraBounds.center, CameraBounds.size);
}
}
}