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 }