Files
mrgameeng/src/MrGameEng.Core/GameSpeed.cs
T
Leonid Pershin ef1111bcb6
CI / build-test (push) Successful in 1m10s
Enhance audio management and documentation
Added a MasterVolume property to the AudioManager for unified control over sound effects and music volume. Updated the Play method to incorporate MasterVolume adjustments. Enhanced documentation in CLAUDE.md and architecture.md to reflect changes in audio management and overall engine architecture. Introduced a new test project for audio functionalities in the solution file.
2026-06-12 08:41:39 +03:00

145 lines
4.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
namespace MrGameEng.Core;
/// <summary>
/// Discrete game-speed control layered over <see cref="GameClock.TimeScale"/>: a pause plus an
/// ordered list of speed multipliers (1×, 3×, 6× by default). Pausing remembers the current
/// running step so <see cref="Resume"/> restores it. <see cref="Changed"/> fires on every
/// transition so UI (speed buttons, indicators) can refresh. Deterministic and GPU-free;
/// registered as a service via <see cref="GameSpeedEngineExtensions.UseGameSpeed"/>.
/// </summary>
public sealed class GameSpeed
{
private readonly GameClock _clock;
private readonly float[] _steps;
private int _stepIndex;
private bool _paused;
/// <summary>
/// Creates a controller that writes <see cref="CurrentSpeed"/> to
/// <paramref name="clock"/>. <paramref name="steps"/> are the running multipliers in
/// ascending order; each must be positive. Empty defaults to 1×, 3×, 6×.
/// </summary>
public GameSpeed(GameClock clock, params float[] steps)
{
_clock = clock ?? throw new ArgumentNullException(nameof(clock));
_steps = steps is { Length: > 0 } ? (float[])steps.Clone() : [1f, 3f, 6f];
foreach (var step in _steps)
{
if (step <= 0f)
{
throw new ArgumentOutOfRangeException(
nameof(steps),
"Speed steps must be positive."
);
}
}
Apply();
}
/// <summary>The ordered running speeds (excludes the pause state).</summary>
public IReadOnlyList<float> Steps => _steps;
/// <summary>Index of the active running step within <see cref="Steps"/>.</summary>
public int StepIndex => _stepIndex;
/// <summary>True while gameplay is paused (clock time scale is 0).</summary>
public bool IsPaused => _paused;
/// <summary>Active multiplier: 0 while paused, otherwise <c>Steps[StepIndex]</c>.</summary>
public float CurrentSpeed => _paused ? 0f : _steps[_stepIndex];
/// <summary>Raised after any change to the pause state or the active step.</summary>
public event Action? Changed;
/// <summary>Pauses gameplay, remembering the current step for <see cref="Resume"/>.</summary>
public void Pause()
{
if (_paused)
{
return;
}
_paused = true;
Apply();
}
/// <summary>Resumes gameplay at the remembered step.</summary>
public void Resume()
{
if (!_paused)
{
return;
}
_paused = false;
Apply();
}
/// <summary>Toggles between paused and running.</summary>
public void TogglePause()
{
_paused = !_paused;
Apply();
}
/// <summary>Selects a running step by index (clamped to the valid range) and unpauses.</summary>
public void SetStep(int index)
{
_stepIndex = Math.Clamp(index, 0, _steps.Length - 1);
_paused = false;
Apply();
}
/// <summary>Steps to the next faster speed (clamped to the fastest) and unpauses.</summary>
public void Faster() => SetStep(_stepIndex + 1);
/// <summary>Steps to the next slower speed (clamped to the slowest) and unpauses.</summary>
public void Slower() => SetStep(_stepIndex - 1);
/// <summary>
/// Cycles through states: pause → slowest step → … → fastest step → pause. Handy for a
/// single "next speed" key or button.
/// </summary>
public void Cycle()
{
if (_paused)
{
_paused = false;
_stepIndex = 0;
}
else if (_stepIndex + 1 < _steps.Length)
{
_stepIndex++;
}
else
{
_paused = true;
}
Apply();
}
private void Apply()
{
_clock.TimeScale = CurrentSpeed;
Changed?.Invoke();
}
}
/// <summary>Wires the game-speed controller into the engine.</summary>
public static class GameSpeedEngineExtensions
{
/// <summary>
/// Creates a <see cref="GameSpeed"/> bound to the context's clock and registers it as a
/// service. Call once at startup. <paramref name="steps"/> are the running multipliers
/// (defaults to 1×, 3×, 6× when empty).
/// </summary>
public static GameSpeed UseGameSpeed(this EngineContext context, params float[] steps)
{
var speed = new GameSpeed(context.Clock, steps);
context.Services.Add(speed);
return speed;
}
}