diff --git a/Engine b/Engine
index efed24d..a4b8367 160000
--- a/Engine
+++ b/Engine
@@ -1 +1 @@
-Subproject commit efed24de2011eb9aad6fbeac38697eb9a7c37e3f
+Subproject commit a4b83679b1c45313b57a7a2581547aacf9f3064f
diff --git a/Platforms/Android/.config/dotnet-tools.json b/Platforms/Android/.config/dotnet-tools.json
new file mode 100644
index 0000000..efabe22
--- /dev/null
+++ b/Platforms/Android/.config/dotnet-tools.json
@@ -0,0 +1,36 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "dotnet-mgcb": {
+ "version": "3.8.1.303",
+ "commands": [
+ "mgcb"
+ ]
+ },
+ "dotnet-mgcb-editor": {
+ "version": "3.8.1.303",
+ "commands": [
+ "mgcb-editor"
+ ]
+ },
+ "dotnet-mgcb-editor-linux": {
+ "version": "3.8.1.303",
+ "commands": [
+ "mgcb-editor-linux"
+ ]
+ },
+ "dotnet-mgcb-editor-windows": {
+ "version": "3.8.1.303",
+ "commands": [
+ "mgcb-editor-windows"
+ ]
+ },
+ "dotnet-mgcb-editor-mac": {
+ "version": "3.8.1.303",
+ "commands": [
+ "mgcb-editor-mac"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/Platforms/Android/Android.csproj b/Platforms/Android/Android.csproj
new file mode 100644
index 0000000..845b1c5
--- /dev/null
+++ b/Platforms/Android/Android.csproj
@@ -0,0 +1,42 @@
+
+
+
+ net9.0-android
+ 23
+ Exe
+ com.MyCompany.MyGame
+ 1
+ 1.0
+ true
+ true
+
+
+
+
+
+
+
+
+
+ Content/Content.mgcb
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Platforms/Android/AndroidManifest.xml b/Platforms/Android/AndroidManifest.xml
new file mode 100644
index 0000000..e22c6dc
--- /dev/null
+++ b/Platforms/Android/AndroidManifest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/Platforms/Android/MobileInputs.cs b/Platforms/Android/MobileInputs.cs
new file mode 100644
index 0000000..9055ffc
--- /dev/null
+++ b/Platforms/Android/MobileInputs.cs
@@ -0,0 +1,90 @@
+using System.Collections.Generic;
+
+using Microsoft.Xna.Framework.Input.Touch;
+
+using Engine.Core;
+using Engine.Systems.Input;
+
+using MyUniverse.Shared.Behaviours.Abstract;
+
+namespace MyUniverse.Platforms.Android;
+
+public class MobileInputs : Behaviour, IUpdate, IGameInputs
+{
+ public Event, IButtonInputs.ButtonCallbackArguments> OnAnyButtonPressed { get; } = new();
+ public Event, IButtonInputs.ButtonCallbackArguments> OnAnyButtonReleased { get; } = new();
+
+ private readonly Dictionary, IButtonInputs.ButtonCallbackArguments>> OnPressed = new(256);
+ private readonly Dictionary, IButtonInputs.ButtonCallbackArguments>> OnReleased = new(256);
+
+ private TouchCollection touchCollection = default;
+
+ public void RegisterOnPress(IGameInputs.Button key, Event, IButtonInputs.ButtonCallbackArguments>.EventHandler callback)
+ {
+ if (OnPressed.TryGetValue(key, out Event, IButtonInputs.ButtonCallbackArguments> @event))
+ {
+ @event.AddListener(callback);
+ return;
+ }
+
+ @event = new();
+ @event.AddListener(callback);
+ OnPressed.Add(key, @event);
+ }
+
+ public void UnregisterOnPress(IGameInputs.Button key, Event, IButtonInputs.ButtonCallbackArguments>.EventHandler callback)
+ {
+ if (OnPressed.TryGetValue(key, out Event, IButtonInputs.ButtonCallbackArguments> @event))
+ @event.RemoveListener(callback);
+ }
+
+ public void RegisterOnRelease(IGameInputs.Button key, Event, IButtonInputs.ButtonCallbackArguments>.EventHandler callback)
+ {
+ if (OnReleased.TryGetValue(key, out Event, IButtonInputs.ButtonCallbackArguments> @event))
+ {
+ @event.AddListener(callback);
+ return;
+ }
+
+ @event = new();
+ @event.AddListener(callback);
+ OnReleased.Add(key, @event);
+ }
+
+ public void UnregisterOnRelease(IGameInputs.Button key, Event, IButtonInputs.ButtonCallbackArguments>.EventHandler callback)
+ {
+ if (OnReleased.TryGetValue(key, out Event, IButtonInputs.ButtonCallbackArguments> @event))
+ @event.RemoveListener(callback);
+ }
+
+ public void Update()
+ {
+ touchCollection = TouchPanel.GetState();
+
+ if (touchCollection.Count > 0)
+ {
+ TouchLocation touchLocation = touchCollection[(int)IGameInputs.Button.Interact];
+ if (touchLocation.State == TouchLocationState.Pressed)
+ {
+ if (OnPressed.TryGetValue(IGameInputs.Button.Interact, out Event, IButtonInputs.ButtonCallbackArguments> @event))
+ @event.Invoke(this, new(IGameInputs.Button.Interact));
+ OnAnyButtonPressed?.Invoke(this, new(IGameInputs.Button.Interact));
+ }
+ else if (touchLocation.State == TouchLocationState.Released)
+ {
+ if (OnReleased.TryGetValue(IGameInputs.Button.Interact, out Event, IButtonInputs.ButtonCallbackArguments> @event))
+ @event.Invoke(this, new(IGameInputs.Button.Interact));
+ OnAnyButtonReleased?.Invoke(this, new(IGameInputs.Button.Interact));
+ }
+ }
+ }
+
+ public bool IsPressed(IGameInputs.Button button)
+ {
+ if (touchCollection.Count > 0)
+ return false;
+
+ TouchLocation touchLocation = touchCollection[(int)IGameInputs.Button.Interact];
+ return touchLocation.State == TouchLocationState.Pressed || touchLocation.State == TouchLocationState.Moved;
+ }
+}
diff --git a/Platforms/Android/Program.cs b/Platforms/Android/Program.cs
new file mode 100644
index 0000000..2fc0962
--- /dev/null
+++ b/Platforms/Android/Program.cs
@@ -0,0 +1,60 @@
+using Microsoft.Xna.Framework;
+
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+using Android.Views;
+using Engine.Integration.MonoGame;
+using Engine.Core;
+using Engine.Core.Debug;
+
+namespace MyUniverse.Platforms.Android
+{
+ [Activity(
+ Label = "@string/app_name",
+ MainLauncher = true,
+ Icon = "@drawable/icon",
+ AlwaysRetainTaskState = true,
+ LaunchMode = LaunchMode.SingleInstance,
+ ScreenOrientation = ScreenOrientation.Portrait,
+ ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden | ConfigChanges.ScreenSize
+ )]
+ public class Program : AndroidGameActivity
+ {
+ private MonoGameWindow _game;
+ private View _view;
+
+ protected override void OnCreate(Bundle bundle)
+ {
+ base.OnCreate(bundle);
+
+ Universe universe = new();
+
+ ILogger logger = new RotatingFileLogger("Logs", "MyGame");
+
+ universe.InstantiateUniverseObject().SetUniverseObject("Logger")
+ .BehaviourController.AddBehaviour().Logger = ILogger.Shared = logger;
+
+ universe.InstantiateUniverseObject().SetUniverseObject("Desktop Inputs")
+ .BehaviourController.AddBehaviour();
+
+ universe.InstantiateUniverseObject().SetUniverseObject("Visual Managers")
+ .BehaviourController.AddBehaviour()
+ .BehaviourController.AddBehaviour();
+
+ /* For Networking
+ LiteNetLibClient client = universe.InstantiateUniverseObject().SetUniverseObject("Client").BehaviourController.AddBehaviour();
+ client.BehaviourController.AddBehaviour();
+ universe.OnPreUpdate.AddOneTimeListener((_, _) => client.Connect("localhost", 8888));
+ */
+
+ Shared.UniverseSource.ApplyUniverse(universe);
+
+ _game = new MonoGameWindow(universe);
+ _view = _game.Services.GetService(typeof(View)) as View;
+
+ SetContentView(_view);
+ _game.Run();
+ }
+ }
+}
diff --git a/Platforms/Android/Resources/Drawable/Icon.png b/Platforms/Android/Resources/Drawable/Icon.png
new file mode 100644
index 0000000..0e4c41c
Binary files /dev/null and b/Platforms/Android/Resources/Drawable/Icon.png differ
diff --git a/Platforms/Android/Resources/Values/Strings.xml b/Platforms/Android/Resources/Values/Strings.xml
new file mode 100644
index 0000000..267b972
--- /dev/null
+++ b/Platforms/Android/Resources/Values/Strings.xml
@@ -0,0 +1,4 @@
+
+
+ MyGame
+
diff --git a/Project.sln b/Project.sln
index ed57f5b..72a23b9 100644
--- a/Project.sln
+++ b/Project.sln
@@ -29,6 +29,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Integration.Yaml", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Integration.LiteNetLib", "Engine\Engine.Integration\Engine.Integration.LiteNetLib\Engine.Integration.LiteNetLib.csproj", "{7AA22306-772F-45F4-8F30-97EBD1FC124D}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Android", "Platforms\Android\Android.csproj", "{6D4F3E3C-4E89-44A1-B6D3-03627AA8A8AF}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -159,6 +161,18 @@ Global
{7AA22306-772F-45F4-8F30-97EBD1FC124D}.Release|x64.Build.0 = Release|Any CPU
{7AA22306-772F-45F4-8F30-97EBD1FC124D}.Release|x86.ActiveCfg = Release|Any CPU
{7AA22306-772F-45F4-8F30-97EBD1FC124D}.Release|x86.Build.0 = Release|Any CPU
+ {6D4F3E3C-4E89-44A1-B6D3-03627AA8A8AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6D4F3E3C-4E89-44A1-B6D3-03627AA8A8AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6D4F3E3C-4E89-44A1-B6D3-03627AA8A8AF}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6D4F3E3C-4E89-44A1-B6D3-03627AA8A8AF}.Debug|x64.Build.0 = Debug|Any CPU
+ {6D4F3E3C-4E89-44A1-B6D3-03627AA8A8AF}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6D4F3E3C-4E89-44A1-B6D3-03627AA8A8AF}.Debug|x86.Build.0 = Debug|Any CPU
+ {6D4F3E3C-4E89-44A1-B6D3-03627AA8A8AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6D4F3E3C-4E89-44A1-B6D3-03627AA8A8AF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6D4F3E3C-4E89-44A1-B6D3-03627AA8A8AF}.Release|x64.ActiveCfg = Release|Any CPU
+ {6D4F3E3C-4E89-44A1-B6D3-03627AA8A8AF}.Release|x64.Build.0 = Release|Any CPU
+ {6D4F3E3C-4E89-44A1-B6D3-03627AA8A8AF}.Release|x86.ActiveCfg = Release|Any CPU
+ {6D4F3E3C-4E89-44A1-B6D3-03627AA8A8AF}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -173,5 +187,6 @@ Global
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283} = {9059393F-4073-9273-0EEC-2B1BA61B620B}
{A15263DB-DF65-4A07-8CA1-33A2919501A0} = {FECFFD54-338F-4060-9161-1E5770D1DC33}
{7AA22306-772F-45F4-8F30-97EBD1FC124D} = {9059393F-4073-9273-0EEC-2B1BA61B620B}
+ {6D4F3E3C-4E89-44A1-B6D3-03627AA8A8AF} = {FECFFD54-338F-4060-9161-1E5770D1DC33}
EndGlobalSection
EndGlobal