1 module prova.core.game; 2 3 import derelict.sdl2.sdl; 4 import prova; 5 import prova.init; 6 import std.string; 7 8 /// Core class that manages input, scenes, and the window 9 final class Game 10 { 11 /// The FPS the gameloop will attempt to maintain 12 int targetFPS = 60; 13 package(prova) SDL_Window* window; 14 private Screen _screen; 15 private Input _input; 16 private Scene _activeScene; 17 private AssetManager assetManager; 18 private bool _isFullscreen = false; 19 private bool running = false; 20 21 /// Sets up the window 22 this(string title, int width, int height) 23 { 24 init(); 25 26 auto context = new GraphicsContext(); 27 28 window = SDL_CreateWindow( 29 toStringz(title), 30 SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 31 width, height, 32 SDL_WINDOW_OPENGL 33 ); 34 35 context.setWindow(window); 36 37 _screen = new Screen(this, context, width, height); 38 _input = new Input(this); 39 assetManager = new AssetManager(); 40 } 41 42 /// 43 @property Scene activeScene() 44 { 45 return _activeScene; 46 } 47 48 /// 49 @property Screen screen() 50 { 51 return _screen; 52 } 53 54 /// 55 @property Input input() 56 { 57 return _input; 58 } 59 60 /// 61 @property AssetManager assets() 62 { 63 return assetManager; 64 } 65 66 /// 67 @property bool isFullscreen() 68 { 69 return _isFullscreen; 70 } 71 72 /// 73 void setTitle(string title) 74 { 75 SDL_SetWindowTitle(window, toStringz(title)); 76 } 77 78 /// Fullscreen windowed 79 void toggleFullscreen() 80 { 81 _isFullscreen = !_isFullscreen; 82 83 SDL_SetWindowFullscreen( 84 window, 85 _isFullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0 86 ); 87 } 88 89 /// Allows/Prevents window resizing 90 void setResizable(bool resizable) 91 { 92 SDL_SetWindowResizable(window, cast(SDL_bool) resizable); 93 } 94 95 /** 96 * Changes the active scene 97 * 98 * use Game.start(Scene) to set the initial scene 99 */ 100 void swapScene(Scene scene) 101 { 102 assert(running, "The initial scene should be set through Game.start(Scene)"); 103 104 setScene(scene); 105 } 106 107 private void setScene(Scene scene) 108 { 109 _activeScene = scene; 110 _activeScene._game = this; 111 112 if(!_activeScene.isSetup) 113 _activeScene.setup(); 114 _activeScene.start(); 115 } 116 117 /** 118 * Starts the game loop and sets the initial scene 119 * 120 * Lines after this statement will not execute until the loop has stopped 121 */ 122 void start(Scene scene) 123 { 124 assert(!running, "The game should not already be running"); 125 126 running = true; 127 setScene(scene); 128 129 loop(); 130 131 // cleanup after finishing the final loop 132 cleanUp(); 133 } 134 135 /// Stops the game loop after it finishes a final cycle 136 void quit() 137 { 138 running = false; 139 } 140 141 private void loop() 142 { 143 int lag = 0; 144 Watch watch = new Watch(); 145 watch.start(); 146 147 while(running) { 148 const int frameDuration = 1000 / targetFPS; 149 lag += watch.getElapsedMilliseconds(); 150 watch.restart(); 151 152 while(lag >= frameDuration) { 153 update(); 154 lag -= frameDuration; 155 } 156 157 draw(); 158 159 // give the processor a break if we are ahead of schedule 160 const int sleepTime = frameDuration - watch.getElapsedMilliseconds() - lag; 161 162 if(sleepTime > 0) 163 SDL_Delay(sleepTime); 164 } 165 } 166 167 private void update() 168 { 169 SDL_Event event; 170 string inputText = ""; 171 172 _input.reset(); 173 174 while(SDL_PollEvent(&event) != 0) { 175 switch(event.type) { 176 case SDL_QUIT: 177 quit(); 178 return; 179 case SDL_WINDOWEVENT: 180 switch(event.window.event) { 181 case SDL_WINDOWEVENT_RESIZED: 182 screen.updateResolution(event.window.data1, event.window.data2); 183 break; 184 default: 185 break; 186 } 187 break; 188 case SDL_TEXTINPUT: 189 inputText = fromStringz(event.text.text.ptr).idup; 190 break; 191 case SDL_KEYDOWN: 192 _input.setKeyDown(event.key.keysym.sym); 193 break; 194 default: 195 break; 196 } 197 } 198 199 _input.update(); 200 _input.updateTextInput(inputText); 201 202 _activeScene.update(); 203 _activeScene.updateAudio(); 204 } 205 206 private void draw() 207 { 208 _screen.prepare(); 209 210 _screen.prepareDynamic(); 211 _activeScene.draw(screen); 212 _screen.endDynamic(); 213 214 _screen.prepareStatic(); 215 _activeScene.drawStatic(screen); 216 _screen.endStatic(); 217 218 _screen.swapBuffer(); 219 } 220 221 private void cleanUp() 222 { 223 SDL_DestroyWindow(window); 224 finalize(); 225 } 226 }