1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
|
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
namespace DS
{
public class CameraHandler : MonoBehaviour
{
public Transform player; // 跟踪目标(如玩家角色)
// ========== 公开引用 ==========
[Header("Camera Transforms")]
public Transform cameraTransform; // 摄像机 Transform(子物体)
public Transform cameraPivotTransform; // 摄像机枢轴 Transform(用于俯仰旋转)
// ========== 私有引用与状态 ==========
private Transform _myTransform; // 当前对象的 Transform 缓存
private Vector3 _cameraTransformPosition; // 用于 Lerp 的摄像机局部位置
private LayerMask _ignoreLayers; // 忽略的碰撞层(玩家、UI 等)
private Vector3 cameraFollowVelocity = Vector3.zero; // SmoothDamp 速度参考值
private bool _cursorLocked = false; // 鼠标是否锁定
// ========== 单例 ==========
public static CameraHandler singleton; // 全局访问实例
// ========== 速度参数(可调) ==========
[Header("Movement Speeds")]
public float lookSpeed = 0.1f; // 水平旋转速度
public float followSpeed = 0.1f; // 跟随目标的速度
public float pivotSpeed = 0.03f; // 俯仰旋转速度
// ========== 内部状态 ==========
private float _targetPosition; // 摄像机与枢轴的目标距离
private float _defaultPosition; // 摄像机的默认 Z 轴局部位置(距离)
private float _lookAngle; // 水平旋转角度(Y 轴)
private float _pivotAngle; // 俯仰旋转角度(X 轴)
[Header("Pivot Clamps")]
public float minimumPivot = -35; // 俯仰最小角度
public float maximumPivot = 35; // 俯仰最大角度
// ========== 碰撞检测参数 ==========
[Header("Collision Settings")]
public float cameraSphereRadius = 0.2f; // 球体投射的半径
public float cameraCollisionOffSet = 0.2f; // 碰撞后摄像机偏移量
public float minimumCollisionOffSet = 0.2f; // 最小偏移量,防止穿透
// ====================================================================
// Awake:初始化引用与默认值
// ====================================================================
private void Awake()
{
singleton = this; // 设置单例
_myTransform = transform; // 缓存自身 Transform
_defaultPosition = cameraTransform.localPosition.z; // 记录默认 Z 距离
// 忽略层级:8(玩家)、9、10;使用位取反运算
_ignoreLayers = ~(1 << 8 | 1 << 9 | 1 << 10);
// 自动绑定 PlayerLocomotion
if (player == null)
{
player = FindObjectOfType<PlayerLocomotion>().transform;
}
}
// ====================================================================
// Update:每帧检测鼠标锁定状态
// ====================================================================
private void Update()
{
UpdateCursorLock();
}
// ====================================================================
// UpdateCursorLock:管理鼠标锁定与可见性
// ====================================================================
private void UpdateCursorLock()
{
// 左键点击 -> 锁定鼠标(游戏控制模式)
if (Input.GetMouseButtonDown(0))
{
Cursor.lockState = CursorLockMode.Locked; // 锁定鼠标到窗口中心
Cursor.visible = false; // 隐藏鼠标
_cursorLocked = true;
}
// 按下 Escape -> 解锁鼠标(UI / 菜单模式)
if (Input.GetKeyDown(KeyCode.Escape))
{
Cursor.lockState = CursorLockMode.None; // 释放鼠标
Cursor.visible = true; // 显示鼠标
_cursorLocked = false;
}
}
// ====================================================================
// FollowTarget:平滑跟随目标,并处理摄像机碰撞
// ====================================================================
public void FollowTarget(float delta)
{
// 使用 SmoothDamp 平滑移动到目标位置
Vector3 targetPosition = Vector3.SmoothDamp(
_myTransform.position,
player.position,
ref cameraFollowVelocity,
delta / followSpeed);
_myTransform.position = targetPosition;
// 处理摄像机与场景物体的碰撞
HandleCameraCollisions(delta);
}
// ====================================================================
// HandleCameraRotation:处理鼠标输入的摄像机旋转
// ====================================================================
public void HandleCameraRotation(float delta, float mouseInputX, float mouseInputY)
{
// 鼠标未锁定时不处理旋转
if (!_cursorLocked)
return;
// 累加水平旋转角度(左右)
_lookAngle += (mouseInputX * lookSpeed) / delta;
// 累加俯仰旋转角度(上下,取反使鼠标向上移动时视角向上)
_pivotAngle -= (mouseInputY * pivotSpeed) / delta;
// 限制俯仰角度范围,防止视角翻转
_pivotAngle = Mathf.Clamp(_pivotAngle, minimumPivot, maximumPivot);
// 应用水平旋转到当前对象(Y 轴旋转)
Vector3 rotation = Vector3.zero;
rotation.y = _lookAngle;
Quaternion targetRotation = Quaternion.Euler(rotation);
_myTransform.rotation = targetRotation;
// 应用俯仰旋转到枢轴对象(X 轴旋转,局部坐标)
rotation = Vector3.zero;
rotation.x = _pivotAngle;
targetRotation = Quaternion.Euler(rotation);
cameraPivotTransform.localRotation = targetRotation;
}
// ====================================================================
// HandleCameraCollisions:检测并避免摄像机与场景物体穿插
// ====================================================================
public void HandleCameraCollisions(float delta)
{
// 目标距离默认设为默认距离
_targetPosition = _defaultPosition;
RaycastHit hit;
// 计算从枢轴指向摄像机的方向向量
Vector3 direction = cameraTransform.position - cameraPivotTransform.position;
direction.Normalize();
// 从枢轴位置沿 direction 方向进行 SphereCast 检测
// 检测距离为 _targetPosition 的绝对值
if (Physics.SphereCast(
cameraPivotTransform.position,
cameraSphereRadius,
direction,
out hit,
Mathf.Abs(_targetPosition)))
{
// 计算枢轴到碰撞点的实际距离
float dis = Vector3.Distance(cameraPivotTransform.position, hit.point);
// 摄像机应位于碰撞点前方 cameraCollisionOffSet 处
_targetPosition = -(dis - cameraCollisionOffSet);
}
// 如果计算出的目标距离绝对值太小,强制使用最小值防止视觉异常
if (Mathf.Abs(_targetPosition) < minimumCollisionOffSet)
{
_targetPosition = -minimumCollisionOffSet;
}
// 使用 Lerp 平滑调整摄像机的局部 Z 轴位置
_cameraTransformPosition.z = Mathf.Lerp(
cameraTransform.localPosition.z,
_targetPosition,
delta / 0.2f);
// 应用位置
cameraTransform.localPosition = _cameraTransformPosition;
}
}
}
|