From 1be5679823781632bd683726e261c58f2d3f7b68 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Apr 2020 21:19:02 -0400 Subject: [PATCH 1/6] Event cleanup part 1 --- SharpDL/Events/EventManager.cs | 179 ++++++++++++++ SharpDL/Events/GameEventArgs.cs | 4 - SharpDL/Events/GameEventArgsFactory.cs | 19 -- SharpDL/Events/GameEventType.cs | 2 +- SharpDL/Events/MouseMotionEventArgs.cs | 2 +- SharpDL/Events/QuitEventArgs.cs | 5 - SharpDL/Events/VideoDeviceSystemEventArgs.cs | 5 - SharpDL/Events/WindowEventArgs.cs | 4 - SharpDL/Events/WindowEventType.cs | 5 - SharpDL/Game.cs | 243 ++----------------- SharpDL/InitializeType.cs | 19 ++ 11 files changed, 223 insertions(+), 264 deletions(-) create mode 100644 SharpDL/Events/EventManager.cs delete mode 100644 SharpDL/Events/GameEventArgsFactory.cs create mode 100644 SharpDL/InitializeType.cs diff --git a/SharpDL/Events/EventManager.cs b/SharpDL/Events/EventManager.cs new file mode 100644 index 0000000..9f9e619 --- /dev/null +++ b/SharpDL/Events/EventManager.cs @@ -0,0 +1,179 @@ +using System; +using SDL2; +using SharpDL.Input; + +namespace SharpDL.Events +{ + public class EventManager + { + public event EventHandler MouseWheelScrolling; + + public event EventHandler MouseButtonPressed; + + public event EventHandler MouseButtonReleased; + + public event EventHandler MouseMoving; + + public event EventHandler TextInputting; + + public event EventHandler TextEditing; + + public event EventHandler KeyPressed; + + public event EventHandler KeyReleased; + + public event EventHandler VideoDeviceSystemEvent; + + public event EventHandler Quitting; + + public event EventHandler Activated; + + public event EventHandler Deactivated; + + public event EventHandler Disposed; + + public event EventHandler Exiting; + + public event EventHandler WindowShown; + + public event EventHandler WindowHidden; + + public event EventHandler WindowExposed; + + public event EventHandler WindowMoved; + + public event EventHandler WindowResized; + + public event EventHandler WindowSizeChanged; + + public event EventHandler WindowMinimized; + + public event EventHandler WindowMaximized; + + public event EventHandler WindowRestored; + + public event EventHandler WindowEntered; + + public event EventHandler WindowLeave; + + public event EventHandler WindowFocusGained; + + public event EventHandler WindowFocusLost; + + public event EventHandler WindowClosed; + + /// Raises the Exiting event. Exiting occurs when an unrecoverable exception occurs or the + /// user directly exits the game. + /// /// + /// + /// + public void HandleExiting(object sender, EventArgs args) + { + RaiseEvent(Exiting, args); + } + + /// Raises the Disposed event. Disposed occurs when the game objects are being cleaned up. + /// /// + /// + /// + public void HandleDisposed(object sender, EventArgs args) + { + RaiseEvent(Disposed, args); + } + + public void HandleEvent(SDL.SDL_Event rawEvent) + { + var eventType = (GameEventType)rawEvent.type; + switch(eventType) + { + case GameEventType.FirstEvent: + return; + case GameEventType.WindowEvent: + var windowEventType = (WindowEventType)rawEvent.window.windowEvent; + switch(windowEventType) + { + case WindowEventType.Close: RaiseEvent(WindowClosed, rawEvent); break; + case WindowEventType.Enter: RaiseEvent(WindowEntered, rawEvent); break; + case WindowEventType.Exposed: RaiseEvent(WindowExposed, rawEvent); break; + case WindowEventType.FocusGained: RaiseEvent(WindowFocusGained, rawEvent); break; + case WindowEventType.FocusLost: RaiseEvent(WindowFocusLost, rawEvent); break; + case WindowEventType.Hidden: RaiseEvent(WindowHidden, rawEvent); break; + case WindowEventType.Leave: RaiseEvent(WindowLeave, rawEvent); break; + case WindowEventType.Maximized: RaiseEvent(WindowMaximized, rawEvent); break; + case WindowEventType.Minimized: RaiseEvent(WindowMinimized, rawEvent); break; + case WindowEventType.Moved: RaiseEvent(WindowMoved, rawEvent); break; + case WindowEventType.Resized: RaiseEvent(WindowResized, rawEvent); break; + case WindowEventType.Restored: RaiseEvent(WindowRestored, rawEvent); break; + case WindowEventType.Shown: RaiseEvent(WindowShown, rawEvent); break; + case WindowEventType.SizeChanged: RaiseEvent(WindowSizeChanged, rawEvent); break; + } + break; + case GameEventType.Quit: + RaiseEvent(Quitting, rawEvent); break; + case GameEventType.VideoDeviceSystemEvent: + RaiseEvent(VideoDeviceSystemEvent, rawEvent); break; + case GameEventType.TextEditing: + RaiseEvent(TextEditing, rawEvent); break; + case GameEventType.TextInput: + RaiseEvent(TextInputting, rawEvent); break; + case GameEventType.KeyDown: + case GameEventType.KeyUp: + var keyState = (KeyState)rawEvent.key.state; + if (keyState == KeyState.Pressed) + RaiseEvent(KeyPressed, rawEvent); + else if (keyState == KeyState.Released) + RaiseEvent(KeyReleased, rawEvent); + break; + case GameEventType.MouseMotion: + Mouse.UpdateMousePosition(rawEvent.motion.x, rawEvent.motion.y); + RaiseEvent(MouseMoving, rawEvent); + break; + case GameEventType.MouseButtonDown: + case GameEventType.MouseButtonUp: + var mouseButtonState = (MouseButtonState)rawEvent.button.state; + if (mouseButtonState == MouseButtonState.Pressed) + RaiseEvent(MouseButtonPressed, rawEvent); + else if (mouseButtonState == MouseButtonState.Released) + RaiseEvent(MouseButtonReleased, rawEvent); + break; + case GameEventType.MouseWheel: + RaiseEvent(MouseWheelScrolling, rawEvent); break; + } + } + + /// Raises an event handler with arguments parsed from the SDL event parameter. + /// This method does nothing if there are no event subscribers. + /// + /// + /// + /// + private void RaiseEvent(EventHandler eventHandler, SDL.SDL_Event rawEvent) + where T : EventArgs + { + var eventArgs = CreateEventArgs(rawEvent); + RaiseEvent(eventHandler, eventArgs); + } + + /// Raises an event handler using the event arguments parameter. + /// This method does nothing if there are no event subscribers. + /// + /// + /// + /// + private void RaiseEvent(EventHandler eventHandler, T eventArgs) + where T : EventArgs + { + if (eventHandler != null) + { + eventHandler(this, eventArgs); + } + } + + private static T CreateEventArgs(SDL.SDL_Event rawEvent) + where T : class + { + return Activator.CreateInstance(typeof(T), + new object[] { rawEvent }) as T; + } + } +} \ No newline at end of file diff --git a/SharpDL/Events/GameEventArgs.cs b/SharpDL/Events/GameEventArgs.cs index 5afbd92..280d0c7 100644 --- a/SharpDL/Events/GameEventArgs.cs +++ b/SharpDL/Events/GameEventArgs.cs @@ -1,9 +1,5 @@ using SDL2; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SharpDL.Events { diff --git a/SharpDL/Events/GameEventArgsFactory.cs b/SharpDL/Events/GameEventArgsFactory.cs deleted file mode 100644 index dfa5a7d..0000000 --- a/SharpDL/Events/GameEventArgsFactory.cs +++ /dev/null @@ -1,19 +0,0 @@ -using SDL2; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SharpDL.Events -{ - public static class GameEventArgsFactory - where T : class - { - public static T Create(SDL.SDL_Event rawEvent) - { - return Activator.CreateInstance(typeof(T), - new object[] { rawEvent }) as T; - } - } -} diff --git a/SharpDL/Events/GameEventType.cs b/SharpDL/Events/GameEventType.cs index fa3797e..be474c9 100644 --- a/SharpDL/Events/GameEventType.cs +++ b/SharpDL/Events/GameEventType.cs @@ -9,7 +9,7 @@ namespace SharpDL.Events { public enum GameEventType : uint { - None = 0, + FirstEvent = 0, Quit = SDL.SDL_EventType.SDL_QUIT, WindowEvent = SDL.SDL_EventType.SDL_WINDOWEVENT, VideoDeviceSystemEvent = SDL.SDL_EventType.SDL_SYSWMEVENT, diff --git a/SharpDL/Events/MouseMotionEventArgs.cs b/SharpDL/Events/MouseMotionEventArgs.cs index feeb881..3311a00 100644 --- a/SharpDL/Events/MouseMotionEventArgs.cs +++ b/SharpDL/Events/MouseMotionEventArgs.cs @@ -14,7 +14,7 @@ public class MouseMotionEventArgs : GameEventArgs public UInt32 MouseDeviceID { get; private set; } - public IList MouseButtonsPressed { get; private set; } + public IEnumerable MouseButtonsPressed { get; private set; } /// /// Mouse X position relative to the window origin. Setter is public because we may need to intercept and adjust the value for detecting click/hover events diff --git a/SharpDL/Events/QuitEventArgs.cs b/SharpDL/Events/QuitEventArgs.cs index 314abb4..f17274b 100644 --- a/SharpDL/Events/QuitEventArgs.cs +++ b/SharpDL/Events/QuitEventArgs.cs @@ -1,9 +1,4 @@ using SDL2; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SharpDL.Events { diff --git a/SharpDL/Events/VideoDeviceSystemEventArgs.cs b/SharpDL/Events/VideoDeviceSystemEventArgs.cs index 7ccafeb..a5e0e66 100644 --- a/SharpDL/Events/VideoDeviceSystemEventArgs.cs +++ b/SharpDL/Events/VideoDeviceSystemEventArgs.cs @@ -1,9 +1,4 @@ using SDL2; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SharpDL.Events { diff --git a/SharpDL/Events/WindowEventArgs.cs b/SharpDL/Events/WindowEventArgs.cs index 23c586b..33be1bd 100644 --- a/SharpDL/Events/WindowEventArgs.cs +++ b/SharpDL/Events/WindowEventArgs.cs @@ -1,9 +1,5 @@ using SDL2; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SharpDL.Events { diff --git a/SharpDL/Events/WindowEventType.cs b/SharpDL/Events/WindowEventType.cs index 06e053d..0d47258 100644 --- a/SharpDL/Events/WindowEventType.cs +++ b/SharpDL/Events/WindowEventType.cs @@ -1,9 +1,4 @@ using SDL2; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SharpDL.Events { diff --git a/SharpDL/Game.cs b/SharpDL/Game.cs index 1ac421c..a5439f3 100644 --- a/SharpDL/Game.cs +++ b/SharpDL/Game.cs @@ -3,26 +3,30 @@ using SharpDL.Graphics; using SharpDL.Input; using System; +using System.Collections.Generic; namespace SharpDL { - public class Game : IDisposable + public abstract class Game : IDisposable { #region Members private const uint EMPTY_UINT = 0; private const int EMPTY_INT = -1; private const float FRAMES_PER_SECOND = 60f; + private readonly GameTime gameTime = new GameTime(); + private readonly Timer gameTimer = new Timer(); + private TimeSpan accumulatedElapsedTime = TimeSpan.Zero; private TimeSpan targetElapsedTime = TimeSpan.FromSeconds(1 / FRAMES_PER_SECOND); private bool isFrameRateCapped = true; - private GameTime gameTime = new GameTime(); - private Timer gameTimer = new Timer(); #endregion Members #region Properties + protected EventManager EventManager { get; private set; } + protected Window Window { get; private set; } protected Renderer Renderer { get; private set; } @@ -40,112 +44,18 @@ public class Game : IDisposable /// public Game() { - WindowClosed += Game_WindowClosed; - } - - private void Game_WindowClosed(object sender, GameEventArgs e) - { - IsExiting = true; + EventManager = new EventManager(); + EventManager.WindowClosed += OnExiting; + EventManager.Quitting += OnExiting; } #endregion Constructors #region Events - public event EventHandler MouseWheelScrolling; - - public event EventHandler MouseButtonPressed; - - public event EventHandler MouseButtonReleased; - - public event EventHandler MouseMoving; - - public event EventHandler TextInputting; - - public event EventHandler TextEditing; - - public event EventHandler KeyPressed; - - public event EventHandler KeyReleased; - - public event EventHandler VideoDeviceSystemEvent; - - public event EventHandler Quitting; - - public event EventHandler Activated; - - public event EventHandler Deactivated; - - public event EventHandler Disposed; - - public event EventHandler Exiting; - - public event EventHandler WindowShown; - - public event EventHandler WindowHidden; - - public event EventHandler WindowExposed; - - public event EventHandler WindowMoved; - - public event EventHandler WindowResized; - - public event EventHandler WindowSizeChanged; - - public event EventHandler WindowMinimized; - - public event EventHandler WindowMaximized; - - public event EventHandler WindowRestored; - - public event EventHandler WindowEntered; - - public event EventHandler WindowLeave; - - public event EventHandler WindowFocusGained; - - public event EventHandler WindowFocusLost; - - public event EventHandler WindowClosed; - - /// Raises the Activated event. Activation occurs when the game gains focus. - /// - /// - /// - protected virtual void OnActivated(object sender, EventArgs args) - { - RaiseEvent(Activated, args); - } - - /// Raises the Deactivated event. Deactivation occurs when the game loses focus. - /// - /// - /// - protected virtual void OnDeactivated(object sender, EventArgs args) + private void OnExiting(object sender, GameEventArgs e) { - RaiseEvent(Deactivated, args); - } - - /// Raises the Exiting event. Exiting occurs when an unrecoverable exception occurs or the - /// user directly exits the game. - /// - /// - /// - protected virtual void OnExiting(object sender, EventArgs args) - { - RaiseEvent(Exiting, args); - } - - /// Checks if there are any event subscribers prior to raising the event. - /// - /// - /// - /// - private void RaiseEvent(EventHandler eventHandler, T eventArguments) - where T : EventArgs - { - if (eventHandler != null) - eventHandler(this, eventArguments); + IsExiting = true; } #endregion Events @@ -164,106 +74,16 @@ public void Run() { SDL.SDL_Event rawEvent = new SDL.SDL_Event(); while (SDL.SDL_PollEvent(out rawEvent) == 1) - RaiseGameEventFromRawEvent(rawEvent); - + { + EventManager.HandleEvent(rawEvent); + } + Tick(); } UnloadContent(); } - // The passed raw SDL_Event object is translated into a SharpDL game object and raised using - // the appropriate EventHandler. - // - // - private void RaiseGameEventFromRawEvent(SDL.SDL_Event rawEvent) - { - if (rawEvent.type == SDL.SDL_EventType.SDL_FIRSTEVENT) - return; - else if (rawEvent.type == SDL.SDL_EventType.SDL_WINDOWEVENT) - { - WindowEventArgs eventArgs = GameEventArgsFactory.Create(rawEvent); - if (eventArgs.SubEventType == WindowEventType.Close) - RaiseEvent(WindowClosed, eventArgs); - else if (eventArgs.SubEventType == WindowEventType.Enter) - RaiseEvent(WindowEntered, eventArgs); - else if (eventArgs.SubEventType == WindowEventType.Exposed) - RaiseEvent(WindowExposed, eventArgs); - else if (eventArgs.SubEventType == WindowEventType.FocusGained) - RaiseEvent(WindowFocusGained, eventArgs); - else if (eventArgs.SubEventType == WindowEventType.FocusLost) - RaiseEvent(WindowFocusLost, eventArgs); - else if (eventArgs.SubEventType == WindowEventType.Hidden) - RaiseEvent(WindowHidden, eventArgs); - else if (eventArgs.SubEventType == WindowEventType.Leave) - RaiseEvent(WindowLeave, eventArgs); - else if (eventArgs.SubEventType == WindowEventType.Maximized) - RaiseEvent(WindowMaximized, eventArgs); - else if (eventArgs.SubEventType == WindowEventType.Minimized) - RaiseEvent(WindowMinimized, eventArgs); - else if (eventArgs.SubEventType == WindowEventType.Moved) - RaiseEvent(WindowMoved, eventArgs); - else if (eventArgs.SubEventType == WindowEventType.Resized) - RaiseEvent(WindowResized, eventArgs); - else if (eventArgs.SubEventType == WindowEventType.Restored) - RaiseEvent(WindowRestored, eventArgs); - else if (eventArgs.SubEventType == WindowEventType.Shown) - RaiseEvent(WindowShown, eventArgs); - else if (eventArgs.SubEventType == WindowEventType.SizeChanged) - RaiseEvent(WindowSizeChanged, eventArgs); - } - else if (rawEvent.type == SDL.SDL_EventType.SDL_QUIT) - { - QuitEventArgs eventArgs = GameEventArgsFactory.Create(rawEvent); - RaiseEvent(Quitting, eventArgs); - } - else if (rawEvent.type == SDL.SDL_EventType.SDL_SYSWMEVENT) - { - VideoDeviceSystemEventArgs eventArgs = GameEventArgsFactory.Create(rawEvent); - RaiseEvent(VideoDeviceSystemEvent, eventArgs); - } - else if (rawEvent.type == SDL.SDL_EventType.SDL_KEYDOWN - || rawEvent.type == SDL.SDL_EventType.SDL_KEYUP) - { - KeyboardEventArgs eventArgs = GameEventArgsFactory.Create(rawEvent); - if (eventArgs.State == KeyState.Pressed) - RaiseEvent(KeyPressed, eventArgs); - else if (eventArgs.State == KeyState.Released) - RaiseEvent(KeyReleased, eventArgs); - } - else if (rawEvent.type == SDL.SDL_EventType.SDL_TEXTEDITING) - { - TextEditingEventArgs eventArgs = GameEventArgsFactory.Create(rawEvent); - RaiseEvent(TextEditing, eventArgs); - } - else if (rawEvent.type == SDL.SDL_EventType.SDL_TEXTINPUT) - { - TextInputEventArgs eventArgs = GameEventArgsFactory.Create(rawEvent); - RaiseEvent(TextInputting, eventArgs); - } - else if (rawEvent.type == SDL.SDL_EventType.SDL_MOUSEMOTION) - { - MouseMotionEventArgs eventArgs = GameEventArgsFactory.Create(rawEvent); - Mouse.UpdateMousePosition(eventArgs.RelativeToWindowX, eventArgs.RelativeToWindowY); - RaiseEvent(MouseMoving, eventArgs); - } - else if (rawEvent.type == SDL.SDL_EventType.SDL_MOUSEBUTTONUP - || rawEvent.type == SDL.SDL_EventType.SDL_MOUSEBUTTONDOWN) - { - MouseButtonEventArgs eventArgs = GameEventArgsFactory.Create(rawEvent); - - if (eventArgs.State == MouseButtonState.Pressed) - RaiseEvent(MouseButtonPressed, eventArgs); - else if (eventArgs.State == MouseButtonState.Released) - RaiseEvent(MouseButtonReleased, eventArgs); - } - else if (rawEvent.type == SDL.SDL_EventType.SDL_MOUSEWHEEL) - { - MouseWheelEventArgs eventArgs = GameEventArgsFactory.Create(rawEvent); - RaiseEvent(MouseWheelScrolling, eventArgs); - } - } - /// A tick is equal to a single time step forward in the game state. During each tick, the game will update total game time, /// elapsed update time, and frame rates. It is important to note that the implementation is based on a Fixed Time Step algorithm where /// each update and draw occur in the same constant fixed intervals. Additionally, the game will call the Update and Draw game cycle @@ -312,33 +132,22 @@ private void Tick() public void Quit() { IsExiting = true; - RaiseEvent(Exiting, EventArgs.Empty); + EventManager.HandleExiting(this, EventArgs.Empty); } #endregion Game Cycle Control #region Game Cycle - /// - /// Initializes the game by calling initialize on the SDL2 instance with "EVERYTHING". - /// - protected virtual void Initialize() - { - Initialize(SDL.SDL_INIT_EVERYTHING); - } - /// Initializes the game by calling initialize on the SDL2 instance with the passed flags /// or "EVERYTHING" if 0. Additionally, this method will initialize SDL_ttf and SDL_image to load fonts and images. /// - /// Bit flags indicating the way in which SDL should be initialized - protected virtual void Initialize(uint flags) + /// Bit flags indicating the way in which SDL should be initialized + protected virtual void Initialize(InitializeType types = InitializeType.Everything) { - if (flags == EMPTY_UINT) - flags = SDL.SDL_INIT_EVERYTHING; - - if (SDL.SDL_Init(flags) != 0) + if (SDL.SDL_Init((uint)types) != 0) { - throw new InvalidOperationException(String.Format("SDL_Init: {0}", SDL.SDL_GetError())); + throw new InvalidOperationException($"SDL_Init: {SDL.SDL_GetError()}"); } if (SDL_ttf.TTF_Init() != 0) @@ -356,13 +165,7 @@ protected virtual void Initialize(uint flags) /// Used for potentially long lasting operations that should only occur relatively rarely. Usually, this /// method is used to load images, textures, maps, sounds, videos, and other game assets at the beginning of a level or area. /// - protected virtual void LoadContent() - { - } - - //TimeSpan elapsedTime = TimeSpan.Zero; - //int frameRate = 0; - //int frameCounter = 0; + protected abstract void LoadContent(); /// Update the state of the game such as positions, health, entity properties, and more. /// This is called before Draw in the main game loop. @@ -483,7 +286,7 @@ protected virtual void Dispose(bool disposing) SDL_ttf.TTF_Quit(); SDL_image.IMG_Quit(); SDL.SDL_Quit(); - RaiseEvent(Disposed, EventArgs.Empty); + EventManager.HandleDisposed(this, EventArgs.Empty); } #endregion Dispose diff --git a/SharpDL/InitializeType.cs b/SharpDL/InitializeType.cs new file mode 100644 index 0000000..d16a062 --- /dev/null +++ b/SharpDL/InitializeType.cs @@ -0,0 +1,19 @@ +using System; + +namespace SharpDL +{ + [Flags] + public enum InitializeType + { + Timer = 1, + Audio = 16, + Video = 32, + Joystick = 512, + Haptic = 4096, + GameController = 8192, + Events = 16384, + Sensor = 32768, + Everything = 62001, + NoParachute = 1048576 + } +} \ No newline at end of file From b136400a0cd36e2b659a6f47774fe2b8557900e0 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 9 Apr 2020 22:54:37 -0400 Subject: [PATCH 2/6] Game class cleanup --- SharpDL/Events/EventManager.cs | 19 ++---------- SharpDL/Game.cs | 53 ++++++++++++++++++++++++---------- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/SharpDL/Events/EventManager.cs b/SharpDL/Events/EventManager.cs index 9f9e619..f2373e9 100644 --- a/SharpDL/Events/EventManager.cs +++ b/SharpDL/Events/EventManager.cs @@ -26,12 +26,6 @@ public class EventManager public event EventHandler Quitting; - public event EventHandler Activated; - - public event EventHandler Deactivated; - - public event EventHandler Disposed; - public event EventHandler Exiting; public event EventHandler WindowShown; @@ -67,21 +61,12 @@ public class EventManager /// /// /// /// - public void HandleExiting(object sender, EventArgs args) + internal void RaiseExiting(object sender, EventArgs args) { RaiseEvent(Exiting, args); } - /// Raises the Disposed event. Disposed occurs when the game objects are being cleaned up. - /// /// - /// - /// - public void HandleDisposed(object sender, EventArgs args) - { - RaiseEvent(Disposed, args); - } - - public void HandleEvent(SDL.SDL_Event rawEvent) + internal void RaiseEvent(SDL.SDL_Event rawEvent) { var eventType = (GameEventType)rawEvent.type; switch(eventType) diff --git a/SharpDL/Game.cs b/SharpDL/Game.cs index a5439f3..a75771d 100644 --- a/SharpDL/Game.cs +++ b/SharpDL/Game.cs @@ -3,7 +3,6 @@ using SharpDL.Graphics; using SharpDL.Input; using System; -using System.Collections.Generic; namespace SharpDL { @@ -65,9 +64,9 @@ private void OnExiting(object sender, GameEventArgs e) /// Begins the game by performing the following cycle events in this order: Initialize, LoadContent, /// CheckInputs, Update, Draw, UnloadContent. /// - public void Run() + public void Run(InitializeType types = InitializeType.Everything) { - Initialize(); + PerformInitialize(types); LoadContent(); while (!IsExiting) @@ -75,13 +74,14 @@ public void Run() SDL.SDL_Event rawEvent = new SDL.SDL_Event(); while (SDL.SDL_PollEvent(out rawEvent) == 1) { - EventManager.HandleEvent(rawEvent); + EventManager.RaiseEvent(rawEvent); } Tick(); } UnloadContent(); + Dispose(); } /// A tick is equal to a single time step forward in the game state. During each tick, the game will update total game time, @@ -132,18 +132,34 @@ private void Tick() public void Quit() { IsExiting = true; - EventManager.HandleExiting(this, EventArgs.Empty); + EventManager.RaiseExiting(this, EventArgs.Empty); } #endregion Game Cycle Control #region Game Cycle + /// Override to initialize any custom objects or large helpers that are required by the game. + /// + protected abstract void Initialize(); + + /// + /// Template Method Pattern to require initialize of the game engine before calling the game's custom + /// initialize method. + /// + /// + private void PerformInitialize(InitializeType types = InitializeType.Everything) + { + // Initialize base SDL before the game's custom initialize + InitializeBase(types); + Initialize(); + } + /// Initializes the game by calling initialize on the SDL2 instance with the passed flags /// or "EVERYTHING" if 0. Additionally, this method will initialize SDL_ttf and SDL_image to load fonts and images. /// /// Bit flags indicating the way in which SDL should be initialized - protected virtual void Initialize(InitializeType types = InitializeType.Everything) + private void InitializeBase(InitializeType types = InitializeType.Everything) { if (SDL.SDL_Init((uint)types) != 0) { @@ -152,13 +168,18 @@ protected virtual void Initialize(InitializeType types = InitializeType.Everythi if (SDL_ttf.TTF_Init() != 0) { - throw new InvalidOperationException(String.Format("TTF_Init: {0}", SDL.SDL_GetError())); + throw new InvalidOperationException($"TTF_Init: {SDL.SDL_GetError()}"); } - int initImageResult = SDL_image.IMG_Init(SDL_image.IMG_InitFlags.IMG_INIT_PNG); - if ((initImageResult & (int)SDL_image.IMG_InitFlags.IMG_INIT_PNG) != (int)SDL_image.IMG_InitFlags.IMG_INIT_PNG) + SDL_image.IMG_InitFlags initImageFlags = + SDL_image.IMG_InitFlags.IMG_INIT_JPG + | SDL_image.IMG_InitFlags.IMG_INIT_PNG + | SDL_image.IMG_InitFlags.IMG_INIT_TIF + | SDL_image.IMG_InitFlags.IMG_INIT_WEBP; + int initImageResult = SDL_image.IMG_Init(initImageFlags); + if ((initImageResult & (int)initImageFlags) != (int)initImageFlags) { - throw new InvalidOperationException(String.Format("IMG_Init: {0}", SDL.SDL_GetError())); + throw new InvalidOperationException($"IMG_Init: {SDL.SDL_GetError()}"); } } @@ -210,10 +231,7 @@ protected virtual void Draw(GameTime gameTime) /// Used to unload game assets that were loaded during the LoadContent method. Usually, you use this to free /// any resources that should not be lingering any longer or are no longer required. /// - protected virtual void UnloadContent() - { - Dispose(); - } + protected abstract void UnloadContent(); #endregion Game Cycle @@ -260,7 +278,7 @@ protected void CreateRenderer(int index, RendererFlags flags) #region Dispose - public void Dispose() + public virtual void Dispose() { Dispose(true); GC.SuppressFinalize(this); @@ -271,6 +289,10 @@ public void Dispose() Dispose(false); } + /// Override to dispose of any custom objects that you've instantiated. Always call + /// base.Dispose() so that the base class objects are disposed as well. + /// + /// protected virtual void Dispose(bool disposing) { if (Window != null) @@ -286,7 +308,6 @@ protected virtual void Dispose(bool disposing) SDL_ttf.TTF_Quit(); SDL_image.IMG_Quit(); SDL.SDL_Quit(); - EventManager.HandleDisposed(this, EventArgs.Empty); } #endregion Dispose From 99a11ee134316e97f7ed3c0a7f7d6d4039edba1a Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 10 Apr 2020 15:39:30 -0400 Subject: [PATCH 3/6] General Game class cleanup. Added ability to injecte loggers from consumers. --- SharpDL/Events/EventManager.cs | 4 +- SharpDL/Events/GameEventType.cs | 9 +--- SharpDL/Game.cs | 75 +++++++++++++++++---------------- SharpDL/GameTime.cs | 7 +-- SharpDL/InitializeType.cs | 23 +++++----- SharpDL/MessageBox.cs | 40 ++++++++---------- SharpDL/Shared/LogManager.cs | 4 ++ SharpDL/SharpDL.csproj | 1 + SharpDL/Timer.cs | 32 +++++--------- 9 files changed, 88 insertions(+), 107 deletions(-) create mode 100644 SharpDL/Shared/LogManager.cs diff --git a/SharpDL/Events/EventManager.cs b/SharpDL/Events/EventManager.cs index f2373e9..85b3574 100644 --- a/SharpDL/Events/EventManager.cs +++ b/SharpDL/Events/EventManager.cs @@ -71,9 +71,9 @@ internal void RaiseEvent(SDL.SDL_Event rawEvent) var eventType = (GameEventType)rawEvent.type; switch(eventType) { - case GameEventType.FirstEvent: + case GameEventType.First: return; - case GameEventType.WindowEvent: + case GameEventType.Window: var windowEventType = (WindowEventType)rawEvent.window.windowEvent; switch(windowEventType) { diff --git a/SharpDL/Events/GameEventType.cs b/SharpDL/Events/GameEventType.cs index be474c9..f77a06b 100644 --- a/SharpDL/Events/GameEventType.cs +++ b/SharpDL/Events/GameEventType.cs @@ -1,17 +1,12 @@ using SDL2; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SharpDL.Events { public enum GameEventType : uint { - FirstEvent = 0, + First = 0, Quit = SDL.SDL_EventType.SDL_QUIT, - WindowEvent = SDL.SDL_EventType.SDL_WINDOWEVENT, + Window = SDL.SDL_EventType.SDL_WINDOWEVENT, VideoDeviceSystemEvent = SDL.SDL_EventType.SDL_SYSWMEVENT, KeyDown = SDL.SDL_EventType.SDL_KEYDOWN, KeyUp = SDL.SDL_EventType.SDL_KEYUP, diff --git a/SharpDL/Game.cs b/SharpDL/Game.cs index a75771d..4d428af 100644 --- a/SharpDL/Game.cs +++ b/SharpDL/Game.cs @@ -1,4 +1,5 @@ -using SDL2; +using Microsoft.Extensions.Logging; +using SDL2; using SharpDL.Events; using SharpDL.Graphics; using SharpDL.Input; @@ -8,6 +9,8 @@ namespace SharpDL { public abstract class Game : IDisposable { + private readonly ILogger logger; + #region Members private const uint EMPTY_UINT = 0; @@ -17,7 +20,8 @@ public abstract class Game : IDisposable private readonly Timer gameTimer = new Timer(); private TimeSpan accumulatedElapsedTime = TimeSpan.Zero; - private TimeSpan targetElapsedTime = TimeSpan.FromSeconds(1 / FRAMES_PER_SECOND); + private readonly TimeSpan targetElapsedTime = TimeSpan.FromSeconds(1 / FRAMES_PER_SECOND); + private readonly TimeSpan maxElapsedTime = TimeSpan.FromSeconds(0.5); private bool isFrameRateCapped = true; #endregion Members @@ -41,8 +45,9 @@ public abstract class Game : IDisposable /// Default constructor of the base Game class does nothing. Only when Initialize is called /// is anything useful done. /// - public Game() + public Game(ILogger logger = null) { + this.logger = logger; EventManager = new EventManager(); EventManager.WindowClosed += OnExiting; EventManager.Quitting += OnExiting; @@ -92,37 +97,55 @@ public void Run(InitializeType types = InitializeType.Everything) /// private void Tick() { + // If our frame rate is capped, we want to wait until we have elapsed enough time to have a fixed-step + // At 60 FPS, the target elapsed time is 1/60 or 0.01667~ seconds. while (isFrameRateCapped && (accumulatedElapsedTime < targetElapsedTime)) { accumulatedElapsedTime += gameTimer.ElapsedTime; - gameTimer.Stop(); gameTimer.Start(); if (isFrameRateCapped && (accumulatedElapsedTime < targetElapsedTime)) { + // Sleep for as long as we need to reach the target elapsed time TimeSpan sleepTime = targetElapsedTime - accumulatedElapsedTime; - SDL.SDL_Delay((UInt32)sleepTime.TotalMilliseconds); + SDL.SDL_Delay((uint)sleepTime.TotalMilliseconds); } } - if (accumulatedElapsedTime > TimeSpan.FromSeconds(0.5)) - accumulatedElapsedTime = TimeSpan.FromSeconds(0.5); + // Don't allow any updates to go beyond the max update time + if (accumulatedElapsedTime > maxElapsedTime) + accumulatedElapsedTime = maxElapsedTime; + // Fixed time step update if (isFrameRateCapped) { int stepCount = 0; + // If we have waited longer than the target time (non-precision timers/waits?), we need to advance + // the game state in a fixed step interval. while (accumulatedElapsedTime >= targetElapsedTime) { gameTime.TotalGameTime += targetElapsedTime; accumulatedElapsedTime -= targetElapsedTime; stepCount++; - Update(gameTime); + PerformUpdate(gameTime); } + // In normal scenarios, this will advance the elapsed time by the target, but in cases where + // we have had to "catch up" because of non-precise waits, we will need to take the fixed steps + // into account. gameTime.ElapsedGameTime = TimeSpan.FromTicks(targetElapsedTime.Ticks * stepCount); } + // Variable time step update + else + { + gameTime.ElapsedGameTime = accumulatedElapsedTime; + gameTime.TotalGameTime += targetElapsedTime; + accumulatedElapsedTime = TimeSpan.Zero; + + PerformUpdate(gameTime); + } Draw(gameTime); } @@ -148,7 +171,7 @@ public void Quit() /// initialize method. /// /// - private void PerformInitialize(InitializeType types = InitializeType.Everything) + private void PerformInitialize(InitializeType types) { // Initialize base SDL before the game's custom initialize InitializeBase(types); @@ -159,7 +182,7 @@ private void PerformInitialize(InitializeType types = InitializeType.Everything) /// or "EVERYTHING" if 0. Additionally, this method will initialize SDL_ttf and SDL_image to load fonts and images. /// /// Bit flags indicating the way in which SDL should be initialized - private void InitializeBase(InitializeType types = InitializeType.Everything) + private void InitializeBase(InitializeType types) { if (SDL.SDL_Init((uint)types) != 0) { @@ -192,41 +215,19 @@ private void InitializeBase(InitializeType types = InitializeType.Everything) /// This is called before Draw in the main game loop. /// /// Allows access to total game time and elapsed game time since the last update - protected virtual void Update(GameTime gameTime) + protected abstract void Update(GameTime gameTime); + + private void PerformUpdate(GameTime gameTime) { Mouse.UpdateMouseState(); - - //if (rawEvents.Count > 0) - // RaiseGameEventFromRawEvent(rawEvents.Dequeue()); - - //elapsedTime += gameTime.ElapsedGameTime; - - //if (elapsedTime >= TimeSpan.FromSeconds(1)) - //{ - // elapsedTime -= TimeSpan.FromSeconds(1); - // frameRate = frameCounter; - // frameCounter = 0; - //} + Update(gameTime); } - //TrueTypeText fpsText; - /// Draw the current state of the game such as textures, surfaces, maps, and other visual content. /// This is called after Update in the main game loop. /// /// Allows access to total game time and elapsed game time since the last update - protected virtual void Draw(GameTime gameTime) - { - //frameCounter++; - - //string fps = String.Format("FPS: {0}", frameRate); - - //fpsText.UpdateText(fps); - - //Renderer.RenderTexture(fpsText.Texture, 0, 100); - - //Renderer.RenderPresent(); - } + protected abstract void Draw(GameTime gameTime); /// Used to unload game assets that were loaded during the LoadContent method. Usually, you use this to free /// any resources that should not be lingering any longer or are no longer required. diff --git a/SharpDL/GameTime.cs b/SharpDL/GameTime.cs index a842da5..2babbd6 100644 --- a/SharpDL/GameTime.cs +++ b/SharpDL/GameTime.cs @@ -1,9 +1,4 @@ -using SDL2; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System; namespace SharpDL { diff --git a/SharpDL/InitializeType.cs b/SharpDL/InitializeType.cs index d16a062..84b823a 100644 --- a/SharpDL/InitializeType.cs +++ b/SharpDL/InitializeType.cs @@ -1,19 +1,20 @@ using System; +using SDL2; namespace SharpDL { [Flags] - public enum InitializeType + public enum InitializeType : uint { - Timer = 1, - Audio = 16, - Video = 32, - Joystick = 512, - Haptic = 4096, - GameController = 8192, - Events = 16384, - Sensor = 32768, - Everything = 62001, - NoParachute = 1048576 + Timer = SDL.SDL_INIT_TIMER, + Audio = SDL.SDL_INIT_AUDIO, + Video = SDL.SDL_INIT_VIDEO, + Joystick = SDL.SDL_INIT_JOYSTICK, + Haptic = SDL.SDL_INIT_HAPTIC, + GameController = SDL.SDL_INIT_GAMECONTROLLER, + Events = SDL.SDL_INIT_EVENTS, + Sensor = SDL.SDL_INIT_SENSOR, + Everything = SDL.SDL_INIT_EVERYTHING, + NoParachute = SDL.SDL_INIT_NOPARACHUTE } } \ No newline at end of file diff --git a/SharpDL/MessageBox.cs b/SharpDL/MessageBox.cs index ab2c6ca..83363c1 100644 --- a/SharpDL/MessageBox.cs +++ b/SharpDL/MessageBox.cs @@ -1,31 +1,27 @@ using SDL2; using SharpDL.Graphics; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SharpDL { - public static class MessageBox - { - public enum MessageBoxType : uint - { - Error = SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, - Information = SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_INFORMATION, - Warning = SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_WARNING - } + public enum MessageBoxType : uint + { + Error = SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, + Information = SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_INFORMATION, + Warning = SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_WARNING + } - public static void Show(MessageBoxType messageBoxType, - String title, String message, Window parentWindow = null) - { - IntPtr parentWindowHandle = IntPtr.Zero; - if (parentWindow != null) - parentWindowHandle = parentWindow.Handle; + public static class MessageBox + { + public static void Show(MessageBoxType messageBoxType, + String title, String message, Window parentWindow = null) + { + IntPtr parentWindowHandle = IntPtr.Zero; + if (parentWindow != null) + parentWindowHandle = parentWindow.Handle; - SDL.SDL_ShowSimpleMessageBox((SDL.SDL_MessageBoxFlags)messageBoxType, - title, message, parentWindowHandle); - } - } + SDL.SDL_ShowSimpleMessageBox((SDL.SDL_MessageBoxFlags)messageBoxType, + title, message, parentWindowHandle); + } + } } diff --git a/SharpDL/Shared/LogManager.cs b/SharpDL/Shared/LogManager.cs new file mode 100644 index 0000000..68922f5 --- /dev/null +++ b/SharpDL/Shared/LogManager.cs @@ -0,0 +1,4 @@ +namespace SharpDL.Shared +{ + +} \ No newline at end of file diff --git a/SharpDL/SharpDL.csproj b/SharpDL/SharpDL.csproj index 857aa00..882e4e5 100644 --- a/SharpDL/SharpDL.csproj +++ b/SharpDL/SharpDL.csproj @@ -23,6 +23,7 @@ + \ No newline at end of file diff --git a/SharpDL/Timer.cs b/SharpDL/Timer.cs index 3663d6e..6c6a178 100644 --- a/SharpDL/Timer.cs +++ b/SharpDL/Timer.cs @@ -1,22 +1,18 @@ using SDL2; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SharpDL { public class Timer { - private UInt32 startTicks; - private UInt32 pausedTicks; - bool isStarted; - bool isPaused; + private uint startedAtTicks = 0; + private uint pausedTicks = 0; + private bool isStarted = false; + private bool isPaused = false; - public TimeSpan StartTime + public TimeSpan StartedAtTime { - get { return TimeSpan.FromMilliseconds((double)startTicks); } + get { return TimeSpan.FromMilliseconds((double)startedAtTicks); } } public TimeSpan ElapsedTime @@ -28,7 +24,7 @@ public TimeSpan ElapsedTime if (isPaused) return TimeSpan.FromMilliseconds((double)pausedTicks); else - return TimeSpan.FromMilliseconds((double)(SDL.SDL_GetTicks() - startTicks)); + return TimeSpan.FromMilliseconds((double)(SDL.SDL_GetTicks() - startedAtTicks)); } else return TimeSpan.Zero; @@ -39,19 +35,11 @@ public TimeSpan ElapsedTime public bool IsPaused { get { return isPaused; } } - public Timer() - { - startTicks = 0; - pausedTicks = 0; - isPaused = false; - isStarted = false; - } - public void Start() { isStarted = true; isPaused = false; - startTicks = SDL.SDL_GetTicks(); + startedAtTicks = SDL.SDL_GetTicks(); } public void Stop() @@ -65,7 +53,7 @@ public void Pause() if (isStarted && !isPaused) { isPaused = true; - pausedTicks = SDL.SDL_GetTicks() - startTicks; + pausedTicks = SDL.SDL_GetTicks() - startedAtTicks; } } @@ -74,7 +62,7 @@ public void Unpause() if (isPaused) { isPaused = false; - startTicks = SDL.SDL_GetTicks() - pausedTicks; + startedAtTicks = SDL.SDL_GetTicks() - pausedTicks; pausedTicks = 0; } } From b17098aa9d09ce648365e7fdefe003ad84537c71 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 10 Apr 2020 21:53:34 -0400 Subject: [PATCH 4/6] Added dependency injection. Added logging. Added DI helpers to bootstrap game engine. General cleanup. --- .../Example0_Sandbox/Example0_Sandbox.csproj | 49 +++++++++++++ Examples/Example0_Sandbox/MainGame.cs | 57 ++++++++++++++++ Examples/Example0_Sandbox/Program.cs | 36 ++++++++++ Examples/SharpDL_Examples.sln | 10 +++ SharpDL/Game.cs | 68 +++++-------------- SharpDL/Graphics/IRendererFactory.cs | 28 ++++++++ SharpDL/Graphics/IWindowFactory.cs | 40 +++++++++++ SharpDL/Graphics/Renderer.cs | 23 ++++--- SharpDL/Graphics/RendererFactory.cs | 47 +++++++++++++ SharpDL/Graphics/RendererFlags.cs | 1 + SharpDL/Graphics/Window.cs | 15 ++-- SharpDL/Graphics/WindowFactory.cs | 51 ++++++++++++++ SharpDL/Shared/LogManager.cs | 4 -- SharpDL/Shared/ServiceCollectionExtensions.cs | 29 ++++++++ SharpDL/Shared/Utilities.cs | 4 -- SharpDL/SharpDL.csproj | 1 + 16 files changed, 389 insertions(+), 74 deletions(-) create mode 100644 Examples/Example0_Sandbox/Example0_Sandbox.csproj create mode 100644 Examples/Example0_Sandbox/MainGame.cs create mode 100644 Examples/Example0_Sandbox/Program.cs create mode 100644 SharpDL/Graphics/IRendererFactory.cs create mode 100644 SharpDL/Graphics/IWindowFactory.cs create mode 100644 SharpDL/Graphics/RendererFactory.cs create mode 100644 SharpDL/Graphics/WindowFactory.cs delete mode 100644 SharpDL/Shared/LogManager.cs create mode 100644 SharpDL/Shared/ServiceCollectionExtensions.cs diff --git a/Examples/Example0_Sandbox/Example0_Sandbox.csproj b/Examples/Example0_Sandbox/Example0_Sandbox.csproj new file mode 100644 index 0000000..5f39dfd --- /dev/null +++ b/Examples/Example0_Sandbox/Example0_Sandbox.csproj @@ -0,0 +1,49 @@ + + + + + + + + Exe + netcoreapp3.1 + + + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + + + + + + + + + diff --git a/Examples/Example0_Sandbox/MainGame.cs b/Examples/Example0_Sandbox/MainGame.cs new file mode 100644 index 0000000..41ce2f7 --- /dev/null +++ b/Examples/Example0_Sandbox/MainGame.cs @@ -0,0 +1,57 @@ +using Microsoft.Extensions.Logging; +using SharpDL; +using SharpDL.Graphics; + +namespace Example0_Sandbox +{ + public class MainGame : Game + { + private readonly ILogger logger; + + public MainGame( + IWindowFactory windowFactory, + IRendererFactory rendererFactory, + ILogger logger = null, + ILogger baseLogger = null) + : base(windowFactory, rendererFactory, baseLogger) + { + this.logger = logger; + } + + /// Initialize SDL and any sub-systems. Window and Renderer must be initialized before use. + /// + protected override void Initialize() + { + Window = WindowFactory.CreateWindow("Example 0 - Sandbox"); + Renderer = RendererFactory.CreateRenderer(Window); + Renderer.SetRenderLogicalSize(1152, 720); + } + + /// Load any game assets such as textures and audio. + /// + protected override void LoadContent() + { + } + + /// Update the state of the game. + /// + /// + protected override void Update(GameTime gameTime) + { + } + + /// Render the current state of the game. + /// + /// + protected override void Draw(GameTime gameTime) + { + Renderer.RenderPresent(); + } + + /// Unload and dispose of any assets. Remember to dispose SDL-native objects! + /// + protected override void UnloadContent() + { + } + } +} \ No newline at end of file diff --git a/Examples/Example0_Sandbox/Program.cs b/Examples/Example0_Sandbox/Program.cs new file mode 100644 index 0000000..f678426 --- /dev/null +++ b/Examples/Example0_Sandbox/Program.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; +using SharpDL.Shared; + +namespace Example0_Sandbox +{ + class Program + { + static void Main(string[] args) + { + ServiceProvider serviceProvider = GetServiceProvider(); + var game = serviceProvider.GetService(); + game.Run(); + } + + private static ServiceProvider GetServiceProvider() + { + var services = new ServiceCollection(); + ConfigureServices(services); + var serviceProvider = services.BuildServiceProvider(); + return serviceProvider; + } + + private static void ConfigureServices(ServiceCollection services) + { + services.AddSharpGame() + .AddLogging(config => { + config.AddConsole(); + }) + .Configure(options => { + options.AddFilter(null, LogLevel.Trace); + }); + } + } +} diff --git a/Examples/SharpDL_Examples.sln b/Examples/SharpDL_Examples.sln index bd2c6dd..31aef26 100644 --- a/Examples/SharpDL_Examples.sln +++ b/Examples/SharpDL_Examples.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example1_BlankWindow", "Exa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example2_DrawTexture", "Example2_DrawTexture\Example2_DrawTexture.csproj", "{A877AE4B-FFF1-4447-BE1D-7D18CE034F58}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example0_Sandbox", "Example0_Sandbox\Example0_Sandbox.csproj", "{9368165A-A423-44BC-AE32-709C79848CB5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,6 +33,14 @@ Global {A877AE4B-FFF1-4447-BE1D-7D18CE034F58}.Release|Any CPU.Build.0 = Release|Any CPU {A877AE4B-FFF1-4447-BE1D-7D18CE034F58}.Release|x64.ActiveCfg = Release|Any CPU {A877AE4B-FFF1-4447-BE1D-7D18CE034F58}.Release|x64.Build.0 = Release|Any CPU + {9368165A-A423-44BC-AE32-709C79848CB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9368165A-A423-44BC-AE32-709C79848CB5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9368165A-A423-44BC-AE32-709C79848CB5}.Debug|x64.ActiveCfg = Debug|Any CPU + {9368165A-A423-44BC-AE32-709C79848CB5}.Debug|x64.Build.0 = Debug|Any CPU + {9368165A-A423-44BC-AE32-709C79848CB5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9368165A-A423-44BC-AE32-709C79848CB5}.Release|Any CPU.Build.0 = Release|Any CPU + {9368165A-A423-44BC-AE32-709C79848CB5}.Release|x64.ActiveCfg = Release|Any CPU + {9368165A-A423-44BC-AE32-709C79848CB5}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SharpDL/Game.cs b/SharpDL/Game.cs index 4d428af..4931a4e 100644 --- a/SharpDL/Game.cs +++ b/SharpDL/Game.cs @@ -3,36 +3,37 @@ using SharpDL.Events; using SharpDL.Graphics; using SharpDL.Input; +using SharpDL.Shared; using System; namespace SharpDL { public abstract class Game : IDisposable { - private readonly ILogger logger; - #region Members - private const uint EMPTY_UINT = 0; - private const int EMPTY_INT = -1; - private const float FRAMES_PER_SECOND = 60f; + private const float fixedFramesPerSecond = 60f; + private readonly ILogger logger; private readonly GameTime gameTime = new GameTime(); private readonly Timer gameTimer = new Timer(); - private TimeSpan accumulatedElapsedTime = TimeSpan.Zero; - private readonly TimeSpan targetElapsedTime = TimeSpan.FromSeconds(1 / FRAMES_PER_SECOND); + private readonly TimeSpan targetElapsedTime = TimeSpan.FromSeconds(1 / fixedFramesPerSecond); private readonly TimeSpan maxElapsedTime = TimeSpan.FromSeconds(0.5); + private TimeSpan accumulatedElapsedTime = TimeSpan.Zero; private bool isFrameRateCapped = true; #endregion Members #region Properties + protected IWindowFactory WindowFactory { get; private set; } + + protected IRendererFactory RendererFactory { get; private set; } protected EventManager EventManager { get; private set; } - protected Window Window { get; private set; } + protected Window Window { get; set; } - protected Renderer Renderer { get; private set; } + protected Renderer Renderer { get; set; } protected bool IsActive { get; private set; } @@ -45,8 +46,13 @@ public abstract class Game : IDisposable /// Default constructor of the base Game class does nothing. Only when Initialize is called /// is anything useful done. /// - public Game(ILogger logger = null) + public Game( + IWindowFactory windowFactory, + IRendererFactory rendererFactory, + ILogger logger = null) { + WindowFactory = windowFactory ?? throw new ArgumentNullException(nameof(windowFactory)); + RendererFactory = rendererFactory ?? throw new ArgumentNullException(nameof(rendererFactory)); this.logger = logger; EventManager = new EventManager(); EventManager.WindowClosed += OnExiting; @@ -79,6 +85,7 @@ public void Run(InitializeType types = InitializeType.Everything) SDL.SDL_Event rawEvent = new SDL.SDL_Event(); while (SDL.SDL_PollEvent(out rawEvent) == 1) { + logger?.LogTrace($"SDL_Event: {rawEvent.type.ToString()}"); EventManager.RaiseEvent(rawEvent); } @@ -236,47 +243,6 @@ private void PerformUpdate(GameTime gameTime) #endregion Game Cycle - #region Initializers - - /// Creates a SDL window to render content within. - /// - /// Title of the window - /// X position of the top left corner - /// Y position of the top left corner - /// Width of the window - /// Height of the window - /// Bit flags indicating the way in which the window should be created - protected void CreateWindow(string title, int x, int y, int width, int height, WindowFlags flags) - { - Window = new Window(title, x, y, width, height, flags); - } - - /// Creates a SDL Renderer to copy and draw textures to a window - /// - /// Bit flags indicating the way in which the renderer should be created - protected void CreateRenderer(RendererFlags flags) - { - CreateRenderer(EMPTY_INT, flags); - } - - /// Creates a SDL Renderer to copy and draw textures to a window - /// - /// Index of the renderering driver. -1 to choose the first available. - /// Bit flags indicating the way in which the renderer should be created - protected void CreateRenderer(int index, RendererFlags flags) - { - if (Window == null) - { - throw new InvalidOperationException("Window has not been initialized. You must first create a Window before creating a Renderer."); - } - - Renderer = new Renderer(this.Window, index, flags); - - SDL2.SDL.SDL_SetHint(SDL2.SDL.SDL_HINT_RENDER_SCALE_QUALITY, "linear"); - } - - #endregion Initializers - #region Dispose public virtual void Dispose() diff --git a/SharpDL/Graphics/IRendererFactory.cs b/SharpDL/Graphics/IRendererFactory.cs new file mode 100644 index 0000000..65dbf2d --- /dev/null +++ b/SharpDL/Graphics/IRendererFactory.cs @@ -0,0 +1,28 @@ +namespace SharpDL.Graphics +{ + public interface IRendererFactory + { + + /// Creates a Renderer paired with a Window to perform rendering. + /// + /// The window where rendering is displayed + /// Instance of a Renderer. + /// + Renderer CreateRenderer(Window window); + + /// Creates a Renderer paired with a Window to perform rendering. + /// + /// The window where rendering is displayed + /// The index of the rendering driver to initialize, or -1 to initialize the first one supporting the requested flags + /// Instance of a Renderer. + Renderer CreateRenderer(Window window, int index); + + /// Creates a Renderer paired with a Window to perform rendering. + /// + /// The window where rendering is displayed + /// The index of the rendering driver to initialize, or -1 to initialize the first one supporting the requested flags + /// 0, or one or more RendererFlags OR'd together + /// Instance of a Renderer. + Renderer CreateRenderer(Window window, int index, RendererFlags flags); + } +} \ No newline at end of file diff --git a/SharpDL/Graphics/IWindowFactory.cs b/SharpDL/Graphics/IWindowFactory.cs new file mode 100644 index 0000000..1572284 --- /dev/null +++ b/SharpDL/Graphics/IWindowFactory.cs @@ -0,0 +1,40 @@ +namespace SharpDL.Graphics +{ + public interface IWindowFactory + { + /// Creates a Window used for display and rendering. + /// + /// String displayed in the Window title bar. + /// Instance of a Window. + Window CreateWindow(string title); + + /// Creates a Window used for display and rendering. + /// + /// String displayed in the Window title bar. + /// X coordinate to position the Window. + /// Y coordinate to position the Window. + /// Instance of a Window. + Window CreateWindow(string title, int x, int y); + + /// Creates a Window used for display and rendering. + /// + /// String displayed in the Window title bar. + /// X coordinate to position the Window. + /// Y coordinate to position the Window. + /// Width of the Window. + /// Height of the Window. + /// Instance of a Window. + Window CreateWindow(string title, int x, int y, int width, int height); + + /// Creates a Window used for display and rendering. + /// + /// String displayed in the Window title bar. + /// X coordinate to position the Window. + /// Y coordinate to position the Window. + /// Width of the Window. + /// Height of the Window. + /// Flags to give special behaviors and features to the Window. + /// Instance of a Window. + Window CreateWindow(string title, int x, int y, int width, int height, WindowFlags flags); + } +} \ No newline at end of file diff --git a/SharpDL/Graphics/Renderer.cs b/SharpDL/Graphics/Renderer.cs index f199e0e..1e7dd0a 100644 --- a/SharpDL/Graphics/Renderer.cs +++ b/SharpDL/Graphics/Renderer.cs @@ -1,15 +1,14 @@ -using SDL2; +using Microsoft.Extensions.Logging; +using SDL2; using SharpDL.Shared; using System; using System.Collections.Generic; -using System.Diagnostics; namespace SharpDL.Graphics { public class Renderer : IDisposable { - //private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); - + private readonly ILogger logger; private List flags = new List(); public Window Window { get; private set; } @@ -20,13 +19,19 @@ public class Renderer : IDisposable public IntPtr Handle { get; private set; } - public Renderer(Window window, int index, RendererFlags flags) + internal Renderer(Window window, int index, RendererFlags flags, ILogger logger = null) { if (window == null) { - throw new ArgumentNullException(Errors.E_WINDOW_NULL); + throw new ArgumentNullException(nameof(window), "Window has not been initialized. You must first create a Window before creating a Renderer."); + } + + if (index < -1) + { + throw new ArgumentOutOfRangeException(nameof(index)); } + this.logger = logger; Window = window; Index = index; @@ -59,7 +64,7 @@ public void ClearScreen() internal void RenderTexture(IntPtr textureHandle, float positionX, float positionY, int sourceWidth, int sourceHeight, double angle, Vector center) { - if(textureHandle == IntPtr.Zero) + if (textureHandle == IntPtr.Zero) { throw new ArgumentNullException("textureHandle", Errors.E_TEXTURE_NULL); } @@ -171,7 +176,7 @@ public void SetRenderLogicalSize(int width, int height) private void ThrowExceptionIfRendererIsNull() { - if(Handle == IntPtr.Zero) + if (Handle == IntPtr.Zero) { throw new InvalidOperationException(Errors.E_RENDERER_NULL); } @@ -185,7 +190,7 @@ public void Dispose() ~Renderer() { - //log.Debug("A renderer resource has leaked. Did you forget to dispose the object?"); + logger?.LogWarning("A renderer resource has leaked. Did you forget to dispose the object?"); } private void Dispose(bool disposing) diff --git a/SharpDL/Graphics/RendererFactory.cs b/SharpDL/Graphics/RendererFactory.cs new file mode 100644 index 0000000..5b7d337 --- /dev/null +++ b/SharpDL/Graphics/RendererFactory.cs @@ -0,0 +1,47 @@ +using System; +using Microsoft.Extensions.Logging; +using SharpDL.Shared; + +namespace SharpDL.Graphics +{ + public class RendererFactory : IRendererFactory + { + private readonly ILogger logger; + private readonly ILogger loggerRenderer; + + public RendererFactory( + ILogger logger = null, + ILogger loggerRenderer = null) + { + this.logger = logger; + this.loggerRenderer = loggerRenderer; + } + + public Renderer CreateRenderer(Window window) + { + return CreateRenderer(window, -1, RendererFlags.None); + } + + public Renderer CreateRenderer(Window window, int index) + { + return CreateRenderer(window, index, RendererFlags.None); + } + + public Renderer CreateRenderer(Window window, int index, RendererFlags flags) + { + try + { + var renderer = new Renderer(window, index, flags, loggerRenderer); + logger?.LogTrace($"Renderer created. Handle = {renderer.Handle}, Window Title = {window.Title}, Window Handle = {window.Handle}."); + SDL2.SDL.SDL_SetHint(SDL2.SDL.SDL_HINT_RENDER_SCALE_QUALITY, "linear"); + return renderer; + } + catch(Exception ex) + { + logger?.LogError(ex, ex.Message); + throw; + } + } + + } +} \ No newline at end of file diff --git a/SharpDL/Graphics/RendererFlags.cs b/SharpDL/Graphics/RendererFlags.cs index 7740d1a..3467816 100644 --- a/SharpDL/Graphics/RendererFlags.cs +++ b/SharpDL/Graphics/RendererFlags.cs @@ -6,6 +6,7 @@ namespace SharpDL.Graphics [Flags] public enum RendererFlags : uint { + None = 0, RendererAccelerated = SDL.SDL_RendererFlags.SDL_RENDERER_ACCELERATED, RendererPresentVSync = SDL.SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC, SupportRenderTargets = SDL.SDL_RendererFlags.SDL_RENDERER_TARGETTEXTURE diff --git a/SharpDL/Graphics/Window.cs b/SharpDL/Graphics/Window.cs index 1e427c5..35cabcf 100644 --- a/SharpDL/Graphics/Window.cs +++ b/SharpDL/Graphics/Window.cs @@ -1,4 +1,5 @@ -using SDL2; +using Microsoft.Extensions.Logging; +using SDL2; using System; using System.Collections.Generic; @@ -6,7 +7,7 @@ namespace SharpDL.Graphics { public class Window : IDisposable { - //private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + private readonly ILogger logger; public string Title { get; private set; } @@ -22,13 +23,15 @@ public class Window : IDisposable public IntPtr Handle { get; private set; } - public Window(string title, int x, int y, int width, int height, WindowFlags flags) + internal Window(string title, int x, int y, int width, int height, WindowFlags flags, ILogger logger = null) { - if (String.IsNullOrEmpty(title)) + if(string.IsNullOrWhiteSpace(title)) { title = "SharpDL Window"; } + this.logger = logger; + Title = title; X = x; Y = y; @@ -45,7 +48,7 @@ public Window(string title, int x, int y, int width, int height, WindowFlags fla Handle = SDL.SDL_CreateWindow(this.Title, this.X, this.Y, this.Width, this.Height, (SDL.SDL_WindowFlags)flags); if (Handle == IntPtr.Zero) { - throw new InvalidOperationException(String.Format("SDL_CreateWindow: {0}", SDL.SDL_GetError())); + throw new InvalidOperationException($"SDL_CreateWindow: {SDL.SDL_GetError()}"); } } @@ -57,7 +60,7 @@ public void Dispose() ~Window() { - //log.Debug("A window resource has leaked. Did you forget to dispose the object?"); + logger.LogWarning("A window resource has leaked. Did you forget to dispose the object?"); } private void Dispose(bool isDisposing) diff --git a/SharpDL/Graphics/WindowFactory.cs b/SharpDL/Graphics/WindowFactory.cs new file mode 100644 index 0000000..e6aeb65 --- /dev/null +++ b/SharpDL/Graphics/WindowFactory.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.Extensions.Logging; +using SharpDL.Shared; + +namespace SharpDL.Graphics +{ + public class WindowFactory : IWindowFactory + { + private readonly ILogger logger; + private readonly ILogger loggerWindow; + + public WindowFactory( + ILogger logger = null, + ILogger loggerWindow = null) + { + this.logger = logger; + this.loggerWindow = loggerWindow; + } + + public Window CreateWindow(string title) + { + return CreateWindow(title, 100, 100, 1280, 720, WindowFlags.Shown); + } + + public Window CreateWindow(string title, int x, int y) + { + return CreateWindow(title, x, y, 1280, 720, WindowFlags.Shown); + } + + public Window CreateWindow(string title, int x, int y, int width, int height) + { + return CreateWindow(title, x, y, width, height, WindowFlags.Shown); + } + + public Window CreateWindow(string title, int x, int y, int width, int height, WindowFlags flags) + { + try + { + var window = new Window(title, x, y, width, height, flags, loggerWindow); + logger?.LogTrace($"Window created. Title = {window.Title}, X = {window.X}, Y = {window.Y}, Width = {window.Width}, Height = {window.Height}, Handle = {window.Handle}."); + return window; + } + catch(Exception ex) + { + logger?.LogError(ex, ex.Message); + throw; + } + } + + } +} \ No newline at end of file diff --git a/SharpDL/Shared/LogManager.cs b/SharpDL/Shared/LogManager.cs deleted file mode 100644 index 68922f5..0000000 --- a/SharpDL/Shared/LogManager.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace SharpDL.Shared -{ - -} \ No newline at end of file diff --git a/SharpDL/Shared/ServiceCollectionExtensions.cs b/SharpDL/Shared/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..55139f5 --- /dev/null +++ b/SharpDL/Shared/ServiceCollectionExtensions.cs @@ -0,0 +1,29 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using SharpDL.Graphics; + +namespace SharpDL.Shared +{ + public static class SharpGameServiceCollectionExtensions + { + /// Registers the base game services with the specified specified Microsoft.Extensions.DependencyInjection.IServiceCollection. + /// + /// Collection of services to register base game. + /// Type of main game class that inherits from SharpDL.Game to register with container. + /// The Microsoft.Extensions.DependencyInjection.IServiceCollection so that additional calls can be chained. + public static IServiceCollection AddSharpGame(this IServiceCollection services) + where T : Game + { + if(services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + return services; + } + } +} \ No newline at end of file diff --git a/SharpDL/Shared/Utilities.cs b/SharpDL/Shared/Utilities.cs index 1f33447..c6c1b12 100644 --- a/SharpDL/Shared/Utilities.cs +++ b/SharpDL/Shared/Utilities.cs @@ -1,9 +1,5 @@ using SDL2; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SharpDL.Shared { diff --git a/SharpDL/SharpDL.csproj b/SharpDL/SharpDL.csproj index 882e4e5..6c91963 100644 --- a/SharpDL/SharpDL.csproj +++ b/SharpDL/SharpDL.csproj @@ -23,6 +23,7 @@ + From f72c75a40c5ac724a54684c16631864fb48cfd9e Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 11 Apr 2020 14:50:22 -0400 Subject: [PATCH 5/6] Experimenting with compositional game engine rather than using inheritance. --- Examples/Example0_Sandbox/MainGame.cs | 41 +++++---- Examples/Example0_Sandbox/Program.cs | 3 +- SharpDL/Events/EventManager.cs | 6 +- SharpDL/Events/IEventManager.cs | 36 ++++++++ SharpDL/{Game.cs => GameEngine.cs} | 90 +++++++++---------- SharpDL/IGame.cs | 10 +++ SharpDL/IGameEngine.cs | 59 ++++++++++++ SharpDL/Shared/ServiceCollectionExtensions.cs | 12 ++- 8 files changed, 187 insertions(+), 70 deletions(-) create mode 100644 SharpDL/Events/IEventManager.cs rename SharpDL/{Game.cs => GameEngine.cs} (84%) create mode 100644 SharpDL/IGame.cs create mode 100644 SharpDL/IGameEngine.cs diff --git a/Examples/Example0_Sandbox/MainGame.cs b/Examples/Example0_Sandbox/MainGame.cs index 41ce2f7..85d58e3 100644 --- a/Examples/Example0_Sandbox/MainGame.cs +++ b/Examples/Example0_Sandbox/MainGame.cs @@ -4,53 +4,64 @@ namespace Example0_Sandbox { - public class MainGame : Game + public class MainGame : IGame { private readonly ILogger logger; + private IGameEngine engine; + private Window window; + private Renderer renderer; public MainGame( - IWindowFactory windowFactory, - IRendererFactory rendererFactory, - ILogger logger = null, - ILogger baseLogger = null) - : base(windowFactory, rendererFactory, baseLogger) + IGameEngine engine, + ILogger logger = null) { + this.engine = engine; this.logger = logger; + engine.Initialize = () => Initialize(); + engine.LoadContent = () => LoadContent(); + engine.Update = (gameTime) => Update(gameTime); + engine.Draw = (gameTime) => Draw(gameTime); + engine.UnloadContent = () => UnloadContent(); } + public void Run() + { + engine.Start(InitializeType.Everything); + } + /// Initialize SDL and any sub-systems. Window and Renderer must be initialized before use. /// - protected override void Initialize() + private void Initialize() { - Window = WindowFactory.CreateWindow("Example 0 - Sandbox"); - Renderer = RendererFactory.CreateRenderer(Window); - Renderer.SetRenderLogicalSize(1152, 720); + window = engine.WindowFactory.CreateWindow("Example 0 - Sandbox"); + renderer = engine.RendererFactory.CreateRenderer(window); + renderer.SetRenderLogicalSize(1152, 720); } /// Load any game assets such as textures and audio. /// - protected override void LoadContent() + private void LoadContent() { } /// Update the state of the game. /// /// - protected override void Update(GameTime gameTime) + private void Update(GameTime gameTime) { } /// Render the current state of the game. /// /// - protected override void Draw(GameTime gameTime) + private void Draw(GameTime gameTime) { - Renderer.RenderPresent(); + renderer.RenderPresent(); } /// Unload and dispose of any assets. Remember to dispose SDL-native objects! /// - protected override void UnloadContent() + private void UnloadContent() { } } diff --git a/Examples/Example0_Sandbox/Program.cs b/Examples/Example0_Sandbox/Program.cs index f678426..fec547b 100644 --- a/Examples/Example0_Sandbox/Program.cs +++ b/Examples/Example0_Sandbox/Program.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; +using SharpDL; using SharpDL.Shared; namespace Example0_Sandbox @@ -10,7 +11,7 @@ class Program static void Main(string[] args) { ServiceProvider serviceProvider = GetServiceProvider(); - var game = serviceProvider.GetService(); + var game = serviceProvider.GetService(); game.Run(); } diff --git a/SharpDL/Events/EventManager.cs b/SharpDL/Events/EventManager.cs index 85b3574..eb8fd9e 100644 --- a/SharpDL/Events/EventManager.cs +++ b/SharpDL/Events/EventManager.cs @@ -4,7 +4,7 @@ namespace SharpDL.Events { - public class EventManager + public class EventManager : IEventManager { public event EventHandler MouseWheelScrolling; @@ -61,12 +61,12 @@ public class EventManager /// /// /// /// - internal void RaiseExiting(object sender, EventArgs args) + public void RaiseExiting(object sender, EventArgs args) { RaiseEvent(Exiting, args); } - internal void RaiseEvent(SDL.SDL_Event rawEvent) + public void RaiseEvent(SDL.SDL_Event rawEvent) { var eventType = (GameEventType)rawEvent.type; switch(eventType) diff --git a/SharpDL/Events/IEventManager.cs b/SharpDL/Events/IEventManager.cs new file mode 100644 index 0000000..a60a823 --- /dev/null +++ b/SharpDL/Events/IEventManager.cs @@ -0,0 +1,36 @@ +using System; +using SDL2; + +namespace SharpDL.Events +{ + public interface IEventManager + { + event EventHandler MouseWheelScrolling; + event EventHandler MouseButtonPressed; + event EventHandler MouseButtonReleased; + event EventHandler MouseMoving; + event EventHandler TextInputting; + event EventHandler TextEditing; + event EventHandler KeyPressed; + event EventHandler KeyReleased; + event EventHandler VideoDeviceSystemEvent; + event EventHandler Quitting; + event EventHandler Exiting; + event EventHandler WindowShown; + event EventHandler WindowHidden; + event EventHandler WindowExposed; + event EventHandler WindowMoved; + event EventHandler WindowResized; + event EventHandler WindowSizeChanged; + event EventHandler WindowMinimized; + event EventHandler WindowMaximized; + event EventHandler WindowRestored; + event EventHandler WindowEntered; + event EventHandler WindowLeave; + event EventHandler WindowFocusGained; + event EventHandler WindowFocusLost; + event EventHandler WindowClosed; + void RaiseExiting(object sender, EventArgs args); + void RaiseEvent(SDL.SDL_Event rawEvent); + } +} \ No newline at end of file diff --git a/SharpDL/Game.cs b/SharpDL/GameEngine.cs similarity index 84% rename from SharpDL/Game.cs rename to SharpDL/GameEngine.cs index 4931a4e..4409d43 100644 --- a/SharpDL/Game.cs +++ b/SharpDL/GameEngine.cs @@ -3,41 +3,46 @@ using SharpDL.Events; using SharpDL.Graphics; using SharpDL.Input; -using SharpDL.Shared; using System; namespace SharpDL { - public abstract class Game : IDisposable + public sealed class GameEngine : IGameEngine { #region Members private const float fixedFramesPerSecond = 60f; - private readonly ILogger logger; + private bool isFrameRateCapped = true; + private readonly ILogger logger; private readonly GameTime gameTime = new GameTime(); private readonly Timer gameTimer = new Timer(); - private readonly TimeSpan targetElapsedTime = TimeSpan.FromSeconds(1 / fixedFramesPerSecond); private readonly TimeSpan maxElapsedTime = TimeSpan.FromSeconds(0.5); private TimeSpan accumulatedElapsedTime = TimeSpan.Zero; - private bool isFrameRateCapped = true; #endregion Members #region Properties - protected IWindowFactory WindowFactory { get; private set; } - protected IRendererFactory RendererFactory { get; private set; } + public IWindowFactory WindowFactory { get; private set; } + + public IRendererFactory RendererFactory { get; private set; } - protected EventManager EventManager { get; private set; } + public IEventManager EventManager { get; private set; } - protected Window Window { get; set; } + private bool IsActive { get; set; } - protected Renderer Renderer { get; set; } + private bool IsExiting { get; set; } - protected bool IsActive { get; private set; } + public Action Initialize { private get; set; } - protected bool IsExiting { get; private set; } + public Action LoadContent { private get; set; } + + public Action Update { private get; set; } + + public Action Draw { private get; set; } + + public Action UnloadContent { private get; set; } #endregion Properties @@ -46,15 +51,17 @@ public abstract class Game : IDisposable /// Default constructor of the base Game class does nothing. Only when Initialize is called /// is anything useful done. /// - public Game( + public GameEngine( IWindowFactory windowFactory, - IRendererFactory rendererFactory, - ILogger logger = null) + IRendererFactory rendererFactory, + IEventManager eventManager, + ILogger logger = null) { WindowFactory = windowFactory ?? throw new ArgumentNullException(nameof(windowFactory)); RendererFactory = rendererFactory ?? throw new ArgumentNullException(nameof(rendererFactory)); + EventManager = eventManager ?? throw new ArgumentNullException(nameof(eventManager)); this.logger = logger; - EventManager = new EventManager(); + EventManager.WindowClosed += OnExiting; EventManager.Quitting += OnExiting; } @@ -75,10 +82,10 @@ private void OnExiting(object sender, GameEventArgs e) /// Begins the game by performing the following cycle events in this order: Initialize, LoadContent, /// CheckInputs, Update, Draw, UnloadContent. /// - public void Run(InitializeType types = InitializeType.Everything) + public void Start(InitializeType types) { PerformInitialize(types); - LoadContent(); + PerformLoadContent(); while (!IsExiting) { @@ -92,7 +99,7 @@ public void Run(InitializeType types = InitializeType.Everything) Tick(); } - UnloadContent(); + PerformUnloadContent(); Dispose(); } @@ -154,12 +161,12 @@ private void Tick() PerformUpdate(gameTime); } - Draw(gameTime); + PerformDraw(gameTime); } /// Raises the Exiting event and disposes of this instance. /// - public void Quit() + public void End() { IsExiting = true; EventManager.RaiseExiting(this, EventArgs.Empty); @@ -171,16 +178,8 @@ public void Quit() /// Override to initialize any custom objects or large helpers that are required by the game. /// - protected abstract void Initialize(); - - /// - /// Template Method Pattern to require initialize of the game engine before calling the game's custom - /// initialize method. - /// - /// private void PerformInitialize(InitializeType types) { - // Initialize base SDL before the game's custom initialize InitializeBase(types); Initialize(); } @@ -216,14 +215,15 @@ private void InitializeBase(InitializeType types) /// Used for potentially long lasting operations that should only occur relatively rarely. Usually, this /// method is used to load images, textures, maps, sounds, videos, and other game assets at the beginning of a level or area. /// - protected abstract void LoadContent(); + private void PerformLoadContent() + { + LoadContent(); + } /// Update the state of the game such as positions, health, entity properties, and more. /// This is called before Draw in the main game loop. /// /// Allows access to total game time and elapsed game time since the last update - protected abstract void Update(GameTime gameTime); - private void PerformUpdate(GameTime gameTime) { Mouse.UpdateMouseState(); @@ -234,24 +234,30 @@ private void PerformUpdate(GameTime gameTime) /// This is called after Update in the main game loop. /// /// Allows access to total game time and elapsed game time since the last update - protected abstract void Draw(GameTime gameTime); + private void PerformDraw(GameTime gameTime) + { + Draw(gameTime); + } /// Used to unload game assets that were loaded during the LoadContent method. Usually, you use this to free /// any resources that should not be lingering any longer or are no longer required. /// - protected abstract void UnloadContent(); + private void PerformUnloadContent() + { + UnloadContent(); + } #endregion Game Cycle #region Dispose - public virtual void Dispose() + public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } - ~Game() + ~GameEngine() { Dispose(false); } @@ -260,18 +266,8 @@ public virtual void Dispose() /// base.Dispose() so that the base class objects are disposed as well. /// /// - protected virtual void Dispose(bool disposing) + private void Dispose(bool disposing) { - if (Window != null) - { - Window.Dispose(); - } - - if (Renderer != null) - { - Renderer.Dispose(); - } - SDL_ttf.TTF_Quit(); SDL_image.IMG_Quit(); SDL.SDL_Quit(); diff --git a/SharpDL/IGame.cs b/SharpDL/IGame.cs new file mode 100644 index 0000000..aef8cac --- /dev/null +++ b/SharpDL/IGame.cs @@ -0,0 +1,10 @@ +namespace SharpDL +{ + public interface IGame + { + /// + /// Entry point to the main game. This method should start the game engine. + /// + void Run(); + } +} \ No newline at end of file diff --git a/SharpDL/IGameEngine.cs b/SharpDL/IGameEngine.cs new file mode 100644 index 0000000..23f27ef --- /dev/null +++ b/SharpDL/IGameEngine.cs @@ -0,0 +1,59 @@ +using System; +using SharpDL.Events; +using SharpDL.Graphics; + +namespace SharpDL +{ + public interface IGameEngine : IDisposable + { + /// Defines the Initialize method that should be injected into the game loop. + /// This method should initialize any long running game assets, systems, and services. + /// + Action Initialize { set; } + + /// Defines the LoadContent method that should be injected into the game loop. + /// This method should load any game assets, images, sounds, models, etc. + /// + Action LoadContent { set; } + + /// Defines the Update method that should be injected into the game loop. + /// This method should update the game state when the game loop ticks forward in time. + /// + Action Update { set; } + + /// Defines the Draw method that should be injected into the game loop. + /// This method should render any assets, surfaces, textures, animations to a renderer. + /// + Action Draw { set; } + + /// Defines the UnloadContent method that should be injected into the game loop. + /// This method should dispose of any game assets (especially any unmanaged resources). + /// + Action UnloadContent { set; } + + /// Used to create windows in which a renderer will display assets. + /// + IWindowFactory WindowFactory { get; } + + /// Used to create renderers tied to a window in which assets will be displayed. + /// + IRendererFactory RendererFactory { get; } + + /// Used to subscribe to game loop events. + /// + IEventManager EventManager { get; } + + /// Starts the game engine by initializing chosen subsystems. + /// This method should start the game loop and utilize the various Action properties + /// defined on the IGameEngine interface. + /// + /// Enumerated subsystems to initialize. + void Start(InitializeType initilizeTypes); + + /// + /// Ends the game engine. + /// This method should dispose of and stop any subsystems and remaining assets. + /// + void End(); + } +} \ No newline at end of file diff --git a/SharpDL/Shared/ServiceCollectionExtensions.cs b/SharpDL/Shared/ServiceCollectionExtensions.cs index 55139f5..288d2e9 100644 --- a/SharpDL/Shared/ServiceCollectionExtensions.cs +++ b/SharpDL/Shared/ServiceCollectionExtensions.cs @@ -1,5 +1,7 @@ using System; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using SharpDL.Events; using SharpDL.Graphics; namespace SharpDL.Shared @@ -12,16 +14,18 @@ public static class SharpGameServiceCollectionExtensions /// Type of main game class that inherits from SharpDL.Game to register with container. /// The Microsoft.Extensions.DependencyInjection.IServiceCollection so that additional calls can be chained. public static IServiceCollection AddSharpGame(this IServiceCollection services) - where T : Game + where T : class, IGame { if(services == null) { throw new ArgumentNullException(nameof(services)); } - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.TryAdd(ServiceDescriptor.Singleton()); + services.TryAdd(ServiceDescriptor.Singleton()); + services.TryAdd(ServiceDescriptor.Singleton()); + services.TryAdd(ServiceDescriptor.Singleton()); + services.TryAdd(ServiceDescriptor.Singleton()); return services; } From 27d404156fb1a89cf3cc7c306a04680ef6f8f7ec Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 11 Apr 2020 16:59:36 -0400 Subject: [PATCH 6/6] More interfaces and documentation on Window and Renderer. --- SharpDL/Graphics/IRenderer.cs | 60 ++++++++++++++++++++++++++++ SharpDL/Graphics/IRendererFactory.cs | 6 +-- SharpDL/Graphics/IWindow.cs | 36 +++++++++++++++++ SharpDL/Graphics/IWindowFactory.cs | 8 ++-- SharpDL/Graphics/Renderer.cs | 23 ++++++++--- SharpDL/Graphics/RendererFactory.cs | 6 +-- SharpDL/Graphics/Window.cs | 22 ++++++++-- SharpDL/Graphics/WindowFactory.cs | 9 ++--- SharpDL/Shared/Errors.cs | 8 +--- 9 files changed, 146 insertions(+), 32 deletions(-) create mode 100644 SharpDL/Graphics/IRenderer.cs create mode 100644 SharpDL/Graphics/IWindow.cs diff --git a/SharpDL/Graphics/IRenderer.cs b/SharpDL/Graphics/IRenderer.cs new file mode 100644 index 0000000..08bcde7 --- /dev/null +++ b/SharpDL/Graphics/IRenderer.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; + +namespace SharpDL.Graphics +{ + public interface IRenderer : IDisposable + { + /// Window in which this Renderer was created and will display to. + /// + IWindow Window { get; } + + /// The index of the rendering driver to initialize, or -1 to initialize the first one supporting the requested flags. + /// + int Index { get; } + + /// Initial behavior flags of the Renderer upon creation. + /// + IEnumerable Flags { get; } + + /// Native pointer to Renderer as stored in memory by SDL2. TODO: should this be on the interface? + /// + IntPtr Handle { get; } + + /// Clears the screen to whatever color is set in the Renderer. + /// + void ClearScreen(); + + /// Updates the Window with any rendering committed to the Renderer. + /// + void RenderPresent(); + + /// Resets the Renderer's render target back to null. + /// + void ResetRenderTarget(); + + /// Updates the Renderer's blend mode such as Alpha, Additive, or Modulation. + /// + /// Blend mode to select. + void SetBlendMode(BlendMode blendMode); + + /// Updates the Renderer's current draw color to use with Rectangles, Lines, and Points. + /// + /// Red + /// Green + /// Blue + /// Alpha + void SetDrawColor(byte r, byte g, byte b, byte a); + + /// Sets the logical size in which the Renderer will draw. Useful when we want to render to a device independent resolution. + /// + /// Width of the logical size + /// Height of the logical size + void SetRenderLogicalSize(int width, int height); + + /// Updates the Renderer's current texture target for rendering operations. + /// + /// Texture to render to. + void SetRenderTarget(RenderTarget renderTarget); + } +} \ No newline at end of file diff --git a/SharpDL/Graphics/IRendererFactory.cs b/SharpDL/Graphics/IRendererFactory.cs index 65dbf2d..12cba04 100644 --- a/SharpDL/Graphics/IRendererFactory.cs +++ b/SharpDL/Graphics/IRendererFactory.cs @@ -8,14 +8,14 @@ public interface IRendererFactory /// The window where rendering is displayed /// Instance of a Renderer. /// - Renderer CreateRenderer(Window window); + IRenderer CreateRenderer(Window window); /// Creates a Renderer paired with a Window to perform rendering. /// /// The window where rendering is displayed /// The index of the rendering driver to initialize, or -1 to initialize the first one supporting the requested flags /// Instance of a Renderer. - Renderer CreateRenderer(Window window, int index); + IRenderer CreateRenderer(Window window, int index); /// Creates a Renderer paired with a Window to perform rendering. /// @@ -23,6 +23,6 @@ public interface IRendererFactory /// The index of the rendering driver to initialize, or -1 to initialize the first one supporting the requested flags /// 0, or one or more RendererFlags OR'd together /// Instance of a Renderer. - Renderer CreateRenderer(Window window, int index, RendererFlags flags); + IRenderer CreateRenderer(Window window, int index, RendererFlags flags); } } \ No newline at end of file diff --git a/SharpDL/Graphics/IWindow.cs b/SharpDL/Graphics/IWindow.cs new file mode 100644 index 0000000..47a4a7d --- /dev/null +++ b/SharpDL/Graphics/IWindow.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace SharpDL.Graphics +{ + public interface IWindow : IDisposable + { + /// Title of the Window upon creation. + /// + string Title { get; } + + /// Initial X coordinate of Window upon creation. Value does not update when Window is moved. + /// + int X { get; } + + /// Initial Y coordinate of Window upon creation. Value does not update when Window is moved. + /// + int Y { get; } + + /// Initial width of Window upon creation. Value does not update when Window is resized. + /// + int Width { get; } + + /// Initial height of Window upon creation. Value does not update when Window is resized. + /// + int Height { get; } + + /// Initial behavior flags of the Window upon creation. + /// + IEnumerable Flags { get; } + + /// Native pointer to Window as stored in memory by SDL2. TODO: should this be on the interface? + /// + IntPtr Handle { get; } + } +} \ No newline at end of file diff --git a/SharpDL/Graphics/IWindowFactory.cs b/SharpDL/Graphics/IWindowFactory.cs index 1572284..cdbd363 100644 --- a/SharpDL/Graphics/IWindowFactory.cs +++ b/SharpDL/Graphics/IWindowFactory.cs @@ -6,7 +6,7 @@ public interface IWindowFactory /// /// String displayed in the Window title bar. /// Instance of a Window. - Window CreateWindow(string title); + IWindow CreateWindow(string title); /// Creates a Window used for display and rendering. /// @@ -14,7 +14,7 @@ public interface IWindowFactory /// X coordinate to position the Window. /// Y coordinate to position the Window. /// Instance of a Window. - Window CreateWindow(string title, int x, int y); + IWindow CreateWindow(string title, int x, int y); /// Creates a Window used for display and rendering. /// @@ -24,7 +24,7 @@ public interface IWindowFactory /// Width of the Window. /// Height of the Window. /// Instance of a Window. - Window CreateWindow(string title, int x, int y, int width, int height); + IWindow CreateWindow(string title, int x, int y, int width, int height); /// Creates a Window used for display and rendering. /// @@ -35,6 +35,6 @@ public interface IWindowFactory /// Height of the Window. /// Flags to give special behaviors and features to the Window. /// Instance of a Window. - Window CreateWindow(string title, int x, int y, int width, int height, WindowFlags flags); + IWindow CreateWindow(string title, int x, int y, int width, int height, WindowFlags flags); } } \ No newline at end of file diff --git a/SharpDL/Graphics/Renderer.cs b/SharpDL/Graphics/Renderer.cs index 1e7dd0a..eabe008 100644 --- a/SharpDL/Graphics/Renderer.cs +++ b/SharpDL/Graphics/Renderer.cs @@ -6,12 +6,13 @@ namespace SharpDL.Graphics { - public class Renderer : IDisposable + public class Renderer : IRenderer { private readonly ILogger logger; + private List flags = new List(); - public Window Window { get; private set; } + public IWindow Window { get; private set; } public int Index { get; private set; } @@ -19,7 +20,7 @@ public class Renderer : IDisposable public IntPtr Handle { get; private set; } - internal Renderer(Window window, int index, RendererFlags flags, ILogger logger = null) + internal Renderer(IWindow window, int index, RendererFlags flags, ILogger logger = null) { if (window == null) { @@ -66,7 +67,7 @@ internal void RenderTexture(IntPtr textureHandle, float positionX, float positio { if (textureHandle == IntPtr.Zero) { - throw new ArgumentNullException("textureHandle", Errors.E_TEXTURE_NULL); + throw new ArgumentNullException(nameof(textureHandle), Errors.E_TEXTURE_NULL); } // SDL only accepts integer positions (x,y) in the rendering Rect @@ -91,7 +92,7 @@ internal void RenderTexture(IntPtr textureHandle, float positionX, float positio { if (textureHandle == IntPtr.Zero) { - throw new ArgumentNullException("textureHandle", Errors.E_TEXTURE_NULL); + throw new ArgumentNullException(nameof(textureHandle), Errors.E_TEXTURE_NULL); } int width = source.Width; @@ -147,7 +148,7 @@ public void SetBlendMode(BlendMode blendMode) int result = SDL2.SDL.SDL_SetRenderDrawBlendMode(Handle, (SDL2.SDL.SDL_BlendMode)blendMode); if (Utilities.IsError(result)) { - throw new InvalidOperationException(Utilities.GetErrorMessage("SDL_SetDrawBlendMode")); + throw new InvalidOperationException(Utilities.GetErrorMessage("SDL_SetRenderDrawBlendMode")); } } @@ -165,6 +166,16 @@ public void SetDrawColor(byte r, byte g, byte b, byte a) public void SetRenderLogicalSize(int width, int height) { + if (width < 0) + { + throw new ArgumentOutOfRangeException(nameof(width)); + } + + if (height < 0) + { + throw new ArgumentOutOfRangeException(nameof(height)); + } + ThrowExceptionIfRendererIsNull(); int result = SDL2.SDL.SDL_RenderSetLogicalSize(Handle, width, height); diff --git a/SharpDL/Graphics/RendererFactory.cs b/SharpDL/Graphics/RendererFactory.cs index 5b7d337..23210a4 100644 --- a/SharpDL/Graphics/RendererFactory.cs +++ b/SharpDL/Graphics/RendererFactory.cs @@ -17,17 +17,17 @@ public RendererFactory( this.loggerRenderer = loggerRenderer; } - public Renderer CreateRenderer(Window window) + public IRenderer CreateRenderer(Window window) { return CreateRenderer(window, -1, RendererFlags.None); } - public Renderer CreateRenderer(Window window, int index) + public IRenderer CreateRenderer(Window window, int index) { return CreateRenderer(window, index, RendererFlags.None); } - public Renderer CreateRenderer(Window window, int index, RendererFlags flags) + public IRenderer CreateRenderer(Window window, int index, RendererFlags flags) { try { diff --git a/SharpDL/Graphics/Window.cs b/SharpDL/Graphics/Window.cs index 35cabcf..1acabcd 100644 --- a/SharpDL/Graphics/Window.cs +++ b/SharpDL/Graphics/Window.cs @@ -5,7 +5,7 @@ namespace SharpDL.Graphics { - public class Window : IDisposable + public class Window : IWindow { private readonly ILogger logger; @@ -25,13 +25,23 @@ public class Window : IDisposable internal Window(string title, int x, int y, int width, int height, WindowFlags flags, ILogger logger = null) { - if(string.IsNullOrWhiteSpace(title)) + if (string.IsNullOrWhiteSpace(title)) { title = "SharpDL Window"; } + if (width < 0) + { + width = 0; + } + + if (height < 0) + { + height = 0; + } + this.logger = logger; - + Title = title; X = x; Y = y; @@ -40,8 +50,12 @@ internal Window(string title, int x, int y, int width, int height, WindowFlags f List copyFlags = new List(); foreach (WindowFlags flag in Enum.GetValues(typeof(WindowFlags))) + { if (flags.HasFlag(flag)) + { copyFlags.Add(flag); + } + } Flags = copyFlags; @@ -60,7 +74,7 @@ public void Dispose() ~Window() { - logger.LogWarning("A window resource has leaked. Did you forget to dispose the object?"); + logger?.LogWarning("A window resource has leaked. Did you forget to dispose the object?"); } private void Dispose(bool isDisposing) diff --git a/SharpDL/Graphics/WindowFactory.cs b/SharpDL/Graphics/WindowFactory.cs index e6aeb65..cad6070 100644 --- a/SharpDL/Graphics/WindowFactory.cs +++ b/SharpDL/Graphics/WindowFactory.cs @@ -1,6 +1,5 @@ using System; using Microsoft.Extensions.Logging; -using SharpDL.Shared; namespace SharpDL.Graphics { @@ -17,22 +16,22 @@ public WindowFactory( this.loggerWindow = loggerWindow; } - public Window CreateWindow(string title) + public IWindow CreateWindow(string title) { return CreateWindow(title, 100, 100, 1280, 720, WindowFlags.Shown); } - public Window CreateWindow(string title, int x, int y) + public IWindow CreateWindow(string title, int x, int y) { return CreateWindow(title, x, y, 1280, 720, WindowFlags.Shown); } - public Window CreateWindow(string title, int x, int y, int width, int height) + public IWindow CreateWindow(string title, int x, int y, int width, int height) { return CreateWindow(title, x, y, width, height, WindowFlags.Shown); } - public Window CreateWindow(string title, int x, int y, int width, int height, WindowFlags flags) + public IWindow CreateWindow(string title, int x, int y, int width, int height, WindowFlags flags) { try { diff --git a/SharpDL/Shared/Errors.cs b/SharpDL/Shared/Errors.cs index 57902f9..bb705d7 100644 --- a/SharpDL/Shared/Errors.cs +++ b/SharpDL/Shared/Errors.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SharpDL.Shared +namespace SharpDL.Shared { public static class Errors {