using System.Threading.Tasks; using Godot; namespace Milimoe.GodotGame; public partial class ChapterScene : Node2D, INovelStartEvent, INovelEndEvent, IMenuObject, IFadeInFadeOutBlack { [Export] public PackedScene NovelScene { get; set; } [Export] public PackedScene MenuScene { get; set; } [Export] public CanvasLayer UI { get; set; } [Export] public string ChapterInGame { get; set; } [Export] public string AreaInGame { get; set; } [Export] public string NovelName { get; set; } [Export] public string SceneName { get; set; } [Export] public CameraFight CameraFight { get; set; } [Export] public CharacterBody Player { get; set; } [Export] public PackedScene Fighting { get; set; } public string NovelNodeKey { get; set; } = ""; private CanvasLayer _novelCanvasLayer; private CanvasLayer _menuCanvasLayer; private Node _novelInterface; private Node _menuInterface; private AudioStreamPlayer _buttonPlayer; private AudioStreamPlayer _selectPlayer; private AudioStreamPlayer _enterFightPlayer; private Node _fighting; private UserInterface _userInterface; public override async void _Ready() { _buttonPlayer = GetNode("ButtonPlayer"); _selectPlayer = GetNode("SelectPlayer"); _enterFightPlayer = GetNode("EnterFightPlayer"); if (UI.GetNode("UserInterface") is UserInterface userInterface) { _userInterface = userInterface; } GameConstant.InitGame(); Player.BattleStarted += OnBattleStarted; // 实例化菜单界面场景 if (MenuScene != null) { _menuCanvasLayer = GetNode("Menu"); _menuCanvasLayer.Visible = false; _menuInterface = MenuScene.Instantiate(); if (_menuInterface is MenuController menuController) { menuController.Parent = this; } _menuCanvasLayer.AddChild(_menuInterface); } OnNovelStart(ChapterInGame, AreaInGame, NovelName, SceneName, NovelNodeKey); } public override void _Input(InputEvent @event) { if (@event.IsActionPressed("toggle_fight_log")) { ToggleFightLog(); } if (@event is InputEventKey keyEvent) { if (keyEvent.Pressed && keyEvent.Keycode == Key.Escape) { if (_menuCanvasLayer != null) { ChangeState(); } } } } public async void OnNovelStart(string chapter_in_game, string area_in_game, string novel_name, string scene_name, string node_key = "") { GameConstant.Pause = true; GameConstant.ChapterInGame = chapter_in_game; GameConstant.AreaInGame = area_in_game; GameConstant.NovelName = novel_name; GameConstant.SceneName = scene_name; _novelCanvasLayer = GetNode("Novel"); _novelCanvasLayer.Visible = true; ColorRect colorRect = GetNode("ColorRect"); colorRect.SelfModulate = new Color(0, 0, 0, 1); colorRect.Show(); // 实例化小说界面场景 if (NovelScene != null) { _novelInterface = NovelScene.Instantiate(); _novelCanvasLayer.AddChild(_novelInterface); if (_novelInterface is NovelController novelController) { novelController.ChapterObject = this; novelController.MenuObject = this; novelController.FadeObject = this; await FadeOutBlack(1f, true); novelController.InitNovel(novel_name, scene_name, node_key); GameConstant.AutoSave(0); } } } public async void OnNovelEnd(CanvasLayer node, string novel_name, string scene_name) { node.Visible = false; await FadeInBlack(1f); await Task.Delay(500); await FadeOutBlack(0.5f, true); if (UI != null) { GameConstant.Pause = false; UI.Visible = true; if (UI.GetNode("UserInterface") is UserInterface userInterface) { if (userInterface.Location != null) { userInterface.Location.Text = GameConstant.AreaInGame; } if (userInterface.Rayne is CharacterStatus cs1) { cs1.UpdateLabel(); } if (userInterface.Irene is CharacterStatus cs2) { cs2.UpdateLabel(); } userInterface.FightLog.Visible = false; userInterface.RoundQueue.Visible = false; //if (GameConstant.Characters.Count == 1) //{ // userInterface.CharacterStatus1.Visible = true; // TextureRect image = userInterface.CharacterStatus1.GetNode("Image"); // using Texture2D texture = GD.Load("res://assets/character/雷恩.png"); // image.Texture = texture; //} //else if (GameConstant.Characters.Count == 2) //{ // userInterface.CharacterStatus1.Visible = true; // userInterface.CharacterStatus2.Visible = true; //} } } } public async Task FadeInBlack(float fadeTime, bool hide = false) { // 淡入黑色遮罩 ColorRect colorRect = GetNode("ColorRect"); colorRect.SelfModulate = new Color(0, 0, 0, 0); colorRect.Show(); Tween tween = CreateTween(); tween.TweenProperty(colorRect, "self_modulate", new Color(0, 0, 0, 1), fadeTime); await ToSignal(tween, "finished"); if (hide) colorRect.Hide(); } public async Task FadeOutBlack(float fadeTime, bool hide = false) { // 淡出黑色遮罩 ColorRect colorRect = GetNode("ColorRect"); colorRect.SelfModulate = new Color(0, 0, 0, 1); colorRect.Show(); Tween tween = CreateTween(); tween.TweenProperty(colorRect, "self_modulate", new Color(0, 0, 0, 0), fadeTime); await ToSignal(tween, "finished"); if (hide) colorRect.Hide(); } public void ChangeState() { GD.Print("Change Menu State!"); if (_menuCanvasLayer != null) { _menuCanvasLayer.Visible = !_menuCanvasLayer.Visible; } } private async void OnBattleStarted(CharacterBody2D player, CharacterBody2D enemy) { EnemyBody enemyBody = null; if (enemy is EnemyBody body) { enemyBody = body; enemyBody.Enabled = false; } GD.Print("信号已接收:开始切换至战斗摄像机"); GameConstant.Pause = true; Camera2D Camera = GetNode("Player/Camera2D"); Vector2 targetZoom = Camera.Zoom; CanvasLayer ColorRectCanvasLayer = GetNode("ColorRectCanvasLayer"); ColorRectCanvasLayer.Visible = true; ColorRect BlurOverlay = ColorRectCanvasLayer.GetNode("BlurOverlay"); _enterFightPlayer.Play(); GetNode("战斗提示动画").Play("fight_start"); PlayZoomBlurTransition(Camera, BlurOverlay); Engine.TimeScale = 0.05f; ShakeCamera(Camera, 0.2f, 35.0f); await Task.Delay(1000); Engine.TimeScale = 1.0f; AudioStreamPlayer MapMusic = GetNode("MapMusic"); if (MapMusic.Playing) { MapMusic.StreamPaused = true; } AudioStreamPlayer FightMusic = GetNode("FightMusic"); FightMusic.Play(); FightMusic.StreamPaused = false; Vector2I battleStartCell = new(100, 10); // 如果 player 脚本里实现了上面的 TeleportToTile if (player is CharacterBody myPlayer) { myPlayer.TeleportToTile(GetNode("TileMapLayer"), battleStartCell); } // 切换当前摄像机 if (CameraFight != null) { CameraFight.Enabled = true; CameraFight.UpdateLimitsFromTileMap(); CameraFight.MakeCurrent(); // 激活摄像机的移动逻辑 CameraFight.SetPhysicsProcess(true); } _fighting = Fighting.Instantiate(); Panel fightLog = null; UserInterface userInterface = null; if (UI.GetNode("UserInterface") is UserInterface tempUI) { userInterface = tempUI; userInterface.Location.Text = ""; fightLog = userInterface.FightLog; fightLog.Visible = true; userInterface.RoundQueue.Visible = true; } GetTree().CreateTween().TweenProperty(Camera, "zoom", targetZoom, 0.2f); ColorRectCanvasLayer.Visible = false; if (_fighting is Fighting fighting) { fighting.ParentUI = this; fighting.TileMapLayer = GetNode("TileMapLayer"); fighting.CameraFight = CameraFight; fighting.Character = GameConstant.Characters[1]; fighting.FightLog = fightLog?.GetNode("RichTextLabel"); fighting.StartCell = battleStartCell; fighting.Enemy = enemyBody; await fighting.StartGame(false, true); // 战斗结束恢复场景 if (enemyBody != null && enemyBody.Dead && IsInstanceValid(enemy)) { enemy.QueueFree(); } CameraFight?.ExitBattleMode(); if (player is CharacterBody myPlayer2) { myPlayer2.ReturnToSavedPosition(); } if (userInterface != null) { userInterface.Location.Text = GameConstant.AreaInGame; userInterface.FightLog.Visible = false; userInterface.RoundQueue.Visible = false; } } } private void ShakeCamera(Camera2D camera, float duration, float intensity) { Tween tween = GetTree().CreateTween(); for (int i = 0; i < 5; i++) { Vector2 randomOffset = new( (float)GD.RandRange(-intensity, intensity), (float)GD.RandRange(-intensity, intensity) ); // 快速来回摆动 tween.TweenProperty(camera, "offset", randomOffset, duration / 5); } // 最后归位 tween.TweenProperty(camera, "offset", Vector2.Zero, 0.5f); } private void PlayZoomBlurTransition(Camera2D camera, ColorRect BlurOverlay) { // 确保模糊层可见 BlurOverlay.Visible = true; ShaderMaterial mat = BlurOverlay.Material as ShaderMaterial; // 创建补间动画 Tween tween = GetTree().CreateTween(); tween.SetIgnoreTimeScale(true); // 设置并行播放,让拉近和模糊同时发生 tween.SetParallel(true); // 1. 镜头拉近动画 (ExpoOut 效果最像“冲刺”) Vector2 targetZoom = camera.Zoom * 1.5f; tween.TweenProperty(camera, "zoom", targetZoom, 0.3f) .SetTrans(Tween.TransitionType.Expo) .SetEase(Tween.EaseType.Out); // 2. Shader 模糊强度动画 tween.TweenMethod(Callable.From((val) => { mat.SetShaderParameter("blur_amount", val); }), 0.0f, 5f, 0.2f); // 3. (可选) 稍微等待一下后,执行下一步逻辑 tween.Chain().SetParallel(false); tween.TweenCallback(Callable.From(() => { GD.Print("拉近完成,切换到战斗UI或开始战斗"); })); } private void ToggleFightLog() { if (_userInterface != null && _userInterface.FightLog != null) { // 这一行代码就能实现“可见”与“不可见”的快速取反 _userInterface.FightLog.Visible = !_userInterface.FightLog.Visible; GD.Print($"战斗日志状态: {(_userInterface.FightLog.Visible ? "显示" : "隐藏")}"); } } }