348 lines
8.8 KiB
GDScript
348 lines
8.8 KiB
GDScript
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() |