1 /** 2 * Space shooter demo. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <vladimir@thecybershadow.net> 12 */ 13 14 module ae.demo.pewpew.pewpew; 15 16 import std.algorithm.iteration; 17 import std.format : format; 18 import std.math; 19 import std.random; 20 import std.datetime; 21 import std.algorithm : min; 22 import std.conv; 23 import std.range; 24 import std.traits, std.typecons; 25 26 import ae.ui.app.application; 27 import ae.ui.app.main; 28 import ae.ui.audio.mixer.software; 29 import ae.ui.audio.sdl2.audio; 30 import ae.ui.audio.source.memory; 31 import ae.ui.audio.source.wave; 32 import ae.ui.shell.shell; 33 import ae.ui.shell.sdl2.shell; 34 import ae.ui.video.bmfont; 35 import ae.ui.video.video; 36 import ae.ui.video.sdl2.video; 37 import ae.ui.video.renderer; 38 import ae.utils.graphics.draw; 39 import ae.utils.graphics.fonts.font8x8; 40 import ae.utils.graphics.gamma; 41 import ae.utils.fps; 42 import ae.utils.meta; 43 import ae.utils.sound.wave; 44 45 import ae.demo.pewpew.objects; 46 47 final class MyApplication : Application 48 { 49 override string getName() { return "Demo/PewPew"; } 50 override string getCompanyName() { return "CyberShadow"; } 51 52 Shell shell; 53 uint ticks; 54 alias GammaRamp!(COLOR.ChannelType, ubyte) MyGamma; 55 MyGamma gamma; 56 FPSCounter fps; 57 58 static uint currentTick() { return TickDuration.currSystemTick().to!("msecs", uint)(); } 59 60 enum InputSource { keyboard, joystick, max } 61 enum GameKey { up, down, left, right, fire, none } 62 63 int[InputSource.max][GameKey.max] inputMatrix; 64 65 MemorySoundSource!short sndShoot, sndWarpIn, sndTorpedoHit, sndEnemyFire; 66 FontTextureSource!Font8x8 font; 67 int highScore; 68 69 override void render(Renderer s) 70 { 71 fps.tick(&shell.setCaption); 72 73 { 74 auto screenCanvas = s.lock(); 75 scope(exit) s.unlock(); 76 77 if (initializing) 78 { 79 gamma = MyGamma(ColorSpace.sRGB); 80 new Game(); 81 foreach (i; 0..1000) step(10); 82 ticks = currentTick(); 83 initializing = false; 84 } 85 86 foreach (i, key; EnumMembers!GameKey[0..$-1]) 87 { 88 enum name = __traits(allMembers, GameKey)[i]; 89 bool pressed; 90 foreach (input; inputMatrix[key]) 91 if (input) 92 pressed = true; 93 mixin(name ~ " = pressed;"); 94 } 95 96 //auto destTicks = ticks+deltaTicks; 97 uint destTicks = currentTick(); 98 // step(deltaTicks); 99 while (ticks < destTicks) 100 ticks++, 101 step(1); 102 103 auto canvasSize = min(screenCanvas.w, screenCanvas.h); 104 canvas.size(canvasSize, canvasSize); 105 canvas.fill(canvas.COLOR.init); 106 foreach (ref plane; planes) 107 foreach (obj; plane) 108 obj.render(); 109 110 auto x = (screenCanvas.w-canvasSize)/2; 111 auto y = (screenCanvas.h-canvasSize)/2; 112 auto dest = screenCanvas.crop(x, y, x+canvasSize, y+canvasSize); 113 114 import std.parallelism; 115 import std.range; 116 foreach (j; taskPool.parallel(iota(canvasSize))) 117 { 118 auto src = canvas.crop(0, j, canvasSize, j+1); 119 auto ramp = gamma.lum2pixValues.ptr; 120 src.colorMap!(c => Renderer.COLOR.monochrome(ramp[c.l])).blitTo(dest, 0, j); 121 } 122 } 123 124 if (highScore < score) 125 { 126 highScore = score; 127 config.write("HighScore", highScore); 128 config.save(); 129 } 130 font.drawText(s, 4, 4, " Score: %08d".format( score)); 131 font.drawText(s, 4, 12, "High Score: %08d".format(highScore)); 132 133 foreach (sound; sounds) 134 final switch (sound) 135 { 136 case Sound.fire : shell.audio.mixer.playSound(sndShoot ); break; 137 case Sound.warpIn : shell.audio.mixer.playSound(sndWarpIn ); break; 138 case Sound.torpedoHit: shell.audio.mixer.playSound(sndTorpedoHit); break; 139 case Sound.enemyFire : shell.audio.mixer.playSound(sndEnemyFire ); break; 140 case Sound.explosion : playExplosion(); break; 141 } 142 sounds = null; 143 } 144 145 void step(uint deltaTicks) 146 { 147 foreach (ref plane; planes) 148 foreach (obj; plane) 149 obj.step(deltaTicks); 150 } 151 152 GameKey keyToGameKey(Key key) 153 { 154 switch (key) 155 { 156 case Key.up : return GameKey.up ; 157 case Key.down : return GameKey.down ; 158 case Key.left : return GameKey.left ; 159 case Key.right: return GameKey.right; 160 case Key.space: return GameKey.fire ; 161 default : return GameKey.none ; 162 } 163 } 164 165 override void handleKeyDown(Key key, dchar character) 166 { 167 auto gameKey = keyToGameKey(key); 168 if (gameKey != GameKey.none) 169 inputMatrix[gameKey][InputSource.keyboard]++; 170 else 171 if (key == Key.esc) 172 shell.quit(); 173 } 174 175 override void handleKeyUp(Key key) 176 { 177 auto gameKey = keyToGameKey(key); 178 if (gameKey != GameKey.none) 179 inputMatrix[gameKey][InputSource.keyboard] = 0; 180 } 181 182 override bool needJoystick() { return true; } 183 184 int[2] axisInitial; 185 bool[2] axisCalibrated; 186 187 override void handleJoyAxisMotion(int axis, short svalue) 188 { 189 if (axis >= 2) return; 190 191 int value = svalue; 192 if (!axisCalibrated[axis]) // assume first input event is inert 193 axisInitial[axis] = value, 194 axisCalibrated[axis] = true; 195 value -= axisInitial[axis]; 196 197 import ae.utils.math; 198 if (abs(value) > short.max/2) // hack? 199 useAnalog = true; 200 auto fvalue = bound(cast(float)value / short.max, -1f, 1f); 201 (axis==0 ? analogX : analogY) = fvalue; 202 } 203 204 JoystickHatState lastState = cast(JoystickHatState)0; 205 206 override void handleJoyHatMotion (int hat, JoystickHatState state) 207 { 208 void checkDirection(JoystickHatState direction, GameKey key) 209 { 210 if (!(lastState & direction) && (state & direction)) inputMatrix[key][InputSource.joystick]++; 211 if ((lastState & direction) && !(state & direction)) inputMatrix[key][InputSource.joystick]--; 212 } 213 checkDirection(JoystickHatState.up , GameKey.up ); 214 checkDirection(JoystickHatState.down , GameKey.down ); 215 checkDirection(JoystickHatState.left , GameKey.left ); 216 checkDirection(JoystickHatState.right, GameKey.right); 217 lastState = state; 218 } 219 220 override void handleJoyButtonDown(int button) 221 { 222 inputMatrix[GameKey.fire][InputSource.joystick]++; 223 } 224 225 override void handleJoyButtonUp (int button) 226 { 227 inputMatrix[GameKey.fire][InputSource.joystick]--; 228 } 229 230 private final void genSounds() 231 { 232 enum sr = 44100; 233 sndShoot = (sr*2/3) 234 .I!(dur => 235 dur.iota.map!(n => 236 cast(short)(whiteNoiseSqr!short[cast(size_t)(n / (n / 10000.0 + 5))] / 8) 237 )).retro.fade.array.memorySoundSource(sr); 238 239 { 240 enum freq = 1000; 241 sndWarpIn = (sr*3).I!(dur => dur.iota.map!(n => n % freq < (freq * n / dur) ? short.init : short.max)) 242 .array.memorySoundSource(sr); 243 } 244 245 sndTorpedoHit = (sr*2/3).I!(dur => dur.iota.map!(n => short(whiteNoiseSqr!short[cast(size_t)(n / (n / 10000.0 + 5))] / 4))).fade 246 .array.memorySoundSource(sr); 247 248 sndEnemyFire = (sr/3).iota.map!(n => short(squareWave!short(n / 1500.0 + 30)[n] / 4)).fade 249 .array.memorySoundSource(sr); 250 } 251 252 private final void playExplosion() 253 { 254 enum sr = 44100; 255 auto freq = uniform(50, 100); 256 auto w = (sr*2).I!(dur => dur.iota.map!(n => short(whiteNoiseSqr!short[cast(size_t)(n / (n / 10000.0 + freq))] / 8))).fade; 257 shell.audio.mixer.playSound(w.waveSoundSource(sr)); 258 } 259 260 override int run(string[] args) 261 { 262 genSounds(); 263 font = new FontTextureSource!Font8x8(font8x8, BGRX(128, 128, 128)); 264 highScore = config.read("HighScore", 0); 265 266 shell = new SDL2Shell(this); 267 shell.video = new SDL2SoftwareVideo(); 268 269 shell.audio = new SDL2Audio(); 270 shell.audio.mixer = new SoftwareMixer(); 271 272 shell.run(); 273 shell.video.shutdown(); 274 return 0; 275 } 276 277 override void handleQuit() 278 { 279 shell.quit(); 280 } 281 } 282 283 shared static this() 284 { 285 createApplication!MyApplication(); 286 }