移动角色:三种方式里哪种适合你
怎么让角色移动?
三种方式里只选一种,绝不同时用两种:由你直接操控的玩家用 CharacterController,需要靠物理推动的物体用 Rigidbody,而 transform.Translate 只用于不发生碰撞的物体。所有速度都要乘以 Time.deltaTime,否则机器越快、游戏跑得越快。
在 Unity 里移动物体有三种方式,它们不能混着用。选错方式,角色就会陷进墙里、干脆不动,或者在 120 Hz 的手机上跑出两倍速。
同一面墙,三种方式去撞。只有 CharacterController 会贴着墙滑行,只有 Rigidbody 能推开木箱,而 transform.Translate 根本不知道那里有墙。
选一个 —— 全部的决定都在这里
CharacterController —— 你直接操控的角色就用它。它会贴着墙滑行、能爬上台阶,也不会被物理引擎推得乱晃。绝大多数第一人称和第三人称游戏用的都是它。
Rigidbody —— 该交给物理引擎打理的一切:滚动的球、你推的木箱、布娃娃。你只负责发出移动指令,实际发生什么由物理引擎决定。
transform.Translate —— 只用于永远不参与碰撞的东西:漂浮的拾取物、飘动的云、UI 元素。它是瞬移,推不动任何东西。
CharacterController
挂上组件后,用 Move() 来移动。它接收的是这一帧要走多远,而不是速度 —— 所以里面才有 deltaTime。
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class PlayerMove : MonoBehaviour
{
public float speed = 6f;
public float gravity = -20f;
CharacterController cc;
float verticalSpeed;
void Awake()
{
cc = GetComponent<CharacterController>();
}
void Update()
{
// Input is a direction, length 0 to 1. Never a distance.
Vector3 input = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
if (input.sqrMagnitude > 1f) input.Normalize();
// Gravity has to be applied by hand: a CharacterController ignores physics.
if (cc.isGrounded && verticalSpeed < 0f) verticalSpeed = -2f;
verticalSpeed += gravity * Time.deltaTime;
Vector3 move = input * speed + Vector3.up * verticalSpeed;
cc.Move(move * Time.deltaTime);
}
}Rigidbody
记住两条规则,剩下的水到渠成:要写 Rigidbody 而不是 transform,要在 FixedUpdate 里做而不是 Update 里。物理引擎按自己的时钟运行,你直接改 transform 它毫不知情,转头就把物体拽回原位。
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class BallMove : MonoBehaviour
{
public float speed = 8f;
Rigidbody rb;
Vector3 input;
void Awake()
{
rb = GetComponent<Rigidbody>();
}
void Update()
{
// Read input here: Update runs once per frame, so no press is missed.
input = new Vector3(Input.GetAxisRaw("Horizontal"), 0f, Input.GetAxisRaw("Vertical"));
if (input.sqrMagnitude > 1f) input.Normalize();
}
void FixedUpdate()
{
// Move here: FixedUpdate runs on the physics clock.
rb.MovePosition(rb.position + input * speed * Time.fixedDeltaTime);
}
}为什么 input 在 Update 里读取、在 FixedUpdate 里使用
FixedUpdate 默认每秒跑 50 次,而你的游戏画面可能每秒渲染 120 帧。所以在 FixedUpdate 里读 Input.GetKeyDown,会把两次物理步之间按下的键漏掉。
在 FixedUpdate 里用 Time.fixedDeltaTime,在 Update 里用 Time.deltaTime。其实 Unity 在哪边都会返回正确的值,但写对名字,读代码的人一眼就明白你的意图。
如果相机跟着它走,就把 Rigidbody 的 Interpolate 设为 Interpolate,不然那运动看起来就像在以 50 Hz 抖动。
transform.Translate
void Update()
{
transform.Translate(Vector3.forward * speed * Time.deltaTime);
}做出这个的那一课
移动、跳跃并落到方块上
这篇指南单独成篇,是一份可以直接照着做的配方。在课程里,同样的东西会作为贯穿全部五个章的那个项目的一部分来搭建 —— 角色与战斗,第 02 课。