diff --git a/GFramework.Game.Abstractions/enums/SceneTransitionPhases.cs b/GFramework.Game.Abstractions/enums/SceneTransitionPhases.cs
new file mode 100644
index 0000000..8745188
--- /dev/null
+++ b/GFramework.Game.Abstractions/enums/SceneTransitionPhases.cs
@@ -0,0 +1,42 @@
+// Copyright (c) 2026 GeWuYou
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+namespace GFramework.Game.Abstractions.enums;
+
+///
+/// 场景过渡阶段枚举,定义了场景切换过程中的不同执行阶段。
+/// 使用 Flags 特性支持组合多个阶段。
+///
+[Flags]
+public enum SceneTransitionPhases
+{
+ ///
+ /// 场景切换前阶段(阻塞执行)。
+ /// 用于执行需要在场景切换前完成的操作,如显示加载动画、弹出确认对话框等。
+ /// 此阶段的处理器会阻塞场景切换流程,直到所有处理器执行完成。
+ ///
+ BeforeChange = 1,
+
+ ///
+ /// 场景切换后阶段(非阻塞执行)。
+ /// 用于执行场景切换后的后续操作,如播放音效、记录日志、发送统计数据等。
+ /// 此阶段的处理器异步执行,不会阻塞场景切换流程。
+ ///
+ AfterChange = 2,
+
+ ///
+ /// 所有阶段的组合标志。
+ /// 表示处理器适用于场景切换的所有阶段。
+ ///
+ All = BeforeChange | AfterChange
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/enums/SceneTransitionType.cs b/GFramework.Game.Abstractions/enums/SceneTransitionType.cs
new file mode 100644
index 0000000..edd8599
--- /dev/null
+++ b/GFramework.Game.Abstractions/enums/SceneTransitionType.cs
@@ -0,0 +1,44 @@
+// Copyright (c) 2026 GeWuYou
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+namespace GFramework.Game.Abstractions.enums;
+
+///
+/// 场景过渡类型枚举,定义了场景切换的不同操作模式。
+///
+public enum SceneTransitionType
+{
+ ///
+ /// 压入新场景到场景栈顶部。
+ /// 当前场景会被暂停,新场景成为活动场景。
+ ///
+ Push,
+
+ ///
+ /// 弹出当前场景并恢复下一个场景。
+ /// 当前场景会被卸载,栈中的下一个场景变为活动场景。
+ ///
+ Pop,
+
+ ///
+ /// 替换所有场景,清空整个场景栈并加载新场景。
+ /// 此操作会卸载所有现有场景,然后加载指定的新场景。
+ ///
+ Replace,
+
+ ///
+ /// 清空所有已加载的场景。
+ /// 卸载场景栈中的所有场景,使系统回到无场景状态。
+ ///
+ Clear
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/scene/ISceneLoadingProgress.cs b/GFramework.Game.Abstractions/scene/ISceneLoadingProgress.cs
new file mode 100644
index 0000000..153325e
--- /dev/null
+++ b/GFramework.Game.Abstractions/scene/ISceneLoadingProgress.cs
@@ -0,0 +1,45 @@
+// Copyright (c) 2026 GeWuYou
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+namespace GFramework.Game.Abstractions.scene;
+
+///
+/// 场景加载进度接口,用于跟踪和报告场景资源的加载状态。
+/// 实现此接口的类可以提供场景加载的实时进度信息。
+///
+public interface ISceneLoadingProgress
+{
+ ///
+ /// 获取正在加载的场景的唯一标识符。
+ ///
+ string SceneKey { get; }
+
+ ///
+ /// 获取当前加载进度,范围为 0.0 到 1.0。
+ /// 0.0 表示刚开始加载,1.0 表示加载完成。
+ ///
+ float Progress { get; }
+
+ ///
+ /// 获取当前加载阶段的描述信息。
+ /// 例如:"加载纹理资源"、"初始化场景对象"等。
+ /// 如果没有具体信息则返回 null。
+ ///
+ string? Message { get; }
+
+ ///
+ /// 获取加载是否已完成的状态。
+ /// true 表示场景资源已全部加载完成,false 表示仍在加载中。
+ ///
+ bool IsCompleted { get; }
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/scene/ISceneRouteGuard.cs b/GFramework.Game.Abstractions/scene/ISceneRouteGuard.cs
new file mode 100644
index 0000000..07ed138
--- /dev/null
+++ b/GFramework.Game.Abstractions/scene/ISceneRouteGuard.cs
@@ -0,0 +1,50 @@
+// Copyright (c) 2026 GeWuYou
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+namespace GFramework.Game.Abstractions.scene;
+
+///
+/// 场景路由守卫接口,用于在场景切换前进行权限检查和条件验证。
+/// 实现此接口可以拦截场景的进入和离开操作。
+///
+public interface ISceneRouteGuard
+{
+ ///
+ /// 获取守卫的执行优先级。
+ /// 数值越小优先级越高,越先执行。
+ /// 建议范围:-1000 到 1000。
+ ///
+ int Priority { get; }
+
+ ///
+ /// 获取守卫是否可以中断后续守卫的执行。
+ /// true 表示当前守卫通过后,可以跳过后续守卫直接允许操作。
+ /// false 表示即使当前守卫通过,仍需执行所有后续守卫。
+ ///
+ bool CanInterrupt { get; }
+
+ ///
+ /// 异步检查是否允许进入指定场景。
+ ///
+ /// 目标场景的唯一标识符。
+ /// 场景进入参数,可能包含初始化数据或上下文信息。
+ /// 如果允许进入则返回 true,否则返回 false。
+ Task CanEnterAsync(string sceneKey, ISceneEnterParam? param);
+
+ ///
+ /// 异步检查是否允许离开指定场景。
+ ///
+ /// 当前场景的唯一标识符。
+ /// 如果允许离开则返回 true,否则返回 false。
+ Task CanLeaveAsync(string sceneKey);
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/scene/ISceneTransitionHandler.cs b/GFramework.Game.Abstractions/scene/ISceneTransitionHandler.cs
new file mode 100644
index 0000000..b1d31c7
--- /dev/null
+++ b/GFramework.Game.Abstractions/scene/ISceneTransitionHandler.cs
@@ -0,0 +1,52 @@
+// Copyright (c) 2026 GeWuYou
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using GFramework.Game.Abstractions.enums;
+
+namespace GFramework.Game.Abstractions.scene;
+
+///
+/// 场景过渡处理器接口,定义了场景切换过程中的扩展点。
+/// 实现此接口可以在场景切换的不同阶段执行自定义逻辑。
+///
+public interface ISceneTransitionHandler
+{
+ ///
+ /// 获取处理器的执行优先级。
+ /// 数值越小优先级越高,越先执行。
+ /// 建议范围:-1000 到 1000。
+ ///
+ int Priority { get; }
+
+ ///
+ /// 获取处理器适用的场景过渡阶段。
+ /// 可以使用 Flags 组合多个阶段(如 BeforeChange | AfterChange)。
+ ///
+ SceneTransitionPhases Phases { get; }
+
+ ///
+ /// 判断处理器是否应该处理当前场景过渡事件。
+ ///
+ /// 场景过渡事件。
+ /// 当前执行的阶段。
+ /// 如果应该处理则返回 true,否则返回 false。
+ bool ShouldHandle(SceneTransitionEvent @event, SceneTransitionPhases phases);
+
+ ///
+ /// 异步处理场景过渡事件。
+ ///
+ /// 场景过渡事件,包含切换的上下文信息。
+ /// 取消令牌,用于支持操作取消。
+ /// 表示处理操作完成的异步任务。
+ Task HandleAsync(SceneTransitionEvent @event, CancellationToken cancellationToken);
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/scene/SceneTransitionEvent.cs b/GFramework.Game.Abstractions/scene/SceneTransitionEvent.cs
new file mode 100644
index 0000000..348bb6d
--- /dev/null
+++ b/GFramework.Game.Abstractions/scene/SceneTransitionEvent.cs
@@ -0,0 +1,115 @@
+// Copyright (c) 2026 GeWuYou
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using GFramework.Game.Abstractions.enums;
+
+namespace GFramework.Game.Abstractions.scene;
+
+///
+/// 场景过渡事件,封装了场景切换过程中的上下文信息。
+/// 提供了场景切换的元数据和自定义上下文数据的存储能力。
+///
+public sealed class SceneTransitionEvent
+{
+ private readonly Dictionary _context = new();
+
+ ///
+ /// 获取或初始化源场景的唯一标识符。
+ /// 表示切换前的场景,如果是首次加载场景则为 null。
+ ///
+ public string? FromSceneKey { get; init; }
+
+ ///
+ /// 获取或初始化目标场景的唯一标识符。
+ /// 表示切换后的场景,如果是清空操作则为 null。
+ ///
+ public string? ToSceneKey { get; init; }
+
+ ///
+ /// 获取或初始化场景过渡类型。
+ /// 指示当前执行的是哪种场景切换操作(Push/Pop/Replace/Clear)。
+ ///
+ public SceneTransitionType TransitionType { get; init; }
+
+ ///
+ /// 获取或初始化场景进入参数。
+ /// 包含传递给新场景的初始化数据或上下文信息。
+ ///
+ public ISceneEnterParam? EnterParam { get; init; }
+
+ ///
+ /// 从上下文中获取指定键的值。
+ ///
+ /// 值的类型。
+ /// 上下文键。
+ /// 如果键不存在时返回的默认值。
+ /// 上下文中存储的值,如果键不存在则返回默认值。
+ public T Get(string key, T defaultValue = default!)
+ {
+ if (_context.TryGetValue(key, out var value) && value is T typedValue)
+ return typedValue;
+ return defaultValue;
+ }
+
+ ///
+ /// 设置上下文中指定键的值。
+ ///
+ /// 值的类型。
+ /// 上下文键。
+ /// 要存储的值。
+ public void Set(string key, T value)
+ {
+ if (key == null)
+ throw new ArgumentNullException(nameof(key));
+ _context[key] = value!;
+ }
+
+ ///
+ /// 尝试从上下文中获取指定键的值。
+ ///
+ /// 值的类型。
+ /// 上下文键。
+ /// 输出参数,如果键存在则包含对应的值。
+ /// 如果键存在且类型匹配则返回 true,否则返回 false。
+ public bool TryGet(string key, out T value)
+ {
+ if (_context.TryGetValue(key, out var obj) && obj is T typedValue)
+ {
+ value = typedValue;
+ return true;
+ }
+
+ value = default!;
+ return false;
+ }
+
+ ///
+ /// 检查上下文中是否存在指定的键。
+ ///
+ /// 上下文键。
+ /// 如果键存在则返回 true,否则返回 false。
+ public bool Has(string key)
+ {
+ return _context.ContainsKey(key);
+ }
+
+ ///
+ /// 从上下文中移除指定的键。
+ ///
+ /// 上下文键。
+ /// 如果键存在并成功移除则返回 true,否则返回 false。
+ public bool Remove(string key)
+ {
+ return _context.Remove(key);
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/scene/SceneTransitionHandlerOptions.cs b/GFramework.Game.Abstractions/scene/SceneTransitionHandlerOptions.cs
new file mode 100644
index 0000000..d6c6fdd
--- /dev/null
+++ b/GFramework.Game.Abstractions/scene/SceneTransitionHandlerOptions.cs
@@ -0,0 +1,32 @@
+// Copyright (c) 2026 GeWuYou
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+namespace GFramework.Game.Abstractions.scene;
+
+///
+/// 场景过渡处理器选项,定义了处理器执行时的配置参数。
+///
+///
+/// 处理器执行的超时时间(毫秒)。
+/// 设置为 0 表示无超时限制。
+/// 如果处理器执行超过此时间,将触发超时异常。
+///
+///
+/// 当处理器执行失败时是否继续执行后续处理器。
+/// true 表示即使当前处理器失败,管道仍会继续执行后续处理器。
+/// false 表示当前处理器失败时,管道会立即停止并抛出异常。
+///
+public record SceneTransitionHandlerOptions(
+ int TimeoutMs = 0,
+ bool ContinueOnError = true
+);
\ No newline at end of file
diff --git a/GFramework.Game/scene/SceneRouterBase.cs b/GFramework.Game/scene/SceneRouterBase.cs
index 9b884a5..04052a2 100644
--- a/GFramework.Game/scene/SceneRouterBase.cs
+++ b/GFramework.Game/scene/SceneRouterBase.cs
@@ -12,8 +12,10 @@
// limitations under the License.
using GFramework.Core.Abstractions.logging;
+using GFramework.Core.extensions;
using GFramework.Core.logging;
using GFramework.Core.system;
+using GFramework.Game.Abstractions.enums;
using GFramework.Game.Abstractions.scene;
namespace GFramework.Game.scene;
@@ -28,20 +30,43 @@ public abstract class SceneRouterBase
private static readonly ILogger Log =
LoggerFactoryResolver.Provider.CreateLogger(nameof(SceneRouterBase));
+ private readonly List _guards = new();
+ private readonly SceneTransitionPipeline _pipeline = new();
+
private readonly Stack _stack = new();
private readonly SemaphoreSlim _transitionLock = new(1, 1);
+ private ISceneFactory _factory = null!;
+ ///
+ /// 场景根节点
+ ///
protected ISceneRoot? Root;
+ ///
+ /// 获取当前场景行为对象。
+ ///
public ISceneBehavior? Current => _stack.Count > 0 ? _stack.Peek() : null;
+ ///
+ /// 获取当前场景的键名。
+ ///
public string? CurrentKey => Current?.Key;
+ ///
+ /// 获取场景栈的只读列表,按压入顺序排列。
+ ///
public IReadOnlyList Stack =>
_stack.Reverse().ToList();
+ ///
+ /// 获取是否正在进行场景转换。
+ ///
public bool IsTransitioning { get; private set; }
+ ///
+ /// 绑定场景根节点。
+ ///
+ /// 场景根节点实例。
public void BindRoot(ISceneRoot root)
{
Root = root;
@@ -50,6 +75,12 @@ public abstract class SceneRouterBase
#region Replace
+ ///
+ /// 替换当前场景为指定场景。
+ ///
+ /// 目标场景键名。
+ /// 场景进入参数。
+ /// 异步任务。
public async ValueTask ReplaceAsync(
string sceneKey,
ISceneEnterParam? param = null)
@@ -59,10 +90,12 @@ public abstract class SceneRouterBase
{
IsTransitioning = true;
- Log.Debug("Replace Scene: {0}", sceneKey);
+ var @event = CreateEvent(sceneKey, SceneTransitionType.Replace, param);
+ await BeforeChangeAsync(@event);
await ClearInternalAsync();
await PushInternalAsync(sceneKey, param);
+ AfterChange(@event);
}
finally
{
@@ -75,6 +108,11 @@ public abstract class SceneRouterBase
#region Query
+ ///
+ /// 检查指定场景是否在栈中。
+ ///
+ /// 场景键名。
+ /// 如果场景在栈中返回true,否则返回false。
public bool Contains(string sceneKey)
{
return _stack.Any(s => s.Key == sceneKey);
@@ -82,8 +120,84 @@ public abstract class SceneRouterBase
#endregion
+ ///
+ /// 注册场景过渡处理器。
+ ///
+ /// 处理器实例。
+ /// 执行选项。
+ public void RegisterHandler(ISceneTransitionHandler handler, SceneTransitionHandlerOptions? options = null)
+ {
+ _pipeline.RegisterHandler(handler, options);
+ }
+
+ ///
+ /// 注销场景过渡处理器。
+ ///
+ /// 处理器实例。
+ public void UnregisterHandler(ISceneTransitionHandler handler)
+ {
+ _pipeline.UnregisterHandler(handler);
+ }
+
+ ///
+ /// 添加场景路由守卫。
+ ///
+ /// 守卫实例。
+ public void AddGuard(ISceneRouteGuard guard)
+ {
+ ArgumentNullException.ThrowIfNull(guard);
+ if (!_guards.Contains(guard))
+ {
+ _guards.Add(guard);
+ _guards.Sort((a, b) => a.Priority.CompareTo(b.Priority));
+ Log.Debug("Guard added: {0}, Priority={1}", guard.GetType().Name, guard.Priority);
+ }
+ }
+
+ ///
+ /// 添加场景路由守卫(泛型版本)。
+ ///
+ /// 守卫类型。
+ public void AddGuard() where T : ISceneRouteGuard, new()
+ {
+ AddGuard(new T());
+ }
+
+ ///
+ /// 移除场景路由守卫。
+ ///
+ /// 守卫实例。
+ public void RemoveGuard(ISceneRouteGuard guard)
+ {
+ if (_guards.Remove(guard))
+ {
+ Log.Debug("Guard removed: {0}", guard.GetType().Name);
+ }
+ }
+
+ ///
+ /// 注册场景过渡处理器的抽象方法,由子类实现。
+ ///
+ protected abstract void RegisterHandlers();
+
+ ///
+ /// 系统初始化方法,获取场景工厂并注册处理器。
+ ///
+ protected override void OnInit()
+ {
+ _factory = this.GetUtility()!;
+ Log.Debug("SceneRouterBase initialized. Factory={0}", _factory.GetType().Name);
+ RegisterHandlers();
+ }
+
#region Push
+ ///
+ /// 将指定场景推入栈顶。
+ ///
+ /// 目标场景键名。
+ /// 场景进入参数。
+ /// 异步任务。
public async ValueTask PushAsync(
string sceneKey,
ISceneEnterParam? param = null)
@@ -92,7 +206,12 @@ public abstract class SceneRouterBase
try
{
IsTransitioning = true;
+
+ var @event = CreateEvent(sceneKey, SceneTransitionType.Push, param);
+
+ await BeforeChangeAsync(@event);
await PushInternalAsync(sceneKey, param);
+ AfterChange(@event);
}
finally
{
@@ -101,6 +220,13 @@ public abstract class SceneRouterBase
}
}
+ ///
+ /// 内部推送场景实现方法。
+ /// 执行守卫检查、场景加载、暂停当前场景、压入栈等操作。
+ ///
+ /// 场景键名。
+ /// 场景进入参数。
+ /// 异步任务。
private async ValueTask PushInternalAsync(
string sceneKey,
ISceneEnterParam? param)
@@ -111,18 +237,31 @@ public abstract class SceneRouterBase
return;
}
+ // 守卫检查
+ if (!await ExecuteEnterGuardsAsync(sceneKey, param))
+ {
+ Log.Warn("Push blocked by guard: {0}", sceneKey);
+ return;
+ }
+
+ // 通过 Root 加载场景(Root.LoadAsync 返回 ISceneBehavior)
var scene = await Root!.LoadAsync(sceneKey);
+ // 加载资源
+ await scene.OnLoadAsync(param);
+
+ // 暂停当前场景
if (_stack.Count > 0)
{
var current = _stack.Peek();
await current.OnPauseAsync();
}
+ // 压入栈
_stack.Push(scene);
- await scene.OnEnterAsync(param);
- await scene.OnShowAsync();
+ // 进入场景
+ await scene.OnEnterAsync();
Log.Debug("Push Scene: {0}, stackCount={1}",
sceneKey, _stack.Count);
@@ -132,13 +271,22 @@ public abstract class SceneRouterBase
#region Pop
+ ///
+ /// 弹出栈顶场景。
+ ///
+ /// 异步任务。
public async ValueTask PopAsync()
{
await _transitionLock.WaitAsync();
try
{
IsTransitioning = true;
+
+ var @event = CreateEvent(null, SceneTransitionType.Pop);
+
+ await BeforeChangeAsync(@event);
await PopInternalAsync();
+ AfterChange(@event);
}
finally
{
@@ -147,21 +295,41 @@ public abstract class SceneRouterBase
}
}
+ ///
+ /// 内部弹出场景实现方法。
+ /// 执行守卫检查、退出场景、卸载资源、恢复下一个场景等操作。
+ ///
+ /// 异步任务。
private async ValueTask PopInternalAsync()
{
if (_stack.Count == 0)
return;
- var top = _stack.Pop();
+ var top = _stack.Peek();
+ // 守卫检查
+ if (!await ExecuteLeaveGuardsAsync(top.Key))
+ {
+ Log.Warn("Pop blocked by guard: {0}", top.Key);
+ return;
+ }
+
+ _stack.Pop();
+
+ // 退出场景
await top.OnExitAsync();
+
+ // 卸载资源
+ await top.OnUnloadAsync();
+
+ // 从场景树卸载
await Root!.UnloadAsync(top);
+ // 恢复下一个场景
if (_stack.Count > 0)
{
var next = _stack.Peek();
await next.OnResumeAsync();
- await next.OnShowAsync();
}
Log.Debug("Pop Scene, stackCount={0}", _stack.Count);
@@ -171,13 +339,22 @@ public abstract class SceneRouterBase
#region Clear
+ ///
+ /// 清空所有场景栈。
+ ///
+ /// 异步任务。
public async ValueTask ClearAsync()
{
await _transitionLock.WaitAsync();
try
{
IsTransitioning = true;
+
+ var @event = CreateEvent(null, SceneTransitionType.Clear);
+
+ await BeforeChangeAsync(@event);
await ClearInternalAsync();
+ AfterChange(@event);
}
finally
{
@@ -186,6 +363,11 @@ public abstract class SceneRouterBase
}
}
+ ///
+ /// 内部清空场景栈实现方法。
+ /// 循环调用弹出操作直到栈为空。
+ ///
+ /// 异步任务。
private async ValueTask ClearInternalAsync()
{
while (_stack.Count > 0)
@@ -195,4 +377,141 @@ public abstract class SceneRouterBase
}
#endregion
+
+ #region Helper Methods
+
+ ///
+ /// 创建场景转换事件对象。
+ ///
+ /// 目标场景键名。
+ /// 转换类型。
+ /// 进入参数。
+ /// 场景转换事件实例。
+ private SceneTransitionEvent CreateEvent(
+ string? toSceneKey,
+ SceneTransitionType type,
+ ISceneEnterParam? param = null)
+ {
+ return new SceneTransitionEvent
+ {
+ FromSceneKey = CurrentKey,
+ ToSceneKey = toSceneKey,
+ TransitionType = type,
+ EnterParam = param
+ };
+ }
+
+ ///
+ /// 执行转换前阶段的处理逻辑。
+ ///
+ /// 场景转换事件。
+ /// 异步任务。
+ private async Task BeforeChangeAsync(SceneTransitionEvent @event)
+ {
+ Log.Debug("BeforeChange phases started: {0}", @event.TransitionType);
+ await _pipeline.ExecuteAsync(@event, SceneTransitionPhases.BeforeChange);
+ Log.Debug("BeforeChange phases completed: {0}", @event.TransitionType);
+ }
+
+ ///
+ /// 执行转换后阶段的处理逻辑。
+ /// 在后台线程中异步执行,避免阻塞主线程。
+ ///
+ /// 场景转换事件。
+ private void AfterChange(SceneTransitionEvent @event)
+ {
+ Log.Debug("AfterChange phases started: {0}", @event.TransitionType);
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ await _pipeline.ExecuteAsync(@event, SceneTransitionPhases.AfterChange);
+ Log.Debug("AfterChange phases completed: {0}", @event.TransitionType);
+ }
+ catch (Exception ex)
+ {
+ Log.Error("AfterChange phases failed: {0}, Error: {1}",
+ @event.TransitionType, ex.Message);
+ }
+ });
+ }
+
+ ///
+ /// 执行进入场景的守卫检查。
+ /// 按优先级顺序执行所有守卫的CanEnterAsync方法。
+ ///
+ /// 场景键名。
+ /// 进入参数。
+ /// 如果所有守卫都允许进入返回true,否则返回false。
+ private async Task ExecuteEnterGuardsAsync(string sceneKey, ISceneEnterParam? param)
+ {
+ foreach (var guard in _guards)
+ {
+ try
+ {
+ Log.Debug("Executing enter guard: {0} for {1}", guard.GetType().Name, sceneKey);
+ var canEnter = await guard.CanEnterAsync(sceneKey, param);
+
+ if (!canEnter)
+ {
+ Log.Debug("Enter guard blocked: {0}", guard.GetType().Name);
+ return false;
+ }
+
+ if (guard.CanInterrupt)
+ {
+ Log.Debug("Enter guard {0} passed, can interrupt = true", guard.GetType().Name);
+ return true;
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Enter guard {0} failed: {1}", guard.GetType().Name, ex.Message);
+ if (guard.CanInterrupt)
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// 执行离开场景的守卫检查。
+ /// 按优先级顺序执行所有守卫的CanLeaveAsync方法。
+ ///
+ /// 场景键名。
+ /// 如果所有守卫都允许离开返回true,否则返回false。
+ private async Task ExecuteLeaveGuardsAsync(string sceneKey)
+ {
+ foreach (var guard in _guards)
+ {
+ try
+ {
+ Log.Debug("Executing leave guard: {0} for {1}", guard.GetType().Name, sceneKey);
+ var canLeave = await guard.CanLeaveAsync(sceneKey);
+
+ if (!canLeave)
+ {
+ Log.Debug("Leave guard blocked: {0}", guard.GetType().Name);
+ return false;
+ }
+
+ if (guard.CanInterrupt)
+ {
+ Log.Debug("Leave guard {0} passed, can interrupt = true", guard.GetType().Name);
+ return true;
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Leave guard {0} failed: {1}", guard.GetType().Name, ex.Message);
+ if (guard.CanInterrupt)
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/GFramework.Game/scene/SceneTransitionPipeline.cs b/GFramework.Game/scene/SceneTransitionPipeline.cs
new file mode 100644
index 0000000..61a73c2
--- /dev/null
+++ b/GFramework.Game/scene/SceneTransitionPipeline.cs
@@ -0,0 +1,182 @@
+// Copyright (c) 2026 GeWuYou
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using GFramework.Core.Abstractions.logging;
+using GFramework.Core.logging;
+using GFramework.Game.Abstractions.enums;
+using GFramework.Game.Abstractions.scene;
+
+namespace GFramework.Game.scene;
+
+///
+/// 场景过渡处理器管道,负责管理和执行场景切换扩展点。
+/// 提供了处理器的注册、注销和按优先级顺序执行的功能。
+///
+public class SceneTransitionPipeline
+{
+ private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger("SceneTransitionPipeline");
+ private readonly List _handlers = [];
+ private readonly Dictionary _options = new();
+
+ ///
+ /// 注册场景过渡处理器。
+ ///
+ /// 处理器实例。
+ /// 执行选项,如果为 null 则使用默认选项。
+ public void RegisterHandler(ISceneTransitionHandler handler, SceneTransitionHandlerOptions? options = null)
+ {
+ ArgumentNullException.ThrowIfNull(handler);
+
+ if (_handlers.Contains(handler))
+ {
+ Log.Debug("Handler already registered: {0}", handler.GetType().Name);
+ return;
+ }
+
+ _handlers.Add(handler);
+ _options[handler] = options ?? new SceneTransitionHandlerOptions();
+ Log.Debug(
+ "Handler registered: {0}, Priority={1}, Phases={2}, TimeoutMs={3}",
+ handler.GetType().Name,
+ handler.Priority,
+ handler.Phases,
+ _options[handler].TimeoutMs
+ );
+ }
+
+ ///
+ /// 注销场景过渡处理器。
+ ///
+ /// 处理器实例。
+ public void UnregisterHandler(ISceneTransitionHandler handler)
+ {
+ ArgumentNullException.ThrowIfNull(handler);
+
+ if (!_handlers.Remove(handler)) return;
+ _options.Remove(handler);
+ Log.Debug("Handler unregistered: {0}", handler.GetType().Name);
+ }
+
+ ///
+ /// 执行指定阶段的所有处理器。
+ ///
+ /// 场景过渡事件。
+ /// 执行阶段。
+ /// 取消令牌。
+ /// 异步任务。
+ public async Task ExecuteAsync(
+ SceneTransitionEvent @event,
+ SceneTransitionPhases phases,
+ CancellationToken cancellationToken = default
+ )
+ {
+ @event.Set("Phases", phases.ToString());
+
+ Log.Debug(
+ "Execute pipeline: Phases={0}, From={1}, To={2}, Type={3}, HandlerCount={4}",
+ phases,
+ @event.FromSceneKey,
+ @event.ToSceneKey ?? "None",
+ @event.TransitionType,
+ _handlers.Count
+ );
+
+ var sortedHandlers = FilterAndSortHandlers(@event, phases);
+
+ if (sortedHandlers.Count == 0)
+ {
+ Log.Debug("No handlers to execute for phases: {0}", phases);
+ return;
+ }
+
+ Log.Debug(
+ "Executing {0} handlers for phases {1}",
+ sortedHandlers.Count,
+ phases
+ );
+
+ foreach (var handler in sortedHandlers)
+ {
+ var options = _options[handler];
+ await ExecuteSingleHandlerAsync(handler, options, @event, cancellationToken);
+ }
+
+ Log.Debug("Pipeline execution completed for phases: {0}", phases);
+ }
+
+ private List FilterAndSortHandlers(
+ SceneTransitionEvent @event,
+ SceneTransitionPhases phases)
+ {
+ return _handlers
+ .Where(h => h.Phases.HasFlag(phases) && h.ShouldHandle(@event, phases))
+ .OrderBy(h => h.Priority)
+ .ToList();
+ }
+
+ private static async Task ExecuteSingleHandlerAsync(
+ ISceneTransitionHandler handler,
+ SceneTransitionHandlerOptions options,
+ SceneTransitionEvent @event,
+ CancellationToken cancellationToken)
+ {
+ Log.Debug(
+ "Executing handler: {0}, Priority={1}",
+ handler.GetType().Name,
+ handler.Priority
+ );
+
+ try
+ {
+ using var timeoutCts = options.TimeoutMs > 0
+ ? new CancellationTokenSource(options.TimeoutMs)
+ : null;
+
+ using var linkedCts = timeoutCts != null && cancellationToken.CanBeCanceled
+ ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token)
+ : null;
+
+ await handler.HandleAsync(
+ @event,
+ linkedCts?.Token ?? cancellationToken
+ ).ConfigureAwait(false);
+
+ Log.Debug("Handler completed: {0}", handler.GetType().Name);
+ }
+ catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
+ {
+ Log.Error(
+ "Handler timeout: {0}, TimeoutMs={1}",
+ handler.GetType().Name,
+ options.TimeoutMs
+ );
+
+ if (options.ContinueOnError) return;
+ Log.Error("Stopping pipeline due to timeout and ContinueOnError=false");
+ throw;
+ }
+ catch (OperationCanceledException)
+ {
+ Log.Debug("Handler cancelled: {0}", handler.GetType().Name);
+ throw;
+ }
+ catch (Exception ex)
+ {
+ Log.Error("Handler failed: {0}, Error: {1}", handler.GetType().Name, ex.Message);
+
+ if (options.ContinueOnError) return;
+ Log.Error("Stopping pipeline due to error and ContinueOnError=false");
+ throw;
+ }
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Game/scene/handler/LoadingProgressHandler.cs b/GFramework.Game/scene/handler/LoadingProgressHandler.cs
new file mode 100644
index 0000000..95b33d8
--- /dev/null
+++ b/GFramework.Game/scene/handler/LoadingProgressHandler.cs
@@ -0,0 +1,61 @@
+// Copyright (c) 2026 GeWuYou
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using GFramework.Core.Abstractions.logging;
+using GFramework.Core.logging;
+using GFramework.Game.Abstractions.enums;
+using GFramework.Game.Abstractions.scene;
+
+namespace GFramework.Game.scene.handler;
+
+///
+/// 加载进度处理器,用于在场景加载前显示加载界面和进度反馈。
+///
+public sealed class LoadingProgressHandler : SceneTransitionHandlerBase
+{
+ private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger(nameof(LoadingProgressHandler));
+
+ ///
+ /// 获取处理器优先级,数值越小优先级越高(优先执行)。
+ ///
+ public override int Priority => -100;
+
+ ///
+ /// 获取处理器处理的场景切换阶段,只处理 BeforeChange 阶段。
+ ///
+ public override SceneTransitionPhases Phases => SceneTransitionPhases.BeforeChange;
+
+ ///
+ /// 处理场景切换事件的异步方法。
+ ///
+ /// 场景切换事件对象,包含切换的相关信息。
+ /// 取消令牌,用于控制异步操作的取消。
+ /// 表示异步操作的任务。
+ public override async Task HandleAsync(SceneTransitionEvent @event, CancellationToken cancellationToken)
+ {
+ // 只处理 Push 和 Replace 操作(需要加载场景)
+ if (@event.TransitionType != SceneTransitionType.Push &&
+ @event.TransitionType != SceneTransitionType.Replace)
+ return;
+
+ // 显示加载界面
+ // TODO: 调用 UI 系统显示加载界面
+ Log.Info("Loading scene: {0}", @event.ToSceneKey);
+
+ // 监听加载进度(如果需要)
+ // 这里可以通过事件上下文传递进度信息
+ // 例如:@event.Set("LoadingProgress", progressValue);
+
+ await Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Game/scene/handler/LoggingTransitionHandler.cs b/GFramework.Game/scene/handler/LoggingTransitionHandler.cs
new file mode 100644
index 0000000..d8afbc6
--- /dev/null
+++ b/GFramework.Game/scene/handler/LoggingTransitionHandler.cs
@@ -0,0 +1,56 @@
+// Copyright (c) 2026 GeWuYou
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using GFramework.Core.Abstractions.logging;
+using GFramework.Core.logging;
+using GFramework.Game.Abstractions.enums;
+using GFramework.Game.Abstractions.scene;
+
+namespace GFramework.Game.scene.handler;
+
+///
+/// 日志场景切换处理器,用于记录场景切换的详细信息。
+///
+public sealed class LoggingTransitionHandler : SceneTransitionHandlerBase
+{
+ private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger(nameof(LoggingTransitionHandler));
+
+ ///
+ /// 获取处理器优先级,数值越大优先级越低(最后执行)。
+ ///
+ public override int Priority => 999;
+
+ ///
+ /// 获取处理器处理的场景切换阶段,处理所有阶段。
+ ///
+ public override SceneTransitionPhases Phases => SceneTransitionPhases.All;
+
+ ///
+ /// 处理场景切换事件的异步方法。
+ ///
+ /// 场景切换事件对象,包含切换的相关信息。
+ /// 取消令牌,用于控制异步操作的取消。
+ /// 表示异步操作的任务。
+ public override Task HandleAsync(SceneTransitionEvent @event, CancellationToken cancellationToken)
+ {
+ Log.Info(
+ "Scene Transition: Phases={0}, Type={1}, From={2}, To={3}",
+ @event.Get("Phases", "Unknown"),
+ @event.TransitionType,
+ @event.FromSceneKey ?? "None",
+ @event.ToSceneKey ?? "None"
+ );
+
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Game/scene/handler/SceneTransitionHandlerBase.cs b/GFramework.Game/scene/handler/SceneTransitionHandlerBase.cs
new file mode 100644
index 0000000..d83f814
--- /dev/null
+++ b/GFramework.Game/scene/handler/SceneTransitionHandlerBase.cs
@@ -0,0 +1,55 @@
+// Copyright (c) 2026 GeWuYou
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using GFramework.Game.Abstractions.enums;
+using GFramework.Game.Abstractions.scene;
+
+namespace GFramework.Game.scene.handler;
+
+///
+/// 场景过渡处理器抽象基类,提供了 ISceneTransitionHandler 接口的默认实现。
+/// 派生类只需重写必要的方法即可快速实现自定义处理器。
+///
+public abstract class SceneTransitionHandlerBase : ISceneTransitionHandler
+{
+ ///
+ /// 获取处理器适用的场景过渡阶段。
+ /// 默认为所有阶段(BeforeChange 和 AfterChange)。
+ ///
+ public virtual SceneTransitionPhases Phases => SceneTransitionPhases.All;
+
+ ///
+ /// 获取处理器的执行优先级。
+ /// 派生类必须实现此属性以指定优先级。
+ ///
+ public abstract int Priority { get; }
+
+ ///
+ /// 判断处理器是否应该处理当前场景过渡事件。
+ /// 默认实现总是返回 true,派生类可以重写此方法以添加自定义过滤逻辑。
+ ///
+ /// 场景过渡事件。
+ /// 当前执行的阶段。
+ /// 默认返回 true,表示总是处理。
+ public virtual bool ShouldHandle(SceneTransitionEvent @event, SceneTransitionPhases phases)
+ => true;
+
+ ///
+ /// 异步处理场景过渡事件。
+ /// 派生类必须实现此方法以定义具体的处理逻辑。
+ ///
+ /// 场景过渡事件,包含切换的上下文信息。
+ /// 取消令牌,用于支持操作取消。
+ /// 表示处理操作完成的异步任务。
+ public abstract Task HandleAsync(SceneTransitionEvent @event, CancellationToken cancellationToken);
+}
\ No newline at end of file