控制移动和转向的方法很多,教程也很多,照着教程代码输入能实现,反正能试出来,不是什么复杂的事情,但是过段时间后就忘记了,感觉还是没有搞清楚原理,因此趁机弄清楚原理记下来。
关于向量的描述,就是有大小和方向的量,unity
中大小用magnitude
表示,方向用normalized
表示。如下:
Vector3 vector = new Vector3(3f, 0f, 3f);
Vector3 vector2 = vector.normalized; // 获取vector的归一化向量,即(0.7f, 0f, 0.7f), vector不变,vector2大小是1
float magnitude = vector.magnitude; // magnitude是vector的大小, vector不变
vector.Normalize(); // vector本身被归一化,即变成(0.7f, 0f, 0.7f)
向量的加减,初中数学和初中物理的力学力的合成有学过,不需要这里描述。
控制物体移动通常通过键盘输入方向;或者鼠标点击目标位置,如下图:
上图是unity
俯视图,x
坐标和z
坐标是世界坐标,O点是世界坐标原点,物体在S
点,那么物体的位置transform.position
就是向量OS
或者s
,当按下键盘按键D
或者向右方向键的时候,输入向量是OM
,或者m
,那么向量 u = s + m u = s + m u=s+m,向量u
或者说就位置T
是物体要移动到的位置。代码如下:
float x = Input.GetAxis("Horizontal"); // 获取控制输入
float z = Input.GetAxis("Vertical");
Vector3 m = new Vector3(x,0f,z); // 创建控制输入向量m
Vector3 u = transform.position + m; // transform.position就是向量s,计算出要移动到的目标向量位置
上面计算出目标向量位置后,赋值给transform.position
就能实现移动。如下:
transform.position = u; // 通过修改物体位置实现移动
或者用刚体的方法,如下:
Rigidbody rigidbody = GetComponent(); // 获取刚体组件
rigidbody.MovePosition(u); // 让刚体从当前位置移动到目标位置向量u,即T点的位置
以上代码,通常在Update
或者FixedUpdate
方法中调用,以Update
方法为例,即每一帧调用一次,每次移动更新物体的移动位置transform.position
,等下一帧调用的时候,检测到控制还在输入,又在新的物体的位置下加上控制输入向量m
,得到新的目标位置移动向量u
,实现不断的移动。流程如下:
因为帧率会变动的,而且物体又移动速度的差异,所以实际使用时,每次的移动大小需要考虑速度和帧率的变动,也要考虑控制输入斜向的时候大小会比单个方向大,比如单纯按下右方向,获取到最大的x
是1,此时向量大小是1,如果向右和向上同时按下,那么获取到最大x
是1以及z
是1,此时向量大小要乘以个 2 \sqrt2 2,造成斜向移动快。所以实际使用是只取控制向量的方向,大小按照速度和帧率考虑。如果物体速度是speed
米/秒,unity提供了距离上一帧的时间是Time.deltatime
秒,可以理解成是Time.deltatime
秒/帧,那么speed * Time.deltatime
就是这一帧移动的米数,可以作为这一帧移动向量的大小。
如果用刚体的方法实现移动,实际的代码会是下面的样子:
/* 没有根动画的移动 */
public float speed = 1f; // 允许设置速度
void Start()
{
rigidbody = GetComponent(); // 获取刚体组件
}
void Update()
{
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 m = new Vector3(x,0f,z); // 创建控制输入向量m
m.Normalize(); // 把控制输入向量归一化,只要方向,这样就不会斜向速度偏快了
Vector3 u = transform.position + m * speed * Time.deltatime; // 考虑每帧移动的向量大小
rigidbody.MovePosition(u); // 让刚体从当前位置移动到目标位置向量
}
如果物体带有根动画的情况下,因为根动画,动画本身会影响位置变化,会涉及到图像渲染更新和物理操作更新的同步问题,改天另外写一篇总结一下。
上图是unity
俯视图,x
坐标和z
坐标是世界坐标。橙色的长方形代表物体,垂直于长方形的向量transform.forward
就是物体自身坐标的Z轴,即物体的前向向量。
如果此时按下了W按键或者向上按键,那么在世界坐标的Z轴正方向就会有一个控制向量,表示物体的目标朝向。
可见就是要让物体直接转到输入控制向量的方向,转向是用transform.rotation
表示的,这是一个四元数,需要把输入控制向量m
转成四元数,代码如下:
float x = Input.GetAxis("Horizontal"); // 获取控制输入
float z = Input.GetAxis("Vertical");
Vector3 m = new Vector3(x,0f,z); // 创建控制输入向量m
transform.rotation = Quaternion.LookRotation(m); // Vector3转成4元数Quaternion
这段代码直接实现了转向到输入控制的方向。
但是!以上代码通常也是在Update
或者FixedUpdate
方法中调用的,这样一帧就转向到位置了,不符合实际情况,应该转向有个过程的,比如转速turnSpeed
是90°/秒,即 π / 2 \pi/2 π/2弧度/秒,那么一帧的时间Time.deltatime
,转过的角度应该是 t u r n S p e e d ∗ T i m e . d e l t a t i m e turnSpeed * Time.deltatime turnSpeed∗Time.deltatime,unity提供了一个方法Vector3.RotateTowards
,参数传入开始的前向向量和最终的前向向量,以及每次转向允许的最大弧度和最大向量大小,这个方法会返回计算好的这一次应该转向的前向向量,代码如下:
float x = Input.GetAxis("Horizontal"); // 获取控制输入
float z = Input.GetAxis("Vertical");
Vector3 m = new Vector3(x,0f,z); // 创建控制输入向量m
Vector3 turnForward = Vector3.RotateTowards(transform.forward, m, turnSpeed * Time.deltaTime, 0f);
transform.rotation = Quaternion.LookRotation(turnForward); // Vector3转成4元数Quaternion
以上代码的第4行的方法,第一个参数就是开始前向向量,即物体当前的Z轴方向,第二个参数是最终的目标前向向量m
,第三个就是每帧的转过的弧度。很显然,每帧都转过一个小角度,更新transform.rotation
和transform.forward
,随着每一帧更新,最终到达目标朝向。
执行转向除了给transform.rotation
赋值外,还可以有刚体的方法:
Quaternion rotation = Quaternion.LookRotation(turnForward); // Vector3转成4元数Quaternion
rigidbody.MoveRotation(rotation); // 4元数传给刚体方法实现转向
如果要实现背对,最终的朝向向量取反就行了,这个很容易理解。
Vector3 turnForward = Vector3.RotateTowards(transform.forward, -m, turnSpeed * Time.deltaTime, 0f); // m取反实现背对朝向
transform.rotation = Quaternion.LookRotation(turnForward); // Vector3转成4元数Quaternion