前言
在游戏开发中,如何移动物体?是我们需要思考的事情。
Unity 引擎也提供了众多的方法,每个开发者的使用习惯也各不相同,所以往往不是很清楚在这种场景下哪种方式最好的或者最有效的。
那么,这篇文章,我想分享一下移动物体的一些方法和优缺点。
向某个方向移动
Transform.Position
众所周知,我们可以给对象的Transform组件赋予一个坐标来决定其位置。
transform.position = new Vector3(2, 1, 0);
当我们每一帧给对象赋予一个新的坐标,那么看起来,这个物体就是在运动的。
void Update() { var dir = new Vector3(0.02f, 0, 0); transform.position += dir; }
效果如下:
Transform.Translate()
由于直接改变 Position 属性看起来不太优雅。所以 Transform 组件提供了一个更友好的方法:Transform.Translate()
。
void Update() { var dir = new Vector3(0.02f, 0, 0); transform.Translate(dir); }
其实,他的内部与 Transform.Position
无异。
public void Translate(Vector3 translation, [DefaultValue("Space.Self")] Space relativeTo) { if (relativeTo == Space.World) this.position += translation; else this.position += this.TransformDirection(translation); }
效果与 Transform.Position 一致
但是这种方法产生了一个问题。由于设备之间的差异或者动态数据的变化会导致每一帧之间的间隔是不相等的,因此,如果以帧数来控制物体移动,物体的移动距离就没办法准确把握。
效果如下
所以我们需要在原来的基础上乘以 Time.deltaTime属性的值,从而保证每秒移动的距离是一致的。
void Update() { var dir = new Vector3(2f, 0, 0)*time; transform.Translate(dir); }
这样不同的帧数移动距离都会一致。
效果如下:
但这还不够优雅。在游戏中,我们经常需要改变物体的速度。为了方便实现,我们通常会使用单位向量来确定方向,增加一个浮点值来控制速度。
public float speed = 2; void Update() { var dir = new Vector3(2,0,0) transform.Translate(dir.normalized * speed * Time.deltaTime); }
移动到指定位置
移动到指定位置,大概有两种方式。
- 速度:物体通过特定速度向目标移动。
- 时间:物体在时间内到达目标。
Vector3.MoveTowards():固定速度
以固定的速度移动到目标位置
public Vector3 targetPosition; public float speed=10; void Update() { transform.position = Vector3.MoveTowards(transform.position, targetPosition, speed * Time.deltaTime); }
效果:
Vector3.SmoothDamp():平滑移动
又或者,我们可以用平滑的方式到达目标位置。(平滑:到达位置前提前减速)
public Vector3 targetPosition; public float smoothTime = 0.5f; public float speed = 10; Vector3 velocity ; void Update() { transform.position = Vector3.SmoothDamp(transform.position, targetPosition, ref velocity, smoothTime, speed); }
效果:
Vector3.Lerp():线性时间移动
该方法的意思是在调用方法期间,已经过的时间除以总持续时间,得到当前的位移目标。
// 终点 public Vector3 targetPosition; // 开始位置 public Vector3 startPosition; // 持续时间 public float lerpDuration = 4; // 记录运行时间 private float _timeElapsed = 0; void Start() { startPosition = transform.position; } void Update() { // 记录下一个位置 Vector3 valueToLerp; _timeElapsed += Time.deltaTime; if (_timeElapsed < lerpDuration) { valueToLerp = Vector3.Lerp(startPosition, targetPosition, _timeElapsed / lerpDuration); } else { valueToLerp = targetPosition; } transform.position = valueToLerp; }
效果如下:
以上的这些方法足以让我们准确且随心的操纵物体移动。
但有一些场景,我们并不希望如此精确或始终如一的运动轨迹,我们想物体的移动受 Unity 的物理引擎影响或者其他物体影响。
同时如果用以上方法移动,在 Unity 的物理引擎下会出现抖动,穿过刚体等奇怪的现象。
那么接下来,我们就需要用到一些涉及到物理引擎的移动方式。
物理引擎移动
Rigidbody.AddForce()
使用这个方法给物体添加一个方向力。在力的作用下,物体将会移动。那么移动速度和位移就会与物理特效有关,比如物体质量,阻力,甚至还有重力。
一般会有两种使用方式。
在初始时给物体一个力,让其顺着物理规律下运动。使用场景一般时跳跃或者碰撞。
// 赋予200的力 public float force = 200; private Rigidbody2D _rigidbody2D; // 移动方向 private Vector3 dir = Vector3.right; void Start() { _rigidbody2D = GetComponent<Rigidbody2D>(); _rigidbody2D.AddForce(Vector2.right * force); }
为了更好演示刚体的运动,我还给刚体的线性阻力改为1,这样没有持续施加外力的情况下,物体会因为摩擦力的存在而停下。
效果如下:
可以看到物体很快就停下了
第二种,会在每一帧持续给物体施加力,使物体可以持续运动。
// 赋予2的力 public float force = 2; private Rigidbody2D _rigidbody2D; // 移动方向 private Vector3 dir = Vector3.right; void Start() { _rigidbody2D = GetComponent<Rigidbody2D>(); } void FixedUpdate() { _rigidbody2D.AddForce(Vector2.right * force); }
效果如下:
从效果可以看到,在持续给外力的作用下,物体移送越来越快,但在阻挡物前会停下。
Rigidbody.Velocity
直接赋予 Velocity 属性一个向量,可以立即改变物体的速度。一般情况下,我们不需要直接修改速度,除非你非常明确需要立即改变物体的速度。
public float speed = 10; private Rigidbody2D _rigidbody2D; // 移动方向 private Vector3 dir = Vector3.right; void Start() { _rigidbody2D = GetComponent<Rigidbody2D>(); } void FixedUpdate() { _rigidbody2D.velocity = dir * speed; }
效果如下:
看到物体一开始就已经有速度,而通过AddForce方法添加力的物体,速度时慢慢提高的。
Rigidbody.MovePosition()
该方法有比较局限的使用场景,当物体的刚体类型是 Kinematic 时,使用Rigidbody.MovePosition() 方法进行移动。
因为 Kinematic 类型下,不会受到重力和AddForce、AddTorque等力相关的函数的影响!!!
public float speed = 10; private Rigidbody2D _rigidbody2D; // 移动方向 private Vector3 dir = Vector3.right; void Start() { _rigidbody2D = GetComponent<Rigidbody2D>(); } private void FixedUpdate() { var positon = dir * (speed * Time.deltaTime); _rigidbody2D.MovePosition(transform.position + positon); }
效果如下:
刚体类型是 Kinematic 时 ,会对刚体类型为 Dynamic 施加力,而无视 static 类型。