最终效果
文章目录
- 最终效果
- 前言
- 一、前期准备
- 二、真实开始
- 1、输入控制
- 2、使用Character Controller实现人物移动
- 3、添加虚拟相机Cinemachine跟随
- 4、人物跟随相机旋转
- 5、导入动画
- 6、添加BlendTree混合动画
- 7、添加Animator控制代码
- 8、添加动画过渡
- 9、使用Root Motion实现动画和位移同步
- 10、添加下蹲移动
- 11、添加重力和跳跃
- 12、播放跳跃下落动画
- 13、修复原地跳跃问题
- 14、原地转向
- 15、角色在下斜坡时,短暂浮空
- 16、冲刺时跳跃,落地速度就会立即恢复到最高的跑步速度
- 17、在墙壁边缘走上走下或者跳跃,会出现抖动卡顿
- 18、我们跳上超斜的斜面,会被卡住
- 19、限制人物下落速度
- 20、物体会挡住相机视野和相机穿模
- 21、相机上下楼梯,跳跃有点抖动
- 待续
- 专栏推荐
- 完结
前言
本文我将带大家从零手搓一个带动画的第一人称和第三人称unity人物控制器,不借助任何外在的第三方插件。主要使用到了新输入系统InputSystem
+虚拟相机Cinemachine
+Animator
。
其实第一人称和第三人称unity人物控制器我之前做过不少,但是都没有带动画效果。
注意本文不是新手向的,如果你还不了解如何使用unity可以参考【unity游戏开发入门到精通——通用篇】,可以等有一定基础了再来看本篇文章。
一、前期准备
1、创建项目
如果你不知道如何下载unity6可以参考:【unity小技巧】国内Unity6下载安装和一些Unity6新功能使用介绍
这里我直接使用最新的Unity6版本,当然,如果你想使用其他版本也是可以的。
2、选择在进入播放模式时启动哪些重新加载选项
为了更快地进入播放模式,节约我们的时间,我们可以禁用 scene 或域重新加载。这里我在项目设置里将进入播放模式设置
设置成Reload Scene Only (仅重新加载场景)
。
域重新加载是指编辑器在开始播放模式之前重置脚本状态。场景重新加载是指编辑器在播放模式开始之前销毁所有场景 GameObjects
并从磁盘重新加载场景。具体介绍可以参考官方文档说明:禁用域和场景重新加载的详细信息
3、搭建基本场景原型
如果你还不知道如何使用ProBuilder,可以参考:【推荐100个unity插件之28】在unity中建模,使用Unity制作基础模型,搭建场景原型——ProBuilder的使用
这里我使用unity自带的ProBuilder插件搭建基本场景原型,效果就大家自由发挥。当然你也可以自己使用其他方式,比如使用不同的3D模型进行拼接。
4、拖入3D角色模型
这里随便找个3D人物模型即可,我这里导入的是:そふぃーら
如果你不知道vrm模型如何在unity中使用,可以参考:【推荐100个unity插件之25】在unity中直接使用VRM模型——URPUniVrm插件的使用
这个模型其实不算特别好,衣服头发经常穿模,VRM插件也经常爆警告,看着很难受。所以到后面开发我更换成了模型:【善良米塔/瘋狂米塔 vroid版本/vrm】,如果中途发现我的突然换了隔模型,千万不用惊讶。
这里推荐另一种方式使用VRM模型,也就是将VRM模型转成FBX模型再到unity中使用,可以参考:【blender小技巧】使用Blender将VRM或者其他模型转化为FBX模型,并在unity使用,导出带贴图的FBX模型,贴图材质问题修复
二、真实开始
1、输入控制
对InputSystem还不熟悉的小伙伴可以先看:【unity游戏开发——InputSystem】
using UnityEngine;
using UnityEngine.InputSystem;
public class InputController : MonoBehaviour {
PlayerInput playerInput;
public Vector2 moveVector2;
public bool isSprint;
public bool isCrouch;
public bool isJump;
void Awake()
{
playerInput = GetComponent<PlayerInput>();
}
private void OnEnable() {
// 输入触发事件,任何输入都会触发该事件
playerInput.onActionTriggered += OnActionTrigger;
}
void OnDisable() {
playerInput.onActionTriggered -= OnActionTrigger;
}
private void OnActionTrigger(InputAction.CallbackContext context)
{
switch (context.action.name)
{
case "Move":
// 获取输入的方向值(Vector2类型)
moveVector2 = context.ReadValue<Vector2>();
// Debug.Log("Move: " + moveVector2);//打印
break;
case "Sprint":
//按下返回true,松开返回false
isSprint = context.action.IsPressed();
break;
case "Crouch":
isCrouch = !isCrouch;
break;
case "Jump":
isJump = context.action.IsPressed();
break;
}
}
}
挂载脚本
结果,键盘wasd输入打印值
2、使用Character Controller实现人物移动
这里我们使用Character Controller实现第三人称控制,如果你还不知道具体如何使用CharacterController,可以参考:
给角色添加Character Controller,并配置合适的参数
新增PlayerController脚本,控制角色移动
using UnityEngine;
[RequireComponent(typeof(Animator), typeof(CharacterController))]
public class PlayerController : MonoBehaviour
{
CharacterController characterController;
[Header("输入")]
InputController inputController;
Vector3 direction;//输入创建移动方向向量
[Header("移动")]
public float speed = 5f; // 玩家移动的速度
Vector3 moveDirection;//角色的移动方向
void Awake()
{
characterController = GetComponent<CharacterController>();
inputController = GetComponent<InputController>();
}
void Update()
{
SetPlayerMove();
}
//处理角色移动
void SetPlayerMove(){
// 根据输入创建移动方向向量
direction = new Vector3(inputController.moveVector2.x, 0, inputController.moveVector2.y);
//将该向量从局部坐标系转换为世界坐标系,得到最终的移动方向
moveDirection = transform.TransformDirection(direction);
characterController.Move(moveDirection.normalized * speed * Time.deltaTime);
}
}
挂载脚本
效果
3、添加虚拟相机Cinemachine跟随
如果你还不知道如何使用Cinemachine,可以参考:【推荐100个unity插件之10】Unity最全的最详细的Cinemachine(虚拟相机系统)介绍,详细案例讲解,快速上手
新增虚拟相机
配置参数
效果
4、人物跟随相机旋转
因为我们要做的是FPS第三人称视角,所以我们需要实现人物一直面向相机
代码如下
// 处理角色旋转
void SetPlayerRotation()
{
// 获取相机的旋转(只考虑 Y 轴的旋转)
float yRotation = mainCamera.transform.eulerAngles.y;
// 计算角色的目标旋转
Quaternion targetRotation = Quaternion.Euler(0, yRotation, 0);
//进行平滑过渡到目标旋转(如果需要平滑效果)
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, turnSpeed * Time.deltaTime);
}
效果
5、导入动画
这里所有的动画,都可以在mixamo网站免费获取,如果你还不懂如何使用它,可以参考:【游戏资源】获取免费开源的人物模型,为obj fbx人物模型绑定人形骨骼和人形动画,并导入到unity中使用——mixamo的使用介绍
这里为了节省时间,我就不去mixamo网站一个个下载了,这里我直接使用之前unity免费的动画包:Basic Motions FREE
6、添加BlendTree混合动画
如果你还了解
Blend Tree
动画混合树的知识,可以先去查看:【unity游戏开发入门到精通——动画篇】动画混合(Blend Tree)
添加行为树,配置动画和参数。当然,在这里配置参数的做法有很多种。
1. 配置参数方式一
我们可以把移动阈值设置成1,奔跑设置成2。注意这里斜着走我们要设置成0.707,因为我们会发现斜着输入最大值就是-0.70到0.707,这涉及三角函数的计算,斜边长为1的等腰直角三角形的两边长度就是1/√2≈0.707
。
2. 配置参数方式二
点击xy按动画的xz方向上的速度自动分配阈值
然后我们可以参考Unity官方在标准资源包里的做法。
- 我们把待机动画阈值设置成0。
- 把前后运动的动画x方向阈值设置成0,左右运动的动画y轴阈值设置0,另一个方向的阈值设置成和向前行走一样。
- 斜方向阈值都设置成向前行走的阈值*0.707,也就是
+-1.066*0.707=0.753662
,为什么是0.707前面已经解释了
- 奔跑动画同理
注意
:这里如果你对动画要求比较高,其实我们还可以进行更加精细的调整,比如修改缩放,让所有的行走奔跑动画移动速度都保持在一个值。这里我就不做调整了,具体的方式我在RootMotion
动画混合树的知识的文章里有提到:【unity实战】OnAnimatorMove+Root Motion+BlendTree+CharacterController解决Animator动画和位移不同步问题,完美实现移动动画动作匹配
7、添加Animator控制代码
修改PlayerController
[Header("移动")]
public float currentSpeed = 0f;//当前速度
[SerializeField] private float walkSpeedValue = 1.066f;//行走速度
[SerializeField] private float runSpeedValue = 4.067f;//奔跑速度
Vector3 moveDirection;//角色的移动方向
//处理角色移动
void SetPlayerMove(){
if(inputController.moveVector2.magnitude > 0.1f){
if(inputController.isSprint){
currentSpeed = runSpeedValue;
}else{
currentSpeed = walkSpeedValue;
}
}else{
currentSpeed = 0f;
}
// 根据输入创建移动方向向量
direction = new Vector3(inputController.moveVector2.x, 0, inputController.moveVector2.y);
//将该向量从局部坐标系转换为世界坐标系,得到最终的移动方向
moveDirection = transform.TransformDirection(direction);
characterController.Move(moveDirection.normalized * currentSpeed * Time.deltaTime);
}
新增AnimatorController 代码,控制动画切换
using UnityEngine;
public class AnimatorController : MonoBehaviour {
InputController inputController;
Animator animator;
PlayerController playerController;
private static int inputX = Animator.StringToHash("inputX");
private static int inputY = Animator.StringToHash("inputY");
void Awake()
{
inputController = GetComponent<InputController>();
animator = GetComponent<Animator>();
playerController = GetComponent<PlayerController>();
}
void Update()
{
UpdateAnimatorState();
}
private void UpdateAnimatorState(){
animator.SetFloat(inputX, inputController.moveVector2.x * playerController.currentSpeed);
animator.SetFloat(inputY, inputController.moveVector2.y * playerController.currentSpeed);
}
}
使用Animator.StringToHash哈希处理字符串
:
使用哈希的主要原因是提高效率。状态机中的标签和参数通常用字符串表示,但字符串比较在性能上可能较慢。通过使用哈希值,可以将字符串转换为唯一的整数值,进行更快速的比较操作。这避免了每帧都进行昂贵的字符串比较操作,从而提升了状态机查询的性能。
效果
8、添加动画过渡
虽然现在看到一切正常,但是其实混合树实际上并没有进行混合,它只是在不同的动画之间即时切换,这是因为你可以看到输入x和输入y参数立即被设置为新值,我们需要做的是缓慢地增加它们。
1 方法一 使用Vector3.Lerp
进行平滑插值
我们可以在设置输入值前,使用Vector3.Lerp
进行平滑插值,修改AnimatorController代码
[SerializeField] private float smoothTime = 4f;//平滑插值速度
Vector2 currentBlendInput = Vector3.zero;//当前移动输入
private void UpdateAnimatorState(){
//平滑插值
currentBlendInput = Vector2.Lerp(currentBlendInput, inputController.moveVector2, smoothTime * Time.deltaTime);
animator.SetFloat(inputX, currentBlendInput.x);
animator.SetFloat(inputY, currentBlendInput.y);
}
2 方法二 使用animator.SetFloat进行平滑插值
animator.SetFloat方法其实自带了平滑插值方法,我们只需要新增两个参数即可。
[SerializeField] private float smoothTime = 0.1f;//平滑插值速度
private void UpdateAnimatorState(){
animator.SetFloat(inputX, inputController.moveVector2.x * playerController.currentSpeed, smoothTime, Time.deltaTime);
animator.SetFloat(inputY, inputController.moveVector2.y * playerController.currentSpeed, smoothTime, Time.deltaTime);
}
3 效果
9、使用Root Motion实现动画和位移同步
现在我们的动画切换虽然进行混合了,但是很明显我们人物移动出现了滑步
的想象或者说很容易出现滑步
现象。因为我们还没有对人物的速度进行限制。当然,你可以选择同样对速度进行平滑,然后解决这个滑步问题。
但是我这里打算使用Root Motion,要完全解决人物移动的滑步问题,使用Root Motion一定是最好的方式。
如果你还了解
Root Motion
动画混合树的知识,可以先去查看:【unity实战】OnAnimatorMove+Root Motion+Blend Tree+CharacterController解决Animator动画和位移不同步问题,完美实现移动动画动作匹配
修改,取消之前的SetPlayerMove方法,新增OnAnimatorMove方法代替
private void OnAnimatorMove()
{
if(inputController.moveVector2.magnitude > 0.1f){
if(inputController.isSprint){
currentSpeed = runSpeedValue;
}else{
currentSpeed = walkSpeedValue;
}
}else{
currentSpeed = 0f;
}
Vector3 deltaPosition = animator.deltaPosition;//当前帧相对于上一帧的位移增量
// 移动 CharacterController
characterController.Move(deltaPosition);
}
开启animator动画物理
效果,现在使用动画根位移控制角色移动,实现了角色动画和位移绝对同步
10、添加下蹲移动
实现下蹲移动其实和前面的操作基本一样,新增一个下蹲bleedTree,再做切换即可,这里不浪费时间再介绍一次了
直接看看最终效果
11、添加重力和跳跃
修改PlayerController
[Header("跳跃")]
public float jumpHeight = 2f;//跳跃高度
[SerializeField] private bool isGround;//地面检测
[SerializeField] private float Gravity = -39.8f;//重力
private Vector3 verticalVelocity; // 垂直方向上的速度
void Update()
{
SetPlayerRotation();
SetPlayerGravity();
SetPlayerJump();
}
// 处理角色重力
void SetPlayerGravity()
{
// 应用重力
characterController.Move(verticalVelocity * Time.deltaTime);
//地面检测
isGround = characterController.isGrounded;
if (isGround)
{
// 当isGrounded为true时,将Y轴方向的速度设为了一个较小的负值,这是为了确保能稳定触碰地面,避免isGrounded误判为false
verticalVelocity = new Vector3(0, -2f, 0);
}
else
{
// 更新重力影响的速度
verticalVelocity.y += Gravity * Time.deltaTime;
}
}
// 处理角色跳跃
void SetPlayerJump()
{
// 跳跃处理
if (isGround && inputController.isJump)
{
verticalVelocity.y = Mathf.Sqrt(jumpHeight * -2 * Gravity);
}
}
效果
12、播放跳跃下落动画
我们添加一个跳跃下落的子状态机,并配置参数
跳跃下落的子状态机参数如下。(注意:可以看到这里默认有条黄线,不用管他,好像是unity6的显示bug)
修改AnimatorController,进行跳跃动画切换
private static int jumpSpeed = Animator.StringToHash("jumpSpeed");
private static int isJump = Animator.StringToHash("isJump");
private static int isGround = Animator.StringToHash("isGround");
animator.SetFloat(jumpSpeed, playerController.verticalVelocity.y);
animator.SetBool(isJump, inputController.isJump);
animator.SetBool(isGround, playerController.isGround);
效果
13、修复原地跳跃问题
应用跳跃动画之后,我们可以看到目前我们都只能在原地跳跃了,水平方向的速度一直都是0,其实是因为前面我们使用了OnAnimatorMove来控制人物移动,而我们的跳跃动画是没有水平方向位移的。
想解决这一问题,我们可以不在地面时手动设置一个水平方向的速度,修改PlayerController
private Vector3 horizontalVelocity; // 水平方向速度
//设置水平速度
void SetHorizontalVelocity()
{
//不在地面时更新水平速度
if (!isGround)
{
//将该向量从局部坐标系转换为世界坐标系,得到最终的移动方向
horizontalVelocity = transform.TransformDirection(new Vector3(inputController.moveVector2.x, 0, inputController.moveVector2.y)) * currentSpeed;
}
else
{
horizontalVelocity = Vector3.zero;
}
characterController.Move(horizontalVelocity * Time.deltaTime);
}
效果
14、原地转向
业余游戏开发者在做人物控制系统时,经常忽略的一个内容就是原地转身,如果你想用3D玩家控制器制作可发布的游戏,这确实是一个你必须拥有的机制。
老实说,这也是比较难做好的机制之一。这里我打算复刻一下类似PUBG吃鸡这样的原地转身机制。及人物面向方向与相机的水平正方向的夹角如果在45度范围内,角色不发生转身。如果超出这个范围,则等待3秒后角色平滑旋转,与相机的水平正方向对齐。
我们新增一个子状态机叫转身动作
。
配置子状态机内部连线,因为我这里只有左右转的动画,所以只配置了旋转角度大于小于0播放不同动画。如果你有更多动画问题,比如旋转45、90、180度动画,可以进行更加精细的配置,效果将会更好。
修改PlayerController代码,我们要修改覆盖掉原来我们角色转身的代码。这里代码我都加了很详细的中文注释,方便大家理解,就不多做解释了。
[Header("相机跟随设置")]
[SerializeField] private float angleThreshold = 45f; // 角度阈值
[SerializeField] private float rotationSpeed = 8f; // 转向速度
[HideInInspector] public float _currentAngle; // 当前角度差
public float turnDelay = 3f; // 转身动画延迟发生时间
private float lastTurnTime;//上次转身时间
public float turnTime = 0.6f;//转身需要的时间
[HideInInspector] public bool _isTurning = false; // 是否正在转身
private float _turnStartTime; // 转身开始时间
private Quaternion _startRotation; // 转身开始时的旋转
private Quaternion _targetRotation; // 转身目标旋转
private bool isIdle;//是否在原地
private Vector3 cameraForward;//获取相机水平方向
//...记得在update中调用下面这些函数
// 计算人物和相机的夹角
void CalculateCameraAngle()
{
// 获取相机在水平面(XZ平面)的正前方向
cameraForward = mainCamera.transform.forward;
cameraForward.y = 0;
cameraForward.Normalize();
// 获取角色在水平面的正前方向
Vector3 playerForward = transform.forward;
playerForward.y = 0;
playerForward.Normalize();
// 通过点乘计算两个向量的夹角
_currentAngle = Vector3.Angle(playerForward, cameraForward);
// 计算两个向量的叉乘,确定角度方向(正负值)
float crossY = Vector3.Cross(playerForward, cameraForward).y;
//y小于0 证明相机在人物左侧
if (crossY < 0) _currentAngle *= -1;
}
//处理旋转逻辑
private void HandleRotationLogic()
{
isIdle = inputController.moveVector2.magnitude < 0.1f; // 如果移动向量长度小于0.1,则认为处于静止状态
// 如果没有移动,且角度大于阈值,且当前没有正在转身,且上次转身时间超过延迟时间,且在地面上,且没有下蹲
if (isIdle && Mathf.Abs(_currentAngle) > angleThreshold && !_isTurning && Time.time - lastTurnTime > turnDelay && isGround && !inputController.isCrouch)
{
_isTurning = true;
_turnStartTime = Time.time;
_startRotation = transform.rotation;
_targetRotation = Quaternion.LookRotation(cameraForward);
}
// 如果转身动画结束或者移动,重置状态
if ((_isTurning && Time.time - _turnStartTime >= turnTime) || !isIdle)
{
_isTurning = false;
lastTurnTime = Time.time;
}
}
// 处理角色旋转
private void SetPlayerRotation()
{
if (_isTurning)
{
// 计算转身进度
float turnProgress = (Time.time - _turnStartTime) / turnTime;
// 确保进度在0到1之间
turnProgress = Mathf.Clamp01(turnProgress);
// 使用插值平滑旋转
transform.rotation = Quaternion.Slerp(_startRotation, _targetRotation, turnProgress);
}
else if (!isIdle)//非待机状态角色才跟随相机旋转
{
lastTurnTime = Time.time;
// 普通情况下的相机跟随旋转
Vector3 cameraForward = mainCamera.transform.forward;
cameraForward.y = 0;
Quaternion targetRotation = Quaternion.LookRotation(cameraForward);
transform.rotation = Quaternion.Slerp(
transform.rotation,
targetRotation,
rotationSpeed * Time.deltaTime
);
}
}
效果
15、角色在下斜坡时,短暂浮空
角色在下斜坡时,会短暂浮空,特别是速度比较快的时候。
我们可以给角色一个向下的压力,我们可以将它配置化成参数,方便在面板调整。
[Header("斜坡处理")]
[SerializeField] private Vector3 downForceVector = new Vector3(0, -10, 0); //下压力
void Update()
{
SetDownForce();//记得一定要在isGround检测之前调用
//。。。
}
//加下压力
void SetDownForce(){
if (!isGround) return;
characterController.Move(downForceVector * Time.deltaTime);
}
效果
16、冲刺时跳跃,落地速度就会立即恢复到最高的跑步速度
我们可以先在PlayerController获取跳跃状态
public bool isJumping;//是否在跳跃
// 处理角色跳跃
void SetPlayerJump()
{
// 跳跃处理
if (isGround)
{
isJumping = false;
if(inputController.isJump){
isJumping = true;
verticalVelocity.y = Mathf.Sqrt(jumpHeight * -2 * Gravity);
}
}
}
在AnimatorController里判断,如果正在跳跃,则inputX和inputY归零
if (playerController.isJumping)
{
animator.SetFloat(inputX, 0f);
animator.SetFloat(inputY, 0f);
}
else
{
animator.SetFloat(inputX, inputController.moveVector2.x * playerController.currentSpeed, smoothTime, Time.deltaTime);
animator.SetFloat(inputY, inputController.moveVector2.y * playerController.currentSpeed, smoothTime, Time.deltaTime);
}
效果
17、在墙壁边缘走上走下或者跳跃,会出现抖动卡顿
其实这是一个非常细微的问题,比如我把玩家的跳跃高度设置成1.7米,无论如何他都不能跳上2米的墙壁才对。但是事实并非如此。
这主要其实就是收到Character Controller的每步偏移量的影响。跳跃2米的墙,还差0.3米时他会把他当作是楼梯,然后顿一下走上前。
我的解决方案是我们在跳跃或下落(也就是空中)时,我们将步长偏移设置为零,在任何其他状态下,再将他设置回默认值。
[Header("楼梯处理")]
float stepOffset; //默认步长偏移
void Awake()
{
//。。。
stepOffset = characterController.stepOffset;
}
void SetPlayerGravity()
{
//。。。
if(isGround){
//。。。
characterController.stepOffset = stepOffset;
}else{
//。。。
characterController.stepOffset = 0f;
}
}
效果
18、我们跳上超斜的斜面,会被卡住
我们可以在玩家脚下使用射线检测,检测斜面的法线向量,然后计算出斜坡的角度。
RaycastHit hit; //射线检测结果
float slopeAngle; //斜率角度
Vector3 normal;//获取法线向量
public LayerMask layerMask; //射线检测的层
public float distance = 0.5f; //射线检测距离
// 处理角色重力
void SetPlayerGravity()
{
if (isGround)
{
verticalVelocity = new Vector3(0, -2f, 0);
characterController.stepOffset = stepOffset;
}
else
{
// 累加重力
verticalVelocity.y += Gravity * Time.deltaTime;
characterController.stepOffset = 0f;
}
}
// 处理角色跳跃
void SetPlayerJump()
{
// 跳跃处理
if (isGround)
{
isJumping = false;
if(inputController.isJump){
isJumping = true;
verticalVelocity.y = Mathf.Sqrt(jumpHeight * -2 * Gravity);
}
}
verticalVelocity = SetSlopeVelocity(verticalVelocity);
// 应用y轴速度
characterController.Move(verticalVelocity * Time.deltaTime);
//地面检测,注意地面检测一定要在Move之后,否则可能会检测不到
isGround = characterController.isGrounded;
}
//斜坡检测
void CheckIsSlope()
{
// 射线检测
if (Physics.Raycast(transform.position, Vector3.down,out hit, distance, layerMask))
{
normal = hit.normal;
// 计算斜面角度
slopeAngle = Vector3.Angle(normal, Vector3.up);
}
}
//斜坡速度处理
private Vector3 SetSlopeVelocity(Vector3 velocity)
{
//角色在大斜坡上下落
if (slopeAngle > characterController.slopeLimit && verticalVelocity.y < 0f)
{
// 则将速度投射到斜坡上
velocity = Vector3.ProjectOnPlane(velocity, normal);
}
//返回处理后的速度向量
return velocity;
}
我们可以使用OnDrawGizmos辅助函数,在场景显示射线,方便调试查看
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawLine(transform.position, transform.position + Vector3.down * distance;);
}
可以在场景视图看到射线检测的距离
记得修改玩家图层和检测图层
效果
不过如果你人物在边角的时候,检测还是会有问题。
如果你有更加精确的要求的话,可以使用多条射线检测,或者使用Physics.SphereCast
球形检测、Physics.BoxCast
方形检测、Physics.CapsuleCast
胶囊体检测,效果会更好,这里就不演示了
if(Physics.SphereCast(transform.position, characterController.radius, Vector3.down, out hit, distance, layerMask)){
Debug.Log(2222);
};
19、限制人物下落速度
如果我们开始下落,我们不想他无限加速,我希望限制这个值。我不希望玩家从很高的地方跌落,速度太快。
public float maxVelocityY = 50f;//最大垂直速度
//将y轴速度限制在+-maxVelocityY之间
verticalVelocity.y = Mathf.Clamp(verticalVelocity.y, -maxVelocityY, maxVelocityY);
20、物体会挡住相机视野和相机穿模
我们可以添加虚拟相机Cinemachine Deoccluder
反遮挡器,具体可以参考:https://docs.unity3d.com/Packages/com.unity.cinemachine@3.1/manual/CinemachineDeoccluder.html
效果
21、相机上下楼梯,跳跃有点抖动
我们可以适当增大相机的阻尼
效果
待续
暂时先实现这么多,如果后续想到加其他功能再来添加。
专栏推荐
完结
好了,我是向宇
,博客地址:https://xiangyu.blog.csdn.net,如果学习过程中遇到任何问题,也欢迎你评论私信找我。
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!