1 module evael.core.Game; 2 3 import std.array : split; 4 import std.conv; 5 import std.typecons; 6 7 public import decs; 8 9 import dnogc.DynamicArray; 10 11 import evael.core.GameState; 12 13 import evael.graphics.GraphicsDevice; 14 import evael.graphics.Font; 15 import evael.graphics.gui.GuiManager; 16 17 import evael.audio.AudioDevice; 18 19 import evael.system.AssetLoader; 20 import evael.system.InputHandler; 21 import evael.system.Input; 22 import evael.system.I18n; 23 import evael.system.Window; 24 import evael.system.WindowSettings; 25 import evael.system.GLContextSettings; 26 import evael.system.Cursor; 27 28 import evael.utils.Math; 29 import evael.utils.Singleton; 30 import evael.utils.Size; 31 import evael.utils.Config; 32 import evael.utils.Functions; 33 34 import evael.Init; 35 36 /** 37 * Game 38 */ 39 class Game 40 { 41 private Window m_window; 42 private GraphicsDevice m_graphicsDevice; 43 private GuiManager m_guiManager; 44 private AssetLoader m_assetLoader; 45 private AudioDevice m_audioDevice; 46 private EntityManager m_entityManager; 47 private I18n m_i18n; 48 private InputHandler m_inputHandler; 49 50 /// Current game state. 51 private GameState m_currentGameState; 52 53 /// Tasks of other thread that will be processed by main thread. 54 private alias OnEvent = void delegate(); 55 private DynamicArray!OnEvent m_events; 56 57 private alias Task = Tuple!(long, OnEvent); 58 private DynamicArray!Task m_scheduledTasks; 59 60 /// Indicates if the game is still running. 61 private bool m_running; 62 63 /// Values for fixed timestep game loop. 64 private float m_tickrate; // How many times world will be updated per second. 65 private float m_deltaTime; // Time that elapsed since the last tick in ms. 66 private int m_maxFrameSkip; 67 68 private GameState[string] m_gameStates; 69 70 /** 71 * Game constructor. 72 * Params: 73 * settings : window settings 74 * contextSettings : gl context settings 75 */ 76 public this(in WindowSettings settings, in GLContextSettings contextSettings = GLContextSettings()) 77 { 78 loadExternalLibraries(); 79 80 Config.load("./config.ini"); 81 82 this.m_window = new Window(settings, contextSettings); 83 this.m_window.setWindowCloseCallback(bindDelegate(&this.onWindowClose)); 84 this.m_window.setWindowSizeCallback(bindDelegate(&this.onWindowResize)); 85 this.m_window.setScrollCallback(bindDelegate(&this.onMouseWheel)); 86 this.m_window.setKeyCallback(bindDelegate(&this.onKey)); 87 this.m_window.setCharCallback(bindDelegate(&this.onText)); 88 89 this.m_inputHandler = new InputHandler(this); 90 91 this.m_window.setCursorPosCallback(bindDelegate(&this.m_inputHandler.onMouseMove)); 92 this.m_window.setMouseButtonCallback(bindDelegate(&this.m_inputHandler.onMouseClick)); 93 94 this.m_graphicsDevice = GraphicsDevice.getInstance(); 95 this.m_assetLoader = AssetLoader.getInstance(); 96 this.m_graphicsDevice.defaultFont = this.m_assetLoader.load!(Font)("Roboto-Regular.ttf", this.m_graphicsDevice.nvgContext); 97 this.m_graphicsDevice.resolution = settings.resolution; 98 this.m_guiManager = new GuiManager(this.m_graphicsDevice); 99 this.m_audioDevice = new AudioDevice(); 100 this.m_entityManager = new EntityManager(); 101 102 this.m_tickrate = 64.0f; 103 this.m_deltaTime = 1000.0f / this.m_tickrate; 104 this.m_maxFrameSkip = 5; 105 106 this.m_i18n = new I18n(); 107 this.m_i18n.setLocale("fr"); 108 109 this.m_running = true; 110 } 111 112 /** 113 * Game destructor. 114 */ 115 public void dispose() 116 { 117 this.m_running = false; 118 119 this.m_window.dispose(); 120 this.m_events.dispose(); 121 this.m_currentGameState.dispose(); 122 this.m_audioDevice.dispose(); 123 this.m_graphicsDevice.dispose(); 124 this.m_guiManager.dispose(); 125 126 unloadExternalLibraries(); 127 } 128 129 /** 130 * Deault game loop. 131 */ 132 public void run() 133 { 134 float nextGameTick = getCurrentTime(); 135 136 int loops = 0; 137 138 while (this.m_running) 139 { 140 immutable currentTick = getCurrentTime(); 141 loops = 0; 142 143 while (currentTick > nextGameTick && loops < this.m_maxFrameSkip) 144 { 145 this.fixedUpdate(); 146 147 nextGameTick += this.m_deltaTime; 148 loops++; 149 } 150 151 immutable interpolation = cast(float)(currentTick + this.m_deltaTime - nextGameTick) / cast(float) this.m_deltaTime; 152 153 this.update(interpolation); 154 this.pollEvents(); 155 } 156 } 157 158 /** 159 * Processes game logic at fixed time rate, defined by m_tickrate. 160 */ 161 public void fixedUpdate() 162 { 163 synchronized(this) 164 { 165 for (; this.m_events.length ;) 166 { 167 this.m_events[0](); 168 this.m_events.remove(0); 169 } 170 } 171 172 this.m_currentGameState.fixedUpdate(); 173 } 174 175 /** 176 * Processes game rendering. 177 * Params: 178 * interpolation : 179 */ 180 public void update(in float interpolation) 181 { 182 this.m_inputHandler.update(); 183 this.m_currentGameState.update(interpolation); 184 } 185 186 /** 187 * Defines current game state. 188 * Params: 189 * gameState : new gamestate to set 190 */ 191 public void setGameState(T)() nothrow 192 { 193 T* gameState = cast(T*) (T.stringof in this.m_gameStates); 194 195 if (gameState is null) 196 { 197 T gs = new T(); 198 (cast(GameState) gs).setParent(this); 199 this.m_gameStates[T.stringof] = gs; 200 this.m_currentGameState = gs; 201 } 202 else this.m_currentGameState = *gameState; 203 } 204 205 /** 206 * Sets window mouse cursor. 207 * Params: 208 * cursor : new cursor toset 209 */ 210 @nogc 211 public void setCursor(in Cursor cursor) nothrow 212 { 213 this.m_window.setCursor(cursor); 214 } 215 216 /** 217 * Polls window events. 218 */ 219 @nogc 220 public void pollEvents() nothrow 221 { 222 this.m_window.pollEvents(); 223 } 224 225 /** 226 * Adds event that will be processed by the main thread. 227 * Params: 228 * event : event delegate 229 */ 230 @nogc 231 public void addEvent(OnEvent event) 232 { 233 synchronized(this) 234 { 235 this.m_events ~= event; 236 } 237 } 238 239 /** 240 * Adds scheduled event that will be processed by the main thread. 241 * Params: 242 * time : time in seconds before executing the task 243 * task : task delegate 244 */ 245 @nogc 246 public void addScheduledTask(in float time, OnEvent task) nothrow 247 { 248 auto taskTuple = tuple(cast(long) (1000 * time), task); 249 250 // Before adding a new tuple, we search for a free task slot 251 foreach (i, ref t; this.m_scheduledTasks) 252 { 253 // Free slot check 254 if (t[1] is null) 255 { 256 this.m_scheduledTasks[i] = taskTuple; 257 return; 258 } 259 } 260 261 this.m_scheduledTasks ~= taskTuple; 262 } 263 264 extern(C) nothrow 265 { 266 /** 267 * Event called on mouse wheel action. 268 * Params: 269 * window : window 270 * xoffset : xoffset 271 * yoffset : yoffset 272 */ 273 public void onMouseWheel(GLFWwindow* window, double xoffset, double yoffset) 274 { 275 try 276 { 277 this.m_currentGameState.onMouseWheel(cast(int)xoffset); 278 } 279 catch(Exception e) 280 { 281 } 282 } 283 284 /** 285 * Event called on character input. 286 * Params: 287 * window : window 288 * codepoint : codepoint 289 */ 290 public void onText(GLFWwindow* window, uint codepoint) 291 { 292 try 293 { 294 this.m_currentGameState.onText(codepoint); 295 } 296 catch(Exception e) 297 { 298 } 299 } 300 301 /** 302 * Event called on key action. 303 * Params: 304 * window : window 305 * key : key 306 * scancode : scancode 307 * action : action (key press or release) 308 * mods : mods 309 */ 310 public void onKey(GLFWwindow* window, int key, int scancode, int action, int mods) 311 { 312 if(action == GLFW_RELEASE) 313 { 314 try 315 { 316 this.m_currentGameState.onKey(key); 317 } 318 catch(Exception e) 319 { 320 } 321 } 322 } 323 324 /** 325 * Event called on window resize action. 326 * Params: 327 * window : window 328 * width : new width 329 * height : new height 330 */ 331 public void onWindowResize(GLFWwindow* window, int width, int height) 332 { 333 this.m_graphicsDevice.resolution = Size!int(width, height); 334 } 335 336 /** 337 * Event called on window close action. 338 * Params: 339 * window : window 340 */ 341 public void onWindowClose(GLFWwindow* window) 342 { 343 this.m_running = false; 344 } 345 } 346 347 /** 348 * Properties. 349 */ 350 @nogc @safe 351 @property pure nothrow 352 { 353 public GraphicsDevice graphicsDevice() 354 { 355 return this.m_graphicsDevice; 356 } 357 358 public GuiManager guiManager() 359 { 360 return this.m_guiManager; 361 } 362 363 public AssetLoader assetLoader() 364 { 365 return this.m_assetLoader; 366 } 367 368 public EntityManager entityManager() 369 { 370 return this.m_entityManager; 371 } 372 373 public GameState currentGameState() 374 { 375 return this.m_currentGameState; 376 } 377 378 public I18n i18n() 379 { 380 return this.m_i18n; 381 } 382 383 public InputHandler inputHandler() 384 { 385 return this.m_inputHandler; 386 } 387 388 public bool running() const 389 { 390 return this.m_running; 391 } 392 393 public float deltaTime() const 394 { 395 return this.m_deltaTime; 396 } 397 398 public void tickrate(in float value) 399 { 400 this.m_tickrate = value; 401 this.m_deltaTime = 1000.0f / this.m_tickrate; 402 } 403 404 public int maxFrameSkip() const 405 { 406 return this.m_maxFrameSkip; 407 } 408 409 public void maxFrameSkip(in int value) 410 { 411 this.m_maxFrameSkip = value; 412 } 413 414 public Window window() 415 { 416 return this.m_window; 417 } 418 } 419 }