-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathSSLGame.cs
More file actions
250 lines (211 loc) · 9.56 KB
/
SSLGame.cs
File metadata and controls
250 lines (211 loc) · 9.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
using Gum.DataTypes;
using Gum.Wireframe;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using MonoGame.ImGuiNet;
using MonoGameGum;
using SKSSL.ECS;
using SKSSL.Localization;
using SKSSL.Scenes;
using static SKSSL.DustLogger;
// ReSharper disable ConvertToConstant.Global
// ReSharper disable CollectionNeverQueried.Global
// ReSharper disable FieldCanBeMadeReadOnly.Global
// ReSharper disable NotAccessedField.Global
// ReSharper disable VirtualMemberCallInConstructor
// ReSharper disable NotAccessedField.Local
namespace SKSSL;
/// <summary>
/// Game Instances should inherit this class to have Gum and other systems automatically initialized.
/// <code>
/// override Initialize() {
/// base.Initialize(); // Naturally!
/// GameLoader.Register(...); // <- Registering Game Loaders
/// }
/// </code>
/// Registering Game factories, loaders and such, such as anything that inherits BaseRegistry or <see cref="Loc"/>,
/// is incredibly important as these are the loaders that will LOAD the game's content.
/// </summary>
public abstract class SSLGame : Game
{
#region Fields
/// Title of game window.
public string Title => Window.Title;
/// Total time played for this game session.
public static DateTime GameplayTime;
/// Ultimate toggle to use ECS service. Enable this at project initialization.
/// To use, add the following to the game class inheriting SSLGame:
/// <code>
/// static MyGameClass() => UseECS = true;
/// </code>
public static bool UseECS = false;
/// General context of the game dictated here.
public static SceneManager SceneManager = null!;
/// Static-instanced access for the Content Manager belonging to the active game instance.
public static readonly List<ContentManager> ContentManagers = [];
private readonly GraphicsDeviceManager _graphicsManager;
private readonly SpriteBatch _spriteBatch;
private static GumService Gum => GumService.Default;
private readonly InteractiveGue _currentScreenGue = new();
public readonly ImGuiRenderer GuiRenderer;
/// Registries and services belonging to the game.
private readonly IServiceProvider GameServices;
/// <summary>
/// An array of Tuple paths assigned to an ID. These are loaded into the game's pather, and should
/// NEVER change. General examples include game texture and yaml prototypes folders.
/// </summary>
protected abstract (string id, string path)[] StaticPaths { get; }
/// <summary>
/// The Project Gum UI file that will dictate how UI is loaded.
/// <code>
/// Example: "Gum/SolKom.gumx"
/// </code>
/// </summary>
public static string GumFile = "CHANGE_ME";
/// All content directories contained in the game folder. (E.g. game, mods ➡ etc.)
public readonly IEnumerable<GameContentDirectory> GameContentDirectories;
#endregion
/// <remarks>
/// In order to Spawn, Remove, or generally interact with entities in an ECS, a context is required. This context
/// varies between scenes.
/// </remarks>
/// <returns>Scene Manager's Current World's Entity Context.</returns>
public static EntityContext ECS(BaseWorld? world = null)
{
string message;
// If not using ECS, then why? Throw an error!
if (!UseECS)
{
message = "Failed to get Entity Context because ECS is not enabled.";
Log(message, LOG.SYSTEM_ERROR, outputToFile: true);
throw new SettingsException(message);
}
// If the scene manager has a world, then use that world instead of the provided one if this is null.
if (world == null && SceneManager.CurrentWorld is BaseWorld res)
{
world ??= res; // Reassign world.
}
// Final check to validate that the world (and its ECS) is functioning.
if (world?.ECS is null)
{
message = "Failed to get Entity Context from null world or null World ECS!";
Log(message, LOG.SYSTEM_ERROR, outputToFile: true);
throw new Exception(message);
}
// Return the latest & greatest entity context!
// Do NOT instantiate a blank-constructor EntityContext here! It will cause an infinite loop of ECS() calls!
var entityContext = new EntityContext(world);
return entityContext;
}
/// <summary>
/// Constructor for SSLGame.
/// </summary>
/// <param name="title">Title of the game window.</param>
/// <param name="gumFile">Gum Interface File</param>
/// <param name="contents">Additional content managers belonging to attached libraries.</param>
protected SSLGame(string title, string gumFile = "", params ContentManager[] contents)
{
Window.Title = title;
Content.RootDirectory = "Content";
Window.AllowUserResizing = true;
Window.ClientSizeChanged += HandleClientSizeChanged;
_graphicsManager = HandleGraphicsDesignManager(new GraphicsDeviceManager(this));
_spriteBatch = new SpriteBatch(GraphicsDevice);
_currentScreenGue.UpdateLayout(); // UI Behaviour when dragged
if (string.IsNullOrEmpty(gumFile))
Log($"Provided gum project file is empty! {title}, {nameof(SSLGame)}", LOG.SYSTEM_WARNING);
else
GumFile = gumFile;
var services = new ServiceCollection();
LoadServices(services);
GameServices = services.BuildServiceProvider();
// Assign static-access content managers.
ContentManagers.Add(Content);
ContentManagers.AddRange(contents);
// Initialize all static paths, which the developer must have defined!
// Includes load-order implementation. Higher values override lower values.
// TODO: Add a way to change load order priorities in game directories. Likely requires a file? Master file?
// A file per-game folder means version mismatches per file change that breaks every update.
// Ergo, a master file may be the best solution.
var gameDirectories = StaticGameLoader.GetAllGameDirectories();
GameContentDirectories = gameDirectories.OrderBy(d => d.LoadOrder).ToList();
// Display ECS status. This constructor is called after inheritors.
Log($"ECS status: {(UseECS ? "on" : "off")}");
if (UseECS)
{
// Initializing component registry before anything else.
Log("Initializing components.");
ComponentRegistry.Initialize();
}
// Load Static Game Content
Log("Initializing static paths.");
StaticGameLoader.Initialize(StaticPaths);
StaticGameLoader.Load(path => StaticGameLoader.GPath(path));
Log("Initializing ImGUI.");
GuiRenderer = new ImGuiRenderer(this);
GuiRenderer.RebuildFontAtlas();
Log("SSLGame Root Initialized. Proceeding...");
}
/// <summary>
/// Loads programmer-provided game services and registries.
/// </summary>
/// <param name="services"></param>
/// <code>services.AddSingleton<ExampleRegistry>();</code>
protected virtual void LoadServices(ServiceCollection services)
{
// Add game services to override method here.
}
/// <summary>
/// Accommodates for when the user readjusts the UI dimensions.
/// </summary>
private void HandleClientSizeChanged(object? _, EventArgs e)
{
GraphicalUiElement.CanvasWidth = _graphicsManager.GraphicsDevice.Viewport.Width;
GraphicalUiElement.CanvasHeight = _graphicsManager.GraphicsDevice.Viewport.Height;
}
private static GraphicsDeviceManager HandleGraphicsDesignManager(GraphicsDeviceManager graphicsDeviceManager)
{
var monitorWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
var monitorHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
graphicsDeviceManager.PreferredBackBufferWidth = monitorWidth; // Set preferred width
graphicsDeviceManager.PreferredBackBufferHeight = monitorHeight; // Set preferred height
graphicsDeviceManager.ApplyChanges();
return graphicsDeviceManager;
}
/// <summary>
/// For custom <see cref="StaticGameLoader"/>s, you MUST initialize them before the base.Initialize() an inheritance
/// level above this class.
/// </summary>
protected override void Initialize()
{
// Initialize Gum UI Handling (Some projects may choose not to utilize Gum)
GumProjectSave? gumSave = null;
if (!string.IsNullOrEmpty(GumFile)) gumSave = Gum.Initialize(this, GumFile);
SceneManager = new SceneManager(this, _graphicsManager, _spriteBatch, gumSave);
Components.Add(SceneManager);
// Continue
base.Initialize();
}
/// Quits the game.
public static void Quit() =>
throw new NotImplementedException("Quit is not implemented, really. Let's crash, instead.");
/// Resets the game.
public static void ResetGame() =>
throw new NotImplementedException("ResetGame is not implemented, really. Let's crash, instead.");
/// <inheritdoc />
protected override void Draw(GameTime gameTime)
{
base.Draw(gameTime);
Gum.Draw(); // Draw Gum UI after game draw.
}
/// <inheritdoc />
protected override void Update(GameTime gameTime)
{
GameplayTime = GameplayTime.AddSeconds(gameTime.ElapsedGameTime.TotalSeconds);
base.Update(gameTime);
Gum.Update(gameTime); // Update Gum UI after game update.
}
}