UI系统
This commit is contained in:
@@ -57,7 +57,6 @@ namespace BaseGames.Core
|
||||
SceneName = sm?.LastCheckpointScene,
|
||||
EntryTransitionId = sm?.LastCheckpointSpawnId,
|
||||
TransitionType = TransitionType.Scene,
|
||||
ShowLoadingScreen = true,
|
||||
IsRespawn = true,
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user