类刺客信条跑酷系统开发日记

Parkour Climbing System开发日记

——类刺客信条跑酷系统

Day1 摄像头脚本

在unity中,xyz轴是右手坐标系,即x水平向右,y垂直向上,z水平向前

public class CameraController : MonoBehaviour {     //摄像机跟随的目标     [SerializeField] Transform followTarget;      // Update is called once per frame     void Update()     {         //摄像机放在目标后面5个单位的位置         transform.position = followTarget.position - new Vector3(0, 0, 5);     } } 

怎么旋转这个相机呢?

类刺客信条跑酷系统开发日记

摄像机向后移动的参量乘一个水平旋转角度

所以,引入四元数点欧拉Quaternion.Euler

这个水平视角旋转角度需要绕y轴的旋转角度,还需要鼠标控制这个角度

并且,当摄像头旋转的时候,摄像头始终对着player

public class CameraController : MonoBehaviour {     //摄像机跟随的目标     [SerializeField] Transform followTarget;     //距离     [SerializeField] float distance;      //绕y轴的旋转角度     float rotationY;      private void Update()     {         //鼠标x轴控制rotationY         rotationY += Input.GetAxis("Mouse X");         //水平视角旋转参量         //想要水平旋转视角,所以需要的参量为绕y轴旋转角度         var horizontalRotation = Quaternion.Euler(0, rotationY, 0);          //摄像机放在目标后面5个单位的位置         transform.position = followTarget.position - horizontalRotation * new Vector3(0, 0, distance);         //摄像机始终朝向目标         transform.rotation = horizontalRotation;     } } 

类刺客信条跑酷系统开发日记

完成了水平视角的旋转

让相机垂直旋转

类刺客信条跑酷系统开发日记

还需要在垂直视角旋转的时候合理的限幅:让视角最高不超过45°,最低到人物的胸部位置

public class CameraController : MonoBehaviour {     //摄像机跟随的目标     [SerializeField] Transform followTarget;     [SerializeField] float rotationSpeed = 1.5f;     //距离     [SerializeField] float distance;      //绕y轴的旋转角度——水平视角旋转     float rotationY;     //绕x轴的旋转角度——垂直视角旋转     float rotationX;     //限制rotationX幅度     [SerializeField] float minVerticalAngle = -20;     [SerializeField] float maxVerticalAngle = 45;     //框架偏移向量——摄像机位置视差偏移     [SerializeField] Vector2 frameOffset;      private void Update()     {         //鼠标x轴控制rotationY         rotationY += Input.GetAxis("Mouse X") * rotationSpeed;         //鼠标y轴控制rotationX         rotationX += Input.GetAxis("Mouse Y") * rotationSpeed;         //限制rotationX幅度         rotationX = Mathf.Clamp(rotationX, minVerticalAngle, maxVerticalAngle);          //视角旋转参量         //想要水平旋转视角,所以需要的参量为绕y轴旋转角度         var targetRotation = Quaternion.Euler(rotationX, rotationY, 0);          //摄像机的焦点位置         var focusPosition = followTarget.position + new Vector3(frameOffset.x, frameOffset.y, 0);         //摄像机放在目标后面5个单位的位置         transform.position = focusPosition - targetRotation * new Vector3(0, 0, distance);         //摄像机始终朝向目标         transform.rotation = targetRotation;     } } 

类刺客信条跑酷系统开发日记

大致实现了摄像机跟随人物进行旋转

还需要一些细节调整:

