Files
Cielonos/Assets/Scripts/MainGame/GameRun/Map/MapManager.cs
SoulliesOfficial 39b43680a9 爆更
2026-07-18 03:16:20 -04:00

206 lines
8.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections;
using Cielonos.Core.UI;
using Cielonos.MainGame.Characters;
using Cielonos.MainGame.Map;
using DG.Tweening;
using SLSUtilities.General;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Cielonos.MainGame
{
/// <summary>
/// Zone 加载后的初始化模式。
/// EnvironmentOnly 用于回到已完成节点或起点等非战斗场景,只恢复场景和交互物;
/// CombatEncounter 用于正式进入战斗节点,会生成敌人并启动 CombatRoomSubmodule。
/// </summary>
public enum ZoneSetupMode
{
EnvironmentOnly,
CombatEncounter,
}
public partial class MapManager : Singleton<MapManager>
{
public MapBaseCollection baseCollection;
public ZoneData currentZoneData;
public ZoneManager currentZoneManager;
/// <summary>
/// 加载指定 ZoneData 对应的场景。保留 bool 参数是为了兼容旧调用点;
/// 新代码优先使用 ZoneSetupMode 重载,让调用语义更清晰。
/// </summary>
public void LoadZone(ZoneData nextZoneData, Action onComplete = null, bool skipBattleSetup = false)
{
ZoneSetupMode setupMode = skipBattleSetup
? ZoneSetupMode.EnvironmentOnly
: ZoneSetupMode.CombatEncounter;
LoadZone(nextZoneData, onComplete, setupMode);
}
/// <summary>
/// 使用明确的初始化模式加载 Zone并在场景、出生点、战斗初始化全部完成后回调调用方。
/// </summary>
public void LoadZone(ZoneData nextZoneData, Action onComplete, ZoneSetupMode setupMode)
{
StartCoroutine(SwitchRoomRoutine(nextZoneData, onComplete, setupMode));
}
}
public partial class MapManager
{
public static MapBaseCollection BaseCollection => Instance.baseCollection;
}
public partial class MapManager
{
private IEnumerator SwitchRoomRoutine(ZoneData nextZoneData, Action onComplete, ZoneSetupMode setupMode)
{
bool isFirstLoad = currentZoneData == null;
// 切换场景期间禁止玩家输入,避免玩家在卸载/加载窗口里触发移动或攻击。
PlayerInputSubcontroller playerInput = MainGameManager.Player.inputSc;
playerInput.inputActions.Player.Disable();
// 加载前禁用 CharacterController并清空下落速度避免玩家在异步加载期间掉入虚空。
if (MainGameManager.Player.collisionSc != null && MainGameManager.Player.collisionSc.useCharacterController)
{
MainGameManager.Player.collisionSc.characterController.enabled = false;
}
if (MainGameManager.Player.movementSc != null)
{
MainGameManager.Player.movementSc.gravitationalMovement = Vector3.zero;
MainGameManager.Player.movementSc.jumpVelocity = 0f;
}
// 首次进入时画面本来就是黑屏;后续切换需要先淡出再卸载旧场景。
if (!isFirstLoad)
{
bool fadeOutComplete = false;
ScreenFader.Instance.FadeToBlack(onComplete: () => fadeOutComplete = true).Play();
yield return new WaitUntil(() => fadeOutComplete);
}
if (!isFirstLoad)
{
yield return SceneManager.UnloadSceneAsync(currentZoneData.sceneName);
}
currentZoneData = nextZoneData;
string nextSceneName = nextZoneData.sceneName;
AsyncOperation op = SceneManager.LoadSceneAsync(nextSceneName, LoadSceneMode.Additive);
op.allowSceneActivation = false;
while (op.progress < 0.9f)
{
yield return null;
}
op.allowSceneActivation = true;
yield return op;
Scene nextScene = SceneManager.GetSceneByName(nextSceneName);
SceneManager.SetActiveScene(nextScene);
yield return new WaitForEndOfFrame();
// 每次切换 Zone 都先清理上一场战斗遭遇的运行时缓存。
CombatManager.ResetCurrentEncounter();
// 出生点放置独立于战斗模式:回到已完成节点时也需要正确传送玩家。
SpawnPlayerAtZone(nextZoneData);
if (setupMode == ZoneSetupMode.EnvironmentOnly)
{
// 已完成节点或非战斗入口:只生成 ExitGate 等交互物,不生成敌人,也不启动房间结算。
ZoneManager.instance.SetupZoneWithoutEnemies(currentZoneData);
}
else
{
ZoneManager.instance.SetupZone(currentZoneData);
// 等待一帧,让 Instantiate 出的敌人完成 Start() 并注册到 EnemySubmodule。
yield return null;
CombatManager.CombatRoomSm.StartRoom();
}
yield return new WaitForSeconds(0.5f);
ScreenFader.Instance?.FadeToClear().Play();
playerInput.inputActions.Player.Enable();
onComplete?.Invoke();
}
/// <summary>
/// 将玩家传送到 ZoneData.playerSpawns 中随机选择的出生点;若配置缺失则回退到原点。
/// </summary>
private void SpawnPlayerAtZone(ZoneData zoneData)
{
Transform playerTransform = MainGameManager.Player.transform;
// CharacterController 启用时直接设置 Transform 可能被物理状态覆盖,所以传送前先禁用。
if (MainGameManager.Player.collisionSc != null && MainGameManager.Player.collisionSc.useCharacterController)
{
MainGameManager.Player.collisionSc.characterController.enabled = false;
}
Vector3 targetPosition = Vector3.zero;
Quaternion targetRotation = Quaternion.identity;
bool foundSpawn = false;
if (zoneData.playerSpawns != null && zoneData.playerSpawns.Count > 0)
{
ZoneData.SpawnPointKey spawnKey = zoneData.playerSpawns.Count == 1
? zoneData.playerSpawns[0]
: zoneData.playerSpawns[UnityEngine.Random.Range(0, zoneData.playerSpawns.Count)];
if (ZoneManager.instance.spawnPoints.TryGetValue(spawnKey.group, out var points)
&& spawnKey.index < points.Count)
{
SpawnPoint point = points[spawnKey.index];
point.GetTransform(out targetPosition, out targetRotation);
foundSpawn = true;
Debug.Log($"[MapManager] 玩家放置在出生点 {spawnKey.group}_{spawnKey.index}。");
}
}
if (!foundSpawn)
{
Debug.LogWarning("[MapManager] ZoneData 未配置 playerSpawns 或出生点不存在,玩家将放置在原点。");
}
playerTransform.position = targetPosition;
playerTransform.rotation = targetRotation;
ResetPlayerMovementVelocity();
if (MainGameManager.Player.collisionSc != null && MainGameManager.Player.collisionSc.useCharacterController)
{
MainGameManager.Player.collisionSc.characterController.enabled = true;
}
}
/// <summary>
/// 清空玩家移动控制器里的速度缓存,避免传送后继承上一帧的重力、跳跃或水平位移。
/// </summary>
private void ResetPlayerMovementVelocity()
{
var movementSc = MainGameManager.Player.movementSc;
if (movementSc != null)
{
movementSc.gravitationalMovement = Vector3.zero;
movementSc.jumpVelocity = 0f;
movementSc.horizontalMovement = Vector3.zero;
movementSc.verticalMovement = Vector3.zero;
movementSc.finalMovementVelocity = Vector3.zero;
movementSc.jumpMovement = Vector3.zero;
movementSc.movementModifier = Vector3.zero;
}
}
}
}