using System; using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.UIElements; public class CameraControllerTest : MonoBehaviour { public float PointVerticalSpeed; // 中心点纵向移动速度 public float PointHorizontalSpeed; // 中心点横向移动速度 public float CameraToPointSpeed; // 摄像机到中心点移动速度 public Scpoe CameraToPointScpoe; // 摄像机到中心点移动限制 public float PointVerticalRotationSpeed; // 绕中心点纵向旋转速度 public float PointHorizontalRotationSpeed; // 绕中心点横向旋转速度 public Scpoe PointVerticalRotationScpoe; // 绕中心点纵向旋转限制 public Scpoe PointHorizontalRotationScpoe; // 绕中心点横向旋转限制 public Transform PointScpoePlane; // 中心点所在平面 public List PointScpoeData; // 中心点限制范围 public bool IsPosition; // 是否开启移动 public bool IsRotation; // 是否开启旋转 public bool IsScale; // 是否开启摄像机到中心点移动 public float CameraToPointInit; // 摄像机到中心点插值 public float PointRotationInit; // 旋转插值 private Vector3 Point; // 操作中心点 private Vector3 Point_Temp; // 中心点临时位置 private Vector3 CameraToPoint; // 摄像机到中心点单位向量 private Vector2 MouseLValue; // 鼠标左键变量 private Vector2 MouseRValue; // 鼠标右键变量 private float MouseCValue; // 鼠标中键变量 void Start() { Init(); } /// /// 初始化 /// void Init() { Point = PointScpoePlane.transform.position; CameraToPoint = transform.position - Point; } /// /// 中心点操作 /// void PointOperation() { Point_Temp = Point - Vector3.ProjectOnPlane(transform.right, Vector3.up ).normalized * PointHorizontalSpeed * MouseLValue.x - Vector3.ProjectOnPlane(transform.forward, Vector3.up ).normalized * PointVerticalSpeed * MouseLValue.y; Point = IsPointInPolygon(PointOnPlane(Point_Temp, PointScpoePlane.right, PointScpoePlane.forward, PointScpoePlane.position), PointScpoeData) ? Point_Temp : PointNotInScpoe(Point_Temp,PointScpoeData); CameraToPointInit += MouseCValue * CameraToPointSpeed; CameraToPointInit = Mathf.Clamp01(CameraToPointInit); transform.position = Point + CameraToPoint.normalized * Mathf.Lerp(CameraToPointScpoe.Min, CameraToPointScpoe.Max, CameraToPointInit); } /// /// 获取指定点位在指定平面内的坐标 /// /// 指定点位 /// 指定平面中心点 /// 指定点位在指定平面内的坐标 public static Vector3 PointOnPlane(Vector3 pos, Vector3 right, Vector3 forward, Vector3 plane) { Vector3 normal = Vector3.Cross(right, forward).normalized; Vector3 onPlane = Vector3.ProjectOnPlane(pos - plane, normal); Vector3 projectX = Vector3.Project(onPlane, right); float dirX = Vector3.Angle(projectX, right) - 90; Vector3 projectZ = Vector3.Project(onPlane, forward); float dirZ = Vector3.Angle(projectZ, forward) - 90; return new Vector3(projectX.magnitude * -Mathf.Sign(dirX), plane.y, projectZ.magnitude * -Mathf.Sign(dirZ)); } /// /// 判断点是否在平面内 /// /// /// /// public static bool IsPointInPolygon(Vector3 point, List 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; } Vector3 PointNotInScpoe(Vector3 outsidePoint, List 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; } /// /// 鼠标变量更新 /// void MouseValueUpdate() { if (Input.GetMouseButton(0)) MouseLValue = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y")) * Time.deltaTime; MouseLValue = Vector2.Lerp(MouseLValue, Vector2.zero, Time.deltaTime); if (Input.GetMouseButton(1)) MouseRValue = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y")) * Time.deltaTime; MouseRValue = Vector2.Lerp(MouseRValue, Vector2.zero, Time.deltaTime); MouseCValue = -Input.GetAxis("Mouse ScrollWheel") * Time.deltaTime; MouseCValue = Mathf.Lerp(MouseCValue, 0, Time.deltaTime); } /// /// 旋转 /// void RotationController() { Vector3 X = Vector3.Cross(CameraToPoint, Vector3.up).normalized; Vector3 Z = Vector3.Cross(Vector3.up, X).normalized; Vector3 Y = Vector3.Cross(X, Z).normalized; Debug.DrawLine(Point, Point + X * 10, Color.red); Debug.DrawLine(Point, Point + Z * 10, Color.blue); Debug.DrawLine(Point, Point + Y * 10, Color.green); float Lenght = CameraToPoint.magnitude; PointRotationInit -= MouseRValue.y; PointRotationInit = Mathf.Clamp01(PointRotationInit); Vector3 DirNext = Vector3.Lerp(Vector3.Lerp(Z, Y, PointVerticalRotationScpoe.Min / 90), Vector3.Lerp(Z, Y, PointVerticalRotationScpoe.Max / 90), PointRotationInit).normalized; transform.position = Point + DirNext * Lenght; transform.RotateAround(Point, Y, MouseRValue.x * PointHorizontalRotationSpeed); transform.LookAt(Point); CameraToPoint = transform.position - Point; } private void OnDrawGizmos() { Gizmos.color = Color.yellow; for (int i = 0; i < PointScpoeData.Count; i++) { Vector3 S = PointScpoeData[i]; S = PointScpoePlane.position + PointScpoePlane.right * S.x + PointScpoePlane.forward * S.z; Vector3 E = PointScpoeData[(i + 1) % PointScpoeData.Count]; E = PointScpoePlane.position + PointScpoePlane.right * E.x + PointScpoePlane.forward * E.z; Gizmos.DrawLine(S,E); } } void Update() { MouseValueUpdate(); RotationController(); PointOperation(); } } [Serializable] public struct Scpoe { public float Min; public float Max; } #if UNITY_EDITOR [CustomEditor(typeof(CameraControllerTest))] public class CameraControllerTestEditor : Editor { private CameraControllerTest cameraControllerTest; private void OnEnable() { cameraControllerTest = target as CameraControllerTest; SceneView.duringSceneGui += SceneGUI; } private void OnDisable() { SceneView.duringSceneGui -= SceneGUI; } private void SceneGUI(SceneView sceneView) { var plane = serializedObject.FindProperty("PointScpoePlane").objectReferenceValue as Transform; if (plane == null) return; var data = serializedObject.FindProperty("PointScpoeData"); GUIStyle style = new GUIStyle(); style.normal.textColor = Color.yellow; for (int i = 0; i < data.arraySize; i++) { var v3 = data.GetArrayElementAtIndex(i); Vector3 vector = v3.vector3Value; Vector3 point = plane.position + plane.right * vector.x + plane.forward * vector.z; Handles.Label(point, i.ToString(), style); Vector3 pointGUI = Handles.PositionHandle(point, plane.rotation); v3.vector3Value = CameraControllerTest.PointOnPlane(pointGUI, plane.right,plane.forward,plane.position); } // 这里应用值会实时覆盖Undo, 所以要排除 serializedObject.ApplyModifiedPropertiesWithoutUndo(); } } #endif