Files
whale-town/scripts/UIAnimationManager.gd
2025-12-05 19:00:14 +08:00

348 lines
8.8 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
extends Node
## UI动画管理器
## 统一管理UI动画效果提升用户体验
# 动画配置
const FADE_DURATION = 0.25
const SLIDE_DURATION = 0.3
const SCALE_DURATION = 0.2
const BOUNCE_DURATION = 0.4
# 缓动类型
enum EaseType {
EASE_IN,
EASE_OUT,
EASE_IN_OUT,
BOUNCE,
ELASTIC
}
## 淡入动画
func fade_in(node: Control, duration: float = FADE_DURATION, ease_type: EaseType = EaseType.EASE_OUT) -> Tween:
"""
淡入动画
@param node: 要动画的节点
@param duration: 动画时长
@param ease_type: 缓动类型
@return: Tween对象
"""
if not node:
return null
node.modulate.a = 0.0
node.show()
var tween = node.create_tween()
tween.set_ease(_get_tween_ease(ease_type))
tween.tween_property(node, "modulate:a", 1.0, duration)
return tween
## 淡出动画
func fade_out(node: Control, duration: float = FADE_DURATION, ease_type: EaseType = EaseType.EASE_IN) -> Tween:
"""
淡出动画
@param node: 要动画的节点
@param duration: 动画时长
@param ease_type: 缓动类型
@return: Tween对象
"""
if not node:
return null
var tween = node.create_tween()
tween.set_ease(_get_tween_ease(ease_type))
tween.tween_property(node, "modulate:a", 0.0, duration)
tween.tween_callback(func(): node.hide())
return tween
## 滑入动画(从左侧)
func slide_in_left(node: Control, duration: float = SLIDE_DURATION, ease_type: EaseType = EaseType.EASE_OUT) -> Tween:
"""
从左侧滑入动画
@param node: 要动画的节点
@param duration: 动画时长
@param ease_type: 缓动类型
@return: Tween对象
"""
if not node:
return null
var original_pos = node.position
node.position.x = -node.size.x
node.show()
var tween = node.create_tween()
tween.set_ease(_get_tween_ease(ease_type))
tween.tween_property(node, "position", original_pos, duration)
return tween
## 滑入动画(从右侧)
func slide_in_right(node: Control, duration: float = SLIDE_DURATION, ease_type: EaseType = EaseType.EASE_OUT) -> Tween:
"""
从右侧滑入动画
@param node: 要动画的节点
@param duration: 动画时长
@param ease_type: 缓动类型
@return: Tween对象
"""
if not node:
return null
var original_pos = node.position
var viewport_width = node.get_viewport().get_visible_rect().size.x
node.position.x = viewport_width
node.show()
var tween = node.create_tween()
tween.set_ease(_get_tween_ease(ease_type))
tween.tween_property(node, "position", original_pos, duration)
return tween
## 滑入动画(从上方)
func slide_in_top(node: Control, duration: float = SLIDE_DURATION, ease_type: EaseType = EaseType.EASE_OUT) -> Tween:
"""
从上方滑入动画
@param node: 要动画的节点
@param duration: 动画时长
@param ease_type: 缓动类型
@return: Tween对象
"""
if not node:
return null
var original_pos = node.position
node.position.y = -node.size.y
node.show()
var tween = node.create_tween()
tween.set_ease(_get_tween_ease(ease_type))
tween.tween_property(node, "position", original_pos, duration)
return tween
## 滑入动画(从下方)
func slide_in_bottom(node: Control, duration: float = SLIDE_DURATION, ease_type: EaseType = EaseType.EASE_OUT) -> Tween:
"""
从下方滑入动画
@param node: 要动画的节点
@param duration: 动画时长
@param ease_type: 缓动类型
@return: Tween对象
"""
if not node:
return null
var original_pos = node.position
var viewport_height = node.get_viewport().get_visible_rect().size.y
node.position.y = viewport_height
node.show()
var tween = node.create_tween()
tween.set_ease(_get_tween_ease(ease_type))
tween.tween_property(node, "position", original_pos, duration)
return tween
## 缩放动画(弹出效果)
func scale_popup(node: Control, duration: float = SCALE_DURATION, ease_type: EaseType = EaseType.BOUNCE) -> Tween:
"""
缩放弹出动画
@param node: 要动画的节点
@param duration: 动画时长
@param ease_type: 缓动类型
@return: Tween对象
"""
if not node:
return null
node.scale = Vector2.ZERO
node.show()
var tween = node.create_tween()
tween.set_ease(_get_tween_ease(ease_type))
tween.tween_property(node, "scale", Vector2.ONE, duration)
return tween
## 按钮点击反馈动画
func button_press_feedback(button: Button, scale_factor: float = 0.95, duration: float = 0.1) -> Tween:
"""
按钮点击反馈动画
@param button: 按钮节点
@param scale_factor: 缩放因子
@param duration: 动画时长
@return: Tween对象
"""
if not button:
return null
var tween = button.create_tween()
tween.tween_property(button, "scale", Vector2.ONE * scale_factor, duration)
tween.tween_property(button, "scale", Vector2.ONE, duration)
return tween
## 摇摆动画(错误提示)
func shake_error(node: Control, intensity: float = 10.0, duration: float = 0.5) -> Tween:
"""
摇摆动画(用于错误提示)
@param node: 要动画的节点
@param intensity: 摇摆强度
@param duration: 动画时长
@return: Tween对象
"""
if not node:
return null
var original_pos = node.position
var tween = node.create_tween()
# 创建摇摆效果
for i in range(4):
var offset = Vector2(randf_range(-intensity, intensity), 0)
tween.tween_property(node, "position", original_pos + offset, duration / 8)
tween.tween_property(node, "position", original_pos, duration / 8)
return tween
## 脉冲动画(强调效果)
func pulse_highlight(node: Control, scale_max: float = 1.1, duration: float = 0.6) -> Tween:
"""
脉冲动画(强调效果)
@param node: 要动画的节点
@param scale_max: 最大缩放
@param duration: 动画时长
@return: Tween对象
"""
if not node:
return null
var tween = node.create_tween()
tween.set_loops() # 无限循环
tween.tween_property(node, "scale", Vector2.ONE * scale_max, duration / 2)
tween.tween_property(node, "scale", Vector2.ONE, duration / 2)
return tween
## 停止脉冲动画
func stop_pulse(node: Control) -> void:
"""
停止脉冲动画并恢复原始缩放
@param node: 节点
"""
if not node:
return
# 停止所有tween
node.get_tree().call_group("tween", "kill")
# 恢复原始缩放
var tween = node.create_tween()
tween.tween_property(node, "scale", Vector2.ONE, 0.2)
## 组合动画:淡入+滑入
func fade_slide_in(node: Control, direction: String = "bottom", duration: float = SLIDE_DURATION) -> Tween:
"""
组合动画:淡入+滑入
@param node: 要动画的节点
@param direction: 滑入方向("left", "right", "top", "bottom"
@param duration: 动画时长
@return: Tween对象
"""
if not node:
return null
# 设置初始状态
node.modulate.a = 0.0
var original_pos = node.position
match direction:
"left":
node.position.x = -node.size.x
"right":
node.position.x = node.get_viewport().get_visible_rect().size.x
"top":
node.position.y = -node.size.y
"bottom":
node.position.y = node.get_viewport().get_visible_rect().size.y
node.show()
# 创建并行动画
var tween = node.create_tween()
tween.set_parallel(true)
tween.tween_property(node, "modulate:a", 1.0, duration)
tween.tween_property(node, "position", original_pos, duration)
return tween
## 获取Tween缓动类型
func _get_tween_ease(ease_type: EaseType) -> Tween.EaseType:
"""
将自定义缓动类型转换为Tween缓动类型
@param ease_type: 自定义缓动类型
@return: Tween缓动类型
"""
match ease_type:
EaseType.EASE_IN:
return Tween.EASE_IN
EaseType.EASE_OUT:
return Tween.EASE_OUT
EaseType.EASE_IN_OUT:
return Tween.EASE_IN_OUT
EaseType.BOUNCE:
return Tween.EASE_OUT # Godot没有内置bounce使用ease_out
EaseType.ELASTIC:
return Tween.EASE_OUT # Godot没有内置elastic使用ease_out
_:
return Tween.EASE_OUT
## 创建加载动画
func create_loading_spinner(parent: Control, size: Vector2 = Vector2(32, 32)) -> Control:
"""
创建加载旋转动画
@param parent: 父节点
@param size: 大小
@return: 旋转节点
"""
var spinner = ColorRect.new()
spinner.size = size
spinner.color = Color.WHITE
# 创建简单的旋转图形
var style = StyleBoxFlat.new()
style.bg_color = Color.TRANSPARENT
style.border_width_top = 3
style.border_width_right = 1
style.border_width_bottom = 1
style.border_width_left = 1
style.border_color = Color.WHITE
style.corner_radius_top_left = size.x / 2
style.corner_radius_top_right = size.x / 2
style.corner_radius_bottom_left = size.x / 2
style.corner_radius_bottom_right = size.x / 2
spinner.add_theme_stylebox_override("panel", style)
parent.add_child(spinner)
# 添加旋转动画
var tween = spinner.create_tween()
tween.set_loops()
tween.tween_property(spinner, "rotation", TAU, 1.0)
return spinner
## 移除加载动画
func remove_loading_spinner(spinner: Control) -> void:
"""
移除加载旋转动画
@param spinner: 旋转节点
"""
if spinner and is_instance_valid(spinner):
spinner.queue_free()