    private void Start()     {         //隐藏光标         Cursor.lockState = CursorLockMode.Locked;         Cursor.visible = false;      } 

考虑到存在大多数角色控制器都有控制反转的选项

using System.Collections; using System.Collections.Generic; using UnityEngine;  public class CameraController : MonoBehaviour {     //摄像机跟随的目标     [SerializeField] Transform followTarget;     [SerializeField] float rotationSpeed = 1.5f;     //距离     [SerializeField] float distance;      //绕y轴的旋转角度——水平视角旋转     float rotationY;     //绕x轴的旋转角度——垂直视角旋转     float rotationX;     //限制rotationX幅度     [SerializeField] float minVerticalAngle = -20;     [SerializeField] float maxVerticalAngle = 45;     //框架偏移向量——摄像机位置视差偏移     [SerializeField] Vector2 frameOffset;      //视角控制反转     [Header("视角控制反转:invertX是否反转垂直视角,invertY是否反转水平视角")]     [SerializeField] bool invertX;     [SerializeField] bool invertY;      float invertXValue;     float invertYValue;      private void Start()     {         //隐藏光标         Cursor.lockState = CursorLockMode.Locked;         Cursor.visible = false;      }      private void Update()     {         //视角控制反转参数         invertXValue = (invertX)? -1 : 1;         invertYValue = (invertY)? -1 : 1;          //水平视角控制——鼠标x轴控制rotationY         rotationY += Input.GetAxis("Mouse X") * rotationSpeed * invertYValue;         //垂直视角控制——鼠标y轴控制rotationX         rotationX += Input.GetAxis("Mouse Y") * rotationSpeed * invertXValue;         //限制rotationX幅度         rotationX = Mathf.Clamp(rotationX, minVerticalAngle, maxVerticalAngle);          //视角旋转参量         //想要水平旋转视角,所以需要的参量为绕y轴旋转角度         var targetRotation = Quaternion.Euler(rotationX, rotationY, 0);          //摄像机的焦点位置         var focusPosition = followTarget.position + new Vector3(frameOffset.x, frameOffset.y, 0);         //摄像机放在目标后面5个单位的位置         transform.position = focusPosition - targetRotation * new Vector3(0, 0, distance);         //摄像机始终朝向目标         transform.rotation = targetRotation;     } }  

我通常喜欢这样选择,垂直反转(鼠标向上就看上面),水平不反转(鼠标向左就看左边)

类刺客信条跑酷系统开发日记

就是让摄像机视角和鼠标移动方向对我来说是同步的,相当于第一人称视角控制的习惯

类刺客信条跑酷系统开发日记

Day2 第三人称人物控制脚本

前序准备

先创建个人物模型( 从Mixamo下载的)

导入unity中,选择模型后点开inspector-Materials-Textures,选一个文件夹存放纹理

类刺客信条跑酷系统开发日记

类刺客信条跑酷系统开发日记

OK,下面就开始为这个角色写控制脚本吧!

最简化的第三人称角色控制

public class PlayerController : MonoBehaviour {     [SerializeField]float moveSpeed = 5f;     private void Update()     {         float h = Input.GetAxis("Horizontal");         float v = Input.GetAxis("Vertical");          //标准化 moveInput 向量         var moveInput = new Vector3(h, 0, v).normalized;          transform.position += moveInput * moveSpeed * Time.deltaTime;      }  } 

需要注意的:

  1. .normalized

    如果不进行标准化,moveInput 向量的长度会变得大于1(具体来说,比如h,v长度都为1,(sqrt{h^2 + 0^2 + v^2} = sqrt{1^2 + 0^2 + 1^2} = sqrt{2} approx 1.414))。这意味着在对角线方向上移动时,玩家的移动速度会比只在一个方向上移动时快。为了确保玩家在所有方向上移动时速度一致,需要对向量进行标准化。

  2. Time.deltaTime

    Time.deltaTime 是Unity引擎提供的一个浮点数,表示从上一帧到当前帧所用的时间(以秒为单位)。使用 Time.deltaTime 可以确保玩家的移动速度在不同帧率下保持一致。如果不使用 Time.deltaTime,在高帧率下玩家会移动得更快,在低帧率下玩家会移动得更慢。

改进

上面这样显然不能满足角色控制,因为当我们按下前进方向键的时候,人物并没有根据当前摄像机显示的方向移动

还需要进行如下改进:

在CameraController.cs里面加入

    //水平方向的旋转,返回摄像机的水平旋转四元数。     public Quaternion PlanarRotation => Quaternion.Euler(0, rotationY, 0); 

这里提一句C#中的特性:

大多数语言中想要获取一个返回值,需要定义一个函数,然后返回

    public Quaternion GetPlanarRotation()     {         return Quaternion.Euler(0, rotationY, 0);     } 

但是C#可以优雅的利用表达式主体定义的属性,直接获取这个属性

然后在PlayerController.cs里调用这个返回值

public class PlayerController : MonoBehaviour {     [SerializeField]float moveSpeed = 5f;      CameraController cameraController;      private void Awake()     {         //相机控制器设置为main camera         cameraController = Camera.main.GetComponent<CameraController>();     }     private void Update()     {         float h = Input.GetAxis("Horizontal");         float v = Input.GetAxis("Vertical");          float moveAmount = Mathf.Abs(h) + Mathf.Abs(v);          //标准化 moveInput 向量         var moveInput = new Vector3(h, 0, v).normalized;          //让人物移动方向关联相机的朝向         var moveDir = cameraController.PlanarRotation * moveInput;          //每次判断moveAmount的时候,确保只有在玩家实际移动时才会更新移动+转向 	//没有输入就不更新转向,也就不会回到初始朝向         if (moveAmount > 0)         {             //帧同步移动             transform.position += moveDir * moveSpeed * Time.deltaTime;             //人物模型转起来:让人物朝向与移动方向一致             transform.rotation = Quaternion.LookRotation(moveDir);         }      }  }  

这里解决了一个问题:

当方向键输入结束,人物模型朝向又回到了初始状态朝向

所以需要实时响应输入

  • if (moveAmount > 0)只有输入的时候才会更新人物朝向
  • 确保模型始终朝向移动方向。

但是还有一个问题:

人物朝向切换太快了,需要设置一个转向速度,让人物从当前朝向到目标朝向慢慢转向

    [SerializeField]float rotationSpeed = 10f;      Quaternion targetRotation; 
        //每次判断moveAmount的时候,确保只有在玩家实际移动时才会更新移动+转向         //没有输入就不更新转向,也就不会回到初始朝向         if (moveAmount > 0)         {             //帧同步移动             transform.position += moveDir * moveSpeed * Time.deltaTime;             //人物模型转起来:让人物朝向与移动方向一致             targetRotation = Quaternion.LookRotation(moveDir);         }         //更新transform.rotation:让人物从当前朝向到目标朝向慢慢转向         transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation,                          rotationSpeed * Time.deltaTime); 

实现效果如下:

类刺客信条跑酷系统开发日记

该部分完整代码:

using System.Collections; using System.Collections.Generic; using UnityEngine;  public class PlayerController : MonoBehaviour {     [Header("玩家属性")]     [SerializeField]float moveSpeed = 5f;     [SerializeField]float rotationSpeed = 10f;      Quaternion targetRotation;      CameraController cameraController;      private void Awake()     {         //相机控制器设置为main camera         cameraController = Camera.main.GetComponent<CameraController>();     }     private void Update()     {         float h = Input.GetAxis("Horizontal");         float v = Input.GetAxis("Vertical");          float moveAmount = Mathf.Abs(h) + Mathf.Abs(v);          //标准化 moveInput 向量         var moveInput = new Vector3(h, 0, v).normalized;          //让人物移动方向关联相机的朝向         var moveDir = cameraController.PlanarRotation * moveInput;          //每次判断moveAmount的时候,确保只有在玩家实际移动时才会更新移动+转向         //没有输入就不更新转向,也就不会回到初始朝向         if (moveAmount > 0)         {             //帧同步移动             transform.position += moveDir * moveSpeed * Time.deltaTime;             //人物模型转起来:让人物朝向与移动方向一致             targetRotation = Quaternion.LookRotation(moveDir);         }         //更新transform.rotation:让人物从当前朝向到目标朝向慢慢转向         transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation,                          rotationSpeed * Time.deltaTime);      }  } 

Day3 Animation动画

前序准备

有一个待解决的问题:如何让动画匹配任意人物模型?

  1. 找到人物模型,进行如下设置:

类刺客信条跑酷系统开发日记

应用后点configure查看骨骼映射情况,如果有没匹配上的需要手动调整

类刺客信条跑酷系统开发日记

最后Done完成

  1. 找到要用到的动画,如下设置:

类刺客信条跑酷系统开发日记

注意Avatar Definition要选择 从其他avatar复制,然后在source里面选择要应用的avatar

  1. 然后每个动画都进行如下设置:

类刺客信条跑酷系统开发日记

注意要选择Loop Pose,如果loop match 的话可以不勾选Bake Into Pose

类刺客信条跑酷系统开发日记

而且几个动画的Length值最好要尽可能接近,以免后面切换的时候出现问题

  1. 新建一个角色控制器的动画脚本

类刺客信条跑酷系统开发日记

  1. 记得在player的Animator属性里添加这个脚本

万事俱备,下面开始编写动画相关脚本!

Animator组件——动画蓝图

新建一个Blend Tree

类刺客信条跑酷系统开发日记

拖入对应动画

类刺客信条跑酷系统开发日记

在PlayerController.cs里写动画播放逻辑

    Animator animator; 
    private void Awake()     {         //相机控制器设置为main camera         cameraController = Camera.main.GetComponent<CameraController>();         //角色动画         animator = GetComponent<Animator>();     } 

Update()方法:

        //把moveAmount限制在0-1之间(混合树的区间)         float moveAmount = Mathf.Clamp01(Mathf.Abs(h) + Mathf.Abs(v)); 

Blender Tree里moveMount的区间是(0,1)

        #region 角色动画控制         //角色动画播放         animator.SetFloat("moveAmount", moveAmount,0.2f,Time.deltaTime);          #endregion 

SetFloat()有四个参数的重载,第三个参数是要平滑到达的值

基本的第三人称角色控制器效果如下:

类刺客信条跑酷系统开发日记

修改后的PlayerController.cs完整代码:

using System.Collections; using System.Collections.Generic; using UnityEngine;  public class PlayerController : MonoBehaviour {     [Header("玩家属性")]     [SerializeField]float moveSpeed = 5f;     [SerializeField]float rotationSpeed = 10f;      Quaternion targetRotation;      CameraController cameraController;     Animator animator;      private void Awake()     {         //相机控制器设置为main camera         cameraController = Camera.main.GetComponent<CameraController>();         //角色动画         animator = GetComponent<Animator>();     }     private void Update()     {         #region 角色输入控制         float h = Input.GetAxis("Horizontal");         float v = Input.GetAxis("Vertical");          //把moveAmount限制在0-1之间(混合树的区间)         float moveAmount = Mathf.Clamp01(Mathf.Abs(h) + Mathf.Abs(v));          //标准化 moveInput 向量         var moveInput = new Vector3(h, 0, v).normalized;          //让人物移动方向关联相机的朝向         var moveDir = cameraController.PlanarRotation * moveInput;          //每次判断moveAmount的时候,确保只有在玩家实际移动时才会更新移动+转向         //没有输入就不更新转向,也就不会回到初始朝向         if (moveAmount > 0)         {             //帧同步移动             transform.position += moveDir * moveSpeed * Time.deltaTime;             //人物模型转起来:让人物朝向与移动方向一致             targetRotation = Quaternion.LookRotation(moveDir);         }         //更新transform.rotation:让人物从当前朝向到目标朝向慢慢转向         transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation,                          rotationSpeed * Time.deltaTime);         #endregion          #region 角色动画控制         //角色动画播放         animator.SetFloat("moveAmount", moveAmount,0.2f,Time.deltaTime);          #endregion       }  }  

Day4 物理引擎——碰撞体和重力

发表评论

评论已关闭。

相关文章