214 lines
6.1 KiB
GDScript
214 lines
6.1 KiB
GDScript
@tool
|
||
class_name ProAnimatedSprite2D extends AnimatedSprite2D
|
||
|
||
@export var autostart := true
|
||
@export var action_configs: Array[Dictionary] = []
|
||
@export var move_configs: Array[Dictionary] = []
|
||
# 0.0 means no light
|
||
# negative value means subtract, positive value means add
|
||
@export_range(-1.0, 1.0) var light_energy := 0.0:
|
||
set(val):
|
||
light2d.energy = abs(val)
|
||
if val > 0:
|
||
light2d.blend_mode = Light2D.BLEND_MODE_ADD
|
||
else:
|
||
light2d.blend_mode = Light2D.BLEND_MODE_SUB
|
||
light_energy = val * modulate.a
|
||
@export var debug_mov_animation := ""
|
||
@export_tool_button("debug 刷新 mov 目标") var debug_mov_projection := _debug_mov_projection
|
||
|
||
const ACTION_CONFIG = {
|
||
"animation_intro": "", "intro_loop": 1, "animation_wait_time": 0.0, "animation_next": ""
|
||
}
|
||
# 参数 {duration} 目前无需求,暂且保留
|
||
const MOVE_CONFIG = {
|
||
"animation": "",
|
||
"velocity": Vector2.ZERO,
|
||
"duration": 10000000.0,
|
||
"movement_x": 0.0,
|
||
"animation_next": ""
|
||
}
|
||
|
||
|
||
static func new_move_config() -> Dictionary:
|
||
return MOVE_CONFIG.duplicate()
|
||
|
||
|
||
static func new_action_config() -> Dictionary:
|
||
return ACTION_CONFIG.duplicate()
|
||
|
||
|
||
@onready var debug_mov_onion_sprite2d = $DebugMovOnionSkinSprite2D as Sprite2D
|
||
|
||
# 从 intro 到 next 的配置信息
|
||
var auto_checkout_dict = {
|
||
# intro -> {state_config instance}
|
||
}
|
||
# 播放 animation 的同时,进行移动
|
||
var animation_mov_dict = {
|
||
# animation -> {velocity, ...}
|
||
}
|
||
|
||
var light2d := PointLight2D.new()
|
||
|
||
|
||
func _ready() -> void:
|
||
if light_energy != 0.0:
|
||
add_child(light2d)
|
||
frame_changed.connect(_on_frame_changed)
|
||
if Engine.is_editor_hint():
|
||
_debug_mov_projection()
|
||
return
|
||
_load_config()
|
||
debug_mov_onion_sprite2d.queue_free()
|
||
if autostart and animation:
|
||
# 制造一点错差
|
||
frame = randi() % sprite_frames.get_frame_count(animation)
|
||
play()
|
||
animation_changed.connect(_on_animation_start)
|
||
animation_looped.connect(_on_animation_start)
|
||
animation_finished.connect(_on_animation_finished)
|
||
animation_looped.connect(_on_animation_finished)
|
||
|
||
|
||
func _debug_mov_projection():
|
||
_load_config()
|
||
if debug_mov_animation and animation_mov_dict.has(debug_mov_animation):
|
||
var mov_config = animation_mov_dict[debug_mov_animation]
|
||
# 展示 accumulated animation 的目标位置
|
||
debug_mov_onion_sprite2d.position.x = mov_config.movement_x
|
||
debug_mov_onion_sprite2d.texture = sprite_frames.get_frame_texture(debug_mov_animation, 0)
|
||
elif debug_mov_animation:
|
||
printerr("Debug move config not found:", debug_mov_animation)
|
||
|
||
|
||
func _load_config():
|
||
# load auto_checkout_dict
|
||
for i in range(action_configs.size()):
|
||
action_configs[i].merge(ACTION_CONFIG)
|
||
var state_config = action_configs[i]
|
||
# 不必关闭循环
|
||
# if sprite_frames and sprite_frames.has_animation(state_config.animation_intro):
|
||
# intro 的 animation 关闭循环
|
||
# sprite_frames.set_animation_loop(state_config.animation_intro, false)
|
||
auto_checkout_dict[state_config.animation_intro] = action_configs[i]
|
||
# load animation_frame_offsets
|
||
for i in range(move_configs.size()):
|
||
move_configs[i].merge(MOVE_CONFIG)
|
||
var move_config = move_configs[i]
|
||
if sprite_frames and sprite_frames.has_animation(move_config.animation):
|
||
animation_mov_dict[move_config.animation] = move_config
|
||
|
||
|
||
func _on_frame_changed():
|
||
if light_energy == 0.0:
|
||
return
|
||
var texture = sprite_frames.get_frame_texture(animation, frame)
|
||
if not texture:
|
||
return
|
||
light2d.texture = texture
|
||
light_energy = light_energy * modulate.a
|
||
|
||
|
||
func _reset_loop(intro: String):
|
||
for c in action_configs:
|
||
if c.animation_intro == intro:
|
||
auto_checkout_dict[intro].intro_loop = max(1, c.intro_loop)
|
||
|
||
|
||
func _on_animation_finished() -> void:
|
||
var intro = animation
|
||
if auto_checkout_dict.has(intro):
|
||
auto_checkout_dict[intro].intro_loop -= 1
|
||
if auto_checkout_dict[intro].intro_loop <= 0:
|
||
# reset loop
|
||
_reset_loop(intro)
|
||
var wait_time = auto_checkout_dict[intro].animation_wait_time
|
||
var next = auto_checkout_dict[intro].animation_next
|
||
if wait_time > 0:
|
||
pause()
|
||
get_tree().create_timer(wait_time).timeout.connect(play.bind(next))
|
||
else:
|
||
play(next)
|
||
|
||
|
||
var velocity := Vector2.ZERO
|
||
var mov_x := 0.0
|
||
var mov_x_next_animation := ""
|
||
var accumulated_mov_x := 0.0
|
||
var prev_animation := ""
|
||
|
||
|
||
func _on_animation_start():
|
||
if prev_animation != animation:
|
||
accumulated_mov_x = 0.0
|
||
prev_animation = animation
|
||
velocity = Vector2.ZERO
|
||
mov_x = 0.0
|
||
mov_x_next_animation = ""
|
||
if not animation or not animation_mov_dict.has(animation):
|
||
return
|
||
# velocity = animation_mov_dict[animation] * speed_scale
|
||
velocity = animation_mov_dict[animation].velocity
|
||
mov_x_next_animation = animation_mov_dict[animation].animation_next
|
||
if mov_x_next_animation:
|
||
# 只有在 next animation 存在时,mov_x 才有意义
|
||
mov_x = animation_mov_dict[animation].movement_x
|
||
|
||
|
||
func _physics_process(delta: float) -> void:
|
||
if Engine.is_editor_hint() or not velocity or not is_playing():
|
||
return
|
||
var diff_x = velocity.x * delta
|
||
if flip_h:
|
||
position.x -= diff_x
|
||
else:
|
||
position.x += diff_x
|
||
# 检查是否切换 animation
|
||
if mov_x != 0.0 and mov_x_next_animation:
|
||
accumulated_mov_x += diff_x
|
||
if absf(accumulated_mov_x) >= absf(mov_x):
|
||
if GlobalConfig.DEBUG:
|
||
print(
|
||
"切换 animation:", mov_x_next_animation, " accumulated_mov_x=", accumulated_mov_x
|
||
)
|
||
play(mov_x_next_animation)
|
||
if not velocity.y:
|
||
return
|
||
if flip_v:
|
||
position.y -= velocity.y * delta
|
||
else:
|
||
position.y += velocity.y * delta
|
||
|
||
|
||
# temporary set velocity
|
||
func set_animation_velocity(anim: String, v: Vector2) -> void:
|
||
if not animation_mov_dict.has(anim):
|
||
animation_mov_dict[anim] = new_move_config()
|
||
animation_mov_dict[anim].velocity = v
|
||
if GlobalConfig.DEBUG:
|
||
print("set_animation_velocity:", anim, v)
|
||
|
||
|
||
func play_with_velocity(anim: String, velocity: Vector2) -> void:
|
||
set_animation_velocity(anim, velocity)
|
||
play(anim)
|
||
|
||
|
||
# -1 means loop forever, 0 is invalid (which means no loop and return)
|
||
func play_with_loop(anim: String, loop := -1, wait := 0.0, next := "") -> void:
|
||
if loop < 0:
|
||
auto_checkout_dict.erase(anim)
|
||
sprite_frames.set_animation_loop(anim, true)
|
||
elif loop == 0:
|
||
return
|
||
else:
|
||
auto_checkout_dict[anim] = {
|
||
"animation_intro": anim,
|
||
"intro_loop": loop,
|
||
"animation_wait_time": wait,
|
||
"animation_next": next
|
||
}
|
||
# 设置完配置后播放
|
||
play(anim)
|