diff --git a/TODO.md b/TODO.md index b910a3b..b4b0b81 100644 --- a/TODO.md +++ b/TODO.md @@ -13,9 +13,9 @@ - ~~Use GUIDs and string ID maps for fetching resources instead of string IDs alone.~~ - Reimplement unloading. - Finalize ResourceManager and ResourceLoader APIs for 1.0. +- Add async API for ResourceManager. - Virtual file system. - (stretch goal) Streamed resource loading. -- (stretch goal) Add async API for ResourceManager. ## Serialization diff --git a/TestGame/TestGame.cs b/TestGame/TestGame.cs index 70beeb0..78b0efc 100644 --- a/TestGame/TestGame.cs +++ b/TestGame/TestGame.cs @@ -7,25 +7,14 @@ using System.Numerics; using System.Diagnostics.CodeAnalysis; using System.Diagnostics; -public class TestGame : Game +public class TestGame : BaseGame { public override string ResourceRoot => "Resources/"; - public override void Initialize() - { - InitializeDefault(); - - _particleSystem = new ParticleSystem(); - - ResourceManager.AddResourceLoaderAssociation(new ParticleEmitterSettingsResourceLoader()); - - Input.AddInputMapping("reload", new InputAction[] { new KeyInputAction(KeyboardKey.R) }); - - _particleSimStopwatch = new Stopwatch(); - } - protected override void LoadResources() { + ResourceManager.AddResourceLoaderAssociation(new ParticleEmitterSettingsResourceLoader()); + if (!ResourceManager.TryLoad("fonts/Inter-Regular.ttf", out _font)) { @@ -41,60 +30,53 @@ public class TestGame : Game protected override void Ready() { + _particleSystem = new ParticleSystem(); + Input.AddInputMapping("reload", new InputAction[] { new KeyInputAction(KeyboardKey.R) }); + _particleSimStopwatch = new Stopwatch(); + _emitterId = _particleSystem.CreateEmitter(Renderer.WindowSize / 2, _fireEffect); } - protected override void Run() + + protected override void Update(double deltaTime) { - while (Renderer.ShouldRun) - { - if (Input.IsActionPressed("reload")) - { - ResourceManager.Reload(); - _particleSystem!.RestartEmitter(_emitterId); - } - - if (Input.KeyboardKeyJustPressed(KeyboardKey.One)) - { - _particleSystem.CreateEmitter(Input.GetMousePosition(), _fireEffect); - } - - if (Input.IsMouseButtonDown(MouseButton.Left)) - { - _particleSystem.SetEmitterPosition(_emitterId, Input.GetMousePosition()); - } - - _particleSimStopwatch = Stopwatch.StartNew(); - _particleSystem!.Update(Renderer.FrameTime); - _particleSimStopwatch.Stop(); - - _renderStopwatch = Stopwatch.StartNew(); - - Renderer.BeginFrame(); - Renderer.ClearBackground(Color.Black); - foreach (var emitter in _particleSystem!.Emitters) - { - DrawEmitter(emitter); - } - - Renderer.ResetTransform(); - Renderer.DrawText(_font, $"Particle Sim: {TimeSpan.FromTicks(_particleSimStopwatch.ElapsedTicks).TotalMilliseconds} ms", Color.White); - - Renderer.SetTransform(new Vector2(0.0f, 16.0f), Vector2.Zero); - Renderer.DrawText(_font, $"Render: {TimeSpan.FromTicks(_lastRenderTime).TotalMilliseconds} ms", Color.White); - - Renderer.EndFrame(); - - _lastRenderTime = _renderStopwatch.ElapsedTicks; - - _renderStopwatch.Restart(); - _particleSimStopwatch.Restart(); - } + _particleSimStopwatch = Stopwatch.StartNew(); + _particleSystem!.Update(deltaTime); + _particleSimStopwatch.Stop(); } - public override void Shutdown() + protected override void Render(double deltaTime) { - ShutdownDefault(); + if (Input.IsActionPressed("reload")) + { + ResourceManager.Reload(); + _particleSystem!.RestartEmitter(_emitterId); + } + + if (Input.KeyboardKeyJustPressed(KeyboardKey.One)) + { + _particleSystem.CreateEmitter(Input.GetMousePosition(), _fireEffect); + } + + if (Input.IsMouseButtonDown(MouseButton.Left)) + { + _particleSystem.SetEmitterPosition(_emitterId, Input.GetMousePosition()); + } + + Renderer.ClearBackground(Color.Black); + foreach (var emitter in _particleSystem!.Emitters) + { + DrawEmitter(emitter); + } + + Renderer.ResetTransform(); + Renderer.DrawText(_font, $"Particle Sim: {TimeSpan.FromTicks(_particleSimStopwatch.ElapsedTicks).TotalMilliseconds} ms", Color.White); + + Renderer.SetTransform(new Vector2(0.0f, 16.0f), Vector2.Zero); + Renderer.DrawText(_font, $"Render: {RenderFrameTime.TotalMilliseconds:F1} ms", Color.White); + + Renderer.SetTransform(new Vector2(0.0f, 32.0f), Vector2.Zero); + Renderer.DrawText(_font, $"Update: {UpdateTimeStep * 1000:F1} ms", Color.White); } private void DrawEmitter(ParticleEmitter emitter) @@ -118,8 +100,7 @@ public class TestGame : Game [NotNull] private ParticleSystem _particleSystem; - private Stopwatch _particleSimStopwatch, _renderStopwatch; - private long _lastRenderTime; + private Stopwatch _particleSimStopwatch; private int _emitterId; private ResourceRef _fireEffect; private ResourceRef _font; diff --git a/Voile/Source/Game.cs b/Voile/Source/Game.cs index 505d3a2..52972df 100644 --- a/Voile/Source/Game.cs +++ b/Voile/Source/Game.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using Voile.Input; using Voile.Rendering; using Voile.Resources; @@ -83,7 +84,6 @@ namespace Voile } Initialize(); - LoadResources(); Ready(); Run(); Shutdown(); @@ -116,10 +116,6 @@ namespace Voile /// public abstract void Initialize(); /// - /// Called when it's time to load the game's resources, such as images or sounds. - /// - protected abstract void LoadResources(); - /// /// Called when it's safe to manipulate the resources or/and systems. /// You can safely create game objects, scenes, etc. in this method. /// @@ -143,4 +139,63 @@ namespace Voile Renderer.Start(RendererSettings.Default); } } + + public abstract class BaseGame : Game + { + public double UpdateTimeStep { get; set; } = 1.0 / 60.0; + public TimeSpan UpdateFrameTime { get; private set; } + public TimeSpan RenderFrameTime { get; private set; } + public override void Initialize() + { + LoadResources(); + InitializeDefault(); + } + + public override void Shutdown() => ShutdownDefault(); + + /// + /// Called when it's time to load the game's resources, such as images or sounds. + /// + protected abstract void LoadResources(); + + protected override void Run() + { + Stopwatch stopwatch = Stopwatch.StartNew(); + double previousTime = stopwatch.Elapsed.TotalSeconds; + + while (Renderer.ShouldRun) + { + double currentTime = stopwatch.Elapsed.TotalSeconds; + double elapsedTime = currentTime - previousTime; + previousTime = currentTime; + + _accumulator += elapsedTime; + + while (_accumulator >= UpdateTimeStep) + { + Update(UpdateTimeStep); + _accumulator -= UpdateTimeStep; + } + + Renderer.BeginFrame(); + Render(Renderer.FrameTime); + Renderer.EndFrame(); + + RenderFrameTime = TimeSpan.FromSeconds(Renderer.FrameTime); + } + } + + /// + /// Triggered on each fixed timestep. Update your game's state here. + /// + /// + protected abstract void Update(double deltaTime); + /// + /// Triggered when the renderer is ready to render the next frame. + /// + /// + protected abstract void Render(double deltaTime); + + private double _accumulator; + } } \ No newline at end of file