概述
本篇论述,如何用加速度在Godot中控制粒子运动。
匀速和匀变速直线运动的统一
以下是匀变速运动的速度和位移公式:
v t = v 0 + a t x t = v 0 t + 1 2 a t 2 v_t=v_0 + at \\ x_t=v_0t + \frac{1}{2}at^2 vt=v0+atxt=v0t+21at2
当a = 0 时:
v t = v 0 x t = v 0 t v_t=v_0 \\ x_t=v_0t vt=v0xt=v0t
所以匀速直线运动可以看成是a = 0 的特殊匀变速直线运动,两者可以共用一套公式。
Godot中的匀变速直线运动实现
另外,我们在Godot的_process()
或者_physics_process()
中得到的delta
其实就是
Δ
t
\Delta t
Δt,而不是一个连续累计的时间
t
t
t。
我们需要计算的当前帧基于前一帧的速度和位移,也就是:
v f r a m e = v f r a m e − 1 + a Δ t x f r a m e = v f r a m e − 1 Δ t + 1 2 a Δ t 2 v_{frame} = v_{frame-1} + a \Delta t \\ x_{frame} = v_{frame-1}\Delta t + \frac{1}{2} a {\Delta t}^2 vframe=vframe−1+aΔtxframe=vframe−1Δt+21aΔt2
其中:
- v f r a m e v_{frame} vframe表示当前帧的速度, v f r a m e − 1 v_{frame-1} vframe−1表示上一帧的速度
- x f r a m e x_{frame} xframe表示当前帧的位置, x f r a m e − 1 x_{frame-1} xframe−1表示上一帧的位置
其实也就是:
Δ v = v f r a m e − v f r a m e − 1 = a Δ t Δ x = x f r a m e − x f r a m e − 1 = v f r a m e − 1 Δ t + 1 2 a Δ t 2 \Delta v = v_{frame} - v_{frame-1} = a \Delta t \\ \Delta x = x_{frame} - x_{frame-1} = v_{frame-1}\Delta t + \frac{1}{2} a {\Delta t}^2 Δv=vframe−vframe−1=aΔtΔx=xframe−xframe−1=vframe−1Δt+21aΔt2
所以当前帧:
v f r a m e = v f r a m e − 1 + Δ v x f r a m e = x f r a m e − 1 + Δ x v_{frame} = v_{frame-1} + \Delta v \\ x_{frame} = x_{frame-1} + \Delta x vframe=vframe−1+Δvxframe=xframe−1+Δx
速度和位移都变成了基于前一帧的累计值,而与初始的速度 v 0 v_0 v0无关,同样加速度a = 0时, Δ v \Delta v Δv=0,当前帧速度保持不变, Δ x = v f r a m e − 1 Δ t \Delta x = v_{frame-1}\Delta t Δx=vframe−1Δt,当前帧的位置 = 上一帧位置 + Δ x \Delta x Δx。
实现粒子类
基于上面的认识,我们可以编写一个粒子类。它的代码如下,其中update()
用于粒子基于_process()
或者_physics_process()
中得到的delta
更新粒子速度和位置,是完全按照上面的思路实现的。
# 粒子
class Particle:
var position:Vector2
var velocity:Vector2
var acceleration:Vector2
func _init(position:Vector2,velocity:Vector2,acceleration:Vector2) -> void:
self.position = position
self.velocity = velocity
self.acceleration = acceleration
# 更新速度和位置
func update(d_t: float)-> void:
var d_v = acceleration * d_t
velocity += d_v
position += velocity * d_t + (d_v * d_t)/2
# 绘制粒子
func draw_particle(
canvas_item:CanvasItem,
color:=Color.AQUAMARINE,
r:=3.0,
fill:=true,
border_width:=1
):
canvas_item.draw_circle(position,r,color,fill,border_width)
# 绘制粒子的速度矢量
func draw_velocity(
canvas_item:CanvasItem,
color:=Color.GREEN_YELLOW,
border_width:=1
):
canvas_item.draw_line(position,position+velocity,color,border_width)
# 绘制粒子的加速度矢量
func draw_acceleration(
canvas_item:CanvasItem,
color:=Color.ORANGE_RED,
border_width:=1
):
canvas_item.draw_line(position,position+acceleration,color,border_width)
测试代码
extends Node2D
var pos:Vector2 = Vector2(100,100) # 位置
var v := Vector2() # 速度
var a := Vector2.RIGHT * 20 # 加速度
# 创建粒子实例
var p = Particle.new(pos,v,a)
func _process(delta: float) -> void:
p.update(delta) # 更新粒子的速度和位置
queue_redraw() # 请求重绘
# 绘制
func _draw() -> void:
p.draw_particle(self) # 绘制粒子
p.draw_velocity(self) # 绘制速度向量
p.draw_acceleration(self) # 绘制加速度向量
可以看到:
- 我们在创建粒子实例时,只需要设定起始位置、初始速度以及加速度就可以了。
- 程序便会自动随时间更新粒子的速度和位置,并且绘制出粒子、粒子当前的速度以及加速度
通过设定不同的起始位置、初始速度以及加速度,我们就可以模拟出匀速直线运动、匀加速直线运动和匀减速直线运动。
# 匀减速直线运动
var pos:Vector2 = Vector2(100,100) # 起始位置
var v := Vector2.RIGHT * 100 # 初始速度
var a := Vector2.LEFT * 20 # 加速度
# 初速度为0的匀加速直线运动
var pos:Vector2 = Vector2(100,100) # 起始位置
var v := Vector2() # 初始速度
var a := Vector2.RIGHT * 20 # 加速度
# 初速度不为0的匀速直线运动
var pos:Vector2 = Vector2(100,100) # 起始位置
var v := Vector2.RIGHT * 100 # 初始速度
var a := Vector2() # 加速度
用曲线控制速度和加速度变化
extends Node2D
# 匀减速直线运动
var pos:Vector2 = Vector2(300,300) # 起始位置
var v := Vector2.RIGHT * 100 # 初始速度
var a := Vector2.LEFT * 0 # 加速度
@export var velocity_curve:Curve
# 创建粒子实例
var p = Particle.new(pos,v,a)
var offset:= 0.0
var step:=0.005
func _process(delta: float) -> void:
p.velocity = v * velocity_curve.sample(offset)
offset += step
if not(offset <=1.0 and offset >= 0.0):
step *= -1
p.update(delta) # 更新粒子的速度和位置
queue_redraw()
# 绘制
func _draw() -> void:
p.draw_particle(self,Color.AQUAMARINE,20.0) # 绘制粒子
p.draw_velocity(self) # 绘制速度向量
p.draw_acceleration(self) # 绘制加速度向量
效果: