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 }