CI / build-test (push) Failing after 1m8s
Add MrGameEng.AI utility-AI module; format codebase with CSharpier New MrGameEng.AI module (ResponseCurve, Consideration, UtilityAction, UtilityAi selector, Blackboard) plus CSharpier formatting applied across the whole engine. Documents the CSharpier convention in CLAUDE.md. @
150 lines
4.5 KiB
C#
150 lines
4.5 KiB
C#
namespace MrGameEng.Core;
|
|
|
|
/// <summary>
|
|
/// 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"/>. 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; a switch requested while a transition is revealing starts
|
|
/// covering again from the current coverage.
|
|
/// </summary>
|
|
public void Switch(Scene? scene, Transition? transition = null)
|
|
{
|
|
_pending = scene;
|
|
_hasPending = true;
|
|
|
|
if (transition is not null && _state != State.CoveringOut)
|
|
{
|
|
_transition = transition;
|
|
if (_state == State.Idle)
|
|
{
|
|
_coverage = 0f;
|
|
}
|
|
|
|
// Из RevealingIn закрытие продолжается с текущего coverage — без скачка.
|
|
_state = State.CoveringOut;
|
|
}
|
|
}
|
|
|
|
/// <summary>Advances a transition and updates the active scene. Called by the host.</summary>
|
|
public void Update(GameClock clock)
|
|
{
|
|
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 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);
|
|
}
|
|
|
|
/// <summary>Disposes the lazily created transition renderer. Called on host shutdown.</summary>
|
|
internal void DisposeRenderer()
|
|
{
|
|
_renderer?.Dispose();
|
|
_renderer = null;
|
|
}
|
|
|
|
internal void ApplyPending()
|
|
{
|
|
if (!_hasPending)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_hasPending = false;
|
|
Current?.Unload();
|
|
Current = _pending;
|
|
_pending = null;
|
|
Current?.Load(_context);
|
|
Log.Info($"Scene switched to {Current?.GetType().Name ?? "<none>"}");
|
|
}
|
|
|
|
private static float Advance(
|
|
float coverage,
|
|
float direction,
|
|
float duration,
|
|
float deltaTime
|
|
) =>
|
|
duration <= 0f
|
|
? coverage + direction
|
|
: Math.Clamp(coverage + direction * deltaTime / duration, 0f, 1f);
|
|
}
|