bezier_interpolate
2026/4/14大约 6 分钟
最后同步日期:2026-04-15 | Godot 官方原文 — bezier_interpolate
bezier_interpolate
定义
bezier_interpolate() 用来在一条二次贝塞尔曲线上,根据进度 t 算出对应的值。
什么是贝塞尔曲线?想象你在画一条从 A 点到 B 点的弧线,但这不是一条直线——你用一根"磁铁"(控制点)把直线"吸弯"了。控制点离直线越远,弯曲越厉害。这就是二次贝塞尔曲线:一条被一个控制点"拉弯"的曲线。
生活中最直观的例子就是 Photoshop 或 Figma 里的钢笔工具:你点击起点和终点,然后拖拽一条"手柄"来控制弯曲方向和程度。那条手柄就是控制点。
在游戏开发中,贝塞尔曲线常用于:抛物线弹道(炮弹飞行)、敌人的曲线移动路径、UI 元素的弧线动画、相机的平滑巡游路径等。任何需要"走曲线而不是直线"的场景,都可以考虑贝塞尔曲线。
函数签名
C#
public static float BezierInterpolate(float start, float control, float end, float t)GDScript
func bezier_interpolate(start: float, control: float, end: float, t: float) -> float参数说明
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
start | float | 是 | 起始值。当 t = 0 时,返回此值 |
control | float | 是 | 控制点的值。它决定曲线的弯曲方向和程度——值离 start 和 end 的连线越远,曲线弯得越厉害 |
end | float | 是 | 终点值。当 t = 1 时,返回此值 |
t | float | 是 | 插值进度,从 0.0 到 1.0。0 = 起点,1 = 终点,0.5 = 曲线中点 |
返回值
float —— 在二次贝塞尔曲线上,对应进度 t 处的值。
计算公式为:((1-t)^2) * start + 2*(1-t)*t * control + (t^2) * end
通俗理解:
t = 0时返回startt = 1时返回endt = 0.5时返回的值受control影响,不一定在start和end的正中间control的值可以超出start和end的范围,产生"先过头再回来"的弧线效果
代码示例
基础用法:观察控制点的影响
C#
using Godot;
public partial class BezierExample : Node
{
public override void _Ready()
{
// 控制点在正中间(50),t=0.5 的结果也在正中间
float mid = Mathf.BezierInterpolate(0f, 50f, 100f, 0.5f);
GD.Print($"控制点=50, t=0.5: {mid}");
// 运行结果: 控制点=50, t=0.5: 50
// 控制点偏上(150),曲线被往上拉
float highCtrl = Mathf.BezierInterpolate(0f, 150f, 100f, 0.5f);
GD.Print($"控制点=150, t=0.5: {highCtrl}");
// 运行结果: 控制点=150, t=0.5: 100
// 控制点超出终点(200),曲线先"冲过头"再回来
float overshoot = Mathf.BezierInterpolate(0f, 200f, 100f, 0.5f);
GD.Print($"控制点=200, t=0.5: {overshoot}");
// 运行结果: 控制点=200, t=0.5: 125
// 不同 t 值
float t0 = Mathf.BezierInterpolate(0f, 50f, 100f, 0.0f);
float t1 = Mathf.BezierInterpolate(0f, 50f, 100f, 1.0f);
GD.Print($"t=0: {t0}, t=1: {t1}");
// 运行结果: t=0: 0, t=1: 100
}
}GDScript
func _ready():
# 起点 0,终点 100,控制点 50(正中间)
var mid = bezier_interpolate(0.0, 50.0, 100.0, 0.5)
print("控制点=50, t=0.5: %f" % mid)
# 运行结果: 控制50, t=0.5: 50.000000
# 控制点偏上(150),曲线被往上拉
var high_ctrl = bezier_interpolate(0.0, 150.0, 100.0, 0.5)
print("控制点=150, t=0.5: %f" % high_ctrl)
# 运行结果: 控制150, t=0.5: 100.000000
# 控制点超出终点(200),曲线先"冲过头"再回来
var overshoot = bezier_interpolate(0.0, 200.0, 100.0, 0.5)
print("控制点=200, t=0.5: %f" % overshoot)
# 运行结果: 控制200, t=0.5: 125.000000
# 不同 t 值
var t0 = bezier_interpolate(0.0, 50.0, 100.0, 0.0)
var t1 = bezier_interpolate(0.0, 50.0, 100.0, 1.0)
print("t=0: %f, t=1: %f" % [t0, t1])
# 运行结果: t=0: 0.000000, t=1: 100.000000实际场景:炮弹抛物线弹道
C#
using Godot;
public partial class ProjectileArc : Node2D
{
// 导出属性:发射起点
[Export] public Vector2 ExStartPos = new Vector2(100f, 400f);
// 导出属性:目标终点
[Export] public Vector2 ExEndPos = new Vector2(500f, 400f);
// 导出属性:抛物线高度(控制点的高度偏移)
[Export] public float ExArcHeight = 200f;
// 导出属性:飞行时间(秒)
[Export] public float ExFlightTime = 1.0f;
// 内部变量:飞行计时器
private float _flightTimer = 0f;
// 内部变量:炮弹节点
private Sprite2D _projectile;
public override void _Ready()
{
_projectile = GetNode<Sprite2D>("ProjectileSprite");
}
public void Fire()
{
_flightTimer = 0f;
SetProcess(true);
}
public override void _Process(double delta)
{
_flightTimer += (float)delta;
float t = Mathf.Clamp(_flightTimer / ExFlightTime, 0f, 1f);
// 控制点在起点和终点的中间,但 Y 值往上偏移(往上就是减小)
float controlY = Mathf.Min(ExStartPos.Y, ExEndPos.Y) - ExArcHeight;
float controlX = (ExStartPos.X + ExEndPos.X) / 2f;
// X 和 Y 分别用贝塞尔插值
float x = Mathf.BezierInterpolate(
ExStartPos.X, controlX, ExEndPos.X, t);
float y = Mathf.BezierInterpolate(
ExStartPos.Y, controlY, ExEndPos.Y, t);
_projectile.Position = new Vector2(x, y);
GD.Print($"炮弹位置: ({x:F1}, {y:F1})");
if (t >= 1.0f)
{
SetProcess(false);
GD.Print("命中目标!");
}
// 运行结果: 炮弹位置: (300.0, 200.0)(最高点)
}
}GDScript
extends Node2D
# 导出属性:发射起点
@export var ex_start_pos: Vector2 = Vector2(100.0, 400.0)
# 导出属性:目标终点
@export var ex_end_pos: Vector2 = Vector2(500.0, 400.0)
# 导出属性:抛物线高度(控制点的高度偏移)
@export var ex_arc_height: float = 200.0
# 导出属性:飞行时间(秒)
@export var ex_flight_time: float = 1.0
# 内部变量:飞行计时器
var _flight_timer: float = 0.0
# 内部变量:炮弹节点
@onready var _projectile = $ProjectileSprite
func fire():
_flight_timer = 0.0
set_process(true)
func _process(delta):
_flight_timer += delta
var t = clampf(_flight_timer / ex_flight_time, 0.0, 1.0)
# 控制点在起点和终点的中间,但 Y 值往上偏移(往上就是减小)
var control_y = minf(ex_start_pos.y, ex_end_pos.y) - ex_arc_height
var control_x = (ex_start_pos.x + ex_end_pos.x) / 2.0
# X 和 Y 分别用贝塞尔插值
var x = bezier_interpolate(ex_start_pos.x, control_x, ex_end_pos.x, t)
var y = bezier_interpolate(ex_start_pos.y, control_y, ex_end_pos.y, t)
_projectile.position = Vector2(x, y)
print("炮弹位置: (%.1f, %.1f)" % [x, y])
if t >= 1.0:
set_process(false)
print("命中目标!")
# 运行结果: 炮弹位置: (300.0, 200.0)(最高点)进阶用法:2D 贝塞尔曲线路径(结合 Vector2)
C#
using Godot;
public partial class BezierPath2D : PathFollow2D
{
// 导出属性:曲线起点
[Export] public Vector2 ExStart = new Vector2(100f, 300f);
// 导出属性:曲线终点
[Export] public Vector2 ExEnd = new Vector2(500f, 300f);
// 导出属性:控制点(决定弯曲方向)
[Export] public Vector2 ExControl = new Vector2(300f, 100f);
// 导出属性:移动速度
[Export] public float ExSpeed = 0.3f;
// 内部变量:当前进度
private float _t = 0f;
public override void _Process(double delta)
{
_t += ExSpeed * (float)delta;
if (_t > 1f) _t = 0f;
// 分别对 X 和 Y 做贝塞尔插值
float x = Mathf.BezierInterpolate(ExStart.X, ExControl.X, ExEnd.X, _t);
float y = Mathf.BezierInterpolate(ExStart.Y, ExControl.Y, ExEnd.Y, _t);
Position = new Vector2(x, y);
GD.Print($"路径进度: {_t:F2}, 位置: ({x:F1}, {y:F1})");
// 运行结果: 路径进度: 0.50, 位置: (300.0, 175.0)
}
}GDScript
extends PathFollow2D
# 导出属性:曲线起点
@export var ex_start: Vector2 = Vector2(100.0, 300.0)
# 导出属性:曲线终点
@export var ex_end: Vector2 = Vector2(500.0, 300.0)
# 导出属性:控制点(决定弯曲方向)
@export var ex_control: Vector2 = Vector2(300.0, 100.0)
# 导出属性:移动速度
@export var ex_speed: float = 0.3
# 内部变量:当前进度
var _t: float = 0.0
func _process(delta):
_t += ex_speed * delta
if _t > 1.0:
_t = 0.0
# 分别对 X 和 Y 做贝塞尔插值
var x = bezier_interpolate(ex_start.x, ex_control.x, ex_end.x, _t)
var y = bezier_interpolate(ex_start.y, ex_control.y, ex_end.y, _t)
position = Vector2(x, y)
print("路径进度: %.2f, 位置: (%.1f, %.1f)" % [_t, x, y])
# 运行结果: 路径进度: 0.50, 位置: (300.0, 175.0)注意事项
bezier_interpolate只处理单个维度:它是一个一维(1D)函数,只处理一个 float 值。要做 2D 曲线运动,需要对 X 和 Y 分别调用一次。要做 3D 运动,需要对 X、Y、Z 分别调用。t可以超出 0~1 范围:但超出后曲线会延伸到起点和终点之外,可能产生意想不到的结果。大多数情况下应该把t限制在 0~1 之间。- 二次 vs 三次贝塞尔:这个函数是"二次贝塞尔曲线"(一个控制点)。Godot 还有
cubic_interpolate()(三次贝塞尔,两个控制点),可以做更复杂的曲线形状。 - 起点和终点不会被改变:无论控制点怎么设置,当
t = 0时一定返回start,t = 1时一定返回end。控制点只影响中间的弯曲路径。 - Vector2 有内置的 BezierInterpolate 方法:在 C# 中
Vector2也提供了BezierInterpolate()方法,可以一步完成 2D 贝塞尔插值,比手动对 X 和 Y 分别调用更方便。
