Skip to content

Commit 69ee1a6

Browse files
authored
Merge pull request #48 from bijington/feature/game-controller-support
Game controller support
2 parents cf4614d + 926f65d commit 69ee1a6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+2804
-220
lines changed

.editorconfig

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,13 +216,13 @@ dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
216216
dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
217217
dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
218218

219-
dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
220-
dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
221-
dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
219+
dotnet_naming_rule.private_fields_should_be_camelcase.severity = suggestion
220+
dotnet_naming_rule.private_fields_should_be_camelcase.symbols = private_fields
221+
dotnet_naming_rule.private_fields_should_be_camelcase.style = camelcase
222222

223-
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
224-
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
225-
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase
223+
dotnet_naming_rule.private_static_fields_should_be_camelcase.severity = suggestion
224+
dotnet_naming_rule.private_static_fields_should_be_camelcase.symbols = private_static_fields
225+
dotnet_naming_rule.private_static_fields_should_be_camelcase.style = camelcase
226226

227227
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
228228
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields

.github/workflows/dotnet-macos.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,20 @@ jobs:
1818
- name: Setup .NET
1919
uses: actions/setup-dotnet@v2
2020
with:
21-
dotnet-version: 8.0.x
21+
dotnet-version: 9.0.x
2222
- name: workload install
2323
run: dotnet workload install maui
24-
- name: Restore dependencies
24+
- name: Restore dependencies - engine
2525
run: dotnet restore engine/Orbit.Engine/Orbit.Engine.csproj
26-
- name: Restore dependencies
26+
- name: Restore dependencies - input
27+
run: dotnet restore engine/Orbit.Input/Orbit.Input.csproj
28+
- name: Restore dependencies - tests
2729
run: dotnet restore engine/Orbit.Engine.Tests/Orbit.Engine.Tests.csproj
2830
- name: Build engine
29-
run: dotnet build engine/Orbit.Engine/Orbit.Engine.csproj --no-restore
31+
run: dotnet build engine/Orbit.Engine/Orbit.Engine.csproj --no-restore -c Release
32+
- name: Build input
33+
run: dotnet build engine/Orbit.Input/Orbit.Input.csproj --no-restore -c Release
3034
- name: Build tests
31-
run: dotnet build engine/Orbit.Engine.Tests/Orbit.Engine.Tests.csproj --no-restore
35+
run: dotnet build engine/Orbit.Engine.Tests/Orbit.Engine.Tests.csproj --no-restore -c Release
3236
- name: Run tests
3337
run: dotnet test engine/Orbit.Engine.Tests/Orbit.Engine.Tests.csproj --no-restore

.github/workflows/dotnet-windows.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,20 @@ jobs:
1818
- name: Setup .NET
1919
uses: actions/setup-dotnet@v2
2020
with:
21-
dotnet-version: 8.0.x
21+
dotnet-version: 9.0.x
2222
- name: workload install
2323
run: dotnet workload install maui
24-
- name: Restore dependencies
24+
- name: Restore dependencies - engine
2525
run: dotnet restore engine/Orbit.Engine/Orbit.Engine.csproj
26-
- name: Restore dependencies
26+
- name: Restore dependencies - input
27+
run: dotnet restore engine/Orbit.Input/Orbit.Input.csproj
28+
- name: Restore dependencies - tests
2729
run: dotnet restore engine/Orbit.Engine.Tests/Orbit.Engine.Tests.csproj
2830
- name: Build engine
29-
run: dotnet build engine/Orbit.Engine/Orbit.Engine.csproj --no-restore
31+
run: dotnet build engine/Orbit.Engine/Orbit.Engine.csproj --no-restore -c Release
32+
- name: Build input
33+
run: dotnet build engine/Orbit.Input/Orbit.Input.csproj --no-restore -c Release
3034
- name: Build tests
31-
run: dotnet build engine/Orbit.Engine.Tests/Orbit.Engine.Tests.csproj --no-restore
35+
run: dotnet build engine/Orbit.Engine.Tests/Orbit.Engine.Tests.csproj --no-restore -c Release
3236
- name: Run tests
3337
run: dotnet test engine/Orbit.Engine.Tests/Orbit.Engine.Tests.csproj --no-restore

