UnityRPG第三人称控制器
Posted Talkey817
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UnityRPG第三人称控制器相关的知识,希望对你有一定的参考价值。
## 引言:
* 之前一直都很苦恼角色控制器和相机的跟随编写,最新版本中unity官方更新了InputActionAssety和StarterAssets,下面对其进行简单的介绍
*
### 下载示例:
unity资源商店:Starter Assets - Third Person Character Controllerhttps://assetstore.unity.com/packages/essentials/starter-assets-third-person-character-controller-196526* 添加到unity项目中就可以使用,个人认为蛮方便的
* 下载后打开starterassets中的PlayGround
* 打开后会有一个这样的场景:(我把模型替换成雷电将军了)
* 玩家角色物体是PlayerAmature,其中包含着网格和骨骼两个部分
* PlayerMature默认挂载的脚本有:
### Player Input:
* 决定了玩家的输入操作,比如移动,跳跃冲刺灯等操作的按键和操作方式(虚拟摇杆或者键盘)
* 在assets中右键,找到Input Action选项创建一个新的Input Action Asset,双击打开会出现下面的界面
* 点击ActionMap的加号创建新的行为映射,命名为Player,一个行为映射下会有多个行为,这里创建wasd移动为例子
* 点击Actions中的加号创建新的Action,并在右侧ActionProperties进行如下设置:
* 在行为旁边点击加号创建一个新的Composite,会自动生成Up,Down,Left,Right四个方向的行为
* 我们一一对键位进行设置,Up设置为键盘上的w键位
* 四个键位都设置好了之后就可以保存并关闭这个界面,并在刚刚打开的角色上找到PlayerInput组件,将刚刚创建的输入添加为行为映射并运行游戏
* 可以看到角色移动没有问题,当然,原本官方已经配置好了包括鼠标视角跟随,角色跳跃,角色移动等按键的输入,没有必要自己再写一套输入(默认的配置如下图)
### 如何添加角色技能的输入?
* 新建行为并添加一个binding,设置好对应键位和可以在哪个平台上使用就可以了
### 当然仅仅添加键位还是不行的,还要在代码中添加,打开脚本StarterAssetsInputs
* 我刚刚添加了AEQ三个攻击方式,代码添加如下
* 布尔值可以代表着该按键是否被按下,用来后期处理输入和角色技能释放的过程
### 接下来进行按键按下角色状态转换的代码,在角色身上挂载着ThirdPersonController,打开并添加相关的变量
接下来就是动画状态机的处理,下一篇文章继续说吧
[Unity]尝试实现第三人称控制器(其四移动控制②)
[Unity]尝试实现第三人称控制器(其四、移动控制②)
【声明】此第三人称控制器是复现unity明星资产中第三人称控制器,如果有需要可以直接下载该资产并学习。我个人是不喜欢重复造轮子的行为的,但是官方的控制器我使用时有些不尽如人意的BUG。尽管如此,该控制器的效果让我觉得很不错,虽然无法实现无缝动画,不过个人项目应该先想办法让其动起来才行。
【版本】此项目基于Unity【2021.3lts】版本制作
方向修正
还记得我们在Move方法中的位移角度的控制吗?
我们既然获得了摄像机,现在我们只需要将摄像机的角度也计算进来,我们的方向和摄像机就达到了联动效果
_targetRotation = Mathf.Atan2(currentInput.x, currentInput.z) * Mathf.Rad2Deg
+ _mainCamera.transform.eulerAngles.y;
我自己的代码里加了一部分对于动画的控制,这部分以后再讲,仅仅是对一个值的控制罢了。
速度拓展
接下来我们可以制作跑步之类的速度了。我们可以使用如下两种方式制作:
Ⅰ.对不同的状态进行bool值的创建。当激活某个状态时,直接将该bool值激活。
Ⅱ.对一组相斥的状态创建enum,如idle,walk,run应该时互斥的,为其创建一组互斥状态。
老实讲,我认为两种方式没什么太大的差距,enum不熟悉,第一种就足够。第二种主要是看的会清晰一点点。还是那句话,没有优劣,仅仅是是否习惯。
public enum PlayerActionState
Idle,
Walk,
Run
public PlayerActionState playerActionState = PlayerActionState.Idle;
我们现在考虑对这个状态的控制放到哪里。其实你可以放在之前的输入控制部分,毕竟我们角色的状态大都是由输入值来决定的,我们在那边改变后,只需要在Move中结合角色的坠落之类的信息判断角色的状态即可。当然,我们在输入控制中也做过对外的状态数据,所以我们也可以在Move中直接查看。我们首先在每次Move执行的一开始对角色目前的状态进行判断
private void PlayerStateJudge()
playerActionState = PlayerActionState.Idle;
if (_inputsMassage.move!=Vector2.zero)
playerActionState = PlayerActionState.Walk;
if (_inputsMassage.run)
playerActionState = PlayerActionState.Run;
判断完成后,我们根据状态为_currentSpeed赋值
_currentSpeed = playerActionState switch
PlayerActionState.Idle => 0f,
PlayerActionState.Walk => walkSpeed,
PlayerActionState.Run => _runSpeed,
_ => 0f
;
这样就可以跑步了。
新的状态
我们会为角色添加下蹲的状态,当然,这个效果涉及到动画的更改
因为是较为独立的一个状态,我们通过bool控制。
我们在Move的开始去判断(请在所有状态判断时考虑依赖问题,将无需依赖的优先判断)
然后考虑run等状态与下蹲状态的优先度,越优先越靠后赋值
我们认为当你进入下蹲状态后,无法跑步,所以固定速度为下蹲行走速度。当退出下蹲状态不执行。
_currentSpeed = playerActionState switch
PlayerActionState.Idle => 0f,
PlayerActionState.Walk => walkSpeed,
PlayerActionState.Run => _runSpeed,
_ => 0f
;
if (_isCrouch)
_currentSpeed = _crouchSpeed;
速度的逐渐变化
目前来说,我们的速度是直接变化的。如果有朋友连接上动画进行测试会发现,我们的速度由于是直接变化的,动画与动画之间存在“跳变”。我们需要逐渐改变速度来润滑这个速度
如果学过数值分析,大家会发现这个就是插值。那么插值时,我们需要获取两个点,才能对其创建插值函数。那么这两个点是什么?就是当前的速度和要变化到的速度。
我们首先记录当前速度:
//玩家当前水平速度的参考
float currentHorizontalSpeed = new Vector3(_characterController.velocity.x,
0.0f, _characterController.velocity.z).magnitude;、
//偏离度,保证目标速度与目前速度相差大才可以插值,避免小幅度的抽搐
float speedOffset = 0.1f;
这里的magnitude是精确长度(我们之前说过速度和向量的问题,这里只需要大小)
接下来会有语义重复的情况,所以我们上方判断速度变成targetSpeed。
我们开始判断偏离度,如果说目标值和当前值相差很大,我们需要有一个插值变化过程使其顺滑;当小于偏离度后,我们认为直接变化也不会有人能够看出来,所以直接将其改变。
//判断偏离度
if (currentHorizontalSpeed < targetSpeed - speedOffset ||
currentHorizontalSpeed > targetSpeed + speedOffset)
_currentSpeed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed ,Time.deltaTime * SpeedChangeRate);
//四舍五入到小数点后3位
_currentSpeed = Mathf.Round(_currentSpeed * 1000f) / 1000f;
else
_currentSpeed = targetSpeed;
至此,我们的移动控制部分基本完成。我在与动画系统相连后,会出现动画抖动的问题,如果各位也有这种问题或者有解决这个问题的方式,请在下方留言
代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class ThirdPlayerMoveController : MonoBehaviour
CharacterController _characterController;
PlayerInput _playerInput;
PlayerInputsMassage _inputsMassage;
GameObject _mainCamera;
Animator _anim;
public PlayerActionState playerActionState = PlayerActionState.Idle;
[Header("相机设置")]
public GameObject _cinemachineFollowTarget;
float _cinemachineTagertX;
float _cinemachineTagertY;
[Tooltip("相机仰角")]
public float TopClamp = 70.0f;
[Tooltip("相机俯角")]
public float BottomClamp = -30.0f;
[Tooltip("额外的度数覆盖摄像头。有用的微调相机位置时,锁定")]
public float CameraAngleOverride = 0.0f;
[Header("玩家设置")]
[Tooltip("这将决定普通行走时的速度")]
public float walkSpeed = 1.5f;
[Tooltip("这将决定跑步时的速度")]
public float _runSpeed = 5.0f;
private bool _isCrouch = false;
[Tooltip("这将决定蹲下行走的速度")]
public float _crouchSpeed = 1.0f;
[Tooltip("这将决定不同状态速度间切换的速度")]
public float SpeedChangeRate = 10.0f;
private float _currentSpeed;
private float _targetRotation = 0.0f;
[Tooltip("角色光滑旋转时间")]
private float RotationSmoothTime = 0.12f;
[Tooltip("在角色光滑旋转过程中的速度")]
private float _rotationVelocity;
private float _threshold = 0.01f;
private bool IsCurrentDeviceMouse
get return _playerInput.currentControlScheme == "KeyboardMouse";
private void Awake()
// get a reference to our main camera
if (_mainCamera == null)
_mainCamera = GameObject.FindGameObjectWithTag("MainCamera");
// Start is called before the first frame update
void Start()
_characterController = GetComponent<CharacterController>();
_inputsMassage = GetComponent<PlayerInputsMassage>();
_playerInput = GetComponent<PlayerInput>();
_anim = GetComponentInChildren<Animator>();
private void FixedUpdate()
Move();
private void LateUpdate()
CameraRotation();
/// <summary>
/// 相机追踪点的控制
/// </summary>
private void CameraRotation()
if(_inputsMassage.look.sqrMagnitude>_threshold)//look值大于误差代表有输入
float deltaTimeMultiplier = IsCurrentDeviceMouse ? 1f : Time.deltaTime;
_cinemachineTagertX += _inputsMassage.look.x * deltaTimeMultiplier;
_cinemachineTagertY += _inputsMassage.look.y * deltaTimeMultiplier;
_cinemachineTagertX = ClampAngle(_cinemachineTagertX, float.MinValue, float.MaxValue);
_cinemachineTagertY = ClampAngle(_cinemachineTagertY, BottomClamp, TopClamp);
_cinemachineFollowTarget.transform.rotation = Quaternion.Euler((-_cinemachineTagertY - CameraAngleOverride) * Settings.mouseYmoveTimes,
_cinemachineTagertX * Settings.mouseXmoveTimes, 0.0f);
private void Move()
_isCrouch = _inputsMassage.crouch;
//在这里进行状态的判断
PlayerStateJudge();
//首先将移动速度赋予临时变量,考虑到有可能在其他地方使用,我们将其存储起来
//_currentSpeed = walkSpeed;(转换为更加完善的速度控制)
float targetSpeed = playerActionState switch
PlayerActionState.Idle => 0f,
PlayerActionState.Walk => walkSpeed,
PlayerActionState.Run => _runSpeed,
_ => 0f
;
if (_isCrouch) targetSpeed = _crouchSpeed;
//玩家当前水平速度的参考
float currentHorizontalSpeed = new Vector3(_characterController.velocity.x, 0.0f, _characterController.velocity.z).magnitude;
//偏离度,保证目标速度与目前速度相差大才可以插值,避免小幅度的抽搐
float speedOffset = 0.1f;
//判断偏离度
if (currentHorizontalSpeed < targetSpeed - speedOffset ||
currentHorizontalSpeed > targetSpeed + speedOffset)
_currentSpeed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed ,Time.deltaTime * SpeedChangeRate);
//四舍五入到小数点后3位
_currentSpeed = Mathf.Round(_currentSpeed * 1000f) / 1000f;
else
_currentSpeed = targetSpeed;
//判断是否进行移动输入
if (_inputsMassage.move == Vector2.zero) _currentSpeed = 0;
var currentInput = new Vector3(_inputsMassage.move.x, 0, _inputsMassage.move.y).normalized;
//单位向量的方向,或者说位移方向
if (_inputsMassage.move!=Vector2.zero)
_targetRotation = Mathf.Atan2(currentInput.x, currentInput.z) * Mathf.Rad2Deg + _mainCamera.transform.eulerAngles.y;
#region 在位移过程中的转向
float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity,
RotationSmoothTime);
transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
#endregion
Vector3 targetDir = Quaternion.Euler(0.0f, _targetRotation, 0.0f) * Vector3.forward;
_characterController.Move(targetDir.normalized * _currentSpeed * Time.deltaTime);
//TODO:这里的Move可以执行垂直方向的速度,直接加上垂直的Vector就可以
_anim.SetFloat("Speed", _currentSpeed);
_anim.SetBool("Crouch", _isCrouch);
/// <summary>
/// 限制角度
/// </summary>
/// <param name="lfAngle"></param>
/// <param name="lfMin"></param>
/// <param name="lfMax"></param>
/// <returns></returns>
private static float ClampAngle(float lfAngle, float lfMin, float lfMax)
if (lfAngle < -360f) lfAngle += 360f;
if (lfAngle > 360f) lfAngle -= 360f;
return Mathf.Clamp(lfAngle, lfMin, lfMax);
/// <summary>
/// 对玩家状态进行判断
/// </summary>
private void PlayerStateJudge()
playerActionState = PlayerActionState.Idle;
if (_inputsMassage.move!=Vector2.zero)
playerActionState = PlayerActionState.Walk;
if (_inputsMassage.run)
playerActionState = PlayerActionState.Run;
以上是关于UnityRPG第三人称控制器的主要内容,如果未能解决你的问题,请参考以下文章
Unity怎么添加character controller的