1 /++ 2 A module for playing and organizing a game cycle with a scene manager. 3 4 Macros: 5 LREF = <a href="#$1">$1</a> 6 HREF = <a href="$1">$2</a> 7 8 Authors: $(HREF https://github.com/TodNaz,TodNaz) 9 Copyright: Copyright (c) 2020 - 2021, TodNaz. 10 License: $(HREF https://github.com/TodNaz/Tida/blob/master/LICENSE,MIT) 11 +/ 12 module tida.game; 13 14 import tida.window; 15 import tida.render; 16 import tida.fps; 17 import tida.scenemanager; 18 import tida.scene; 19 import tida.event; 20 import tida.image; 21 import tida.listener; 22 import tida.loader; 23 import tida.gl; 24 25 __gshared 26 { 27 IWindow _window; 28 IRenderer _renderer; 29 FPSManager _fps; 30 Listener _listener; 31 } 32 33 /// Window global object. 34 @property IWindow window() @trusted 35 { 36 return _window; 37 } 38 39 /// Render global object. 40 @property IRenderer renderer() @trusted 41 { 42 return _renderer; 43 } 44 45 /// FPS global object 46 @property FPSManager fps() @trusted 47 { 48 return _fps; 49 } 50 51 /// Listener global object 52 @property Listener listener() @trusted 53 { 54 return _listener; 55 } 56 57 /++ 58 Game initialization information. 59 +/ 60 struct GameConfig 61 { 62 import tida.color; 63 64 public: 65 uint windowWidth = 640; /// Window width. 66 uint windowHeight = 480; /// Window height. 67 string windowTitle = "Tida"; /// Window title. 68 Color!ubyte background = rgb(255, 255, 255); /// Window background. 69 int positionWindowX = 100; /// Window position x-axis. 70 int positionWindowY = 100; /// Window position y-axis. 71 string icon; /// Icon path. 72 } 73 74 class Game 75 { 76 private: 77 EventHandler event; 78 InstanceThread[] threads; 79 bool isGame = true; 80 81 public: 82 int result = 0; 83 84 @trusted: 85 this(GameConfig config) 86 { 87 _window = new Window(config.windowWidth, config.windowHeight, config.windowTitle); 88 (cast(Window) _window).windowInitialize!(WithContext)(config.positionWindowX,config.positionWindowY); 89 loadGraphicsLibrary(); 90 _renderer = createRenderer(_window); 91 _renderer.background = config.background; 92 if (config.icon != "") 93 _window.icon = new Image().load(config.icon); 94 95 initSceneManager(); 96 event = new EventHandler((cast(Window) window)); 97 _fps = new FPSManager(); 98 _loader = new Loader(); 99 _listener = new Listener(); 100 } 101 102 void threadClose(uint value) @safe 103 { 104 import std.algorithm : remove; 105 106 threads[value].exit(); 107 foreach(i; value .. threads.length) threads[i].rebindThreadID(i - 1); 108 threads.remove(value); 109 } 110 111 private void exit() @safe 112 { 113 import std.algorithm : each; 114 115 isGame = false; 116 threads.each!((e) { if(e !is null) e.exit(); }); 117 sceneManager.callGameExit(); 118 } 119 120 void run() @trusted 121 { 122 sceneManager.callGameStart(); 123 124 _fps = new FPSManager(); 125 126 while (isGame) 127 { 128 fps.countDown(); 129 130 scope (failure) 131 { 132 sceneManager.callOnError(); 133 } 134 135 while (event.nextEvent) 136 { 137 if (event.isQuit) 138 { 139 exit(); 140 return; 141 } 142 143 sceneManager.callEvent(event); 144 if (listener !is null) listener.eventHandle(event); 145 } 146 147 if (listener !is null) listener.timerHandle(); 148 149 foreach (response; sceneManager.api) 150 { 151 if (response.code == APIType.ThreadClose) 152 { 153 if (response.value == 0) 154 { 155 exit(); 156 return; 157 } else 158 { 159 if (response.value >= threads.length) 160 { 161 sceneManager.apiError[response.code] = APIError.ThreadIsNotExists; 162 continue; 163 } else 164 { 165 threadClose(response.value); 166 } 167 } 168 }else 169 if (response.code == APIType.GameClose) 170 { 171 result = response.value; 172 exit(); 173 return; 174 } else 175 if (response.code == APIType.ThreadPause) 176 { 177 if(response.value >= threads.length) 178 { 179 sceneManager.apiError[response.code] = APIError.ThreadIsNotExists; 180 continue; 181 } else 182 { 183 threads[response.value].pause(); 184 } 185 } else 186 if (response.code == APIType.ThreadResume) 187 { 188 if (response.value >= threads.length) 189 { 190 sceneManager.apiError[response.code] = APIError.ThreadIsNotExists; 191 continue; 192 } else 193 { 194 threads[response.value].resume(); 195 } 196 } else 197 if (response.code == APIType.ThreadCreate) 198 { 199 auto thread = new InstanceThread(threads.length,renderer); 200 threads ~= thread; 201 202 thread.start(); 203 } 204 } 205 206 sceneManager.api = []; // GC, please, clear this 207 208 sceneManager.callStep(0,renderer); 209 210 renderer.clear(); 211 sceneManager.callDraw(renderer); 212 renderer.drawning(); 213 214 fps.control(); 215 } 216 } 217 } 218 219 template GameRun(GameConfig config, T...) 220 { 221 import tida.runtime; 222 import tida.scenemanager; 223 224 version(unittest) {} else 225 int main(string[] args) 226 { 227 TidaRuntime.initialize(args, AllLibrary); 228 Game game = new Game(config); 229 230 static foreach (e; T) 231 { 232 sceneManager.add(new e()); 233 } 234 235 debug(single) 236 { 237 static foreach (e; T) 238 { 239 mixin(" 240 debug(single_" ~ e.stringof ~ ") 241 { 242 sceneManager.gotoin!(" ~ e.stringof ~ "); 243 } 244 "); 245 } 246 }else 247 sceneManager.inbegin(); 248 249 game.run(); 250 return game.result; 251 } 252 }