1 module demos.brick_breaker;
2 
3 import app;
4 
5 import bindbc.sdl;
6 
7 import bubel.ecs.attributes;
8 import bubel.ecs.core;
9 import bubel.ecs.entity;
10 import bubel.ecs.manager;
11 import bubel.ecs.std;
12 
13 import cimgui.cimgui;
14 
15 import ecs_utils.gfx.texture;
16 import ecs_utils.math.vector;
17 import ecs_utils.utils;
18 
19 import game_core.basic;
20 import game_core.rendering;
21 import game_core.collision;
22 
23 extern(C):
24 
25 private enum float px = 1.0/512.0;
26 
27 float clamp(float v, float min, float max)
28 {
29     if(v<min)return min;
30     else if (v>max)return max;
31     else return v;
32 }
33 
34 /*#######################################################################################################################
35 ------------------------------------------------ Components ------------------------------------------------------------------
36 #######################################################################################################################*/
37 
38 /*struct CLocation
39 {
40     mixin ECS.Component;
41 
42     alias location this;
43 
44     vec2 location;
45 }*/
46 
47 struct CBrick
48 {
49     mixin ECS.Component;
50 }
51 
52 struct CPaddle
53 {
54     mixin ECS.Component;
55 }
56 
57 struct CBall
58 {
59     mixin ECS.Component;
60 
61     //ubyte radius;
62 }
63 
64 struct CHitPoints
65 {
66     mixin ECS.Component;
67 
68     alias value this;
69 
70     short value;
71 }
72 
73 // struct CVelocityFactor
74 // {
75 //     mixin ECS.Component;
76 
77 //     alias value this;
78 
79 //     vec2 value = vec2(1);
80 // }
81 
82 // struct CVelocity
83 // {
84 //     mixin ECS.Component;
85 
86 //     alias value this;
87 
88 //     vec2 value = vec2(0);
89 // }
90 
91 struct EDamage
92 {
93     mixin ECS.Event;
94 
95     ubyte damage = 1;
96 }
97 
98 /*#######################################################################################################################
99 ------------------------------------------------ Systems ------------------------------------------------------------------
100 #######################################################################################################################*/
101 
102 // struct MoveSystem
103 // {
104 //     mixin ECS.System!64;
105 
106 //     struct EntitiesData
107 //     {
108 //         uint length;
109 //         CLocation[] location;
110 //         @readonly CVelocity[] velocity;
111 //         @optional @readonly CVelocityFactor[] vel_factor;
112 //     }
113 
114 //     void onUpdate(EntitiesData data)
115 //     {
116 //         if(data.vel_factor)
117 //         {
118 //             foreach(i; 0..data.length)
119 //             {
120 //                 data.location[i] += data.velocity[i] * data.vel_factor[i] * launcher.delta_time;
121 //             }
122 //         }
123 //         else
124 //         {
125 //             foreach(i; 0..data.length)
126 //             {
127 //                 data.location[i] += data.velocity[i] * launcher.delta_time;
128 //             }
129 //         }
130 //     }
131 // }
132 
133 struct EdgeCollisionSystem
134 {
135     mixin ECS.System!64;
136 
137     struct EntitiesData
138     {
139         uint length;
140         CLocation[] location;
141         CVelocity[] velocity;
142         //CBall[] ball_flag;
143     }
144 
145     void onUpdate(EntitiesData data)
146     {
147         foreach(i; 0..data.length)
148         {
149             if(data.location[i].x < 0)
150             {
151                 if(data.velocity[i].x < 0)data.velocity[i].x = -data.velocity[i].x;
152                 data.location[i].x = 0;
153             }
154             else if(data.location[i].x > 400)
155             {
156                 if(data.velocity[i].x > 0)data.velocity[i].x = -data.velocity[i].x;
157                 data.location[i].x = 400;
158             }
159 
160             if(data.location[i].y < 0)
161             {
162                 if(data.velocity[i].y < 0)data.velocity[i].y = -data.velocity[i].y;
163                 data.location[i].y = 0;
164             }
165             else if(data.location[i].y > 300)
166             {
167                 if(data.velocity[i].y > 0)data.velocity[i].y = -data.velocity[i].y;
168                 data.location[i].y = 300;
169             }
170         }
171     }
172 }
173 
174 struct BallCollisionSystem
175 {
176     mixin ECS.System!64;
177 
178     mixin ECS.ReadOnlyDependencies!(ShootGridDependency, BVHDependency);
179 
180     struct EntitiesData
181     {
182         ///variable named "length" contain entites count
183         uint length;
184         const (Entity)[] entity;
185         CVelocity[] velocity;
186         @readonly CLocation[] location;
187         @readonly CScale[] scale;
188         @readonly CBall[] ball_flag;
189     }
190 
191     struct State
192     {
193         bool test(EntityID id)
194         {
195             if(id == data.entity[i].id)return true;
196             Entity* entity = gEntityManager.getEntity(id);
197             if(entity)
198             {
199                 CLocation* location = entity.getComponent!CLocation;
200                 CScale* scale = entity.getComponent!CScale;
201                 if(location && scale)
202                 {
203                     float radius = data.scale[i].x*0.5;
204                     vec2 rel_pos = *location - data.location[i];
205                     
206                     vec2 half_scale = *scale * 0.5f;
207                     
208                     vec2 nearest_point;
209                     nearest_point.x = clamp(rel_pos.x, -half_scale.x, half_scale.x);
210                     nearest_point.y = clamp(rel_pos.y, -half_scale.y, half_scale.y);
211 
212                     vec2 vector;
213                     if(nearest_point == rel_pos)
214                     {
215                         vector = nearest_point;
216                         radius = float.max;
217                     }
218                     else vector = nearest_point - rel_pos;
219                     float pow_dist = dot(vector, vector);
220 
221                     if(dot(data.velocity[i], vector) > 0.01)return true;
222 
223                     if(pow_dist < radius*radius)
224                     {
225                         vector = vector / sqrtf(pow_dist);
226                         data.velocity[i] = data.velocity[i] - vector * (2 * dot(vector, data.velocity[i]));
227                         gEntityManager.sendEvent(id,EDamage(1));
228                         return cast(bool)(hits--);
229                     }
230                 }
231             }
232             return true;
233         }
234 
235         EntitiesData data;
236         uint i;
237         uint hits;
238     }
239 
240     ShootGrid* grid;
241     BVHTree* tree;
242     BVHTree* static_tree;
243 
244     bool onBegin()
245     {
246         tree = gEntityManager.getSystem!BVHBuilder().tree;
247         static_tree = gEntityManager.getSystem!StaticBVHBuilder().tree;
248         if(tree is null || static_tree is null)return false;
249         else return true;
250     }
251 
252     void onUpdate(EntitiesData data)
253     {
254         State state;
255         state.data = data;
256         foreach(i; 0..data.length)
257         {
258             state.i = i;
259             state.hits = 1;
260             AABB bounding = AABB(data.location[i]-data.scale[i], data.location[i]+data.scale[i]);
261             tree.test(bounding, cast(bool delegate(EntityID id))&state.test);
262             static_tree.test(bounding, cast(bool delegate(EntityID id))&state.test);
263         }
264     }
265 }
266 
267 struct DamageSystem
268 {
269     mixin ECS.System!64;
270 
271     mixin ECS.ReadOnlyDependencies!(ShootGridDependency);
272 
273     struct EntitiesData
274     {
275         ///variable named "length" contain entites count
276         uint length;
277         const (Entity)[] entity;
278         CHitPoints[] hit_points;
279     }
280 
281     void handleEvent(Entity* entity, EDamage event)
282     {
283         EntityMeta meta = entity.getMeta();
284         CHitPoints* hp = meta.getComponent!CHitPoints;
285         hp.value -= event.damage;
286         if(hp.value < 0)gEntityManager.removeEntity(entity.id);
287     }
288 
289 }
290 
291 /*#######################################################################################################################
292 ------------------------------------------------ Functions ------------------------------------------------------------------
293 #######################################################################################################################*/
294 
295 struct BrickBreakerDemo
296 {
297     __gshared const (char)* tips = "Brick breaker demo. It's a game about destroying evil bricks.
298 
299 This demo is usnfinished yet but collision works well. Bricks can be destroyed. Spawning thousands of bricks and then thousands of balls is good way to try demo.
300 Bricks uses StaticBVH, ball don't collide witch each other, paddle is added to dynamic BVH and collide with balls. But nothing keeps you from adding dynamic collision for balls.
301 Currently dynamic collisions are pretty slow as dynamic BVH is rebuilded every frame on single thread.";
302 
303     //EntityTemplate* tmpl;
304     Texture texture;
305 }
306 
307 __gshared BrickBreakerDemo* demo;
308 
309 void brickBreakerRegister()
310 {
311     demo = Mallocator.make!BrickBreakerDemo;
312 
313     demo.texture.create();
314     demo.texture.load("assets/textures/atlas.png");
315 
316     gEntityManager.beginRegister();
317 
318     registerRenderingModule(gEntityManager);
319     registerCollisionModule(gEntityManager);
320 
321     gEntityManager.registerComponent!CLocation;
322     gEntityManager.registerComponent!CRotation;
323     gEntityManager.registerComponent!CScale;
324     gEntityManager.registerComponent!CTexCoords;
325     gEntityManager.registerComponent!CTexCoordsIndex;
326     gEntityManager.registerComponent!CVelocity;
327     gEntityManager.registerComponent!CInput;
328     gEntityManager.registerComponent!CPaddle;
329     gEntityManager.registerComponent!CDamping;
330     gEntityManager.registerComponent!CVelocityFactor;
331     gEntityManager.registerComponent!CBall;
332     gEntityManager.registerComponent!CHitPoints;
333 
334     gEntityManager.registerEvent!EDamage;
335     
336     gEntityManager.registerSystem!MoveSystem(-100);
337     gEntityManager.registerSystem!EdgeCollisionSystem(-99);
338     gEntityManager.registerSystem!BallCollisionSystem(-79);
339     gEntityManager.registerSystem!InputMovementSystem(-120);
340     gEntityManager.registerSystem!DampingSystem(-120);
341     gEntityManager.registerSystem!DamageSystem(-120);
342     
343     gEntityManager.endRegister();
344 }
345 
346 void brickBreakerStart()
347 {
348     DrawSystem* draw_system = gEntityManager.getSystem!DrawSystem;
349     draw_system.default_data.color = 0x80808080;
350     draw_system.default_data.texture = demo.texture;
351     draw_system.default_data.size = vec2(16,16);
352     draw_system.default_data.coords = vec4(246,64,2,2)*px;
353     draw_system.default_data.material_id = 0;
354 
355     EntityTemplate* brick_tmpl = gEntityManager.allocateTemplate(
356                 [becsID!CLocation, becsID!CScale, becsID!CColor, 
357                 becsID!CTexCoordsIndex, becsID!CBVH, becsID!CHitPoints,
358                 becsID!CAABB, becsID!CStatic].staticArray
359                 );
360     brick_tmpl.getComponent!CTexCoordsIndex().value = TexCoordsManager.instance.getCoordIndex(vec4(304,40,16,8)*px);
361     brick_tmpl.getComponent!CColor().value = 0x80206020;
362     brick_tmpl.getComponent!CScale().value = vec2(16,8);
363     brick_tmpl.getComponent!CHitPoints().value = 2;
364     //brick_tmpl.getComponent!CAABB().bounding = AABB(vec2(),vec2());
365 
366     EntityTemplate* big_brick_tmpl = gEntityManager.allocateTemplate(brick_tmpl);
367     big_brick_tmpl.getComponent!CTexCoordsIndex().value = TexCoordsManager.instance.getCoordIndex(vec4(320,32,16,16)*px);
368     big_brick_tmpl.getComponent!CScale().value = vec2(16,16);
369 
370     EntityTemplate* paddle_tmpl = gEntityManager.allocateTemplate(
371                 [becsID!CLocation, becsID!CScale, becsID!CInput,
372                 becsID!CTexCoordsIndex, becsID!CPaddle, becsID!CVelocity,
373                 becsID!CDamping, becsID!CVelocityFactor, becsID!CBVH,
374                 becsID!CAABB].staticArray
375                 );
376     paddle_tmpl.getComponent!CTexCoordsIndex().value = TexCoordsManager.instance.getCoordIndex(vec4(272,48,64,10)*px);
377     paddle_tmpl.getComponent!CScale().value = vec2(64,10);
378     paddle_tmpl.getComponent!CDamping().value = 14;
379     paddle_tmpl.getComponent!CVelocityFactor().value = vec2(1,0);
380 
381     EntityTemplate* ball_tmpl = gEntityManager.allocateTemplate(
382                 [becsID!CLocation, becsID!CScale, //becsID!CDamping,
383                 becsID!CTexCoordsIndex, becsID!CBall, becsID!CVelocity].staticArray
384                 );
385     ball_tmpl.getComponent!CTexCoordsIndex().value = TexCoordsManager.instance.getCoordIndex(vec4(304,32,8,8)*px);
386     ball_tmpl.getComponent!CScale().value = vec2(8,8);
387     ball_tmpl.getComponent!CVelocity().value = vec2(0.1,0.1);
388     // paddle_tmpl.getComponent!CDamping().value = 14;
389 
390     launcher.gui_manager.addComponent(CLocation(), "Location");
391     launcher.gui_manager.addComponent(CRotation(), "Rotation");
392     launcher.gui_manager.addComponent(CScale(), "Scale");
393     launcher.gui_manager.addComponent(CColor(), "Color");
394     launcher.gui_manager.addComponent(CTexCoords(), "Tex Coords");
395     launcher.gui_manager.addComponent(CTexCoordsIndex(), "Tex Coords Index");
396     launcher.gui_manager.addComponent(CVelocity(), "Velocity");
397     launcher.gui_manager.addComponent(CInput(), "Input");
398     launcher.gui_manager.addComponent(CPaddle(), "Paddle");
399     launcher.gui_manager.addComponent(CDamping(), "Damping");
400     launcher.gui_manager.addComponent(CBall(), "Ball");
401     launcher.gui_manager.addComponent(CBVH(), "BVH");
402     launcher.gui_manager.addComponent(CAABB(), "AABB");
403     launcher.gui_manager.addComponent(CStatic(), "Static Flag");
404     launcher.gui_manager.addComponent(CVelocityFactor(), "Velocity Factor");
405     launcher.gui_manager.addComponent(CHitPoints(), "Hit Points");
406 
407     launcher.gui_manager.addSystem(becsID!MoveSystem, "Move System");
408     launcher.gui_manager.addSystem(becsID!EdgeCollisionSystem, "Edge Collision System");
409     launcher.gui_manager.addSystem(becsID!BallCollisionSystem, "Ball Collision System");
410     launcher.gui_manager.addSystem(becsID!InputMovementSystem, "Input Movement System");
411     launcher.gui_manager.addSystem(becsID!DampingSystem, "Damping System");
412     launcher.gui_manager.addSystem(becsID!DamageSystem, "Damage System");
413 
414     launcher.gui_manager.addTemplate(brick_tmpl, "Brick");
415     launcher.gui_manager.addTemplate(big_brick_tmpl, "Big Brick");
416     launcher.gui_manager.addTemplate(paddle_tmpl, "Paddle");
417     launcher.gui_manager.addTemplate(ball_tmpl, "Ball");
418 
419     foreach(i;0..10)
420     {
421         CColor color;
422         final switch(i)
423         {
424             case 0:color = 0x80206020;break;
425             case 1:color = 0x80602020;break;
426             case 2:color = 0x80202060;break;
427             case 3:color = 0x80206060;break;
428             case 4:color = 0x80606020;break;
429             case 5:color = 0x80602060;break;
430             case 6:color = 0x80606060;break;
431             case 7:color = 0x80202020;break;
432             case 8:color = 0x80008030;break;
433             case 9:color = 0x80206080;break;
434         }
435         foreach (j; 0..20)
436         {
437             gEntityManager.addEntity(brick_tmpl,[CLocation(vec2(j*18,300-i*10)).ref_, color.ref_].staticArray);
438         }
439     }
440 
441     gEntityManager.addEntity(paddle_tmpl,[CLocation(vec2(190,20)).ref_].staticArray);
442     gEntityManager.addEntity(ball_tmpl,[CLocation(vec2(190,40)).ref_].staticArray);
443 
444 }
445 
446 void brickBreakerEnd()
447 {
448     demo.texture.destroy();
449 
450     Mallocator.dispose(demo);
451 }
452 
453 void brickBreakerEvent(SDL_Event* event)
454 {
455 }
456 
457 bool brickBreakerLoop()
458 {
459     launcher.render_position = (vec2(launcher.window_size.x,launcher.window_size.y)*launcher.scalling - vec2(400,300)) * 0.5;
460     
461     gEntityManager.begin();
462     if(launcher.multithreading)
463     {
464         launcher.job_updater.begin();
465         gEntityManager.updateMT();
466         launcher.job_updater.call();
467     }
468     else
469     {
470         gEntityManager.update();
471     }
472     gEntityManager.end();
473 
474     return true;
475 }
476 
477 DemoCallbacks getBrickBreakerDemo()
478 {
479     DemoCallbacks demo;
480     demo.register = &brickBreakerRegister;
481     demo.initialize = &brickBreakerStart;
482     demo.deinitialize = &brickBreakerEnd;
483     demo.loop = &brickBreakerLoop;
484     demo.tips = .demo.tips;
485     return demo;
486 }