黑魂复刻游戏的玩家控制器(基础移动,动画实现及优化)——Unity随手记
今天实现的内容:
动画机设计理念
要我说,动画机真的是老生常谈的东西了。做了好几次了,目前也就是将角色的地面动画做进去。
设计理念就是使用混合树ground将地面移动动画统一管理起来。
动画机的运用及模型旋转
要运用动画机,需要将动画机和输入模块串接起来。所以我们需要新的脚本,没错,PlayerController。同样的,顺手解决角色的旋转功能,旋转的思路为直接修改模型的forward。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { // 玩家的人物模型 用来获取模型身上的动画机以及其它 public GameObject model; // 输入模块 public PlayerInput pi; // 动画机 private Animator anim; // Awake适合用来做GetComponent void Awake() { anim = model.GetComponent<Animator>(); pi = GetComponent<PlayerInput>(); } // Update is called once per frame void Update() { // 将输入转换为速度 赋值给动画机相关参数 anim.SetFloat("forward", pi.dirMag); // 只在有速度时能够旋转 防止原地旋转 if(pi.dirMag > 0.1f) model.transform.forward = pi.dirVec; } }
在我们的方案中,旋转的只是模型,PlayerController所在的父物体节点不会旋转
我们可以将旋转和输入的模长计算放到PlayerInput中进行,能让我们的代码看起来更漂亮。
// 玩家的输入模长量 用于当成向前量大小作为动画控制 public float dirMag; // 玩家的方向 用于旋转模型 public Vector3 dirVec; // Update is called once per frame void Update() { // 计算输入模长 dirMag = Mathf.Sqrt((dirUp * dirUp) + (dirRight * dirRight)); // 计算玩家的方向 dirVec = dirRight * transform.right + dirUp * transform.forward; // ... }
游戏玩家角色的位移
对于移动,这次我们将采用Rigidbody方案。通过直接修改刚体的位置来移动。
// 刚体 public Rigidbody rigidbody; // 行走速度 public float walkSpeed = 2.0f; // 角色的位移大小 private Vector3 movingVec; // Update is called once per frame void Update() { // ... // 计算移动量 速度向量 movingVec = pi.dirMag * model.transform.forward * walkSpeed; } // 处理刚体的操作 private void FixedUpdate() { // 直接修改position实现位移 rigidbody.position += movingVec * Time.fixedDeltaTime; 修改rb.velocity来实现位移 //rb.velocity = new Vector3(m_planarVec.x, rb.velocity.y, m_planarVec.z) }
爬坡测试
据说黑魂里面的斜楼梯实际上都是斜坡,这就是我们采用Rigidbody的原因,因为Rigidbody处理爬坡比较简单,相对的CharacterController处理爬楼梯比较简单。
坡度比较缓的坡我们可以爬上去,将来会继续完善爬坡的能力。
跑步
在原基础上加入奔跑。首先加入新的输入按键。并且在Update中判断是否按下对应按键
public string keyRun; //跑步键 // 是否正在奔跑 按压信号 public bool run; // Update is called once per frame void Update() { // 跑步信号 run = Input.GetKey(keyRun); // ... }
根据run的真假来调整PlayerController中的动画参数和移动速度。
// Update is called once per frame void Update() { // 将输入转换为速度 赋值给动画机相关参数 anim.SetFloat("forward", pi.dirMag * (pi.run ? 2.0f : 1.0f)); // 计算移动量 速度向量 movingVec = pi.dirMag * model.transform.forward * walkSpeed * (pi.run ? runMultiplier : 1.0f); // ... }
旋转的优化
按照之前的设计,旋转速度会很快。这里我们使用Slerp给旋转一个缓动的效果。
// Update is called once per frame void Update() { // 只在有速度时能够旋转 防止原地旋转 if(pi.dirMag > 0.1f) { // 运用旋转 使用Slerp进行效果优化 model.transform.forward = Vector3.Slerp(model.transform.forward, pi.dirVec, 0.3f); } // ... }
跑步动画的优化
// 将输入转换为速度 赋值给动画机相关参数 anim.SetFloat("forward", pi.dirMag * (pi.run ? 2.0f : 1.0f));
按以上代码设计,如果我们在走路时突然切换到跑步,由于dirMag是不变的,我们只是直接将它乘以2,会导致混合树的参数直接突变到2,行走到跑步动画的切换就没有一个过渡了。
void Update() { // 将现在的动画参数forward值通过lerp变化得到 anim.SetFloat("forward", Mathf.Lerp(anim.GetFloat("forward"), pi.dirMag * (pi.run ? 2.0f : 1.0f), 0.1f)); // ... }
采用以上方案能改善问题,forward的值将通过lerp去修改。