![演示](https://img-blog.csdnimg.cn/2020103114224425.gif#pic_center)
实现步骤
在做角色控制器的时候脑海里应该明白一点,与外界做交互的是角色身上的碰撞器(不可见),玩家看到的所有模型以及动画(可见)只是表现层,是角色控制器逻辑的体现。如果往极端地想,哪怕没有模型以及动画,游戏应该也能够正常运行,因为游戏内部的逻辑依旧在运行,只是没有表现给玩家看而已。
这里只是提供一种实现思路,为了压缩文章长度不会把所有细节都解释一遍。
角色模型层级设置
PlayerController放在父级(碰撞器同级),PlayerAnimController放在子级(动画状态机同级),下面所说的角色都代表着父级。
输入检测
Vector3 moveDir;
private void Update()
{
InputCheck();
}
void InputCheck()
{
var h = Input.GetAxis("Horizontal");
var v = Input.GetAxis("Vertical");
moveDir.x = h;
moveDir.z = v;
}
获得横向与竖向的输入值,保存到变量中,非常的基础,需要注意的有以下几点:
- Input.GetAxisRaw 与 Input.GetAxis 区别,这里用GetAxis是因为不想额外设置一堆缓动速度的变量;
- 输入检测统一放在Update里调用,如果放在FixedUpdate会出现检测不到的问题;
- Gravity影响数值衰减速度,Sensitivity影响数值增加速度,调整成自己觉得合适的大小;
模型动画
private void FixedUpdate()
{
Vector2 movement = SquareToCircle(moveDir);
animController.SetFloat("SpeedZ", movement.sqrMagnitude);
}
Vector2 SquareToCircle(Vector3 oldVec)
{
Vector2 newVec;
newVec.x = oldVec.x * Mathf.Sqrt(1 - (oldVec.z * oldVec.z) / 2);
newVec.y = oldVec.z * Mathf.Sqrt(1 - (oldVec.x * oldVec.x) / 2);
return newVec;
}
计算输入的值的大小(sqrMagnitude获取向量的长度的平方),传进BlendTree,需要注意一下几点:
- SquareToCircle函数可以解决斜向时向量长度变成根号2倍的问题,具体原理搜索“椭圆映射法”;
角色移动
/// <summary>
/// 动画执行时每一帧调用
/// </summary>
private void OnAnimatorMove()
{
playerController.SetCharacterVelocity(anim.velocity);
}
public void SetCharacterVelocity(Vector3 velocity)
{
characterController.SimpleMove(new Vector3(velocity.x, characterController.velocity.y, velocity.z));
}
将动画根节点运动的速度传入CharacterController组件提供的SimpleMove函数来移动角色本体。需要注意以下几点:
- 角色移动有几种常用的方法,控制角色位置(Transform.Translate),控制刚体位置(Rigibody.MovePosition)或速度(Rigidbody.velocity),控制角色控制器位置(CharacterController.Move)或速度(CharacterController.SimpleMove),利用动画根节点运动(Animator.rootPosition)或速度(Animator.velocity);
- 这里用动画自带的位移,可以直接获得与动画匹配的移动速度,不带根节点运动的动画不能用这种方法,只能自己给一个合适的速度;
- OnAnimatorMove必须放在与动画状态机同级的脚本中才能被调用;
摄像机自由视角转向
这里用到Cinemachine插件,功能非常强大。如果自己实现需要设置一堆缓冲速度的变量,调试这些变量会浪费大量时间。其中摄像机碰撞问题是最大的难点(我不会=,=),如果想要自己实现请百度“unity摄像机碰撞”。
Cinemachine组件详解
下面只会讲几个用到的参数,其他参数点击上方链接查看,非常详细。
Cinemachine插件在Window/PackageManager中搜索Cinemachine进行安装,安装后最上方一栏会多出Cinemachine菜单,选择FreeLook Camera。
上图右侧标注的属性依次是:优先级,跟随的目标,看向的目标,摄像机视野设置。
相机设置步骤如下:
- 优先级暂时不用变;
- 跟随的目标设置成角色本体;
- 看向的目标设置成角色的脖子(角色增加一个子级空对象,并向上拉至脖子位置);
- Lens中的参数在主摄像机中也有,但只要添加了虚拟相机就只能调整虚拟相机的Lens参数,不能调整主摄像机的Lens参数;
- Binding Mode设置成World Space,只关心坐标位置。下面的参数看着Scene窗口调整很快就能明白用处;
- Damping越大缓动效果越明显,这里我全部设置成1;
- 设置完这些参数后应该就能达到第三人称自由视角的效果了;
角色转向
解决摄像机问题后,会发现无论摄像机处于什么转向,输入检测到哪个方向的值,人物都只会向前移动,那是因为模型的朝向没有改变,一直是往前的,那么速度的方向当然也不会改变。我们要做的就是将相机的朝向与角色的朝向关联起来,也就是 角色目标方向 = 输入的方向 + 摄像机的方向,因为模型是角色的子物体,所以朝向也会随之改变。
void FreeLook()
{
if (moveDir.sqrMagnitude == 0)
return;
//将输入检测时保存的输入值进行归一化处理,得到的向量可以看出想要移动的方向
Vector3 dir = moveDir.normalized;
//Atan2函数可以得到输入方向的弧度,然后乘于Mathf.Rad2Deg从弧度转换成角度
//输入的角度再加上相机目前的角度就能得到最终角色应该朝向的角度
float angle = Mathf.Atan2(dir.x, dir.z) * Mathf.Rad2Deg + Camera.main.transform.eulerAngles.y;
//将目标朝向的三维向量转换为四元数
Quaternion targetDir = Quaternion.Euler(0, angle, 0);
//角色朝向缓动至目标朝向
transform.rotation = Quaternion.Lerp(transform.rotation, targetDir, 12 * Time.deltaTime);
}
上面的代码可能一下子看不懂,没关系,自己搜索一下两个函数的用法,或者自己输出一下moveDir.normalized和Mathf.Atan2(dir.x, dir.z) * Mathf.Rad2Deg,观察得到的结果就能非常直观的感受到这两个数代表了什么,这里就不详细说明了。角色朝向正确的方向后,就能得到文章开头动图的效果。