Files
ZHGD_Web/Assets/CameraControllerTest/CameraControllerTest.cs
2025-07-13 23:16:20 +08:00

265 lines
9.4 KiB
C#

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<Vector3> 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();
}
/// <summary>
/// 初始化
/// </summary>
void Init()
{
Point = PointScpoePlane.transform.position;
CameraToPoint = transform.position - Point;
}
/// <summary>
/// 中心点操作
/// </summary>
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);
}
/// <summary>
/// 获取指定点位在指定平面内的坐标
/// </summary>
/// <param name="pos">指定点位</param>
/// <param name="plane">指定平面中心点</param>
/// <returns>指定点位在指定平面内的坐标</returns>
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));
}
/// <summary>
/// 判断点是否在平面内
/// </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;
}
Vector3 PointNotInScpoe(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;
}
/// <summary>
/// 鼠标变量更新
/// </summary>
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);
}
/// <summary>
/// 旋转
/// </summary>
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