extends SceneTree ## 标题侧节奏音乐 + 标题背景「过去/现在」循环动画。 ## 标题、难度选择、退出确认期间音乐持续播放;进入 gameplay 后停止, ## 由 RhythmManager.start() + AudioLayerController.restart_layers() 从 0 重播。 var failures: Array[String] = [] func _init() -> void: _run.call_deferred() func _run() -> void: root.size = Vector2i(1152, 648) _ensure_game_settings() var flow: Node = load("res://autoload/game_flow_manager.gd").new() flow.name = "FlowUnderTest" flow.set("manage_scene", false) root.add_child(flow) await process_frame # --- 标题音乐播放器:常驻处理、走 Music 总线 --- var player := flow.get_node_or_null("TitleMusicPlayer") as AudioStreamPlayer _expect(player != null, "flow owns a TitleMusicPlayer") if player != null: _expect(player.process_mode == Node.PROCESS_MODE_ALWAYS, "title music must survive the SceneTree pause") _expect_equal(player.bus, &"Music", "title music routes through the Music bus") # headless 下不加载 mp3,注入无限长的生成器流来验证播放开关。 player.stream = AudioStreamGenerator.new() # --- 标题界面同时持有两个时代背景,「现在」画在「过去」上层 --- var title_screen := (flow.get("_screens") as Dictionary).get(&"Title", null) as Control _expect(title_screen != null, "title screen exists") if title_screen != null: var past := title_screen.get_node_or_null("SceneBackgroundPast") as TextureRect var future := title_screen.get_node_or_null("SceneBackgroundFuture") as TextureRect _expect(past != null, "title keeps the past-era background") _expect(future != null, "title keeps the future-era background") if past != null and future != null: _expect(past.texture != null and future.texture != null, "both era textures load") _expect(past.get_index() < future.get_index(), "the future era paints above the past era") # --- 标题状态:音乐播放 + 时代循环动画运行 --- flow.call("_set_state", &"Title") flow.call("_update_title_music") await process_frame _expect(bool(flow.call("_should_play_title_music")), "title state wants the rhythm music") if player != null: _expect(player.playing, "title music plays on the title screen") var tween := flow.get("_title_bg_tween") as Tween _expect(tween != null and tween.is_valid(), "the era crossfade loop runs on the title screen") # --- 难度选择:音乐延续,标题动画停止 --- flow.call("_set_state", &"DifficultySelect") await process_frame _expect(bool(flow.call("_should_play_title_music")), "difficulty select keeps the rhythm music") if player != null: _expect(player.playing, "title music keeps playing on difficulty select") _expect(flow.get("_title_bg_tween") == null, "the era loop stops once the title screen hides") # --- 进入 gameplay:标题音乐停止 --- flow.call("_set_state", &"Gameplay_FrontArea") await process_frame _expect(not bool(flow.call("_should_play_title_music")), "gameplay states drop the title music") if player != null: _expect(not player.playing, "title music stops when the run starts") # --- 回到标题:音乐从头再播 --- flow.call("_set_state", &"Title") await process_frame if player != null: _expect(player.playing, "returning to title restarts the music") flow.free() _finish() func _ensure_game_settings() -> Node: var settings := root.get_node_or_null("GameSettings") if settings != null: return settings settings = load("res://autoload/game_settings.gd").new() settings.name = "GameSettings" root.add_child(settings) return settings func _expect(condition: bool, label: String) -> void: if not condition: failures.append(label) func _expect_equal(actual: Variant, expected: Variant, label: String) -> void: if actual != expected: failures.append("%s: expected %s, got %s" % [label, expected, actual]) func _finish() -> void: paused = false if failures.is_empty(): print("PASS title music and background loop") quit(0) else: for failure: String in failures: push_error(failure) quit(1)