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.math;
17 import std.random;
18 import std.datetime;
19 import std.algorithm : min;
20 import std.conv;
21 import std.traits, std.typecons;
22
23 import ae.ui.app.application;
24 import ae.ui.app.posix.main;
25 import ae.ui.shell.shell;
26 import ae.ui.shell.sdl2.shell;
27 import ae.ui.video.video;
28 import ae.ui.video.sdl2.video;
29 import ae.ui.video.renderer;
30 import ae.utils.graphics.draw;
31 import ae.utils.graphics.gamma;
32 import ae.utils.fps;
33
34 import ae.demo.pewpew.objects;
35
36 final class MyApplication : Application
37 {
38 override string getName() { return "Demo/PewPew"; }
39 override string getCompanyName() { return "CyberShadow"; }
40
41 Shell shell;
42 uint ticks;
43 alias GammaRamp!(COLOR.ChannelType, ubyte) MyGamma;
44 MyGamma gamma;
45 FPSCounter fps;
46
47 static uint currentTick() { return TickDuration.currSystemTick().to!("msecs", uint)(); }
48
49 enum InputSource { keyboard, joystick, max }
50 enum GameKey { up, down, left, right, fire, none }
51
52 int[InputSource.max][GameKey.max] inputMatrix;
53
54 override void render(Renderer s)
55 {
56 fps.tick(&shell.setCaption);
57
58 auto screenCanvas = s.lock();
59 scope(exit) s.unlock();
60
61 if (initializing)
62 {
63 gamma = MyGamma(ColorSpace.sRGB);
64 new Game();
65 foreach (i; 0..1000) step(10);
66 ticks = currentTick();
67 initializing = false;
68 }
69
70 foreach (i, key; EnumMembers!GameKey[0..$-1])
71 {
72 enum name = __traits(allMembers, GameKey)[i];
73 bool pressed;
74 foreach (input; inputMatrix[key])
75 if (input)
76 pressed = true;
77 mixin(name ~ " = pressed;");
78 }
79
80 //auto destTicks = ticks+deltaTicks;
81 uint destTicks = currentTick();
82 // step(deltaTicks);
83 while (ticks < destTicks)
84 ticks++,
85 step(1);
86
87 auto canvasSize = min(screenCanvas.w, screenCanvas.h);
88 canvas.size(canvasSize, canvasSize);
89 canvas.fill(canvas.COLOR.init);
90 foreach (ref plane; planes)
91 foreach (obj; plane)
92 obj.render();
93
94 auto x = (screenCanvas.w-canvasSize)/2;
95 auto y = (screenCanvas.h-canvasSize)/2;
96 auto dest = screenCanvas.crop(x, y, x+canvasSize, y+canvasSize);
97
98 import std.parallelism;
99 import std.range;
100 foreach (j; taskPool.parallel(iota(canvasSize)))
101 {
102 auto src = canvas.crop(0, j, canvasSize, j+1);
103 auto ramp = gamma.lum2pixValues.ptr;
104 src.colorMap!(c => Renderer.COLOR.monochrome(ramp[c.l])).blitTo(dest, 0, j);
105 }
106 }
107
108 void step(uint deltaTicks)
109 {
110 foreach (ref plane; planes)
111 foreach (obj; plane)
112 obj.step(deltaTicks);
113 }
114
115 GameKey keyToGameKey(Key key)
116 {
117 switch (key)
118 {
119 case Key.up : return GameKey.up ;
120 case Key.down : return GameKey.down ;
121 case Key.left : return GameKey.left ;
122 case Key.right: return GameKey.right;
123 case Key.space: return GameKey.fire ;
124 default : return GameKey.none ;
125 }
126 }
127
128 override void handleKeyDown(Key key, dchar character)
129 {
130 auto gameKey = keyToGameKey(key);
131 if (gameKey != GameKey.none)
132 inputMatrix[gameKey][InputSource.keyboard]++;
133 else
134 if (key == Key.esc)
135 shell.quit();
136 }
137
138 override void handleKeyUp(Key key)
139 {
140 auto gameKey = keyToGameKey(key);
141 if (gameKey != GameKey.none)
142 inputMatrix[gameKey][InputSource.keyboard] = 0;
143 }
144
145 override bool needJoystick() { return true; }
146
147 int[2] axisInitial;
148 bool[2] axisCalibrated;
149
150 override void handleJoyAxisMotion(int axis, short svalue)
151 {
152 if (axis >= 2) return;
153
154 int value = svalue;
155 if (!axisCalibrated[axis]) // assume first input event is inert
156 axisInitial[axis] = value,
157 axisCalibrated[axis] = true;
158 value -= axisInitial[axis];
159
160 import ae.utils.math;
161 if (abs(value) > short.max/2) // hack?
162 useAnalog = true;
163 auto fvalue = bound(cast(float)value / short.max, -1f, 1f);
164 (axis==0 ? analogX : analogY) = fvalue;
165 }
166
167 JoystickHatState lastState = cast(JoystickHatState)0;
168
169 override void handleJoyHatMotion (int hat, JoystickHatState state)
170 {
171 void checkDirection(JoystickHatState direction, GameKey key)
172 {
173 if (!(lastState & direction) && (state & direction)) inputMatrix[key][InputSource.joystick]++;
174 if ((lastState & direction) && !(state & direction)) inputMatrix[key][InputSource.joystick]--;
175 }
176 checkDirection(JoystickHatState.up , GameKey.up );
177 checkDirection(JoystickHatState.down , GameKey.down );
178 checkDirection(JoystickHatState.left , GameKey.left );
179 checkDirection(JoystickHatState.right, GameKey.right);
180 lastState = state;
181 }
182
183 override void handleJoyButtonDown(int button)
184 {
185 inputMatrix[GameKey.fire][InputSource.joystick]++;
186 }
187
188 override void handleJoyButtonUp (int button)
189 {
190 inputMatrix[GameKey.fire][InputSource.joystick]--;
191 }
192
193 override int run(string[] args)
194 {
195 shell = new SDL2Shell(this);
196 shell.video = new SDL2SoftwareVideo();
197 shell.run();
198 shell.video.shutdown();
199 return 0;
200 }
201
202 override void handleQuit()
203 {
204 shell.quit();
205 }
206 }
207
208 shared static this()
209 {
210 createApplication!MyApplication();
211 }