状态驱动动画
待机、蓄力、冲刺和恢复,每个状态都有自己的 tween。进入新状态会杀掉上一个的 tween,所以它们不会争抢同一个属性。
上方的编辑器正在运行这个项目。改一行代码,它就会重新加载。
用到的 Godot 类
TweenPolygon2DLabel
代码
scripts/main.gd
extends Node
enum State { IDLE, WINDUP, DASH, RECOVER }
const HOME: float = 760.0
const ACTOR_Y: float = 380.0
const SIZE: float = 76.0
const NAMES: Array = ['IDLE', 'WINDUP', 'DASH', 'RECOVER']
const COLORS: Array = ['#8da5f3', '#f3d48d', '#8eef97', '#fc7f7f']
const DIM: Color = Color('#4a5162')
var state: State = State.IDLE
var tween: Tween
var actor: Polygon2D
var rows: Array[Label] = []
var pointer: Polygon2D
func _ready() -> void:
_add_background()
_add_label('one enum, four tweens, the state picks which one runs', Vector2(40, 40), Color('#999999'))
for i in NAMES.size():
var row: Label = _add_label(NAMES[i], Vector2(110, 130 + i * 60), DIM)
row.add_theme_font_size_override('font_size', 30)
rows.append(row)
pointer = Polygon2D.new()
pointer.polygon = PackedVector2Array([Vector2(0, -13), Vector2(21, 0), Vector2(0, 13)])
pointer.position = Vector2(70, 152)
add_child(pointer)
var rail: ColorRect = ColorRect.new()
rail.color = Color('#2a2f3d')
rail.position = Vector2(600, ACTOR_Y + SIZE * 0.5)
rail.size = Vector2(600, 4)
add_child(rail)
var home_mark: ColorRect = ColorRect.new()
home_mark.color = Color('#4a5162')
home_mark.position = Vector2(HOME - 2, ACTOR_Y + SIZE * 0.5 - 10)
home_mark.size = Vector2(4, 24)
add_child(home_mark)
actor = _make_square()
actor.position = Vector2(HOME, ACTOR_Y)
add_child(actor)
_set_state(State.IDLE)
print('the cycle runs on its own, each state kills the tween of the previous one')
func _set_state(next: State) -> void:
state = next
actor.color = Color(COLORS[state])
_highlight()
if tween and tween.is_valid():
tween.kill()
tween = create_tween()
var following: State
match state:
State.IDLE:
tween.tween_property(actor, 'scale', Vector2(1.08, 0.92), 0.3).set_trans(Tween.TRANS_SINE)
tween.tween_property(actor, 'scale', Vector2.ONE, 0.3).set_trans(Tween.TRANS_SINE)
following = State.WINDUP
State.WINDUP:
tween.tween_property(actor, 'position:x', HOME - 90.0, 0.3).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
tween.parallel().tween_property(actor, 'scale', Vector2(0.78, 1.22), 0.3)
following = State.DASH
State.DASH:
tween.tween_property(actor, 'position:x', 1150.0, 0.4).set_trans(Tween.TRANS_EXPO).set_ease(Tween.EASE_OUT)
tween.parallel().tween_property(actor, 'scale', Vector2(1.5, 0.7), 0.2)
following = State.RECOVER
State.RECOVER:
tween.tween_property(actor, 'position:x', HOME, 0.55).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_IN_OUT)
tween.parallel().tween_property(actor, 'scale', Vector2.ONE, 0.55)
following = State.IDLE
tween.finished.connect(_set_state.bind(following), CONNECT_ONE_SHOT)
func _highlight() -> void:
var active: Color = Color(COLORS[state])
for i in rows.size():
rows[i].add_theme_color_override('font_color', active if i == state else DIM)
pointer.color = active
pointer.position.y = 152 + state * 60
func _make_square() -> Polygon2D:
var square: Polygon2D = Polygon2D.new()
square.polygon = PackedVector2Array(
[
Vector2(-SIZE, -SIZE) * 0.5,
Vector2(SIZE, -SIZE) * 0.5,
Vector2(SIZE, SIZE) * 0.5,
Vector2(-SIZE, SIZE) * 0.5,
]
)
return square
func _add_background() -> void:
var background: ColorRect = ColorRect.new()
background.color = Color('#12141a')
add_child(background)
background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
func _add_label(text: String, at: Vector2, color: Color) -> Label:
var label: Label = Label.new()
label.text = text
label.position = at
label.add_theme_color_override('font_color', color)
add_child(label)
return label