This commit is contained in:
2026-06-07 11:49:55 +08:00
parent ff0f3bde54
commit 1897658a00
98 changed files with 9903 additions and 13907 deletions

View File

@@ -57,7 +57,6 @@ namespace BaseGames.Core
SceneName = sm?.LastCheckpointScene,
EntryTransitionId = sm?.LastCheckpointSpawnId,
TransitionType = TransitionType.Scene,
ShowLoadingScreen = true,
IsRespawn = true,
});

View File

@@ -15,7 +15,8 @@ namespace BaseGames.Core.Events
/// <summary>过渡类型,决定 <see cref="BaseGames.Core.SceneService"/> 的演出行为(淡出时长、加载画面等)。
/// 默认 <see cref="TransitionType.Room"/>,向后兼容旧请求。</summary>
public TransitionType TransitionType;
/// <summary>是否显示加载画面(由 TransitionType 自动推导,通常无需手动设置)。</summary>
/// <summary>是否显示加载画面。<b>无需手动设置</b>:由 <see cref="BaseGames.Core.SceneService"/>
/// 统一按 <see cref="TransitionType"/> 推导Scene→显示其余→不显示调用点设置的值会被覆盖。</summary>
public bool ShowLoadingScreen;
/// <summary>死亡复活时为 true不执行正常过渡动画</summary>
public bool IsRespawn;

View File

@@ -21,57 +21,69 @@ namespace BaseGames.Core
[Header("Event Channels - Raise")]
[SerializeField] private StringEventChannelSO _onSceneLoaded;
[Header("Event Channels - Raise加载画面")]
[Tooltip("ShowLoadingScreen=true 时,在加载开始时发布。")]
[SerializeField] private VoidEventChannelSO _onLoadingStarted;
[Tooltip("ShowLoadingScreen=true 时,在加载完成时发布。")]
[SerializeField] private VoidEventChannelSO _onLoadingComplete;
[Tooltip("ShowLoadingScreen=true 时,持续发布加载进度 [0,1]")]
[Header("Event Channels - Raise加载进度")]
[Tooltip("ShowLoadingScreen=true 时,持续发布加载进度 [0,1]\n" +
"加载画面的显示/隐藏由 SceneService 统一归口(见 SceneService._onLoadingStarted/Complete\n" +
"本组件只负责发布真实进度。")]
[SerializeField] private FloatEventChannelSO _onLoadingProgressUpdated;
private string _currentRoomScene;
private AsyncOperationHandle<SceneInstance> _currentHandle;
// 加载进度按阶段加权,使进度条单调推进、不在末尾从 0.9 直接跳到 1
// ① 加载新场景Addressables → 区间 [0, kLoadWeight]
// ② 卸载旧场景(无旧场景时直接补满)→ 区间 [kLoadWeight, 1]
// 注意Addressables 对单个场景的 PercentComplete 颗粒度较粗(本地常 0→1 跳变),
// 末端视觉平滑由 LoadingScreenManager 的填充缓动负责,这里只保证语义上的真实与单调。
private const float kLoadWeight = 0.85f;
private const float kUnloadWeight = 1f - kLoadWeight; // 0.15
public IEnumerator LoadSceneCoroutine(SceneLoadRequest request)
{
if (request.ShowLoadingScreen)
_onLoadingStarted?.Raise();
bool showLoading = request.ShowLoadingScreen;
if (showLoading)
_onLoadingProgressUpdated?.Raise(0f);
// 先加载新场景Additive成功后再卸载旧场景
// 顺序保证:若加载失败,旧场景仍保持可用,不会出现无场景的空状态
var loadOp = AssetLoader.LoadSceneAsync(request.SceneName, LoadSceneMode.Additive);
// 逐帧轮询以上报进度(不能直接 yield return loadOp那样无法回调进度
// 阶段 ①:逐帧轮询以上报真实进度(不能直接 yield return loadOp那样无法回调进度
while (!loadOp.IsDone)
{
if (request.ShowLoadingScreen)
_onLoadingProgressUpdated?.Raise(loadOp.PercentComplete * 0.9f);
if (showLoading)
_onLoadingProgressUpdated?.Raise(loadOp.PercentComplete * kLoadWeight);
yield return null;
}
if (loadOp.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError($"[SceneLoader] 加载场景失败:{request.SceneName}(旧场景保持不变)");
if (request.ShowLoadingScreen)
_onLoadingComplete?.Raise();
yield break;
yield break; // 加载画面收尾由 SceneService 在外层统一负责(无论成败都会隐藏)
}
// 新场景加载成功,再卸载旧场景
// 加载阶段结束:进度推进到分段边界
if (showLoading)
_onLoadingProgressUpdated?.Raise(kLoadWeight);
// 阶段 ②:新场景加载成功,再卸载旧场景(占进度条末段 [kLoadWeight, 1]
if (!string.IsNullOrEmpty(_currentRoomScene) && _currentHandle.IsValid())
{
var unloadOp = AssetLoader.UnloadSceneAsync(_currentHandle);
yield return unloadOp;
while (!unloadOp.IsDone)
{
if (showLoading)
_onLoadingProgressUpdated?.Raise(kLoadWeight + unloadOp.PercentComplete * kUnloadWeight);
yield return null;
}
}
_currentHandle = loadOp;
_currentRoomScene = request.SceneName;
if (request.ShowLoadingScreen)
{
if (showLoading)
_onLoadingProgressUpdated?.Raise(1f);
_onLoadingComplete?.Raise();
}
_onSceneLoaded?.Raise(request.SceneName);
}

