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..85d58e3 --- /dev/null +++ b/Examples/Example0_Sandbox/MainGame.cs @@ -0,0 +1,68 @@ +using Microsoft.Extensions.Logging; +using SharpDL; +using SharpDL.Graphics; + +namespace Example0_Sandbox +{ + public class MainGame : IGame + { + private readonly ILogger logger; + private IGameEngine engine; + private Window window; + private Renderer renderer; + + public MainGame( + 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. + /// + private void Initialize() + { + 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. + /// + private void LoadContent() + { + } + + /// Update the state of the game. + /// + /// + private void Update(GameTime gameTime) + { + } + + /// Render the current state of the game. + /// + /// + private void Draw(GameTime gameTime) + { + renderer.RenderPresent(); + } + + /// Unload and dispose of any assets. Remember to dispose SDL-native objects! + /// + private 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..fec547b --- /dev/null +++ b/Examples/Example0_Sandbox/Program.cs @@ -0,0 +1,37 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; +using SharpDL; +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/Events/EventManager.cs b/SharpDL/Events/EventManager.cs new file mode 100644 index 0000000..eb8fd9e --- /dev/null +++ b/SharpDL/Events/EventManager.cs @@ -0,0 +1,164 @@ +using System; +using SDL2; +using SharpDL.Input; + +namespace SharpDL.Events +{ + public class EventManager : IEventManager + { + 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 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 RaiseExiting(object sender, EventArgs args) + { + RaiseEvent(Exiting, args); + } + + public void RaiseEvent(SDL.SDL_Event rawEvent) + { + var eventType = (GameEventType)rawEvent.type; + switch(eventType) + { + case GameEventType.First: + return; + case GameEventType.Window: + 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..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 { - None = 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/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/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 deleted file mode 100644 index 1ac421c..0000000 --- a/SharpDL/Game.cs +++ /dev/null @@ -1,491 +0,0 @@ -using SDL2; -using SharpDL.Events; -using SharpDL.Graphics; -using SharpDL.Input; -using System; - -namespace SharpDL -{ - public 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 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 Window Window { get; private set; } - - protected Renderer Renderer { get; private set; } - - protected bool IsActive { get; private set; } - - protected bool IsExiting { get; private set; } - - #endregion Properties - - #region Constructors - - /// Default constructor of the base Game class does nothing. Only when Initialize is called - /// is anything useful done. - /// - public Game() - { - WindowClosed += Game_WindowClosed; - } - - private void Game_WindowClosed(object sender, GameEventArgs e) - { - IsExiting = true; - } - - #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) - { - 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); - } - - #endregion Events - - #region Game Cycle Control - - /// Begins the game by performing the following cycle events in this order: Initialize, LoadContent, - /// CheckInputs, Update, Draw, UnloadContent. - /// - public void Run() - { - Initialize(); - LoadContent(); - - while (!IsExiting) - { - SDL.SDL_Event rawEvent = new SDL.SDL_Event(); - while (SDL.SDL_PollEvent(out rawEvent) == 1) - RaiseGameEventFromRawEvent(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 - /// methods to be overridden by each implementation's specific Game Update and Draw logic. This method is based heavily on MonoGame's - /// tick implementation and suggestions from Glenn Fiedler's blog (http://gafferongames.com/game-physics/fix-your-timestep/). - /// - private void Tick() - { - while (isFrameRateCapped && (accumulatedElapsedTime < targetElapsedTime)) - { - accumulatedElapsedTime += gameTimer.ElapsedTime; - gameTimer.Stop(); - gameTimer.Start(); - - if (isFrameRateCapped && (accumulatedElapsedTime < targetElapsedTime)) - { - TimeSpan sleepTime = targetElapsedTime - accumulatedElapsedTime; - SDL.SDL_Delay((UInt32)sleepTime.TotalMilliseconds); - } - } - - if (accumulatedElapsedTime > TimeSpan.FromSeconds(0.5)) - accumulatedElapsedTime = TimeSpan.FromSeconds(0.5); - - if (isFrameRateCapped) - { - int stepCount = 0; - - while (accumulatedElapsedTime >= targetElapsedTime) - { - gameTime.TotalGameTime += targetElapsedTime; - accumulatedElapsedTime -= targetElapsedTime; - stepCount++; - - Update(gameTime); - } - - gameTime.ElapsedGameTime = TimeSpan.FromTicks(targetElapsedTime.Ticks * stepCount); - } - - Draw(gameTime); - } - - /// Raises the Exiting event and disposes of this instance. - /// - public void Quit() - { - IsExiting = true; - RaiseEvent(Exiting, 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) - { - if (flags == EMPTY_UINT) - flags = SDL.SDL_INIT_EVERYTHING; - - if (SDL.SDL_Init(flags) != 0) - { - throw new InvalidOperationException(String.Format("SDL_Init: {0}", SDL.SDL_GetError())); - } - - if (SDL_ttf.TTF_Init() != 0) - { - throw new InvalidOperationException(String.Format("TTF_Init: {0}", 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) - { - throw new InvalidOperationException(String.Format("IMG_Init: {0}", SDL.SDL_GetError())); - } - } - - /// 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; - - /// 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 virtual void Update(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; - //} - } - - //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(); - } - - /// 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(); - } - - #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 void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - ~Game() - { - Dispose(false); - } - - protected virtual 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(); - RaiseEvent(Disposed, EventArgs.Empty); - } - - #endregion Dispose - } -} \ No newline at end of file diff --git a/SharpDL/GameEngine.cs b/SharpDL/GameEngine.cs new file mode 100644 index 0000000..4409d43 --- /dev/null +++ b/SharpDL/GameEngine.cs @@ -0,0 +1,278 @@ +using Microsoft.Extensions.Logging; +using SDL2; +using SharpDL.Events; +using SharpDL.Graphics; +using SharpDL.Input; +using System; + +namespace SharpDL +{ + public sealed class GameEngine : IGameEngine + { + #region Members + + private const float fixedFramesPerSecond = 60f; + 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; + + #endregion Members + + #region Properties + + public IWindowFactory WindowFactory { get; private set; } + + public IRendererFactory RendererFactory { get; private set; } + + public IEventManager EventManager { get; private set; } + + private bool IsActive { get; set; } + + private bool IsExiting { get; set; } + + public Action Initialize { private get; 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 + + #region Constructors + + /// Default constructor of the base Game class does nothing. Only when Initialize is called + /// is anything useful done. + /// + public GameEngine( + IWindowFactory windowFactory, + 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.WindowClosed += OnExiting; + EventManager.Quitting += OnExiting; + } + + #endregion Constructors + + #region Events + + private void OnExiting(object sender, GameEventArgs e) + { + IsExiting = true; + } + + #endregion Events + + #region Game Cycle Control + + /// Begins the game by performing the following cycle events in this order: Initialize, LoadContent, + /// CheckInputs, Update, Draw, UnloadContent. + /// + public void Start(InitializeType types) + { + PerformInitialize(types); + PerformLoadContent(); + + while (!IsExiting) + { + 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); + } + + Tick(); + } + + PerformUnloadContent(); + 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, + /// 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 + /// methods to be overridden by each implementation's specific Game Update and Draw logic. This method is based heavily on MonoGame's + /// tick implementation and suggestions from Glenn Fiedler's blog (http://gafferongames.com/game-physics/fix-your-timestep/). + /// + 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.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((uint)sleepTime.TotalMilliseconds); + } + } + + // 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++; + + 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); + } + + PerformDraw(gameTime); + } + + /// Raises the Exiting event and disposes of this instance. + /// + public void End() + { + IsExiting = true; + 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. + /// + private void PerformInitialize(InitializeType types) + { + 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 + private void InitializeBase(InitializeType types) + { + if (SDL.SDL_Init((uint)types) != 0) + { + throw new InvalidOperationException($"SDL_Init: {SDL.SDL_GetError()}"); + } + + if (SDL_ttf.TTF_Init() != 0) + { + throw new InvalidOperationException($"TTF_Init: {SDL.SDL_GetError()}"); + } + + 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($"IMG_Init: {SDL.SDL_GetError()}"); + } + } + + /// 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. + /// + 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 + private void PerformUpdate(GameTime gameTime) + { + Mouse.UpdateMouseState(); + Update(gameTime); + } + + /// 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 + 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. + /// + private void PerformUnloadContent() + { + UnloadContent(); + } + + #endregion Game Cycle + + #region Dispose + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~GameEngine() + { + 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. + /// + /// + private void Dispose(bool disposing) + { + SDL_ttf.TTF_Quit(); + SDL_image.IMG_Quit(); + SDL.SDL_Quit(); + } + + #endregion Dispose + } +} \ No newline at end of file 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/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 new file mode 100644 index 0000000..12cba04 --- /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. + /// + 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. + IRenderer 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. + 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 new file mode 100644 index 0000000..cdbd363 --- /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. + IWindow 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. + IWindow 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. + IWindow 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. + 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 f199e0e..eabe008 100644 --- a/SharpDL/Graphics/Renderer.cs +++ b/SharpDL/Graphics/Renderer.cs @@ -1,18 +1,18 @@ -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 + public class Renderer : IRenderer { - //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; } + public IWindow Window { get; private set; } public int Index { get; private set; } @@ -20,13 +20,19 @@ public class Renderer : IDisposable public IntPtr Handle { get; private set; } - public Renderer(Window window, int index, RendererFlags flags) + internal Renderer(IWindow 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,9 +65,9 @@ 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); + throw new ArgumentNullException(nameof(textureHandle), Errors.E_TEXTURE_NULL); } // SDL only accepts integer positions (x,y) in the rendering Rect @@ -86,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; @@ -142,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")); } } @@ -160,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); @@ -171,7 +187,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 +201,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..23210a4 --- /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 IRenderer CreateRenderer(Window window) + { + return CreateRenderer(window, -1, RendererFlags.None); + } + + public IRenderer CreateRenderer(Window window, int index) + { + return CreateRenderer(window, index, RendererFlags.None); + } + + public IRenderer 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..1acabcd 100644 --- a/SharpDL/Graphics/Window.cs +++ b/SharpDL/Graphics/Window.cs @@ -1,12 +1,13 @@ -using SDL2; +using Microsoft.Extensions.Logging; +using SDL2; using System; using System.Collections.Generic; namespace SharpDL.Graphics { - public class Window : IDisposable + public class Window : IWindow { - //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,25 @@ 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"; } + if (width < 0) + { + width = 0; + } + + if (height < 0) + { + height = 0; + } + + this.logger = logger; + Title = title; X = x; Y = y; @@ -37,15 +50,19 @@ public Window(string title, int x, int y, int width, int height, WindowFlags fla List copyFlags = new List(); foreach (WindowFlags flag in Enum.GetValues(typeof(WindowFlags))) + { if (flags.HasFlag(flag)) + { copyFlags.Add(flag); + } + } Flags = copyFlags; 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 +74,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..cad6070 --- /dev/null +++ b/SharpDL/Graphics/WindowFactory.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.Extensions.Logging; + +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 IWindow CreateWindow(string title) + { + return CreateWindow(title, 100, 100, 1280, 720, WindowFlags.Shown); + } + + public IWindow CreateWindow(string title, int x, int y) + { + return CreateWindow(title, x, y, 1280, 720, WindowFlags.Shown); + } + + public IWindow CreateWindow(string title, int x, int y, int width, int height) + { + return CreateWindow(title, x, y, width, height, WindowFlags.Shown); + } + + public IWindow 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/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/InitializeType.cs b/SharpDL/InitializeType.cs new file mode 100644 index 0000000..84b823a --- /dev/null +++ b/SharpDL/InitializeType.cs @@ -0,0 +1,20 @@ +using System; +using SDL2; + +namespace SharpDL +{ + [Flags] + public enum InitializeType : uint + { + 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/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 { diff --git a/SharpDL/Shared/ServiceCollectionExtensions.cs b/SharpDL/Shared/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..288d2e9 --- /dev/null +++ b/SharpDL/Shared/ServiceCollectionExtensions.cs @@ -0,0 +1,33 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using SharpDL.Events; +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 : class, IGame + { + if(services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + services.TryAdd(ServiceDescriptor.Singleton()); + services.TryAdd(ServiceDescriptor.Singleton()); + services.TryAdd(ServiceDescriptor.Singleton()); + services.TryAdd(ServiceDescriptor.Singleton()); + services.TryAdd(ServiceDescriptor.Singleton()); + + 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 857aa00..6c91963 100644 --- a/SharpDL/SharpDL.csproj +++ b/SharpDL/SharpDL.csproj @@ -23,6 +23,8 @@ + + \ 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; } }