initial input handling

main
cosmonaut 2021-01-18 23:29:07 -08:00
parent f89907abf7
commit 7d3932da9b
15 changed files with 509 additions and 28 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
bin/
obj/
.vs/

6
.gitmodules vendored Normal file
View File

@ -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

View File

@ -1,8 +0,0 @@
using System;
namespace MoonWorks
{
public class Game
{
}
}

View File

@ -4,4 +4,13 @@
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<DefaultItemExcludes>$(DefaultItemExcludes);lib\**\*</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include=".\lib\SDL2-CS\SDL2-CS.Core.csproj" />
<ProjectReference Include=".\lib\Campari\Campari.csproj" />
<ProjectReference Include=".\lib\Campari\lib\RefreshCS\RefreshCS.csproj" />
</ItemGroup>
</Project>

View File

@ -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

1
lib/Campari Submodule

@ -0,0 +1 @@
Subproject commit f01b9a3a9acff8672e0d7da253ee2402fcdaa281

1
lib/SDL2-CS Submodule

@ -0,0 +1 @@
Subproject commit 1e01bc8eebb501bf6df24ec98784c32843308e0a

94
src/Game.cs Normal file
View File

@ -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<PresentMode, RefreshCS.Refresh.PresentMode> moonWorksToRefreshPresentMode = new Dictionary<PresentMode, RefreshCS.Refresh.PresentMode>
{
{ 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();
}
}

18
src/Input/ButtonState.cs Normal file
View File

@ -0,0 +1,18 @@
namespace MoonWorks
{
public enum ButtonState
{
/// <summary>
/// Indicates that the input is not pressed.
/// </summary>
Released,
/// <summary>
/// Indicates that the input was pressed this frame.
/// </summary>
Pressed,
/// <summary>
/// Indicates that the input has been held for multiple frames.
/// </summary>
Held
}
}

101
src/Input/Gamepad.cs Normal file
View File

@ -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);
}
}
}

41
src/Input/Input.cs Normal file
View File

@ -0,0 +1,41 @@
using SDL2;
using System.Collections.Generic;
namespace MoonWorks
{
public class Input
{
public Keyboard Keyboard { get; }
List<Gamepad> gamepads = new List<Gamepad>();
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];
}
}
}

14
src/Input/Key.cs Normal file
View File

@ -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;
}
}
}

73
src/Input/Keyboard.cs Normal file
View File

@ -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;
}
}
}

111
src/Input/Keycode.cs Normal file
View File

@ -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
}
}

10
src/PresentMode.cs Normal file
View File

@ -0,0 +1,10 @@
namespace MoonWorks
{
public enum PresentMode
{
Immediate,
Mailbox,
FIFO,
FIFORelaxed
}
}