移动的方式有transform.Translate()、rigidbody.velocity()、rigidbody.MovePosition()等多种。本篇使用rigidbody的方式,并向着相机朝向的方向控制移动。也就是继上一篇“第三人称相机控制”,相机随鼠标点击而旋转位置之后,我们让主角小人朝向相机看向的方向移动(越来越接近商业游戏的界面了)。
本篇用到的API有:Rigidbody.MovePosition、rigidbody.MoveRotation、Vector3.Set、Vector3.Normalize、Camera.main、Vector3.RotateTowards、Vector3.RotateTowards、Mathf.Approximately、Camera.main 最终效果如下:
目录
1. Rigidbody的作用
2. 实现Rigidbody移动的过程分析
3. 实现旋转的过程分析
4. 脚本实现HeroMove
(1)Rigidbody组件:Rigidbody的重要性不用多说了,本专栏前面的文章已经多次使用和提及,凡是GameObject需要涉及物理力的影响,都需要添加此组件,并且大部分情况下需要配合Collider碰撞器使用。
上图是Rigidbody组件的参数解释,详见官方手册Rigidbody
以及笔记【Untiy学习笔记】Rigidbody组件及其常用函数_一白梦人的博客
(2)Rigidbody类:类中提供了关于重力、阻力、速度、移动、质量等与物理相关的方法。在本篇中,我们使用的是其中的 rigidbody.MovePosition()位置移动方法、 rigidbody.MoveRotation()旋转方法
其他方法可以参照笔记:第十章 Rigidbody类
(1)第一步:在初始化时使用GetComponent拿到挂在主角身上的Rigidbody组件;
rb=GetComponent();
(2)第二步:获取键盘输入的横向坐标和纵向坐标(由W、A、S、D提供);
moveInput = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
这个Input.GetAxis中的坐标名字(Horizental和Vertical),是通过InputManager设置的,这些按键触发获取水平方向的值的改变:
(3)第三步:坐标转换。第二步得到的是X、Y方向的坐标,而实际在unity空间中移动的方向是X、Z方向,因此,使用Vector3.set方法将(X,Y,0)转换成(X,0,Y),并使用Vector3..Normalize()方法单位化此向量,得到移动的方向;
movement.Set(moveInput.x, 0, moveInput.y);
movement.Normalize();
(4)第四步:Rigidbody.MovePosition实现移动
rb.MovePosition(rb.position + movement * moveSpeed * Time.DeltaTime);
(1)第一步:使用Mathf.Approximately(近似值)判断是否有横向和纵向的输入改变(键盘控制方向);
Mathf.Approximately(moveInput.x, 0);
Mathf.Approximately(moveInput.y, 0);
Mathf类有很多有用且有趣的API,使设计的过程更加快捷,之后我们还会用到很多。
(2)第二步:如果收到了键盘控制方向的命令,那就改变一下小人移动的方向,让它乘上主相机的方向,这里使用了一个四元数与三维向量相乘,它的几何意义是表示这个向量按照这个四元数进行旋转之后得到的新的向量。
movement = Quaternion.Euler(0, Camera.main.transform.eulerAngles.y, 0) * movement;
然后用Vector3.RotateTowards()方法控制,将当前小人朝向的方向(transform.forwad)向目标前方旋转;
Vector3 lookForward = Vector3.RotateTowards(transform.forward, movement, rotateSpeed * Time.fixedDeltaTime, 360);
*Vector3 RotateTowards(当前向量, 目标向量,每次移动的最大角度(弧度),最大移动弧度):从当前角度向目标角度旋转,并且在旋转中控制增量,类似于球形插值。
(3)第三步:使用Quaternion.LookRotation来改变朝向;
targetRotation = Quaternion.LookRotation(lookForward);
*Quaternion.LookRotation(Vector3):官方文档翻译过来的解释就是“创建一个旋转,沿着forward(z轴)并且头部沿着upwards(y轴)的约束注视。也就是建立一个旋转,使z轴朝向view y轴朝向up”。看着就很迷……
通过实验可以基本了解,这个意思就是将物体的前方(forward)按照传入的这个三维向量旋转,最终结果是这个物体看向传入的这个向量,最后返回一个旋转角度。详见解释:Quaternion.LookRotation实现原理_刺子的博客
(4)第四步:使用Rigidbody.MoveRotation()实现旋转。
rb.MoveRotation(targetRotation);
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//对象:主角Hero(需要加载Rigidbody)
//作用:使用RigidBody控制物体移动
//主角的移动方向是主相机的朝向
public class HeroMove : MonoBehaviour
{
private Rigidbody rb;
private Vector2 movementInput; //获取横向或纵向的命令坐标
private Vector3 movement; //移动的坐标位置(将之前的二维坐标转成这里的三维)
public float moveSpeed = 2f;
private float rotateSpeed = 3;
private Quaternion targetRotation;
void Start()
{
rb = GetComponent();
}
void FixedUpdate()
{ //RigidBody属于物理层调用,主要在FixedUpdate()中
movementInput = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
//获取Hero横向和纵向移动的坐标,这个输入由键盘决定
movement.Set(movementInput.x, 0, movementInput.y); //将获取到的坐标转到三维向量中
movement.Normalize(); //并且将这个三维向量单位化,得到移动的方向
//检查横向和纵向是否有输入
bool hInput = !Mathf.Approximately(movementInput.x, 0);
bool vInput = !Mathf.Approximately(movementInput.y, 0);
if(hInput || vInput)
{ //如果有移动命令,同时还可以获取主相机的旋转位置,获得其绕Y轴的旋转方向
movement = Quaternion.Euler(0, Camera.main.transform.eulerAngles.y, 0) * movement;
//四元数和向量相乘可以表示这个向量按照这个四元数进行旋转之后得到的新的向量。
}
Vector3 lookForward = Vector3.RotateTowards(transform.forward, movement, rotateSpeed * Time.fixedDeltaTime,360);
//RotateTowards从当前方向向目标方向旋转
//这里是Hero的正前方向着目标方向movement旋转,旋转的度数(rotateSpeed * Time.fixedDeltaTime),每次移动的最大角度
//返回值为transform.forward+每次移动的最大角度
//当这个值超过movement时,返回movement
targetRotation = Quaternion.LookRotation(lookForward);//物体的z轴与lookForward对齐
//得到Hero的Rotation值:targetRotation
//这个方法配合transform.rotation = Quaternion.LookRotation()使用
rb.MovePosition(rb.position + movement * speed * Time.fixedDeltaTime);
//先获取当前位置rb.position,然后使用MovePosition方法移动到目标位置
rb.MoveRotation(targetRotation); //实现旋转
}
}
补充:1. 加了刚体组件和碰撞器的主角,碰到其他碰撞器很容易摔倒,可以在刚体组件中约束它的X、Z轴不产生旋转
2. 上一篇的相机跟随,为了让相机移动发生在主角移动之后,特地将相机的移动写在LateUpdate()中,而主角移动因为用到了物理系统的更新,就写在FixedUpdate()中。但它们两个的更新频率是不同的,一个根据逻辑帧的频率,一个是根据真实世界的时间频率,所以这样写会出现移动时卡顿的现象。
解决办法:将相机跟随也写到FixedUpdate()中。 那么该如何确定哪个脚本先执行呢?可以到Edit->ProjectSetting->ScriptExecutionOrder当中调整执行顺序
以上为使用Rigidbody组件实现移动的方法,另外在“管理阶层Managers”栏目中,将控制的方式修改为使用新版InputSystem和事件发送来实现,详见EventManager--事件中心3和新版InputSystem