308 lines
11 KiB
C#
308 lines
11 KiB
C#
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
public class CameraControl : MonoBehaviour
|
||
{
|
||
public JoystickController joystickController;
|
||
|
||
public Transform Character; // 玩家的引用。
|
||
public Vector3 pivotOffset = new Vector3(0.0f, 1.7f, 0.0f); // 相机相对于玩家的偏移量,用于调整相机的中心点。
|
||
public Vector3 camOffset = new Vector3(0.2f, 0.2f, -1f); // 相机相对于玩家位置的偏移量,用于定位相机。
|
||
public float smooth = 10f; // 相机响应的平滑速度。
|
||
public float horizontalAimingSpeed = 6f; // 水平转动速度。
|
||
public float verticalAimingSpeed = 6f; // 垂直转动速度。
|
||
public float maxVerticalAngle = 30f; // 相机最大垂直夹角。
|
||
public float minVerticalAngle = -60f; // 相机最小垂直夹角。
|
||
|
||
|
||
private float angleH = 0; // 用于存储相机水平角度的变量,基于鼠标移动。
|
||
private float angleV = 0; // 用于存储相机垂直角度的变量,基于鼠标移动。
|
||
private Transform cam; // 相机的变换组件。
|
||
private Vector3 smoothPivotOffset; // 当前的中心点偏移量,用于插值计算。
|
||
private Vector3 smoothCamOffset; // 当前的相机偏移量,用于插值计算。
|
||
private Vector3 targetPivotOffset; // 中心点偏移量目标,用于插值计算。
|
||
private Vector3 targetCamOffset; // 相机偏移量目标,用于插值计算。
|
||
private float defaultFOV; // 默认相机视场角(FOV)。
|
||
private float targetFOV; // 目标相机视场角。
|
||
private float targetMaxVerticalAngle; // 自定义相机的最大垂直夹角。
|
||
private bool isCustomOffset; // 布尔值,判断是否正在使用自定义相机偏移量。
|
||
private float deltaH = 0; // 用于锁定相机方向时的水平旋转增量。
|
||
private Vector3 firstDirection; // 用于第一次锁定相机方向时的方向。
|
||
private Vector3 directionToLock; // 当前锁定相机的方向。
|
||
private float recoilAngle = 0f; // 垂直抖动相机时的角度,用于模拟后坐力。
|
||
private Vector3 forwardHorizontalRef; // 水平平面上的前向参考方向,用于限制相机旋转。
|
||
private float leftRelHorizontalAngle, rightRelHorizontalAngle; // 用于限制相机水平旋转的左右相对角度。
|
||
|
||
// 获取相机的水平角度。
|
||
public float GetH => angleH;
|
||
|
||
void Awake()
|
||
{
|
||
// 引用相机的变换组件。
|
||
cam = transform;
|
||
|
||
// 设置相机的默认位置。
|
||
cam.position = Character.position + Quaternion.identity * pivotOffset + Quaternion.identity * camOffset;
|
||
cam.rotation = Quaternion.identity;
|
||
|
||
// 设置参考值和默认参数。
|
||
smoothPivotOffset = pivotOffset;
|
||
smoothCamOffset = camOffset;
|
||
defaultFOV = cam.GetComponent<Camera>().fieldOfView;
|
||
angleH = Character.eulerAngles.y;
|
||
|
||
ResetTargetOffsets();
|
||
ResetFOV();
|
||
ResetMaxVerticalAngle();
|
||
|
||
// 检查是否存在垂直偏移。
|
||
if (camOffset.y > 0)
|
||
Debug.LogWarning("垂直相机偏移量 (Y) 在碰撞时会被忽略!\n" +"建议将所有垂直偏移量设置在 Pivot Offset 中。");
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
// 获取鼠标移动来控制相机的旋转。
|
||
// 鼠标控制:
|
||
|
||
|
||
|
||
|
||
// 设置垂直移动的限制。
|
||
angleV = Mathf.Clamp(angleV, minVerticalAngle, targetMaxVerticalAngle);
|
||
|
||
// 设置垂直相机抖动(模拟后坐力)。
|
||
angleV = Mathf.LerpAngle(angleV, angleV + recoilAngle, 10f * Time.deltaTime);
|
||
|
||
//// 处理相机方向锁定。
|
||
if (firstDirection != Vector3.zero&& !joystickController.isDrag)
|
||
{
|
||
angleH -= deltaH;
|
||
UpdateLockAngle();
|
||
angleH += deltaH;
|
||
}
|
||
|
||
// 处理相机的水平旋转限制(如果设置了)。
|
||
if (forwardHorizontalRef != default(Vector3))
|
||
{
|
||
ClampHorizontal();
|
||
}
|
||
|
||
// 设置相机的方向。
|
||
Quaternion camYRotation = Quaternion.Euler(0, angleH, 0);
|
||
Quaternion aimRotation = Quaternion.Euler(-angleV, angleH, 0);
|
||
cam.rotation = aimRotation;
|
||
|
||
// 设置相机的视场角。
|
||
cam.GetComponent<Camera>().fieldOfView = Mathf.Lerp(cam.GetComponent<Camera>().fieldOfView, targetFOV, Time.deltaTime);
|
||
|
||
// 基于当前相机位置,检测与环境的碰撞。
|
||
Vector3 baseTempPosition = Character.position + camYRotation * targetPivotOffset;
|
||
Vector3 noCollisionOffset = targetCamOffset;
|
||
while (noCollisionOffset.magnitude >= 0.2f)
|
||
{
|
||
if (DoubleViewingPosCheck(baseTempPosition + aimRotation * noCollisionOffset))
|
||
break;
|
||
noCollisionOffset -= noCollisionOffset.normalized * 0.2f;
|
||
}
|
||
if (noCollisionOffset.magnitude < 0.2f)
|
||
noCollisionOffset = Vector3.zero;
|
||
|
||
// 如果有自定义偏移,并且相机碰撞,将进入第一人称模式。
|
||
bool customOffsetCollision = isCustomOffset && noCollisionOffset.sqrMagnitude < targetCamOffset.sqrMagnitude;
|
||
|
||
// 重新定位相机。
|
||
smoothPivotOffset = Vector3.Lerp(smoothPivotOffset, customOffsetCollision ? pivotOffset : targetPivotOffset, smooth * Time.deltaTime);
|
||
smoothCamOffset = Vector3.Lerp(smoothCamOffset, customOffsetCollision ? Vector3.zero : noCollisionOffset, smooth * Time.deltaTime);
|
||
|
||
cam.position = Character.position + camYRotation * smoothPivotOffset + aimRotation * smoothCamOffset;
|
||
|
||
// 平滑相机的垂直抖动(后坐力)。
|
||
if (recoilAngle > 0)
|
||
recoilAngle -= 5 * Time.deltaTime;
|
||
else if (recoilAngle < 0)
|
||
recoilAngle += 5 * Time.deltaTime;
|
||
}
|
||
|
||
// 设置/取消水平旋转的限制。
|
||
public void ToggleClampHorizontal(float LeftAngle = 0, float RightAngle = 0, Vector3 fwd = default(Vector3))
|
||
{
|
||
forwardHorizontalRef = fwd;
|
||
leftRelHorizontalAngle = LeftAngle;
|
||
rightRelHorizontalAngle = RightAngle;
|
||
}
|
||
|
||
// 限制相机的水平旋转。
|
||
private void ClampHorizontal()
|
||
{
|
||
// 获取相机当前朝向和参考方向之间的角度。
|
||
Vector3 cam2dFwd = this.transform.forward;
|
||
cam2dFwd.y = 0;
|
||
float angleBetween = Vector3.Angle(cam2dFwd, forwardHorizontalRef);
|
||
float sign = Mathf.Sign(Vector3.Cross(cam2dFwd, forwardHorizontalRef).y);
|
||
angleBetween = angleBetween * sign;
|
||
|
||
// 获取当前输入移动,以在达到限制角度时进行补偿。
|
||
float acc = Mathf.Clamp(Input.GetAxis("Mouse X"), -1, 1) * horizontalAimingSpeed;
|
||
acc += Mathf.Clamp(Input.GetAxis("Analog X"), -1, 1) * 60 * horizontalAimingSpeed * Time.deltaTime;
|
||
|
||
// 限制左侧角度。
|
||
if (sign < 0 && angleBetween < leftRelHorizontalAngle)
|
||
{
|
||
if (acc > 0)
|
||
angleH -= acc;
|
||
}
|
||
// 限制右侧角度。
|
||
else if (angleBetween > rightRelHorizontalAngle)
|
||
{
|
||
if (acc < 0)
|
||
angleH -= acc;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
// 处理锁定特定方向时的相机朝向。
|
||
private void UpdateLockAngle()
|
||
{
|
||
directionToLock.y = 0f;
|
||
float centerLockAngle = Vector3.Angle(firstDirection, directionToLock);
|
||
Vector3 cross = Vector3.Cross(firstDirection, directionToLock);
|
||
if (cross.y < 0) centerLockAngle = -centerLockAngle;
|
||
deltaH = centerLockAngle;
|
||
}
|
||
|
||
// 锁定相机朝向跟随特定方向。通常用于短暂的动作。
|
||
public void LockOnDirection(Vector3 direction)
|
||
{
|
||
if (firstDirection == Vector3.zero)
|
||
{
|
||
firstDirection = direction;
|
||
firstDirection.y = 0f;
|
||
}
|
||
directionToLock = Vector3.Lerp(directionToLock, direction, 0.15f * smooth * Time.deltaTime);
|
||
}
|
||
|
||
// 解锁相机方向。
|
||
public void UnlockOnDirection()
|
||
{
|
||
deltaH = 0;
|
||
firstDirection = directionToLock = Vector3.zero;
|
||
}
|
||
|
||
// 设置自定义相机偏移量。
|
||
public void SetTargetOffsets(Vector3 newPivotOffset, Vector3 newCamOffset)
|
||
{
|
||
targetPivotOffset = newPivotOffset;
|
||
targetCamOffset = newCamOffset;
|
||
isCustomOffset = true;
|
||
}
|
||
|
||
// 重置相机偏移量为默认值。
|
||
public void ResetTargetOffsets()
|
||
{
|
||
targetPivotOffset = pivotOffset;
|
||
targetCamOffset = camOffset;
|
||
isCustomOffset = false;
|
||
}
|
||
|
||
// 重置相机垂直偏移量。
|
||
public void ResetYCamOffset()
|
||
{
|
||
targetCamOffset.y = camOffset.y;
|
||
}
|
||
|
||
// 设置相机垂直偏移量。
|
||
public void SetYCamOffset(float y)
|
||
{
|
||
targetCamOffset.y = y;
|
||
}
|
||
|
||
// 设置相机水平偏移量。
|
||
public void SetXCamOffset(float x)
|
||
{
|
||
targetCamOffset.x = x;
|
||
}
|
||
|
||
// 设置自定义视场角(FOV)。
|
||
public void SetFOV(float customFOV)
|
||
{
|
||
this.targetFOV = customFOV;
|
||
}
|
||
|
||
// 重置视场角为默认值。
|
||
public void ResetFOV()
|
||
{
|
||
this.targetFOV = defaultFOV;
|
||
}
|
||
|
||
// 设置最大垂直相机旋转角度。
|
||
public void SetMaxVerticalAngle(float angle)
|
||
{
|
||
this.targetMaxVerticalAngle = angle;
|
||
}
|
||
|
||
// 重置最大垂直相机旋转角度为默认值。
|
||
public void ResetMaxVerticalAngle()
|
||
{
|
||
this.targetMaxVerticalAngle = maxVerticalAngle;
|
||
}
|
||
|
||
// 双重检测碰撞:某些凹面物体无法从外部检测到,因此从两个方向进行检测。
|
||
bool DoubleViewingPosCheck(Vector3 checkPos)
|
||
{
|
||
return ViewingPosCheck(checkPos) && ReverseViewingPosCheck(checkPos);
|
||
}
|
||
|
||
// 检查相机到玩家的碰撞。
|
||
bool ViewingPosCheck(Vector3 checkPos)
|
||
{
|
||
// 定义目标和方向。
|
||
Vector3 target = Character.position + pivotOffset;
|
||
Vector3 direction = target - checkPos;
|
||
// 如果从检测位置到玩家的射线检测到障碍物...
|
||
if (Physics.SphereCast(checkPos, 0.2f, direction, out RaycastHit hit, direction.magnitude))
|
||
{
|
||
// 如果不是玩家...
|
||
if (hit.transform != Character && !hit.transform.GetComponent<Collider>().isTrigger)
|
||
{
|
||
// 这个位置不合适。
|
||
return false;
|
||
}
|
||
}
|
||
// 如果没有检测到任何障碍物,或者检测到的是玩家,这个位置合适。
|
||
return true;
|
||
}
|
||
|
||
// 检查从玩家到相机的碰撞。
|
||
bool ReverseViewingPosCheck(Vector3 checkPos)
|
||
{
|
||
// 定义起点和方向。
|
||
Vector3 origin = Character.position + pivotOffset;
|
||
Vector3 direction = checkPos - origin;
|
||
if (Physics.SphereCast(origin, 0.2f, direction, out RaycastHit hit, direction.magnitude))
|
||
{
|
||
if (hit.transform != Character && hit.transform != transform && !hit.transform.GetComponent<Collider>().isTrigger)
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// 获取当前相机的中心点偏移量的大小。
|
||
public float GetCurrentPivotMagnitude(Vector3 finalPivotOffset)
|
||
{
|
||
return Mathf.Abs((finalPivotOffset - smoothPivotOffset).magnitude);
|
||
}
|
||
public void CamerMove()
|
||
{
|
||
angleH += Mathf.Clamp(Input.GetAxis("Mouse X"), -1, 1) * horizontalAimingSpeed;
|
||
angleV += Mathf.Clamp(Input.GetAxis("Mouse Y"), -1, 1) * verticalAimingSpeed;
|
||
}
|
||
|
||
}
|
||
|
||
|