.github/workflows/nuget-release.yml

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ on:
55
tags:
66
- "v[0-9]+.[0-9]+.[0-9]+"
77
- "v[0-9]+.[0-9]+.[0-9]+-preview[0-9]+"
8+
- "engine[0-9]+.[0-9]+.[0-9]+"
9+
- "engine[0-9]+.[0-9]+.[0-9]+-preview[0-9]+"
10+
- "input[0-9]+.[0-9]+.[0-9]+"
11+
- "input[0-9]+.[0-9]+.[0-9]+-preview[0-9]+"
812
jobs:
913
release-nuget:
1014

@@ -21,21 +25,9 @@ jobs:
2125
id: get_version
2226
uses: battila7/get-version-action@v2
2327

24-
- name: Restore dependencies
25-
run: dotnet restore engine/Orbit.Engine/Orbit.Engine.csproj
26-
27-
- name: Build
28-
run: dotnet build --configuration Release --no-restore engine/Orbit.Engine/Orbit.Engine.csproj /p:Version=${{ steps.get_version.outputs.version-without-v }}
29-
30-
- name: Pack
31-
run: dotnet pack engine/Orbit.Engine/Orbit.Engine.csproj -c Release /p:Version=${{ steps.get_version.outputs.version-without-v }} --no-build --output .
32-
33-
- name: Push
34-
run: dotnet nuget push Bijington.Orbit.Engine.${{ steps.get_version.outputs.version-without-v }}.nupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }}
35-
env:
36-
GITHUB_TOKEN: ${{ secrets.NUGET_API_KEY }}
37-
38-
- name: Push symbols
39-
run: dotnet nuget push Bijington.Orbit.Engine.${{ steps.get_version.outputs.version-without-v }}.snupkg -s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }}
28+
- name: Package releases
29+
shell: pwsh
30+
run: |
31+
.\scripts\package-releases.ps1 -Version ${{ steps.get_version.outputs.version-without-v }} -ApiKey ${{ secrets.NUGET_API_KEY }}
4032
env:
4133
GITHUB_TOKEN: ${{ secrets.NUGET_API_KEY }}

engine/Orbit.Engine.Tests/Orbit.Engine.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net8.0</TargetFramework>
4+
<TargetFramework>net9.0</TargetFramework>
55
<Nullable>enable</Nullable>
66
<UseMaui>true</UseMaui>
77

engine/Orbit.Engine/GameSceneManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ private void UpdateScene()
134134
var postUpdate = DateTime.UtcNow;
135135
var updateDuration = callbackMilliseconds - (postUpdate - currentUpdate).TotalMilliseconds;
136136

137-
var delayUntilNextUpdate = Math.Min(updateDuration, callbackMilliseconds);
137+
var delayUntilNextUpdate = Math.Clamp(updateDuration, 0, callbackMilliseconds);
138138

