Files
ZHGD_Web/Assets/3rds/Camera/Test/Scripts/CommonCamera.cs
2025-07-13 23:16:20 +08:00

899 lines
32 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine.EventSystems;
using System.Linq;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Hanatric.Unity.MonoComponent
{
public interface ICommonCameraInput
{
float MouseDeltaX { get; }
float MouseDeltaY { get; }
float MouseScrollWheel { get; }
bool IsMouse0Down { get; }
bool IsMouse0 { get; }
bool IsMouse0Up { get; }
bool IsMouse1Down { get; }
bool IsMouse1 { get; }
bool IsMouse1Up { get; }
/// <summary>
/// 当前鼠标是否可以开始交互
/// eg. 当鼠标悬浮在UI上时一般是不允许相机被交互的, 通过这个属性来指示是否接受交互开始
/// </summary>
bool CanMouseInteractStart { get; }
}
public class DefaultCommonCameraInput : ICommonCameraInput
{
public virtual float MouseDeltaX => Input.GetAxis("Mouse X");
public virtual float MouseDeltaY => Input.GetAxis("Mouse Y");
public virtual float MouseScrollWheel => Input.GetAxis("Mouse ScrollWheel");
public virtual bool IsMouse0Down => Input.GetMouseButtonDown(0);
public virtual bool IsMouse0 => Input.GetMouseButton(0);
public virtual bool IsMouse0Up => Input.GetMouseButtonUp(0);
public virtual bool IsMouse1Down => Input.GetMouseButtonDown(1);
public virtual bool IsMouse1 => Input.GetMouseButton(1);
public virtual bool IsMouse1Up => Input.GetMouseButtonUp(1);
public virtual bool CanMouseInteractStart
{
get
{
// 使用全局的默认EventSystem来判断鼠标是否浮在UI上
var eventSystem = EventSystem.current;
PointerEventData pointerEventData = new PointerEventData(eventSystem);
var mp = eventSystem.currentInputModule.input.mousePosition;
pointerEventData.position = mp;
List<RaycastResult> raycastResults = new List<RaycastResult>();
eventSystem.RaycastAll(pointerEventData, raycastResults);
return raycastResults.Count <= 0;
}
}
}
public class CommonCamera : MonoBehaviour
{
public static bool IsMouseScpoe;
/// <summary>
/// 设置目标
/// </summary>
public class Target
{
public Vector3 TargetPosition { get; set; }
public Quaternion SelfRotation { get; set; }
public float Distance { get; set; }
}
/// <summary>
/// 灵敏度
/// </summary>
public class Sensitivity
{
public float MoveSpeed { get; set; }
public float RotateSpeedHorizontal { get; set; }
public float RotateSpeedVertical { get; set; }
public float ScaleSpeed { get; set; }
public float MoveSlowdown { get; set; }
public float RotateSlowdown { get; set; }
public float ScaleSlowdown { get; set; }
public float LookTargetMoveSlowdown { get; set; }
public float LookTargetRotSlowdown { get; set; }
}
/// <summary>
/// 整合Target和Limit的Preset
/// </summary>
public class Preset
{
public Target Target { get; set; }
public Limit Limit { get; set; }
}
/// <summary>
/// 操作限制
/// </summary>
public class Limit
{
public Range VerticalRotateRange { get; set; }
public Range ScaleRange { get; set; }
public bool CanRotate { get; set; }
public bool CanMove { get; set; }
public bool CanScale { get; set; }
}
/// <summary>
/// 范围值
/// </summary>
[Serializable]
public class Range
{
[SerializeField]
private float min;
[SerializeField]
private float max;
public float Min { get => min; set => min = value; }
public float Max { get => max; set => max = value; }
public float ActualMin => Mathf.Min(min, max);
public float ActualMax => Mathf.Max(min, max);
}
/// <summary>
/// 相机使用的平面
/// </summary>
[SerializeField]
private Transform plane;
/// <summary>
/// 是否在Awake时初始化toPosition和toRotation值
/// </summary>
[SerializeField]
private bool isInitToOnAwake = true;
#if UNITY_EDITOR
[SerializeField]
private bool showGizoms = true;
#endif
#region
[SerializeField]
private float moveSpeed = 5;
[SerializeField]
private float rotateSpeedHorizontal = 5;
[SerializeField]
private float rotateSpeedVertical = 5;
[SerializeField]
private float scaleSpeed = 5;
#endregion
#region
[SerializeField]
private float moveSlowdown = 3;
[SerializeField]
private float rotateSlowdown = 3;
[SerializeField]
private float scaleSlowdown = 3;
#endregion
#region
[SerializeField]
private float lookTargetMoveSlowdown = 3;
[SerializeField]
private float lookTargetRotSlowdown = 3;
#endregion
#region
[SerializeField]
private Range verticalRotateRange;
[SerializeField]
private Range scaleRange;
[SerializeField]
private bool canMove = true;
[SerializeField]
private bool canRotate = true;
[SerializeField]
private bool canScale = true;
[SerializeField]
private List<Vector3> boundarys = new List<Vector3>()
{
new Vector3(-200, 0, -200),
new Vector3(200, 0, -200),
new Vector3(200, 0, 200),
new Vector3(-200, 0, 200),
};
public bool CanMove { get => canMove; set => canMove = value; }
public bool CanRotate { get => canRotate; set => canRotate = value; }
public bool CanScale { get => canScale; set => canScale = value; }
public Range ScaleRange { get => scaleRange; set => scaleRange = value; }
public Range VerticalRotateRange { get => verticalRotateRange; set => verticalRotateRange = value; }
#endregion
/// <summary>
/// 鼠标输入源
/// </summary>
public ICommonCameraInput CameraInput { get; set; } = new DefaultCommonCameraInput();
private new Camera camera;
public Camera Camera => camera;
private Vector3 deltaMove;
private float deltaRotHorizontal;
private float deltaRotVertical;
private float deltaScale;
private Vector3 toPosition;
private Quaternion toRotation;
private Vector3 centerPos;
private Vector3 ToForward => toRotation * Vector3.forward;
private Vector3 ToRight => toRotation * Vector3.right;
private Vector3 ToUp => toRotation * Vector3.up;
/// <summary>
/// 是否处于高度重置状态
/// 位于此状态下, 高度线会一直被拉回到0
/// </summary>
private bool isHeightReseting = true;
/// <summary>
/// 是否处于可交互状态
/// 此状态通常由鼠标按下时位置状态决定
/// </summary>
private bool isInteracting = false;
private void Awake()
{
camera = GetComponent<Camera>();
if (isInitToOnAwake)
{
toPosition = transform.position;
toRotation = transform.rotation;
}
if (plane != null)
{
var pos = GetIntersectWithRayToPlane(toPosition, ToForward, plane.up, plane.position);
if (Vector3.Angle(Vector3.ProjectOnPlane(ToForward, plane.up), ToForward) > 0.01f) centerPos = pos;
}
}
private void Update()
{
if (CameraInput.IsMouse0Up || CameraInput.IsMouse1Up) isInteracting = false;
// if (CameraInput.CanMouseInteractStart && (CameraInput.IsMouse0Down || CameraInput.IsMouse1Down)) isInteracting = true;
if (IsMouseScpoe && (CameraInput.IsMouse0Down || CameraInput.IsMouse1Down)) isInteracting = true;
float mouseX = CameraInput.MouseDeltaX;
float mouseY = CameraInput.MouseDeltaY;
float deltaTime = Time.deltaTime;
float centerDistance = Vector3.Distance(centerPos, toPosition);
float actualMoveSpeed = moveSpeed * centerDistance / 100;
float actualScaleSpeed = scaleSpeed * centerDistance / 100;
float mouseScroll = -CameraInput.MouseScrollWheel;
/*
if (CameraInput.CanMouseInteractStart && canScale)
{
deltaScale += mouseScroll * actualScaleSpeed;
}
*/
if (IsMouseScpoe && canScale)
{
deltaScale += mouseScroll * actualScaleSpeed;
}
#region ========== ==========
var scaleToPosition = Vector3.MoveTowards(toPosition, centerPos, -deltaScale);
var scaleToDistance = Vector3.Distance(centerPos, scaleToPosition);
if (scaleToDistance < scaleRange.ActualMin)
{
if (centerDistance > scaleRange.ActualMin)
{
deltaScale += scaleRange.ActualMin - scaleToDistance;
}
else if (deltaScale < 0)
{
deltaScale = 0;
}
}
if (scaleToDistance > scaleRange.ActualMax)
{
if (centerDistance < scaleRange.ActualMax)
{
deltaScale += scaleRange.ActualMax - scaleToDistance;
}
else if (deltaScale > 0)
{
deltaScale = 0;
}
}
#endregion
if (CameraInput.IsMouse1 && isInteracting && canRotate)
{
deltaRotHorizontal += rotateSpeedHorizontal * deltaTime * mouseX;
deltaRotVertical += rotateSpeedVertical * -deltaTime * mouseY;
}
#region ========== ==========
float currentVecticalAngle = toRotation.eulerAngles.x;
// 垂直角度范围为[-90,90]度, 表现在四元数转换的角度上为[270,360]&[0,90]度
if (currentVecticalAngle > 180) currentVecticalAngle -= 360;
float changeToAngle = currentVecticalAngle + deltaRotVertical;
float max = verticalRotateRange.ActualMax;
float min = verticalRotateRange.ActualMin;
// 垂直角度越过90度会导致画面逆向或不可计算, 这里强制限制在89度以下
max = Mathf.Clamp(max, -89f, 89f);
min = Mathf.Clamp(min, -89f, 89f);
if (changeToAngle > max)
{
if (currentVecticalAngle < max)
{
deltaRotVertical += max - changeToAngle;
}
else if (deltaRotVertical > 0)
{
deltaRotVertical = 0;
}
}
if (changeToAngle < min)
{
if (currentVecticalAngle > min)
{
deltaRotVertical += min - changeToAngle;
}
else if (deltaRotVertical < 0)
{
deltaRotVertical = 0;
}
}
#endregion
var deltaRotHorizontalOffset = Mathf.Lerp(0, deltaRotHorizontal, Time.deltaTime * rotateSlowdown);
deltaRotHorizontal -= deltaRotHorizontalOffset;
var deltaRotVerticalOffset = Mathf.Lerp(0, deltaRotVertical, Time.deltaTime * rotateSlowdown);
deltaRotVertical -= deltaRotVerticalOffset;
RotateAroundCenter(deltaRotHorizontalOffset, deltaRotVerticalOffset);
if (CameraInput.IsMouse0 && isInteracting && canMove)
{
Vector3 upDir = ToUp;
Vector3 rightDir = ToRight;
if (plane != null)
{
upDir = -Vector3.ProjectOnPlane(ToUp, plane.up).normalized;
rightDir = -Vector3.ProjectOnPlane(ToRight, plane.up).normalized;
}
// 先判断移动方向
float angleUp = Vector3.Angle(upDir, deltaMove);
float angleRight = Vector3.Angle(rightDir, deltaMove);
// 判断移动方向与鼠标方向相反, 则快速缓动将原移动量向降为0
if (mouseX != 0 && (angleRight > 90 ^ mouseX < 0) || mouseY != 0 && (angleUp > 90 ^ mouseY < 0))
{
deltaMove = Vector3.Lerp(deltaMove, Vector3.zero, Time.deltaTime * moveSlowdown * 3);
}
deltaMove += actualMoveSpeed * deltaTime * (mouseX * rightDir + mouseY * upDir);
if (mouseX != 0 || mouseY != 0)
{
isHeightReseting = true;
}
}
#region ========== ==========
if (plane != null)
{
var toCenterPos = centerPos + deltaMove;
var toCenterPosXZ = GetPointXZFromProjectedPlane(toCenterPos, plane.forward, plane.right, plane.position);
if (!IsPointInPolygon(toCenterPosXZ, boundarys))
{
var closestPoint = FindClosestPointOnPolygonWithOutsidePointApproximately(toCenterPosXZ, boundarys);
Vector3 deltaXZ = toCenterPosXZ - closestPoint;
Vector3 delta = plane.right * deltaXZ.x + plane.forward * deltaXZ.z;
deltaMove -= delta;
}
}
#endregion
var deltaMoveOffset = Vector3.Lerp(Vector3.zero, deltaMove, Time.deltaTime * moveSlowdown);
deltaMove -= deltaMoveOffset;
var deltaScaleOffset = Mathf.Lerp(0, deltaScale, Time.deltaTime * scaleSlowdown);
deltaScale -= deltaScaleOffset;
toPosition += deltaMoveOffset;
centerPos += deltaMoveOffset;
if (Vector3.Distance(Vector3.MoveTowards(toPosition, centerPos, -deltaScaleOffset), centerPos) > 1e-4)
{
toPosition = Vector3.MoveTowards(toPosition, centerPos, -deltaScaleOffset);
if (float.IsNaN(toPosition.x) || float.IsNaN(toPosition.y) || float.IsNaN(toPosition.z))
{
toPosition = Vector3.zero;
}
}
// 重置高度
if (isHeightReseting && plane != null)
{
Vector3 centerProjectedPoint = Vector3.ProjectOnPlane(centerPos, plane.up) + Vector3.Project(plane.position, plane.up);
var centerProjectedPointOffset = Vector3.Lerp(Vector3.zero, centerPos - centerProjectedPoint, Time.deltaTime * moveSlowdown);
centerPos -= centerProjectedPointOffset;
toPosition -= centerProjectedPointOffset;
}
// 设置坐标和角度
transform.SetPositionAndRotation(toPosition, toRotation);
}
/// <summary>
/// 相机绕中心旋转
/// </summary>
/// <param name="angleHorizontal"></param>
/// <param name="angleVertical"></param>
private void RotateAroundCenter(float angleHorizontal, float angleVertical)
{
var center = centerPos;
// 先计算水平旋转, 在计算竖直旋转, 必须按顺序计算
// (竖直旋转依赖于目标右方向, 目标右方向由旋转后的Rotation得来)
#region ========== ==========
Quaternion rotHorizontal = Quaternion.AngleAxis(angleHorizontal, Vector3.up);
var horizontal = toPosition - center;
horizontal = rotHorizontal * horizontal;
toPosition = center + horizontal;
toRotation = rotHorizontal * toRotation;
#endregion
#region ========== ==========
Quaternion rotVertical = Quaternion.AngleAxis(angleVertical, ToRight);
var vertical = toPosition - center;
vertical = rotVertical * vertical;
toPosition = center + vertical;
toRotation = rotVertical * toRotation;
#endregion
// 相机在跳转聚焦物体过程中, Slerp函数会产生不等于0的z轴角度, 导致水平度偏移, 这里获取z轴角度消除偏差
#region ========== ==========
float checkZ = toRotation.eulerAngles.z;
Quaternion rotCheckZ = Quaternion.AngleAxis(-checkZ, ToForward);
toRotation = rotCheckZ * toRotation;
#endregion
}
public void SetCenterPosition(Vector3 centerPosition)
{
var dis = centerPosition - centerPos;
deltaMove = dis;
isHeightReseting = false;
}
public void SetCenterPosition(Vector3 centerPosition,float targetDistance)
{
SetCenterPosition(centerPosition,transform.rotation, targetDistance);
}
public void SetCenterPosition(Vector3 centerPosition, Quaternion targetRotation, float targetDistance)
{
isHeightReseting = false;
Vector3 toForward = ToForward;
Vector3 targetForward = targetRotation * Vector3.forward;
float angleToForwardYAxis = Vector3.Angle(Vector3.up, toForward);
float angleTargetForwardYAxis = Vector3.Angle(Vector3.up, targetForward);
deltaRotVertical = angleTargetForwardYAxis - angleToForwardYAxis;
Vector3 toForwardXZ = new Vector3(toForward.x, 0, toForward.z);
Vector3 targetForwardXZ = new Vector3(targetForward.x, 0, targetForward.z);
deltaRotHorizontal = Vector3.SignedAngle(toForwardXZ, targetForwardXZ, Vector3.up);
var targetCenterDistance = centerPosition - centerPos;
deltaMove = targetCenterDistance;
var toCenterDistance = (toPosition - centerPos).magnitude;
deltaScale = targetDistance - toCenterDistance;
}
/// <summary>
/// 加载鼠标灵敏度, 一般来说程序生命周期中只加载一份灵敏度参数
/// </summary>
/// <param name="sensitivity"></param>
public void LoadSensitivity(Sensitivity sensitivity)
{
moveSpeed = sensitivity.MoveSpeed;
rotateSpeedHorizontal = sensitivity.RotateSpeedHorizontal;
rotateSpeedVertical = sensitivity.RotateSpeedVertical;
scaleSpeed = sensitivity.ScaleSpeed;
moveSlowdown = sensitivity.MoveSlowdown;
rotateSlowdown = sensitivity.RotateSlowdown;
scaleSlowdown = sensitivity.ScaleSlowdown;
lookTargetMoveSlowdown = sensitivity.LookTargetMoveSlowdown;
lookTargetRotSlowdown = sensitivity.LookTargetRotSlowdown;
}
/// <summary>
/// 加载目标, 加载后相机会立刻开始向目标变化
/// 一般会和 LoadLimit 一起使用
/// </summary>
/// <param name="target"></param>
public void LoadTarget(Target target)
{
if (target is null) throw new ArgumentNullException(nameof(target));
SetCenterPosition(target.TargetPosition, target.SelfRotation, target.Distance);
}
/// <summary>
/// 加载相机限制, 加载后立刻生效
/// 一般会和 LoadTarget 一起使用
/// </summary>
/// <param name="limit"></param>
public void LoadLimit(Limit limit)
{
if (limit is null) throw new ArgumentNullException(nameof(limit));
verticalRotateRange = new Range
{
Min = limit.VerticalRotateRange.Min,
Max = limit.VerticalRotateRange.Max,
};
scaleRange = new Range
{
Min = limit.ScaleRange.Min,
Max = limit.ScaleRange.Max,
};
canMove = limit.CanMove;
canRotate = limit.CanRotate;
canScale = limit.CanScale;
}
/// <summary>
/// 加载预设
/// </summary>
/// <param name="preset"></param>
public void LoadPreset(Preset preset)
{
if (preset is null) throw new ArgumentNullException(nameof(preset));
LoadTarget(preset.Target);
LoadLimit(preset.Limit);
}
public Sensitivity SaveSensitivity()
{
return new Sensitivity
{
MoveSpeed = moveSpeed,
RotateSpeedHorizontal = rotateSpeedHorizontal,
RotateSpeedVertical = rotateSpeedVertical,
ScaleSpeed = scaleSpeed,
MoveSlowdown = moveSlowdown,
RotateSlowdown = rotateSlowdown,
ScaleSlowdown = scaleSlowdown,
LookTargetMoveSlowdown = lookTargetMoveSlowdown,
LookTargetRotSlowdown = lookTargetRotSlowdown,
};
}
public Target SaveTarget()
{
return new Target
{
TargetPosition = centerPos,
SelfRotation = toRotation,
Distance = (centerPos - toPosition).magnitude,
};
}
public Limit SaveLimit()
{
return new Limit
{
CanMove = canMove,
CanRotate = canRotate,
CanScale = canScale,
ScaleRange = new Range
{
Max = scaleRange.Max,
Min = scaleRange.Min,
},
VerticalRotateRange = new Range
{
Max = verticalRotateRange.Max,
Min = verticalRotateRange.Min,
},
};
}
public Preset SavePreset()
{
return new Preset
{
Limit = SaveLimit(),
Target = SaveTarget(),
};
}
/// <summary>
/// 获取射线和平面的交点
/// </summary>
/// <param name="point"></param>
/// <param name="direct"></param>
/// <param name="planeNormal"></param>
/// <param name="planePoint"></param>
/// <returns></returns>
private static Vector3 GetIntersectWithRayToPlane(Vector3 point, Vector3 direct, Vector3 planeNormal, Vector3 planePoint)
{
float d = Vector3.Dot(planePoint - point, planeNormal) / Vector3.Dot(direct.normalized, planeNormal);
return d * direct.normalized + point;
}
/// <summary>
/// 判断点在多边形内
/// X-Z平面
/// </summary>
/// <param name="point"></param>
/// <param name="polygon"></param>
/// <returns></returns>
public static bool IsPointInPolygon(Vector3 point, List<Vector3> polygon)
{
int polygonLength = polygon.Count, i = 0;
bool inside = false;
float pointX = point.x, pointZ = point.z;
float startX, startZ, endX, endZ;
Vector3 endPoint = polygon[polygonLength - 1];
endX = endPoint.x;
endZ = endPoint.z;
while (i < polygonLength)
{
startX = endX; startZ = endZ;
endPoint = polygon[i++];
endX = endPoint.x; endZ = endPoint.z;
inside ^= (endZ > pointZ ^ startZ > pointZ) && ((pointX - endX) < (pointZ - endZ) * (startX - endX) / (startZ - endZ));
}
return inside;
}
/// <summary>
/// 取某点相对对平面的XZ轴坐标分量 (即平面上的坐标)
/// </summary>
/// <param name="point"></param>
/// <param name="planeForward"></param>
/// <param name="planeRight"></param>
/// <param name="planePoint"></param>
/// <returns></returns>
public static Vector3 GetPointXZFromProjectedPlane(Vector3 point, Vector3 planeForward, Vector3 planeRight, Vector3 planePoint)
{
Vector3 planeNormal = Vector3.Cross(planeForward, planeRight).normalized;
Vector3 projectOnPlane = Vector3.ProjectOnPlane(point - planePoint, planeNormal);
Vector3 projectX = Vector3.Project(projectOnPlane, planeRight);
float angleX = Vector3.Angle(projectX, planeRight) - 90;
Vector3 projectZ = Vector3.Project(projectOnPlane, planeForward);
float angleZ = Vector3.Angle(projectZ, planeForward) - 90;
return new Vector3(projectX.magnitude * -Mathf.Sign(angleX), 0, projectZ.magnitude * -Mathf.Sign(angleZ));
}
/// <summary>
/// 找到 多边形上 的, 距离 多边形外部某点 的 最近距离点
/// 需要保证外部点和多边形在一个平面上
/// </summary>
/// <param name="outsidePoint"></param>
/// <param name="polygon"></param>
/// <returns></returns>
public static Vector3 FindClosestPointOnPolygonWithOutsidePointApproximately(Vector3 outsidePoint, List<Vector3> polygon)
{
// 先拿到点在边上的投射点
// 如果点在线段上, 则直接使用两点的距离作为点边最小距离
// 如果点超出线段范围, 则使用点到两个端点的最小值作为点边最小距离
// 遍历所有点边距离, 找到点边距离最小的, 则使用该线段上的点作为 最近距离点
float[] distances = new float[polygon.Count];
Vector3[] closestPoints = new Vector3[polygon.Count];
for (int i = 0; i < polygon.Count; i++)
{
Vector3 a = polygon[i];
Vector3 b = polygon[(i + 1) % polygon.Count];
Vector3 projectPoint = a + Vector3.Project(outsidePoint - a, b - a);
distances[i] = Vector3.Distance(projectPoint, outsidePoint);
closestPoints[i] = projectPoint;
float angle = Vector3.Angle(projectPoint - a, b - a);
if (angle > 90)
{
distances[i] = Vector3.Distance(outsidePoint, a);
closestPoints[i] = a;
}
angle = Vector3.Angle(projectPoint - b, a - b);
if (angle > 90)
{
distances[i] = Vector3.Distance(outsidePoint, b);
closestPoints[i] = b;
}
}
float minDistance = float.MaxValue;
Vector3 closestPoint = Vector3.zero;
for (int i = 0; i < polygon.Count; i++)
{
if (distances[i] < minDistance)
{
minDistance = distances[i];
closestPoint = closestPoints[i];
}
}
return closestPoint;
}
public Vector3 ForwardInPlane()
{
if (plane) return Vector3.ProjectOnPlane(ToForward, plane.up).normalized;
throw new ArgumentException("使用plane关联属性时需要保证plane不为空");
}
public Vector3 RightInPlane()
{
if (plane) return Vector3.ProjectOnPlane(ToRight, plane.up).normalized;
throw new ArgumentException("使用plane关联属性时需要保证plane不为空");
}
#if UNITY_EDITOR
private void OnDrawGizmos()
{
if (showGizoms)
{
Gizmos.color = Color.yellow;
var center = centerPos;
Gizmos.DrawLine(toPosition, center);
Gizmos.DrawWireSphere(center, 2f);
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(toPosition, 2f);
Gizmos.color = Color.green;
if (plane != null)
{
for (int i = 0; i < boundarys.Count; i++)
{
Vector3 v3 = boundarys[i];
v3 = plane.position + plane.right * v3.x + plane.forward * v3.z;
Vector3 v3Next = boundarys[(i + 1) % boundarys.Count];
v3Next = plane.position + plane.right * v3Next.x + plane.forward * v3Next.z;
Gizmos.DrawLine(v3, v3Next);
}
}
}
}
#endif
}
#if UNITY_EDITOR
[CustomEditor(typeof(CommonCamera))]
public class CommonCameraEditor : Editor
{
private CommonCamera commonCamera;
private void OnEnable()
{
commonCamera = target as CommonCamera;
SceneView.duringSceneGui += OnSceneGUI;
}
public override void OnInspectorGUI()
{
GUIStyle groupBoxStyle = new GUIStyle("GroupBox");
groupBoxStyle.padding = new RectOffset(16, 10, 10, 10);
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField("Script", MonoScript.FromMonoBehaviour(commonCamera), typeof(CommonCamera), false);
EditorGUI.EndDisabledGroup();
EditorGUILayout.BeginVertical(groupBoxStyle);
Property("showGizoms", "显示Gizmos");
Property("plane", "目标平面");
Property("isInitToOnAwake", "Awake时初始化目标坐标/角度");
EditorGUILayout.EndVertical();
EditorGUILayout.BeginVertical(groupBoxStyle);
Property("moveSpeed", "移动 速度");
Property("rotateSpeedHorizontal", "水平旋转 速度");
Property("rotateSpeedVertical", "垂直旋转 速度");
Property("scaleSpeed", "缩放 速度");
EditorGUILayout.EndVertical();
EditorGUILayout.BeginVertical(groupBoxStyle);
EditorGUILayout.HelpBox("缩放缓动 与 移动缓动 共享该值", MessageType.Info);
Property("moveSlowdown", "移动/缩放 缓动");
Property("rotateSlowdown", "旋转 缓动");
EditorGUILayout.EndVertical();
EditorGUILayout.BeginVertical(groupBoxStyle);
EditorGUILayout.LabelField("设置目标时的缓动");
Property("lookTargetMoveSlowdown", "移动/缩放 缓动");
Property("lookTargetRotSlowdown", "旋转 缓动");
EditorGUILayout.EndVertical();
EditorGUILayout.BeginVertical(groupBoxStyle);
Property("canMove", "允许移动");
Property("canRotate", "允许旋转");
Property("canScale", "允许缩放");
Property("verticalRotateRange", "垂直旋转角度限制");
Property("scaleRange", "缩放限制 (相机目标位置到相机朝向中心的距离)");
EditorGUILayout.EndVertical();
EditorGUILayout.BeginVertical(groupBoxStyle);
Property("boundarys", "中心移动限制");
EditorGUILayout.EndVertical();
serializedObject.ApplyModifiedProperties();
}
private void Property(string name, string label) => EditorGUILayout.PropertyField(serializedObject.FindProperty(name), new GUIContent(label));
private void OnDisable()
{
SceneView.duringSceneGui -= OnSceneGUI;
}
private void OnSceneGUI(SceneView sceneView)
{
var plane = serializedObject.FindProperty("plane").objectReferenceValue as Transform;
if (plane == null) return;
var boundarys = serializedObject.FindProperty("boundarys");
GUIStyle style = new GUIStyle();
style.normal.textColor = Color.white;
for (int i = 0; i < boundarys.arraySize; i++)
{
var v3 = boundarys.GetArrayElementAtIndex(i);
Vector3 src = v3.vector3Value;
Vector3 point = plane.position + plane.right * src.x + plane.forward * src.z;
Handles.Label(point, i + "", style);
Vector3 pointNext = Handles.PositionHandle(point, plane.rotation);
v3.vector3Value = CommonCamera.GetPointXZFromProjectedPlane(pointNext, plane.forward, plane.right, plane.position);
}
// 这里应用值会实时覆盖Undo, 所以要排除
serializedObject.ApplyModifiedPropertiesWithoutUndo();
}
}
#endif
}