259 lines
7.8 KiB
C#
259 lines
7.8 KiB
C#
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using Voile.Audio;
|
|
using Voile.Input;
|
|
using Voile.Rendering;
|
|
using Voile.Resources;
|
|
using Voile.VFS;
|
|
|
|
namespace Voile
|
|
{
|
|
public interface IGame
|
|
{
|
|
string Name { get; }
|
|
/// <summary>
|
|
/// Resource root path for this game.
|
|
/// </summary>
|
|
string ResourceRoot { get; }
|
|
void Start();
|
|
void Run();
|
|
void Shutdown();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Entry point for the Voile game.
|
|
/// </summary>
|
|
public abstract class Game : IGame
|
|
{
|
|
public double UpdateTimeStep { get; set; } = 1.0 / 60.0;
|
|
public TimeSpan UpdateFrameTime { get; private set; }
|
|
public TimeSpan RenderFrameTime { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The ResourceManager associated with this game.
|
|
/// </summary>
|
|
protected ResourceManager ResourceManager { get; private set; } = new();
|
|
/// <summary>
|
|
/// The InputHandler associated with this game. Uses <see cref="RaylibInputSystem"/> by default.
|
|
/// </summary>
|
|
protected InputSystem? Input { get; set; }
|
|
|
|
/// <summary>
|
|
/// The Renderer associated with this game. Uses <see cref="RaylibRenderSystem"/> by default.
|
|
/// </summary>
|
|
protected RenderSystem? Renderer { get; set; }
|
|
|
|
protected AudioSystem? AudioSystem { get; set; }
|
|
|
|
/// <summary>
|
|
/// Name of this game. Also used as a default window title.
|
|
/// </summary>
|
|
public virtual string Name => "Voile Game";
|
|
public virtual string ResourceRoot => "Resources/";
|
|
|
|
/// <summary>
|
|
/// Default game constructor.
|
|
/// </summary>
|
|
public Game()
|
|
{
|
|
ResourceManager = new ResourceManager();
|
|
Initialize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Game constructor with custom systems.
|
|
/// </summary>
|
|
/// <param name="inputSystem"></param>
|
|
/// <param name="renderSystem"></param>
|
|
public Game(InputSystem inputSystem, RenderSystem renderSystem)
|
|
{
|
|
Input = inputSystem;
|
|
Renderer = renderSystem;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the game application.
|
|
/// This involves initializing the required subsystems, loading resources from the disk, and then preparing the subsystems for running in the main loop.
|
|
/// </summary>
|
|
public void Start()
|
|
{
|
|
if (Renderer is null)
|
|
{
|
|
throw new NullReferenceException("No renderer provided.");
|
|
}
|
|
|
|
if (Input is null)
|
|
{
|
|
throw new NullReferenceException("No input system provided.");
|
|
}
|
|
|
|
if (ResourceManager is null)
|
|
{
|
|
throw new NullReferenceException("No ResourceManager provided.");
|
|
}
|
|
|
|
Mount();
|
|
LoadResources();
|
|
Ready();
|
|
Run();
|
|
Shutdown();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes systems using default types and settings.
|
|
/// </summary>
|
|
public void InitializeSystemsDefault()
|
|
{
|
|
ResourceManager.ResourceRoot = ResourceRoot;
|
|
|
|
if (Renderer is null)
|
|
{
|
|
Renderer = new RaylibRenderSystem();
|
|
}
|
|
|
|
if (Input is null)
|
|
{
|
|
Input = new RaylibInputSystem();
|
|
}
|
|
|
|
Input.Start();
|
|
InitializeRenderer();
|
|
}
|
|
|
|
public void ShutdownDefault()
|
|
{
|
|
Input?.Dispose();
|
|
Renderer?.Dispose();
|
|
ResourceManager.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a system that will start together with the game.
|
|
/// </summary>
|
|
/// <param name="system"></param>
|
|
public void AddSystem([DisallowNull] IStartableSystem system)
|
|
{
|
|
_startableSystems.Add(system);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a system to the Update step.
|
|
/// </summary>
|
|
/// <param name="system"></param>
|
|
public void AddSystemToUpdate([DisallowNull] IUpdatableSystem system)
|
|
{
|
|
_updatableSystems.Add(system);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a system to the render step.
|
|
/// </summary>
|
|
/// <param name="system"></param>
|
|
public void AddSystemToRender([DisallowNull] IUpdatableSystem system)
|
|
{
|
|
_renderableSystems.Add(system);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when everything has been readied to start the main loop.
|
|
/// </summary>
|
|
public void Run()
|
|
{
|
|
if (Renderer is null)
|
|
{
|
|
throw new NullReferenceException("No renderer provided.");
|
|
}
|
|
|
|
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)
|
|
{
|
|
foreach (var system in _updatableSystems)
|
|
{
|
|
system.Update(UpdateTimeStep);
|
|
}
|
|
|
|
Update(UpdateTimeStep);
|
|
_accumulator -= UpdateTimeStep;
|
|
}
|
|
|
|
Renderer.BeginFrame();
|
|
|
|
foreach (var system in _renderableSystems)
|
|
{
|
|
system.Update(Renderer.FrameTime);
|
|
}
|
|
|
|
Render(Renderer.FrameTime);
|
|
Renderer.EndFrame();
|
|
|
|
RenderFrameTime = TimeSpan.FromSeconds(Renderer.FrameTime);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when it's time to initialize the subsystems, or modify default game settings or system settings.
|
|
/// </summary>
|
|
public abstract void Initialize();
|
|
/// <summary>
|
|
/// Called when it's safe to manipulate the resources or/and systems.
|
|
/// You can safely create game objects, scenes, etc. in this method.
|
|
/// </summary>
|
|
protected abstract void Ready();
|
|
|
|
/// <summary>
|
|
/// Called when it's time to load the game's resources, such as images or sounds.
|
|
/// </summary>
|
|
protected abstract void LoadResources();
|
|
|
|
/// <summary>
|
|
/// Triggered on each fixed timestep. Update your game's state here.
|
|
/// </summary>
|
|
/// <param name="deltaTime"></param>
|
|
protected abstract void Update(double deltaTime);
|
|
/// <summary>
|
|
/// Triggered when the renderer is ready to render the next frame.
|
|
/// </summary>
|
|
/// <param name="deltaTime"></param>
|
|
protected abstract void Render(double deltaTime);
|
|
|
|
/// <summary>
|
|
/// Called when the application quits and it's safe to clean up.
|
|
/// </summary>
|
|
public abstract void Shutdown();
|
|
|
|
private void InitializeRenderer()
|
|
{
|
|
var windowSettings = WindowSettings.Default;
|
|
|
|
if (!string.IsNullOrWhiteSpace(Name))
|
|
windowSettings.Title = Name;
|
|
|
|
var renderSettings = RendererSettings.Default;
|
|
renderSettings.WindowSettings = windowSettings;
|
|
|
|
Renderer?.Start(renderSettings);
|
|
}
|
|
|
|
private void Mount()
|
|
{
|
|
var resourceRootMount = new FileSystemMountPoint(ResourceRoot);
|
|
VirtualFileSystem.Mount(resourceRootMount);
|
|
}
|
|
|
|
private List<IStartableSystem> _startableSystems = new();
|
|
private List<IUpdatableSystem> _updatableSystems = new();
|
|
private List<IUpdatableSystem> _renderableSystems = new();
|
|
|
|
private double _accumulator;
|
|
}
|
|
} |