diff --git a/.gitignore b/.gitignore index cd42ee3..03c9b93 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ bin/ obj/ +.vs/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..29b9556 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "lib/SDL2-CS"] + path = lib/SDL2-CS + url = https://github.com/flibitijibibo/SDL2-CS.git +[submodule "lib/Campari"] + path = lib/Campari + url = https://gitea.moonside.games/MoonsideGames/Campari.git diff --git a/Game.cs b/Game.cs deleted file mode 100644 index 7fc265d..0000000 --- a/Game.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace MoonWorks -{ - public class Game - { - } -} diff --git a/MoonWorks.csproj b/MoonWorks.csproj index 9f5c4f4..03f93ad 100644 --- a/MoonWorks.csproj +++ b/MoonWorks.csproj @@ -4,4 +4,13 @@ netstandard2.0 + + $(DefaultItemExcludes);lib\**\* + + + + + + + diff --git a/MoonWorks.sln b/MoonWorks.sln index 76a7124..2004c14 100644 --- a/MoonWorks.sln +++ b/MoonWorks.sln @@ -1,34 +1,43 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26124.0 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30717.126 MinimumVisualStudioVersion = 15.0.26124.0 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoonWorks", "MoonWorks.csproj", "{DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MoonWorks", "MoonWorks.csproj", "{DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SDL2-CS.Core", "lib\SDL2-CS\SDL2-CS.Core.csproj", "{0929F2D8-8FE4-4452-AD1E-50760A1A19A5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Campari", "lib\Campari\Campari.csproj", "{D09577DE-99F5-4C30-9165-4012C37CE0CE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RefreshCS", "lib\Campari\lib\RefreshCS\RefreshCS.csproj", "{66116A40-B360-4BA3-966A-A54F3E562EC1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 - Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Debug|x64.ActiveCfg = Debug|x64 + {DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Debug|x64.Build.0 = Debug|x64 + {DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Release|x64.ActiveCfg = Release|x64 + {DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Release|x64.Build.0 = Release|x64 + {0929F2D8-8FE4-4452-AD1E-50760A1A19A5}.Debug|x64.ActiveCfg = Debug|x64 + {0929F2D8-8FE4-4452-AD1E-50760A1A19A5}.Debug|x64.Build.0 = Debug|x64 + {0929F2D8-8FE4-4452-AD1E-50760A1A19A5}.Release|x64.ActiveCfg = Release|x64 + {0929F2D8-8FE4-4452-AD1E-50760A1A19A5}.Release|x64.Build.0 = Release|x64 + {D09577DE-99F5-4C30-9165-4012C37CE0CE}.Debug|x64.ActiveCfg = Debug|x64 + {D09577DE-99F5-4C30-9165-4012C37CE0CE}.Debug|x64.Build.0 = Debug|x64 + {D09577DE-99F5-4C30-9165-4012C37CE0CE}.Release|x64.ActiveCfg = Release|x64 + {D09577DE-99F5-4C30-9165-4012C37CE0CE}.Release|x64.Build.0 = Release|x64 + {66116A40-B360-4BA3-966A-A54F3E562EC1}.Debug|x64.ActiveCfg = Debug|x64 + {66116A40-B360-4BA3-966A-A54F3E562EC1}.Debug|x64.Build.0 = Debug|x64 + {66116A40-B360-4BA3-966A-A54F3E562EC1}.Release|x64.ActiveCfg = Release|x64 + {66116A40-B360-4BA3-966A-A54F3E562EC1}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Debug|x64.ActiveCfg = Debug|Any CPU - {DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Debug|x64.Build.0 = Debug|Any CPU - {DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Debug|x86.ActiveCfg = Debug|Any CPU - {DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Debug|x86.Build.0 = Debug|Any CPU - {DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Release|Any CPU.Build.0 = Release|Any CPU - {DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Release|x64.ActiveCfg = Release|Any CPU - {DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Release|x64.Build.0 = Release|Any CPU - {DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Release|x86.ActiveCfg = Release|Any CPU - {DDC9BA9B-4440-4CB3-BDB4-D5F91DE1686B}.Release|x86.Build.0 = Release|Any CPU + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C3D68FAA-3165-43C7-95B3-D845F0DAA918} EndGlobalSection EndGlobal diff --git a/lib/Campari b/lib/Campari new file mode 160000 index 0000000..f01b9a3 --- /dev/null +++ b/lib/Campari @@ -0,0 +1 @@ +Subproject commit f01b9a3a9acff8672e0d7da253ee2402fcdaa281 diff --git a/lib/SDL2-CS b/lib/SDL2-CS new file mode 160000 index 0000000..1e01bc8 --- /dev/null +++ b/lib/SDL2-CS @@ -0,0 +1 @@ +Subproject commit 1e01bc8eebb501bf6df24ec98784c32843308e0a diff --git a/src/Game.cs b/src/Game.cs new file mode 100644 index 0000000..a44e4ae --- /dev/null +++ b/src/Game.cs @@ -0,0 +1,94 @@ +using System; +using SDL2; +using Campari; +using System.Collections.Generic; + +namespace MoonWorks +{ + public abstract class Game + { + private bool quit = false; + private double timestep; + ulong currentTime = SDL.SDL_GetPerformanceCounter(); + double accumulator = 0; + + public IntPtr WindowHandle { get; } + public GraphicsDevice GraphicsDevice { get; } + + public Input Input { get; } + + private Dictionary moonWorksToRefreshPresentMode = new Dictionary + { + { PresentMode.Immediate, RefreshCS.Refresh.PresentMode.Immediate }, + { PresentMode.Mailbox, RefreshCS.Refresh.PresentMode.Mailbox }, + { PresentMode.FIFO, RefreshCS.Refresh.PresentMode.FIFO }, + { PresentMode.FIFORelaxed, RefreshCS.Refresh.PresentMode.FIFORelaxed } + }; + + public Game(uint windowWidth, uint windowHeight, PresentMode presentMode, int targetTimestep = 60, bool debugMode = false) + { + timestep = 1.0 / targetTimestep; + + if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_TIMER | SDL.SDL_INIT_GAMECONTROLLER) < 0) + { + System.Console.WriteLine("Failed to initialize SDL!"); + return; + } + + WindowHandle = SDL.SDL_CreateWindow( + "CampariTest", + SDL.SDL_WINDOWPOS_UNDEFINED, + SDL.SDL_WINDOWPOS_UNDEFINED, + (int)windowWidth, + (int)windowHeight, + SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN + ); + + GraphicsDevice = new GraphicsDevice(WindowHandle, moonWorksToRefreshPresentMode[presentMode], debugMode); + + Input = new Input(); + } + + public void Run() + { + while (!quit) + { + var newTime = SDL.SDL_GetPerformanceCounter(); + double frameTime = (newTime - currentTime) / (double)SDL.SDL_GetPerformanceFrequency(); + + if (frameTime > 0.25) + { + frameTime = 0.25; + } + + currentTime = newTime; + + accumulator += frameTime; + + bool updateThisLoop = (accumulator >= timestep); + + if (!quit) + { + while (accumulator >= timestep) + { + SDL.SDL_PumpEvents(); + Input.Update(); + + Update(timestep); + + accumulator -= timestep; + } + + if (updateThisLoop) + { + Draw(); + } + } + } + } + + protected abstract void Update(double dt); + + protected abstract void Draw(); + } +} diff --git a/src/Input/ButtonState.cs b/src/Input/ButtonState.cs new file mode 100644 index 0000000..a2cb54f --- /dev/null +++ b/src/Input/ButtonState.cs @@ -0,0 +1,18 @@ +namespace MoonWorks +{ + public enum ButtonState + { + /// + /// Indicates that the input is not pressed. + /// + Released, + /// + /// Indicates that the input was pressed this frame. + /// + Pressed, + /// + /// Indicates that the input has been held for multiple frames. + /// + Held + } +} diff --git a/src/Input/Gamepad.cs b/src/Input/Gamepad.cs new file mode 100644 index 0000000..d27d0dc --- /dev/null +++ b/src/Input/Gamepad.cs @@ -0,0 +1,101 @@ +using System; +using SDL2; + +namespace MoonWorks +{ + public class Gamepad + { + internal IntPtr Handle; + + public ButtonState A { get; private set; } + public ButtonState B { get; private set; } + public ButtonState X { get; private set; } + public ButtonState Y { get; private set; } + public ButtonState Back { get; private set; } + public ButtonState Guide { get; private set; } + public ButtonState Start { get; private set; } + public ButtonState LeftStick { get; private set; } + public ButtonState RightStick { get; private set; } + public ButtonState LeftShoulder { get; private set; } + public ButtonState RightShoulder { get; private set; } + public ButtonState DpadUp { get; private set; } + public ButtonState DpadDown { get; private set; } + public ButtonState DpadLeft { get; private set; } + public ButtonState DpadRight { get; private set; } + + public float LeftX { get; private set; } + public float LeftY { get; private set; } + public float RightX { get; private set; } + public float RightY { get; private set; } + public float TriggerLeft { get; private set; } + public float TriggerRight { get; private set; } + + public Gamepad(IntPtr handle) + { + Handle = handle; + } + + internal void Update() + { + A = UpdateState(A, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A); + B = UpdateState(B, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B); + X = UpdateState(X, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X); + Y = UpdateState(Y, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y); + Back = UpdateState(Back, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK); + Guide = UpdateState(Guide, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE); + Start = UpdateState(Start, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START); + LeftStick = UpdateState(LeftStick, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK); + RightStick = UpdateState(RightStick, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK); + LeftShoulder = UpdateState(LeftShoulder, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER); + RightShoulder = UpdateState(RightShoulder, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); + DpadUp = UpdateState(DpadUp, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP); + DpadDown = UpdateState(DpadDown, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN); + DpadLeft = UpdateState(DpadLeft, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT); + DpadRight = UpdateState(DpadRight, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT); + + LeftX = UpdateAxis(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX); + LeftY = UpdateAxis(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY); + RightX = UpdateAxis(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX); + RightY = UpdateAxis(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY); + TriggerLeft = UpdateTrigger(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT); + TriggerRight = UpdateTrigger(SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT); + } + + private ButtonState UpdateState(ButtonState state, SDL.SDL_GameControllerButton button) + { + var isPressed = SDL.SDL_GameControllerGetButton(Handle, button); + + if (isPressed == 1) + { + if (state == ButtonState.Pressed) + { + return ButtonState.Held; + } + else if (state == ButtonState.Released) + { + return ButtonState.Pressed; + } + } + + return ButtonState.Released; + } + + private float UpdateAxis(SDL.SDL_GameControllerAxis axis) + { + var axisValue = SDL.SDL_GameControllerGetAxis(Handle, axis); + return Normalize(axisValue, short.MinValue, short.MaxValue); + } + + // Triggers only go from 0 to short.MaxValue + private float UpdateTrigger(SDL.SDL_GameControllerAxis trigger) + { + var triggerValue = SDL.SDL_GameControllerGetAxis(Handle, trigger); + return Normalize(triggerValue, 0, short.MaxValue); + } + + private float Normalize(float value, short min, short max) + { + return (value - min) / (max - min); + } + } +} diff --git a/src/Input/Input.cs b/src/Input/Input.cs new file mode 100644 index 0000000..f9eb5e0 --- /dev/null +++ b/src/Input/Input.cs @@ -0,0 +1,41 @@ +using SDL2; +using System.Collections.Generic; + +namespace MoonWorks +{ + public class Input + { + public Keyboard Keyboard { get; } + + List gamepads = new List(); + + internal Input() + { + Keyboard = new Keyboard(); + + for (int i = 0; i < SDL.SDL_NumJoysticks(); i++) + { + if (SDL.SDL_IsGameController(i) == SDL.SDL_bool.SDL_TRUE) + { + gamepads.Add(new Gamepad(SDL.SDL_GameControllerOpen(i))); + } + } + } + + // Assumes that SDL_PumpEvents has been called! + internal void Update() + { + Keyboard.Update(); + + foreach (var gamepad in gamepads) + { + gamepad.Update(); + } + } + + public Gamepad GetGamepad(int slot) + { + return gamepads[slot]; + } + } +} diff --git a/src/Input/Key.cs b/src/Input/Key.cs new file mode 100644 index 0000000..19a56d5 --- /dev/null +++ b/src/Input/Key.cs @@ -0,0 +1,14 @@ +namespace MoonWorks +{ + internal class Key + { + public Keycode Keycode { get; } + public ButtonState InputState { get; internal set; } + + public Key(Keycode keycode) + { + Keycode = keycode; + InputState = ButtonState.Released; + } + } +} diff --git a/src/Input/Keyboard.cs b/src/Input/Keyboard.cs new file mode 100644 index 0000000..0348be8 --- /dev/null +++ b/src/Input/Keyboard.cs @@ -0,0 +1,73 @@ +using System; +using System.Runtime.InteropServices; +using SDL2; + +namespace MoonWorks +{ + public class Keyboard + { + private Key[] Keys { get; } + private int numKeys; + + internal Keyboard() + { + SDL.SDL_GetKeyboardState(out numKeys); + + Keys = new Key[numKeys]; + foreach (Keycode keycode in Enum.GetValues(typeof(Keycode))) + { + Keys[(int)keycode] = new Key(keycode); + } + } + + internal void Update() + { + IntPtr keyboardState = SDL.SDL_GetKeyboardState(out _); + + foreach (int keycode in Enum.GetValues(typeof(Keycode))) + { + var keyDown = Marshal.ReadByte(keyboardState, keycode); + + if (keyDown == 1) + { + if (Keys[keycode].InputState == ButtonState.Released) + { + Keys[keycode].InputState = ButtonState.Pressed; + } + else if (Keys[keycode].InputState == ButtonState.Pressed) + { + Keys[keycode].InputState = ButtonState.Held; + } + } + else + { + Keys[keycode].InputState = ButtonState.Released; + } + } + } + + public bool IsDown(Keycode keycode) + { + var key = Keys[(int)keycode]; + return (key.InputState == ButtonState.Pressed) || (key.InputState == ButtonState.Held); + } + + public bool IsPressed(Keycode keycode) + { + var key = Keys[(int)keycode]; + return key.InputState == ButtonState.Pressed; + } + + public bool IsHeld(Keycode keycode) + { + var key = Keys[(int)keycode]; + return key.InputState == ButtonState.Held; + } + + public bool IsUp(Keycode keycode) + { + var key = Keys[(int)keycode]; + return key.InputState == ButtonState.Released; + } + } +} diff --git a/src/Input/Keycode.cs b/src/Input/Keycode.cs new file mode 100644 index 0000000..9e183d1 --- /dev/null +++ b/src/Input/Keycode.cs @@ -0,0 +1,111 @@ +namespace MoonWorks +{ + // Enum values are equivalent to the SDL Scancode value. + public enum Keycode : uint + { + Unknown = 0, + A = 4, + B = 5, + C = 6, + D = 7, + E = 8, + F = 9, + G = 10, + H = 11, + I = 12, + J = 13, + K = 14, + L = 15, + M = 16, + N = 17, + O = 18, + P = 19, + Q = 20, + R = 21, + S = 22, + T = 23, + U = 24, + V = 25, + W = 26, + X = 27, + Y = 28, + Z = 29, + D1 = 30, + D2 = 31, + D3 = 32, + D4 = 33, + D5 = 34, + D6 = 35, + D7 = 36, + D8 = 37, + D9 = 38, + D0 = 39, + Return = 40, + Escape = 41, + Backspace = 42, + Tab = 43, + Space = 44, + Minus = 45, + Equals = 46, + LeftBracket = 47, + RightBracket = 48, + Backslash = 49, + NonUSHash = 50, + Semicolon = 51, + Apostrophe = 52, + Grave = 53, + Comma = 54, + Period = 55, + Slash = 56, + CapsLock = 57, + F1 = 58, + F2 = 59, + F3 = 60, + F4 = 61, + F5 = 62, + F6 = 63, + F7 = 64, + F8 = 65, + F9 = 66, + F10 = 67, + F11 = 68, + F12 = 69, + PrintScreen = 70, + ScrollLock = 71, + Pause = 72, + Insert = 73, + Home = 74, + PageUp = 75, + Delete = 76, + End = 77, + PageDown = 78, + Right = 79, + Left = 80, + Down = 81, + Up = 82, + NumLockClear = 83, + KeypadDivide = 84, + KeypadMultiply = 85, + KeypadMinus = 86, + KeypadPlus = 87, + KeypadEnter = 88, + Keypad1 = 89, + Keypad2 = 90, + Keypad3 = 91, + Keypad4 = 92, + Keypad5 = 93, + Keypad6 = 94, + Keypad7 = 95, + Keypad8 = 96, + Keypad9 = 97, + Keypad0 = 98, + KeypadPeriod = 99, + NonUSBackslash = 100, + LeftControl = 224, + LeftShift = 225, + LeftAlt = 226, + RightControl = 228, + RightShift = 229, + RightAlt = 230 + } +} diff --git a/src/PresentMode.cs b/src/PresentMode.cs new file mode 100644 index 0000000..2abed9b --- /dev/null +++ b/src/PresentMode.cs @@ -0,0 +1,10 @@ +namespace MoonWorks +{ + public enum PresentMode + { + Immediate, + Mailbox, + FIFO, + FIFORelaxed + } +}