Add scene transitions (fade, wipe) to SceneManager
Switch(scene, transition) covers the old scene, swaps at full coverage (hiding slow OnLoad), then reveals the new one. Built-in Fade and Wipe transitions draw through TransitionRenderer (BasicEffect quad, no SpriteBatch); custom transitions subclass Transition. Runs on unscaled time so it works while gameplay is paused; Switch during a transition replaces the pending target. Sample: Tab now fades into the stress scene and wipes back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
ff2231a8ab
commit
2b7d4c4fef
@@ -1,39 +1,103 @@
|
||||
namespace MrGameEng.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Owns the active <see cref="Scene"/>. Scene switches are deferred to the start of the
|
||||
/// next update so a scene is never unloaded in the middle of its own frame.
|
||||
/// Owns the active <see cref="Scene"/>. Plain switches are deferred to the start of the next
|
||||
/// update so a scene is never unloaded in the middle of its own frame. Switches with a
|
||||
/// <see cref="Transition"/> first cover the old scene, swap at full coverage (hiding even a
|
||||
/// slow <c>OnLoad</c>), then reveal the new one. Transition time is unscaled, so it works
|
||||
/// while gameplay is paused.
|
||||
/// </summary>
|
||||
public sealed class SceneManager
|
||||
{
|
||||
private enum State
|
||||
{
|
||||
Idle,
|
||||
CoveringOut,
|
||||
RevealingIn,
|
||||
}
|
||||
|
||||
/// <summary>The active scene, or null before the first switch is applied.</summary>
|
||||
public Scene? Current { get; private set; }
|
||||
|
||||
/// <summary>True while a transition is covering or revealing.</summary>
|
||||
public bool IsTransitioning => _state != State.Idle;
|
||||
|
||||
private readonly EngineContext _context;
|
||||
private Scene? _pending;
|
||||
private bool _hasPending;
|
||||
private Transition? _transition;
|
||||
private State _state;
|
||||
private float _coverage;
|
||||
private TransitionRenderer? _renderer;
|
||||
|
||||
internal SceneManager(EngineContext context) => _context = context;
|
||||
|
||||
/// <summary>
|
||||
/// Requests a switch to <paramref name="scene"/>. The current scene is unloaded and the new
|
||||
/// one loaded at the start of the next update tick. Passing null unloads the current scene.
|
||||
/// Requests a switch to <paramref name="scene"/>. Without a transition the swap happens at
|
||||
/// the start of the next update tick; with one, the old scene is first covered.
|
||||
/// Passing null unloads the current scene. Calling during an active transition replaces
|
||||
/// the pending target scene.
|
||||
/// </summary>
|
||||
public void Switch(Scene? scene)
|
||||
public void Switch(Scene? scene, Transition? transition = null)
|
||||
{
|
||||
_pending = scene;
|
||||
_hasPending = true;
|
||||
|
||||
if (_state == State.Idle && transition is not null)
|
||||
{
|
||||
_transition = transition;
|
||||
_state = State.CoveringOut;
|
||||
_coverage = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Applies a pending switch, then updates the active scene. Called by the host.</summary>
|
||||
/// <summary>Advances a transition and updates the active scene. Called by the host.</summary>
|
||||
public void Update(GameClock clock)
|
||||
{
|
||||
ApplyPending();
|
||||
switch (_state)
|
||||
{
|
||||
case State.Idle:
|
||||
ApplyPending();
|
||||
break;
|
||||
|
||||
case State.CoveringOut:
|
||||
_coverage = Advance(_coverage, +1f, _transition!.OutDuration, clock.UnscaledDeltaTime);
|
||||
if (_coverage >= 1f)
|
||||
{
|
||||
ApplyPending();
|
||||
_state = State.RevealingIn;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case State.RevealingIn:
|
||||
_coverage = Advance(_coverage, -1f, _transition!.InDuration, clock.UnscaledDeltaTime);
|
||||
if (_coverage <= 0f)
|
||||
{
|
||||
_state = State.Idle;
|
||||
_transition = null;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Current?.Update(clock);
|
||||
}
|
||||
|
||||
/// <summary>Draws the active scene. Called by the host.</summary>
|
||||
public void Draw(GameClock clock) => Current?.Draw(clock);
|
||||
/// <summary>Draws the active scene and the transition overlay on top. Called by the host.</summary>
|
||||
public void Draw(GameClock clock)
|
||||
{
|
||||
Current?.Draw(clock);
|
||||
|
||||
if (_state == State.Idle || !_context.HasGraphicsDevice)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_renderer ??= new TransitionRenderer(_context.GraphicsDevice);
|
||||
var phase = _state == State.CoveringOut ? TransitionPhase.Out : TransitionPhase.In;
|
||||
_transition!.Draw(_renderer, Math.Clamp(_coverage, 0f, 1f), phase);
|
||||
}
|
||||
|
||||
internal void ApplyPending()
|
||||
{
|
||||
@@ -48,4 +112,9 @@ public sealed class SceneManager
|
||||
_pending = null;
|
||||
Current?.Load(_context);
|
||||
}
|
||||
|
||||
private static float Advance(float coverage, float direction, float duration, float deltaTime) =>
|
||||
duration <= 0f
|
||||
? coverage + direction
|
||||
: Math.Clamp(coverage + direction * deltaTime / duration, 0f, 1f);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user