View File

@@ -51,6 +51,12 @@ namespace BaseGames.Core
[SerializeField] private VoidEventChannelSO _onFadeInRequest;
[SerializeField] private VoidEventChannelSO _onFadeOutRequest;
[Tooltip("加载画面显隐由 SceneService 统一归口包裹两条加载路径SceneLoader 与流式 coordinator\n" +
"保证流式房间Room_*,走 ISceneLoadCoordinator的 Scene 过渡也能显示加载画面。\n" +
"进度条进度仍由 SceneLoader 发布到 EVT_LoadingProgressUpdated。")]
[SerializeField] private VoidEventChannelSO _onLoadingStarted;
[SerializeField] private VoidEventChannelSO _onLoadingComplete;
[Tooltip("场景加载完成、WorldStateRegistry 已就绪后触发。\n" +
"场景内物体应订阅此事件,从 WorldStateRegistry 读取存档状态并应用(替代在 Start() 中读取)。\n" +
"触发后会等待一帧,确保所有处理器执行完毕,再执行淡入显示场景。\n" +
@@ -80,6 +86,11 @@ namespace BaseGames.Core
private void HandleSceneLoadRequest(SceneLoadRequest request)
{
// 加载画面是否显示,由 TransitionType 统一推导(见 SceneLoadRequest.ShowLoadingScreen 注释):
// Scene跨大区/进游戏/快速旅行/复活)→ 显示Room/Seamless/AtmosphericFade → 不显示。
// 各调用点无需再手动设置 ShowLoadingScreen避免出现"是 Scene 却没显示"的不一致。
request.ShowLoadingScreen = request.TransitionType == TransitionType.Scene;
// Seamless / AtmosphericFade 由 ITransitionDirector 处理(需要预加载支持)
if (request.TransitionType == TransitionType.Seamless ||
request.TransitionType == TransitionType.AtmosphericFade)
@@ -113,9 +124,14 @@ namespace BaseGames.Core
if (fadeDuration > 0f)
yield return new WaitForSeconds(fadeDuration);
// 加载画面统一在此显隐,包裹下面两条加载路径(黑幕已落下后再揭开加载画面)。
bool showLoading = request.ShowLoadingScreen;
if (showLoading) _onLoadingStarted?.Raise();
// 流式模式优先:若流式协调器已注册且声明对本场景的所有权,委托给流式系统加载。
// 这确保复活 / 快速传送等使用 Room/Scene 类型的路径也能正确触发冷却和卸载生命周期,
// 避免前一房间在 RoomStreamingManager 中永远停留在 Active 状态。
// 注意:流式路径不发布逐帧进度,加载画面表现为不确定进度(结尾由缓动补满)。
var coordinator = ServiceLocator.GetOrDefault<ISceneLoadCoordinator>();
if (coordinator != null && coordinator.OwnsScene(request.SceneName))
{
@@ -124,6 +140,7 @@ namespace BaseGames.Core
}
else if (_sceneLoader != null)
{
// SceneLoader 负责逐帧发布真实进度EVT_LoadingProgressUpdated
yield return StartCoroutine(_sceneLoader.LoadSceneCoroutine(request));
}
else
@@ -131,6 +148,8 @@ namespace BaseGames.Core
Debug.LogError("[SceneService] _sceneLoader 未赋值,场景加载中断。请在 Inspector 中绑定 SceneLoader 组件。");
}
if (showLoading) _onLoadingComplete?.Raise();
// 通知WorldStateRegistry 已就绪,场景物体应在此帧内从中读取存档状态并应用初始状态。
// 订阅者WorldStateRegistrySaver、各场景 StateApplier 等)会在同一帧同步执行。
_onSceneWorldStateRestored?.Raise();