Files
3d-bianpo/Assets/Scripts/App/CameraControl.cs
2026-03-03 11:30:53 +08:00

389 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class CameraControl : MonoBehaviour
{
[Header("移动速度")]
public float moveSpeed = 1.2f;
[Header("旋转速度")]
public float rotateSpeed = 45f;
[Header("缩放速度")]
public float zoomSpeed = 0.2f;
[Header("最小高度")]
public float minHeight = 0.1f;
[Header("最大高度")]
public float maxHeight = 100f;
[Header("地图中心")]
public Vector3 mapCenter = Vector3.zero;
[Header("最大距离")]
public float maxDistance = 3000f;
[Header("最小角度")]
public float minAngleX = -60f;
[Header("最大角度")]
public float maxAngleX = 88f;
[Header("射线检测提前量")]
public float raycastOffset = 0.1f; // 射线检测提前量
[SerializeField] private LayerMask collisionLayers; // 可碰撞的图层
[SerializeField] private string[] obstacleLayerNames = new string[] { "ObstacleLayer" }; // 障碍物图层名称数组
public float minMoveSpeed = 5f; // 最小移动速度,根据需求设置
// 高度阈值数组 (按升序排列)
[SerializeField] private float[] thresholds = { 27f, 50f, 100f, 150f, 200f };
// 对应速度值数组 (与阈值一一对应)
[SerializeField] private float[] speeds = { 3f, 15f, 30f, 50f, 80f };
//控制变量
private Vector3 viewPos_0; //鼠标左键初始坐标
private Vector3 viewPos_1; //鼠标右键初始坐标
private Vector3 startCameraPos; //相机初始位置
private Vector3 targetCameraPos; //相机目标位置
private Vector2 startCameraEuler; //初始角度
private Vector2 targetCameraEuler; //目标角度
//相机控制
private Camera mainCamera;
private bool isCameraCtrl = true; //相机是否可控制
private float screenAdapter = 1f; //屏幕长度比
//相机操作有效性判断
private bool isClickUI = false;
private void Awake()
{
mainCamera = Camera.main;
screenAdapter = Screen.width / (float)Screen.height;
targetCameraPos = transform.position;
targetCameraEuler = transform.eulerAngles;
if (collisionLayers == 0 && obstacleLayerNames.Length > 0)
{
collisionLayers = LayerMask.GetMask(obstacleLayerNames);
}
}
void Update()
{
//相机控制
if (isCameraCtrl)
{
//操作有效性判断
if (EventSystem.current != null)
{
if (Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1) || Input.GetMouseButtonDown(2))
{
isClickUI = EventSystem.current.IsPointerOverGameObject();
}
if (Input.GetMouseButtonUp(0) || Input.GetMouseButtonUp(1) || Input.GetMouseButtonUp(2))
{
isClickUI = false;
}
}
if (!isClickUI)
{
//平移控制
if (Input.GetMouseButtonDown(1) || Input.GetMouseButtonDown(2))
{
viewPos_0 = mainCamera.ScreenToViewportPoint(Input.mousePosition);
startCameraPos = transform.position;
startCameraEuler = transform.eulerAngles;
}
if (Input.GetMouseButton(1) || Input.GetMouseButton(2))
{
Vector3 currentViewPos = mainCamera.ScreenToViewportPoint(Input.mousePosition);
Vector3 dis = currentViewPos - viewPos_0;
dis = dis * GetMoveSpeed();
// 获取相机的右向量和上向量
Vector3 right = transform.right;
Vector3 up = transform.up;
// 分别计算水平和垂直方向的位移
float horizontalDis = -dis.x * screenAdapter;
float verticalDis = -dis.y * screenAdapter;
// 根据相机的右向量和上向量计算最终位移
Vector3 finalDis = right * horizontalDis + up * verticalDis;
targetCameraPos = startCameraPos + finalDis;
// Vector3 newTargetPos = startCameraPos + finalDis;
// newTargetPos = CheckCollisionOnMove(transform.position, newTargetPos);
// targetCameraPos = newTargetPos;
// Debug.Log("targetCameraPos" + targetCameraPos);
}
//旋转控制
if (Input.GetMouseButtonDown(0))
{
viewPos_1 = mainCamera.ScreenToViewportPoint(Input.mousePosition);
startCameraPos = transform.position;
startCameraEuler = transform.eulerAngles;
}
if (Input.GetMouseButton(0))
{
Vector2 angle = (mainCamera.ScreenToViewportPoint(Input.mousePosition) - viewPos_1) * rotateSpeed;
angle = new Vector3(-angle.y, angle.x * screenAdapter);
targetCameraEuler = startCameraEuler + angle;
}
//缩进控制
if (Input.mouseScrollDelta != Vector2.zero)
{
float moveSpeed = GetMoveSpeed() * zoomSpeed;
targetCameraPos = transform.position + transform.forward * moveSpeed * Input.mouseScrollDelta.y;
// Vector3 newTargetPos = targetCameraPos + transform.forward * moveSpeed * Input.mouseScrollDelta.y;
// newTargetPos = CheckCollisionOnMove(transform.position, newTargetPos);
// targetCameraPos = newTargetPos;
}
//相机运动
LimitCamera();
// CheckCollision(); // 检查碰撞
//控制相机运动
// 控制相机运动
if (!isCheckCollisionOnMove(transform.position, targetCameraPos)) // 无碰撞时执行移动
{
if (Vector3.Distance(transform.position, targetCameraPos) > 0.1f)
{
transform.position = Vector3.Lerp(transform.position, targetCameraPos, 5f * Time.deltaTime);
}
// 控制相机旋转
if (Quaternion.Angle(transform.rotation, Quaternion.Euler(targetCameraEuler)) > 0.1f)
{
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.Euler(targetCameraEuler), 5f * Time.deltaTime);
}
}
// if (Vector3.Distance(transform.position, targetCameraPos) > 0.1f)
// {
// transform.position = Vector3.Lerp(transform.position, targetCameraPos, 5f * Time.deltaTime);
// }
// // 控制相机旋转
// if (Quaternion.Angle(transform.rotation, Quaternion.Euler(targetCameraEuler)) > 0.1f)
// {
// transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.Euler(targetCameraEuler), 5f * Time.deltaTime);
// }
}
}
}
public bool isCheckCollisionOnMove(Vector3 position, Vector3 targetCameraPos)
{
Vector3 newPosition = CheckCollisionOnMove(position, targetCameraPos);
// 手动判断是否发生碰撞
bool hasCollision = newPosition != targetCameraPos;
return hasCollision;
}
//开启相机控制
public void StartCameraControl()
{
isCameraCtrl = true;
isClickUI = false;
viewPos_0 = mainCamera.ScreenToViewportPoint(Input.mousePosition);
viewPos_1 = mainCamera.ScreenToViewportPoint(Input.mousePosition);
startCameraPos = transform.position;
startCameraEuler = transform.eulerAngles;
targetCameraPos = transform.position;
targetCameraEuler = transform.eulerAngles;
}
//停止相机控制
public void StopCameraControl()
{
isCameraCtrl = false;
}
//获取相机移动速度
float GetMoveSpeed()
{
// // 计算高度差确保不小于0避免出现负速度
// float highDis = Mathf.Max(transform.position.y - minHeight, 0f);
// // 计算基础速度(基于高度的动态速度)
// float dynamicSpeed = moveSpeed + moveSpeed * highDis;
// // 确保速度不低于最小值,同时保留向上浮动的可能性
// float finalSpeed = Mathf.Max(dynamicSpeed, minMoveSpeed);
// // 可选:限制最大速度,防止过高高度下速度过快
// // finalSpeed = Mathf.Min(finalSpeed, maxMoveSpeed);
// // 保留整数化处理(如果需要)
// finalSpeed = Mathf.RoundToInt(finalSpeed);
// return finalSpeed;
// return transform.position.y > 80 ? 80f : transform.position.y > 30 ? 40f : 5f;
// // 高度阈值数组 (按升序排列)
float yPosition = transform.position.y;
// 检查每个阈值,返回对应速度
for (int i = 0; i < thresholds.Length; i++)
{
if (yPosition < thresholds[i])
{
return speeds[i];
}
}
Debug.Log("Speed: " + speeds[speeds.Length - 1]);
// 超过最高阈值,返回最高速度
return speeds[speeds.Length - 1];
}
//限制相机位置,角度
void LimitCamera()
{
//位置限制
targetCameraPos.y = Mathf.Clamp(targetCameraPos.y, minHeight, maxHeight);
float dis = Vector3.Distance(targetCameraPos, mapCenter);
if (dis > maxDistance)
{
Vector3 dir = (targetCameraPos - mapCenter).normalized;
targetCameraPos = mapCenter + dir * maxDistance;
}
//角度限制
if (targetCameraEuler.x > 180) { targetCameraEuler.x -= 360f; }
if (targetCameraEuler.x < -180) { targetCameraEuler.x += 360; }
if (targetCameraEuler.y > 180) { targetCameraEuler.y -= 360f; }
if (targetCameraEuler.y < -180) { targetCameraEuler.y += 360; }
targetCameraEuler.x = Mathf.Clamp(targetCameraEuler.x, minAngleX, maxAngleX);
}
// 修改CheckCollisionOnMove方法增加垂直方向检测
Vector3 CheckCollisionOnMove(Vector3 startPos, Vector3 targetPos)
{
Vector3 direction = targetPos - startPos;
float distance = direction.magnitude;
Vector3 safePosition = targetPos;
// 确保射线检测始终有足够的距离
if (distance < 0.01f)
return ApplyGroundCheck(safePosition);
float sphereRadius = 0.1f;
Bounds startBounds = new Bounds(startPos, Vector3.one * sphereRadius * 2);
Bounds endBounds = new Bounds(targetPos, Vector3.one * sphereRadius * 2);
Bounds movementBounds = new Bounds();
movementBounds.Encapsulate(startBounds);
movementBounds.Encapsulate(endBounds);
bool collisionDetected = false;
// 主方向检测
if (Physics.SphereCast(startPos, sphereRadius, direction.normalized, out RaycastHit hit,
distance + raycastOffset, collisionLayers))
{
float safeDistance = hit.distance - raycastOffset - sphereRadius;
safePosition = startPos + direction.normalized * Mathf.Max(safeDistance, 0);
collisionDetected = true;
}
// 顶部检测
if (direction.y > 0.1f)
{
Vector3 topStart = startPos + Vector3.up * sphereRadius;
Vector3 topDirection = Vector3.up * (endBounds.max.y - startBounds.max.y);
if (Physics.SphereCast(topStart, sphereRadius, topDirection.normalized, out hit,
topDirection.magnitude + raycastOffset, collisionLayers))
{
float verticalSafeDistance = hit.distance - raycastOffset - sphereRadius;
safePosition.y = startPos.y + verticalSafeDistance;
collisionDetected = true;
}
}
// 底部检测(防止向下穿透)
if (direction.y < -0.1f)
{
Vector3 bottomStart = startPos - Vector3.up * sphereRadius;
Vector3 bottomDirection = Vector3.down * Mathf.Abs(direction.y);
if (Physics.SphereCast(bottomStart, sphereRadius, bottomDirection.normalized, out hit,
bottomDirection.magnitude + raycastOffset, collisionLayers))
{
float verticalSafeDistance = hit.distance - raycastOffset - sphereRadius;
safePosition.y = startPos.y - verticalSafeDistance;
collisionDetected = true;
}
}
if (collisionDetected)
{
// 应用地面检测并确保最小高度
safePosition = ApplyGroundCheck(safePosition);
return safePosition;
}
// 无碰撞时仍需确保地面高度
return ApplyGroundCheck(targetPos);
}
// 地面检测辅助方法
Vector3 ApplyGroundCheck(Vector3 position)
{
// 执行多次射线检测以提高精度
float groundDistance = float.MaxValue;
bool groundFound = false;
// 中心射线
Ray centerRay = new Ray(position + Vector3.up, Vector3.down);
if (Physics.Raycast(centerRay, out RaycastHit centerHit, 10f, collisionLayers))
{
groundDistance = centerHit.distance;
groundFound = true;
}
// 四角射线检测(提高边缘精度)
float checkRadius = 0.2f;
Vector3[] checkDirections = new Vector3[]
{
Vector3.forward * checkRadius + Vector3.right * checkRadius,
Vector3.forward * checkRadius - Vector3.right * checkRadius,
-Vector3.forward * checkRadius + Vector3.right * checkRadius,
-Vector3.forward * checkRadius - Vector3.right * checkRadius
};
foreach (Vector3 dir in checkDirections)
{
Ray cornerRay = new Ray(position + Vector3.up + dir, Vector3.down);
if (Physics.Raycast(cornerRay, out RaycastHit cornerHit, 10f, collisionLayers))
{
if (cornerHit.distance < groundDistance)
{
groundDistance = cornerHit.distance;
groundFound = true;
}
}
}
// 应用地面高度
if (groundFound)
{
float minSafeHeight = centerHit.point.y + minHeight;
if (position.y < minSafeHeight)
{
position.y = minSafeHeight;
}
}
return position;
}
}