[Unity角色控制专题] 详细说明如何使用Character Controller配合脚本实现类似MC的第一人称控制(仅移动与视角摇晃)
关于角色控制器的基本用法我就不做介绍了,请自行查看相关文档:
Unity - Manual: Character Controller component reference
本文用到了三角函数和插值函数,非常简单,如有疑问请查询以下文章:
Unity中的数学 之 Mathf_unity mathf-CSDN博客Unity中的数学应用 之 插值函数处理角色朝向 (初中难度 +Matlab)-CSDN博客
首先来看实现效果
目录
0.玩家与组件基础
基础场景
结构关系
实际图
组件一览
辅助功能:
基本变量
1.玩家的视角钳制
1.视野分析
2. 旋转轴
3.变量一览
4.功能实现
2.玩家的移动控制
3.摄像头的摇晃
4.代码总览
0.玩家与组件基础
基础场景
结构关系
第一人称视角,主摄像机在玩家的头部做子对象没什么好说的
实际图
组件一览
这里要注意,Character Controller本身就是一个另类的碰撞器,所以不要再加capsule collider了,但可以添加刚体组件,因为碰撞器只提供碰撞/触发检测,不能模拟物理效果
最后一个脚本是我们本次要编写的
辅助功能:
开发者/用户鼠标隐藏:
private void HideMouse(){
// 锁定鼠标光标并隐藏
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
简单准心
基本变量
private Camera HeadCamera;
private CharacterController Controller;
1.玩家的视角钳制
首先来看我们实现控制视角的时候都需要注意什么地方:
1.视野分析
人的头能抬起和看下的角度不同,还有眼睛的转动配合 会导致视野情况因人而异(大致在-90°到90°),因此我们直接不考虑眼球转动 直接让玩家的视野钳制在±90°即可
横向我们就不考虑了直接做360°旋转即可也就是±180 不做任何钳制
2. 旋转轴
注意加的方向,这里有一个点是比较容易忽略的,我们角色向下看是对x值做加法
但是我们的十字轴 向下拖动是负方向
所以在代码之中,要注意这一点
3.变量一览
public Vector2 xY;
[Header("鼠标灵敏度")]
public float mouseSensitivity = 1.0f;
private Vector2 VirticalPersective = new Vector2(-90, 90);
private float currentVerticalAngle;
4.功能实现
private void PersPactive() {
xY = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y")) * mouseSensitivity;
currentVerticalAngle -= xY.y;
currentVerticalAngle = Mathf.Clamp(currentVerticalAngle, VirticalPersective.x, VirticalPersective.y);
//相机上下视角
HeadCamera.transform.localRotation = Quaternion.Euler(currentVerticalAngle, 0f, 0f);
//角色旋转
this.transform.Rotate(xY *xY.x);
}
需要注意的一点是,上下控制相机旋转,左右控制角色旋转,不要搞混
2.玩家的移动控制
这个其实就没有什么好讲的直接看代码
private Vector2 xZ;
Vector3 moveDirection;
[Header("移动速度")]
public float moveSpeed = 3.0f;
[Header("跑步速度")]
public float runSpeed = 6.0f;
private void Move(){
xZ = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
moveDirection = (transform.forward * xZ.y + transform.right * xZ.x).normalized * moveSpeed *Time.deltaTime;
if (Input.GetKey(KeyCode.LeftShift)) {
moveDirection = (transform.forward * xZ.y + transform.right * xZ.x).normalized * runSpeed * Time.deltaTime;
}
Controller.Move(moveDirection);
}
3.摄像头的摇晃
我们只需要分析一下相机应该怎么动
假设这个是相机:
想要其做简谐运动第一时间想到的肯定是三角函数
三角函数必然要有一个连续的输入所以输入值 也就是三角函数的参数取一个Time.time作为横轴移动
但是单纯的左右或上下移动摄像机只会很突兀就比如下面这样:
单纯x跟蛇一样
单纯y一跳一跳的
因此需要将二者结合 然后设计一个数字去调整其周期,这里我已经做好了 测试
相机的位移:
x位移保持在0.0x(x<5)
y位移保持在0.00x(x<5)
相机的运动周期:
x保持在0.0x (x<9)
y保持在0.0x/2 (x<9)
不运动的时候要将相机缓慢放回到原来的位置, Lerp即可
直接看代码
private Vector3 originalCameraPos;
[Header("摇晃强度")]
public float shakeIntensity = 0.05f;
[Header("摇晃频率")]
public float shakeFrequency = 10f;
void CameraShake()
{
if (xZ.magnitude != 0)
{
float shakeOffsetX = Mathf.Sin(Time.time * shakeFrequency) * shakeIntensity;
//float shakeOffsetY = Mathf.Cos(Time.time * shakeFrequency * 2f) * shakeIntensity * 0.5f;
HeadCamera.transform.localPosition = originalCameraPos + new Vector3(shakeOffsetX, 0f, 0f);
}
else
{
HeadCamera.transform.localPosition = Vector3.Lerp(HeadCamera.transform.localPosition, originalCameraPos, Time.deltaTime * 10f);
}
}
4.代码总览
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem.XR;
using static UnityEditor.Searcher.SearcherWindow.Alignment;
public class CCLearn : MonoBehaviour {
private Camera HeadCamera;
private CharacterController Controller;
#region 玩家视角
public Vector2 xY;
[Header("鼠标灵敏度")]
public float mouseSensitivity = 1.0f;
private Vector2 VirticalPersective = new Vector2(-90, 90);
private float currentVerticalAngle;
#endregion
#region 玩家移动
private Vector2 xZ;
Vector3 moveDirection;
[Header("移动速度")]
public float moveSpeed = 3.0f;
[Header("跑步速度")]
public float runSpeed = 6.0f;
#endregion
#region 摄像机摇晃
private Vector3 originalCameraPos;
[Header("摇晃强度")]
public float shakeIntensity = 0.05f;
[Header("摇晃频率")]
public float shakeFrequency = 10f;
#endregion
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start() {
Controller = GetComponent<CharacterController>();
HeadCamera = transform.Find("Head").GetComponentInChildren<Camera>();
HideMouse();
}
private void HideMouse(){
// 锁定鼠标光标并隐藏
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
// Update is called once per frame
void Update() {
PersPactive();
Move();
CameraShake();
Debug.Log(Time.deltaTime);
}
private void PersPactive() {
xY = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y")) * mouseSensitivity;
currentVerticalAngle -= xY.y;
currentVerticalAngle = Mathf.Clamp(currentVerticalAngle, VirticalPersective.x, VirticalPersective.y);
//相机上下视角
HeadCamera.transform.localRotation = Quaternion.Euler(currentVerticalAngle, 0f, 0f);
//角色旋转
this.transform.Rotate(Vector3.up *xY.x);
}
private void Move(){
xZ = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
moveDirection = (transform.forward * xZ.y + transform.right * xZ.x).normalized * moveSpeed *Time.deltaTime;
if (Input.GetKey(KeyCode.LeftShift)) {
moveDirection = (transform.forward * xZ.y + transform.right * xZ.x).normalized * runSpeed * Time.deltaTime;
}
Controller.Move(moveDirection);
}
void CameraShake()
{
if (xZ.magnitude != 0)
{
float shakeOffsetX = Mathf.Sin(Time.time * shakeFrequency) * shakeIntensity;
float shakeOffsetY = Mathf.Cos(Time.time * shakeFrequency * 2f) * shakeIntensity * 0.5f;
HeadCamera.transform.localPosition = originalCameraPos + new Vector3(shakeOffsetX, shakeOffsetY, 0f);
}
else
{
HeadCamera.transform.localPosition = Vector3.Lerp(HeadCamera.transform.localPosition, originalCameraPos, Time.deltaTime * 10f);
}
}
}