第三人称角色摄像机案例分析

很多第三人称角色扮演游戏都有一个很棒的角色摄像机

预览

实现后的效果如下:
自由摄像机
使用鼠标即可以移动摄像机,摄像机能智能的检测到碰撞体,并自动修正位置。

备注

Camera.fieldOfView 属性可以获取到摄像机的焦距,修改该值,可以实现拉进拉远效果。

核心参考

1
2
3
var mouseX = Input.GetAxis("Mouse X");
var mouseY = Input.GetAxis("Mouse Y");
Camera.main.transform.localRotation = Camera.main.transform.localRotation *Quaternion.Euler(-mouseY, mouseX, 0);

以上代码是将鼠标移动和摄像机位置关联起来的关键代码,首先我们取得鼠标的x和y轴上的axis值,然后将摄像机的旋转量*(-mouseY, mouseX, 0)构成的四元数。

1
Quaternion.Euler(vector3)

可以将一个3维向量转换为一个四元数。

完整代码

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
using System;
using NaughtyAttributes;
using UnityEngine;
namespace _Scripts
{
/// <summary>
/// 摄像机控制器
/// </summary>
public class CameraController : MonoBehaviour
{
[SerializeField,Header("玩家")] private Transform player;
[SerializeField,Header("平滑速度")] private float smooth = 10f;
[SerializeField,Header("h轴偏转速度")] private float hSpeed = 6f;
[SerializeField,Header("v轴偏转速度")] private float vSpeed = 6f;

[SerializeField,Header("相机位置偏移")] private Vector3 camOffset;
[SerializeField,Header("锚点位置偏移")] private Vector3 pivotOffset;
[MinMaxSlider(-100, 100), SerializeField,Header("偏转角范围")]
private Vector2 minMaxVAngle;

private float targetFov; //目标Fov
private Vector3 targetPivotOffset; //目标锚点offset
private Vector3 targetCamOffset; //目标相机offset

private Transform cam;
private Camera myCamera;


private Vector3 smoothPivotOffset; //当前锚点插值
private Vector3 smoothCamOffset; //当前相机位置插值


private float defaultFov; //默认fov
private float relCameraPosMag; //射线检测长度
private float angleH, angleV;

private void Awake()
{
cam = transform;
myCamera = GetComponent<Camera>();
defaultFov = myCamera.fieldOfView;
//设置相机默认位置
cam.position = player.position + Quaternion.identity * pivotOffset + Quaternion.identity * camOffset;
cam.rotation = Quaternion.identity;

smoothPivotOffset = pivotOffset;
smoothCamOffset = camOffset;
angleH = player.eulerAngles.y;

//射线检测
relCameraPosMag = (transform.position - player.position).magnitude - 0.5f;


//重置属性
ResetTargetOffsets ();
ResetFov ();
}

private void Update()
{
angleH += Input.GetAxis($"Mouse X") * hSpeed;
angleV += Input.GetAxis($"Mouse Y") * vSpeed;

//限制偏转
angleV = Mathf.Clamp(angleV, minMaxVAngle.x, minMaxVAngle.y);
var aimRotation = Quaternion.Euler(-angleV, angleH, 0);
cam.rotation = Quaternion.Euler(-angleV, angleH, 0);

//y 旋转
var camYRotation = Quaternion.Euler(0, angleH, 0);

//焦距
myCamera.fieldOfView = Mathf.Lerp(myCamera.fieldOfView, targetFov, Time.deltaTime);

//射线检测动态修正位置
var baseTempPosition = player.position + camYRotation * targetPivotOffset;
var noCollisionOffset = targetCamOffset;
for (var zOffset = targetCamOffset.z; zOffset <= 0; zOffset+=0.5f)
{
noCollisionOffset.z = zOffset;
if (PosCheck(baseTempPosition+aimRotation*noCollisionOffset,Mathf.Abs(zOffset)))
{
break;
}
}

//碰撞修正相机位置
smoothPivotOffset = Vector3.Lerp(smoothPivotOffset, targetPivotOffset, smooth * Time.deltaTime);
smoothCamOffset = Vector3.Lerp(smoothCamOffset, noCollisionOffset, smooth * Time.deltaTime);

cam.position = player.position + camYRotation * smoothPivotOffset + aimRotation * smoothCamOffset;

}

/// <summary>
/// 重置offset
/// </summary>
public void ResetTargetOffsets()
{
targetPivotOffset = pivotOffset;
targetCamOffset = camOffset;
}
/// <summary>
/// 重置FOV
/// </summary>
public void ResetFov()
{
targetFov = defaultFov;
}

/// <summary>
/// 检查摄像机位置
/// </summary>
/// <param name="checkPos"></param>
/// <param name="offset"></param>
/// <returns></returns>
private bool PosCheck(Vector3 checkPos, float offset)
{
//获得碰撞器高度
var playerH=player.GetComponent<CapsuleCollider>().height * 0.75f > 0
? player.GetComponent<CapsuleCollider>().height * 0.75f
: player.GetComponent<CharacterController>().height * 0.75f;
return ViewPosCheck(checkPos, playerH) && ReverseViewPosCheck(checkPos,playerH,offset);
}

/// <summary>
/// 碰撞检查
/// </summary>
/// <param name="checkPos"></param>
/// <param name="playerH"></param>
/// <returns></returns>
private bool ViewPosCheck(Vector3 checkPos, float playerH)
{
var target = player.position + (Vector3.up * playerH);
if (Physics.SphereCast(checkPos,0.2f,target-checkPos,out RaycastHit hit,relCameraPosMag))
{
if (hit.transform!=player&&!hit.transform.GetComponent<Collider>().isTrigger)
{
return false;
}
}

return true;
}
/// <summary>
/// 反向碰撞检测
/// </summary>
/// <param name="checkPos"></param>
/// <param name="playerH"></param>
/// <param name="maxDistance"></param>
/// <returns></returns>
private bool ReverseViewPosCheck(Vector3 checkPos, float playerH, float maxDistance)
{
// Cast origin.
var origin = player.position + (Vector3.up * playerH);
if (Physics.SphereCast(origin, 0.2f, checkPos - origin, out RaycastHit hit, maxDistance))
{
if(hit.transform != player && hit.transform != transform && !hit.transform.GetComponent<Collider>().isTrigger)
{
return false;
}
}
return true;
}

}
}