diff --git a/THIRD PARTY.md b/THIRD PARTY.md
index 0240b0b096..99513c7a2a 100644
--- a/THIRD PARTY.md
+++ b/THIRD PARTY.md
@@ -62,6 +62,7 @@ Stride uses the following open-source products.
* [SLNTools](https://slntools.codeplex.com) (MIT License)
* [WPFToolKit](http://wpftoolkit.codeplex.com/) (Ms-PL)
* [XamlMarkdown](https://github.com/Kryptos-FR/XamlMarkdown/) (MIT License)
+* [DotRecast](https://github.com/ikpil/DotRecast) (Zlib license)
### Fugue Icons
diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props
index d80c39307b..9937848ac4 100644
--- a/sources/Directory.Packages.props
+++ b/sources/Directory.Packages.props
@@ -11,6 +11,7 @@
+
@@ -148,4 +149,4 @@
-
\ No newline at end of file
+
diff --git a/sources/buildengine/Stride.Core.BuildEngine.Common/MicrothreadLocalDatabases.cs b/sources/buildengine/Stride.Core.BuildEngine.Common/MicrothreadLocalDatabases.cs
index 8c892bea0e..de655f4b79 100644
--- a/sources/buildengine/Stride.Core.BuildEngine.Common/MicrothreadLocalDatabases.cs
+++ b/sources/buildengine/Stride.Core.BuildEngine.Common/MicrothreadLocalDatabases.cs
@@ -113,6 +113,10 @@ private static DatabaseFileProvider CreateDatabase(BuildTransaction transaction)
private class MicroThreadLocalProviderService : IDatabaseFileProviderService
{
- public DatabaseFileProvider FileProvider => MicroThreadLocalDatabaseFileProvider.Value;
+ public DatabaseFileProvider FileProvider
+ {
+ get => MicroThreadLocalDatabaseFileProvider.Value;
+ set => MicroThreadLocalDatabaseFileProvider.Value = value;
+ }
}
}
diff --git a/sources/core/Stride.Core.Serialization/IO/IDatabaseFileProviderService.cs b/sources/core/Stride.Core.Serialization/IO/IDatabaseFileProviderService.cs
index 93c36db2f8..7d251cd2c2 100644
--- a/sources/core/Stride.Core.Serialization/IO/IDatabaseFileProviderService.cs
+++ b/sources/core/Stride.Core.Serialization/IO/IDatabaseFileProviderService.cs
@@ -5,5 +5,5 @@ namespace Stride.Core.IO;
public interface IDatabaseFileProviderService
{
- DatabaseFileProvider FileProvider { get; }
-}
\ No newline at end of file
+ DatabaseFileProvider FileProvider { get; set; }
+}
diff --git a/sources/core/Stride.Core.Serialization/Serialization/Contents/IContentManager.cs b/sources/core/Stride.Core.Serialization/Serialization/Contents/IContentManager.cs
index baf4983438..d495e40f7c 100644
--- a/sources/core/Stride.Core.Serialization/Serialization/Contents/IContentManager.cs
+++ b/sources/core/Stride.Core.Serialization/Serialization/Contents/IContentManager.cs
@@ -25,6 +25,19 @@ public interface IContentManager
/// A stream to the raw asset.
Stream OpenAsStream(string url, StreamFlags streamFlags = StreamFlags.None);
+ ///
+ /// Saves an asset at a specific URL.
+ ///
+ /// The URL.
+ /// The asset.
+ /// The custom storage type to use. Use null as default.
+ ///
+ /// url
+ /// or
+ /// asset
+ ///
+ void Save(string url, object asset, Type? storageType);
+
///
/// Loads content from the specified URL.
///
diff --git a/sources/core/Stride.Core/IServiceRegistry.cs b/sources/core/Stride.Core/IServiceRegistry.cs
index c2b564561a..9f25c94df1 100644
--- a/sources/core/Stride.Core/IServiceRegistry.cs
+++ b/sources/core/Stride.Core/IServiceRegistry.cs
@@ -47,6 +47,15 @@ public interface IServiceRegistry
/// Thrown when a service of the same type is already registered.
void AddService(T service) where T : class;
+ ///
+ /// Adds a service to this .
+ ///
+ /// The service to add.
+ /// The type to register as.
+ /// Thrown when the provided service is null.
+ /// Thrown when a service of the same type is already registered.
+ void AddService(object service, Type type);
+
///
/// Gets the service object of the specified type.
///
diff --git a/sources/core/Stride.Core/ServiceRegistry.cs b/sources/core/Stride.Core/ServiceRegistry.cs
index 09b7543516..ca5dcede37 100644
--- a/sources/core/Stride.Core/ServiceRegistry.cs
+++ b/sources/core/Stride.Core/ServiceRegistry.cs
@@ -76,6 +76,22 @@ public void AddService(T service)
OnServiceAdded(new ServiceEventArgs(type, service));
}
+ ///
+ ///
+ /// This implementation triggers the event after a service is successfully added.
+ ///
+ public void AddService(object service, Type type)
+ {
+ ArgumentNullException.ThrowIfNull(service);
+
+ lock (registeredService)
+ {
+ if (!registeredService.TryAdd(type, service))
+ throw new ArgumentException("Service is already registered with this type", nameof(type));
+ }
+ OnServiceAdded(new ServiceEventArgs(type, service));
+ }
+
///
///
/// This implementation triggers the event after a service is successfully removed.
diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/EntityHierarchyEditor/Game/EntityHierarchyEditorGame.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/EntityHierarchyEditor/Game/EntityHierarchyEditorGame.cs
index 589ceaeccb..b0059ad35d 100644
--- a/sources/editor/Stride.Assets.Presentation/AssetEditors/EntityHierarchyEditor/Game/EntityHierarchyEditorGame.cs
+++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/EntityHierarchyEditor/Game/EntityHierarchyEditorGame.cs
@@ -48,7 +48,8 @@ public abstract class EntityHierarchyEditorGame : EditorServiceGame
private Material fallbackColorMaterial;
private Material fallbackTextureMaterial;
- protected EntityHierarchyEditorGame(TaskCompletionSource gameContentLoadedTaskSource, IEffectCompiler effectCompiler, string effectLogPath)
+ protected EntityHierarchyEditorGame(TaskCompletionSource gameContentLoadedTaskSource, IEffectCompiler effectCompiler, string effectLogPath, GameContext context = null)
+ : base(context)
{
this.gameContentLoadedTaskSource = gameContentLoadedTaskSource;
this.effectCompiler = effectCompiler;
diff --git a/sources/editor/Stride.Editor/EditorGame/Game/EditorServiceGame.cs b/sources/editor/Stride.Editor/EditorGame/Game/EditorServiceGame.cs
index bd04fdc2b0..25a80860c5 100644
--- a/sources/editor/Stride.Editor/EditorGame/Game/EditorServiceGame.cs
+++ b/sources/editor/Stride.Editor/EditorGame/Game/EditorServiceGame.cs
@@ -99,6 +99,11 @@ public bool IsEditorHidden
public event EventHandler ExceptionThrown;
+ public EditorServiceGame(GameContext context) : base(context)
+ {
+
+ }
+
///
/// Calculates and returns the position of the mouse in the scene.
///
diff --git a/sources/editor/Stride.Editor/Engine/EmbeddedGame.cs b/sources/editor/Stride.Editor/Engine/EmbeddedGame.cs
index 5eab6d24fc..66d262c9c3 100644
--- a/sources/editor/Stride.Editor/Engine/EmbeddedGame.cs
+++ b/sources/editor/Stride.Editor/Engine/EmbeddedGame.cs
@@ -3,6 +3,7 @@
using Stride.Core.Diagnostics;
using Stride.Engine;
+using Stride.Games;
using Stride.Graphics;
namespace Stride.Editor.Engine
@@ -17,7 +18,7 @@ public class EmbeddedGame : Game
///
public static bool DebugMode { get; set; }
- public EmbeddedGame()
+ public EmbeddedGame(GameContext context) : base(context)
{
GraphicsDeviceManager.PreferredGraphicsProfile = new [] { GraphicsProfile.Level_11_0, GraphicsProfile.Level_10_1, GraphicsProfile.Level_10_0 };
GraphicsDeviceManager.PreferredBackBufferWidth = 64;
diff --git a/sources/editor/Stride.Editor/Preview/GameStudioPreviewService.cs b/sources/editor/Stride.Editor/Preview/GameStudioPreviewService.cs
index 522b71f911..99047e09fa 100644
--- a/sources/editor/Stride.Editor/Preview/GameStudioPreviewService.cs
+++ b/sources/editor/Stride.Editor/Preview/GameStudioPreviewService.cs
@@ -137,8 +137,8 @@ private void StrideUIThread()
initializationSignal.Set();
- PreviewGame = new PreviewGame(AssetBuilderService.EffectCompiler);
var context = new GameContextWinforms(gameForm) { InitializeDatabase = false };
+ PreviewGame = new PreviewGame(AssetBuilderService.EffectCompiler, context);
// Wait for shaders to be loaded
AssetBuilderService.WaitForShaders();
diff --git a/sources/editor/Stride.Editor/Preview/PreviewGame.cs b/sources/editor/Stride.Editor/Preview/PreviewGame.cs
index c88ac25032..b7a5a8c9f1 100644
--- a/sources/editor/Stride.Editor/Preview/PreviewGame.cs
+++ b/sources/editor/Stride.Editor/Preview/PreviewGame.cs
@@ -2,15 +2,16 @@
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System;
using System.Threading.Tasks;
-using Stride.Core.BuildEngine;
-using Stride.Core;
-using Stride.Core.Diagnostics;
-using Stride.Core.Mathematics;
using Stride.Assets;
using Stride.Assets.SpriteFont;
using Stride.Assets.SpriteFont.Compiler;
+using Stride.Core;
+using Stride.Core.BuildEngine;
+using Stride.Core.Diagnostics;
+using Stride.Core.Mathematics;
using Stride.Engine;
using Stride.Engine.Design;
+using Stride.Games;
using Stride.Graphics;
using Stride.Rendering.Compositing;
using Stride.Shaders.Compiler;
@@ -43,7 +44,7 @@ public class PreviewGame : EditorGame.Game.EditorServiceGame
private Scene previewScene;
- public PreviewGame(IEffectCompiler effectCompiler)
+ public PreviewGame(IEffectCompiler effectCompiler, GameContext context) : base(context)
{
this.effectCompiler = effectCompiler;
}
diff --git a/sources/engine/Stride.Debugger/Debugger/LiveAssemblyReloader.cs b/sources/engine/Stride.Debugger/Debugger/LiveAssemblyReloader.cs
index 67c314e85f..bb4d910626 100644
--- a/sources/engine/Stride.Debugger/Debugger/LiveAssemblyReloader.cs
+++ b/sources/engine/Stride.Debugger/Debugger/LiveAssemblyReloader.cs
@@ -1,29 +1,30 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Reflection;
using Stride.Core;
using Stride.Core.Reflection;
-using Stride.Core.Serialization;
using Stride.Core.Yaml;
using Stride.Core.Yaml.Events;
using Stride.Core.Yaml.Serialization;
using Stride.Debugger.Target;
using Stride.Engine;
+using Stride.Games;
namespace Stride.Debugger
{
public static class LiveAssemblyReloader
{
- public static void Reload(Game game, AssemblyContainer assemblyContainer, List assembliesToUnregister, List assembliesToRegister)
+ public static void Reload(GameBase game, AssemblyContainer assemblyContainer, List assembliesToUnregister, List assembliesToRegister)
{
List entities = new List();
+ var sceneSystem = game.Services.GetSafeServiceAs();
+
if (game != null)
- entities.AddRange(game.SceneSystem.SceneInstance);
+ entities.AddRange(sceneSystem.SceneInstance);
CloneReferenceSerializer.References = new List();
diff --git a/sources/engine/Stride.Engine/Engine/Builder/GameBaseExtensions.cs b/sources/engine/Stride.Engine/Engine/Builder/GameBaseExtensions.cs
new file mode 100644
index 0000000000..143e5d0c82
--- /dev/null
+++ b/sources/engine/Stride.Engine/Engine/Builder/GameBaseExtensions.cs
@@ -0,0 +1,41 @@
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
+using Stride.Core.IO;
+using Stride.Core.Serialization.Contents;
+using Stride.Engine.Design;
+using Stride.Games;
+using Stride.Rendering;
+
+namespace Stride.Engine.Builder;
+
+public static class GameBaseExtensions
+{
+ public static GameBase UseInitialSceneFromSettings(this GameBase game)
+ {
+ var content = game.Services.GetService();
+ var settings = content.Load("GameSettings");
+ var sceneSystem = game.Services.GetService();
+ sceneSystem.InitialSceneUrl = settings.DefaultSceneUrl;
+ return game;
+ }
+
+ public static GameBase UseInitialGraphicsCompositorFromSettings(this GameBase game)
+ {
+ var content = game.Services.GetService();
+ var settings = content.Load("GameSettings");
+ var sceneSystem = game.Services.GetService();
+ sceneSystem.InitialGraphicsCompositorUrl = settings.DefaultGraphicsCompositorUrl;
+
+ return game;
+ }
+
+ public static GameBase UseDefaultEffectCompiler(this GameBase game)
+ {
+ var fileProviderService = game.Services.GetService().FileProvider;
+ game.Services.GetService()
+ .CreateDefaultEffectCompiler(fileProviderService);
+
+ return game;
+ }
+}
diff --git a/sources/engine/Stride.Engine/Engine/Builder/GameBuilder.cs b/sources/engine/Stride.Engine/Engine/Builder/GameBuilder.cs
new file mode 100644
index 0000000000..ab78eb29f5
--- /dev/null
+++ b/sources/engine/Stride.Engine/Engine/Builder/GameBuilder.cs
@@ -0,0 +1,154 @@
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.DependencyInjection;
+using Stride.Core;
+using Stride.Core.Diagnostics;
+using Stride.Games;
+using Stride.Input;
+
+namespace Stride.Engine.Builder;
+
+///
+/// Helps build the game and preps it to be able to run after .
+///
+public class GameBuilder : IGameBuilder
+{
+ ///
+ /// This is used to allow the same instance to be registered multiple times as different interfaces or types. This was done due to how works."/>
+ ///
+ public Dictionary InternalServices { get; internal set; } = [];
+
+ ///
+ /// This allows for Service to be registered through DI.
+ ///
+ public IServiceCollection Services { get; internal set; } = new ServiceCollection();
+
+ ///
+ /// This is a direct reference to the game systems collection of the .
+ ///
+ public GameSystemCollection GameSystems { get; internal set; }
+
+ ///
+ /// Adds log listeners to the game on . This is registered first so it will log build errors if they occur.
+ ///
+ public List LogListeners { get; internal set; } = [];
+
+ ///
+ /// Adds input sources to the game on .
+ ///
+ public List InputSources { get; internal set; } = [];
+
+ public GameBase Game { get; set; }
+
+ public GameContext Context { get; set; }
+
+ private static Logger _log => GlobalLogger.GetLogger(nameof(GameBuilder));
+
+ internal GameBuilder(GameBase game)
+ {
+ Game = game ?? new MinimalGame(null);
+ GameSystems = Game.GameSystems;
+ Services.AddSingleton(Game.Services);
+ InternalServices.Add(typeof(IServiceRegistry), Game.Services);
+ }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ ///
+ public static GameBuilder Create(GameBase game = null)
+ {
+ return new GameBuilder(game);
+ }
+
+ public virtual GameBase Build()
+ {
+ foreach (var logListener in LogListeners)
+ {
+ GlobalLogger.GlobalMessageLogged += logListener;
+ }
+
+ var provider = Services.BuildServiceProvider();
+ foreach (var service in InternalServices)
+ {
+ if (service.Key == typeof(IServiceRegistry) || service.Key == typeof(IServiceProvider))
+ continue;
+
+ try
+ {
+ if (service.Value == null)
+ {
+ var instance = provider.GetService(service.Key);
+
+ if(instance == null)
+ {
+ //check if the type is inherited from another instance in the services.
+ foreach (var kvp in InternalServices)
+ {
+ if (kvp.Key.IsAssignableFrom(service.Key) && kvp.Value != null)
+ {
+ instance = provider.GetService(kvp.Key);
+ if(instance is not null)
+ break;
+ }
+ }
+ }
+
+ _log.Info($"Registering service {service.Key.Name}.");
+ Game.Services.AddService(instance, service.Key);
+ InternalServices[service.Key] = instance;
+ }
+ else
+ {
+ _log.Info($"Registering service {service.Key.Name}.");
+ Game.Services.AddService(service.Value, service.Key);
+ }
+ }
+ catch (Exception ex)
+ {
+ // TODO: check if service is already registered first.
+ _log.Error($"Failed to register service {service.Key.Name}.\n\n", ex);
+ }
+ }
+
+ // Add all game systems to the game.
+ foreach (var service in InternalServices)
+ {
+ var system = provider.GetService(service.Key);
+ if (system is IGameSystemBase gameSystem && !Game.GameSystems.Contains(gameSystem))
+ {
+ _log.Info($"Adding game system {gameSystem.GetType().Name} to the game systems collection.");
+ Game.GameSystems.Add(gameSystem);
+ }
+ }
+
+ if (Context != null)
+ {
+ _log.Info($"Setting game context.");
+ Game.SetGameContext(Context);
+ }
+
+ if(InputSources.Count > 0)
+ {
+ var inputManager = Game.Services.GetService();
+
+ if (inputManager is null)
+ {
+ _log.Info("No InputManager found in the game services, creating default.");
+ inputManager = new InputManager();
+ Game.Services.AddService(inputManager);
+ }
+
+ foreach (var inputSource in InputSources)
+ {
+ _log.Info($"Adding input source {inputSource.GetType().Name} to the input manager.");
+ inputManager.Sources.Add(inputSource);
+ }
+ }
+
+ return Game;
+ }
+}
diff --git a/sources/engine/Stride.Engine/Engine/Builder/GameBuilderExtensions.cs b/sources/engine/Stride.Engine/Engine/Builder/GameBuilderExtensions.cs
new file mode 100644
index 0000000000..75a80446c9
--- /dev/null
+++ b/sources/engine/Stride.Engine/Engine/Builder/GameBuilderExtensions.cs
@@ -0,0 +1,223 @@
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
+using System;
+using Microsoft.Extensions.DependencyInjection;
+using Stride.Audio;
+using Stride.Core;
+using Stride.Core.Diagnostics;
+using Stride.Core.IO;
+using Stride.Core.Serialization.Contents;
+using Stride.Core.Storage;
+using Stride.Engine.Processors;
+using Stride.Games;
+using Stride.Graphics.Font;
+using Stride.Input;
+using Stride.Profiling;
+using Stride.Rendering;
+using Stride.Rendering.Fonts;
+using Stride.Rendering.Sprites;
+using Stride.Shaders.Compiler;
+using Stride.Streaming;
+
+namespace Stride.Engine.Builder;
+public static class GameBuilderExtensions
+{
+
+ ///
+ /// Adds core systems to the game. Does not register the systems into the
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static IGameBuilder AddGameSystem(this IGameBuilder gameBuilder, T gameSystem) where T : IGameSystemBase
+ {
+ gameBuilder.GameSystems.Add(gameSystem);
+ return gameBuilder;
+ }
+
+ ///
+ /// Registers a service into the .
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static IGameBuilder AddService(this IGameBuilder gameBuilder, T service) where T : class
+ {
+ gameBuilder.InternalServices.Add(typeof(T), service);
+ gameBuilder.Services.AddSingleton(service);
+ return gameBuilder;
+ }
+
+ ///
+ /// Registers a service into the .
+ ///
+ ///
+ ///
+ ///
+ public static IGameBuilder AddService(this IGameBuilder gameBuilder) where T : class
+ {
+ gameBuilder.InternalServices.Add(typeof(T), null);
+ gameBuilder.Services.AddSingleton();
+ return gameBuilder;
+ }
+
+ ///
+ /// Registers a service and its interface into the .
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static IGameBuilder AddService(this IGameBuilder gameBuilder) where TClass : class, TInterface where TInterface : class
+ {
+ // This is a work around to allow DI to work the same way as the ServiceRegistry expects.
+ // Without registering both the interface and the class, the DI will not be able to resolve the interface on build.
+ gameBuilder.InternalServices.Add(typeof(TInterface), null);
+ gameBuilder.InternalServices.Add(typeof(TClass), null);
+ gameBuilder.Services.AddSingleton();
+ return gameBuilder;
+ }
+
+ ///
+ /// Adds a log listener to the game. This is used thoughout Stride systems for logging events.
+ ///
+ ///
+ ///
+ ///
+ public static IGameBuilder AddLogListener(this IGameBuilder gameBuilder, LogListener logListener)
+ {
+ gameBuilder.LogListeners.Add(logListener);
+ return gameBuilder;
+ }
+
+ ///
+ /// Adds the Stride input system to the game with no sources.
+ ///
+ ///
+ ///
+ public static IGameBuilder UseDefaultInput(this IGameBuilder gameBuilder)
+ {
+ var services = gameBuilder.InternalServices[typeof(IServiceRegistry)] as IServiceRegistry;
+
+ var inputSystem = new InputSystem(services);
+
+ gameBuilder
+ .AddGameSystem(inputSystem)
+ .AddService(inputSystem)
+ .AddService(inputSystem.Manager);
+
+ return gameBuilder;
+ }
+
+ public static IGameBuilder SetGameContext(this IGameBuilder gameBuilder, GameContext context)
+ {
+ gameBuilder.Context = context;
+ return gameBuilder;
+ }
+
+ ///
+ /// Add a custom database file provider to the game.
+ ///
+ ///
+ ///
+ ///
+ public static IGameBuilder SetDbFileProvider(this IGameBuilder gameBuilder, DatabaseFileProvider provider)
+ {
+ // Gets initialized by the GameBase constructor.
+ var fileProviderService = gameBuilder.Game.Services.GetService();
+
+ fileProviderService.FileProvider = provider;
+ return gameBuilder;
+ }
+
+ ///
+ /// Creates a default database to be used in the game.
+ ///
+ ///
+ ///
+ public static IGameBuilder UseDefaultDb(this IGameBuilder gameBuilder)
+ {
+ using (Profiler.Begin(GameProfilingKeys.ObjectDatabaseInitialize))
+ {
+ // Create and mount database file system
+ var objDatabase = ObjectDatabase.CreateDefaultDatabase();
+
+ // Only set a mount path if not mounted already
+ var mountPath = VirtualFileSystem.ResolveProviderUnsafe("/asset", true).Provider == null ? "/asset" : null;
+ var result = new DatabaseFileProvider(objDatabase, mountPath);
+
+ gameBuilder.SetDbFileProvider(result);
+ }
+
+ return gameBuilder;
+ }
+
+ public static IGameBuilder UseDefaultContentManager(this IGameBuilder gameBuilder)
+ {
+ var services = gameBuilder.Game.Services;
+ var content = new ContentManager(services);
+ services.AddService(content);
+ services.AddService(content);
+ return gameBuilder;
+ }
+
+ ///
+ /// Adds a default effect compiler to the game. This is used to compile shaders and effects.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static EffectSystem CreateDefaultEffectCompiler(this EffectSystem effectSystem, IVirtualFileProvider fileProvider)
+ {
+ EffectCompilerBase compiler = new EffectCompiler(fileProvider)
+ {
+ SourceDirectories = { EffectCompilerBase.DefaultSourceShaderFolder },
+ };
+
+ if(fileProvider is DatabaseFileProvider databaseFileProvider)
+ {
+ effectSystem.Compiler = new EffectCompilerCache(compiler, databaseFileProvider);
+ return effectSystem;
+ }
+
+ throw new ArgumentException("The file provider must be a DatabaseFileProvider", nameof(fileProvider));
+ }
+
+ ///
+ /// Adds an input source to the game. This requires the Stride input system to be used.
+ ///
+ ///
+ ///
+ ///
+ public static IGameBuilder AddInputSource(this IGameBuilder gameBuilder, IInputSource inputSource)
+ {
+ gameBuilder.InputSources.Add(inputSource);
+ return gameBuilder;
+ }
+
+ ///
+ /// Default s for a Stride game.
+ ///
+ ///
+ ///
+ public static IGameBuilder UseDefaultGameSystems(this IGameBuilder gameBuilder)
+ {
+ gameBuilder
+ .AddService()
+ .AddService()
+ .AddService()
+ .AddService()
+ .AddService()
+ .AddService()
+ .AddService()
+ .AddService()
+ .AddService()
+ .AddService();
+
+ return gameBuilder;
+ }
+}
diff --git a/sources/engine/Stride.Engine/Engine/Builder/IGameBuilder.cs b/sources/engine/Stride.Engine/Engine/Builder/IGameBuilder.cs
new file mode 100644
index 0000000000..bfba8b2b95
--- /dev/null
+++ b/sources/engine/Stride.Engine/Engine/Builder/IGameBuilder.cs
@@ -0,0 +1,27 @@
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
+using System.Collections.Generic;
+using Stride.Core.Diagnostics;
+using Stride.Games;
+using System;
+using Microsoft.Extensions.DependencyInjection;
+using Stride.Input;
+
+namespace Stride.Engine.Builder;
+public interface IGameBuilder
+{
+ Dictionary InternalServices { get; }
+
+ IServiceCollection Services { get; }
+
+ GameSystemCollection GameSystems { get; }
+
+ List LogListeners { get; }
+
+ List InputSources { get; }
+
+ GameBase Game { get; set; }
+
+ GameContext Context { get; set; }
+}
diff --git a/sources/engine/Stride.Engine/Engine/Builder/MinimalGame.cs b/sources/engine/Stride.Engine/Engine/Builder/MinimalGame.cs
new file mode 100644
index 0000000000..ec96b4272c
--- /dev/null
+++ b/sources/engine/Stride.Engine/Engine/Builder/MinimalGame.cs
@@ -0,0 +1,108 @@
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Hosting;
+using Stride.Core.Diagnostics;
+using Stride.Core.Serialization;
+using Stride.Core.Serialization.Contents;
+using Stride.Games;
+using Stride.Graphics;
+using Stride.Input;
+
+namespace Stride.Engine.Builder;
+
+///
+/// A game class with no registered systems by default.
+///
+public class MinimalGame : GameBase, IHostedService
+{
+
+ ///
+ /// Gets the graphics device manager.
+ ///
+ /// The graphics device manager.
+ public GraphicsDeviceManager GraphicsDeviceManager { get; internal set; }
+
+ public MinimalGame(GameContext gameContext) : base()
+ {
+ Context = gameContext ?? GetDefaultContext();
+ Context.CurrentGame = this;
+ Context.Services = Services;
+
+ // Create Platform
+ Context.GamePlatform = GamePlatform.Create(Context);
+ Context.GamePlatform.Activated += GamePlatform_Activated;
+ Context.GamePlatform.Deactivated += GamePlatform_Deactivated;
+ Context.GamePlatform.Exiting += GamePlatform_Exiting;
+ Context.GamePlatform.WindowCreated += GamePlatformOnWindowCreated;
+
+ // Setup registry
+ Services.AddService(this);
+ Services.AddService(Context.GamePlatform);
+ Services.AddService(Context.GamePlatform);
+
+ // Creates the graphics device manager
+ GraphicsDeviceManager = new GraphicsDeviceManager(this);
+ Services.AddService(GraphicsDeviceManager);
+ Services.AddService(GraphicsDeviceManager);
+ }
+
+ public override void ConfirmRenderingSettings(bool gameCreation)
+ {
+ var deviceManager = (GraphicsDeviceManager)graphicsDeviceManager;
+
+ if (gameCreation)
+ {
+ //if our device width or height is actually smaller than requested we use the device one
+ deviceManager.PreferredBackBufferWidth = Context.RequestedWidth = Math.Min(deviceManager.PreferredBackBufferWidth, Window.ClientBounds.Width);
+ deviceManager.PreferredBackBufferHeight = Context.RequestedHeight = Math.Min(deviceManager.PreferredBackBufferHeight, Window.ClientBounds.Height);
+ }
+ }
+
+ protected override void Initialize()
+ {
+ base.Initialize();
+
+ Content.Serializer.LowLevelSerializerSelector = new SerializerSelector("Default", "Content");
+
+ // Add window specific input source
+ var inputManager = Services.GetService();
+ if (inputManager is not null)
+ {
+ var windowInputSource = InputSourceFactory.NewWindowInputSource(Context);
+ inputManager.Sources.Add(windowInputSource);
+ }
+ }
+
+ protected override void PrepareContext()
+ {
+ //Allow the user to add their own ContentManager
+ var contentManager = Services.GetService();
+
+ if (contentManager is null)
+ {
+ Log.Info("No ContentManager found, creating default ContentManager");
+ contentManager = new ContentManager(Services);
+ Services.AddService(contentManager);
+ Services.AddService(contentManager);
+ }
+
+ Content = contentManager;
+ }
+
+ public Task StartAsync(CancellationToken cancellationToken = default)
+ {
+ Run();
+ return Task.CompletedTask;
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken = default)
+ {
+ Exit();
+ return Task.CompletedTask;
+ }
+
+}
diff --git a/sources/engine/Stride.Engine/Engine/Design/GameSettings.cs b/sources/engine/Stride.Engine/Engine/Design/GameSettings.cs
index a012e43d9d..7bfd6fab92 100644
--- a/sources/engine/Stride.Engine/Engine/Design/GameSettings.cs
+++ b/sources/engine/Stride.Engine/Engine/Design/GameSettings.cs
@@ -1,12 +1,10 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
using Stride.Core;
using Stride.Core.Mathematics;
using Stride.Core.Serialization.Contents;
using Stride.Data;
-using Stride.Graphics;
namespace Stride.Engine.Design
{
diff --git a/sources/engine/Stride.Engine/Engine/Game.cs b/sources/engine/Stride.Engine/Engine/Game.cs
index 6c4c162ed5..c211e0ec6e 100644
--- a/sources/engine/Stride.Engine/Engine/Game.cs
+++ b/sources/engine/Stride.Engine/Engine/Game.cs
@@ -188,8 +188,24 @@ public LogMessageType ConsoleLogLevel
///
/// Initializes a new instance of the class.
///
- public Game()
+ public Game(GameContext context = null) : base()
{
+ Context = context ?? GetDefaultContext();
+ Context.CurrentGame = this;
+ Context.Services = Services;
+
+ // Create Platform
+ Context.GamePlatform = GamePlatform.Create(Context);
+ Context.GamePlatform.Activated += GamePlatform_Activated;
+ Context.GamePlatform.Deactivated += GamePlatform_Deactivated;
+ Context.GamePlatform.Exiting += GamePlatform_Exiting;
+ Context.GamePlatform.WindowCreated += GamePlatformOnWindowCreated;
+
+ // Setup registry
+ Services.AddService(this);
+ Services.AddService(Context.GamePlatform);
+ Services.AddService(Context.GamePlatform);
+
// Register the logger backend before anything else
logListener = GetLogListener();
@@ -205,6 +221,7 @@ public Game()
Services.AddService(SceneSystem);
Streaming = new StreamingManager(Services);
+ Services.AddService(Streaming);
Audio = new AudioSystem(Services);
Services.AddService(Audio);
@@ -256,7 +273,7 @@ protected override void PrepareContext()
if (Context.InitializeDatabase)
{
databaseFileProvider = InitializeAssetDatabase();
- ((DatabaseFileProviderService)Services.GetService()).FileProvider = databaseFileProvider;
+ Services.GetService().FileProvider = databaseFileProvider;
var renderingSettings = new RenderingSettings();
if (Content.Exists(GameSettings.AssetUrl))
@@ -342,6 +359,7 @@ protected override void Initialize()
Input = inputSystem.Manager;
Services.AddService(Input);
GameSystems.Add(inputSystem);
+ SetInitialInputSources(Input);
// Initialize the systems
base.Initialize();
@@ -393,6 +411,49 @@ protected override void Initialize()
OnGameStarted(this);
}
+ private void SetInitialInputSources(InputManager inputManager)
+ {
+ // Add window specific input source
+ var windowInputSource = InputSourceFactory.NewWindowInputSource(Context);
+ inputManager.Sources.Add(windowInputSource);
+
+ // Add platform specific input sources
+ switch (Context.ContextType)
+ {
+#if STRIDE_UI_SDL
+ case AppContextType.DesktopSDL:
+ break;
+#endif
+#if STRIDE_PLATFORM_ANDROID
+ case AppContextType.Android:
+ break;
+#endif
+#if STRIDE_PLATFORM_IOS
+ case AppContextType.iOS:
+ break;
+#endif
+#if STRIDE_PLATFORM_UWP
+ case AppContextType.UWPXaml:
+ case AppContextType.UWPCoreWindow:
+ break;
+#endif
+ case AppContextType.Desktop:
+#if (STRIDE_UI_WINFORMS || STRIDE_UI_WPF)
+ inputManager.Sources.Add(new InputSourceWindowsDirectInput());
+ if (InputSourceWindowsXInput.IsSupported())
+ inputManager.Sources.Add(new InputSourceWindowsXInput());
+#if STRIDE_INPUT_RAWINPUT
+ if (rawInputEnabled && Context is GameContextWinforms gameContextWinforms)
+ inputManager.Sources.Add(new InputSourceWindowsRawInput(gameContextWinforms.Control));
+#endif
+#endif
+ break;
+ default:
+ Log.Warning("GameContext type is not supported by the InputManager. Register your own for input to be handled properly.");
+ break;
+ }
+ }
+
internal static DatabaseFileProvider InitializeAssetDatabase()
{
using (Profiler.Begin(GameProfilingKeys.ObjectDatabaseInitialize))
diff --git a/sources/engine/Stride.Engine/Engine/GameSystem.cs b/sources/engine/Stride.Engine/Engine/GameSystem.cs
index 3ea3a8f9ec..545609b8c9 100644
--- a/sources/engine/Stride.Engine/Engine/GameSystem.cs
+++ b/sources/engine/Stride.Engine/Engine/GameSystem.cs
@@ -19,6 +19,6 @@ protected GameSystem(IServiceRegistry registry) : base(registry)
///
/// The game.
/// This value can be null
- public new Game Game => (Game)base.Game;
+ public new GameBase Game => base.Game;
}
}
diff --git a/sources/engine/Stride.Engine/Stride.Engine.csproj b/sources/engine/Stride.Engine/Stride.Engine.csproj
index 7bded5b689..7836b61cf4 100644
--- a/sources/engine/Stride.Engine/Stride.Engine.csproj
+++ b/sources/engine/Stride.Engine/Stride.Engine.csproj
@@ -26,6 +26,7 @@
+
diff --git a/sources/engine/Stride.Games/Android/GamePlatformAndroid.cs b/sources/engine/Stride.Games/Android/GamePlatformAndroid.cs
index 167afa863d..5db252837c 100644
--- a/sources/engine/Stride.Games/Android/GamePlatformAndroid.cs
+++ b/sources/engine/Stride.Games/Android/GamePlatformAndroid.cs
@@ -28,7 +28,7 @@ private void PopulateFullName()
FullName = $"{manufacturer} - {model}";
}
- public GamePlatformAndroid(GameBase game) : base(game)
+ public GamePlatformAndroid(GameContext context) : base(context)
{
PopulateFullName();
}
diff --git a/sources/engine/Stride.Games/Desktop/GamePlatformDesktop.cs b/sources/engine/Stride.Games/Desktop/GamePlatformDesktop.cs
index 7d48fae49e..c3911ba734 100644
--- a/sources/engine/Stride.Games/Desktop/GamePlatformDesktop.cs
+++ b/sources/engine/Stride.Games/Desktop/GamePlatformDesktop.cs
@@ -29,7 +29,7 @@ namespace Stride.Games
{
internal class GamePlatformDesktop : GamePlatform
{
- public GamePlatformDesktop(GameBase game) : base(game)
+ public GamePlatformDesktop(GameContext context) : base(context)
{
IsBlockingRun = true;
#if (STRIDE_UI_WINFORMS || STRIDE_UI_WPF)
diff --git a/sources/engine/Stride.Games/GameBase.cs b/sources/engine/Stride.Games/GameBase.cs
index 9fd977372d..9cb2262684 100644
--- a/sources/engine/Stride.Games/GameBase.cs
+++ b/sources/engine/Stride.Games/GameBase.cs
@@ -41,7 +41,7 @@ public abstract class GameBase : ComponentBase, IGame
{
#region Fields
- private readonly GamePlatform gamePlatform;
+ private GamePlatform gamePlatform => Context.GamePlatform;
private IGraphicsDeviceService graphicsDeviceService;
protected IGraphicsDeviceManager graphicsDeviceManager;
private ResumeManager resumeManager;
@@ -59,7 +59,7 @@ public abstract class GameBase : ComponentBase, IGame
private bool isMouseVisible;
- internal object TickLock = new object();
+ internal object TickLock = new();
#endregion
@@ -95,21 +95,23 @@ protected GameBase()
GameSystems = new GameSystemCollection(Services);
Services.AddService(GameSystems);
- // Create Platform
- gamePlatform = GamePlatform.Create(this);
- gamePlatform.Activated += GamePlatform_Activated;
- gamePlatform.Deactivated += GamePlatform_Deactivated;
- gamePlatform.Exiting += GamePlatform_Exiting;
- gamePlatform.WindowCreated += GamePlatformOnWindowCreated;
-
- // Setup registry
- Services.AddService(this);
- Services.AddService(gamePlatform);
- Services.AddService(gamePlatform);
-
IsActive = true;
}
+ protected static GameContext GetDefaultContext()
+ {
+#if STRIDE_PLATFORM_UWP
+ return GameContextFactory.NewGameContextUWPXaml();
+#elif STRIDE_PLATFORM_ANDROID
+ return GameContextFactory.NewGameContextAndroid();
+#elif STRIDE_PLATFORM_IOS
+ return GameContextFactory.NewGameContextiOS();
+#else
+ // Here we cover all Desktop variants: OpenTK, SDL, Winforms,...
+ return GameContextFactory.NewGameContextDesktop();
+#endif
+ }
+
#endregion
#region Public Events
@@ -165,7 +167,7 @@ protected GameBase()
///
/// Gets the .
///
- public ContentManager Content { get; private set; }
+ public ContentManager Content { get; protected set; }
///
/// Gets the game components registered by this game.
@@ -177,15 +179,15 @@ protected GameBase()
/// Gets the game context.
///
/// The game context.
- public GameContext Context { get; private set; }
+ public GameContext Context { get; protected set; }
///
/// Gets the graphics device.
///
/// The graphics device.
- public GraphicsDevice GraphicsDevice { get; private set; }
+ public GraphicsDevice GraphicsDevice { get; protected set; }
- public GraphicsContext GraphicsContext { get; private set; }
+ public GraphicsContext GraphicsContext { get; protected set; }
///
/// Gets or sets the time between each when is false.
@@ -197,13 +199,13 @@ protected GameBase()
/// Gets a value indicating whether this instance is active.
///
/// true if this instance is active; otherwise, false .
- public bool IsActive { get; private set; }
+ public bool IsActive { get; protected set; }
///
/// Gets a value indicating whether this instance is exiting.
///
/// true if this instance is exiting; otherwise, false .
- public bool IsExiting{ get; private set; }
+ public bool IsExiting{ get; protected set; }
///
/// Gets or sets a value indicating whether the elapsed time between each update should be constant,
@@ -295,13 +297,14 @@ public bool IsMouseVisible
/// Gets the abstract window.
///
/// The window.
+ [Obsolete("Use GameContext.GameWindow instead.")]
public GameWindow Window
{
get
{
- if (gamePlatform != null)
+ if (Context != null)
{
- return gamePlatform.MainWindow;
+ return Context.GameWindow;
}
return null;
}
@@ -402,7 +405,7 @@ internal void InitializeBeforeRun()
///
/// The window Context for this game.
/// Cannot run this instance while it is already running
- public void Run(GameContext gameContext = null)
+ public virtual void Run(GameContext gameContext = null)
{
if (IsRunning)
{
@@ -416,22 +419,12 @@ public void Run(GameContext gameContext = null)
throw new InvalidOperationException("No GraphicsDeviceManager found");
}
- // Gets the GameWindow Context
- if (gameContext == null)
+ SetGameContext(gameContext);
+ EnsureGameContextIsSet();
+ if(Context is null)
{
- AppContextType c;
- if (OperatingSystem.IsWindows())
- c = AppContextType.Desktop;
- else if (OperatingSystem.IsAndroid())
- c = AppContextType.Android;
- else if (OperatingSystem.IsIOS() || OperatingSystem.IsTvOS() || OperatingSystem.IsWatchOS())
- c = AppContextType.iOS;
- else
- c = AppContextType.DesktopSDL;
- gameContext = GameContextFactory.NewGameContext(c);
+ throw new InvalidOperationException("No GameContext found");
}
-
- Context = gameContext;
PrepareContext();
@@ -446,7 +439,7 @@ public void Run(GameContext gameContext = null)
Context.RequestedGraphicsProfile = graphicsDeviceManagerImpl.PreferredGraphicsProfile;
Context.DeviceCreationFlags = graphicsDeviceManagerImpl.DeviceCreationFlags;
- gamePlatform.Run(Context);
+ gamePlatform.Run();
if (gamePlatform.IsBlockingRun)
{
@@ -468,6 +461,28 @@ public void Run(GameContext gameContext = null)
}
}
+ ///
+ /// Attempts to get GameContext based on the current platform.
+ ///
+ private void EnsureGameContextIsSet()
+ {
+ // Gets the Game Context
+ if (Context == null)
+ {
+ AppContextType c;
+ if (OperatingSystem.IsWindows())
+ c = AppContextType.Desktop;
+ else if (OperatingSystem.IsAndroid())
+ c = AppContextType.Android;
+ else if (OperatingSystem.IsIOS() || OperatingSystem.IsTvOS() || OperatingSystem.IsWatchOS())
+ c = AppContextType.iOS;
+ else
+ c = AppContextType.DesktopSDL;
+
+ Context = GameContextFactory.NewGameContext(c);
+ }
+ }
+
///
/// Creates or updates before window and device are created.
///
@@ -512,6 +527,46 @@ public void Tick()
}
}
+ public virtual void SetGameContext(GameContext context)
+ {
+ if(IsRunning)
+ {
+ throw new InvalidOperationException("Cannot set the game context while the game is running");
+ }
+
+ if (Context is not null && context is not null)
+ {
+ context.GamePlatform = Context.GamePlatform;
+ Context = context;
+ Context.CurrentGame = this;
+
+ // Overwrite Platform
+ if (gamePlatform != null)
+ {
+ Context.GamePlatform.Activated -= GamePlatform_Activated;
+ Context.GamePlatform.Deactivated -= GamePlatform_Deactivated;
+ Context.GamePlatform.Exiting -= GamePlatform_Exiting;
+ Context.GamePlatform.WindowCreated -= GamePlatformOnWindowCreated;
+
+ Services.RemoveService();
+ Services.RemoveService();
+ }
+
+ Context.GamePlatform = GamePlatform.Create(context);
+ Context.GamePlatform.Activated += GamePlatform_Activated;
+ Context.GamePlatform.Deactivated += GamePlatform_Deactivated;
+ Context.GamePlatform.Exiting += GamePlatform_Exiting;
+ Context.GamePlatform.WindowCreated += GamePlatformOnWindowCreated;
+
+ Services.AddService(Context.GamePlatform);
+ Services.AddService(Context.GamePlatform);
+ }
+ else if (context is not null)
+ {
+ Context = context;
+ }
+ }
+
///
/// Calls automatically based on this game's setup, override it to implement your own system.
///
@@ -586,7 +641,7 @@ protected virtual void RawTickProducer()
RawTick(singleFrameElapsedTime, updateCount, drawLag / (float)TargetElapsedTime.Ticks, drawFrame);
- var window = gamePlatform.MainWindow;
+ var window = Window;
if (gamePlatform.IsBlockingRun) // throttle fps if Game.Tick() called from internal main loop
{
if (window.IsMinimized || window.Visible == false || (window.Focused == false && TreatNotFocusedLikeMinimized))
@@ -717,17 +772,13 @@ protected override void Destroy()
for (int i = 0; i < array.Length; i++)
{
var disposable = array[i] as IDisposable;
- if (disposable != null)
- {
- disposable.Dispose();
- }
+ disposable?.Dispose();
}
// Reset graphics context
GraphicsContext = null;
- var disposableGraphicsManager = graphicsDeviceManager as IDisposable;
- if (disposableGraphicsManager != null)
+ if (graphicsDeviceManager is IDisposable disposableGraphicsManager)
{
disposableGraphicsManager.Dispose();
}
@@ -888,7 +939,7 @@ protected virtual void OnWindowCreated()
WindowCreated?.Invoke(this, EventArgs.Empty);
}
- private void GamePlatformOnWindowCreated(object sender, EventArgs eventArgs)
+ protected void GamePlatformOnWindowCreated(object sender, EventArgs eventArgs)
{
Window.IsMouseVisible = isMouseVisible;
OnWindowCreated();
@@ -912,7 +963,7 @@ protected virtual void UnloadContent()
GameSystems.UnloadContent();
}
- private void GamePlatform_Activated(object sender, EventArgs e)
+ protected void GamePlatform_Activated(object sender, EventArgs e)
{
if (!IsActive)
{
@@ -921,7 +972,7 @@ private void GamePlatform_Activated(object sender, EventArgs e)
}
}
- private void GamePlatform_Deactivated(object sender, EventArgs e)
+ protected void GamePlatform_Deactivated(object sender, EventArgs e)
{
if (IsActive)
{
@@ -930,7 +981,7 @@ private void GamePlatform_Deactivated(object sender, EventArgs e)
}
}
- private void GamePlatform_Exiting(object sender, EventArgs e)
+ protected void GamePlatform_Exiting(object sender, EventArgs e)
{
OnExiting(this, EventArgs.Empty);
}
diff --git a/sources/engine/Stride.Games/GameContext.cs b/sources/engine/Stride.Games/GameContext.cs
index 8e2338269e..4fd1a2bc8a 100644
--- a/sources/engine/Stride.Games/GameContext.cs
+++ b/sources/engine/Stride.Games/GameContext.cs
@@ -30,7 +30,7 @@
namespace Stride.Games
{
///
- /// Contains context used to render the game (Control for WinForm, a DrawingSurface for WP8...etc.).
+ /// Contains context for the game and its core modules.
///
public abstract class GameContext
{
@@ -56,6 +56,14 @@ public abstract class GameContext
/// The run loop.
public Action ExitCallback { get; internal set; }
+ public GameBase CurrentGame { get; set; }
+
+ public GamePlatform GamePlatform { get; set; }
+
+ public GameWindow GameWindow { get; set; }
+
+ public IServiceRegistry Services { get; set; }
+
// TODO: remove these requested values.
///
diff --git a/sources/engine/Stride.Games/GamePlatform.cs b/sources/engine/Stride.Games/GamePlatform.cs
index 36848ae436..743d2f46ad 100644
--- a/sources/engine/Stride.Games/GamePlatform.cs
+++ b/sources/engine/Stride.Games/GamePlatform.cs
@@ -30,35 +30,38 @@
namespace Stride.Games
{
- internal abstract class GamePlatform : ReferenceBase, IGraphicsDeviceFactory, IGamePlatform
+ public abstract class GamePlatform : ReferenceBase, IGraphicsDeviceFactory, IGamePlatform
{
private bool hasExitRan = false;
- protected readonly GameBase game;
+ //protected readonly IServiceRegistry Services;
- protected readonly IServiceRegistry Services;
+ protected GameBase game => context.CurrentGame;
- protected GameWindow gameWindow;
+ protected GameWindow gameWindow => context.GameWindow;
+
+ protected GameContext context;
public string FullName { get; protected set; } = string.Empty;
- protected GamePlatform(GameBase game)
+ protected GamePlatform(GameContext context)
{
- this.game = game;
- Services = game.Services;
+ //Services = context.Services;
+ this.context = context;
+ this.context.GamePlatform = this;
}
- public static GamePlatform Create(GameBase game)
+ public static GamePlatform Create(GameContext context)
{
#if STRIDE_PLATFORM_UWP
- return new GamePlatformUWP(game);
+ return new GamePlatformUWP(context);
#elif STRIDE_PLATFORM_ANDROID
- return new GamePlatformAndroid(game);
+ return new GamePlatformAndroid(context);
#elif STRIDE_PLATFORM_IOS
- return new GamePlatformiOS(game);
+ return new GamePlatformiOS(context);
#else
// Here we cover all Desktop variants: OpenTK, SDL, Winforms,...
- return new GamePlatformDesktop(game);
+ return new GamePlatformDesktop(context);
#endif
}
@@ -95,7 +98,7 @@ public virtual GameWindow CreateWindow(GameContext gameContext)
var window = GetSupportedGameWindow(gameContext.ContextType);
if (window != null)
{
- window.Services = Services;
+ //window.Services = Services;
// Pass initial size
var requestedSize = new Int2(gameContext.RequestedWidth, gameContext.RequestedHeight);
@@ -115,11 +118,12 @@ public virtual GameWindow CreateWindow(GameContext gameContext)
///
public bool IsBlockingRun { get; protected set; }
- public void Run(GameContext gameContext)
+ public void Run()
{
- IsBlockingRun = !gameContext.IsUserManagingRun;
+ IsBlockingRun = !context.IsUserManagingRun;
- gameWindow = CreateWindow(gameContext);
+ // Create the game window if not already created manually
+ context.GameWindow ??= CreateWindow(context);
// Register on Activated
gameWindow.Activated += OnActivated;
@@ -293,7 +297,7 @@ public virtual List FindBestDevices(GameGraphicsParam
IsFullScreen = preferredParameters.IsFullScreen,
PreferredFullScreenOutputIndex = preferredParameters.PreferredFullScreenOutputIndex,
PresentationInterval = preferredParameters.SynchronizeWithVerticalRetrace ? PresentInterval.One : PresentInterval.Immediate,
- DeviceWindowHandle = MainWindow.NativeWindow,
+ DeviceWindowHandle = context.GameWindow.NativeWindow,
ColorSpace = preferredParameters.ColorSpace,
},
};
@@ -380,7 +384,7 @@ protected override void Destroy()
if (gameWindow != null)
{
gameWindow.Dispose();
- gameWindow = null;
+ context.GameWindow = null;
}
Activated = null;
diff --git a/sources/engine/Stride.Games/GameWindow.cs b/sources/engine/Stride.Games/GameWindow.cs
index 26a1b83937..02dd1be2d7 100644
--- a/sources/engine/Stride.Games/GameWindow.cs
+++ b/sources/engine/Stride.Games/GameWindow.cs
@@ -268,7 +268,7 @@ public virtual IMessageLoop CreateUserManagedMessageLoop()
throw new PlatformNotSupportedException();
}
- internal IServiceRegistry Services { get; set; }
+ //internal IServiceRegistry Services { get; set; }
protected internal abstract void SetSupportedOrientations(DisplayOrientation orientations);
diff --git a/sources/engine/Stride.Games/GraphicsDeviceManager.cs b/sources/engine/Stride.Games/GraphicsDeviceManager.cs
index 86cfab0220..08cd2404b4 100644
--- a/sources/engine/Stride.Games/GraphicsDeviceManager.cs
+++ b/sources/engine/Stride.Games/GraphicsDeviceManager.cs
@@ -49,7 +49,7 @@ public class GraphicsDeviceManager : ComponentBase, IGraphicsDeviceManager, IGra
private readonly object lockDeviceCreation;
- private GameBase game;
+ private readonly GameBase game;
private bool deviceSettingsChanged;
@@ -100,12 +100,12 @@ public class GraphicsDeviceManager : ComponentBase, IGraphicsDeviceManager, IGra
///
/// The game.
/// The game instance cannot be null.
- internal GraphicsDeviceManager(GameBase game)
+ public GraphicsDeviceManager(GameBase game)
{
this.game = game;
if (this.game == null)
{
- throw new ArgumentNullException("game");
+ throw new ArgumentNullException(nameof(game));
}
lockDeviceCreation = new object();
@@ -119,8 +119,8 @@ internal GraphicsDeviceManager(GameBase game)
preferredBackBufferHeight = DefaultBackBufferHeight;
preferredRefreshRate = new Rational(60, 1);
PreferredMultisampleCount = MultisampleCount.None;
- PreferredGraphicsProfile = new[]
- {
+ PreferredGraphicsProfile =
+ [
GraphicsProfile.Level_11_1,
GraphicsProfile.Level_11_0,
GraphicsProfile.Level_10_1,
@@ -128,7 +128,7 @@ internal GraphicsDeviceManager(GameBase game)
GraphicsProfile.Level_9_3,
GraphicsProfile.Level_9_2,
GraphicsProfile.Level_9_1,
- };
+ ];
graphicsDeviceFactory = game.Services.GetService();
if (graphicsDeviceFactory == null)
@@ -661,6 +661,7 @@ protected virtual GraphicsDeviceInformation FindBestDevice(bool anySuitableDevic
preferredParameters.PreferredBackBufferHeight = resizedBackBufferHeight;
}
+ graphicsDeviceFactory = game.Services.GetService();
var devices = graphicsDeviceFactory.FindBestDevices(preferredParameters);
if (devices.Count == 0)
{
diff --git a/sources/engine/Stride.Games/IGame.cs b/sources/engine/Stride.Games/IGame.cs
index c8da0cf304..5788899fcd 100644
--- a/sources/engine/Stride.Games/IGame.cs
+++ b/sources/engine/Stride.Games/IGame.cs
@@ -4,7 +4,6 @@
using Stride.Core;
using Stride.Core.Serialization.Contents;
-using Stride.Games.Time;
using Stride.Graphics;
namespace Stride.Games
@@ -140,5 +139,11 @@ public interface IGame
///
/// The window.
GameWindow Window { get; }
+
+ ///
+ /// Sets the game context.
+ ///
+ ///
+ public void SetGameContext(GameContext context);
}
}
diff --git a/sources/engine/Stride.Games/IGamePlatform.cs b/sources/engine/Stride.Games/IGamePlatform.cs
index 9db9b9618f..ea4671c2dc 100644
--- a/sources/engine/Stride.Games/IGamePlatform.cs
+++ b/sources/engine/Stride.Games/IGamePlatform.cs
@@ -1,5 +1,7 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+using System;
+
namespace Stride.Games
{
///
@@ -17,6 +19,7 @@ public interface IGamePlatform
/// Gets the main window.
///
/// The main window.
+ [Obsolete("Use GameContext.MainWindow instead. This property will be removed in a future version.")]
GameWindow MainWindow { get; }
///
diff --git a/sources/engine/Stride.Games/Windowing/IGameWindow.cs b/sources/engine/Stride.Games/Windowing/IGameWindow.cs
new file mode 100644
index 0000000000..5f23573032
--- /dev/null
+++ b/sources/engine/Stride.Games/Windowing/IGameWindow.cs
@@ -0,0 +1,22 @@
+using System;
+using Stride.Core.Mathematics;
+
+namespace Stride.Games.Windowing;
+public interface IGameWindow : IStrideSurface
+{
+ public IntPtr WindowHandle { get; }
+ public Int2 Position { get; set; }
+
+ public string Title { get; set; }
+
+ public WindowState State { get; set; }
+}
+
+public enum WindowState
+{
+ Normal,
+ Minimized,
+ Maximized,
+ FullscreenWindowed,
+ FullscreenExclusive,
+}
diff --git a/sources/engine/Stride.Games/Windowing/IStrideSurface.cs b/sources/engine/Stride.Games/Windowing/IStrideSurface.cs
new file mode 100644
index 0000000000..7e7481ff54
--- /dev/null
+++ b/sources/engine/Stride.Games/Windowing/IStrideSurface.cs
@@ -0,0 +1,8 @@
+using System;
+using Stride.Core.Mathematics;
+
+namespace Stride.Games.Windowing;
+public interface IStrideSurface
+{
+ public Int2 Size { get; set; }
+}
diff --git a/sources/engine/Stride.Games/WindowsStore/GamePlatformUWP.cs b/sources/engine/Stride.Games/WindowsStore/GamePlatformUWP.cs
index 466ac7fc17..87cfa1bbc7 100644
--- a/sources/engine/Stride.Games/WindowsStore/GamePlatformUWP.cs
+++ b/sources/engine/Stride.Games/WindowsStore/GamePlatformUWP.cs
@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
//
// Copyright (c) 2010-2013 SharpDX - Alexandre Mutel
@@ -32,7 +32,7 @@ namespace Stride.Games
{
internal class GamePlatformUWP : GamePlatform
{
- public GamePlatformUWP(GameBase game) : base(game)
+ public GamePlatformUWP(GameContext context) : base(context)
{
// Application lifecycle reference:
// https://docs.microsoft.com/en-us/windows/uwp/launch-resume/app-lifecycle
diff --git a/sources/engine/Stride.Games/iOS/GamePlatformiOS.cs b/sources/engine/Stride.Games/iOS/GamePlatformiOS.cs
index 70f2c9ddbd..5ca80248c4 100644
--- a/sources/engine/Stride.Games/iOS/GamePlatformiOS.cs
+++ b/sources/engine/Stride.Games/iOS/GamePlatformiOS.cs
@@ -27,7 +27,7 @@ private unsafe void PopulateFullName()
Marshal.FreeHGlobal(output);
}
- public GamePlatformiOS(GameBase game) : base(game)
+ public GamePlatformiOS(GameContext context) : base(context)
{
PopulateFullName();
}
diff --git a/sources/engine/Stride.Input/InputManager.cs b/sources/engine/Stride.Input/InputManager.cs
index 5b16714c14..eb9e1d788c 100644
--- a/sources/engine/Stride.Input/InputManager.cs
+++ b/sources/engine/Stride.Input/InputManager.cs
@@ -318,8 +318,6 @@ public void Initialize(GameContext gameContext)
{
this.gameContext = gameContext ?? throw new ArgumentNullException(nameof(gameContext));
- AddSources();
-
// After adding initial devices, reassign gamepad id's
// this creates a beter index assignment in the case where you have both an xbox controller and another controller at startup
var sortedGamePads = GamePads.OrderBy(x => x.CanChangeIndex);
@@ -626,10 +624,11 @@ public void PoolInputEvent(InputEvent inputEvent)
{
eventRouters[inputEvent.GetType()].PoolEvent(inputEvent);
}
-
+
///
/// Resets the collection back to it's default values
///
+ [Obsolete("This should be managed manually instead by using the Sources collection")]
public void ResetSources()
{
Sources.Clear();
@@ -704,7 +703,8 @@ private void AddSources()
#endif
break;
default:
- throw new InvalidOperationException("GameContext type is not supported by the InputManager");
+ Logger.Warning("GameContext type is not supported by the InputManager. Register your own for input to be handled properly.");
+ break;
}
}
diff --git a/sources/engine/Stride.Input/SDL/InputSourceSDL.cs b/sources/engine/Stride.Input/SDL/InputSourceSDL.cs
index 7b33c0afb0..328cb58cad 100644
--- a/sources/engine/Stride.Input/SDL/InputSourceSDL.cs
+++ b/sources/engine/Stride.Input/SDL/InputSourceSDL.cs
@@ -14,7 +14,7 @@ namespace Stride.Input
///
/// Provides support for mouse/touch/keyboard/gamepads using SDL
///
- internal unsafe class InputSourceSDL : InputSourceBase
+ public unsafe class InputSourceSDL : InputSourceBase
{
private static Sdl SDL = Window.SDL;
diff --git a/sources/engine/Stride.Input/Windows/InputSourceWinforms.cs b/sources/engine/Stride.Input/Windows/InputSourceWinforms.cs
index 41179232e5..8023f0459f 100644
--- a/sources/engine/Stride.Input/Windows/InputSourceWinforms.cs
+++ b/sources/engine/Stride.Input/Windows/InputSourceWinforms.cs
@@ -18,7 +18,7 @@ namespace Stride.Input
///
/// Provides support for mouse and keyboard input on windows forms
///
- internal class InputSourceWinforms : InputSourceBase
+ public class InputSourceWinforms : InputSourceBase
{
private readonly HashSet heldKeys = new HashSet();
private readonly List keysToRelease = new List();
diff --git a/sources/engine/Stride.Rendering/Rendering/Fonts/GameFontSystem.cs b/sources/engine/Stride.Rendering/Rendering/Fonts/GameFontSystem.cs
index 0db5f3866f..570f6895b3 100644
--- a/sources/engine/Stride.Rendering/Rendering/Fonts/GameFontSystem.cs
+++ b/sources/engine/Stride.Rendering/Rendering/Fonts/GameFontSystem.cs
@@ -15,11 +15,11 @@ public class GameFontSystem : GameSystemBase
{
public FontSystem FontSystem { get; private set; }
- public GameFontSystem(IServiceRegistry registry)
+ public GameFontSystem(IServiceRegistry registry, FontSystem fontSystem = null)
: base(registry)
{
Visible = true;
- FontSystem = new FontSystem();
+ FontSystem = fontSystem ?? new FontSystem();
}
public override void Draw(GameTime gameTime)
diff --git a/sources/engine/Stride.Rendering/Streaming/StreamingManager.cs b/sources/engine/Stride.Rendering/Streaming/StreamingManager.cs
index 394c791ec6..21ca774303 100644
--- a/sources/engine/Stride.Rendering/Streaming/StreamingManager.cs
+++ b/sources/engine/Stride.Rendering/Streaming/StreamingManager.cs
@@ -92,9 +92,8 @@ public class StreamingManager : GameSystemBase, IStreamingManager, ITexturesStre
public StreamingManager([NotNull] IServiceRegistry services)
: base(services)
{
- services.AddService(this);
- services.AddService(this);
- services.AddService(this);
+ Services.AddService(this);
+ Services.AddService(this);
ContentStreaming = new ContentStreamingService();