@tool class_name PPTHelper extends Node2D signal slide_changed(new_slide_index: int) signal presentation_finished ## 转场配置数组,按顺序播放 @export var configs: Array[PPTHelperConfig] = [] ## 如果为 true, 将在场景加载完成后自动播放 @export var auto_start := true ## 如果为 true, 播放到结尾后将从头开始 @export var loop := false ## 当前激活的配置索引 var current_index := -1 ## 动画播放锁,防止在转场时重复触发 var is_playing := false ## 编辑器工具按钮:自动配置 Slides @export_tool_button("自动配置 Slides") var auto_config_slides = _auto_config_slides func _ready() -> void: if Engine.is_editor_hint(): return if auto_start: play() ## 开始或重新开始播放 ## @param start_index 从指定的索引开始播放 func play(start_index := 0) -> void: if configs.is_empty(): printerr("PPTHelper: No configs to play.") return if start_index < 0 or start_index >= configs.size(): printerr("PPTHelper: 'start_index' is out of bounds.") return # 重置状态 is_playing = false current_index = -1 # 隐藏所有页面,准备从头开始 for config in configs: if config and config.canvas_item: var node := get_node_or_null(config.canvas_item) as CanvasItem if is_instance_valid(node): node.modulate.a = 0.0 # 开始播放指定的页面 _transition_to(start_index) ## 手动切换到下一个页面 func next() -> void: if is_playing or current_index == -1: return var next_index := current_index + 1 if next_index >= configs.size(): if loop: next_index = 0 else: presentation_finished.emit() print("PPTHelper: Reached the end of the presentation.") return _transition_to(next_index) ## 跳转到指定的页面 func goto(target_index: int) -> void: if target_index < 0 or target_index >= configs.size(): printerr("PPTHelper: 'target_index' for goto() is out of bounds.") return if target_index == current_index or is_playing: return _transition_to(target_index) # 核心转换函数 func _transition_to(target_index: int) -> void: if is_playing: return is_playing = true var from_index := current_index var tween: Tween = create_tween() tween.set_parallel(true) # 并行处理 in 和 out 动画 # --- 处理上一页的 "Ease Out" --- if from_index != -1: var from_config := configs[from_index] var from_node := get_node_or_null(from_config.canvas_item) as CanvasItem if is_instance_valid(from_node): if from_config.ease_out_duration > 0: ( tween . tween_property(from_node, "modulate:a", 0.0, from_config.ease_out_duration) . set_trans(_get_trans_type(from_config.ease_out_trans_type)) . set_ease(_get_ease_type(from_config.ease_out_type)) ) else: from_node.modulate.a = 0.0 else: printerr("PPTHelper: Invalid node for ease out at index %d." % from_index) # --- 处理目标页的 "Ease In" --- var target_config := configs[target_index] var target_node := get_node_or_null(target_config.canvas_item) as CanvasItem if is_instance_valid(target_node): if target_config.ease_in_duration > 0: target_node.modulate.a = 0.0 # 确保开始时透明 ( tween . tween_property(target_node, "modulate:a", 1.0, target_config.ease_in_duration) . set_trans(_get_trans_type(target_config.ease_in_trans_type)) . set_ease(_get_ease_type(target_config.ease_in_type)) ) else: target_node.modulate.a = 1.0 else: printerr("PPTHelper: Invalid node for ease in at index %d." % target_index) is_playing = false # 如果节点无效,立即解锁 return tween.tween_interval(0.001) # 最小延迟防止无效 await tween.finished # --- 更新状态并检查自动播放下一个 --- current_index = target_index is_playing = false slide_changed.emit(current_index) # 发射信号 # 如果配置为自动转到下一个,等待指定时间后执行 if target_config.ease_out_to_next: if target_config.ease_out_wait_time > 0.0: await Util.wait(target_config.ease_out_wait_time) next() else: presentation_finished.emit() # 辅助函数:将字符串映射到 Tween.TransitionType func _get_trans_type(trans_str: String) -> Tween.TransitionType: match trans_str.to_lower(): "linear": return Tween.TRANS_LINEAR "sine": return Tween.TRANS_SINE "cubic": return Tween.TRANS_CUBIC "quart": return Tween.TRANS_QUART "quint": return Tween.TRANS_QUINT "expo": return Tween.TRANS_EXPO "elastic": return Tween.TRANS_ELASTIC "bounce": return Tween.TRANS_BOUNCE "spring": return Tween.TRANS_SPRING _: return Tween.TRANS_LINEAR # 辅助函数:将字符串映射到 Tween.EaseType func _get_ease_type(ease_str: String) -> Tween.EaseType: match ease_str.to_lower(): "in": return Tween.EASE_IN "out": return Tween.EASE_OUT "inout": return Tween.EASE_IN_OUT _: return Tween.EASE_IN_OUT #### TOOL BUTTON func _auto_config_slides() -> void: if not Engine.is_editor_hint(): return # 只在编辑器中运行 # 清空现有配置 configs = [] # 获取所有直接子节点 var children := get_children() if children.is_empty(): printerr("PPTHelper: No child nodes found for auto-configuration.") notify_property_list_changed() return # 从下到上配置(反转数组) children.reverse() var configured_count := 0 for child in children: if child is CanvasItem: var config := PPTHelperConfig.new() config.canvas_item = get_path_to(child) # 设置 NodePath 到子节点 # 其他属性使用默认值(如 ease_in_duration = 0.0 等),可手动调整 configs.append(config) configured_count += 1 else: print("PPTHelper: Skipping non-CanvasItem child: %s" % child.name) if configured_count > 0: print("PPTHelper: Auto-configured %d slides from child nodes." % configured_count) else: printerr("PPTHelper: No valid CanvasItem children found for auto-configuration.") # 刷新编辑器属性以显示更新 notify_property_list_changed()