xiandie/addons/property-inspector/pro_animation_sprite2d/pro_animated_sprite.gd

214 lines
6.1 KiB
GDScript3
Raw Normal View History

@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": ""
}
2025-01-31 10:44:30 +00:00
# 参数 {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:
push_warning("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)