move_toward
2026/4/14大约 6 分钟
最后同步日期:2026-04-15 | Godot 官方原文 — move_toward
move_toward
定义
move_toward() 用来让一个值以固定的步长向目标值靠近——简单说,就是"一步一步走向目标,走到就停下"。
想象你在走廊上走路:目标是前方 10 米处的大门,你每一步走 0.5 米。走到 10 米处你就停下了,不会多走一步。move_toward() 就是这个"走路"函数:你告诉它"我现在在哪"(from)、"我要去哪"(to)、"每一步走多远"(delta),它帮你算出走完这一步后的新位置。
和 lerp() 的区别:lerp() 是按"比例"靠近目标(每帧靠近剩余距离的某个百分比),所以永远无法精确到达目标(只会无限接近)。而 move_toward() 是按"固定步长"靠近目标,到达目标后立刻停下,不会越过。这个特性让 move_toward() 特别适合做"匀速移动"。
在游戏开发中,move_toward() 常用于:角色匀速移动到某点、血条匀速变化、摄像机匀速跟随、冷却计时器递减等。只要你需要"以恒定速度让一个值靠近另一个值",就应该想到 move_toward()。
函数签名
C#
public static float MoveToward(float from, float to, float delta)GDScript
func move_toward(from: float, to: float, delta: float) -> float参数说明
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
from | float | 是 | 当前值(出发点) |
to | float | 是 | 目标值(目的地) |
delta | float | 是 | 每次移动的步长(移动量)。必须 >= 0。如果为 0 则不移动 |
返回值
float —— 移动后的新值。具体规则:
- 如果
from到to的距离小于delta,直接返回to(不会越过目标) - 如果
from < to,返回from + delta(向右移动) - 如果
from > to,返回from - delta(向左移动) - 如果
from == to,返回to(已经到达目标) - 如果
delta == 0,返回from(不移动) - 如果
delta < 0,返回from(不移动)
代码示例
基础用法:数值匀速移动
C#
using Godot;
public partial class MoveTowardExample : Node
{
public override void _Ready()
{
// 从 0 向目标 10 移动,步长 3
float a = Mathf.MoveToward(0f, 10f, 3f);
GD.Print($"move_toward(0, 10, 3) = {a}");
// 运行结果: move_toward(0, 10, 3) = 3
// 从 8 向目标 10 移动,步长 5(距离只有 2,不会越过)
float b = Mathf.MoveToward(8f, 10f, 5f);
GD.Print($"move_toward(8, 10, 5) = {b}");
// 运行结果: move_toward(8, 10, 5) = 10(精确到达,不会超过)
// 反方向移动:从 10 向目标 0 移动
float c = Mathf.MoveToward(10f, 0f, 3f);
GD.Print($"move_toward(10, 0, 3) = {c}");
// 运行结果: move_toward(10, 0, 3) = 7
// 已经在目标位置
float d = Mathf.MoveToward(5f, 5f, 3f);
GD.Print($"move_toward(5, 5, 3) = {d}");
// 运行结果: move_toward(5, 5, 3) = 5(不动)
}
}GDScript
func _ready():
# 从 0 向目标 10 移动,步长 3
var a = move_toward(0.0, 10.0, 3.0)
print("move_toward(0, 10, 3) = %f" % a)
# 运行结果: move_toward(0, 10, 3) = 3.000000
# 从 8 向目标 10 移动,步长 5(距离只有 2,不会越过)
var b = move_toward(8.0, 10.0, 5.0)
print("move_toward(8, 10, 5) = %f" % b)
# 运行结果: move_toward(8, 10, 5) = 10.000000(精确到达,不会超过)
# 反方向移动:从 10 向目标 0 移动
var c = move_toward(10.0, 0.0, 3.0)
print("move_toward(10, 0, 3) = %f" % c)
# 运行结果: move_toward(10, 0, 3) = 7.000000
# 已经在目标位置
var d = move_toward(5.0, 5.0, 3.0)
print("move_toward(5, 5, 3) = %f" % d)
# 运行结果: move_toward(5, 5, 3) = 5.000000(不动)实际场景:角色匀速移动到目标点
C#
using Godot;
public partial class UniformMover : Node2D
{
// 导出属性:移动速度(像素/秒)
[Export] public float ExMoveSpeed = 200f;
// 导出属性:目标位置
[Export] public Vector2 ExTarget = new Vector2(500f, 300f);
public override void _Process(double delta)
{
// X 和 Y 分别用 move_toward 匀速移动
float newX = Mathf.MoveToward(Position.X, ExTarget.X, ExMoveSpeed * (float)delta);
float newY = Mathf.MoveToward(Position.Y, ExTarget.Y, ExMoveSpeed * (float)delta);
Position = new Vector2(newX, newY);
// 检查是否到达目标
if (Position == ExTarget)
{
GD.Print("已到达目标位置!");
SetProcess(false);
}
else
{
GD.Print($"移动中... 当前位置: ({Position.X:F0}, {Position.Y:F0})");
}
// 运行结果: 移动中... 当前位置: (350, 250)
}
}GDScript
extends Node2D
# 导出属性:移动速度(像素/秒)
@export var ex_move_speed: float = 200.0
# 导出属性:目标位置
@export var ex_target: Vector2 = Vector2(500.0, 300.0)
func _process(delta):
# X 和 Y 分别用 move_toward 匀速移动
var new_x = move_toward(position.x, ex_target.x, ex_move_speed * delta)
var new_y = move_toward(position.y, ex_target.y, ex_move_speed * delta)
position = Vector2(new_x, new_y)
# 检查是否到达目标
if position == ex_target:
print("已到达目标位置!")
set_process(false)
else:
print("移动中... 当前位置: (%.0f, %.0f)" % [position.x, position.y])
# 运行结果: 移动中... 当前位置: (350, 250)进阶用法:血条匀速变化 + 冷却计时器
C#
using Godot;
public partial class HealthBar : Control
{
// 导出属性:最大生命值
[Export] public int ExMaxHealth = 100;
// 导出属性:血条变化速度(每秒变化的点数)
[Export] public float ExBarSpeed = 30f;
// 内部变量:实际生命值(立即变化)
private float _actualHealth = 100f;
// 内部变量:显示的生命值(匀速追赶)
private float _displayHealth = 100f;
// 内部变量:技能冷却剩余时间
private float _cooldownTimer = 0f;
// 内部变量:技能冷却总时间
private float _cooldownDuration = 5f;
private ProgressBar _bar;
private Label _cooldownLabel;
public override void _Ready()
{
_bar = GetNode<ProgressBar>("Bar");
_cooldownLabel = GetNode<Label>("CooldownLabel");
}
public void TakeDamage(float damage)
{
// 实际血量立即减少
_actualHealth = Mathf.Max(_actualHealth - damage, 0f);
}
public void UseSkill()
{
if (_cooldownTimer > 0) return;
// ... 施放技能 ...
_cooldownTimer = _cooldownDuration;
}
public override void _Process(double delta)
{
// 显示血量匀速追赶实际血量(产生"滑动"效果)
_displayHealth = Mathf.MoveToward(_displayHealth, _actualHealth, ExBarSpeed * (float)delta);
_bar.Value = _displayHealth;
// 冷却计时器匀速递减到 0
_cooldownTimer = Mathf.MoveToward(_cooldownTimer, 0f, 1f * (float)delta);
if (_cooldownTimer > 0)
{
_cooldownLabel.Text = $"冷却中: {_cooldownTimer:F1}s";
}
else
{
_cooldownLabel.Text = "技能就绪";
}
GD.Print($"实际血量: {_actualHealth:F0}, 显示血量: {_displayHealth:F1}, 冷却: {_cooldownTimer:F1}s");
// 运行结果: 实际血量: 70, 显示血量: 85.5, 冷却: 3.2s
}
}GDScript
extends Control
# 导出属性:最大生命值
@export var ex_max_health: int = 100
# 导出属性:血条变化速度(每秒变化的点数)
@export var ex_bar_speed: float = 30.0
# 内部变量:实际生命值(立即变化)
var _actual_health: float = 100.0
# 内部变量:显示的生命值(匀速追赶)
var _display_health: float = 100.0
# 内部变量:技能冷却剩余时间
var _cooldown_timer: float = 0.0
# 内部变量:技能冷却总时间
var _cooldown_duration: float = 5.0
@onready var _bar = $Bar
@onready var _cooldown_label = $CooldownLabel
func take_damage(damage: float) -> void:
# 实际血量立即减少
_actual_health = maxf(_actual_health - damage, 0.0)
func use_skill() -> void:
if _cooldown_timer > 0:
return
# ... 施放技能 ...
_cooldown_timer = _cooldown_duration
func _process(delta):
# 显示血量匀速追赶实际血量(产生"滑动"效果)
_display_health = move_toward(_display_health, _actual_health, ex_bar_speed * delta)
_bar.value = _display_health
# 冷却计时器匀速递减到 0
_cooldown_timer = move_toward(_cooldown_timer, 0.0, 1.0 * delta)
if _cooldown_timer > 0:
_cooldown_label.text = "冷却中: %.1fs" % _cooldown_timer
else:
_cooldown_label.text = "技能就绪"
print("实际血量: %.0f, 显示血量: %.1f, 冷却: %.1fs" % [_actual_health, _display_health, _cooldown_timer])
# 运行结果: 实际血量: 70, 显示血量: 85.5, 冷却: 3.2s注意事项
- 不会越过目标:这是
move_toward()最重要的特性。无论delta多大,结果最多到达to,永远不会超过。这和from + delta不同——后者可能越过目标。 delta必须非负:如果传入负数,move_toward()不会移动(相当于delta = 0)。- 和
lerp()的关键区别:lerp(from, to, 0.1)每帧移动剩余距离的 10%,所以永远无法精确到达目标;move_toward(from, to, speed * delta)每帧移动固定距离,能精确到达。选择哪个取决于你的需求。 - Vector2 和 Vector3 的移动:
Vector2和Vector3没有内置的MoveToward方法。要对 2D/3D 位置做匀速移动,需要分别对 X、Y(和 Z)调用Mathf.MoveToward(),或者使用MoveAndSlide()等物理方法。 - 适合做计时器递减:
move_toward(cooldown, 0, delta)是一个非常简洁的"冷却倒计时"写法,到达 0 后自动停住,不需要额外的if判断。