139139
dispatcher.DispatchDelayed(
140140
TimeSpan.FromMilliseconds(delayUntilNextUpdate),

engine/Orbit.Engine/Orbit.Engine.csproj

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net8.0;net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
5-
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
4+
<TargetFrameworks>net9.0;net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks>
5+
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.19041.0</TargetFrameworks>
66
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
7-
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
7+
<!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> -->
88
<UseMaui>true</UseMaui>
99
<SingleProject>true</SingleProject>
1010
<ImplicitUsings>enable</ImplicitUsings>
1111

1212
<GenerateDocumentationFile>True</GenerateDocumentationFile>
1313

14-
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
15-
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">14.0</SupportedOSPlatformVersion>
14+
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
15+
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
1616
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
1717
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.19041.0</SupportedOSPlatformVersion>
1818
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.19041.0</TargetPlatformMinVersion>
@@ -58,10 +58,10 @@
5858
<None Include="..\..\readme.md" Pack="true" PackagePath="\"/>
5959
</ItemGroup>
6060

61-
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-ios|AnyCPU'">
61+
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net9.0-ios|AnyCPU'">
6262
<CreatePackage>false</CreatePackage>
6363
</PropertyGroup>
64-
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0-windows10.0.19041.0'">
64+
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0-windows10.0.19041.0'">
6565
<PackageReference Include="Microsoft.Maui.Graphics.Win2D.WinUI.Desktop" Version="$(MauiVersion)">
6666
</PackageReference>
6767
</ItemGroup>
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
namespace Orbit.Input;
2+
3+
/// <summary>
4+
/// Represents a game controller button and its associated value.
5+
/// </summary>
6+
public abstract class ButtonValue
7+
{
8+
/// <summary>
9+
/// Creates a new instance of <see cref="ButtonValue"/>.
10+
/// </summary>
11+
/// <param name="parent">The name of the component of a game controller that this button belongs to.</param>
12+
/// <param name="name">The name of the button.</param>
13+
protected ButtonValue(string parent, string name)
14+
{
15+
Name = NameHelper.GetName(parent, name);
16+
}
17+
18+
/// <summary>
19+
/// Gets the name of the button.
20+
/// </summary>
21+
public string Name { get; }
22+
}
23+
24+
/// <inheritdoc />
25+
public class ButtonValue<TValue> : ButtonValue where TValue : struct
26+
{
27+
private readonly GameController gameController;
28+
private readonly IComparer<TValue> comparer;
29+
private TValue buttonValue;
30+
31+
/// <summary>
32+
/// Creates a new instance of <see cref="ButtonValue"/>.
33+
/// </summary>
34+
/// <param name="gameController">The <see cref="GameController"/> that this button belongs to.</param>
35+
/// <param name="parent">The name of the component of a game controller that this button belongs to.</param>
36+
/// <param name="name">The name of the button.</param>
37+
/// <param name="comparer">The <see cref="IComparer{T}"/> to use in comparisons for value changed events. Particularly useful when dealing with values like <see cref="float"/> where accuracy can be messy.</param>
38+
public ButtonValue(GameController gameController, string parent, string name, IComparer<TValue>? comparer = null)
39+
: base(parent, name)
40+
{
41+
this.gameController = gameController;
42+
this.comparer = comparer ?? Comparer<TValue>.Default;
43+
}
44+
45+
/// <summary>
46+
/// Creates a new instance of <see cref="ButtonValue"/>.
47+
/// </summary>
48+
/// <param name="gameController">The <see cref="GameController"/> that this button belongs to.</param>
49+
/// <param name="name">The name of the button.</param>
50+
/// <param name="comparer">The <see cref="IComparer{T}"/> to use in comparisons for value changed events. Particularly useful when dealing with values like <see cref="float"/> where accuracy can be messy.</param>
51+
public ButtonValue(GameController gameController, string name, IComparer<TValue>? comparer = null)
52+
: base(string.Empty, name)
53+
{
54+
this.gameController = gameController;
55+
this.comparer = comparer ?? Comparer<TValue>.Default;
56+
}
57+
58+
/// <summary>
59+
/// Gets the current value for the button.
60+
/// </summary>
61+
public TValue Value
62+
{
63+
get => buttonValue;
64+
internal set
65+
{
66+
if (comparer.Compare(buttonValue, value) == 0)
67+
{
68+
return;
69+
}
70+
71+
buttonValue = value;
72+
73+
this.gameController.RaiseButtonValueChanged(this);
74+
}
75+
}
76+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
namespace Orbit.Input;
2+
3+
/// <summary>
4+
/// A <see cref="float"/> based implementation of <see cref="IComparer{T}"/>.
5+
/// </summary>
6+
public class FloatComparer : IComparer<float>
7+
{
8+
private readonly float threshold;
9+
10+
internal static FloatComparer Default { get; set; } = new FloatComparer(0.001f);
11+
12+
/// <summary>
13+
/// Creates a new instance of <see cref="FloatComparer"/>.
14+
/// </summary>
15+
/// <param name="threshold">The threshold to use when determining whether values are equal.</param>
16+
public FloatComparer(float threshold)
17+
{
18+
this.threshold = threshold;
19+
}
20+
21+
/// <inheritdoc cref="IComparer{T}.Compare"/>
22+
public int Compare(float x, float y)
23+
{
24+
if (Math.Abs(x - y) < this.threshold)
25+
{
26+
return 0;
27+
}
28+
29+
return x < y ? 1 : -1;
30+
}
31+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
namespace Orbit.Input;
2+
3+
/// <summary>
4+
/// Represents a physical game controller that is connected to a device.
5+
/// </summary>
6+
public partial class GameController
7+
{
8+
private readonly WeakEventManager weakEventManager = new();
9+
10+
/// <summary>
11+
/// Gets the <see cref="Stick"/> that represents the D-pad on the game controller.
12+
/// </summary>
13+
public Stick Dpad { get; }
14+
15+
/// <summary>
16+
/// Gets the <see cref="Stick"/> that represents the left thumbstick on the game controller.
17+
/// </summary>
18+
public Stick LeftStick { get; }
19+
20+
/// <summary>
21+
/// Gets the <see cref="Stick"/> that represents the right thumbstick on the game controller.
22+
/// </summary>
23+
public Stick RightStick { get; }
24+
25+
/// <summary>
26+
/// Gets the <see cref="ButtonValue{T}"/> that represents the right most button on the controller.
27+
/// Circle on Playstation and B on XBox controllers.
28+
/// </summary>
29+
public ButtonValue<bool> East { get; }
30+
31+
/// <summary>
32+
/// Gets the <see cref="ButtonValue{T}"/> that represents the top most button on the controller.
33+
/// Triangle on Playstation and Y on XBox controllers.
34+
/// </summary>
35+
public ButtonValue<bool> North { get; }
36+
37+
/// <summary>
38+
/// Gets the <see cref="ButtonValue{T}"/> that represents the bottom most button on the controller.
39+
/// X on Playstation and A on XBox controllers.
40+
/// </summary>
41+
public ButtonValue<bool> South { get; }
42+
43+
/// <summary>
44+
/// Gets the <see cref="ButtonValue{T}"/> that represents the left most button on the controller.
45+
/// Square on Playstation and X on XBox controllers.
46+
/// </summary>
47+
public ButtonValue<bool> West { get; }
48+
49+
/// <summary>
50+
/// Gets the <see cref="ButtonValue{T}"/> that represents the left most button on the controller.
51+
/// Options on Playstation and hamburger on XBox controllers.
52+
/// </summary>
53+
public ButtonValue<bool> Pause { get; }
54+
55+
/// <summary>
56+
/// Gets the <see cref="Shoulder"/> that represents the left hand shoulder on the controller.
57+
/// </summary>
58+
public Shoulder LeftShoulder { get; }
59+
60+
/// <summary>
61+
/// Gets the <see cref="Shoulder"/> that represents the right hand shoulder on the controller.
62+
/// </summary>
63+
public Shoulder RightShoulder { get; }
64+
65+
/// <summary>
66+
/// Event that is raised when a button on the game controller is detected as being pressed or released.
67+
/// </summary>
68+
public event EventHandler<GameControllerButtonChangedEventArgs> ButtonChanged
69+
{
70+
add => weakEventManager.AddEventHandler(value);
71+
remove => weakEventManager.RemoveEventHandler(value);
72+
}
73+
74+
/// <summary>
75+
/// Event that is raised when a button that supports a varying value on the game controller is detected as being pressed or released to some degree.
76+
/// </summary>
77+
public event EventHandler<GameControllerValueChangedEventArgs> ValueChanged
78+
{
79+
add => weakEventManager.AddEventHandler(value);
80+
remove => weakEventManager.RemoveEventHandler(value);
81+
}
82+
83+
internal void RaiseButtonValueChanged(ButtonValue buttonValue)
84+
{
85+
switch (buttonValue)
86+
{
87+
case ButtonValue<float> floatValue:
88+
weakEventManager.HandleEvent(this, new GameControllerValueChangedEventArgs(buttonValue.Name, floatValue.Value), nameof(ValueChanged));
89+
break;
90+
case ButtonValue<bool> boolValue:
91+
weakEventManager.HandleEvent(this, new GameControllerButtonChangedEventArgs(buttonValue.Name, boolValue.Value), nameof(ButtonChanged));
92+
break;
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)