353 lines
12 KiB
C#
353 lines
12 KiB
C#
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<AudioStreamPlayer>("ButtonPlayer");
|
||
_selectPlayer = GetNode<AudioStreamPlayer>("SelectPlayer");
|
||
_enterFightPlayer = GetNode<AudioStreamPlayer>("EnterFightPlayer");
|
||
if (UI.GetNode<Control>("UserInterface") is UserInterface userInterface)
|
||
{
|
||
_userInterface = userInterface;
|
||
}
|
||
|
||
GameConstant.InitGame();
|
||
|
||
Player.BattleStarted += OnBattleStarted;
|
||
|
||
// 实例化菜单界面场景
|
||
if (MenuScene != null)
|
||
{
|
||
_menuCanvasLayer = GetNode<CanvasLayer>("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<CanvasLayer>("Novel");
|
||
_novelCanvasLayer.Visible = true;
|
||
|
||
ColorRect colorRect = GetNode<ColorRect>("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<Control>("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<TextureRect>("Image");
|
||
// using Texture2D texture = GD.Load<CompressedTexture2D>("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");
|
||
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");
|
||
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<Camera2D>("Player/Camera2D");
|
||
Vector2 targetZoom = Camera.Zoom;
|
||
CanvasLayer ColorRectCanvasLayer = GetNode<CanvasLayer>("ColorRectCanvasLayer");
|
||
ColorRectCanvasLayer.Visible = true;
|
||
ColorRect BlurOverlay = ColorRectCanvasLayer.GetNode<ColorRect>("BlurOverlay");
|
||
_enterFightPlayer.Play();
|
||
GetNode<AnimationPlayer>("战斗提示动画").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<AudioStreamPlayer>("MapMusic");
|
||
if (MapMusic.Playing)
|
||
{
|
||
MapMusic.StreamPaused = true;
|
||
}
|
||
AudioStreamPlayer FightMusic = GetNode<AudioStreamPlayer>("FightMusic");
|
||
FightMusic.Play();
|
||
FightMusic.StreamPaused = false;
|
||
|
||
Vector2I battleStartCell = new(100, 10);
|
||
|
||
// 如果 player 脚本里实现了上面的 TeleportToTile
|
||
if (player is CharacterBody myPlayer)
|
||
{
|
||
myPlayer.TeleportToTile(GetNode<TileMapLayer>("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<Control>("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>("TileMapLayer");
|
||
fighting.CameraFight = CameraFight;
|
||
fighting.Character = GameConstant.Characters[1];
|
||
fighting.FightLog = fightLog?.GetNode<RichTextLabel>("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<float>((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 ? "显示" : "隐藏")}");
|
||
}
|
||
}
|
||
}
|