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