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 }