Simple tower defense tutorial, part 4

As like with the previous parts, let's first have a look at the current state of the game:

To summarize the current state:

What we want to add next is that enemies attack buildings and that we can build new buildings via a simple interface. We will start with the first part: Enemies attacking buildings.

Enemies attacking buildings

A simple take on the problem could be to let enemies "explode" when they touch buildings, causing damage. That sort of invalidates the wall collisions we implemented in the last part, but maybe other units will be able reuse it.

So next we'll add building health and exploding enemies. When a building's damage reaches the health, the building is destroyed (line 472 + 634):

  • 💾
  1 #include "td-tut-2-main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 //# Declarations
  7 
  8 #define TOWER_MAX_COUNT 400
  9 #define TOWER_TYPE_NONE 0
 10 #define TOWER_TYPE_BASE 1
 11 #define TOWER_TYPE_GUN 2
 12 #define TOWER_TYPE_WALL 3
 13 
 14 typedef struct Tower
 15 {
 16   int16_t x, y;
 17   uint8_t towerType;
 18   float cooldown;
19 float damage;
20 } Tower; 21 22 typedef struct GameTime 23 { 24 float time; 25 float deltaTime; 26 } GameTime; 27 28 GameTime gameTime = {0}; 29 30 Tower towers[TOWER_MAX_COUNT];
31 int towerCount = 0; 32 33 float TowerGetMaxHealth(Tower *tower);
34 35 //# Pathfinding map 36 typedef struct DeltaSrc 37 { 38 char x, y; 39 } DeltaSrc; 40 41 typedef struct PathfindingMap 42 { 43 int width, height; 44 float scale; 45 float *distances; 46 long *towerIndex; 47 DeltaSrc *deltaSrc; 48 float maxDistance; 49 Matrix toMapSpace; 50 Matrix toWorldSpace; 51 } PathfindingMap; 52 53 // when we execute the pathfinding algorithm, we need to store the active nodes 54 // in a queue. Each node has a position, a distance from the start, and the 55 // position of the node that we came from. 56 typedef struct PathfindingNode 57 { 58 int16_t x, y, fromX, fromY; 59 float distance; 60 } PathfindingNode; 61 62 // The queue is a simple array of nodes, we add nodes to the end and remove 63 // nodes from the front. We keep the array around to avoid unnecessary allocations 64 static PathfindingNode *pathfindingNodeQueue = 0; 65 static int pathfindingNodeQueueCount = 0; 66 static int pathfindingNodeQueueCapacity = 0; 67 68 // The pathfinding map stores the distances from the castle to each cell in the map. 69 PathfindingMap pathfindingMap = {0}; 70 71 void PathfindingMapInit(int width, int height, Vector3 translate, float scale) 72 { 73 // transforming between map space and world space allows us to adapt 74 // position and scale of the map without changing the pathfinding data 75 pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z); 76 pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale)); 77 pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace); 78 pathfindingMap.width = width; 79 pathfindingMap.height = height; 80 pathfindingMap.scale = scale; 81 pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float)); 82 for (int i = 0; i < width * height; i++) 83 { 84 pathfindingMap.distances[i] = -1.0f; 85 } 86 87 pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long)); 88 pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc)); 89 } 90 91 float PathFindingGetDistance(int mapX, int mapY) 92 { 93 if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height) 94 { 95 // when outside the map, we return the manhattan distance to the castle (0,0) 96 return fabsf((float)mapX) + fabsf((float)mapY); 97 } 98 99 return pathfindingMap.distances[mapY * pathfindingMap.width + mapX]; 100 } 101 102 void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance) 103 { 104 if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity) 105 { 106 pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2; 107 // we use MemAlloc/MemRealloc to allocate memory for the queue 108 // I am not entirely sure if MemRealloc allows passing a null pointer 109 // so we check if the pointer is null and use MemAlloc in that case 110 if (pathfindingNodeQueue == 0) 111 { 112 pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode)); 113 } 114 else 115 { 116 pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode)); 117 } 118 } 119 120 PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++]; 121 node->x = x; 122 node->y = y; 123 node->fromX = fromX; 124 node->fromY = fromY; 125 node->distance = distance; 126 } 127 128 PathfindingNode *PathFindingNodePop() 129 { 130 if (pathfindingNodeQueueCount == 0) 131 { 132 return 0; 133 } 134 // we return the first node in the queue; we want to return a pointer to the node 135 // so we can return 0 if the queue is empty. 136 // We should _not_ return a pointer to the element in the list, because the list 137 // may be reallocated and the pointer would become invalid. Or the 138 // popped element is overwritten by the next push operation. 139 // Using static here means that the variable is permanently allocated. 140 static PathfindingNode node; 141 node = pathfindingNodeQueue[0]; 142 // we shift all nodes one position to the front 143 for (int i = 1; i < pathfindingNodeQueueCount; i++) 144 { 145 pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i]; 146 } 147 --pathfindingNodeQueueCount; 148 return &node; 149 } 150 151 // transform a world position to a map position in the array; 152 // returns true if the position is inside the map 153 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY) 154 { 155 Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace); 156 *mapX = (int16_t)mapPosition.x; 157 *mapY = (int16_t)mapPosition.z; 158 return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height; 159 } 160 161 void PathFindingMapUpdate() 162 { 163 const int castleX = 0, castleY = 0; 164 int16_t castleMapX, castleMapY; 165 if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY)) 166 { 167 return; 168 } 169 int width = pathfindingMap.width, height = pathfindingMap.height; 170 171 // reset the distances to -1 172 for (int i = 0; i < width * height; i++) 173 { 174 pathfindingMap.distances[i] = -1.0f; 175 } 176 // reset the tower indices 177 for (int i = 0; i < width * height; i++) 178 { 179 pathfindingMap.towerIndex[i] = -1; 180 } 181 // reset the delta src 182 for (int i = 0; i < width * height; i++) 183 { 184 pathfindingMap.deltaSrc[i].x = 0; 185 pathfindingMap.deltaSrc[i].y = 0; 186 } 187 188 for (int i = 0; i < towerCount; i++) 189 { 190 Tower *tower = &towers[i]; 191 if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE) 192 { 193 continue; 194 } 195 int16_t mapX, mapY; 196 // technically, if the tower cell scale is not in sync with the pathfinding map scale, 197 // this would not work correctly and needs to be refined to allow towers covering multiple cells 198 // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly 199 // one cell. For now. 200 if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY)) 201 { 202 continue; 203 } 204 int index = mapY * width + mapX; 205 pathfindingMap.towerIndex[index] = i; 206 } 207 208 // we start at the castle and add the castle to the queue 209 pathfindingMap.maxDistance = 0.0f; 210 pathfindingNodeQueueCount = 0; 211 PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f); 212 PathfindingNode *node = 0; 213 while ((node = PathFindingNodePop())) 214 { 215 if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height) 216 { 217 continue; 218 } 219 int index = node->y * width + node->x; 220 if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance) 221 { 222 continue; 223 } 224 225 int deltaX = node->x - node->fromX; 226 int deltaY = node->y - node->fromY; 227 // even if the cell is blocked by a tower, we still may want to store the direction 228 // (though this might not be needed, IDK right now) 229 pathfindingMap.deltaSrc[index].x = (char) deltaX; 230 pathfindingMap.deltaSrc[index].y = (char) deltaY; 231 232 // we skip nodes that are blocked by towers 233 if (pathfindingMap.towerIndex[index] >= 0) 234 { 235 node->distance += 8.0f; 236 } 237 pathfindingMap.distances[index] = node->distance; 238 pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance); 239 PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f); 240 PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f); 241 PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f); 242 PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f); 243 } 244 } 245 246 void PathFindingMapDraw() 247 { 248 float cellSize = pathfindingMap.scale * 0.9f; 249 float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance); 250 for (int x = 0; x < pathfindingMap.width; x++) 251 { 252 for (int y = 0; y < pathfindingMap.height; y++) 253 { 254 float distance = pathfindingMap.distances[y * pathfindingMap.width + x]; 255 float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f); 256 Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255}; 257 Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace); 258 // animate the distance "wave" to show how the pathfinding algorithm expands 259 // from the castle 260 if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance) 261 { 262 color = BLACK; 263 } 264 DrawCube(position, cellSize, 0.1f, cellSize, color); 265 } 266 } 267 } 268 269 Vector2 PathFindingGetGradient(Vector3 world) 270 { 271 int16_t mapX, mapY; 272 if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY)) 273 { 274 DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX]; 275 return (Vector2){(float)-delta.x, (float)-delta.y}; 276 } 277 // fallback to a simple gradient calculation 278 float n = PathFindingGetDistance(mapX, mapY - 1); 279 float s = PathFindingGetDistance(mapX, mapY + 1); 280 float w = PathFindingGetDistance(mapX - 1, mapY); 281 float e = PathFindingGetDistance(mapX + 1, mapY); 282 return (Vector2){w - e + 0.25f, n - s + 0.125f}; 283 } 284 285 //# Enemies 286 287 #define ENEMY_MAX_PATH_COUNT 8 288 #define ENEMY_MAX_COUNT 400 289 #define ENEMY_TYPE_NONE 0 290 #define ENEMY_TYPE_MINION 1 291 292 typedef struct EnemyId 293 { 294 uint16_t index; 295 uint16_t generation; 296 } EnemyId; 297 298 typedef struct EnemyClassConfig 299 { 300 float speed; 301 float health; 302 float radius;
303 float maxAcceleration; 304 float explosionDamage;
305 } EnemyClassConfig; 306 307 typedef struct Enemy 308 { 309 int16_t currentX, currentY; 310 int16_t nextX, nextY; 311 Vector2 simPosition; 312 Vector2 simVelocity; 313 uint16_t generation; 314 float startMovingTime; 315 float damage, futureDamage; 316 uint8_t enemyType; 317 uint8_t movePathCount; 318 Vector2 movePath[ENEMY_MAX_PATH_COUNT]; 319 } Enemy; 320 321 Enemy enemies[ENEMY_MAX_COUNT]; 322 int enemyCount = 0; 323 324 EnemyClassConfig enemyClassConfigs[] = {
325 [ENEMY_TYPE_MINION] = { 326 .health = 3.0f, 327 .speed = 1.0f, 328 .radius = 0.25f, 329 .maxAcceleration = 1.0f, 330 .explosionDamage = 1.0f, 331 },
332 }; 333 334 void EnemyInit() 335 { 336 for (int i = 0; i < ENEMY_MAX_COUNT; i++) 337 { 338 enemies[i] = (Enemy){0}; 339 } 340 enemyCount = 0; 341 } 342 343 float EnemyGetCurrentMaxSpeed(Enemy *enemy) 344 { 345 return enemyClassConfigs[enemy->enemyType].speed; 346 } 347 348 float EnemyGetMaxHealth(Enemy *enemy) 349 { 350 return enemyClassConfigs[enemy->enemyType].health; 351 } 352 353 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY) 354 { 355 int16_t castleX = 0; 356 int16_t castleY = 0; 357 int16_t dx = castleX - currentX; 358 int16_t dy = castleY - currentY; 359 if (dx == 0 && dy == 0) 360 { 361 *nextX = currentX; 362 *nextY = currentY; 363 return 1; 364 } 365 Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY}); 366 367 if (gradient.x == 0 && gradient.y == 0) 368 { 369 *nextX = currentX; 370 *nextY = currentY; 371 return 1; 372 } 373 374 if (fabsf(gradient.x) > fabsf(gradient.y)) 375 { 376 *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1); 377 *nextY = currentY; 378 return 0; 379 } 380 *nextX = currentX; 381 *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1); 382 return 0; 383 } 384 385 386 // this function predicts the movement of the unit for the next deltaT seconds 387 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount) 388 { 389 const float pointReachedDistance = 0.25f; 390 const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance; 391 const float maxSimStepTime = 0.015625f; 392 393 float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration; 394 float maxSpeed = EnemyGetCurrentMaxSpeed(enemy); 395 int16_t nextX = enemy->nextX; 396 int16_t nextY = enemy->nextY; 397 Vector2 position = enemy->simPosition; 398 int passedCount = 0; 399 for (float t = 0.0f; t < deltaT; t += maxSimStepTime) 400 { 401 float stepTime = fminf(deltaT - t, maxSimStepTime); 402 Vector2 target = (Vector2){nextX, nextY}; 403 float speed = Vector2Length(*velocity); 404 // draw the target position for debugging 405 DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED); 406 Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed)); 407 if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2) 408 { 409 // we reached the target position, let's move to the next waypoint 410 EnemyGetNextPosition(nextX, nextY, &nextX, &nextY); 411 target = (Vector2){nextX, nextY}; 412 // track how many waypoints we passed 413 passedCount++; 414 } 415 416 // acceleration towards the target 417 Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos)); 418 Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime); 419 *velocity = Vector2Add(*velocity, acceleration); 420 421 // limit the speed to the maximum speed 422 if (speed > maxSpeed) 423 { 424 *velocity = Vector2Scale(*velocity, maxSpeed / speed); 425 } 426 427 // move the enemy 428 position = Vector2Add(position, Vector2Scale(*velocity, stepTime)); 429 } 430 431 if (waypointPassedCount) 432 { 433 (*waypointPassedCount) = passedCount; 434 } 435 436 return position; 437 } 438 439 void EnemyDraw() 440 { 441 for (int i = 0; i < enemyCount; i++) 442 { 443 Enemy enemy = enemies[i]; 444 if (enemy.enemyType == ENEMY_TYPE_NONE) 445 { 446 continue; 447 } 448 449 Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0); 450 451 if (enemy.movePathCount > 0) 452 { 453 Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y}; 454 DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN); 455 } 456 for (int j = 1; j < enemy.movePathCount; j++) 457 { 458 Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y}; 459 Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y}; 460 DrawLine3D(p, q, GREEN); 461 } 462 463 switch (enemy.enemyType) 464 { 465 case ENEMY_TYPE_MINION: 466 DrawCubeWires((Vector3){position.x, 0.2f, position.y}, 0.4f, 0.4f, 0.4f, GREEN);
467 break; 468 } 469 } 470 } 471 472 void EnemyTriggerExplode(Enemy *enemy, Tower *tower) 473 { 474 // damage the tower 475 tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage; 476 // explode the enemy 477 if (tower->damage >= TowerGetMaxHealth(tower)) 478 { 479 tower->towerType = TOWER_TYPE_NONE; 480 } 481 482 enemy->enemyType = ENEMY_TYPE_NONE;
483 } 484 485 void EnemyUpdate() 486 { 487 const float castleX = 0; 488 const float castleY = 0; 489 const float maxPathDistance2 = 0.25f * 0.25f; 490 491 for (int i = 0; i < enemyCount; i++) 492 { 493 Enemy *enemy = &enemies[i]; 494 if (enemy->enemyType == ENEMY_TYPE_NONE) 495 { 496 continue; 497 } 498 499 int waypointPassedCount = 0; 500 enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount); 501 enemy->startMovingTime = gameTime.time; 502 // track path of unit 503 if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2) 504 { 505 for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--) 506 { 507 enemy->movePath[j] = enemy->movePath[j - 1]; 508 } 509 enemy->movePath[0] = enemy->simPosition; 510 if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT) 511 { 512 enemy->movePathCount = ENEMY_MAX_PATH_COUNT; 513 } 514 } 515 516 if (waypointPassedCount > 0) 517 { 518 enemy->currentX = enemy->nextX; 519 enemy->currentY = enemy->nextY; 520 if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) && 521 Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f) 522 { 523 // enemy reached the castle; remove it 524 enemy->enemyType = ENEMY_TYPE_NONE; 525 continue; 526 } 527 } 528 } 529 530 // handle collisions between enemies 531 for (int i = 0; i < enemyCount - 1; i++) 532 { 533 Enemy *enemyA = &enemies[i]; 534 if (enemyA->enemyType == ENEMY_TYPE_NONE) 535 { 536 continue; 537 } 538 for (int j = i + 1; j < enemyCount; j++) 539 { 540 Enemy *enemyB = &enemies[j]; 541 if (enemyB->enemyType == ENEMY_TYPE_NONE) 542 { 543 continue; 544 } 545 float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition); 546 float radiusA = enemyClassConfigs[enemyA->enemyType].radius; 547 float radiusB = enemyClassConfigs[enemyB->enemyType].radius; 548 float radiusSum = radiusA + radiusB; 549 if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f) 550 { 551 // collision 552 float distance = sqrtf(distanceSqr); 553 float overlap = radiusSum - distance; 554 // move the enemies apart, but softly; if we have a clog of enemies, 555 // moving them perfectly apart can cause them to jitter 556 float positionCorrection = overlap / 5.0f; 557 Vector2 direction = (Vector2){ 558 (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection, 559 (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection}; 560 enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction); 561 enemyB->simPosition = Vector2Add(enemyB->simPosition, direction); 562 } 563 } 564 } 565 566 // handle collisions between enemies and towers 567 for (int i = 0; i < enemyCount; i++) 568 { 569 Enemy *enemy = &enemies[i]; 570 if (enemy->enemyType == ENEMY_TYPE_NONE) 571 { 572 continue; 573 } 574 float enemyRadius = enemyClassConfigs[enemy->enemyType].radius; 575 // linear search over towers; could be optimized by using path finding tower map, 576 // but for now, we keep it simple 577 for (int j = 0; j < towerCount; j++) 578 { 579 Tower *tower = &towers[j]; 580 if (tower->towerType == TOWER_TYPE_NONE) 581 { 582 continue; 583 } 584 float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y}); 585 float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1 586 if (distanceSqr > combinedRadius * combinedRadius) 587 { 588 continue; 589 } 590 // potential collision; square / circle intersection 591 float dx = tower->x - enemy->simPosition.x; 592 float dy = tower->y - enemy->simPosition.y; 593 float absDx = fabsf(dx); 594 float absDy = fabsf(dy); 595 if (absDx <= 0.5f && absDx <= absDy) { 596 // vertical collision; push the enemy out horizontally 597 float overlap = enemyRadius + 0.5f - absDy; 598 if (overlap < 0.0f) 599 { 600 continue; 601 } 602 float direction = dy > 0.0f ? -1.0f : 1.0f; 603 enemy->simPosition.y += direction * overlap; 604 } 605 else if (absDy <= 0.5f && absDy <= absDx) 606 { 607 // horizontal collision; push the enemy out vertically 608 float overlap = enemyRadius + 0.5f - absDx; 609 if (overlap < 0.0f) 610 { 611 continue; 612 } 613 float direction = dx > 0.0f ? -1.0f : 1.0f; 614 enemy->simPosition.x += direction * overlap; 615 } 616 else 617 { 618 // possible collision with a corner 619 float cornerDX = dx > 0.0f ? -0.5f : 0.5f; 620 float cornerDY = dy > 0.0f ? -0.5f : 0.5f; 621 float cornerX = tower->x + cornerDX; 622 float cornerY = tower->y + cornerDY; 623 float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY}); 624 if (cornerDistanceSqr > enemyRadius * enemyRadius) 625 { 626 continue; 627 } 628 // push the enemy out along the diagonal 629 float cornerDistance = sqrtf(cornerDistanceSqr); 630 float overlap = enemyRadius - cornerDistance; 631 float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX; 632 float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY; 633 enemy->simPosition.x -= directionX * overlap;
634 enemy->simPosition.y -= directionY * overlap; 635 } 636 637 if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f) 638 { 639 EnemyTriggerExplode(enemy, tower);
640 } 641 } 642 } 643 } 644 645 EnemyId EnemyGetId(Enemy *enemy) 646 { 647 return (EnemyId){enemy - enemies, enemy->generation}; 648 } 649 650 Enemy *EnemyTryResolve(EnemyId enemyId) 651 { 652 if (enemyId.index >= ENEMY_MAX_COUNT) 653 { 654 return 0; 655 } 656 Enemy *enemy = &enemies[enemyId.index]; 657 if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE) 658 { 659 return 0; 660 } 661 return enemy; 662 } 663 664 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY) 665 { 666 Enemy *spawn = 0; 667 for (int i = 0; i < enemyCount; i++) 668 { 669 Enemy *enemy = &enemies[i]; 670 if (enemy->enemyType == ENEMY_TYPE_NONE) 671 { 672 spawn = enemy; 673 break; 674 } 675 } 676 677 if (enemyCount < ENEMY_MAX_COUNT && !spawn) 678 { 679 spawn = &enemies[enemyCount++]; 680 } 681 682 if (spawn) 683 { 684 spawn->currentX = currentX; 685 spawn->currentY = currentY; 686 spawn->nextX = currentX; 687 spawn->nextY = currentY; 688 spawn->simPosition = (Vector2){currentX, currentY}; 689 spawn->simVelocity = (Vector2){0, 0}; 690 spawn->enemyType = enemyType; 691 spawn->startMovingTime = gameTime.time; 692 spawn->damage = 0.0f; 693 spawn->futureDamage = 0.0f; 694 spawn->generation++; 695 spawn->movePathCount = 0; 696 } 697 698 return spawn; 699 } 700 701 int EnemyAddDamage(Enemy *enemy, float damage) 702 { 703 enemy->damage += damage; 704 if (enemy->damage >= EnemyGetMaxHealth(enemy)) 705 { 706 enemy->enemyType = ENEMY_TYPE_NONE; 707 return 1; 708 } 709 710 return 0; 711 } 712 713 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range) 714 { 715 int16_t castleX = 0; 716 int16_t castleY = 0; 717 Enemy* closest = 0; 718 int16_t closestDistance = 0; 719 float range2 = range * range; 720 for (int i = 0; i < enemyCount; i++) 721 { 722 Enemy* enemy = &enemies[i]; 723 if (enemy->enemyType == ENEMY_TYPE_NONE) 724 { 725 continue; 726 } 727 float maxHealth = EnemyGetMaxHealth(enemy); 728 if (enemy->futureDamage >= maxHealth) 729 { 730 // ignore enemies that will die soon 731 continue; 732 } 733 int16_t dx = castleX - enemy->currentX; 734 int16_t dy = castleY - enemy->currentY; 735 int16_t distance = abs(dx) + abs(dy); 736 if (!closest || distance < closestDistance) 737 { 738 float tdx = towerX - enemy->currentX; 739 float tdy = towerY - enemy->currentY; 740 float tdistance2 = tdx * tdx + tdy * tdy; 741 if (tdistance2 <= range2) 742 { 743 closest = enemy; 744 closestDistance = distance; 745 } 746 } 747 } 748 return closest; 749 } 750 751 int EnemyCount() 752 { 753 int count = 0; 754 for (int i = 0; i < enemyCount; i++) 755 { 756 if (enemies[i].enemyType != ENEMY_TYPE_NONE) 757 { 758 count++; 759 } 760 } 761 return count; 762 } 763 764 //# Projectiles 765 #define PROJECTILE_MAX_COUNT 1200 766 #define PROJECTILE_TYPE_NONE 0 767 #define PROJECTILE_TYPE_BULLET 1 768 769 typedef struct Projectile 770 { 771 uint8_t projectileType; 772 float shootTime; 773 float arrivalTime; 774 float damage; 775 Vector2 position; 776 Vector2 target; 777 Vector2 directionNormal; 778 EnemyId targetEnemy; 779 } Projectile; 780 781 Projectile projectiles[PROJECTILE_MAX_COUNT]; 782 int projectileCount = 0; 783 784 void ProjectileInit() 785 { 786 for (int i = 0; i < PROJECTILE_MAX_COUNT; i++) 787 { 788 projectiles[i] = (Projectile){0}; 789 } 790 } 791 792 void ProjectileDraw() 793 { 794 for (int i = 0; i < projectileCount; i++) 795 { 796 Projectile projectile = projectiles[i]; 797 if (projectile.projectileType == PROJECTILE_TYPE_NONE) 798 { 799 continue; 800 } 801 float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime); 802 if (transition >= 1.0f) 803 { 804 continue; 805 } 806 Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition); 807 float x = position.x; 808 float y = position.y; 809 float dx = projectile.directionNormal.x; 810 float dy = projectile.directionNormal.y; 811 for (float d = 1.0f; d > 0.0f; d -= 0.25f) 812 { 813 x -= dx * 0.1f; 814 y -= dy * 0.1f; 815 float size = 0.1f * d; 816 DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED); 817 } 818 } 819 } 820 821 void ProjectileUpdate() 822 { 823 for (int i = 0; i < projectileCount; i++) 824 { 825 Projectile *projectile = &projectiles[i]; 826 if (projectile->projectileType == PROJECTILE_TYPE_NONE) 827 { 828 continue; 829 } 830 float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime); 831 if (transition >= 1.0f) 832 { 833 projectile->projectileType = PROJECTILE_TYPE_NONE; 834 Enemy *enemy = EnemyTryResolve(projectile->targetEnemy); 835 if (enemy) 836 { 837 EnemyAddDamage(enemy, projectile->damage); 838 } 839 continue; 840 } 841 } 842 } 843 844 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage) 845 { 846 for (int i = 0; i < PROJECTILE_MAX_COUNT; i++) 847 { 848 Projectile *projectile = &projectiles[i]; 849 if (projectile->projectileType == PROJECTILE_TYPE_NONE) 850 { 851 projectile->projectileType = projectileType; 852 projectile->shootTime = gameTime.time; 853 projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed; 854 projectile->damage = damage; 855 projectile->position = position; 856 projectile->target = target; 857 projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position)); 858 projectile->targetEnemy = EnemyGetId(enemy); 859 projectileCount = projectileCount <= i ? i + 1 : projectileCount; 860 return projectile; 861 } 862 } 863 return 0; 864 } 865 866 //# Towers 867 868 void TowerInit() 869 { 870 for (int i = 0; i < TOWER_MAX_COUNT; i++) 871 { 872 towers[i] = (Tower){0}; 873 } 874 towerCount = 0; 875 } 876 877 Tower *TowerGetAt(int16_t x, int16_t y) 878 { 879 for (int i = 0; i < towerCount; i++) 880 { 881 if (towers[i].x == x && towers[i].y == y) 882 { 883 return &towers[i]; 884 } 885 } 886 return 0; 887 } 888 889 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y) 890 { 891 if (towerCount >= TOWER_MAX_COUNT) 892 { 893 return 0; 894 } 895 896 Tower *tower = TowerGetAt(x, y); 897 if (tower) 898 { 899 return 0; 900 } 901 902 tower = &towers[towerCount++]; 903 tower->x = x; 904 tower->y = y;
905 tower->towerType = towerType; 906 tower->cooldown = 0.0f; 907 tower->damage = 0.0f; 908 return tower; 909 } 910 911 float TowerGetMaxHealth(Tower *tower) 912 { 913 switch (tower->towerType) 914 { 915 case TOWER_TYPE_BASE: 916 return 10.0f; 917 case TOWER_TYPE_GUN: 918 return 3.0f; 919 case TOWER_TYPE_WALL: 920 return 5.0f; 921 } 922 return 0.0f;
923 } 924 925 void TowerDraw() 926 { 927 for (int i = 0; i < towerCount; i++) 928 { 929 Tower tower = towers[i]; 930 DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY); 931 switch (tower.towerType) 932 { 933 case TOWER_TYPE_BASE: 934 DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON); 935 break; 936 case TOWER_TYPE_GUN: 937 DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE); 938 break; 939 case TOWER_TYPE_WALL: 940 DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY); 941 break; 942 } 943 } 944 } 945 946 void TowerGunUpdate(Tower *tower) 947 { 948 if (tower->cooldown <= 0) 949 { 950 Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f); 951 if (enemy) 952 { 953 tower->cooldown = 0.125f; 954 // shoot the enemy; determine future position of the enemy 955 float bulletSpeed = 1.0f; 956 float bulletDamage = 3.0f; 957 Vector2 velocity = enemy->simVelocity; 958 Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0); 959 Vector2 towerPosition = {tower->x, tower->y}; 960 float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed; 961 for (int i = 0; i < 8; i++) { 962 velocity = enemy->simVelocity; 963 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0); 964 float distance = Vector2Distance(towerPosition, futurePosition); 965 float eta2 = distance / bulletSpeed; 966 if (fabs(eta - eta2) < 0.01f) { 967 break; 968 } 969 eta = (eta2 + eta) * 0.5f; 970 } 971 ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, towerPosition, futurePosition, 972 bulletSpeed, bulletDamage); 973 enemy->futureDamage += bulletDamage; 974 } 975 } 976 else 977 { 978 tower->cooldown -= gameTime.deltaTime; 979 } 980 } 981 982 void TowerUpdate() 983 { 984 for (int i = 0; i < towerCount; i++) 985 { 986 Tower *tower = &towers[i]; 987 switch (tower->towerType) 988 { 989 case TOWER_TYPE_GUN: 990 TowerGunUpdate(tower); 991 break; 992 } 993 } 994 } 995 996 //# Game 997 998 float nextSpawnTime = 0.0f; 999 1000 void InitGame() 1001 { 1002 TowerInit(); 1003 EnemyInit(); 1004 ProjectileInit(); 1005 PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f); 1006 1007 TowerTryAdd(TOWER_TYPE_BASE, 0, 0); 1008 TowerTryAdd(TOWER_TYPE_GUN, 2, 0); 1009 1010 for (int i = -2; i <= 2; i += 1) 1011 { 1012 TowerTryAdd(TOWER_TYPE_WALL, i, 2); 1013 TowerTryAdd(TOWER_TYPE_WALL, i, -2); 1014 TowerTryAdd(TOWER_TYPE_WALL, -2, i); 1015 } 1016 1017 EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4); 1018 } 1019 1020 void GameUpdate() 1021 { 1022 float dt = GetFrameTime(); 1023 // cap maximum delta time to 0.1 seconds to prevent large time steps 1024 if (dt > 0.1f) dt = 0.1f; 1025 gameTime.time += dt; 1026 gameTime.deltaTime = dt; 1027 PathFindingMapUpdate(); 1028 EnemyUpdate(); 1029 TowerUpdate(); 1030 ProjectileUpdate(); 1031 1032 // spawn a new enemy every second 1033 if (gameTime.time >= nextSpawnTime && EnemyCount() < 50) 1034 { 1035 nextSpawnTime = gameTime.time + 0.2f; 1036 // add a new enemy at the boundary of the map 1037 int randValue = GetRandomValue(-5, 5); 1038 int randSide = GetRandomValue(0, 3); 1039 int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue; 1040 int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue; 1041 static int alternation = 0; 1042 alternation += 1; 1043 if (alternation % 3 == 0) { 1044 EnemyTryAdd(ENEMY_TYPE_MINION, 0, -5); 1045 } 1046 else if (alternation % 3 == 1) 1047 { 1048 EnemyTryAdd(ENEMY_TYPE_MINION, 0, 5); 1049 } 1050 EnemyTryAdd(ENEMY_TYPE_MINION, x, y); 1051 } 1052 } 1053 1054 int main(void) 1055 { 1056 int screenWidth, screenHeight; 1057 GetPreferredSize(&screenWidth, &screenHeight); 1058 InitWindow(screenWidth, screenHeight, "Tower defense"); 1059 SetTargetFPS(30); 1060 1061 Camera3D camera = {0}; 1062 camera.position = (Vector3){0.0f, 10.0f, -0.5f}; 1063 camera.target = (Vector3){0.0f, 0.0f, -0.5f}; 1064 camera.up = (Vector3){0.0f, 0.0f, -1.0f}; 1065 camera.fovy = 12.0f; 1066 camera.projection = CAMERA_ORTHOGRAPHIC; 1067 1068 InitGame(); 1069 1070 while (!WindowShouldClose()) 1071 { 1072 if (IsPaused()) { 1073 // canvas is not visible in browser - do nothing 1074 continue; 1075 } 1076 BeginDrawing(); 1077 ClearBackground(DARKBLUE); 1078 1079 BeginMode3D(camera); 1080 DrawGrid(10, 1.0f); 1081 TowerDraw(); 1082 EnemyDraw(); 1083 ProjectileDraw(); 1084 PathFindingMapDraw(); 1085 GameUpdate(); 1086 EndMode3D(); 1087 1088 const char *title = "Tower defense tutorial"; 1089 int titleWidth = MeasureText(title, 20); 1090 DrawText(title, (GetScreenWidth() - titleWidth) * 0.5f + 2, 5 + 2, 20, BLACK); 1091 DrawText(title, (GetScreenWidth() - titleWidth) * 0.5f, 5, 20, WHITE); 1092 EndDrawing(); 1093 } 1094 1095 CloseWindow(); 1096 1097 return 0; 1098 }
  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 #endif
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif

Our base didn't stand a chance 😶

Ok, let's make it more interesting; an enemy needs to touch the building for a certain amount of time before exploding. Also, when the enemy explodes, it should deal damage to other enemies in the area and pushing them back (line 483, 600 and 673):

  • 💾
  1 #include "td-tut-2-main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 //# Declarations
  7 
  8 #define TOWER_MAX_COUNT 400
  9 #define TOWER_TYPE_NONE 0
 10 #define TOWER_TYPE_BASE 1
 11 #define TOWER_TYPE_GUN 2
 12 #define TOWER_TYPE_WALL 3
 13 
 14 typedef struct Tower
 15 {
 16   int16_t x, y;
 17   uint8_t towerType;
 18   float cooldown;
 19   float damage;
 20 } Tower;
 21 
 22 typedef struct GameTime
 23 {
 24   float time;
 25   float deltaTime;
 26 } GameTime;
 27 
 28 GameTime gameTime = {0};
 29 
 30 Tower towers[TOWER_MAX_COUNT];
 31 int towerCount = 0;
 32 
 33 float TowerGetMaxHealth(Tower *tower);
 34 
 35 //# Pathfinding map
 36 typedef struct DeltaSrc
 37 {
 38   char x, y;
 39 } DeltaSrc;
 40 
 41 typedef struct PathfindingMap
 42 {
 43   int width, height;
 44   float scale;
 45   float *distances;
 46   long *towerIndex; 
 47   DeltaSrc *deltaSrc;
 48   float maxDistance;
 49   Matrix toMapSpace;
 50   Matrix toWorldSpace;
 51 } PathfindingMap;
 52 
 53 // when we execute the pathfinding algorithm, we need to store the active nodes
 54 // in a queue. Each node has a position, a distance from the start, and the
 55 // position of the node that we came from.
 56 typedef struct PathfindingNode
 57 {
 58   int16_t x, y, fromX, fromY;
 59   float distance;
 60 } PathfindingNode;
 61 
 62 // The queue is a simple array of nodes, we add nodes to the end and remove
 63 // nodes from the front. We keep the array around to avoid unnecessary allocations
 64 static PathfindingNode *pathfindingNodeQueue = 0;
 65 static int pathfindingNodeQueueCount = 0;
 66 static int pathfindingNodeQueueCapacity = 0;
 67 
 68 // The pathfinding map stores the distances from the castle to each cell in the map.
 69 PathfindingMap pathfindingMap = {0};
 70 
 71 void PathfindingMapInit(int width, int height, Vector3 translate, float scale)
 72 {
 73   // transforming between map space and world space allows us to adapt 
 74   // position and scale of the map without changing the pathfinding data
 75   pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z);
 76   pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale));
 77   pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace);
 78   pathfindingMap.width = width;
 79   pathfindingMap.height = height;
 80   pathfindingMap.scale = scale;
 81   pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float));
 82   for (int i = 0; i < width * height; i++)
 83   {
 84     pathfindingMap.distances[i] = -1.0f;
 85   }
 86 
 87   pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long));
 88   pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc));
 89 }
 90 
 91 float PathFindingGetDistance(int mapX, int mapY)
 92 {
 93   if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height)
 94   {
 95     // when outside the map, we return the manhattan distance to the castle (0,0)
 96     return fabsf((float)mapX) + fabsf((float)mapY);
 97   }
 98 
 99   return pathfindingMap.distances[mapY * pathfindingMap.width + mapX];
100 }
101 
102 void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance)
103 {
104   if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity)
105   {
106     pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2;
107     // we use MemAlloc/MemRealloc to allocate memory for the queue
108     // I am not entirely sure if MemRealloc allows passing a null pointer
109     // so we check if the pointer is null and use MemAlloc in that case
110     if (pathfindingNodeQueue == 0)
111     {
112       pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
113     }
114     else
115     {
116       pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
117     }
118   }
119 
120   PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++];
121   node->x = x;
122   node->y = y;
123   node->fromX = fromX;
124   node->fromY = fromY;
125   node->distance = distance;
126 }
127 
128 PathfindingNode *PathFindingNodePop()
129 {
130   if (pathfindingNodeQueueCount == 0)
131   {
132     return 0;
133   }
134   // we return the first node in the queue; we want to return a pointer to the node
135   // so we can return 0 if the queue is empty. 
136   // We should _not_ return a pointer to the element in the list, because the list
137   // may be reallocated and the pointer would become invalid. Or the 
138   // popped element is overwritten by the next push operation.
139   // Using static here means that the variable is permanently allocated.
140   static PathfindingNode node;
141   node = pathfindingNodeQueue[0];
142   // we shift all nodes one position to the front
143   for (int i = 1; i < pathfindingNodeQueueCount; i++)
144   {
145     pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i];
146   }
147   --pathfindingNodeQueueCount;
148   return &node;
149 }
150 
151 // transform a world position to a map position in the array; 
152 // returns true if the position is inside the map
153 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY)
154 {
155   Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace);
156   *mapX = (int16_t)mapPosition.x;
157   *mapY = (int16_t)mapPosition.z;
158   return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height;
159 }
160 
161 void PathFindingMapUpdate()
162 {
163   const int castleX = 0, castleY = 0;
164   int16_t castleMapX, castleMapY;
165   if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY))
166   {
167     return;
168   }
169   int width = pathfindingMap.width, height = pathfindingMap.height;
170 
171   // reset the distances to -1
172   for (int i = 0; i < width * height; i++)
173   {
174     pathfindingMap.distances[i] = -1.0f;
175   }
176   // reset the tower indices
177   for (int i = 0; i < width * height; i++)
178   {
179     pathfindingMap.towerIndex[i] = -1;
180   }
181   // reset the delta src
182   for (int i = 0; i < width * height; i++)
183   {
184     pathfindingMap.deltaSrc[i].x = 0;
185     pathfindingMap.deltaSrc[i].y = 0;
186   }
187 
188   for (int i = 0; i < towerCount; i++)
189   {
190     Tower *tower = &towers[i];
191     if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE)
192     {
193       continue;
194     }
195     int16_t mapX, mapY;
196     // technically, if the tower cell scale is not in sync with the pathfinding map scale,
197     // this would not work correctly and needs to be refined to allow towers covering multiple cells
198     // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly
199     // one cell. For now.
200     if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY))
201     {
202       continue;
203     }
204     int index = mapY * width + mapX;
205     pathfindingMap.towerIndex[index] = i;
206   }
207 
208   // we start at the castle and add the castle to the queue
209   pathfindingMap.maxDistance = 0.0f;
210   pathfindingNodeQueueCount = 0;
211   PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f);
212   PathfindingNode *node = 0;
213   while ((node = PathFindingNodePop()))
214   {
215     if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height)
216     {
217       continue;
218     }
219     int index = node->y * width + node->x;
220     if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance)
221     {
222       continue;
223     }
224 
225     int deltaX = node->x - node->fromX;
226     int deltaY = node->y - node->fromY;
227     // even if the cell is blocked by a tower, we still may want to store the direction
228     // (though this might not be needed, IDK right now)
229     pathfindingMap.deltaSrc[index].x = (char) deltaX;
230     pathfindingMap.deltaSrc[index].y = (char) deltaY;
231 
232     // we skip nodes that are blocked by towers
233     if (pathfindingMap.towerIndex[index] >= 0)
234     {
235       node->distance += 8.0f;
236     }
237     pathfindingMap.distances[index] = node->distance;
238     pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance);
239     PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f);
240     PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f);
241     PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f);
242     PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f);
243   }
244 }
245 
246 void PathFindingMapDraw()
247 {
248   float cellSize = pathfindingMap.scale * 0.9f;
249   float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance);
250   for (int x = 0; x < pathfindingMap.width; x++)
251   {
252     for (int y = 0; y < pathfindingMap.height; y++)
253     {
254       float distance = pathfindingMap.distances[y * pathfindingMap.width + x];
255       float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f);
256       Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255};
257       Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace);
258       // animate the distance "wave" to show how the pathfinding algorithm expands
259       // from the castle
260       if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance)
261       {
262         color = BLACK;
263       }
264       DrawCube(position, cellSize, 0.1f, cellSize, color);
265     }
266   }
267 }
268 
269 Vector2 PathFindingGetGradient(Vector3 world)
270 {
271   int16_t mapX, mapY;
272   if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY))
273   {
274     DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX];
275     return (Vector2){(float)-delta.x, (float)-delta.y};
276   }
277   // fallback to a simple gradient calculation
278   float n = PathFindingGetDistance(mapX, mapY - 1);
279   float s = PathFindingGetDistance(mapX, mapY + 1);
280   float w = PathFindingGetDistance(mapX - 1, mapY);
281   float e = PathFindingGetDistance(mapX + 1, mapY);
282   return (Vector2){w - e + 0.25f, n - s + 0.125f};
283 }
284 
285 //# Enemies
286 
287 #define ENEMY_MAX_PATH_COUNT 8
288 #define ENEMY_MAX_COUNT 400
289 #define ENEMY_TYPE_NONE 0
290 #define ENEMY_TYPE_MINION 1
291 
292 typedef struct EnemyId
293 {
294   uint16_t index;
295   uint16_t generation;
296 } EnemyId;
297 
298 typedef struct EnemyClassConfig
299 {
300   float speed;
301   float health;
302   float radius;
303   float maxAcceleration;
304 float requiredContactTime; 305 float explosionDamage; 306 float explosionRange; 307 float explosionPushbackPower;
308 } EnemyClassConfig; 309 310 typedef struct Enemy 311 { 312 int16_t currentX, currentY; 313 int16_t nextX, nextY; 314 Vector2 simPosition; 315 Vector2 simVelocity; 316 uint16_t generation; 317 float startMovingTime;
318 float damage, futureDamage; 319 float contactTime;
320 uint8_t enemyType; 321 uint8_t movePathCount; 322 Vector2 movePath[ENEMY_MAX_PATH_COUNT]; 323 } Enemy; 324 325 Enemy enemies[ENEMY_MAX_COUNT]; 326 int enemyCount = 0; 327 328 EnemyClassConfig enemyClassConfigs[] = { 329 [ENEMY_TYPE_MINION] = { 330 .health = 3.0f, 331 .speed = 1.0f, 332 .radius = 0.25f, 333 .maxAcceleration = 1.0f,
334 .explosionDamage = 1.0f, 335 .requiredContactTime = 0.5f, 336 .explosionRange = 1.0f, 337 .explosionPushbackPower = 0.25f,
338 },
339 }; 340 341 int EnemyAddDamage(Enemy *enemy, float damage);
342 343 void EnemyInit() 344 { 345 for (int i = 0; i < ENEMY_MAX_COUNT; i++) 346 { 347 enemies[i] = (Enemy){0}; 348 } 349 enemyCount = 0; 350 } 351 352 float EnemyGetCurrentMaxSpeed(Enemy *enemy) 353 { 354 return enemyClassConfigs[enemy->enemyType].speed; 355 } 356 357 float EnemyGetMaxHealth(Enemy *enemy) 358 { 359 return enemyClassConfigs[enemy->enemyType].health; 360 } 361 362 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY) 363 { 364 int16_t castleX = 0; 365 int16_t castleY = 0; 366 int16_t dx = castleX - currentX; 367 int16_t dy = castleY - currentY; 368 if (dx == 0 && dy == 0) 369 { 370 *nextX = currentX; 371 *nextY = currentY; 372 return 1; 373 } 374 Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY}); 375 376 if (gradient.x == 0 && gradient.y == 0) 377 { 378 *nextX = currentX; 379 *nextY = currentY; 380 return 1; 381 } 382 383 if (fabsf(gradient.x) > fabsf(gradient.y)) 384 { 385 *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1); 386 *nextY = currentY; 387 return 0; 388 } 389 *nextX = currentX; 390 *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1); 391 return 0; 392 } 393 394 395 // this function predicts the movement of the unit for the next deltaT seconds 396 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount) 397 { 398 const float pointReachedDistance = 0.25f; 399 const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance; 400 const float maxSimStepTime = 0.015625f; 401 402 float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration; 403 float maxSpeed = EnemyGetCurrentMaxSpeed(enemy); 404 int16_t nextX = enemy->nextX; 405 int16_t nextY = enemy->nextY; 406 Vector2 position = enemy->simPosition; 407 int passedCount = 0; 408 for (float t = 0.0f; t < deltaT; t += maxSimStepTime) 409 { 410 float stepTime = fminf(deltaT - t, maxSimStepTime); 411 Vector2 target = (Vector2){nextX, nextY}; 412 float speed = Vector2Length(*velocity); 413 // draw the target position for debugging 414 DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED); 415 Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed)); 416 if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2) 417 { 418 // we reached the target position, let's move to the next waypoint 419 EnemyGetNextPosition(nextX, nextY, &nextX, &nextY); 420 target = (Vector2){nextX, nextY}; 421 // track how many waypoints we passed 422 passedCount++; 423 } 424 425 // acceleration towards the target 426 Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos)); 427 Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime); 428 *velocity = Vector2Add(*velocity, acceleration); 429 430 // limit the speed to the maximum speed 431 if (speed > maxSpeed) 432 { 433 *velocity = Vector2Scale(*velocity, maxSpeed / speed); 434 } 435 436 // move the enemy 437 position = Vector2Add(position, Vector2Scale(*velocity, stepTime)); 438 } 439 440 if (waypointPassedCount) 441 { 442 (*waypointPassedCount) = passedCount; 443 } 444 445 return position; 446 } 447 448 void EnemyDraw() 449 { 450 for (int i = 0; i < enemyCount; i++) 451 { 452 Enemy enemy = enemies[i]; 453 if (enemy.enemyType == ENEMY_TYPE_NONE) 454 { 455 continue; 456 } 457 458 Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0); 459 460 if (enemy.movePathCount > 0) 461 { 462 Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y}; 463 DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN); 464 } 465 for (int j = 1; j < enemy.movePathCount; j++) 466 { 467 Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y}; 468 Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y}; 469 DrawLine3D(p, q, GREEN); 470 } 471 472 switch (enemy.enemyType) 473 { 474 case ENEMY_TYPE_MINION: 475 DrawCubeWires((Vector3){position.x, 0.2f, position.y}, 0.4f, 0.4f, 0.4f, GREEN); 476 break; 477 } 478 } 479 } 480 481 void EnemyTriggerExplode(Enemy *enemy, Tower *tower) 482 {
483 // damage the tower 484 float explosionDamge = enemyClassConfigs[enemy->enemyType].explosionDamage; 485 float explosionRange = enemyClassConfigs[enemy->enemyType].explosionRange; 486 float explosionPushbackPower = enemyClassConfigs[enemy->enemyType].explosionPushbackPower; 487 float explosionRange2 = explosionRange * explosionRange;
488 tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage; 489 // explode the enemy 490 if (tower->damage >= TowerGetMaxHealth(tower)) 491 { 492 tower->towerType = TOWER_TYPE_NONE; 493 } 494
495 enemy->enemyType = ENEMY_TYPE_NONE; 496 497 // push back enemies & dealing damage 498 for (int i = 0; i < enemyCount; i++) 499 { 500 Enemy *other = &enemies[i]; 501 if (other->enemyType == ENEMY_TYPE_NONE) 502 { 503 continue; 504 } 505 float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition); 506 if (distanceSqr > 0 && distanceSqr < explosionRange2) 507 { 508 Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition)); 509 other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower)); 510 EnemyAddDamage(other, explosionDamge); 511 } 512 }
513 } 514 515 void EnemyUpdate() 516 { 517 const float castleX = 0; 518 const float castleY = 0; 519 const float maxPathDistance2 = 0.25f * 0.25f; 520 521 for (int i = 0; i < enemyCount; i++) 522 { 523 Enemy *enemy = &enemies[i]; 524 if (enemy->enemyType == ENEMY_TYPE_NONE) 525 { 526 continue; 527 } 528 529 int waypointPassedCount = 0; 530 enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount); 531 enemy->startMovingTime = gameTime.time; 532 // track path of unit 533 if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2) 534 { 535 for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--) 536 { 537 enemy->movePath[j] = enemy->movePath[j - 1]; 538 } 539 enemy->movePath[0] = enemy->simPosition; 540 if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT) 541 { 542 enemy->movePathCount = ENEMY_MAX_PATH_COUNT; 543 } 544 } 545 546 if (waypointPassedCount > 0) 547 { 548 enemy->currentX = enemy->nextX; 549 enemy->currentY = enemy->nextY; 550 if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) && 551 Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f) 552 { 553 // enemy reached the castle; remove it 554 enemy->enemyType = ENEMY_TYPE_NONE; 555 continue; 556 } 557 } 558 } 559 560 // handle collisions between enemies 561 for (int i = 0; i < enemyCount - 1; i++) 562 { 563 Enemy *enemyA = &enemies[i]; 564 if (enemyA->enemyType == ENEMY_TYPE_NONE) 565 { 566 continue; 567 } 568 for (int j = i + 1; j < enemyCount; j++) 569 { 570 Enemy *enemyB = &enemies[j]; 571 if (enemyB->enemyType == ENEMY_TYPE_NONE) 572 { 573 continue; 574 } 575 float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition); 576 float radiusA = enemyClassConfigs[enemyA->enemyType].radius; 577 float radiusB = enemyClassConfigs[enemyB->enemyType].radius; 578 float radiusSum = radiusA + radiusB; 579 if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f) 580 { 581 // collision 582 float distance = sqrtf(distanceSqr); 583 float overlap = radiusSum - distance; 584 // move the enemies apart, but softly; if we have a clog of enemies, 585 // moving them perfectly apart can cause them to jitter 586 float positionCorrection = overlap / 5.0f; 587 Vector2 direction = (Vector2){ 588 (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection, 589 (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection}; 590 enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction); 591 enemyB->simPosition = Vector2Add(enemyB->simPosition, direction); 592 } 593 } 594 } 595 596 // handle collisions between enemies and towers 597 for (int i = 0; i < enemyCount; i++) 598 { 599 Enemy *enemy = &enemies[i]; 600 if (enemy->enemyType == ENEMY_TYPE_NONE) 601 {
602 continue; 603 } 604 enemy->contactTime -= gameTime.deltaTime; 605 if (enemy->contactTime < 0.0f) 606 { 607 enemy->contactTime = 0.0f; 608 } 609
610 float enemyRadius = enemyClassConfigs[enemy->enemyType].radius; 611 // linear search over towers; could be optimized by using path finding tower map, 612 // but for now, we keep it simple 613 for (int j = 0; j < towerCount; j++) 614 { 615 Tower *tower = &towers[j]; 616 if (tower->towerType == TOWER_TYPE_NONE) 617 { 618 continue; 619 } 620 float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y}); 621 float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1 622 if (distanceSqr > combinedRadius * combinedRadius) 623 { 624 continue; 625 } 626 // potential collision; square / circle intersection 627 float dx = tower->x - enemy->simPosition.x; 628 float dy = tower->y - enemy->simPosition.y; 629 float absDx = fabsf(dx); 630 float absDy = fabsf(dy); 631 if (absDx <= 0.5f && absDx <= absDy) { 632 // vertical collision; push the enemy out horizontally 633 float overlap = enemyRadius + 0.5f - absDy; 634 if (overlap < 0.0f) 635 { 636 continue; 637 } 638 float direction = dy > 0.0f ? -1.0f : 1.0f; 639 enemy->simPosition.y += direction * overlap; 640 } 641 else if (absDy <= 0.5f && absDy <= absDx) 642 { 643 // horizontal collision; push the enemy out vertically 644 float overlap = enemyRadius + 0.5f - absDx; 645 if (overlap < 0.0f) 646 { 647 continue; 648 } 649 float direction = dx > 0.0f ? -1.0f : 1.0f; 650 enemy->simPosition.x += direction * overlap; 651 } 652 else 653 { 654 // possible collision with a corner 655 float cornerDX = dx > 0.0f ? -0.5f : 0.5f; 656 float cornerDY = dy > 0.0f ? -0.5f : 0.5f; 657 float cornerX = tower->x + cornerDX; 658 float cornerY = tower->y + cornerDY; 659 float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY}); 660 if (cornerDistanceSqr > enemyRadius * enemyRadius) 661 { 662 continue; 663 } 664 // push the enemy out along the diagonal 665 float cornerDistance = sqrtf(cornerDistanceSqr); 666 float overlap = enemyRadius - cornerDistance; 667 float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX; 668 float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY; 669 enemy->simPosition.x -= directionX * overlap; 670 enemy->simPosition.y -= directionY * overlap; 671 } 672
673 if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f) 674 { 675 enemy->contactTime += gameTime.deltaTime * 2.0f; // * 2 to undo the subtraction above 676 if (enemy->contactTime >= enemyClassConfigs[enemy->enemyType].requiredContactTime)
677 {
678 EnemyTriggerExplode(enemy, tower); 679 }
680 } 681 } 682 } 683 } 684 685 EnemyId EnemyGetId(Enemy *enemy) 686 { 687 return (EnemyId){enemy - enemies, enemy->generation}; 688 } 689 690 Enemy *EnemyTryResolve(EnemyId enemyId) 691 { 692 if (enemyId.index >= ENEMY_MAX_COUNT) 693 { 694 return 0; 695 } 696 Enemy *enemy = &enemies[enemyId.index]; 697 if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE) 698 { 699 return 0; 700 } 701 return enemy; 702 } 703 704 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY) 705 { 706 Enemy *spawn = 0; 707 for (int i = 0; i < enemyCount; i++) 708 { 709 Enemy *enemy = &enemies[i]; 710 if (enemy->enemyType == ENEMY_TYPE_NONE) 711 { 712 spawn = enemy; 713 break; 714 } 715 } 716 717 if (enemyCount < ENEMY_MAX_COUNT && !spawn) 718 { 719 spawn = &enemies[enemyCount++]; 720 } 721 722 if (spawn) 723 { 724 spawn->currentX = currentX; 725 spawn->currentY = currentY; 726 spawn->nextX = currentX; 727 spawn->nextY = currentY; 728 spawn->simPosition = (Vector2){currentX, currentY}; 729 spawn->simVelocity = (Vector2){0, 0}; 730 spawn->enemyType = enemyType; 731 spawn->startMovingTime = gameTime.time; 732 spawn->damage = 0.0f; 733 spawn->futureDamage = 0.0f; 734 spawn->generation++; 735 spawn->movePathCount = 0; 736 } 737 738 return spawn; 739 } 740 741 int EnemyAddDamage(Enemy *enemy, float damage) 742 { 743 enemy->damage += damage; 744 if (enemy->damage >= EnemyGetMaxHealth(enemy)) 745 { 746 enemy->enemyType = ENEMY_TYPE_NONE; 747 return 1; 748 } 749 750 return 0; 751 } 752 753 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range) 754 { 755 int16_t castleX = 0; 756 int16_t castleY = 0; 757 Enemy* closest = 0; 758 int16_t closestDistance = 0; 759 float range2 = range * range; 760 for (int i = 0; i < enemyCount; i++) 761 { 762 Enemy* enemy = &enemies[i]; 763 if (enemy->enemyType == ENEMY_TYPE_NONE) 764 { 765 continue; 766 } 767 float maxHealth = EnemyGetMaxHealth(enemy); 768 if (enemy->futureDamage >= maxHealth) 769 { 770 // ignore enemies that will die soon 771 continue; 772 } 773 int16_t dx = castleX - enemy->currentX; 774 int16_t dy = castleY - enemy->currentY; 775 int16_t distance = abs(dx) + abs(dy); 776 if (!closest || distance < closestDistance) 777 { 778 float tdx = towerX - enemy->currentX; 779 float tdy = towerY - enemy->currentY; 780 float tdistance2 = tdx * tdx + tdy * tdy; 781 if (tdistance2 <= range2) 782 { 783 closest = enemy; 784 closestDistance = distance; 785 } 786 } 787 } 788 return closest; 789 } 790 791 int EnemyCount() 792 { 793 int count = 0; 794 for (int i = 0; i < enemyCount; i++) 795 { 796 if (enemies[i].enemyType != ENEMY_TYPE_NONE) 797 { 798 count++; 799 } 800 } 801 return count; 802 } 803 804 //# Projectiles 805 #define PROJECTILE_MAX_COUNT 1200 806 #define PROJECTILE_TYPE_NONE 0 807 #define PROJECTILE_TYPE_BULLET 1 808 809 typedef struct Projectile 810 { 811 uint8_t projectileType; 812 float shootTime; 813 float arrivalTime; 814 float damage; 815 Vector2 position; 816 Vector2 target; 817 Vector2 directionNormal; 818 EnemyId targetEnemy; 819 } Projectile; 820 821 Projectile projectiles[PROJECTILE_MAX_COUNT]; 822 int projectileCount = 0; 823 824 void ProjectileInit() 825 { 826 for (int i = 0; i < PROJECTILE_MAX_COUNT; i++) 827 { 828 projectiles[i] = (Projectile){0}; 829 } 830 } 831 832 void ProjectileDraw() 833 { 834 for (int i = 0; i < projectileCount; i++) 835 { 836 Projectile projectile = projectiles[i]; 837 if (projectile.projectileType == PROJECTILE_TYPE_NONE) 838 { 839 continue; 840 } 841 float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime); 842 if (transition >= 1.0f) 843 { 844 continue; 845 } 846 Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition); 847 float x = position.x; 848 float y = position.y; 849 float dx = projectile.directionNormal.x; 850 float dy = projectile.directionNormal.y; 851 for (float d = 1.0f; d > 0.0f; d -= 0.25f) 852 { 853 x -= dx * 0.1f; 854 y -= dy * 0.1f; 855 float size = 0.1f * d; 856 DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED); 857 } 858 } 859 } 860 861 void ProjectileUpdate() 862 { 863 for (int i = 0; i < projectileCount; i++) 864 { 865 Projectile *projectile = &projectiles[i]; 866 if (projectile->projectileType == PROJECTILE_TYPE_NONE) 867 { 868 continue; 869 } 870 float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime); 871 if (transition >= 1.0f) 872 { 873 projectile->projectileType = PROJECTILE_TYPE_NONE; 874 Enemy *enemy = EnemyTryResolve(projectile->targetEnemy); 875 if (enemy) 876 { 877 EnemyAddDamage(enemy, projectile->damage); 878 } 879 continue; 880 } 881 } 882 } 883 884 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage) 885 { 886 for (int i = 0; i < PROJECTILE_MAX_COUNT; i++) 887 { 888 Projectile *projectile = &projectiles[i]; 889 if (projectile->projectileType == PROJECTILE_TYPE_NONE) 890 { 891 projectile->projectileType = projectileType; 892 projectile->shootTime = gameTime.time; 893 projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed; 894 projectile->damage = damage; 895 projectile->position = position; 896 projectile->target = target; 897 projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position)); 898 projectile->targetEnemy = EnemyGetId(enemy); 899 projectileCount = projectileCount <= i ? i + 1 : projectileCount; 900 return projectile; 901 } 902 } 903 return 0; 904 } 905 906 //# Towers 907 908 void TowerInit() 909 { 910 for (int i = 0; i < TOWER_MAX_COUNT; i++) 911 { 912 towers[i] = (Tower){0}; 913 } 914 towerCount = 0; 915 } 916 917 Tower *TowerGetAt(int16_t x, int16_t y) 918 { 919 for (int i = 0; i < towerCount; i++) 920 { 921 if (towers[i].x == x && towers[i].y == y) 922 { 923 return &towers[i]; 924 } 925 } 926 return 0; 927 } 928 929 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y) 930 { 931 if (towerCount >= TOWER_MAX_COUNT) 932 { 933 return 0; 934 } 935 936 Tower *tower = TowerGetAt(x, y); 937 if (tower) 938 { 939 return 0; 940 } 941 942 tower = &towers[towerCount++]; 943 tower->x = x; 944 tower->y = y; 945 tower->towerType = towerType; 946 tower->cooldown = 0.0f; 947 tower->damage = 0.0f; 948 return tower; 949 } 950 951 float TowerGetMaxHealth(Tower *tower) 952 { 953 switch (tower->towerType) 954 { 955 case TOWER_TYPE_BASE: 956 return 10.0f; 957 case TOWER_TYPE_GUN: 958 return 3.0f; 959 case TOWER_TYPE_WALL: 960 return 5.0f; 961 } 962 return 0.0f; 963 } 964 965 void TowerDraw() 966 { 967 for (int i = 0; i < towerCount; i++) 968 { 969 Tower tower = towers[i]; 970 DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY); 971 switch (tower.towerType) 972 { 973 case TOWER_TYPE_BASE: 974 DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON); 975 break; 976 case TOWER_TYPE_GUN: 977 DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE); 978 break; 979 case TOWER_TYPE_WALL: 980 DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY); 981 break; 982 } 983 } 984 } 985 986 void TowerGunUpdate(Tower *tower) 987 { 988 if (tower->cooldown <= 0) 989 { 990 Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f); 991 if (enemy) 992 { 993 tower->cooldown = 0.125f; 994 // shoot the enemy; determine future position of the enemy 995 float bulletSpeed = 1.0f; 996 float bulletDamage = 3.0f; 997 Vector2 velocity = enemy->simVelocity; 998 Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0); 999 Vector2 towerPosition = {tower->x, tower->y}; 1000 float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed; 1001 for (int i = 0; i < 8; i++) { 1002 velocity = enemy->simVelocity; 1003 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0); 1004 float distance = Vector2Distance(towerPosition, futurePosition); 1005 float eta2 = distance / bulletSpeed; 1006 if (fabs(eta - eta2) < 0.01f) { 1007 break; 1008 } 1009 eta = (eta2 + eta) * 0.5f; 1010 } 1011 ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, towerPosition, futurePosition, 1012 bulletSpeed, bulletDamage); 1013 enemy->futureDamage += bulletDamage; 1014 } 1015 } 1016 else 1017 { 1018 tower->cooldown -= gameTime.deltaTime; 1019 } 1020 } 1021 1022 void TowerUpdate() 1023 { 1024 for (int i = 0; i < towerCount; i++) 1025 { 1026 Tower *tower = &towers[i]; 1027 switch (tower->towerType) 1028 { 1029 case TOWER_TYPE_GUN: 1030 TowerGunUpdate(tower); 1031 break; 1032 } 1033 } 1034 } 1035 1036 //# Game 1037 1038 float nextSpawnTime = 0.0f; 1039 1040 void InitGame() 1041 { 1042 TowerInit(); 1043 EnemyInit(); 1044 ProjectileInit(); 1045 PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f); 1046 1047 TowerTryAdd(TOWER_TYPE_BASE, 0, 0); 1048 TowerTryAdd(TOWER_TYPE_GUN, 2, 0); 1049 1050 for (int i = -2; i <= 2; i += 1) 1051 { 1052 TowerTryAdd(TOWER_TYPE_WALL, i, 2); 1053 TowerTryAdd(TOWER_TYPE_WALL, i, -2); 1054 TowerTryAdd(TOWER_TYPE_WALL, -2, i); 1055 } 1056 1057 EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4); 1058 } 1059 1060 void GameUpdate() 1061 { 1062 float dt = GetFrameTime(); 1063 // cap maximum delta time to 0.1 seconds to prevent large time steps 1064 if (dt > 0.1f) dt = 0.1f; 1065 gameTime.time += dt; 1066 gameTime.deltaTime = dt; 1067 PathFindingMapUpdate(); 1068 EnemyUpdate(); 1069 TowerUpdate(); 1070 ProjectileUpdate(); 1071 1072 // spawn a new enemy every second 1073 if (gameTime.time >= nextSpawnTime && EnemyCount() < 50) 1074 { 1075 nextSpawnTime = gameTime.time + 0.2f; 1076 // add a new enemy at the boundary of the map 1077 int randValue = GetRandomValue(-5, 5); 1078 int randSide = GetRandomValue(0, 3); 1079 int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue; 1080 int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue; 1081 static int alternation = 0; 1082 alternation += 1; 1083 if (alternation % 3 == 0) { 1084 EnemyTryAdd(ENEMY_TYPE_MINION, 0, -5); 1085 } 1086 else if (alternation % 3 == 1) 1087 { 1088 EnemyTryAdd(ENEMY_TYPE_MINION, 0, 5); 1089 } 1090 EnemyTryAdd(ENEMY_TYPE_MINION, x, y); 1091 } 1092 } 1093 1094 int main(void) 1095 { 1096 int screenWidth, screenHeight; 1097 GetPreferredSize(&screenWidth, &screenHeight); 1098 InitWindow(screenWidth, screenHeight, "Tower defense"); 1099 SetTargetFPS(30); 1100 1101 Camera3D camera = {0}; 1102 camera.position = (Vector3){0.0f, 10.0f, -0.5f}; 1103 camera.target = (Vector3){0.0f, 0.0f, -0.5f}; 1104 camera.up = (Vector3){0.0f, 0.0f, -1.0f}; 1105 camera.fovy = 12.0f; 1106 camera.projection = CAMERA_ORTHOGRAPHIC; 1107 1108 InitGame(); 1109 1110 while (!WindowShouldClose()) 1111 { 1112 if (IsPaused()) { 1113 // canvas is not visible in browser - do nothing 1114 continue; 1115 } 1116 BeginDrawing(); 1117 ClearBackground(DARKBLUE); 1118 1119 BeginMode3D(camera); 1120 DrawGrid(10, 1.0f); 1121 TowerDraw(); 1122 EnemyDraw(); 1123 ProjectileDraw(); 1124 PathFindingMapDraw(); 1125 GameUpdate(); 1126 EndMode3D(); 1127 1128 const char *title = "Tower defense tutorial"; 1129 int titleWidth = MeasureText(title, 20); 1130 DrawText(title, (GetScreenWidth() - titleWidth) * 0.5f + 2, 5 + 2, 20, BLACK); 1131 DrawText(title, (GetScreenWidth() - titleWidth) * 0.5f, 5, 20, WHITE); 1132 EndDrawing(); 1133 } 1134 1135 CloseWindow(); 1136 1137 return 0; 1138 }
  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 #endif
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif

Here's how the pushback works:

  1 // push back enemies & dealing damage
  2 for (int i = 0; i < enemyCount; i++)
  3 {
  4   Enemy *other = &enemies[i];
  5   if (other->enemyType == ENEMY_TYPE_NONE)
  6   {
  7     continue;
  8   }
  9   float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition);
 10   if (distanceSqr > 0 && distanceSqr < explosionRange2)
 11   {
 12     Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition));
 13     other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower));
 14     EnemyAddDamage(other, explosionDamge);
 15   }
 16 }

The pushback is calculated by the distance between the enemy and the other enemy. If the distance is less than the explosion range, the other enemy is pushed back in the direction of the explosion. Like in other case, we avoid here to calculate the square root of the distance by comparing the squared distance to the squared explosion range. This is a common optimization in games, as the square root is a relatively expensive operation.

Now it would be nice to have some feedback on the damage dealt. A small explosion effect would be nice. A simple particle system would be nice to have as well, so let's add that next. Nothing fancy... just something simple that gets the job done. The definition of the particle system is in line 8 and from line 48 on, the implementation starts. From line 1160 on, The integration starts. There are also a few lines in between to spawn the particles when an enemy explodes.

  • 💾
  1 #include "td-tut-2-main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 //# Declarations
  7 
8 #define PARTICLE_MAX_COUNT 400 9 #define PARTICLE_TYPE_NONE 0 10 #define PARTICLE_TYPE_EXPLOSION 1 11 12 typedef struct Particle 13 { 14 uint8_t particleType; 15 float spawnTime; 16 float lifetime; 17 Vector3 position; 18 Vector3 velocity; 19 } Particle; 20 21 Particle particles[PARTICLE_MAX_COUNT]; 22 int particleCount = 0; 23
24 #define TOWER_MAX_COUNT 400 25 #define TOWER_TYPE_NONE 0 26 #define TOWER_TYPE_BASE 1 27 #define TOWER_TYPE_GUN 2 28 #define TOWER_TYPE_WALL 3 29 30 typedef struct Tower 31 { 32 int16_t x, y; 33 uint8_t towerType; 34 float cooldown; 35 float damage; 36 } Tower; 37 38 typedef struct GameTime 39 { 40 float time; 41 float deltaTime; 42 } GameTime; 43 44 GameTime gameTime = {0}; 45 46 Tower towers[TOWER_MAX_COUNT]; 47 int towerCount = 0; 48
49 float TowerGetMaxHealth(Tower *tower); 50 51 //# Particle system 52 53 void ParticleInit() 54 { 55 for (int i = 0; i < PARTICLE_MAX_COUNT; i++) 56 { 57 particles[i] = (Particle){0}; 58 } 59 particleCount = 0; 60 } 61 62 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime) 63 { 64 if (particleCount >= PARTICLE_MAX_COUNT) 65 { 66 return; 67 } 68 69 int index = -1; 70 for (int i = 0; i < particleCount; i++) 71 { 72 if (particles[i].particleType == PARTICLE_TYPE_NONE) 73 { 74 index = i; 75 break; 76 } 77 } 78 79 if (index == -1) 80 { 81 index = particleCount++; 82 } 83 84 Particle *particle = &particles[index]; 85 particle->particleType = particleType; 86 particle->spawnTime = gameTime.time; 87 particle->lifetime = lifetime; 88 particle->position = position; 89 particle->velocity = velocity; 90 } 91 92 void ParticleUpdate() 93 { 94 for (int i = 0; i < particleCount; i++) 95 { 96 Particle *particle = &particles[i]; 97 if (particle->particleType == PARTICLE_TYPE_NONE) 98 { 99 continue; 100 } 101 102 float age = gameTime.time - particle->spawnTime; 103 104 if (particle->lifetime > age) 105 { 106 particle->position = Vector3Add(particle->position, Vector3Scale(particle->velocity, gameTime.deltaTime)); 107 } 108 else { 109 particle->particleType = PARTICLE_TYPE_NONE; 110 } 111 } 112 } 113 114 void DrawExplosionParticle(Particle *particle, float transition) 115 { 116 float size = 1.2f * (1.0f - transition); 117 Color startColor = WHITE; 118 Color endColor = RED; 119 Color color = ColorLerp(startColor, endColor, transition); 120 DrawCube(particle->position, size, size, size, color); 121 } 122 123 void ParticleDraw() 124 { 125 for (int i = 0; i < particleCount; i++) 126 { 127 Particle particle = particles[i]; 128 if (particle.particleType == PARTICLE_TYPE_NONE) 129 { 130 continue; 131 } 132 133 float age = gameTime.time - particle.spawnTime; 134 float transition = age / particle.lifetime; 135 switch (particle.particleType) 136 { 137 case PARTICLE_TYPE_EXPLOSION: 138 DrawExplosionParticle(&particle, transition); 139 break; 140 default: 141 DrawCube(particle.position, 0.3f, 0.5f, 0.3f, RED); 142 break; 143 } 144 } 145 }
146 147 //# Pathfinding map 148 typedef struct DeltaSrc 149 { 150 char x, y; 151 } DeltaSrc; 152 153 typedef struct PathfindingMap 154 { 155 int width, height; 156 float scale; 157 float *distances; 158 long *towerIndex; 159 DeltaSrc *deltaSrc; 160 float maxDistance; 161 Matrix toMapSpace; 162 Matrix toWorldSpace; 163 } PathfindingMap; 164 165 // when we execute the pathfinding algorithm, we need to store the active nodes 166 // in a queue. Each node has a position, a distance from the start, and the 167 // position of the node that we came from. 168 typedef struct PathfindingNode 169 { 170 int16_t x, y, fromX, fromY; 171 float distance; 172 } PathfindingNode; 173 174 // The queue is a simple array of nodes, we add nodes to the end and remove 175 // nodes from the front. We keep the array around to avoid unnecessary allocations 176 static PathfindingNode *pathfindingNodeQueue = 0; 177 static int pathfindingNodeQueueCount = 0; 178 static int pathfindingNodeQueueCapacity = 0; 179 180 // The pathfinding map stores the distances from the castle to each cell in the map. 181 PathfindingMap pathfindingMap = {0}; 182 183 void PathfindingMapInit(int width, int height, Vector3 translate, float scale) 184 { 185 // transforming between map space and world space allows us to adapt 186 // position and scale of the map without changing the pathfinding data 187 pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z); 188 pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale)); 189 pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace); 190 pathfindingMap.width = width; 191 pathfindingMap.height = height; 192 pathfindingMap.scale = scale; 193 pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float)); 194 for (int i = 0; i < width * height; i++) 195 { 196 pathfindingMap.distances[i] = -1.0f; 197 } 198 199 pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long)); 200 pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc)); 201 } 202 203 float PathFindingGetDistance(int mapX, int mapY) 204 { 205 if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height) 206 { 207 // when outside the map, we return the manhattan distance to the castle (0,0) 208 return fabsf((float)mapX) + fabsf((float)mapY); 209 } 210 211 return pathfindingMap.distances[mapY * pathfindingMap.width + mapX]; 212 } 213 214 void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance) 215 { 216 if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity) 217 { 218 pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2; 219 // we use MemAlloc/MemRealloc to allocate memory for the queue 220 // I am not entirely sure if MemRealloc allows passing a null pointer 221 // so we check if the pointer is null and use MemAlloc in that case 222 if (pathfindingNodeQueue == 0) 223 { 224 pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode)); 225 } 226 else 227 { 228 pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode)); 229 } 230 } 231 232 PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++]; 233 node->x = x; 234 node->y = y; 235 node->fromX = fromX; 236 node->fromY = fromY; 237 node->distance = distance; 238 } 239 240 PathfindingNode *PathFindingNodePop() 241 { 242 if (pathfindingNodeQueueCount == 0) 243 { 244 return 0; 245 } 246 // we return the first node in the queue; we want to return a pointer to the node 247 // so we can return 0 if the queue is empty. 248 // We should _not_ return a pointer to the element in the list, because the list 249 // may be reallocated and the pointer would become invalid. Or the 250 // popped element is overwritten by the next push operation. 251 // Using static here means that the variable is permanently allocated. 252 static PathfindingNode node; 253 node = pathfindingNodeQueue[0]; 254 // we shift all nodes one position to the front 255 for (int i = 1; i < pathfindingNodeQueueCount; i++) 256 { 257 pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i]; 258 } 259 --pathfindingNodeQueueCount; 260 return &node; 261 } 262 263 // transform a world position to a map position in the array; 264 // returns true if the position is inside the map 265 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY) 266 { 267 Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace); 268 *mapX = (int16_t)mapPosition.x; 269 *mapY = (int16_t)mapPosition.z; 270 return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height; 271 } 272 273 void PathFindingMapUpdate() 274 { 275 const int castleX = 0, castleY = 0; 276 int16_t castleMapX, castleMapY; 277 if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY)) 278 { 279 return; 280 } 281 int width = pathfindingMap.width, height = pathfindingMap.height; 282 283 // reset the distances to -1 284 for (int i = 0; i < width * height; i++) 285 { 286 pathfindingMap.distances[i] = -1.0f; 287 } 288 // reset the tower indices 289 for (int i = 0; i < width * height; i++) 290 { 291 pathfindingMap.towerIndex[i] = -1; 292 } 293 // reset the delta src 294 for (int i = 0; i < width * height; i++) 295 { 296 pathfindingMap.deltaSrc[i].x = 0; 297 pathfindingMap.deltaSrc[i].y = 0; 298 } 299 300 for (int i = 0; i < towerCount; i++) 301 { 302 Tower *tower = &towers[i]; 303 if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE) 304 { 305 continue; 306 } 307 int16_t mapX, mapY; 308 // technically, if the tower cell scale is not in sync with the pathfinding map scale, 309 // this would not work correctly and needs to be refined to allow towers covering multiple cells 310 // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly 311 // one cell. For now. 312 if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY)) 313 { 314 continue; 315 } 316 int index = mapY * width + mapX; 317 pathfindingMap.towerIndex[index] = i; 318 } 319 320 // we start at the castle and add the castle to the queue 321 pathfindingMap.maxDistance = 0.0f; 322 pathfindingNodeQueueCount = 0; 323 PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f); 324 PathfindingNode *node = 0; 325 while ((node = PathFindingNodePop())) 326 { 327 if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height) 328 { 329 continue; 330 } 331 int index = node->y * width + node->x; 332 if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance) 333 { 334 continue; 335 } 336 337 int deltaX = node->x - node->fromX; 338 int deltaY = node->y - node->fromY; 339 // even if the cell is blocked by a tower, we still may want to store the direction 340 // (though this might not be needed, IDK right now) 341 pathfindingMap.deltaSrc[index].x = (char) deltaX; 342 pathfindingMap.deltaSrc[index].y = (char) deltaY; 343 344 // we skip nodes that are blocked by towers 345 if (pathfindingMap.towerIndex[index] >= 0) 346 { 347 node->distance += 8.0f; 348 } 349 pathfindingMap.distances[index] = node->distance; 350 pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance); 351 PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f); 352 PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f); 353 PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f); 354 PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f); 355 } 356 } 357 358 void PathFindingMapDraw() 359 { 360 float cellSize = pathfindingMap.scale * 0.9f; 361 float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance); 362 for (int x = 0; x < pathfindingMap.width; x++) 363 { 364 for (int y = 0; y < pathfindingMap.height; y++) 365 { 366 float distance = pathfindingMap.distances[y * pathfindingMap.width + x]; 367 float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f); 368 Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255}; 369 Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace); 370 // animate the distance "wave" to show how the pathfinding algorithm expands 371 // from the castle 372 if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance) 373 { 374 color = BLACK; 375 } 376 DrawCube(position, cellSize, 0.1f, cellSize, color); 377 } 378 } 379 } 380 381 Vector2 PathFindingGetGradient(Vector3 world) 382 { 383 int16_t mapX, mapY; 384 if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY)) 385 { 386 DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX]; 387 return (Vector2){(float)-delta.x, (float)-delta.y}; 388 } 389 // fallback to a simple gradient calculation 390 float n = PathFindingGetDistance(mapX, mapY - 1); 391 float s = PathFindingGetDistance(mapX, mapY + 1); 392 float w = PathFindingGetDistance(mapX - 1, mapY); 393 float e = PathFindingGetDistance(mapX + 1, mapY); 394 return (Vector2){w - e + 0.25f, n - s + 0.125f}; 395 } 396 397 //# Enemies 398 399 #define ENEMY_MAX_PATH_COUNT 8 400 #define ENEMY_MAX_COUNT 400 401 #define ENEMY_TYPE_NONE 0 402 #define ENEMY_TYPE_MINION 1 403 404 typedef struct EnemyId 405 { 406 uint16_t index; 407 uint16_t generation; 408 } EnemyId; 409 410 typedef struct EnemyClassConfig 411 { 412 float speed; 413 float health; 414 float radius; 415 float maxAcceleration; 416 float requiredContactTime; 417 float explosionDamage; 418 float explosionRange; 419 float explosionPushbackPower; 420 } EnemyClassConfig; 421 422 typedef struct Enemy 423 { 424 int16_t currentX, currentY; 425 int16_t nextX, nextY; 426 Vector2 simPosition; 427 Vector2 simVelocity; 428 uint16_t generation; 429 float startMovingTime; 430 float damage, futureDamage; 431 float contactTime; 432 uint8_t enemyType; 433 uint8_t movePathCount; 434 Vector2 movePath[ENEMY_MAX_PATH_COUNT]; 435 } Enemy; 436 437 Enemy enemies[ENEMY_MAX_COUNT]; 438 int enemyCount = 0; 439 440 EnemyClassConfig enemyClassConfigs[] = { 441 [ENEMY_TYPE_MINION] = { 442 .health = 3.0f, 443 .speed = 1.0f, 444 .radius = 0.25f, 445 .maxAcceleration = 1.0f, 446 .explosionDamage = 1.0f, 447 .requiredContactTime = 0.5f, 448 .explosionRange = 1.0f, 449 .explosionPushbackPower = 0.25f, 450 }, 451 }; 452 453 int EnemyAddDamage(Enemy *enemy, float damage); 454 455 void EnemyInit() 456 { 457 for (int i = 0; i < ENEMY_MAX_COUNT; i++) 458 { 459 enemies[i] = (Enemy){0}; 460 } 461 enemyCount = 0; 462 } 463 464 float EnemyGetCurrentMaxSpeed(Enemy *enemy) 465 { 466 return enemyClassConfigs[enemy->enemyType].speed; 467 } 468 469 float EnemyGetMaxHealth(Enemy *enemy) 470 { 471 return enemyClassConfigs[enemy->enemyType].health; 472 } 473 474 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY) 475 { 476 int16_t castleX = 0; 477 int16_t castleY = 0; 478 int16_t dx = castleX - currentX; 479 int16_t dy = castleY - currentY; 480 if (dx == 0 && dy == 0) 481 { 482 *nextX = currentX; 483 *nextY = currentY; 484 return 1; 485 } 486 Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY}); 487 488 if (gradient.x == 0 && gradient.y == 0) 489 { 490 *nextX = currentX; 491 *nextY = currentY; 492 return 1; 493 } 494 495 if (fabsf(gradient.x) > fabsf(gradient.y)) 496 { 497 *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1); 498 *nextY = currentY; 499 return 0; 500 } 501 *nextX = currentX; 502 *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1); 503 return 0; 504 } 505 506 507 // this function predicts the movement of the unit for the next deltaT seconds 508 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount) 509 { 510 const float pointReachedDistance = 0.25f; 511 const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance; 512 const float maxSimStepTime = 0.015625f; 513 514 float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration; 515 float maxSpeed = EnemyGetCurrentMaxSpeed(enemy); 516 int16_t nextX = enemy->nextX; 517 int16_t nextY = enemy->nextY; 518 Vector2 position = enemy->simPosition; 519 int passedCount = 0; 520 for (float t = 0.0f; t < deltaT; t += maxSimStepTime) 521 { 522 float stepTime = fminf(deltaT - t, maxSimStepTime); 523 Vector2 target = (Vector2){nextX, nextY}; 524 float speed = Vector2Length(*velocity); 525 // draw the target position for debugging 526 DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED); 527 Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed)); 528 if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2) 529 { 530 // we reached the target position, let's move to the next waypoint 531 EnemyGetNextPosition(nextX, nextY, &nextX, &nextY); 532 target = (Vector2){nextX, nextY}; 533 // track how many waypoints we passed 534 passedCount++; 535 } 536 537 // acceleration towards the target 538 Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos)); 539 Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime); 540 *velocity = Vector2Add(*velocity, acceleration); 541 542 // limit the speed to the maximum speed 543 if (speed > maxSpeed) 544 { 545 *velocity = Vector2Scale(*velocity, maxSpeed / speed); 546 } 547 548 // move the enemy 549 position = Vector2Add(position, Vector2Scale(*velocity, stepTime)); 550 } 551 552 if (waypointPassedCount) 553 { 554 (*waypointPassedCount) = passedCount; 555 } 556 557 return position; 558 } 559 560 void EnemyDraw() 561 { 562 for (int i = 0; i < enemyCount; i++) 563 { 564 Enemy enemy = enemies[i]; 565 if (enemy.enemyType == ENEMY_TYPE_NONE) 566 { 567 continue; 568 } 569 570 Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0); 571 572 if (enemy.movePathCount > 0) 573 { 574 Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y}; 575 DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN); 576 } 577 for (int j = 1; j < enemy.movePathCount; j++) 578 { 579 Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y}; 580 Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y}; 581 DrawLine3D(p, q, GREEN); 582 } 583 584 switch (enemy.enemyType) 585 { 586 case ENEMY_TYPE_MINION: 587 DrawCubeWires((Vector3){position.x, 0.2f, position.y}, 0.4f, 0.4f, 0.4f, GREEN); 588 break; 589 } 590 } 591 } 592
593 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource)
594 { 595 // damage the tower 596 float explosionDamge = enemyClassConfigs[enemy->enemyType].explosionDamage; 597 float explosionRange = enemyClassConfigs[enemy->enemyType].explosionRange; 598 float explosionPushbackPower = enemyClassConfigs[enemy->enemyType].explosionPushbackPower; 599 float explosionRange2 = explosionRange * explosionRange; 600 tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage; 601 // explode the enemy 602 if (tower->damage >= TowerGetMaxHealth(tower)) 603 { 604 tower->towerType = TOWER_TYPE_NONE;
605 } 606 607 ParticleAdd(PARTICLE_TYPE_EXPLOSION, 608 explosionSource, 609 (Vector3){0, 0.1f, 0}, 1.0f);
610 611 enemy->enemyType = ENEMY_TYPE_NONE; 612 613 // push back enemies & dealing damage 614 for (int i = 0; i < enemyCount; i++) 615 { 616 Enemy *other = &enemies[i]; 617 if (other->enemyType == ENEMY_TYPE_NONE) 618 { 619 continue; 620 } 621 float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition); 622 if (distanceSqr > 0 && distanceSqr < explosionRange2) 623 { 624 Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition)); 625 other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower)); 626 EnemyAddDamage(other, explosionDamge); 627 } 628 } 629 } 630 631 void EnemyUpdate() 632 { 633 const float castleX = 0; 634 const float castleY = 0; 635 const float maxPathDistance2 = 0.25f * 0.25f; 636 637 for (int i = 0; i < enemyCount; i++) 638 { 639 Enemy *enemy = &enemies[i]; 640 if (enemy->enemyType == ENEMY_TYPE_NONE) 641 { 642 continue; 643 } 644 645 int waypointPassedCount = 0; 646 enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount); 647 enemy->startMovingTime = gameTime.time; 648 // track path of unit 649 if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2) 650 { 651 for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--) 652 { 653 enemy->movePath[j] = enemy->movePath[j - 1]; 654 } 655 enemy->movePath[0] = enemy->simPosition; 656 if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT) 657 { 658 enemy->movePathCount = ENEMY_MAX_PATH_COUNT; 659 } 660 } 661 662 if (waypointPassedCount > 0) 663 { 664 enemy->currentX = enemy->nextX; 665 enemy->currentY = enemy->nextY; 666 if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) && 667 Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f) 668 { 669 // enemy reached the castle; remove it 670 enemy->enemyType = ENEMY_TYPE_NONE; 671 continue; 672 } 673 } 674 } 675 676 // handle collisions between enemies 677 for (int i = 0; i < enemyCount - 1; i++) 678 { 679 Enemy *enemyA = &enemies[i]; 680 if (enemyA->enemyType == ENEMY_TYPE_NONE) 681 { 682 continue; 683 } 684 for (int j = i + 1; j < enemyCount; j++) 685 { 686 Enemy *enemyB = &enemies[j]; 687 if (enemyB->enemyType == ENEMY_TYPE_NONE) 688 { 689 continue; 690 } 691 float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition); 692 float radiusA = enemyClassConfigs[enemyA->enemyType].radius; 693 float radiusB = enemyClassConfigs[enemyB->enemyType].radius; 694 float radiusSum = radiusA + radiusB; 695 if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f) 696 { 697 // collision 698 float distance = sqrtf(distanceSqr); 699 float overlap = radiusSum - distance; 700 // move the enemies apart, but softly; if we have a clog of enemies, 701 // moving them perfectly apart can cause them to jitter 702 float positionCorrection = overlap / 5.0f; 703 Vector2 direction = (Vector2){ 704 (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection, 705 (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection}; 706 enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction); 707 enemyB->simPosition = Vector2Add(enemyB->simPosition, direction); 708 } 709 } 710 } 711 712 // handle collisions between enemies and towers 713 for (int i = 0; i < enemyCount; i++) 714 { 715 Enemy *enemy = &enemies[i]; 716 if (enemy->enemyType == ENEMY_TYPE_NONE) 717 { 718 continue; 719 } 720 enemy->contactTime -= gameTime.deltaTime; 721 if (enemy->contactTime < 0.0f) 722 { 723 enemy->contactTime = 0.0f; 724 } 725 726 float enemyRadius = enemyClassConfigs[enemy->enemyType].radius; 727 // linear search over towers; could be optimized by using path finding tower map, 728 // but for now, we keep it simple 729 for (int j = 0; j < towerCount; j++) 730 { 731 Tower *tower = &towers[j]; 732 if (tower->towerType == TOWER_TYPE_NONE) 733 { 734 continue; 735 } 736 float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y}); 737 float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1 738 if (distanceSqr > combinedRadius * combinedRadius) 739 { 740 continue; 741 } 742 // potential collision; square / circle intersection 743 float dx = tower->x - enemy->simPosition.x; 744 float dy = tower->y - enemy->simPosition.y; 745 float absDx = fabsf(dx);
746 float absDy = fabsf(dy); 747 Vector3 contactPoint = {0};
748 if (absDx <= 0.5f && absDx <= absDy) { 749 // vertical collision; push the enemy out horizontally 750 float overlap = enemyRadius + 0.5f - absDy; 751 if (overlap < 0.0f) 752 { 753 continue; 754 } 755 float direction = dy > 0.0f ? -1.0f : 1.0f;
756 enemy->simPosition.y += direction * overlap; 757 contactPoint = (Vector3){enemy->simPosition.x, 0.2f, tower->x + direction * 0.5f};
758 } 759 else if (absDy <= 0.5f && absDy <= absDx) 760 { 761 // horizontal collision; push the enemy out vertically 762 float overlap = enemyRadius + 0.5f - absDx; 763 if (overlap < 0.0f) 764 { 765 continue; 766 } 767 float direction = dx > 0.0f ? -1.0f : 1.0f;
768 enemy->simPosition.x += direction * overlap; 769 contactPoint = (Vector3){tower->x + direction * 0.5f, 0.2f, enemy->simPosition.y};
770 } 771 else 772 { 773 // possible collision with a corner 774 float cornerDX = dx > 0.0f ? -0.5f : 0.5f; 775 float cornerDY = dy > 0.0f ? -0.5f : 0.5f; 776 float cornerX = tower->x + cornerDX; 777 float cornerY = tower->y + cornerDY; 778 float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY}); 779 if (cornerDistanceSqr > enemyRadius * enemyRadius) 780 { 781 continue; 782 } 783 // push the enemy out along the diagonal 784 float cornerDistance = sqrtf(cornerDistanceSqr); 785 float overlap = enemyRadius - cornerDistance; 786 float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX; 787 float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY; 788 enemy->simPosition.x -= directionX * overlap;
789 enemy->simPosition.y -= directionY * overlap; 790 contactPoint = (Vector3){cornerX, 0.2f, cornerY};
791 } 792 793 if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f) 794 { 795 enemy->contactTime += gameTime.deltaTime * 2.0f; // * 2 to undo the subtraction above 796 if (enemy->contactTime >= enemyClassConfigs[enemy->enemyType].requiredContactTime) 797 {
798 EnemyTriggerExplode(enemy, tower, contactPoint);
799 } 800 } 801 } 802 } 803 } 804 805 EnemyId EnemyGetId(Enemy *enemy) 806 { 807 return (EnemyId){enemy - enemies, enemy->generation}; 808 } 809 810 Enemy *EnemyTryResolve(EnemyId enemyId) 811 { 812 if (enemyId.index >= ENEMY_MAX_COUNT) 813 { 814 return 0; 815 } 816 Enemy *enemy = &enemies[enemyId.index]; 817 if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE) 818 { 819 return 0; 820 } 821 return enemy; 822 } 823 824 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY) 825 { 826 Enemy *spawn = 0; 827 for (int i = 0; i < enemyCount; i++) 828 { 829 Enemy *enemy = &enemies[i]; 830 if (enemy->enemyType == ENEMY_TYPE_NONE) 831 { 832 spawn = enemy; 833 break; 834 } 835 } 836 837 if (enemyCount < ENEMY_MAX_COUNT && !spawn) 838 { 839 spawn = &enemies[enemyCount++]; 840 } 841 842 if (spawn) 843 { 844 spawn->currentX = currentX; 845 spawn->currentY = currentY; 846 spawn->nextX = currentX; 847 spawn->nextY = currentY; 848 spawn->simPosition = (Vector2){currentX, currentY}; 849 spawn->simVelocity = (Vector2){0, 0}; 850 spawn->enemyType = enemyType; 851 spawn->startMovingTime = gameTime.time; 852 spawn->damage = 0.0f; 853 spawn->futureDamage = 0.0f; 854 spawn->generation++; 855 spawn->movePathCount = 0; 856 } 857 858 return spawn; 859 } 860 861 int EnemyAddDamage(Enemy *enemy, float damage) 862 { 863 enemy->damage += damage; 864 if (enemy->damage >= EnemyGetMaxHealth(enemy)) 865 { 866 enemy->enemyType = ENEMY_TYPE_NONE; 867 return 1; 868 } 869 870 return 0; 871 } 872 873 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range) 874 { 875 int16_t castleX = 0; 876 int16_t castleY = 0; 877 Enemy* closest = 0; 878 int16_t closestDistance = 0; 879 float range2 = range * range; 880 for (int i = 0; i < enemyCount; i++) 881 { 882 Enemy* enemy = &enemies[i]; 883 if (enemy->enemyType == ENEMY_TYPE_NONE) 884 { 885 continue; 886 } 887 float maxHealth = EnemyGetMaxHealth(enemy); 888 if (enemy->futureDamage >= maxHealth) 889 { 890 // ignore enemies that will die soon 891 continue; 892 } 893 int16_t dx = castleX - enemy->currentX; 894 int16_t dy = castleY - enemy->currentY; 895 int16_t distance = abs(dx) + abs(dy); 896 if (!closest || distance < closestDistance) 897 { 898 float tdx = towerX - enemy->currentX; 899 float tdy = towerY - enemy->currentY; 900 float tdistance2 = tdx * tdx + tdy * tdy; 901 if (tdistance2 <= range2) 902 { 903 closest = enemy; 904 closestDistance = distance; 905 } 906 } 907 } 908 return closest; 909 } 910 911 int EnemyCount() 912 { 913 int count = 0; 914 for (int i = 0; i < enemyCount; i++) 915 { 916 if (enemies[i].enemyType != ENEMY_TYPE_NONE) 917 { 918 count++; 919 } 920 } 921 return count; 922 } 923 924 //# Projectiles 925 #define PROJECTILE_MAX_COUNT 1200 926 #define PROJECTILE_TYPE_NONE 0 927 #define PROJECTILE_TYPE_BULLET 1 928 929 typedef struct Projectile 930 { 931 uint8_t projectileType; 932 float shootTime; 933 float arrivalTime; 934 float damage; 935 Vector2 position; 936 Vector2 target; 937 Vector2 directionNormal; 938 EnemyId targetEnemy; 939 } Projectile; 940 941 Projectile projectiles[PROJECTILE_MAX_COUNT]; 942 int projectileCount = 0; 943 944 void ProjectileInit() 945 { 946 for (int i = 0; i < PROJECTILE_MAX_COUNT; i++) 947 { 948 projectiles[i] = (Projectile){0}; 949 } 950 } 951 952 void ProjectileDraw() 953 { 954 for (int i = 0; i < projectileCount; i++) 955 { 956 Projectile projectile = projectiles[i]; 957 if (projectile.projectileType == PROJECTILE_TYPE_NONE) 958 { 959 continue; 960 } 961 float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime); 962 if (transition >= 1.0f) 963 { 964 continue; 965 } 966 Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition); 967 float x = position.x; 968 float y = position.y; 969 float dx = projectile.directionNormal.x; 970 float dy = projectile.directionNormal.y; 971 for (float d = 1.0f; d > 0.0f; d -= 0.25f) 972 { 973 x -= dx * 0.1f; 974 y -= dy * 0.1f; 975 float size = 0.1f * d; 976 DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED); 977 } 978 } 979 } 980 981 void ProjectileUpdate() 982 { 983 for (int i = 0; i < projectileCount; i++) 984 { 985 Projectile *projectile = &projectiles[i]; 986 if (projectile->projectileType == PROJECTILE_TYPE_NONE) 987 { 988 continue; 989 } 990 float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime); 991 if (transition >= 1.0f) 992 { 993 projectile->projectileType = PROJECTILE_TYPE_NONE; 994 Enemy *enemy = EnemyTryResolve(projectile->targetEnemy); 995 if (enemy) 996 { 997 EnemyAddDamage(enemy, projectile->damage); 998 } 999 continue; 1000 } 1001 } 1002 } 1003 1004 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage) 1005 { 1006 for (int i = 0; i < PROJECTILE_MAX_COUNT; i++) 1007 { 1008 Projectile *projectile = &projectiles[i]; 1009 if (projectile->projectileType == PROJECTILE_TYPE_NONE) 1010 { 1011 projectile->projectileType = projectileType; 1012 projectile->shootTime = gameTime.time; 1013 projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed; 1014 projectile->damage = damage; 1015 projectile->position = position; 1016 projectile->target = target; 1017 projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position)); 1018 projectile->targetEnemy = EnemyGetId(enemy); 1019 projectileCount = projectileCount <= i ? i + 1 : projectileCount; 1020 return projectile; 1021 } 1022 } 1023 return 0; 1024 } 1025 1026 //# Towers 1027 1028 void TowerInit() 1029 { 1030 for (int i = 0; i < TOWER_MAX_COUNT; i++) 1031 { 1032 towers[i] = (Tower){0}; 1033 } 1034 towerCount = 0; 1035 } 1036 1037 Tower *TowerGetAt(int16_t x, int16_t y) 1038 { 1039 for (int i = 0; i < towerCount; i++) 1040 { 1041 if (towers[i].x == x && towers[i].y == y) 1042 { 1043 return &towers[i]; 1044 } 1045 } 1046 return 0; 1047 } 1048 1049 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y) 1050 { 1051 if (towerCount >= TOWER_MAX_COUNT) 1052 { 1053 return 0; 1054 } 1055 1056 Tower *tower = TowerGetAt(x, y); 1057 if (tower) 1058 { 1059 return 0; 1060 } 1061 1062 tower = &towers[towerCount++]; 1063 tower->x = x; 1064 tower->y = y; 1065 tower->towerType = towerType; 1066 tower->cooldown = 0.0f; 1067 tower->damage = 0.0f; 1068 return tower; 1069 } 1070 1071 float TowerGetMaxHealth(Tower *tower) 1072 { 1073 switch (tower->towerType) 1074 { 1075 case TOWER_TYPE_BASE: 1076 return 10.0f; 1077 case TOWER_TYPE_GUN: 1078 return 3.0f; 1079 case TOWER_TYPE_WALL: 1080 return 5.0f; 1081 } 1082 return 0.0f; 1083 } 1084 1085 void TowerDraw() 1086 { 1087 for (int i = 0; i < towerCount; i++) 1088 { 1089 Tower tower = towers[i]; 1090 DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY); 1091 switch (tower.towerType) 1092 { 1093 case TOWER_TYPE_BASE: 1094 DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON); 1095 break; 1096 case TOWER_TYPE_GUN: 1097 DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE); 1098 break; 1099 case TOWER_TYPE_WALL: 1100 DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY); 1101 break; 1102 } 1103 } 1104 } 1105 1106 void TowerGunUpdate(Tower *tower) 1107 { 1108 if (tower->cooldown <= 0) 1109 { 1110 Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f); 1111 if (enemy) 1112 { 1113 tower->cooldown = 0.125f; 1114 // shoot the enemy; determine future position of the enemy 1115 float bulletSpeed = 1.0f; 1116 float bulletDamage = 3.0f; 1117 Vector2 velocity = enemy->simVelocity; 1118 Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0); 1119 Vector2 towerPosition = {tower->x, tower->y}; 1120 float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed; 1121 for (int i = 0; i < 8; i++) { 1122 velocity = enemy->simVelocity; 1123 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0); 1124 float distance = Vector2Distance(towerPosition, futurePosition); 1125 float eta2 = distance / bulletSpeed; 1126 if (fabs(eta - eta2) < 0.01f) { 1127 break; 1128 } 1129 eta = (eta2 + eta) * 0.5f; 1130 } 1131 ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, towerPosition, futurePosition, 1132 bulletSpeed, bulletDamage); 1133 enemy->futureDamage += bulletDamage; 1134 } 1135 } 1136 else 1137 { 1138 tower->cooldown -= gameTime.deltaTime; 1139 } 1140 } 1141 1142 void TowerUpdate() 1143 { 1144 for (int i = 0; i < towerCount; i++) 1145 { 1146 Tower *tower = &towers[i]; 1147 switch (tower->towerType) 1148 { 1149 case TOWER_TYPE_GUN: 1150 TowerGunUpdate(tower); 1151 break; 1152 } 1153 } 1154 } 1155 1156 //# Game 1157 1158 float nextSpawnTime = 0.0f; 1159 1160 void InitGame() 1161 { 1162 TowerInit(); 1163 EnemyInit();
1164 ProjectileInit(); 1165 ParticleInit();
1166 PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f); 1167 1168 TowerTryAdd(TOWER_TYPE_BASE, 0, 0); 1169 TowerTryAdd(TOWER_TYPE_GUN, 2, 0); 1170 1171 for (int i = -2; i <= 2; i += 1) 1172 { 1173 TowerTryAdd(TOWER_TYPE_WALL, i, 2); 1174 TowerTryAdd(TOWER_TYPE_WALL, i, -2); 1175 TowerTryAdd(TOWER_TYPE_WALL, -2, i); 1176 } 1177 1178 EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4); 1179 } 1180 1181 void GameUpdate() 1182 { 1183 float dt = GetFrameTime(); 1184 // cap maximum delta time to 0.1 seconds to prevent large time steps 1185 if (dt > 0.1f) dt = 0.1f; 1186 gameTime.time += dt; 1187 gameTime.deltaTime = dt; 1188 PathFindingMapUpdate(); 1189 EnemyUpdate(); 1190 TowerUpdate();
1191 ProjectileUpdate(); 1192 ParticleUpdate();
1193 1194 // spawn a new enemy every second 1195 if (gameTime.time >= nextSpawnTime && EnemyCount() < 50) 1196 { 1197 nextSpawnTime = gameTime.time + 0.2f; 1198 // add a new enemy at the boundary of the map 1199 int randValue = GetRandomValue(-5, 5); 1200 int randSide = GetRandomValue(0, 3); 1201 int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue; 1202 int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue; 1203 static int alternation = 0; 1204 alternation += 1; 1205 if (alternation % 3 == 0) { 1206 EnemyTryAdd(ENEMY_TYPE_MINION, 0, -5); 1207 } 1208 else if (alternation % 3 == 1) 1209 { 1210 EnemyTryAdd(ENEMY_TYPE_MINION, 0, 5); 1211 } 1212 EnemyTryAdd(ENEMY_TYPE_MINION, x, y); 1213 } 1214 } 1215 1216 int main(void) 1217 { 1218 int screenWidth, screenHeight; 1219 GetPreferredSize(&screenWidth, &screenHeight); 1220 InitWindow(screenWidth, screenHeight, "Tower defense"); 1221 SetTargetFPS(30); 1222 1223 Camera3D camera = {0}; 1224 camera.position = (Vector3){0.0f, 10.0f, -0.5f}; 1225 camera.target = (Vector3){0.0f, 0.0f, -0.5f}; 1226 camera.up = (Vector3){0.0f, 0.0f, -1.0f}; 1227 camera.fovy = 12.0f; 1228 camera.projection = CAMERA_ORTHOGRAPHIC; 1229 1230 InitGame(); 1231 1232 while (!WindowShouldClose()) 1233 { 1234 if (IsPaused()) { 1235 // canvas is not visible in browser - do nothing 1236 continue; 1237 } 1238 BeginDrawing(); 1239 ClearBackground(DARKBLUE); 1240 1241 BeginMode3D(camera); 1242 DrawGrid(10, 1.0f); 1243 TowerDraw(); 1244 EnemyDraw(); 1245 ProjectileDraw();
1246 PathFindingMapDraw(); 1247 ParticleDraw();
1248 GameUpdate(); 1249 EndMode3D(); 1250 1251 const char *title = "Tower defense tutorial"; 1252 int titleWidth = MeasureText(title, 20); 1253 DrawText(title, (GetScreenWidth() - titleWidth) * 0.5f + 2, 5 + 2, 20, BLACK); 1254 DrawText(title, (GetScreenWidth() - titleWidth) * 0.5f, 5, 20, WHITE); 1255 EndDrawing(); 1256 } 1257 1258 CloseWindow(); 1259 1260 return 0; 1261 }
  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 #endif
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif

I made an error here that I only discovered only in a much later post: In line 757 of td-tut-28-main.c, something tiny is wrong - can you spot it? Interestingly, the various game plays I did, did not reveal this error until I created a specific situation.

Now we have a little back of feedback and a simple particle system that we can later extend.

It's time to add an interface to build new buildings. We'll use the approach of immediate GUI for this. In case you are not familiar with the concept, here's a quick explanation: An immediate GUI system describes the interface purely in code; draw instructions and event handling happens at the same time. To illustrate the usage, this is how it looks like (pseudo code):

  1 if (Button("Build Archer", 10, 10, 80, 30))
  2 {
  3   buildingType = BUILDING_TYPE_ARCHER;
  4 }

In the above example, the function Button draws a button at the position 10, 10 with a size of 80x30 pixels. If the button is clicked, the function returns true and therefore the building type is set.

The other, more common approach are retained GUI systems, where GUI elements are objects that are created and manipulated. They typically use events to communicate with the application. For example, this is how the same button would look like in a retained GUI system:

  1 void InitGUI()
  2 {
  3   Button *button = new Button("Build Archer", 10, 10, 80, 30);
  4   button->onClick = OnBuildArcher;
  5 }
  6 
  7 void OnBuildArcher()
  8 {
  9   buildingType = BUILDING_TYPE_ARCHER;
 10 }

Retained GUI systems are more complex to implement and use, but they are often combined with visual authoring tools for creating complex interface layouts. Layouting immediate GUIs usually happens in code, which isn't artist-friendly. Immediate GUIs are often used for development tools where visual appeal is less important than speed and ease of use. A very popuplar immediate GUI system is Dear ImGui, which is used in many game engines and tools. It is quite straight forward to use and has a lot of features. For our game, we'll implement a simple immediate GUI system ourselves, as we only need a few buttons and labels.

To get started with a simple immediate GUI system, I will simply add a button to toggle the camera between two perspectives, just to see how our game is looking from a different angle - and to make sure that when we start interacting with the world, our system works in any camera setup:

  • 💾
  1 #include "td-tut-2-main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 //# Declarations
  7 
  8 #define PARTICLE_MAX_COUNT 400
  9 #define PARTICLE_TYPE_NONE 0
 10 #define PARTICLE_TYPE_EXPLOSION 1
 11 
 12 typedef struct Particle
 13 {
 14   uint8_t particleType;
 15   float spawnTime;
 16   float lifetime;
 17   Vector3 position;
 18   Vector3 velocity;
 19 } Particle;
 20 
 21 Particle particles[PARTICLE_MAX_COUNT];
 22 int particleCount = 0;
 23 
 24 #define TOWER_MAX_COUNT 400
 25 #define TOWER_TYPE_NONE 0
 26 #define TOWER_TYPE_BASE 1
 27 #define TOWER_TYPE_GUN 2
 28 #define TOWER_TYPE_WALL 3
 29 
 30 typedef struct Tower
 31 {
 32   int16_t x, y;
 33   uint8_t towerType;
 34   float cooldown;
 35   float damage;
 36 } Tower;
 37 
 38 typedef struct GameTime
 39 {
 40   float time;
 41   float deltaTime;
 42 } GameTime;
 43 
 44 GameTime gameTime = {0};
 45 
 46 Tower towers[TOWER_MAX_COUNT];
 47 int towerCount = 0;
 48 
 49 float TowerGetMaxHealth(Tower *tower);
 50 
 51 //# Particle system
 52 
 53 void ParticleInit()
 54 {
 55   for (int i = 0; i < PARTICLE_MAX_COUNT; i++)
 56   {
 57     particles[i] = (Particle){0};
 58   }
 59   particleCount = 0;
 60 }
 61 
 62 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime)
 63 {
 64   if (particleCount >= PARTICLE_MAX_COUNT)
 65   {
 66     return;
 67   }
 68 
 69   int index = -1;
 70   for (int i = 0; i < particleCount; i++)
 71   {
 72     if (particles[i].particleType == PARTICLE_TYPE_NONE)
 73     {
 74       index = i;
 75       break;
 76     }
 77   }
 78 
 79   if (index == -1)
 80   {
 81     index = particleCount++;
 82   }
 83 
 84   Particle *particle = &particles[index];
 85   particle->particleType = particleType;
 86   particle->spawnTime = gameTime.time;
 87   particle->lifetime = lifetime;
 88   particle->position = position;
 89   particle->velocity = velocity;
 90 }
 91 
 92 void ParticleUpdate()
 93 {
 94   for (int i = 0; i < particleCount; i++)
 95   {
 96     Particle *particle = &particles[i];
 97     if (particle->particleType == PARTICLE_TYPE_NONE)
 98     {
 99       continue;
100     }
101 
102     float age = gameTime.time - particle->spawnTime;
103 
104     if (particle->lifetime > age)
105     {
106       particle->position = Vector3Add(particle->position, Vector3Scale(particle->velocity, gameTime.deltaTime));
107     }
108     else {
109       particle->particleType = PARTICLE_TYPE_NONE;
110     }
111   }
112 }
113 
114 void DrawExplosionParticle(Particle *particle, float transition)
115 {
116   float size = 1.2f * (1.0f - transition);
117   Color startColor = WHITE;
118   Color endColor = RED;
119   Color color = ColorLerp(startColor, endColor, transition);
120   DrawCube(particle->position, size, size, size, color);
121 }
122 
123 void ParticleDraw()
124 {
125   for (int i = 0; i < particleCount; i++)
126   {
127     Particle particle = particles[i];
128     if (particle.particleType == PARTICLE_TYPE_NONE)
129     {
130       continue;
131     }
132 
133     float age = gameTime.time - particle.spawnTime;
134     float transition = age / particle.lifetime;
135     switch (particle.particleType)
136     {
137     case PARTICLE_TYPE_EXPLOSION:
138       DrawExplosionParticle(&particle, transition);
139       break;
140     default:
141       DrawCube(particle.position, 0.3f, 0.5f, 0.3f, RED);
142       break;
143     }
144   }
145 }
146 
147 //# Pathfinding map
148 typedef struct DeltaSrc
149 {
150   char x, y;
151 } DeltaSrc;
152 
153 typedef struct PathfindingMap
154 {
155   int width, height;
156   float scale;
157   float *distances;
158   long *towerIndex; 
159   DeltaSrc *deltaSrc;
160   float maxDistance;
161   Matrix toMapSpace;
162   Matrix toWorldSpace;
163 } PathfindingMap;
164 
165 // when we execute the pathfinding algorithm, we need to store the active nodes
166 // in a queue. Each node has a position, a distance from the start, and the
167 // position of the node that we came from.
168 typedef struct PathfindingNode
169 {
170   int16_t x, y, fromX, fromY;
171   float distance;
172 } PathfindingNode;
173 
174 // The queue is a simple array of nodes, we add nodes to the end and remove
175 // nodes from the front. We keep the array around to avoid unnecessary allocations
176 static PathfindingNode *pathfindingNodeQueue = 0;
177 static int pathfindingNodeQueueCount = 0;
178 static int pathfindingNodeQueueCapacity = 0;
179 
180 // The pathfinding map stores the distances from the castle to each cell in the map.
181 PathfindingMap pathfindingMap = {0};
182 
183 void PathfindingMapInit(int width, int height, Vector3 translate, float scale)
184 {
185   // transforming between map space and world space allows us to adapt 
186   // position and scale of the map without changing the pathfinding data
187   pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z);
188   pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale));
189   pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace);
190   pathfindingMap.width = width;
191   pathfindingMap.height = height;
192   pathfindingMap.scale = scale;
193   pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float));
194   for (int i = 0; i < width * height; i++)
195   {
196     pathfindingMap.distances[i] = -1.0f;
197   }
198 
199   pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long));
200   pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc));
201 }
202 
203 float PathFindingGetDistance(int mapX, int mapY)
204 {
205   if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height)
206   {
207     // when outside the map, we return the manhattan distance to the castle (0,0)
208     return fabsf((float)mapX) + fabsf((float)mapY);
209   }
210 
211   return pathfindingMap.distances[mapY * pathfindingMap.width + mapX];
212 }
213 
214 void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance)
215 {
216   if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity)
217   {
218     pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2;
219     // we use MemAlloc/MemRealloc to allocate memory for the queue
220     // I am not entirely sure if MemRealloc allows passing a null pointer
221     // so we check if the pointer is null and use MemAlloc in that case
222     if (pathfindingNodeQueue == 0)
223     {
224       pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
225     }
226     else
227     {
228       pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
229     }
230   }
231 
232   PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++];
233   node->x = x;
234   node->y = y;
235   node->fromX = fromX;
236   node->fromY = fromY;
237   node->distance = distance;
238 }
239 
240 PathfindingNode *PathFindingNodePop()
241 {
242   if (pathfindingNodeQueueCount == 0)
243   {
244     return 0;
245   }
246   // we return the first node in the queue; we want to return a pointer to the node
247   // so we can return 0 if the queue is empty. 
248   // We should _not_ return a pointer to the element in the list, because the list
249   // may be reallocated and the pointer would become invalid. Or the 
250   // popped element is overwritten by the next push operation.
251   // Using static here means that the variable is permanently allocated.
252   static PathfindingNode node;
253   node = pathfindingNodeQueue[0];
254   // we shift all nodes one position to the front
255   for (int i = 1; i < pathfindingNodeQueueCount; i++)
256   {
257     pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i];
258   }
259   --pathfindingNodeQueueCount;
260   return &node;
261 }
262 
263 // transform a world position to a map position in the array; 
264 // returns true if the position is inside the map
265 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY)
266 {
267   Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace);
268   *mapX = (int16_t)mapPosition.x;
269   *mapY = (int16_t)mapPosition.z;
270   return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height;
271 }
272 
273 void PathFindingMapUpdate()
274 {
275   const int castleX = 0, castleY = 0;
276   int16_t castleMapX, castleMapY;
277   if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY))
278   {
279     return;
280   }
281   int width = pathfindingMap.width, height = pathfindingMap.height;
282 
283   // reset the distances to -1
284   for (int i = 0; i < width * height; i++)
285   {
286     pathfindingMap.distances[i] = -1.0f;
287   }
288   // reset the tower indices
289   for (int i = 0; i < width * height; i++)
290   {
291     pathfindingMap.towerIndex[i] = -1;
292   }
293   // reset the delta src
294   for (int i = 0; i < width * height; i++)
295   {
296     pathfindingMap.deltaSrc[i].x = 0;
297     pathfindingMap.deltaSrc[i].y = 0;
298   }
299 
300   for (int i = 0; i < towerCount; i++)
301   {
302     Tower *tower = &towers[i];
303     if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE)
304     {
305       continue;
306     }
307     int16_t mapX, mapY;
308     // technically, if the tower cell scale is not in sync with the pathfinding map scale,
309     // this would not work correctly and needs to be refined to allow towers covering multiple cells
310     // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly
311     // one cell. For now.
312     if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY))
313     {
314       continue;
315     }
316     int index = mapY * width + mapX;
317     pathfindingMap.towerIndex[index] = i;
318   }
319 
320   // we start at the castle and add the castle to the queue
321   pathfindingMap.maxDistance = 0.0f;
322   pathfindingNodeQueueCount = 0;
323   PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f);
324   PathfindingNode *node = 0;
325   while ((node = PathFindingNodePop()))
326   {
327     if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height)
328     {
329       continue;
330     }
331     int index = node->y * width + node->x;
332     if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance)
333     {
334       continue;
335     }
336 
337     int deltaX = node->x - node->fromX;
338     int deltaY = node->y - node->fromY;
339     // even if the cell is blocked by a tower, we still may want to store the direction
340     // (though this might not be needed, IDK right now)
341     pathfindingMap.deltaSrc[index].x = (char) deltaX;
342     pathfindingMap.deltaSrc[index].y = (char) deltaY;
343 
344     // we skip nodes that are blocked by towers
345     if (pathfindingMap.towerIndex[index] >= 0)
346     {
347       node->distance += 8.0f;
348     }
349     pathfindingMap.distances[index] = node->distance;
350     pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance);
351     PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f);
352     PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f);
353     PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f);
354     PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f);
355   }
356 }
357 
358 void PathFindingMapDraw()
359 {
360   float cellSize = pathfindingMap.scale * 0.9f;
361   float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance);
362   for (int x = 0; x < pathfindingMap.width; x++)
363   {
364     for (int y = 0; y < pathfindingMap.height; y++)
365     {
366       float distance = pathfindingMap.distances[y * pathfindingMap.width + x];
367       float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f);
368       Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255};
369       Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace);
370       // animate the distance "wave" to show how the pathfinding algorithm expands
371       // from the castle
372       if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance)
373       {
374         color = BLACK;
375       }
376       DrawCube(position, cellSize, 0.1f, cellSize, color);
377     }
378   }
379 }
380 
381 Vector2 PathFindingGetGradient(Vector3 world)
382 {
383   int16_t mapX, mapY;
384   if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY))
385   {
386     DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX];
387     return (Vector2){(float)-delta.x, (float)-delta.y};
388   }
389   // fallback to a simple gradient calculation
390   float n = PathFindingGetDistance(mapX, mapY - 1);
391   float s = PathFindingGetDistance(mapX, mapY + 1);
392   float w = PathFindingGetDistance(mapX - 1, mapY);
393   float e = PathFindingGetDistance(mapX + 1, mapY);
394   return (Vector2){w - e + 0.25f, n - s + 0.125f};
395 }
396 
397 //# Enemies
398 
399 #define ENEMY_MAX_PATH_COUNT 8
400 #define ENEMY_MAX_COUNT 400
401 #define ENEMY_TYPE_NONE 0
402 #define ENEMY_TYPE_MINION 1
403 
404 typedef struct EnemyId
405 {
406   uint16_t index;
407   uint16_t generation;
408 } EnemyId;
409 
410 typedef struct EnemyClassConfig
411 {
412   float speed;
413   float health;
414   float radius;
415   float maxAcceleration;
416   float requiredContactTime;
417   float explosionDamage;
418   float explosionRange;
419   float explosionPushbackPower;
420 } EnemyClassConfig;
421 
422 typedef struct Enemy
423 {
424   int16_t currentX, currentY;
425   int16_t nextX, nextY;
426   Vector2 simPosition;
427   Vector2 simVelocity;
428   uint16_t generation;
429   float startMovingTime;
430   float damage, futureDamage;
431   float contactTime;
432   uint8_t enemyType;
433   uint8_t movePathCount;
434   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
435 } Enemy;
436 
437 Enemy enemies[ENEMY_MAX_COUNT];
438 int enemyCount = 0;
439 
440 EnemyClassConfig enemyClassConfigs[] = {
441     [ENEMY_TYPE_MINION] = {
442       .health = 3.0f, 
443       .speed = 1.0f, 
444       .radius = 0.25f, 
445       .maxAcceleration = 1.0f,
446       .explosionDamage = 1.0f,
447       .requiredContactTime = 0.5f,
448       .explosionRange = 1.0f,
449       .explosionPushbackPower = 0.25f,
450     },
451 };
452 
453 int EnemyAddDamage(Enemy *enemy, float damage);
454 
455 void EnemyInit()
456 {
457   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
458   {
459     enemies[i] = (Enemy){0};
460   }
461   enemyCount = 0;
462 }
463 
464 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
465 {
466   return enemyClassConfigs[enemy->enemyType].speed;
467 }
468 
469 float EnemyGetMaxHealth(Enemy *enemy)
470 {
471   return enemyClassConfigs[enemy->enemyType].health;
472 }
473 
474 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
475 {
476   int16_t castleX = 0;
477   int16_t castleY = 0;
478   int16_t dx = castleX - currentX;
479   int16_t dy = castleY - currentY;
480   if (dx == 0 && dy == 0)
481   {
482     *nextX = currentX;
483     *nextY = currentY;
484     return 1;
485   }
486   Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY});
487 
488   if (gradient.x == 0 && gradient.y == 0)
489   {
490     *nextX = currentX;
491     *nextY = currentY;
492     return 1;
493   }
494 
495   if (fabsf(gradient.x) > fabsf(gradient.y))
496   {
497     *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1);
498     *nextY = currentY;
499     return 0;
500   }
501   *nextX = currentX;
502   *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1);
503   return 0;
504 }
505 
506 
507 // this function predicts the movement of the unit for the next deltaT seconds
508 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
509 {
510   const float pointReachedDistance = 0.25f;
511   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
512   const float maxSimStepTime = 0.015625f;
513   
514   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
515   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
516   int16_t nextX = enemy->nextX;
517   int16_t nextY = enemy->nextY;
518   Vector2 position = enemy->simPosition;
519   int passedCount = 0;
520   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
521   {
522     float stepTime = fminf(deltaT - t, maxSimStepTime);
523     Vector2 target = (Vector2){nextX, nextY};
524     float speed = Vector2Length(*velocity);
525     // draw the target position for debugging
526     DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
527     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
528     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
529     {
530       // we reached the target position, let's move to the next waypoint
531       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
532       target = (Vector2){nextX, nextY};
533       // track how many waypoints we passed
534       passedCount++;
535     }
536     
537     // acceleration towards the target
538     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
539     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
540     *velocity = Vector2Add(*velocity, acceleration);
541 
542     // limit the speed to the maximum speed
543     if (speed > maxSpeed)
544     {
545       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
546     }
547 
548     // move the enemy
549     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
550   }
551 
552   if (waypointPassedCount)
553   {
554     (*waypointPassedCount) = passedCount;
555   }
556 
557   return position;
558 }
559 
560 void EnemyDraw()
561 {
562   for (int i = 0; i < enemyCount; i++)
563   {
564     Enemy enemy = enemies[i];
565     if (enemy.enemyType == ENEMY_TYPE_NONE)
566     {
567       continue;
568     }
569 
570     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
571     
572     if (enemy.movePathCount > 0)
573     {
574       Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
575       DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
576     }
577     for (int j = 1; j < enemy.movePathCount; j++)
578     {
579       Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
580       Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
581       DrawLine3D(p, q, GREEN);
582     }
583 
584     switch (enemy.enemyType)
585     {
586     case ENEMY_TYPE_MINION:
587       DrawCubeWires((Vector3){position.x, 0.2f, position.y}, 0.4f, 0.4f, 0.4f, GREEN);
588       break;
589     }
590   }
591 }
592 
593 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource)
594 {
595   // damage the tower
596   float explosionDamge = enemyClassConfigs[enemy->enemyType].explosionDamage;
597   float explosionRange = enemyClassConfigs[enemy->enemyType].explosionRange;
598   float explosionPushbackPower = enemyClassConfigs[enemy->enemyType].explosionPushbackPower;
599   float explosionRange2 = explosionRange * explosionRange;
600   tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage;
601   // explode the enemy
602   if (tower->damage >= TowerGetMaxHealth(tower))
603   {
604     tower->towerType = TOWER_TYPE_NONE;
605   }
606 
607   ParticleAdd(PARTICLE_TYPE_EXPLOSION, 
608     explosionSource, 
609     (Vector3){0, 0.1f, 0}, 1.0f);
610 
611   enemy->enemyType = ENEMY_TYPE_NONE;
612 
613   // push back enemies & dealing damage
614   for (int i = 0; i < enemyCount; i++)
615   {
616     Enemy *other = &enemies[i];
617     if (other->enemyType == ENEMY_TYPE_NONE)
618     {
619       continue;
620     }
621     float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition);
622     if (distanceSqr > 0 && distanceSqr < explosionRange2)
623     {
624       Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition));
625       other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower));
626       EnemyAddDamage(other, explosionDamge);
627     }
628   }
629 }
630 
631 void EnemyUpdate()
632 {
633   const float castleX = 0;
634   const float castleY = 0;
635   const float maxPathDistance2 = 0.25f * 0.25f;
636   
637   for (int i = 0; i < enemyCount; i++)
638   {
639     Enemy *enemy = &enemies[i];
640     if (enemy->enemyType == ENEMY_TYPE_NONE)
641     {
642       continue;
643     }
644 
645     int waypointPassedCount = 0;
646     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
647     enemy->startMovingTime = gameTime.time;
648     // track path of unit
649     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
650     {
651       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
652       {
653         enemy->movePath[j] = enemy->movePath[j - 1];
654       }
655       enemy->movePath[0] = enemy->simPosition;
656       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
657       {
658         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
659       }
660     }
661 
662     if (waypointPassedCount > 0)
663     {
664       enemy->currentX = enemy->nextX;
665       enemy->currentY = enemy->nextY;
666       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) &&
667         Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
668       {
669         // enemy reached the castle; remove it
670         enemy->enemyType = ENEMY_TYPE_NONE;
671         continue;
672       }
673     }
674   }
675 
676   // handle collisions between enemies
677   for (int i = 0; i < enemyCount - 1; i++)
678   {
679     Enemy *enemyA = &enemies[i];
680     if (enemyA->enemyType == ENEMY_TYPE_NONE)
681     {
682       continue;
683     }
684     for (int j = i + 1; j < enemyCount; j++)
685     {
686       Enemy *enemyB = &enemies[j];
687       if (enemyB->enemyType == ENEMY_TYPE_NONE)
688       {
689         continue;
690       }
691       float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition);
692       float radiusA = enemyClassConfigs[enemyA->enemyType].radius;
693       float radiusB = enemyClassConfigs[enemyB->enemyType].radius;
694       float radiusSum = radiusA + radiusB;
695       if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f)
696       {
697         // collision
698         float distance = sqrtf(distanceSqr);
699         float overlap = radiusSum - distance;
700         // move the enemies apart, but softly; if we have a clog of enemies,
701         // moving them perfectly apart can cause them to jitter
702         float positionCorrection = overlap / 5.0f;
703         Vector2 direction = (Vector2){
704             (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection,
705             (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection};
706         enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction);
707         enemyB->simPosition = Vector2Add(enemyB->simPosition, direction);
708       }
709     }
710   }
711 
712   // handle collisions between enemies and towers
713   for (int i = 0; i < enemyCount; i++)
714   {
715     Enemy *enemy = &enemies[i];
716     if (enemy->enemyType == ENEMY_TYPE_NONE)
717     {
718       continue;
719     }
720     enemy->contactTime -= gameTime.deltaTime;
721     if (enemy->contactTime < 0.0f)
722     {
723       enemy->contactTime = 0.0f;
724     }
725 
726     float enemyRadius = enemyClassConfigs[enemy->enemyType].radius;
727     // linear search over towers; could be optimized by using path finding tower map,
728     // but for now, we keep it simple
729     for (int j = 0; j < towerCount; j++)
730     {
731       Tower *tower = &towers[j];
732       if (tower->towerType == TOWER_TYPE_NONE)
733       {
734         continue;
735       }
736       float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y});
737       float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1
738       if (distanceSqr > combinedRadius * combinedRadius)
739       {
740         continue;
741       }
742       // potential collision; square / circle intersection
743       float dx = tower->x - enemy->simPosition.x;
744       float dy = tower->y - enemy->simPosition.y;
745       float absDx = fabsf(dx);
746       float absDy = fabsf(dy);
747       Vector3 contactPoint = {0};
748       if (absDx <= 0.5f && absDx <= absDy) {
749         // vertical collision; push the enemy out horizontally
750         float overlap = enemyRadius + 0.5f - absDy;
751         if (overlap < 0.0f)
752         {
753           continue;
754         }
755         float direction = dy > 0.0f ? -1.0f : 1.0f;
756         enemy->simPosition.y += direction * overlap;
757         contactPoint = (Vector3){enemy->simPosition.x, 0.2f, tower->x + direction * 0.5f};
758       }
759       else if (absDy <= 0.5f && absDy <= absDx)
760       {
761         // horizontal collision; push the enemy out vertically
762         float overlap = enemyRadius + 0.5f - absDx;
763         if (overlap < 0.0f)
764         {
765           continue;
766         }
767         float direction = dx > 0.0f ? -1.0f : 1.0f;
768         enemy->simPosition.x += direction * overlap;
769         contactPoint = (Vector3){tower->x + direction * 0.5f, 0.2f, enemy->simPosition.y};
770       }
771       else
772       {
773         // possible collision with a corner
774         float cornerDX = dx > 0.0f ? -0.5f : 0.5f;
775         float cornerDY = dy > 0.0f ? -0.5f : 0.5f;
776         float cornerX = tower->x + cornerDX;
777         float cornerY = tower->y + cornerDY;
778         float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY});
779         if (cornerDistanceSqr > enemyRadius * enemyRadius)
780         {
781           continue;
782         }
783         // push the enemy out along the diagonal
784         float cornerDistance = sqrtf(cornerDistanceSqr);
785         float overlap = enemyRadius - cornerDistance;
786         float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX;
787         float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY;
788         enemy->simPosition.x -= directionX * overlap;
789         enemy->simPosition.y -= directionY * overlap;
790         contactPoint = (Vector3){cornerX, 0.2f, cornerY};
791       }
792 
793       if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f)
794       {
795         enemy->contactTime += gameTime.deltaTime * 2.0f; // * 2 to undo the subtraction above
796         if (enemy->contactTime >= enemyClassConfigs[enemy->enemyType].requiredContactTime)
797         {
798           EnemyTriggerExplode(enemy, tower, contactPoint);
799         }
800       }
801     }
802   }
803 }
804 
805 EnemyId EnemyGetId(Enemy *enemy)
806 {
807   return (EnemyId){enemy - enemies, enemy->generation};
808 }
809 
810 Enemy *EnemyTryResolve(EnemyId enemyId)
811 {
812   if (enemyId.index >= ENEMY_MAX_COUNT)
813   {
814     return 0;
815   }
816   Enemy *enemy = &enemies[enemyId.index];
817   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
818   {
819     return 0;
820   }
821   return enemy;
822 }
823 
824 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
825 {
826   Enemy *spawn = 0;
827   for (int i = 0; i < enemyCount; i++)
828   {
829     Enemy *enemy = &enemies[i];
830     if (enemy->enemyType == ENEMY_TYPE_NONE)
831     {
832       spawn = enemy;
833       break;
834     }
835   }
836 
837   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
838   {
839     spawn = &enemies[enemyCount++];
840   }
841 
842   if (spawn)
843   {
844     spawn->currentX = currentX;
845     spawn->currentY = currentY;
846     spawn->nextX = currentX;
847     spawn->nextY = currentY;
848     spawn->simPosition = (Vector2){currentX, currentY};
849     spawn->simVelocity = (Vector2){0, 0};
850     spawn->enemyType = enemyType;
851     spawn->startMovingTime = gameTime.time;
852     spawn->damage = 0.0f;
853     spawn->futureDamage = 0.0f;
854     spawn->generation++;
855     spawn->movePathCount = 0;
856   }
857 
858   return spawn;
859 }
860 
861 int EnemyAddDamage(Enemy *enemy, float damage)
862 {
863   enemy->damage += damage;
864   if (enemy->damage >= EnemyGetMaxHealth(enemy))
865   {
866     enemy->enemyType = ENEMY_TYPE_NONE;
867     return 1;
868   }
869 
870   return 0;
871 }
872 
873 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
874 {
875   int16_t castleX = 0;
876   int16_t castleY = 0;
877   Enemy* closest = 0;
878   int16_t closestDistance = 0;
879   float range2 = range * range;
880   for (int i = 0; i < enemyCount; i++)
881   {
882     Enemy* enemy = &enemies[i];
883     if (enemy->enemyType == ENEMY_TYPE_NONE)
884     {
885       continue;
886     }
887     float maxHealth = EnemyGetMaxHealth(enemy);
888     if (enemy->futureDamage >= maxHealth)
889     {
890       // ignore enemies that will die soon
891       continue;
892     }
893     int16_t dx = castleX - enemy->currentX;
894     int16_t dy = castleY - enemy->currentY;
895     int16_t distance = abs(dx) + abs(dy);
896     if (!closest || distance < closestDistance)
897     {
898       float tdx = towerX - enemy->currentX;
899       float tdy = towerY - enemy->currentY;
900       float tdistance2 = tdx * tdx + tdy * tdy;
901       if (tdistance2 <= range2)
902       {
903         closest = enemy;
904         closestDistance = distance;
905       }
906     }
907   }
908   return closest;
909 }
910 
911 int EnemyCount()
912 {
913   int count = 0;
914   for (int i = 0; i < enemyCount; i++)
915   {
916     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
917     {
918       count++;
919     }
920   }
921   return count;
922 }
923 
924 //# Projectiles
925 #define PROJECTILE_MAX_COUNT 1200
926 #define PROJECTILE_TYPE_NONE 0
927 #define PROJECTILE_TYPE_BULLET 1
928 
929 typedef struct Projectile
930 {
931   uint8_t projectileType;
932   float shootTime;
933   float arrivalTime;
934   float damage;
935   Vector2 position;
936   Vector2 target;
937   Vector2 directionNormal;
938   EnemyId targetEnemy;
939 } Projectile;
940 
941 Projectile projectiles[PROJECTILE_MAX_COUNT];
942 int projectileCount = 0;
943 
944 void ProjectileInit()
945 {
946   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
947   {
948     projectiles[i] = (Projectile){0};
949   }
950 }
951 
952 void ProjectileDraw()
953 {
954   for (int i = 0; i < projectileCount; i++)
955   {
956     Projectile projectile = projectiles[i];
957     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
958     {
959       continue;
960     }
961     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
962     if (transition >= 1.0f)
963     {
964       continue;
965     }
966     Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition);
967     float x = position.x;
968     float y = position.y;
969     float dx = projectile.directionNormal.x;
970     float dy = projectile.directionNormal.y;
971     for (float d = 1.0f; d > 0.0f; d -= 0.25f)
972     {
973       x -= dx * 0.1f;
974       y -= dy * 0.1f;
975       float size = 0.1f * d;
976       DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED);
977     }
978   }
979 }
980 
981 void ProjectileUpdate()
982 {
983   for (int i = 0; i < projectileCount; i++)
984   {
985     Projectile *projectile = &projectiles[i];
986     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
987     {
988       continue;
989     }
990     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
991     if (transition >= 1.0f)
992     {
993       projectile->projectileType = PROJECTILE_TYPE_NONE;
994       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
995       if (enemy)
996       {
997         EnemyAddDamage(enemy, projectile->damage);
998       }
999       continue;
1000     }
1001   }
1002 }
1003 
1004 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage)
1005 {
1006   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
1007   {
1008     Projectile *projectile = &projectiles[i];
1009     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
1010     {
1011       projectile->projectileType = projectileType;
1012       projectile->shootTime = gameTime.time;
1013       projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed;
1014       projectile->damage = damage;
1015       projectile->position = position;
1016       projectile->target = target;
1017       projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position));
1018       projectile->targetEnemy = EnemyGetId(enemy);
1019       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
1020       return projectile;
1021     }
1022   }
1023   return 0;
1024 }
1025 
1026 //# Towers
1027 
1028 void TowerInit()
1029 {
1030   for (int i = 0; i < TOWER_MAX_COUNT; i++)
1031   {
1032     towers[i] = (Tower){0};
1033   }
1034   towerCount = 0;
1035 }
1036 
1037 Tower *TowerGetAt(int16_t x, int16_t y)
1038 {
1039   for (int i = 0; i < towerCount; i++)
1040   {
1041     if (towers[i].x == x && towers[i].y == y)
1042     {
1043       return &towers[i];
1044     }
1045   }
1046   return 0;
1047 }
1048 
1049 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
1050 {
1051   if (towerCount >= TOWER_MAX_COUNT)
1052   {
1053     return 0;
1054   }
1055 
1056   Tower *tower = TowerGetAt(x, y);
1057   if (tower)
1058   {
1059     return 0;
1060   }
1061 
1062   tower = &towers[towerCount++];
1063   tower->x = x;
1064   tower->y = y;
1065   tower->towerType = towerType;
1066   tower->cooldown = 0.0f;
1067   tower->damage = 0.0f;
1068   return tower;
1069 }
1070 
1071 float TowerGetMaxHealth(Tower *tower)
1072 {
1073   switch (tower->towerType)
1074   {
1075   case TOWER_TYPE_BASE:
1076     return 10.0f;
1077   case TOWER_TYPE_GUN:
1078     return 3.0f;
1079   case TOWER_TYPE_WALL:
1080     return 5.0f;
1081   }
1082   return 0.0f;
1083 }
1084 
1085 void TowerDraw()
1086 {
1087   for (int i = 0; i < towerCount; i++)
1088   {
1089     Tower tower = towers[i];
1090     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
1091     switch (tower.towerType)
1092     {
1093     case TOWER_TYPE_BASE:
1094       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
1095       break;
1096     case TOWER_TYPE_GUN:
1097       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
1098       break;
1099     case TOWER_TYPE_WALL:
1100       DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
1101       break;
1102     }
1103   }
1104 }
1105 
1106 void TowerGunUpdate(Tower *tower)
1107 {
1108   if (tower->cooldown <= 0)
1109   {
1110     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
1111     if (enemy)
1112     {
1113       tower->cooldown = 0.125f;
1114       // shoot the enemy; determine future position of the enemy
1115       float bulletSpeed = 1.0f;
1116       float bulletDamage = 3.0f;
1117       Vector2 velocity = enemy->simVelocity;
1118       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
1119       Vector2 towerPosition = {tower->x, tower->y};
1120       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
1121       for (int i = 0; i < 8; i++) {
1122         velocity = enemy->simVelocity;
1123         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
1124         float distance = Vector2Distance(towerPosition, futurePosition);
1125         float eta2 = distance / bulletSpeed;
1126         if (fabs(eta - eta2) < 0.01f) {
1127           break;
1128         }
1129         eta = (eta2 + eta) * 0.5f;
1130       }
1131       ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, towerPosition, futurePosition, 
1132         bulletSpeed, bulletDamage);
1133       enemy->futureDamage += bulletDamage;
1134     }
1135   }
1136   else
1137   {
1138     tower->cooldown -= gameTime.deltaTime;
1139   }
1140 }
1141 
1142 void TowerUpdate()
1143 {
1144   for (int i = 0; i < towerCount; i++)
1145   {
1146     Tower *tower = &towers[i];
1147     switch (tower->towerType)
1148     {
1149     case TOWER_TYPE_GUN:
1150       TowerGunUpdate(tower);
1151       break;
1152     }
1153   }
1154 }
1155 
1156 //# Game
1157 
1158 float nextSpawnTime = 0.0f;
1159 
1160 void InitGame()
1161 {
1162   TowerInit();
1163   EnemyInit();
1164   ProjectileInit();
1165   ParticleInit();
1166   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
1167 
1168   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
1169   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
1170 
1171   for (int i = -2; i <= 2; i += 1)
1172   {
1173     TowerTryAdd(TOWER_TYPE_WALL, i, 2);
1174     TowerTryAdd(TOWER_TYPE_WALL, i, -2);
1175     TowerTryAdd(TOWER_TYPE_WALL, -2, i);
1176   }
1177 
1178   EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4);
1179 }
1180 
1181 //# Immediate GUI functions 1182 1183 int Button(const char *text, int x, int y, int width, int height) 1184 { 1185 Rectangle bounds = {x, y, width, height}; 1186 int isPressed = 0; 1187 if (CheckCollisionPointRec(GetMousePosition(), bounds)) 1188 { 1189 DrawRectangle(x, y, width, height, GRAY); 1190 if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) 1191 { 1192 isPressed = 1; 1193 } 1194 } 1195 else 1196 { 1197 DrawRectangle(x, y, width, height, LIGHTGRAY); 1198 } 1199 Font font = GetFontDefault(); 1200 Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1); 1201 DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, BLACK); 1202 return isPressed; 1203 } 1204 1205 //# Main game loop 1206
1207 void GameUpdate() 1208 { 1209 float dt = GetFrameTime(); 1210 // cap maximum delta time to 0.1 seconds to prevent large time steps 1211 if (dt > 0.1f) dt = 0.1f; 1212 gameTime.time += dt; 1213 gameTime.deltaTime = dt; 1214 PathFindingMapUpdate(); 1215 EnemyUpdate(); 1216 TowerUpdate(); 1217 ProjectileUpdate(); 1218 ParticleUpdate(); 1219 1220 // spawn a new enemy every second 1221 if (gameTime.time >= nextSpawnTime && EnemyCount() < 50) 1222 { 1223 nextSpawnTime = gameTime.time + 0.2f; 1224 // add a new enemy at the boundary of the map 1225 int randValue = GetRandomValue(-5, 5); 1226 int randSide = GetRandomValue(0, 3); 1227 int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue; 1228 int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue; 1229 static int alternation = 0; 1230 alternation += 1; 1231 if (alternation % 3 == 0) { 1232 EnemyTryAdd(ENEMY_TYPE_MINION, 0, -5); 1233 } 1234 else if (alternation % 3 == 1) 1235 { 1236 EnemyTryAdd(ENEMY_TYPE_MINION, 0, 5); 1237 } 1238 EnemyTryAdd(ENEMY_TYPE_MINION, x, y); 1239 } 1240 } 1241 1242 int main(void) 1243 { 1244 int screenWidth, screenHeight; 1245 GetPreferredSize(&screenWidth, &screenHeight); 1246 InitWindow(screenWidth, screenHeight, "Tower defense"); 1247 SetTargetFPS(30); 1248
1249 Camera3D camera = {0}; 1250 1251 int cameraMode = 0; 1252 1253 InitGame(); 1254 1255 while (!WindowShouldClose()) 1256 { 1257 if (IsPaused()) { 1258 // canvas is not visible in browser - do nothing 1259 continue; 1260 } 1261 1262 if (cameraMode == 0) 1263 {
1264 camera.position = (Vector3){0.0f, 10.0f, -0.5f}; 1265 camera.target = (Vector3){0.0f, 0.0f, -0.5f}; 1266 camera.up = (Vector3){0.0f, 0.0f, -1.0f}; 1267 camera.fovy = 12.0f; 1268 camera.projection = CAMERA_ORTHOGRAPHIC;
1269 } 1270 else 1271 { 1272 camera.position = (Vector3){1.0f, 12.0f, 6.5f}; 1273 camera.target = (Vector3){0.0f, 0.5f, 1.0f}; 1274 camera.up = (Vector3){0.0f, 1.0f, 0.0f}; 1275 camera.fovy = 45.0f; 1276 camera.projection = CAMERA_PERSPECTIVE; 1277 } 1278
1279 BeginDrawing(); 1280 ClearBackground(DARKBLUE); 1281 1282 BeginMode3D(camera); 1283 DrawGrid(10, 1.0f); 1284 TowerDraw(); 1285 EnemyDraw(); 1286 ProjectileDraw(); 1287 PathFindingMapDraw(); 1288 ParticleDraw(); 1289 GameUpdate(); 1290 EndMode3D(); 1291 1292 const char *title = "Tower defense tutorial"; 1293 int titleWidth = MeasureText(title, 20); 1294 DrawText(title, (GetScreenWidth() - titleWidth) * 0.5f + 2, 5 + 2, 20, BLACK);
1295 DrawText(title, (GetScreenWidth() - titleWidth) * 0.5f, 5, 20, WHITE); 1296 1297 const char *buttonText = cameraMode == 0 ? "2D" : "3D"; 1298 if (Button(buttonText, 10, 10, 80, 30)) 1299 { 1300 cameraMode = !cameraMode; 1301 } 1302
1303 EndDrawing(); 1304 } 1305 1306 CloseWindow(); 1307 1308 return 0; 1309 }
  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 #endif
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif

If you scroll over the code, you'll see that the function to handle the button drawing and behavior is only 20 lines of code and the usage is also dead simple:

  1 const char *buttonText = cameraMode == 0 ? "2D" : "3D";
  2 if (Button(buttonText, 10, 10, 80, 30))
  3 {
  4   cameraMode = !cameraMode;
  5 }

The next step we need to do is to figure out how to translate the mouse position to the world grid. All we need to do is to cast a ray from the camera to the mouse position and calculate the x-z intersection with the world grid. The math is relatively simple:

  • 💾
  1 #include "td-tut-2-main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 //# Declarations
  7 
  8 #define PARTICLE_MAX_COUNT 400
  9 #define PARTICLE_TYPE_NONE 0
 10 #define PARTICLE_TYPE_EXPLOSION 1
 11 
 12 typedef struct Particle
 13 {
 14   uint8_t particleType;
 15   float spawnTime;
 16   float lifetime;
 17   Vector3 position;
 18   Vector3 velocity;
 19 } Particle;
 20 
 21 Particle particles[PARTICLE_MAX_COUNT];
 22 int particleCount = 0;
 23 
 24 #define TOWER_MAX_COUNT 400
 25 #define TOWER_TYPE_NONE 0
 26 #define TOWER_TYPE_BASE 1
 27 #define TOWER_TYPE_GUN 2
 28 #define TOWER_TYPE_WALL 3
 29 
 30 typedef struct Tower
 31 {
 32   int16_t x, y;
 33   uint8_t towerType;
 34   float cooldown;
 35   float damage;
 36 } Tower;
 37 
 38 typedef struct GameTime
 39 {
 40   float time;
 41   float deltaTime;
 42 } GameTime;
 43 
 44 GameTime gameTime = {0};
 45 
 46 Tower towers[TOWER_MAX_COUNT];
 47 int towerCount = 0;
 48 
 49 float TowerGetMaxHealth(Tower *tower);
 50 
 51 //# Particle system
 52 
 53 void ParticleInit()
 54 {
 55   for (int i = 0; i < PARTICLE_MAX_COUNT; i++)
 56   {
 57     particles[i] = (Particle){0};
 58   }
 59   particleCount = 0;
 60 }
 61 
 62 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime)
 63 {
 64   if (particleCount >= PARTICLE_MAX_COUNT)
 65   {
 66     return;
 67   }
 68 
 69   int index = -1;
 70   for (int i = 0; i < particleCount; i++)
 71   {
 72     if (particles[i].particleType == PARTICLE_TYPE_NONE)
 73     {
 74       index = i;
 75       break;
 76     }
 77   }
 78 
 79   if (index == -1)
 80   {
 81     index = particleCount++;
 82   }
 83 
 84   Particle *particle = &particles[index];
 85   particle->particleType = particleType;
 86   particle->spawnTime = gameTime.time;
 87   particle->lifetime = lifetime;
 88   particle->position = position;
 89   particle->velocity = velocity;
 90 }
 91 
 92 void ParticleUpdate()
 93 {
 94   for (int i = 0; i < particleCount; i++)
 95   {
 96     Particle *particle = &particles[i];
 97     if (particle->particleType == PARTICLE_TYPE_NONE)
 98     {
 99       continue;
100     }
101 
102     float age = gameTime.time - particle->spawnTime;
103 
104     if (particle->lifetime > age)
105     {
106       particle->position = Vector3Add(particle->position, Vector3Scale(particle->velocity, gameTime.deltaTime));
107     }
108     else {
109       particle->particleType = PARTICLE_TYPE_NONE;
110     }
111   }
112 }
113 
114 void DrawExplosionParticle(Particle *particle, float transition)
115 {
116   float size = 1.2f * (1.0f - transition);
117   Color startColor = WHITE;
118   Color endColor = RED;
119   Color color = ColorLerp(startColor, endColor, transition);
120   DrawCube(particle->position, size, size, size, color);
121 }
122 
123 void ParticleDraw()
124 {
125   for (int i = 0; i < particleCount; i++)
126   {
127     Particle particle = particles[i];
128     if (particle.particleType == PARTICLE_TYPE_NONE)
129     {
130       continue;
131     }
132 
133     float age = gameTime.time - particle.spawnTime;
134     float transition = age / particle.lifetime;
135     switch (particle.particleType)
136     {
137     case PARTICLE_TYPE_EXPLOSION:
138       DrawExplosionParticle(&particle, transition);
139       break;
140     default:
141       DrawCube(particle.position, 0.3f, 0.5f, 0.3f, RED);
142       break;
143     }
144   }
145 }
146 
147 //# Pathfinding map
148 typedef struct DeltaSrc
149 {
150   char x, y;
151 } DeltaSrc;
152 
153 typedef struct PathfindingMap
154 {
155   int width, height;
156   float scale;
157   float *distances;
158   long *towerIndex; 
159   DeltaSrc *deltaSrc;
160   float maxDistance;
161   Matrix toMapSpace;
162   Matrix toWorldSpace;
163 } PathfindingMap;
164 
165 // when we execute the pathfinding algorithm, we need to store the active nodes
166 // in a queue. Each node has a position, a distance from the start, and the
167 // position of the node that we came from.
168 typedef struct PathfindingNode
169 {
170   int16_t x, y, fromX, fromY;
171   float distance;
172 } PathfindingNode;
173 
174 // The queue is a simple array of nodes, we add nodes to the end and remove
175 // nodes from the front. We keep the array around to avoid unnecessary allocations
176 static PathfindingNode *pathfindingNodeQueue = 0;
177 static int pathfindingNodeQueueCount = 0;
178 static int pathfindingNodeQueueCapacity = 0;
179 
180 // The pathfinding map stores the distances from the castle to each cell in the map.
181 PathfindingMap pathfindingMap = {0};
182 
183 void PathfindingMapInit(int width, int height, Vector3 translate, float scale)
184 {
185   // transforming between map space and world space allows us to adapt 
186   // position and scale of the map without changing the pathfinding data
187   pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z);
188   pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale));
189   pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace);
190   pathfindingMap.width = width;
191   pathfindingMap.height = height;
192   pathfindingMap.scale = scale;
193   pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float));
194   for (int i = 0; i < width * height; i++)
195   {
196     pathfindingMap.distances[i] = -1.0f;
197   }
198 
199   pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long));
200   pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc));
201 }
202 
203 float PathFindingGetDistance(int mapX, int mapY)
204 {
205   if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height)
206   {
207     // when outside the map, we return the manhattan distance to the castle (0,0)
208     return fabsf((float)mapX) + fabsf((float)mapY);
209   }
210 
211   return pathfindingMap.distances[mapY * pathfindingMap.width + mapX];
212 }
213 
214 void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance)
215 {
216   if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity)
217   {
218     pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2;
219     // we use MemAlloc/MemRealloc to allocate memory for the queue
220     // I am not entirely sure if MemRealloc allows passing a null pointer
221     // so we check if the pointer is null and use MemAlloc in that case
222     if (pathfindingNodeQueue == 0)
223     {
224       pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
225     }
226     else
227     {
228       pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
229     }
230   }
231 
232   PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++];
233   node->x = x;
234   node->y = y;
235   node->fromX = fromX;
236   node->fromY = fromY;
237   node->distance = distance;
238 }
239 
240 PathfindingNode *PathFindingNodePop()
241 {
242   if (pathfindingNodeQueueCount == 0)
243   {
244     return 0;
245   }
246   // we return the first node in the queue; we want to return a pointer to the node
247   // so we can return 0 if the queue is empty. 
248   // We should _not_ return a pointer to the element in the list, because the list
249   // may be reallocated and the pointer would become invalid. Or the 
250   // popped element is overwritten by the next push operation.
251   // Using static here means that the variable is permanently allocated.
252   static PathfindingNode node;
253   node = pathfindingNodeQueue[0];
254   // we shift all nodes one position to the front
255   for (int i = 1; i < pathfindingNodeQueueCount; i++)
256   {
257     pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i];
258   }
259   --pathfindingNodeQueueCount;
260   return &node;
261 }
262 
263 // transform a world position to a map position in the array; 
264 // returns true if the position is inside the map
265 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY)
266 {
267   Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace);
268   *mapX = (int16_t)mapPosition.x;
269   *mapY = (int16_t)mapPosition.z;
270   return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height;
271 }
272 
273 void PathFindingMapUpdate()
274 {
275   const int castleX = 0, castleY = 0;
276   int16_t castleMapX, castleMapY;
277   if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY))
278   {
279     return;
280   }
281   int width = pathfindingMap.width, height = pathfindingMap.height;
282 
283   // reset the distances to -1
284   for (int i = 0; i < width * height; i++)
285   {
286     pathfindingMap.distances[i] = -1.0f;
287   }
288   // reset the tower indices
289   for (int i = 0; i < width * height; i++)
290   {
291     pathfindingMap.towerIndex[i] = -1;
292   }
293   // reset the delta src
294   for (int i = 0; i < width * height; i++)
295   {
296     pathfindingMap.deltaSrc[i].x = 0;
297     pathfindingMap.deltaSrc[i].y = 0;
298   }
299 
300   for (int i = 0; i < towerCount; i++)
301   {
302     Tower *tower = &towers[i];
303     if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE)
304     {
305       continue;
306     }
307     int16_t mapX, mapY;
308     // technically, if the tower cell scale is not in sync with the pathfinding map scale,
309     // this would not work correctly and needs to be refined to allow towers covering multiple cells
310     // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly
311     // one cell. For now.
312     if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY))
313     {
314       continue;
315     }
316     int index = mapY * width + mapX;
317     pathfindingMap.towerIndex[index] = i;
318   }
319 
320   // we start at the castle and add the castle to the queue
321   pathfindingMap.maxDistance = 0.0f;
322   pathfindingNodeQueueCount = 0;
323   PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f);
324   PathfindingNode *node = 0;
325   while ((node = PathFindingNodePop()))
326   {
327     if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height)
328     {
329       continue;
330     }
331     int index = node->y * width + node->x;
332     if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance)
333     {
334       continue;
335     }
336 
337     int deltaX = node->x - node->fromX;
338     int deltaY = node->y - node->fromY;
339     // even if the cell is blocked by a tower, we still may want to store the direction
340     // (though this might not be needed, IDK right now)
341     pathfindingMap.deltaSrc[index].x = (char) deltaX;
342     pathfindingMap.deltaSrc[index].y = (char) deltaY;
343 
344     // we skip nodes that are blocked by towers
345     if (pathfindingMap.towerIndex[index] >= 0)
346     {
347       node->distance += 8.0f;
348     }
349     pathfindingMap.distances[index] = node->distance;
350     pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance);
351     PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f);
352     PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f);
353     PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f);
354     PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f);
355   }
356 }
357 
358 void PathFindingMapDraw()
359 {
360   float cellSize = pathfindingMap.scale * 0.9f;
361   float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance);
362   for (int x = 0; x < pathfindingMap.width; x++)
363   {
364     for (int y = 0; y < pathfindingMap.height; y++)
365     {
366       float distance = pathfindingMap.distances[y * pathfindingMap.width + x];
367       float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f);
368       Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255};
369       Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace);
370       // animate the distance "wave" to show how the pathfinding algorithm expands
371       // from the castle
372       if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance)
373       {
374         color = BLACK;
375       }
376       DrawCube(position, cellSize, 0.1f, cellSize, color);
377     }
378   }
379 }
380 
381 Vector2 PathFindingGetGradient(Vector3 world)
382 {
383   int16_t mapX, mapY;
384   if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY))
385   {
386     DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX];
387     return (Vector2){(float)-delta.x, (float)-delta.y};
388   }
389   // fallback to a simple gradient calculation
390   float n = PathFindingGetDistance(mapX, mapY - 1);
391   float s = PathFindingGetDistance(mapX, mapY + 1);
392   float w = PathFindingGetDistance(mapX - 1, mapY);
393   float e = PathFindingGetDistance(mapX + 1, mapY);
394   return (Vector2){w - e + 0.25f, n - s + 0.125f};
395 }
396 
397 //# Enemies
398 
399 #define ENEMY_MAX_PATH_COUNT 8
400 #define ENEMY_MAX_COUNT 400
401 #define ENEMY_TYPE_NONE 0
402 #define ENEMY_TYPE_MINION 1
403 
404 typedef struct EnemyId
405 {
406   uint16_t index;
407   uint16_t generation;
408 } EnemyId;
409 
410 typedef struct EnemyClassConfig
411 {
412   float speed;
413   float health;
414   float radius;
415   float maxAcceleration;
416   float requiredContactTime;
417   float explosionDamage;
418   float explosionRange;
419   float explosionPushbackPower;
420 } EnemyClassConfig;
421 
422 typedef struct Enemy
423 {
424   int16_t currentX, currentY;
425   int16_t nextX, nextY;
426   Vector2 simPosition;
427   Vector2 simVelocity;
428   uint16_t generation;
429   float startMovingTime;
430   float damage, futureDamage;
431   float contactTime;
432   uint8_t enemyType;
433   uint8_t movePathCount;
434   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
435 } Enemy;
436 
437 Enemy enemies[ENEMY_MAX_COUNT];
438 int enemyCount = 0;
439 
440 EnemyClassConfig enemyClassConfigs[] = {
441     [ENEMY_TYPE_MINION] = {
442       .health = 3.0f, 
443       .speed = 1.0f, 
444       .radius = 0.25f, 
445       .maxAcceleration = 1.0f,
446       .explosionDamage = 1.0f,
447       .requiredContactTime = 0.5f,
448       .explosionRange = 1.0f,
449       .explosionPushbackPower = 0.25f,
450     },
451 };
452 
453 int EnemyAddDamage(Enemy *enemy, float damage);
454 
455 void EnemyInit()
456 {
457   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
458   {
459     enemies[i] = (Enemy){0};
460   }
461   enemyCount = 0;
462 }
463 
464 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
465 {
466   return enemyClassConfigs[enemy->enemyType].speed;
467 }
468 
469 float EnemyGetMaxHealth(Enemy *enemy)
470 {
471   return enemyClassConfigs[enemy->enemyType].health;
472 }
473 
474 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
475 {
476   int16_t castleX = 0;
477   int16_t castleY = 0;
478   int16_t dx = castleX - currentX;
479   int16_t dy = castleY - currentY;
480   if (dx == 0 && dy == 0)
481   {
482     *nextX = currentX;
483     *nextY = currentY;
484     return 1;
485   }
486   Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY});
487 
488   if (gradient.x == 0 && gradient.y == 0)
489   {
490     *nextX = currentX;
491     *nextY = currentY;
492     return 1;
493   }
494 
495   if (fabsf(gradient.x) > fabsf(gradient.y))
496   {
497     *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1);
498     *nextY = currentY;
499     return 0;
500   }
501   *nextX = currentX;
502   *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1);
503   return 0;
504 }
505 
506 
507 // this function predicts the movement of the unit for the next deltaT seconds
508 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
509 {
510   const float pointReachedDistance = 0.25f;
511   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
512   const float maxSimStepTime = 0.015625f;
513   
514   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
515   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
516   int16_t nextX = enemy->nextX;
517   int16_t nextY = enemy->nextY;
518   Vector2 position = enemy->simPosition;
519   int passedCount = 0;
520   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
521   {
522     float stepTime = fminf(deltaT - t, maxSimStepTime);
523     Vector2 target = (Vector2){nextX, nextY};
524     float speed = Vector2Length(*velocity);
525     // draw the target position for debugging
526     DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
527     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
528     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
529     {
530       // we reached the target position, let's move to the next waypoint
531       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
532       target = (Vector2){nextX, nextY};
533       // track how many waypoints we passed
534       passedCount++;
535     }
536     
537     // acceleration towards the target
538     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
539     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
540     *velocity = Vector2Add(*velocity, acceleration);
541 
542     // limit the speed to the maximum speed
543     if (speed > maxSpeed)
544     {
545       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
546     }
547 
548     // move the enemy
549     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
550   }
551 
552   if (waypointPassedCount)
553   {
554     (*waypointPassedCount) = passedCount;
555   }
556 
557   return position;
558 }
559 
560 void EnemyDraw()
561 {
562   for (int i = 0; i < enemyCount; i++)
563   {
564     Enemy enemy = enemies[i];
565     if (enemy.enemyType == ENEMY_TYPE_NONE)
566     {
567       continue;
568     }
569 
570     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
571     
572     if (enemy.movePathCount > 0)
573     {
574       Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
575       DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
576     }
577     for (int j = 1; j < enemy.movePathCount; j++)
578     {
579       Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
580       Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
581       DrawLine3D(p, q, GREEN);
582     }
583 
584     switch (enemy.enemyType)
585     {
586     case ENEMY_TYPE_MINION:
587       DrawCubeWires((Vector3){position.x, 0.2f, position.y}, 0.4f, 0.4f, 0.4f, GREEN);
588       break;
589     }
590   }
591 }
592 
593 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource)
594 {
595   // damage the tower
596   float explosionDamge = enemyClassConfigs[enemy->enemyType].explosionDamage;
597   float explosionRange = enemyClassConfigs[enemy->enemyType].explosionRange;
598   float explosionPushbackPower = enemyClassConfigs[enemy->enemyType].explosionPushbackPower;
599   float explosionRange2 = explosionRange * explosionRange;
600   tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage;
601   // explode the enemy
602   if (tower->damage >= TowerGetMaxHealth(tower))
603   {
604     tower->towerType = TOWER_TYPE_NONE;
605   }
606 
607   ParticleAdd(PARTICLE_TYPE_EXPLOSION, 
608     explosionSource, 
609     (Vector3){0, 0.1f, 0}, 1.0f);
610 
611   enemy->enemyType = ENEMY_TYPE_NONE;
612 
613   // push back enemies & dealing damage
614   for (int i = 0; i < enemyCount; i++)
615   {
616     Enemy *other = &enemies[i];
617     if (other->enemyType == ENEMY_TYPE_NONE)
618     {
619       continue;
620     }
621     float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition);
622     if (distanceSqr > 0 && distanceSqr < explosionRange2)
623     {
624       Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition));
625       other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower));
626       EnemyAddDamage(other, explosionDamge);
627     }
628   }
629 }
630 
631 void EnemyUpdate()
632 {
633   const float castleX = 0;
634   const float castleY = 0;
635   const float maxPathDistance2 = 0.25f * 0.25f;
636   
637   for (int i = 0; i < enemyCount; i++)
638   {
639     Enemy *enemy = &enemies[i];
640     if (enemy->enemyType == ENEMY_TYPE_NONE)
641     {
642       continue;
643     }
644 
645     int waypointPassedCount = 0;
646     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
647     enemy->startMovingTime = gameTime.time;
648     // track path of unit
649     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
650     {
651       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
652       {
653         enemy->movePath[j] = enemy->movePath[j - 1];
654       }
655       enemy->movePath[0] = enemy->simPosition;
656       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
657       {
658         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
659       }
660     }
661 
662     if (waypointPassedCount > 0)
663     {
664       enemy->currentX = enemy->nextX;
665       enemy->currentY = enemy->nextY;
666       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) &&
667         Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
668       {
669         // enemy reached the castle; remove it
670         enemy->enemyType = ENEMY_TYPE_NONE;
671         continue;
672       }
673     }
674   }
675 
676   // handle collisions between enemies
677   for (int i = 0; i < enemyCount - 1; i++)
678   {
679     Enemy *enemyA = &enemies[i];
680     if (enemyA->enemyType == ENEMY_TYPE_NONE)
681     {
682       continue;
683     }
684     for (int j = i + 1; j < enemyCount; j++)
685     {
686       Enemy *enemyB = &enemies[j];
687       if (enemyB->enemyType == ENEMY_TYPE_NONE)
688       {
689         continue;
690       }
691       float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition);
692       float radiusA = enemyClassConfigs[enemyA->enemyType].radius;
693       float radiusB = enemyClassConfigs[enemyB->enemyType].radius;
694       float radiusSum = radiusA + radiusB;
695       if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f)
696       {
697         // collision
698         float distance = sqrtf(distanceSqr);
699         float overlap = radiusSum - distance;
700         // move the enemies apart, but softly; if we have a clog of enemies,
701         // moving them perfectly apart can cause them to jitter
702         float positionCorrection = overlap / 5.0f;
703         Vector2 direction = (Vector2){
704             (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection,
705             (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection};
706         enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction);
707         enemyB->simPosition = Vector2Add(enemyB->simPosition, direction);
708       }
709     }
710   }
711 
712   // handle collisions between enemies and towers
713   for (int i = 0; i < enemyCount; i++)
714   {
715     Enemy *enemy = &enemies[i];
716     if (enemy->enemyType == ENEMY_TYPE_NONE)
717     {
718       continue;
719     }
720     enemy->contactTime -= gameTime.deltaTime;
721     if (enemy->contactTime < 0.0f)
722     {
723       enemy->contactTime = 0.0f;
724     }
725 
726     float enemyRadius = enemyClassConfigs[enemy->enemyType].radius;
727     // linear search over towers; could be optimized by using path finding tower map,
728     // but for now, we keep it simple
729     for (int j = 0; j < towerCount; j++)
730     {
731       Tower *tower = &towers[j];
732       if (tower->towerType == TOWER_TYPE_NONE)
733       {
734         continue;
735       }
736       float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y});
737       float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1
738       if (distanceSqr > combinedRadius * combinedRadius)
739       {
740         continue;
741       }
742       // potential collision; square / circle intersection
743       float dx = tower->x - enemy->simPosition.x;
744       float dy = tower->y - enemy->simPosition.y;
745       float absDx = fabsf(dx);
746       float absDy = fabsf(dy);
747       Vector3 contactPoint = {0};
748       if (absDx <= 0.5f && absDx <= absDy) {
749         // vertical collision; push the enemy out horizontally
750         float overlap = enemyRadius + 0.5f - absDy;
751         if (overlap < 0.0f)
752         {
753           continue;
754         }
755         float direction = dy > 0.0f ? -1.0f : 1.0f;
756         enemy->simPosition.y += direction * overlap;
757         contactPoint = (Vector3){enemy->simPosition.x, 0.2f, tower->x + direction * 0.5f};
758       }
759       else if (absDy <= 0.5f && absDy <= absDx)
760       {
761         // horizontal collision; push the enemy out vertically
762         float overlap = enemyRadius + 0.5f - absDx;
763         if (overlap < 0.0f)
764         {
765           continue;
766         }
767         float direction = dx > 0.0f ? -1.0f : 1.0f;
768         enemy->simPosition.x += direction * overlap;
769         contactPoint = (Vector3){tower->x + direction * 0.5f, 0.2f, enemy->simPosition.y};
770       }
771       else
772       {
773         // possible collision with a corner
774         float cornerDX = dx > 0.0f ? -0.5f : 0.5f;
775         float cornerDY = dy > 0.0f ? -0.5f : 0.5f;
776         float cornerX = tower->x + cornerDX;
777         float cornerY = tower->y + cornerDY;
778         float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY});
779         if (cornerDistanceSqr > enemyRadius * enemyRadius)
780         {
781           continue;
782         }
783         // push the enemy out along the diagonal
784         float cornerDistance = sqrtf(cornerDistanceSqr);
785         float overlap = enemyRadius - cornerDistance;
786         float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX;
787         float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY;
788         enemy->simPosition.x -= directionX * overlap;
789         enemy->simPosition.y -= directionY * overlap;
790         contactPoint = (Vector3){cornerX, 0.2f, cornerY};
791       }
792 
793       if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f)
794       {
795         enemy->contactTime += gameTime.deltaTime * 2.0f; // * 2 to undo the subtraction above
796         if (enemy->contactTime >= enemyClassConfigs[enemy->enemyType].requiredContactTime)
797         {
798           EnemyTriggerExplode(enemy, tower, contactPoint);
799         }
800       }
801     }
802   }
803 }
804 
805 EnemyId EnemyGetId(Enemy *enemy)
806 {
807   return (EnemyId){enemy - enemies, enemy->generation};
808 }
809 
810 Enemy *EnemyTryResolve(EnemyId enemyId)
811 {
812   if (enemyId.index >= ENEMY_MAX_COUNT)
813   {
814     return 0;
815   }
816   Enemy *enemy = &enemies[enemyId.index];
817   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
818   {
819     return 0;
820   }
821   return enemy;
822 }
823 
824 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
825 {
826   Enemy *spawn = 0;
827   for (int i = 0; i < enemyCount; i++)
828   {
829     Enemy *enemy = &enemies[i];
830     if (enemy->enemyType == ENEMY_TYPE_NONE)
831     {
832       spawn = enemy;
833       break;
834     }
835   }
836 
837   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
838   {
839     spawn = &enemies[enemyCount++];
840   }
841 
842   if (spawn)
843   {
844     spawn->currentX = currentX;
845     spawn->currentY = currentY;
846     spawn->nextX = currentX;
847     spawn->nextY = currentY;
848     spawn->simPosition = (Vector2){currentX, currentY};
849     spawn->simVelocity = (Vector2){0, 0};
850     spawn->enemyType = enemyType;
851     spawn->startMovingTime = gameTime.time;
852     spawn->damage = 0.0f;
853     spawn->futureDamage = 0.0f;
854     spawn->generation++;
855     spawn->movePathCount = 0;
856   }
857 
858   return spawn;
859 }
860 
861 int EnemyAddDamage(Enemy *enemy, float damage)
862 {
863   enemy->damage += damage;
864   if (enemy->damage >= EnemyGetMaxHealth(enemy))
865   {
866     enemy->enemyType = ENEMY_TYPE_NONE;
867     return 1;
868   }
869 
870   return 0;
871 }
872 
873 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
874 {
875   int16_t castleX = 0;
876   int16_t castleY = 0;
877   Enemy* closest = 0;
878   int16_t closestDistance = 0;
879   float range2 = range * range;
880   for (int i = 0; i < enemyCount; i++)
881   {
882     Enemy* enemy = &enemies[i];
883     if (enemy->enemyType == ENEMY_TYPE_NONE)
884     {
885       continue;
886     }
887     float maxHealth = EnemyGetMaxHealth(enemy);
888     if (enemy->futureDamage >= maxHealth)
889     {
890       // ignore enemies that will die soon
891       continue;
892     }
893     int16_t dx = castleX - enemy->currentX;
894     int16_t dy = castleY - enemy->currentY;
895     int16_t distance = abs(dx) + abs(dy);
896     if (!closest || distance < closestDistance)
897     {
898       float tdx = towerX - enemy->currentX;
899       float tdy = towerY - enemy->currentY;
900       float tdistance2 = tdx * tdx + tdy * tdy;
901       if (tdistance2 <= range2)
902       {
903         closest = enemy;
904         closestDistance = distance;
905       }
906     }
907   }
908   return closest;
909 }
910 
911 int EnemyCount()
912 {
913   int count = 0;
914   for (int i = 0; i < enemyCount; i++)
915   {
916     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
917     {
918       count++;
919     }
920   }
921   return count;
922 }
923 
924 //# Projectiles
925 #define PROJECTILE_MAX_COUNT 1200
926 #define PROJECTILE_TYPE_NONE 0
927 #define PROJECTILE_TYPE_BULLET 1
928 
929 typedef struct Projectile
930 {
931   uint8_t projectileType;
932   float shootTime;
933   float arrivalTime;
934   float damage;
935   Vector2 position;
936   Vector2 target;
937   Vector2 directionNormal;
938   EnemyId targetEnemy;
939 } Projectile;
940 
941 Projectile projectiles[PROJECTILE_MAX_COUNT];
942 int projectileCount = 0;
943 
944 void ProjectileInit()
945 {
946   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
947   {
948     projectiles[i] = (Projectile){0};
949   }
950 }
951 
952 void ProjectileDraw()
953 {
954   for (int i = 0; i < projectileCount; i++)
955   {
956     Projectile projectile = projectiles[i];
957     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
958     {
959       continue;
960     }
961     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
962     if (transition >= 1.0f)
963     {
964       continue;
965     }
966     Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition);
967     float x = position.x;
968     float y = position.y;
969     float dx = projectile.directionNormal.x;
970     float dy = projectile.directionNormal.y;
971     for (float d = 1.0f; d > 0.0f; d -= 0.25f)
972     {
973       x -= dx * 0.1f;
974       y -= dy * 0.1f;
975       float size = 0.1f * d;
976       DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED);
977     }
978   }
979 }
980 
981 void ProjectileUpdate()
982 {
983   for (int i = 0; i < projectileCount; i++)
984   {
985     Projectile *projectile = &projectiles[i];
986     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
987     {
988       continue;
989     }
990     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
991     if (transition >= 1.0f)
992     {
993       projectile->projectileType = PROJECTILE_TYPE_NONE;
994       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
995       if (enemy)
996       {
997         EnemyAddDamage(enemy, projectile->damage);
998       }
999       continue;
1000     }
1001   }
1002 }
1003 
1004 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage)
1005 {
1006   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
1007   {
1008     Projectile *projectile = &projectiles[i];
1009     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
1010     {
1011       projectile->projectileType = projectileType;
1012       projectile->shootTime = gameTime.time;
1013       projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed;
1014       projectile->damage = damage;
1015       projectile->position = position;
1016       projectile->target = target;
1017       projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position));
1018       projectile->targetEnemy = EnemyGetId(enemy);
1019       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
1020       return projectile;
1021     }
1022   }
1023   return 0;
1024 }
1025 
1026 //# Towers
1027 
1028 void TowerInit()
1029 {
1030   for (int i = 0; i < TOWER_MAX_COUNT; i++)
1031   {
1032     towers[i] = (Tower){0};
1033   }
1034   towerCount = 0;
1035 }
1036 
1037 Tower *TowerGetAt(int16_t x, int16_t y)
1038 {
1039   for (int i = 0; i < towerCount; i++)
1040   {
1041     if (towers[i].x == x && towers[i].y == y)
1042     {
1043       return &towers[i];
1044     }
1045   }
1046   return 0;
1047 }
1048 
1049 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
1050 {
1051   if (towerCount >= TOWER_MAX_COUNT)
1052   {
1053     return 0;
1054   }
1055 
1056   Tower *tower = TowerGetAt(x, y);
1057   if (tower)
1058   {
1059     return 0;
1060   }
1061 
1062   tower = &towers[towerCount++];
1063   tower->x = x;
1064   tower->y = y;
1065   tower->towerType = towerType;
1066   tower->cooldown = 0.0f;
1067   tower->damage = 0.0f;
1068   return tower;
1069 }
1070 
1071 float TowerGetMaxHealth(Tower *tower)
1072 {
1073   switch (tower->towerType)
1074   {
1075   case TOWER_TYPE_BASE:
1076     return 10.0f;
1077   case TOWER_TYPE_GUN:
1078     return 3.0f;
1079   case TOWER_TYPE_WALL:
1080     return 5.0f;
1081   }
1082   return 0.0f;
1083 }
1084 
1085 void TowerDraw()
1086 {
1087   for (int i = 0; i < towerCount; i++)
1088   {
1089     Tower tower = towers[i];
1090     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
1091     switch (tower.towerType)
1092     {
1093     case TOWER_TYPE_BASE:
1094       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
1095       break;
1096     case TOWER_TYPE_GUN:
1097       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
1098       break;
1099     case TOWER_TYPE_WALL:
1100       DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
1101       break;
1102     }
1103   }
1104 }
1105 
1106 void TowerGunUpdate(Tower *tower)
1107 {
1108   if (tower->cooldown <= 0)
1109   {
1110     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
1111     if (enemy)
1112     {
1113       tower->cooldown = 0.125f;
1114       // shoot the enemy; determine future position of the enemy
1115       float bulletSpeed = 1.0f;
1116       float bulletDamage = 3.0f;
1117       Vector2 velocity = enemy->simVelocity;
1118       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
1119       Vector2 towerPosition = {tower->x, tower->y};
1120       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
1121       for (int i = 0; i < 8; i++) {
1122         velocity = enemy->simVelocity;
1123         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
1124         float distance = Vector2Distance(towerPosition, futurePosition);
1125         float eta2 = distance / bulletSpeed;
1126         if (fabs(eta - eta2) < 0.01f) {
1127           break;
1128         }
1129         eta = (eta2 + eta) * 0.5f;
1130       }
1131       ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, towerPosition, futurePosition, 
1132         bulletSpeed, bulletDamage);
1133       enemy->futureDamage += bulletDamage;
1134     }
1135   }
1136   else
1137   {
1138     tower->cooldown -= gameTime.deltaTime;
1139   }
1140 }
1141 
1142 void TowerUpdate()
1143 {
1144   for (int i = 0; i < towerCount; i++)
1145   {
1146     Tower *tower = &towers[i];
1147     switch (tower->towerType)
1148     {
1149     case TOWER_TYPE_GUN:
1150       TowerGunUpdate(tower);
1151       break;
1152     }
1153   }
1154 }
1155 
1156 //# Game
1157 
1158 float nextSpawnTime = 0.0f;
1159 
1160 void InitGame()
1161 {
1162   TowerInit();
1163   EnemyInit();
1164   ProjectileInit();
1165   ParticleInit();
1166   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
1167 
1168   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
1169   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
1170 
1171   for (int i = -2; i <= 2; i += 1)
1172   {
1173     TowerTryAdd(TOWER_TYPE_WALL, i, 2);
1174     TowerTryAdd(TOWER_TYPE_WALL, i, -2);
1175     TowerTryAdd(TOWER_TYPE_WALL, -2, i);
1176   }
1177 
1178   EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4);
1179 }
1180 
1181 //# Immediate GUI functions
1182 
1183 int Button(const char *text, int x, int y, int width, int height)
1184 {
1185   Rectangle bounds = {x, y, width, height};
1186   int isPressed = 0;
1187   if (CheckCollisionPointRec(GetMousePosition(), bounds))
1188   {
1189     DrawRectangle(x, y, width, height, GRAY);
1190     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
1191     {
1192       isPressed = 1;
1193     }
1194   }
1195   else
1196   {
1197     DrawRectangle(x, y, width, height, LIGHTGRAY);
1198   }
1199   Font font = GetFontDefault();
1200   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
1201   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, BLACK);
1202   return isPressed;
1203 }
1204 
1205 //# Main game loop
1206 
1207 void GameUpdate()
1208 {
1209   float dt = GetFrameTime();
1210   // cap maximum delta time to 0.1 seconds to prevent large time steps
1211   if (dt > 0.1f) dt = 0.1f;
1212   gameTime.time += dt;
1213   gameTime.deltaTime = dt;
1214   PathFindingMapUpdate();
1215   EnemyUpdate();
1216   TowerUpdate();
1217   ProjectileUpdate();
1218   ParticleUpdate();
1219 
1220   // spawn a new enemy every second
1221   if (gameTime.time >= nextSpawnTime && EnemyCount() < 50)
1222   {
1223     nextSpawnTime = gameTime.time + 0.2f;
1224     // add a new enemy at the boundary of the map
1225     int randValue = GetRandomValue(-5, 5);
1226     int randSide = GetRandomValue(0, 3);
1227     int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue;
1228     int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue;
1229     static int alternation = 0;
1230     alternation += 1;
1231     if (alternation % 3 == 0) {
1232       EnemyTryAdd(ENEMY_TYPE_MINION, 0, -5);
1233     }
1234     else if (alternation % 3 == 1)
1235     {
1236       EnemyTryAdd(ENEMY_TYPE_MINION, 0, 5);
1237     }
1238     EnemyTryAdd(ENEMY_TYPE_MINION, x, y);
1239   }
1240 }
1241 
1242 int main(void)
1243 {
1244   int screenWidth, screenHeight;
1245   GetPreferredSize(&screenWidth, &screenHeight);
1246   InitWindow(screenWidth, screenHeight, "Tower defense");
1247   SetTargetFPS(30);
1248 
1249   Camera3D camera = {0};
1250 
1251   int cameraMode = 0;
1252 
1253   InitGame();
1254 
1255   while (!WindowShouldClose())
1256   {
1257     if (IsPaused()) {
1258       // canvas is not visible in browser - do nothing
1259       continue;
1260     }
1261 
1262     if (cameraMode == 0)
1263     {
1264       camera.position = (Vector3){0.0f, 10.0f, -0.5f};
1265       camera.target = (Vector3){0.0f, 0.0f, -0.5f};
1266       camera.up = (Vector3){0.0f, 0.0f, -1.0f};
1267       camera.fovy = 12.0f;
1268       camera.projection = CAMERA_ORTHOGRAPHIC;
1269     }
1270     else 
1271     {
1272       camera.position = (Vector3){1.0f, 12.0f, 6.5f};
1273       camera.target = (Vector3){0.0f, 0.5f, 1.0f};
1274       camera.up = (Vector3){0.0f, 1.0f, 0.0f};
1275       camera.fovy = 45.0f;
1276       camera.projection = CAMERA_PERSPECTIVE;
1277     }
1278 
1279     BeginDrawing();
1280     ClearBackground(DARKBLUE);
1281 
1282     BeginMode3D(camera);
1283     DrawGrid(10, 1.0f);
1284     TowerDraw();
1285     EnemyDraw();
1286     ProjectileDraw();
1287 // PathFindingMapDraw();
1288 ParticleDraw();
1289 GameUpdate(); 1290 1291 Ray ray = GetScreenToWorldRay(GetMousePosition(), camera); 1292 float planeDistance = ray.position.y / -ray.direction.y; 1293 float planeX = ray.direction.x * planeDistance + ray.position.x; 1294 float planeY = ray.direction.z * planeDistance + ray.position.z; 1295 int16_t mapX = (int16_t)floorf(planeX + 0.5f); 1296 int16_t mapY = (int16_t)floorf(planeY + 0.5f); 1297 DrawCubeWires((Vector3){mapX, 0.2f, mapY}, 1.0f, 0.4f, 1.0f, RED); 1298
1299 EndMode3D(); 1300 1301 const char *title = "Tower defense tutorial"; 1302 int titleWidth = MeasureText(title, 20); 1303 DrawText(title, (GetScreenWidth() - titleWidth) * 0.5f + 2, 5 + 2, 20, BLACK); 1304 DrawText(title, (GetScreenWidth() - titleWidth) * 0.5f, 5, 20, WHITE); 1305 1306 const char *buttonText = cameraMode == 0 ? "2D" : "3D"; 1307 if (Button(buttonText, 10, 10, 80, 30)) 1308 { 1309 cameraMode = !cameraMode; 1310 } 1311 1312 EndDrawing(); 1313 } 1314 1315 CloseWindow(); 1316 1317 return 0; 1318 }
  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 #endif
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif

This is the first interactbale part of the game: You can now switch between 2D and 3D mode and hover the mouse over the grid to see the projected mouse position.

The relevant code is this piece here, which is a line-plane intersection calculation for the special case of the x-z plane at height 0:

  1 Ray ray = GetScreenToWorldRay(GetMousePosition(), camera);
  2 float planeDistance = ray.position.y / -ray.direction.y;
  3 float planeX = ray.direction.x * planeDistance + ray.position.x;
  4 float planeY = ray.direction.z * planeDistance + ray.position.z;

The variables planeX and planeY are the projected mouse coordinates on the x-z plane at the height 0. Here's an explanation how it works: The distance for the projection of the direction vector is obtained by "looking" how often the vertical component of the ray direction fits into the height of the ray's origin. We then multiply the direction vector with this distance and add the origin to get the projected point.

A simple case is, when we look down from the top: Then the y component is 1, but the x and z components are 0 - so the projection is just the y component of the ray's origin.

Here's a visualization of how the calculation works when seen from the side - which shows that it's nothing else than the intercept theorem:

It's time now to add the building placement. On the left side, we'll have a list of buildings we can place. When we click on a building button, we set it to active and when clicking on the map, this building will be placed and the button will be deactivated. Clicking an activated button again will deactivate it:

  • 💾
  1 #include "td-tut-2-main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 //# Declarations
  7 
  8 #define PARTICLE_MAX_COUNT 400
  9 #define PARTICLE_TYPE_NONE 0
 10 #define PARTICLE_TYPE_EXPLOSION 1
 11 
 12 typedef struct Particle
 13 {
 14   uint8_t particleType;
 15   float spawnTime;
 16   float lifetime;
 17   Vector3 position;
 18   Vector3 velocity;
 19 } Particle;
 20 
 21 Particle particles[PARTICLE_MAX_COUNT];
 22 int particleCount = 0;
 23 
 24 #define TOWER_MAX_COUNT 400
 25 #define TOWER_TYPE_NONE 0
 26 #define TOWER_TYPE_BASE 1
 27 #define TOWER_TYPE_GUN 2
 28 #define TOWER_TYPE_WALL 3
 29 
 30 typedef struct Tower
 31 {
 32   int16_t x, y;
 33   uint8_t towerType;
 34   float cooldown;
 35   float damage;
 36 } Tower;
 37 
 38 typedef struct GameTime
 39 {
 40   float time;
 41   float deltaTime;
 42 } GameTime;
 43 
 44 GameTime gameTime = {0};
 45 
 46 Tower towers[TOWER_MAX_COUNT];
 47 int towerCount = 0;
 48 
 49 float TowerGetMaxHealth(Tower *tower);
 50 
 51 //# Particle system
 52 
 53 void ParticleInit()
 54 {
 55   for (int i = 0; i < PARTICLE_MAX_COUNT; i++)
 56   {
 57     particles[i] = (Particle){0};
 58   }
 59   particleCount = 0;
 60 }
 61 
 62 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime)
 63 {
 64   if (particleCount >= PARTICLE_MAX_COUNT)
 65   {
 66     return;
 67   }
 68 
 69   int index = -1;
 70   for (int i = 0; i < particleCount; i++)
 71   {
 72     if (particles[i].particleType == PARTICLE_TYPE_NONE)
 73     {
 74       index = i;
 75       break;
 76     }
 77   }
 78 
 79   if (index == -1)
 80   {
 81     index = particleCount++;
 82   }
 83 
 84   Particle *particle = &particles[index];
 85   particle->particleType = particleType;
 86   particle->spawnTime = gameTime.time;
 87   particle->lifetime = lifetime;
 88   particle->position = position;
 89   particle->velocity = velocity;
 90 }
 91 
 92 void ParticleUpdate()
 93 {
 94   for (int i = 0; i < particleCount; i++)
 95   {
 96     Particle *particle = &particles[i];
 97     if (particle->particleType == PARTICLE_TYPE_NONE)
 98     {
 99       continue;
100     }
101 
102     float age = gameTime.time - particle->spawnTime;
103 
104     if (particle->lifetime > age)
105     {
106       particle->position = Vector3Add(particle->position, Vector3Scale(particle->velocity, gameTime.deltaTime));
107     }
108     else {
109       particle->particleType = PARTICLE_TYPE_NONE;
110     }
111   }
112 }
113 
114 void DrawExplosionParticle(Particle *particle, float transition)
115 {
116   float size = 1.2f * (1.0f - transition);
117   Color startColor = WHITE;
118   Color endColor = RED;
119   Color color = ColorLerp(startColor, endColor, transition);
120   DrawCube(particle->position, size, size, size, color);
121 }
122 
123 void ParticleDraw()
124 {
125   for (int i = 0; i < particleCount; i++)
126   {
127     Particle particle = particles[i];
128     if (particle.particleType == PARTICLE_TYPE_NONE)
129     {
130       continue;
131     }
132 
133     float age = gameTime.time - particle.spawnTime;
134     float transition = age / particle.lifetime;
135     switch (particle.particleType)
136     {
137     case PARTICLE_TYPE_EXPLOSION:
138       DrawExplosionParticle(&particle, transition);
139       break;
140     default:
141       DrawCube(particle.position, 0.3f, 0.5f, 0.3f, RED);
142       break;
143     }
144   }
145 }
146 
147 //# Pathfinding map
148 typedef struct DeltaSrc
149 {
150   char x, y;
151 } DeltaSrc;
152 
153 typedef struct PathfindingMap
154 {
155   int width, height;
156   float scale;
157   float *distances;
158   long *towerIndex; 
159   DeltaSrc *deltaSrc;
160   float maxDistance;
161   Matrix toMapSpace;
162   Matrix toWorldSpace;
163 } PathfindingMap;
164 
165 // when we execute the pathfinding algorithm, we need to store the active nodes
166 // in a queue. Each node has a position, a distance from the start, and the
167 // position of the node that we came from.
168 typedef struct PathfindingNode
169 {
170   int16_t x, y, fromX, fromY;
171   float distance;
172 } PathfindingNode;
173 
174 // The queue is a simple array of nodes, we add nodes to the end and remove
175 // nodes from the front. We keep the array around to avoid unnecessary allocations
176 static PathfindingNode *pathfindingNodeQueue = 0;
177 static int pathfindingNodeQueueCount = 0;
178 static int pathfindingNodeQueueCapacity = 0;
179 
180 // The pathfinding map stores the distances from the castle to each cell in the map.
181 PathfindingMap pathfindingMap = {0};
182 
183 void PathfindingMapInit(int width, int height, Vector3 translate, float scale)
184 {
185   // transforming between map space and world space allows us to adapt 
186   // position and scale of the map without changing the pathfinding data
187   pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z);
188   pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale));
189   pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace);
190   pathfindingMap.width = width;
191   pathfindingMap.height = height;
192   pathfindingMap.scale = scale;
193   pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float));
194   for (int i = 0; i < width * height; i++)
195   {
196     pathfindingMap.distances[i] = -1.0f;
197   }
198 
199   pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long));
200   pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc));
201 }
202 
203 float PathFindingGetDistance(int mapX, int mapY)
204 {
205   if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height)
206   {
207     // when outside the map, we return the manhattan distance to the castle (0,0)
208     return fabsf((float)mapX) + fabsf((float)mapY);
209   }
210 
211   return pathfindingMap.distances[mapY * pathfindingMap.width + mapX];
212 }
213 
214 void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance)
215 {
216   if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity)
217   {
218     pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2;
219     // we use MemAlloc/MemRealloc to allocate memory for the queue
220     // I am not entirely sure if MemRealloc allows passing a null pointer
221     // so we check if the pointer is null and use MemAlloc in that case
222     if (pathfindingNodeQueue == 0)
223     {
224       pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
225     }
226     else
227     {
228       pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
229     }
230   }
231 
232   PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++];
233   node->x = x;
234   node->y = y;
235   node->fromX = fromX;
236   node->fromY = fromY;
237   node->distance = distance;
238 }
239 
240 PathfindingNode *PathFindingNodePop()
241 {
242   if (pathfindingNodeQueueCount == 0)
243   {
244     return 0;
245   }
246   // we return the first node in the queue; we want to return a pointer to the node
247   // so we can return 0 if the queue is empty. 
248   // We should _not_ return a pointer to the element in the list, because the list
249   // may be reallocated and the pointer would become invalid. Or the 
250   // popped element is overwritten by the next push operation.
251   // Using static here means that the variable is permanently allocated.
252   static PathfindingNode node;
253   node = pathfindingNodeQueue[0];
254   // we shift all nodes one position to the front
255   for (int i = 1; i < pathfindingNodeQueueCount; i++)
256   {
257     pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i];
258   }
259   --pathfindingNodeQueueCount;
260   return &node;
261 }
262 
263 // transform a world position to a map position in the array; 
264 // returns true if the position is inside the map
265 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY)
266 {
267   Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace);
268   *mapX = (int16_t)mapPosition.x;
269   *mapY = (int16_t)mapPosition.z;
270   return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height;
271 }
272 
273 void PathFindingMapUpdate()
274 {
275   const int castleX = 0, castleY = 0;
276   int16_t castleMapX, castleMapY;
277   if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY))
278   {
279     return;
280   }
281   int width = pathfindingMap.width, height = pathfindingMap.height;
282 
283   // reset the distances to -1
284   for (int i = 0; i < width * height; i++)
285   {
286     pathfindingMap.distances[i] = -1.0f;
287   }
288   // reset the tower indices
289   for (int i = 0; i < width * height; i++)
290   {
291     pathfindingMap.towerIndex[i] = -1;
292   }
293   // reset the delta src
294   for (int i = 0; i < width * height; i++)
295   {
296     pathfindingMap.deltaSrc[i].x = 0;
297     pathfindingMap.deltaSrc[i].y = 0;
298   }
299 
300   for (int i = 0; i < towerCount; i++)
301   {
302     Tower *tower = &towers[i];
303     if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE)
304     {
305       continue;
306     }
307     int16_t mapX, mapY;
308     // technically, if the tower cell scale is not in sync with the pathfinding map scale,
309     // this would not work correctly and needs to be refined to allow towers covering multiple cells
310     // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly
311     // one cell. For now.
312     if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY))
313     {
314       continue;
315     }
316     int index = mapY * width + mapX;
317     pathfindingMap.towerIndex[index] = i;
318   }
319 
320   // we start at the castle and add the castle to the queue
321   pathfindingMap.maxDistance = 0.0f;
322   pathfindingNodeQueueCount = 0;
323   PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f);
324   PathfindingNode *node = 0;
325   while ((node = PathFindingNodePop()))
326   {
327     if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height)
328     {
329       continue;
330     }
331     int index = node->y * width + node->x;
332     if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance)
333     {
334       continue;
335     }
336 
337     int deltaX = node->x - node->fromX;
338     int deltaY = node->y - node->fromY;
339     // even if the cell is blocked by a tower, we still may want to store the direction
340     // (though this might not be needed, IDK right now)
341     pathfindingMap.deltaSrc[index].x = (char) deltaX;
342     pathfindingMap.deltaSrc[index].y = (char) deltaY;
343 
344     // we skip nodes that are blocked by towers
345     if (pathfindingMap.towerIndex[index] >= 0)
346     {
347       node->distance += 8.0f;
348     }
349     pathfindingMap.distances[index] = node->distance;
350     pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance);
351     PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f);
352     PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f);
353     PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f);
354     PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f);
355   }
356 }
357 
358 void PathFindingMapDraw()
359 {
360   float cellSize = pathfindingMap.scale * 0.9f;
361   float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance);
362   for (int x = 0; x < pathfindingMap.width; x++)
363   {
364     for (int y = 0; y < pathfindingMap.height; y++)
365     {
366       float distance = pathfindingMap.distances[y * pathfindingMap.width + x];
367       float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f);
368       Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255};
369       Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace);
370       // animate the distance "wave" to show how the pathfinding algorithm expands
371       // from the castle
372       if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance)
373       {
374         color = BLACK;
375       }
376       DrawCube(position, cellSize, 0.1f, cellSize, color);
377     }
378   }
379 }
380 
381 Vector2 PathFindingGetGradient(Vector3 world)
382 {
383   int16_t mapX, mapY;
384   if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY))
385   {
386     DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX];
387     return (Vector2){(float)-delta.x, (float)-delta.y};
388   }
389   // fallback to a simple gradient calculation
390   float n = PathFindingGetDistance(mapX, mapY - 1);
391   float s = PathFindingGetDistance(mapX, mapY + 1);
392   float w = PathFindingGetDistance(mapX - 1, mapY);
393   float e = PathFindingGetDistance(mapX + 1, mapY);
394   return (Vector2){w - e + 0.25f, n - s + 0.125f};
395 }
396 
397 //# Enemies
398 
399 #define ENEMY_MAX_PATH_COUNT 8
400 #define ENEMY_MAX_COUNT 400
401 #define ENEMY_TYPE_NONE 0
402 #define ENEMY_TYPE_MINION 1
403 
404 typedef struct EnemyId
405 {
406   uint16_t index;
407   uint16_t generation;
408 } EnemyId;
409 
410 typedef struct EnemyClassConfig
411 {
412   float speed;
413   float health;
414   float radius;
415   float maxAcceleration;
416   float requiredContactTime;
417   float explosionDamage;
418   float explosionRange;
419   float explosionPushbackPower;
420 } EnemyClassConfig;
421 
422 typedef struct Enemy
423 {
424   int16_t currentX, currentY;
425   int16_t nextX, nextY;
426   Vector2 simPosition;
427   Vector2 simVelocity;
428   uint16_t generation;
429   float startMovingTime;
430   float damage, futureDamage;
431   float contactTime;
432   uint8_t enemyType;
433   uint8_t movePathCount;
434   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
435 } Enemy;
436 
437 Enemy enemies[ENEMY_MAX_COUNT];
438 int enemyCount = 0;
439 
440 EnemyClassConfig enemyClassConfigs[] = {
441     [ENEMY_TYPE_MINION] = {
442       .health = 3.0f, 
443       .speed = 1.0f, 
444       .radius = 0.25f, 
445       .maxAcceleration = 1.0f,
446       .explosionDamage = 1.0f,
447       .requiredContactTime = 0.5f,
448       .explosionRange = 1.0f,
449       .explosionPushbackPower = 0.25f,
450     },
451 };
452 
453 int EnemyAddDamage(Enemy *enemy, float damage);
454 
455 void EnemyInit()
456 {
457   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
458   {
459     enemies[i] = (Enemy){0};
460   }
461   enemyCount = 0;
462 }
463 
464 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
465 {
466   return enemyClassConfigs[enemy->enemyType].speed;
467 }
468 
469 float EnemyGetMaxHealth(Enemy *enemy)
470 {
471   return enemyClassConfigs[enemy->enemyType].health;
472 }
473 
474 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
475 {
476   int16_t castleX = 0;
477   int16_t castleY = 0;
478   int16_t dx = castleX - currentX;
479   int16_t dy = castleY - currentY;
480   if (dx == 0 && dy == 0)
481   {
482     *nextX = currentX;
483     *nextY = currentY;
484     return 1;
485   }
486   Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY});
487 
488   if (gradient.x == 0 && gradient.y == 0)
489   {
490     *nextX = currentX;
491     *nextY = currentY;
492     return 1;
493   }
494 
495   if (fabsf(gradient.x) > fabsf(gradient.y))
496   {
497     *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1);
498     *nextY = currentY;
499     return 0;
500   }
501   *nextX = currentX;
502   *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1);
503   return 0;
504 }
505 
506 
507 // this function predicts the movement of the unit for the next deltaT seconds
508 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
509 {
510   const float pointReachedDistance = 0.25f;
511   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
512   const float maxSimStepTime = 0.015625f;
513   
514   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
515   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
516   int16_t nextX = enemy->nextX;
517   int16_t nextY = enemy->nextY;
518   Vector2 position = enemy->simPosition;
519   int passedCount = 0;
520   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
521   {
522     float stepTime = fminf(deltaT - t, maxSimStepTime);
523     Vector2 target = (Vector2){nextX, nextY};
524     float speed = Vector2Length(*velocity);
525     // draw the target position for debugging
526     DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
527     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
528     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
529     {
530       // we reached the target position, let's move to the next waypoint
531       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
532       target = (Vector2){nextX, nextY};
533       // track how many waypoints we passed
534       passedCount++;
535     }
536     
537     // acceleration towards the target
538     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
539     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
540     *velocity = Vector2Add(*velocity, acceleration);
541 
542     // limit the speed to the maximum speed
543     if (speed > maxSpeed)
544     {
545       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
546     }
547 
548     // move the enemy
549     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
550   }
551 
552   if (waypointPassedCount)
553   {
554     (*waypointPassedCount) = passedCount;
555   }
556 
557   return position;
558 }
559 
560 void EnemyDraw()
561 {
562   for (int i = 0; i < enemyCount; i++)
563   {
564     Enemy enemy = enemies[i];
565     if (enemy.enemyType == ENEMY_TYPE_NONE)
566     {
567       continue;
568     }
569 
570     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
571     
572     if (enemy.movePathCount > 0)
573     {
574       Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
575       DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
576     }
577     for (int j = 1; j < enemy.movePathCount; j++)
578     {
579       Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
580       Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
581       DrawLine3D(p, q, GREEN);
582     }
583 
584     switch (enemy.enemyType)
585     {
586     case ENEMY_TYPE_MINION:
587       DrawCubeWires((Vector3){position.x, 0.2f, position.y}, 0.4f, 0.4f, 0.4f, GREEN);
588       break;
589     }
590   }
591 }
592 
593 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource)
594 {
595   // damage the tower
596   float explosionDamge = enemyClassConfigs[enemy->enemyType].explosionDamage;
597   float explosionRange = enemyClassConfigs[enemy->enemyType].explosionRange;
598   float explosionPushbackPower = enemyClassConfigs[enemy->enemyType].explosionPushbackPower;
599   float explosionRange2 = explosionRange * explosionRange;
600   tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage;
601   // explode the enemy
602   if (tower->damage >= TowerGetMaxHealth(tower))
603   {
604     tower->towerType = TOWER_TYPE_NONE;
605   }
606 
607   ParticleAdd(PARTICLE_TYPE_EXPLOSION, 
608     explosionSource, 
609     (Vector3){0, 0.1f, 0}, 1.0f);
610 
611   enemy->enemyType = ENEMY_TYPE_NONE;
612 
613   // push back enemies & dealing damage
614   for (int i = 0; i < enemyCount; i++)
615   {
616     Enemy *other = &enemies[i];
617     if (other->enemyType == ENEMY_TYPE_NONE)
618     {
619       continue;
620     }
621     float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition);
622     if (distanceSqr > 0 && distanceSqr < explosionRange2)
623     {
624       Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition));
625       other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower));
626       EnemyAddDamage(other, explosionDamge);
627     }
628   }
629 }
630 
631 void EnemyUpdate()
632 {
633   const float castleX = 0;
634   const float castleY = 0;
635   const float maxPathDistance2 = 0.25f * 0.25f;
636   
637   for (int i = 0; i < enemyCount; i++)
638   {
639     Enemy *enemy = &enemies[i];
640     if (enemy->enemyType == ENEMY_TYPE_NONE)
641     {
642       continue;
643     }
644 
645     int waypointPassedCount = 0;
646     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
647     enemy->startMovingTime = gameTime.time;
648     // track path of unit
649     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
650     {
651       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
652       {
653         enemy->movePath[j] = enemy->movePath[j - 1];
654       }
655       enemy->movePath[0] = enemy->simPosition;
656       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
657       {
658         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
659       }
660     }
661 
662     if (waypointPassedCount > 0)
663     {
664       enemy->currentX = enemy->nextX;
665       enemy->currentY = enemy->nextY;
666       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) &&
667         Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
668       {
669         // enemy reached the castle; remove it
670         enemy->enemyType = ENEMY_TYPE_NONE;
671         continue;
672       }
673     }
674   }
675 
676   // handle collisions between enemies
677   for (int i = 0; i < enemyCount - 1; i++)
678   {
679     Enemy *enemyA = &enemies[i];
680     if (enemyA->enemyType == ENEMY_TYPE_NONE)
681     {
682       continue;
683     }
684     for (int j = i + 1; j < enemyCount; j++)
685     {
686       Enemy *enemyB = &enemies[j];
687       if (enemyB->enemyType == ENEMY_TYPE_NONE)
688       {
689         continue;
690       }
691       float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition);
692       float radiusA = enemyClassConfigs[enemyA->enemyType].radius;
693       float radiusB = enemyClassConfigs[enemyB->enemyType].radius;
694       float radiusSum = radiusA + radiusB;
695       if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f)
696       {
697         // collision
698         float distance = sqrtf(distanceSqr);
699         float overlap = radiusSum - distance;
700         // move the enemies apart, but softly; if we have a clog of enemies,
701         // moving them perfectly apart can cause them to jitter
702         float positionCorrection = overlap / 5.0f;
703         Vector2 direction = (Vector2){
704             (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection,
705             (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection};
706         enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction);
707         enemyB->simPosition = Vector2Add(enemyB->simPosition, direction);
708       }
709     }
710   }
711 
712   // handle collisions between enemies and towers
713   for (int i = 0; i < enemyCount; i++)
714   {
715     Enemy *enemy = &enemies[i];
716     if (enemy->enemyType == ENEMY_TYPE_NONE)
717     {
718       continue;
719     }
720     enemy->contactTime -= gameTime.deltaTime;
721     if (enemy->contactTime < 0.0f)
722     {
723       enemy->contactTime = 0.0f;
724     }
725 
726     float enemyRadius = enemyClassConfigs[enemy->enemyType].radius;
727     // linear search over towers; could be optimized by using path finding tower map,
728     // but for now, we keep it simple
729     for (int j = 0; j < towerCount; j++)
730     {
731       Tower *tower = &towers[j];
732       if (tower->towerType == TOWER_TYPE_NONE)
733       {
734         continue;
735       }
736       float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y});
737       float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1
738       if (distanceSqr > combinedRadius * combinedRadius)
739       {
740         continue;
741       }
742       // potential collision; square / circle intersection
743       float dx = tower->x - enemy->simPosition.x;
744       float dy = tower->y - enemy->simPosition.y;
745       float absDx = fabsf(dx);
746       float absDy = fabsf(dy);
747       Vector3 contactPoint = {0};
748       if (absDx <= 0.5f && absDx <= absDy) {
749         // vertical collision; push the enemy out horizontally
750         float overlap = enemyRadius + 0.5f - absDy;
751         if (overlap < 0.0f)
752         {
753           continue;
754         }
755         float direction = dy > 0.0f ? -1.0f : 1.0f;
756         enemy->simPosition.y += direction * overlap;
757         contactPoint = (Vector3){enemy->simPosition.x, 0.2f, tower->x + direction * 0.5f};
758       }
759       else if (absDy <= 0.5f && absDy <= absDx)
760       {
761         // horizontal collision; push the enemy out vertically
762         float overlap = enemyRadius + 0.5f - absDx;
763         if (overlap < 0.0f)
764         {
765           continue;
766         }
767         float direction = dx > 0.0f ? -1.0f : 1.0f;
768         enemy->simPosition.x += direction * overlap;
769         contactPoint = (Vector3){tower->x + direction * 0.5f, 0.2f, enemy->simPosition.y};
770       }
771       else
772       {
773         // possible collision with a corner
774         float cornerDX = dx > 0.0f ? -0.5f : 0.5f;
775         float cornerDY = dy > 0.0f ? -0.5f : 0.5f;
776         float cornerX = tower->x + cornerDX;
777         float cornerY = tower->y + cornerDY;
778         float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY});
779         if (cornerDistanceSqr > enemyRadius * enemyRadius)
780         {
781           continue;
782         }
783         // push the enemy out along the diagonal
784         float cornerDistance = sqrtf(cornerDistanceSqr);
785         float overlap = enemyRadius - cornerDistance;
786         float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX;
787         float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY;
788         enemy->simPosition.x -= directionX * overlap;
789         enemy->simPosition.y -= directionY * overlap;
790         contactPoint = (Vector3){cornerX, 0.2f, cornerY};
791       }
792 
793       if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f)
794       {
795         enemy->contactTime += gameTime.deltaTime * 2.0f; // * 2 to undo the subtraction above
796         if (enemy->contactTime >= enemyClassConfigs[enemy->enemyType].requiredContactTime)
797         {
798           EnemyTriggerExplode(enemy, tower, contactPoint);
799         }
800       }
801     }
802   }
803 }
804 
805 EnemyId EnemyGetId(Enemy *enemy)
806 {
807   return (EnemyId){enemy - enemies, enemy->generation};
808 }
809 
810 Enemy *EnemyTryResolve(EnemyId enemyId)
811 {
812   if (enemyId.index >= ENEMY_MAX_COUNT)
813   {
814     return 0;
815   }
816   Enemy *enemy = &enemies[enemyId.index];
817   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
818   {
819     return 0;
820   }
821   return enemy;
822 }
823 
824 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
825 {
826   Enemy *spawn = 0;
827   for (int i = 0; i < enemyCount; i++)
828   {
829     Enemy *enemy = &enemies[i];
830     if (enemy->enemyType == ENEMY_TYPE_NONE)
831     {
832       spawn = enemy;
833       break;
834     }
835   }
836 
837   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
838   {
839     spawn = &enemies[enemyCount++];
840   }
841 
842   if (spawn)
843   {
844     spawn->currentX = currentX;
845     spawn->currentY = currentY;
846     spawn->nextX = currentX;
847     spawn->nextY = currentY;
848     spawn->simPosition = (Vector2){currentX, currentY};
849     spawn->simVelocity = (Vector2){0, 0};
850     spawn->enemyType = enemyType;
851     spawn->startMovingTime = gameTime.time;
852     spawn->damage = 0.0f;
853     spawn->futureDamage = 0.0f;
854     spawn->generation++;
855     spawn->movePathCount = 0;
856   }
857 
858   return spawn;
859 }
860 
861 int EnemyAddDamage(Enemy *enemy, float damage)
862 {
863   enemy->damage += damage;
864   if (enemy->damage >= EnemyGetMaxHealth(enemy))
865   {
866     enemy->enemyType = ENEMY_TYPE_NONE;
867     return 1;
868   }
869 
870   return 0;
871 }
872 
873 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
874 {
875   int16_t castleX = 0;
876   int16_t castleY = 0;
877   Enemy* closest = 0;
878   int16_t closestDistance = 0;
879   float range2 = range * range;
880   for (int i = 0; i < enemyCount; i++)
881   {
882     Enemy* enemy = &enemies[i];
883     if (enemy->enemyType == ENEMY_TYPE_NONE)
884     {
885       continue;
886     }
887     float maxHealth = EnemyGetMaxHealth(enemy);
888     if (enemy->futureDamage >= maxHealth)
889     {
890       // ignore enemies that will die soon
891       continue;
892     }
893     int16_t dx = castleX - enemy->currentX;
894     int16_t dy = castleY - enemy->currentY;
895     int16_t distance = abs(dx) + abs(dy);
896     if (!closest || distance < closestDistance)
897     {
898       float tdx = towerX - enemy->currentX;
899       float tdy = towerY - enemy->currentY;
900       float tdistance2 = tdx * tdx + tdy * tdy;
901       if (tdistance2 <= range2)
902       {
903         closest = enemy;
904         closestDistance = distance;
905       }
906     }
907   }
908   return closest;
909 }
910 
911 int EnemyCount()
912 {
913   int count = 0;
914   for (int i = 0; i < enemyCount; i++)
915   {
916     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
917     {
918       count++;
919     }
920   }
921   return count;
922 }
923 
924 //# Projectiles
925 #define PROJECTILE_MAX_COUNT 1200
926 #define PROJECTILE_TYPE_NONE 0
927 #define PROJECTILE_TYPE_BULLET 1
928 
929 typedef struct Projectile
930 {
931   uint8_t projectileType;
932   float shootTime;
933   float arrivalTime;
934   float damage;
935   Vector2 position;
936   Vector2 target;
937   Vector2 directionNormal;
938   EnemyId targetEnemy;
939 } Projectile;
940 
941 Projectile projectiles[PROJECTILE_MAX_COUNT];
942 int projectileCount = 0;
943 
944 void ProjectileInit()
945 {
946   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
947   {
948     projectiles[i] = (Projectile){0};
949   }
950 }
951 
952 void ProjectileDraw()
953 {
954   for (int i = 0; i < projectileCount; i++)
955   {
956     Projectile projectile = projectiles[i];
957     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
958     {
959       continue;
960     }
961     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
962     if (transition >= 1.0f)
963     {
964       continue;
965     }
966     Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition);
967     float x = position.x;
968     float y = position.y;
969     float dx = projectile.directionNormal.x;
970     float dy = projectile.directionNormal.y;
971     for (float d = 1.0f; d > 0.0f; d -= 0.25f)
972     {
973       x -= dx * 0.1f;
974       y -= dy * 0.1f;
975       float size = 0.1f * d;
976       DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED);
977     }
978   }
979 }
980 
981 void ProjectileUpdate()
982 {
983   for (int i = 0; i < projectileCount; i++)
984   {
985     Projectile *projectile = &projectiles[i];
986     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
987     {
988       continue;
989     }
990     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
991     if (transition >= 1.0f)
992     {
993       projectile->projectileType = PROJECTILE_TYPE_NONE;
994       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
995       if (enemy)
996       {
997         EnemyAddDamage(enemy, projectile->damage);
998       }
999       continue;
1000     }
1001   }
1002 }
1003 
1004 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage)
1005 {
1006   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
1007   {
1008     Projectile *projectile = &projectiles[i];
1009     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
1010     {
1011       projectile->projectileType = projectileType;
1012       projectile->shootTime = gameTime.time;
1013       projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed;
1014       projectile->damage = damage;
1015       projectile->position = position;
1016       projectile->target = target;
1017       projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position));
1018       projectile->targetEnemy = EnemyGetId(enemy);
1019       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
1020       return projectile;
1021     }
1022   }
1023   return 0;
1024 }
1025 
1026 //# Towers
1027 
1028 void TowerInit()
1029 {
1030   for (int i = 0; i < TOWER_MAX_COUNT; i++)
1031   {
1032     towers[i] = (Tower){0};
1033   }
1034   towerCount = 0;
1035 }
1036 
1037 Tower *TowerGetAt(int16_t x, int16_t y)
1038 {
1039   for (int i = 0; i < towerCount; i++)
1040   {
1041     if (towers[i].x == x && towers[i].y == y)
1042     {
1043       return &towers[i];
1044     }
1045   }
1046   return 0;
1047 }
1048 
1049 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
1050 {
1051   if (towerCount >= TOWER_MAX_COUNT)
1052   {
1053     return 0;
1054   }
1055 
1056   Tower *tower = TowerGetAt(x, y);
1057   if (tower)
1058   {
1059     return 0;
1060   }
1061 
1062   tower = &towers[towerCount++];
1063   tower->x = x;
1064   tower->y = y;
1065   tower->towerType = towerType;
1066   tower->cooldown = 0.0f;
1067   tower->damage = 0.0f;
1068   return tower;
1069 }
1070 
1071 float TowerGetMaxHealth(Tower *tower)
1072 {
1073   switch (tower->towerType)
1074   {
1075   case TOWER_TYPE_BASE:
1076     return 10.0f;
1077   case TOWER_TYPE_GUN:
1078     return 3.0f;
1079   case TOWER_TYPE_WALL:
1080     return 5.0f;
1081   }
1082   return 0.0f;
1083 }
1084 
1085 void TowerDraw()
1086 {
1087   for (int i = 0; i < towerCount; i++)
1088   {
1089     Tower tower = towers[i];
1090     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
1091     switch (tower.towerType)
1092     {
1093     case TOWER_TYPE_BASE:
1094       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
1095       break;
1096     case TOWER_TYPE_GUN:
1097       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
1098       break;
1099     case TOWER_TYPE_WALL:
1100       DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
1101       break;
1102     }
1103   }
1104 }
1105 
1106 void TowerGunUpdate(Tower *tower)
1107 {
1108   if (tower->cooldown <= 0)
1109   {
1110     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
1111     if (enemy)
1112     {
1113       tower->cooldown = 0.125f;
1114       // shoot the enemy; determine future position of the enemy
1115       float bulletSpeed = 1.0f;
1116       float bulletDamage = 3.0f;
1117       Vector2 velocity = enemy->simVelocity;
1118       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
1119       Vector2 towerPosition = {tower->x, tower->y};
1120       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
1121       for (int i = 0; i < 8; i++) {
1122         velocity = enemy->simVelocity;
1123         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
1124         float distance = Vector2Distance(towerPosition, futurePosition);
1125         float eta2 = distance / bulletSpeed;
1126         if (fabs(eta - eta2) < 0.01f) {
1127           break;
1128         }
1129         eta = (eta2 + eta) * 0.5f;
1130       }
1131       ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, towerPosition, futurePosition, 
1132         bulletSpeed, bulletDamage);
1133       enemy->futureDamage += bulletDamage;
1134     }
1135   }
1136   else
1137   {
1138     tower->cooldown -= gameTime.deltaTime;
1139   }
1140 }
1141 
1142 void TowerUpdate()
1143 {
1144   for (int i = 0; i < towerCount; i++)
1145   {
1146     Tower *tower = &towers[i];
1147     switch (tower->towerType)
1148     {
1149     case TOWER_TYPE_GUN:
1150       TowerGunUpdate(tower);
1151       break;
1152     }
1153   }
1154 }
1155 
1156 //# Game
1157 
1158 float nextSpawnTime = 0.0f;
1159 
1160 void InitGame()
1161 {
1162   TowerInit();
1163   EnemyInit();
1164   ProjectileInit();
1165   ParticleInit();
1166   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
1167 
1168   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
1169   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
1170 
1171   for (int i = -2; i <= 2; i += 1)
1172   {
1173     TowerTryAdd(TOWER_TYPE_WALL, i, 2);
1174     TowerTryAdd(TOWER_TYPE_WALL, i, -2);
1175     TowerTryAdd(TOWER_TYPE_WALL, -2, i);
1176   }
1177 
1178   EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4);
1179 }
1180 
1181 //# Immediate GUI functions
1182 
1183 typedef struct ButtonState { 1184 char isSelected; 1185 } ButtonState; 1186 1187 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
1188 { 1189 Rectangle bounds = {x, y, width, height};
1190 int isPressed = 0; 1191 int isSelected = state && state->isSelected;
1192 if (CheckCollisionPointRec(GetMousePosition(), bounds)) 1193 {
1194 Color color = isSelected ? DARKGRAY : GRAY; 1195 DrawRectangle(x, y, width, height, color);
1196 if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) 1197 { 1198 isPressed = 1; 1199 } 1200 } 1201 else 1202 {
1203 Color color = isSelected ? WHITE : LIGHTGRAY; 1204 DrawRectangle(x, y, width, height, color);
1205 } 1206 Font font = GetFontDefault(); 1207 Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1); 1208 DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, BLACK); 1209 return isPressed; 1210 } 1211 1212 //# Main game loop 1213 1214 void GameUpdate() 1215 { 1216 float dt = GetFrameTime(); 1217 // cap maximum delta time to 0.1 seconds to prevent large time steps 1218 if (dt > 0.1f) dt = 0.1f; 1219 gameTime.time += dt; 1220 gameTime.deltaTime = dt; 1221 PathFindingMapUpdate(); 1222 EnemyUpdate(); 1223 TowerUpdate(); 1224 ProjectileUpdate(); 1225 ParticleUpdate(); 1226 1227 // spawn a new enemy every second 1228 if (gameTime.time >= nextSpawnTime && EnemyCount() < 50) 1229 { 1230 nextSpawnTime = gameTime.time + 0.2f; 1231 // add a new enemy at the boundary of the map 1232 int randValue = GetRandomValue(-5, 5); 1233 int randSide = GetRandomValue(0, 3); 1234 int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue; 1235 int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue; 1236 static int alternation = 0; 1237 alternation += 1; 1238 if (alternation % 3 == 0) { 1239 EnemyTryAdd(ENEMY_TYPE_MINION, 0, -5); 1240 } 1241 else if (alternation % 3 == 1) 1242 { 1243 EnemyTryAdd(ENEMY_TYPE_MINION, 0, 5); 1244 } 1245 EnemyTryAdd(ENEMY_TYPE_MINION, x, y); 1246 } 1247 } 1248 1249 int main(void) 1250 { 1251 int screenWidth, screenHeight; 1252 GetPreferredSize(&screenWidth, &screenHeight); 1253 InitWindow(screenWidth, screenHeight, "Tower defense"); 1254 SetTargetFPS(30); 1255 1256 Camera3D camera = {0}; 1257 1258 int cameraMode = 0; 1259 1260 InitGame(); 1261 1262 while (!WindowShouldClose()) 1263 { 1264 if (IsPaused()) { 1265 // canvas is not visible in browser - do nothing 1266 continue; 1267 } 1268 1269 if (cameraMode == 0) 1270 { 1271 camera.position = (Vector3){0.0f, 10.0f, -0.5f}; 1272 camera.target = (Vector3){0.0f, 0.0f, -0.5f}; 1273 camera.up = (Vector3){0.0f, 0.0f, -1.0f}; 1274 camera.fovy = 12.0f; 1275 camera.projection = CAMERA_ORTHOGRAPHIC; 1276 } 1277 else 1278 { 1279 camera.position = (Vector3){1.0f, 12.0f, 6.5f}; 1280 camera.target = (Vector3){0.0f, 0.5f, 1.0f}; 1281 camera.up = (Vector3){0.0f, 1.0f, 0.0f}; 1282 camera.fovy = 45.0f; 1283 camera.projection = CAMERA_PERSPECTIVE; 1284 } 1285 1286 BeginDrawing(); 1287 ClearBackground(DARKBLUE); 1288 1289 BeginMode3D(camera); 1290 DrawGrid(10, 1.0f); 1291 TowerDraw(); 1292 EnemyDraw(); 1293 ProjectileDraw(); 1294 // PathFindingMapDraw(); 1295 ParticleDraw();
1296 GameUpdate(); 1297 1298 static int placementMode = TOWER_TYPE_NONE;
1299 1300 Ray ray = GetScreenToWorldRay(GetMousePosition(), camera); 1301 float planeDistance = ray.position.y / -ray.direction.y; 1302 float planeX = ray.direction.x * planeDistance + ray.position.x; 1303 float planeY = ray.direction.z * planeDistance + ray.position.z; 1304 int16_t mapX = (int16_t)floorf(planeX + 0.5f);
1305 int16_t mapY = (int16_t)floorf(planeY + 0.5f); 1306 if (placementMode) 1307 { 1308 DrawCubeWires((Vector3){mapX, 0.2f, mapY}, 1.0f, 0.4f, 1.0f, RED); 1309 if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) 1310 { 1311 TowerTryAdd(placementMode, mapX, mapY); 1312 placementMode = TOWER_TYPE_NONE; 1313 } 1314 }
1315 1316 EndMode3D(); 1317 1318 const char *title = "Tower defense tutorial"; 1319 int titleWidth = MeasureText(title, 20); 1320 DrawText(title, (GetScreenWidth() - titleWidth) * 0.5f + 2, 5 + 2, 20, BLACK); 1321 DrawText(title, (GetScreenWidth() - titleWidth) * 0.5f, 5, 20, WHITE); 1322 1323 const char *buttonText = cameraMode == 0 ? "2D" : "3D";
1324 if (Button(buttonText, 10, 10, 80, 30, 0))
1325 {
1326 cameraMode = !cameraMode; 1327 } 1328 1329 static ButtonState buildWallButtonState = {0}; 1330 static ButtonState buildGunButtonState = {0}; 1331 buildWallButtonState.isSelected = placementMode == TOWER_TYPE_WALL; 1332 buildGunButtonState.isSelected = placementMode == TOWER_TYPE_GUN; 1333 1334 if (Button("Wall", 10, 50, 80, 30, &buildWallButtonState)) 1335 { 1336 placementMode = buildWallButtonState.isSelected ? 0 : TOWER_TYPE_WALL; 1337 } 1338 if (Button("Gun", 10, 90, 80, 30, &buildGunButtonState)) 1339 { 1340 placementMode = buildGunButtonState.isSelected ? 0 : TOWER_TYPE_GUN;
1341 } 1342 1343 EndDrawing(); 1344 } 1345 1346 CloseWindow(); 1347 1348 return 0; 1349 }
  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 #endif
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif

Placing stuff is pretty nice. But right now, the UI is click through! We need to prevent the mouse ray to be active, when the mouse hovers a button. The solution I use here is to introduce a gui state flag that is set when the mouse is over a button:

  1 typedef struct GUIState {
  2   int isBlocked;
  3 } GUIState;
  4 
  5 GUIState guiState = {0};

This flag is reset during the update loop and set when the mouse is over a button. The ray casting is then only done when the flag is not set. This is a simple solution that works well for our case.

Let's also add now a button to reset the game:

  • 💾
  1 #include "td-tut-2-main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 //# Declarations
  7 
  8 #define PARTICLE_MAX_COUNT 400
  9 #define PARTICLE_TYPE_NONE 0
 10 #define PARTICLE_TYPE_EXPLOSION 1
 11 
 12 typedef struct Particle
 13 {
 14   uint8_t particleType;
 15   float spawnTime;
 16   float lifetime;
 17   Vector3 position;
 18   Vector3 velocity;
 19 } Particle;
 20 
 21 Particle particles[PARTICLE_MAX_COUNT];
 22 int particleCount = 0;
 23 
 24 #define TOWER_MAX_COUNT 400
 25 #define TOWER_TYPE_NONE 0
 26 #define TOWER_TYPE_BASE 1
 27 #define TOWER_TYPE_GUN 2
 28 #define TOWER_TYPE_WALL 3
 29 
 30 typedef struct Tower
 31 {
 32   int16_t x, y;
 33   uint8_t towerType;
 34   float cooldown;
 35   float damage;
 36 } Tower;
 37 
 38 typedef struct GameTime
 39 {
 40   float time;
 41   float deltaTime;
 42 } GameTime;
 43 
 44 GameTime gameTime = {0};
 45 
 46 Tower towers[TOWER_MAX_COUNT];
 47 int towerCount = 0;
 48 
 49 float TowerGetMaxHealth(Tower *tower);
 50 
 51 //# Particle system
 52 
 53 void ParticleInit()
 54 {
 55   for (int i = 0; i < PARTICLE_MAX_COUNT; i++)
 56   {
 57     particles[i] = (Particle){0};
 58   }
 59   particleCount = 0;
 60 }
 61 
 62 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime)
 63 {
 64   if (particleCount >= PARTICLE_MAX_COUNT)
 65   {
 66     return;
 67   }
 68 
 69   int index = -1;
 70   for (int i = 0; i < particleCount; i++)
 71   {
 72     if (particles[i].particleType == PARTICLE_TYPE_NONE)
 73     {
 74       index = i;
 75       break;
 76     }
 77   }
 78 
 79   if (index == -1)
 80   {
 81     index = particleCount++;
 82   }
 83 
 84   Particle *particle = &particles[index];
 85   particle->particleType = particleType;
 86   particle->spawnTime = gameTime.time;
 87   particle->lifetime = lifetime;
 88   particle->position = position;
 89   particle->velocity = velocity;
 90 }
 91 
 92 void ParticleUpdate()
 93 {
 94   for (int i = 0; i < particleCount; i++)
 95   {
 96     Particle *particle = &particles[i];
 97     if (particle->particleType == PARTICLE_TYPE_NONE)
 98     {
 99       continue;
100     }
101 
102     float age = gameTime.time - particle->spawnTime;
103 
104     if (particle->lifetime > age)
105     {
106       particle->position = Vector3Add(particle->position, Vector3Scale(particle->velocity, gameTime.deltaTime));
107     }
108     else {
109       particle->particleType = PARTICLE_TYPE_NONE;
110     }
111   }
112 }
113 
114 void DrawExplosionParticle(Particle *particle, float transition)
115 {
116   float size = 1.2f * (1.0f - transition);
117   Color startColor = WHITE;
118   Color endColor = RED;
119   Color color = ColorLerp(startColor, endColor, transition);
120   DrawCube(particle->position, size, size, size, color);
121 }
122 
123 void ParticleDraw()
124 {
125   for (int i = 0; i < particleCount; i++)
126   {
127     Particle particle = particles[i];
128     if (particle.particleType == PARTICLE_TYPE_NONE)
129     {
130       continue;
131     }
132 
133     float age = gameTime.time - particle.spawnTime;
134     float transition = age / particle.lifetime;
135     switch (particle.particleType)
136     {
137     case PARTICLE_TYPE_EXPLOSION:
138       DrawExplosionParticle(&particle, transition);
139       break;
140     default:
141       DrawCube(particle.position, 0.3f, 0.5f, 0.3f, RED);
142       break;
143     }
144   }
145 }
146 
147 //# Pathfinding map
148 typedef struct DeltaSrc
149 {
150   char x, y;
151 } DeltaSrc;
152 
153 typedef struct PathfindingMap
154 {
155   int width, height;
156   float scale;
157   float *distances;
158   long *towerIndex; 
159   DeltaSrc *deltaSrc;
160   float maxDistance;
161   Matrix toMapSpace;
162   Matrix toWorldSpace;
163 } PathfindingMap;
164 
165 // when we execute the pathfinding algorithm, we need to store the active nodes
166 // in a queue. Each node has a position, a distance from the start, and the
167 // position of the node that we came from.
168 typedef struct PathfindingNode
169 {
170   int16_t x, y, fromX, fromY;
171   float distance;
172 } PathfindingNode;
173 
174 // The queue is a simple array of nodes, we add nodes to the end and remove
175 // nodes from the front. We keep the array around to avoid unnecessary allocations
176 static PathfindingNode *pathfindingNodeQueue = 0;
177 static int pathfindingNodeQueueCount = 0;
178 static int pathfindingNodeQueueCapacity = 0;
179 
180 // The pathfinding map stores the distances from the castle to each cell in the map.
181 PathfindingMap pathfindingMap = {0};
182 
183 void PathfindingMapInit(int width, int height, Vector3 translate, float scale)
184 {
185   // transforming between map space and world space allows us to adapt 
186   // position and scale of the map without changing the pathfinding data
187   pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z);
188   pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale));
189   pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace);
190   pathfindingMap.width = width;
191   pathfindingMap.height = height;
192   pathfindingMap.scale = scale;
193   pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float));
194   for (int i = 0; i < width * height; i++)
195   {
196     pathfindingMap.distances[i] = -1.0f;
197   }
198 
199   pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long));
200   pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc));
201 }
202 
203 float PathFindingGetDistance(int mapX, int mapY)
204 {
205   if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height)
206   {
207     // when outside the map, we return the manhattan distance to the castle (0,0)
208     return fabsf((float)mapX) + fabsf((float)mapY);
209   }
210 
211   return pathfindingMap.distances[mapY * pathfindingMap.width + mapX];
212 }
213 
214 void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance)
215 {
216   if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity)
217   {
218     pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2;
219     // we use MemAlloc/MemRealloc to allocate memory for the queue
220     // I am not entirely sure if MemRealloc allows passing a null pointer
221     // so we check if the pointer is null and use MemAlloc in that case
222     if (pathfindingNodeQueue == 0)
223     {
224       pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
225     }
226     else
227     {
228       pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
229     }
230   }
231 
232   PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++];
233   node->x = x;
234   node->y = y;
235   node->fromX = fromX;
236   node->fromY = fromY;
237   node->distance = distance;
238 }
239 
240 PathfindingNode *PathFindingNodePop()
241 {
242   if (pathfindingNodeQueueCount == 0)
243   {
244     return 0;
245   }
246   // we return the first node in the queue; we want to return a pointer to the node
247   // so we can return 0 if the queue is empty. 
248   // We should _not_ return a pointer to the element in the list, because the list
249   // may be reallocated and the pointer would become invalid. Or the 
250   // popped element is overwritten by the next push operation.
251   // Using static here means that the variable is permanently allocated.
252   static PathfindingNode node;
253   node = pathfindingNodeQueue[0];
254   // we shift all nodes one position to the front
255   for (int i = 1; i < pathfindingNodeQueueCount; i++)
256   {
257     pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i];
258   }
259   --pathfindingNodeQueueCount;
260   return &node;
261 }
262 
263 // transform a world position to a map position in the array; 
264 // returns true if the position is inside the map
265 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY)
266 {
267   Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace);
268   *mapX = (int16_t)mapPosition.x;
269   *mapY = (int16_t)mapPosition.z;
270   return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height;
271 }
272 
273 void PathFindingMapUpdate()
274 {
275   const int castleX = 0, castleY = 0;
276   int16_t castleMapX, castleMapY;
277   if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY))
278   {
279     return;
280   }
281   int width = pathfindingMap.width, height = pathfindingMap.height;
282 
283   // reset the distances to -1
284   for (int i = 0; i < width * height; i++)
285   {
286     pathfindingMap.distances[i] = -1.0f;
287   }
288   // reset the tower indices
289   for (int i = 0; i < width * height; i++)
290   {
291     pathfindingMap.towerIndex[i] = -1;
292   }
293   // reset the delta src
294   for (int i = 0; i < width * height; i++)
295   {
296     pathfindingMap.deltaSrc[i].x = 0;
297     pathfindingMap.deltaSrc[i].y = 0;
298   }
299 
300   for (int i = 0; i < towerCount; i++)
301   {
302     Tower *tower = &towers[i];
303     if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE)
304     {
305       continue;
306     }
307     int16_t mapX, mapY;
308     // technically, if the tower cell scale is not in sync with the pathfinding map scale,
309     // this would not work correctly and needs to be refined to allow towers covering multiple cells
310     // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly
311     // one cell. For now.
312     if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY))
313     {
314       continue;
315     }
316     int index = mapY * width + mapX;
317     pathfindingMap.towerIndex[index] = i;
318   }
319 
320   // we start at the castle and add the castle to the queue
321   pathfindingMap.maxDistance = 0.0f;
322   pathfindingNodeQueueCount = 0;
323   PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f);
324   PathfindingNode *node = 0;
325   while ((node = PathFindingNodePop()))
326   {
327     if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height)
328     {
329       continue;
330     }
331     int index = node->y * width + node->x;
332     if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance)
333     {
334       continue;
335     }
336 
337     int deltaX = node->x - node->fromX;
338     int deltaY = node->y - node->fromY;
339     // even if the cell is blocked by a tower, we still may want to store the direction
340     // (though this might not be needed, IDK right now)
341     pathfindingMap.deltaSrc[index].x = (char) deltaX;
342     pathfindingMap.deltaSrc[index].y = (char) deltaY;
343 
344     // we skip nodes that are blocked by towers
345     if (pathfindingMap.towerIndex[index] >= 0)
346     {
347       node->distance += 8.0f;
348     }
349     pathfindingMap.distances[index] = node->distance;
350     pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance);
351     PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f);
352     PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f);
353     PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f);
354     PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f);
355   }
356 }
357 
358 void PathFindingMapDraw()
359 {
360   float cellSize = pathfindingMap.scale * 0.9f;
361   float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance);
362   for (int x = 0; x < pathfindingMap.width; x++)
363   {
364     for (int y = 0; y < pathfindingMap.height; y++)
365     {
366       float distance = pathfindingMap.distances[y * pathfindingMap.width + x];
367       float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f);
368       Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255};
369       Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace);
370       // animate the distance "wave" to show how the pathfinding algorithm expands
371       // from the castle
372       if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance)
373       {
374         color = BLACK;
375       }
376       DrawCube(position, cellSize, 0.1f, cellSize, color);
377     }
378   }
379 }
380 
381 Vector2 PathFindingGetGradient(Vector3 world)
382 {
383   int16_t mapX, mapY;
384   if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY))
385   {
386     DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX];
387     return (Vector2){(float)-delta.x, (float)-delta.y};
388   }
389   // fallback to a simple gradient calculation
390   float n = PathFindingGetDistance(mapX, mapY - 1);
391   float s = PathFindingGetDistance(mapX, mapY + 1);
392   float w = PathFindingGetDistance(mapX - 1, mapY);
393   float e = PathFindingGetDistance(mapX + 1, mapY);
394   return (Vector2){w - e + 0.25f, n - s + 0.125f};
395 }
396 
397 //# Enemies
398 
399 #define ENEMY_MAX_PATH_COUNT 8
400 #define ENEMY_MAX_COUNT 400
401 #define ENEMY_TYPE_NONE 0
402 #define ENEMY_TYPE_MINION 1
403 
404 typedef struct EnemyId
405 {
406   uint16_t index;
407   uint16_t generation;
408 } EnemyId;
409 
410 typedef struct EnemyClassConfig
411 {
412   float speed;
413   float health;
414   float radius;
415   float maxAcceleration;
416   float requiredContactTime;
417   float explosionDamage;
418   float explosionRange;
419   float explosionPushbackPower;
420 } EnemyClassConfig;
421 
422 typedef struct Enemy
423 {
424   int16_t currentX, currentY;
425   int16_t nextX, nextY;
426   Vector2 simPosition;
427   Vector2 simVelocity;
428   uint16_t generation;
429   float startMovingTime;
430   float damage, futureDamage;
431   float contactTime;
432   uint8_t enemyType;
433   uint8_t movePathCount;
434   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
435 } Enemy;
436 
437 Enemy enemies[ENEMY_MAX_COUNT];
438 int enemyCount = 0;
439 
440 EnemyClassConfig enemyClassConfigs[] = {
441     [ENEMY_TYPE_MINION] = {
442       .health = 3.0f, 
443       .speed = 1.0f, 
444       .radius = 0.25f, 
445       .maxAcceleration = 1.0f,
446       .explosionDamage = 1.0f,
447       .requiredContactTime = 0.5f,
448       .explosionRange = 1.0f,
449       .explosionPushbackPower = 0.25f,
450     },
451 };
452 
453 int EnemyAddDamage(Enemy *enemy, float damage);
454 
455 void EnemyInit()
456 {
457   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
458   {
459     enemies[i] = (Enemy){0};
460   }
461   enemyCount = 0;
462 }
463 
464 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
465 {
466   return enemyClassConfigs[enemy->enemyType].speed;
467 }
468 
469 float EnemyGetMaxHealth(Enemy *enemy)
470 {
471   return enemyClassConfigs[enemy->enemyType].health;
472 }
473 
474 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
475 {
476   int16_t castleX = 0;
477   int16_t castleY = 0;
478   int16_t dx = castleX - currentX;
479   int16_t dy = castleY - currentY;
480   if (dx == 0 && dy == 0)
481   {
482     *nextX = currentX;
483     *nextY = currentY;
484     return 1;
485   }
486   Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY});
487 
488   if (gradient.x == 0 && gradient.y == 0)
489   {
490     *nextX = currentX;
491     *nextY = currentY;
492     return 1;
493   }
494 
495   if (fabsf(gradient.x) > fabsf(gradient.y))
496   {
497     *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1);
498     *nextY = currentY;
499     return 0;
500   }
501   *nextX = currentX;
502   *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1);
503   return 0;
504 }
505 
506 
507 // this function predicts the movement of the unit for the next deltaT seconds
508 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
509 {
510   const float pointReachedDistance = 0.25f;
511   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
512   const float maxSimStepTime = 0.015625f;
513   
514   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
515   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
516   int16_t nextX = enemy->nextX;
517   int16_t nextY = enemy->nextY;
518   Vector2 position = enemy->simPosition;
519   int passedCount = 0;
520   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
521   {
522     float stepTime = fminf(deltaT - t, maxSimStepTime);
523     Vector2 target = (Vector2){nextX, nextY};
524     float speed = Vector2Length(*velocity);
525     // draw the target position for debugging
526     DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
527     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
528     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
529     {
530       // we reached the target position, let's move to the next waypoint
531       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
532       target = (Vector2){nextX, nextY};
533       // track how many waypoints we passed
534       passedCount++;
535     }
536     
537     // acceleration towards the target
538     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
539     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
540     *velocity = Vector2Add(*velocity, acceleration);
541 
542     // limit the speed to the maximum speed
543     if (speed > maxSpeed)
544     {
545       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
546     }
547 
548     // move the enemy
549     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
550   }
551 
552   if (waypointPassedCount)
553   {
554     (*waypointPassedCount) = passedCount;
555   }
556 
557   return position;
558 }
559 
560 void EnemyDraw()
561 {
562   for (int i = 0; i < enemyCount; i++)
563   {
564     Enemy enemy = enemies[i];
565     if (enemy.enemyType == ENEMY_TYPE_NONE)
566     {
567       continue;
568     }
569 
570     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
571     
572     if (enemy.movePathCount > 0)
573     {
574       Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
575       DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
576     }
577     for (int j = 1; j < enemy.movePathCount; j++)
578     {
579       Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
580       Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
581       DrawLine3D(p, q, GREEN);
582     }
583 
584     switch (enemy.enemyType)
585     {
586     case ENEMY_TYPE_MINION:
587       DrawCubeWires((Vector3){position.x, 0.2f, position.y}, 0.4f, 0.4f, 0.4f, GREEN);
588       break;
589     }
590   }
591 }
592 
593 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource)
594 {
595   // damage the tower
596   float explosionDamge = enemyClassConfigs[enemy->enemyType].explosionDamage;
597   float explosionRange = enemyClassConfigs[enemy->enemyType].explosionRange;
598   float explosionPushbackPower = enemyClassConfigs[enemy->enemyType].explosionPushbackPower;
599   float explosionRange2 = explosionRange * explosionRange;
600   tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage;
601   // explode the enemy
602   if (tower->damage >= TowerGetMaxHealth(tower))
603   {
604     tower->towerType = TOWER_TYPE_NONE;
605   }
606 
607   ParticleAdd(PARTICLE_TYPE_EXPLOSION, 
608     explosionSource, 
609     (Vector3){0, 0.1f, 0}, 1.0f);
610 
611   enemy->enemyType = ENEMY_TYPE_NONE;
612 
613   // push back enemies & dealing damage
614   for (int i = 0; i < enemyCount; i++)
615   {
616     Enemy *other = &enemies[i];
617     if (other->enemyType == ENEMY_TYPE_NONE)
618     {
619       continue;
620     }
621     float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition);
622     if (distanceSqr > 0 && distanceSqr < explosionRange2)
623     {
624       Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition));
625       other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower));
626       EnemyAddDamage(other, explosionDamge);
627     }
628   }
629 }
630 
631 void EnemyUpdate()
632 {
633   const float castleX = 0;
634   const float castleY = 0;
635   const float maxPathDistance2 = 0.25f * 0.25f;
636   
637   for (int i = 0; i < enemyCount; i++)
638   {
639     Enemy *enemy = &enemies[i];
640     if (enemy->enemyType == ENEMY_TYPE_NONE)
641     {
642       continue;
643     }
644 
645     int waypointPassedCount = 0;
646     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
647     enemy->startMovingTime = gameTime.time;
648     // track path of unit
649     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
650     {
651       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
652       {
653         enemy->movePath[j] = enemy->movePath[j - 1];
654       }
655       enemy->movePath[0] = enemy->simPosition;
656       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
657       {
658         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
659       }
660     }
661 
662     if (waypointPassedCount > 0)
663     {
664       enemy->currentX = enemy->nextX;
665       enemy->currentY = enemy->nextY;
666       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) &&
667         Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
668       {
669         // enemy reached the castle; remove it
670         enemy->enemyType = ENEMY_TYPE_NONE;
671         continue;
672       }
673     }
674   }
675 
676   // handle collisions between enemies
677   for (int i = 0; i < enemyCount - 1; i++)
678   {
679     Enemy *enemyA = &enemies[i];
680     if (enemyA->enemyType == ENEMY_TYPE_NONE)
681     {
682       continue;
683     }
684     for (int j = i + 1; j < enemyCount; j++)
685     {
686       Enemy *enemyB = &enemies[j];
687       if (enemyB->enemyType == ENEMY_TYPE_NONE)
688       {
689         continue;
690       }
691       float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition);
692       float radiusA = enemyClassConfigs[enemyA->enemyType].radius;
693       float radiusB = enemyClassConfigs[enemyB->enemyType].radius;
694       float radiusSum = radiusA + radiusB;
695       if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f)
696       {
697         // collision
698         float distance = sqrtf(distanceSqr);
699         float overlap = radiusSum - distance;
700         // move the enemies apart, but softly; if we have a clog of enemies,
701         // moving them perfectly apart can cause them to jitter
702         float positionCorrection = overlap / 5.0f;
703         Vector2 direction = (Vector2){
704             (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection,
705             (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection};
706         enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction);
707         enemyB->simPosition = Vector2Add(enemyB->simPosition, direction);
708       }
709     }
710   }
711 
712   // handle collisions between enemies and towers
713   for (int i = 0; i < enemyCount; i++)
714   {
715     Enemy *enemy = &enemies[i];
716     if (enemy->enemyType == ENEMY_TYPE_NONE)
717     {
718       continue;
719     }
720     enemy->contactTime -= gameTime.deltaTime;
721     if (enemy->contactTime < 0.0f)
722     {
723       enemy->contactTime = 0.0f;
724     }
725 
726     float enemyRadius = enemyClassConfigs[enemy->enemyType].radius;
727     // linear search over towers; could be optimized by using path finding tower map,
728     // but for now, we keep it simple
729     for (int j = 0; j < towerCount; j++)
730     {
731       Tower *tower = &towers[j];
732       if (tower->towerType == TOWER_TYPE_NONE)
733       {
734         continue;
735       }
736       float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y});
737       float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1
738       if (distanceSqr > combinedRadius * combinedRadius)
739       {
740         continue;
741       }
742       // potential collision; square / circle intersection
743       float dx = tower->x - enemy->simPosition.x;
744       float dy = tower->y - enemy->simPosition.y;
745       float absDx = fabsf(dx);
746       float absDy = fabsf(dy);
747       Vector3 contactPoint = {0};
748       if (absDx <= 0.5f && absDx <= absDy) {
749         // vertical collision; push the enemy out horizontally
750         float overlap = enemyRadius + 0.5f - absDy;
751         if (overlap < 0.0f)
752         {
753           continue;
754         }
755         float direction = dy > 0.0f ? -1.0f : 1.0f;
756         enemy->simPosition.y += direction * overlap;
757         contactPoint = (Vector3){enemy->simPosition.x, 0.2f, tower->x + direction * 0.5f};
758       }
759       else if (absDy <= 0.5f && absDy <= absDx)
760       {
761         // horizontal collision; push the enemy out vertically
762         float overlap = enemyRadius + 0.5f - absDx;
763         if (overlap < 0.0f)
764         {
765           continue;
766         }
767         float direction = dx > 0.0f ? -1.0f : 1.0f;
768         enemy->simPosition.x += direction * overlap;
769         contactPoint = (Vector3){tower->x + direction * 0.5f, 0.2f, enemy->simPosition.y};
770       }
771       else
772       {
773         // possible collision with a corner
774         float cornerDX = dx > 0.0f ? -0.5f : 0.5f;
775         float cornerDY = dy > 0.0f ? -0.5f : 0.5f;
776         float cornerX = tower->x + cornerDX;
777         float cornerY = tower->y + cornerDY;
778         float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY});
779         if (cornerDistanceSqr > enemyRadius * enemyRadius)
780         {
781           continue;
782         }
783         // push the enemy out along the diagonal
784         float cornerDistance = sqrtf(cornerDistanceSqr);
785         float overlap = enemyRadius - cornerDistance;
786         float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX;
787         float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY;
788         enemy->simPosition.x -= directionX * overlap;
789         enemy->simPosition.y -= directionY * overlap;
790         contactPoint = (Vector3){cornerX, 0.2f, cornerY};
791       }
792 
793       if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f)
794       {
795         enemy->contactTime += gameTime.deltaTime * 2.0f; // * 2 to undo the subtraction above
796         if (enemy->contactTime >= enemyClassConfigs[enemy->enemyType].requiredContactTime)
797         {
798           EnemyTriggerExplode(enemy, tower, contactPoint);
799         }
800       }
801     }
802   }
803 }
804 
805 EnemyId EnemyGetId(Enemy *enemy)
806 {
807   return (EnemyId){enemy - enemies, enemy->generation};
808 }
809 
810 Enemy *EnemyTryResolve(EnemyId enemyId)
811 {
812   if (enemyId.index >= ENEMY_MAX_COUNT)
813   {
814     return 0;
815   }
816   Enemy *enemy = &enemies[enemyId.index];
817   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
818   {
819     return 0;
820   }
821   return enemy;
822 }
823 
824 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
825 {
826   Enemy *spawn = 0;
827   for (int i = 0; i < enemyCount; i++)
828   {
829     Enemy *enemy = &enemies[i];
830     if (enemy->enemyType == ENEMY_TYPE_NONE)
831     {
832       spawn = enemy;
833       break;
834     }
835   }
836 
837   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
838   {
839     spawn = &enemies[enemyCount++];
840   }
841 
842   if (spawn)
843   {
844     spawn->currentX = currentX;
845     spawn->currentY = currentY;
846     spawn->nextX = currentX;
847     spawn->nextY = currentY;
848     spawn->simPosition = (Vector2){currentX, currentY};
849     spawn->simVelocity = (Vector2){0, 0};
850     spawn->enemyType = enemyType;
851     spawn->startMovingTime = gameTime.time;
852     spawn->damage = 0.0f;
853     spawn->futureDamage = 0.0f;
854     spawn->generation++;
855     spawn->movePathCount = 0;
856   }
857 
858   return spawn;
859 }
860 
861 int EnemyAddDamage(Enemy *enemy, float damage)
862 {
863   enemy->damage += damage;
864   if (enemy->damage >= EnemyGetMaxHealth(enemy))
865   {
866     enemy->enemyType = ENEMY_TYPE_NONE;
867     return 1;
868   }
869 
870   return 0;
871 }
872 
873 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
874 {
875   int16_t castleX = 0;
876   int16_t castleY = 0;
877   Enemy* closest = 0;
878   int16_t closestDistance = 0;
879   float range2 = range * range;
880   for (int i = 0; i < enemyCount; i++)
881   {
882     Enemy* enemy = &enemies[i];
883     if (enemy->enemyType == ENEMY_TYPE_NONE)
884     {
885       continue;
886     }
887     float maxHealth = EnemyGetMaxHealth(enemy);
888     if (enemy->futureDamage >= maxHealth)
889     {
890       // ignore enemies that will die soon
891       continue;
892     }
893     int16_t dx = castleX - enemy->currentX;
894     int16_t dy = castleY - enemy->currentY;
895     int16_t distance = abs(dx) + abs(dy);
896     if (!closest || distance < closestDistance)
897     {
898       float tdx = towerX - enemy->currentX;
899       float tdy = towerY - enemy->currentY;
900       float tdistance2 = tdx * tdx + tdy * tdy;
901       if (tdistance2 <= range2)
902       {
903         closest = enemy;
904         closestDistance = distance;
905       }
906     }
907   }
908   return closest;
909 }
910 
911 int EnemyCount()
912 {
913   int count = 0;
914   for (int i = 0; i < enemyCount; i++)
915   {
916     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
917     {
918       count++;
919     }
920   }
921   return count;
922 }
923 
924 //# Projectiles
925 #define PROJECTILE_MAX_COUNT 1200
926 #define PROJECTILE_TYPE_NONE 0
927 #define PROJECTILE_TYPE_BULLET 1
928 
929 typedef struct Projectile
930 {
931   uint8_t projectileType;
932   float shootTime;
933   float arrivalTime;
934   float damage;
935   Vector2 position;
936   Vector2 target;
937   Vector2 directionNormal;
938   EnemyId targetEnemy;
939 } Projectile;
940 
941 Projectile projectiles[PROJECTILE_MAX_COUNT];
942 int projectileCount = 0;
943 
944 void ProjectileInit()
945 {
946   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
947   {
948     projectiles[i] = (Projectile){0};
949   }
950 }
951 
952 void ProjectileDraw()
953 {
954   for (int i = 0; i < projectileCount; i++)
955   {
956     Projectile projectile = projectiles[i];
957     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
958     {
959       continue;
960     }
961     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
962     if (transition >= 1.0f)
963     {
964       continue;
965     }
966     Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition);
967     float x = position.x;
968     float y = position.y;
969     float dx = projectile.directionNormal.x;
970     float dy = projectile.directionNormal.y;
971     for (float d = 1.0f; d > 0.0f; d -= 0.25f)
972     {
973       x -= dx * 0.1f;
974       y -= dy * 0.1f;
975       float size = 0.1f * d;
976       DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED);
977     }
978   }
979 }
980 
981 void ProjectileUpdate()
982 {
983   for (int i = 0; i < projectileCount; i++)
984   {
985     Projectile *projectile = &projectiles[i];
986     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
987     {
988       continue;
989     }
990     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
991     if (transition >= 1.0f)
992     {
993       projectile->projectileType = PROJECTILE_TYPE_NONE;
994       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
995       if (enemy)
996       {
997         EnemyAddDamage(enemy, projectile->damage);
998       }
999       continue;
1000     }
1001   }
1002 }
1003 
1004 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage)
1005 {
1006   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
1007   {
1008     Projectile *projectile = &projectiles[i];
1009     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
1010     {
1011       projectile->projectileType = projectileType;
1012       projectile->shootTime = gameTime.time;
1013       projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed;
1014       projectile->damage = damage;
1015       projectile->position = position;
1016       projectile->target = target;
1017       projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position));
1018       projectile->targetEnemy = EnemyGetId(enemy);
1019       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
1020       return projectile;
1021     }
1022   }
1023   return 0;
1024 }
1025 
1026 //# Towers
1027 
1028 void TowerInit()
1029 {
1030   for (int i = 0; i < TOWER_MAX_COUNT; i++)
1031   {
1032     towers[i] = (Tower){0};
1033   }
1034   towerCount = 0;
1035 }
1036 
1037 Tower *TowerGetAt(int16_t x, int16_t y)
1038 {
1039   for (int i = 0; i < towerCount; i++)
1040   {
1041     if (towers[i].x == x && towers[i].y == y)
1042     {
1043       return &towers[i];
1044     }
1045   }
1046   return 0;
1047 }
1048 
1049 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
1050 {
1051   if (towerCount >= TOWER_MAX_COUNT)
1052   {
1053     return 0;
1054   }
1055 
1056   Tower *tower = TowerGetAt(x, y);
1057   if (tower)
1058   {
1059     return 0;
1060   }
1061 
1062   tower = &towers[towerCount++];
1063   tower->x = x;
1064   tower->y = y;
1065   tower->towerType = towerType;
1066   tower->cooldown = 0.0f;
1067   tower->damage = 0.0f;
1068   return tower;
1069 }
1070 
1071 float TowerGetMaxHealth(Tower *tower)
1072 {
1073   switch (tower->towerType)
1074   {
1075   case TOWER_TYPE_BASE:
1076     return 10.0f;
1077   case TOWER_TYPE_GUN:
1078     return 3.0f;
1079   case TOWER_TYPE_WALL:
1080     return 5.0f;
1081   }
1082   return 0.0f;
1083 }
1084 
1085 void TowerDraw()
1086 {
1087   for (int i = 0; i < towerCount; i++)
1088   {
1089     Tower tower = towers[i];
1090     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
1091     switch (tower.towerType)
1092     {
1093     case TOWER_TYPE_BASE:
1094       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
1095       break;
1096     case TOWER_TYPE_GUN:
1097       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
1098       break;
1099     case TOWER_TYPE_WALL:
1100       DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
1101       break;
1102     }
1103   }
1104 }
1105 
1106 void TowerGunUpdate(Tower *tower)
1107 {
1108   if (tower->cooldown <= 0)
1109   {
1110     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
1111     if (enemy)
1112     {
1113       tower->cooldown = 0.125f;
1114       // shoot the enemy; determine future position of the enemy
1115       float bulletSpeed = 1.0f;
1116       float bulletDamage = 3.0f;
1117       Vector2 velocity = enemy->simVelocity;
1118       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
1119       Vector2 towerPosition = {tower->x, tower->y};
1120       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
1121       for (int i = 0; i < 8; i++) {
1122         velocity = enemy->simVelocity;
1123         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
1124         float distance = Vector2Distance(towerPosition, futurePosition);
1125         float eta2 = distance / bulletSpeed;
1126         if (fabs(eta - eta2) < 0.01f) {
1127           break;
1128         }
1129         eta = (eta2 + eta) * 0.5f;
1130       }
1131       ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, towerPosition, futurePosition, 
1132         bulletSpeed, bulletDamage);
1133       enemy->futureDamage += bulletDamage;
1134     }
1135   }
1136   else
1137   {
1138     tower->cooldown -= gameTime.deltaTime;
1139   }
1140 }
1141 
1142 void TowerUpdate()
1143 {
1144   for (int i = 0; i < towerCount; i++)
1145   {
1146     Tower *tower = &towers[i];
1147     switch (tower->towerType)
1148     {
1149     case TOWER_TYPE_GUN:
1150       TowerGunUpdate(tower);
1151       break;
1152     }
1153   }
1154 }
1155 
1156 //# Game
1157 
1158 float nextSpawnTime = 0.0f;
1159 
1160 void ResetGame() 1161 { 1162 TowerInit(); 1163 EnemyInit(); 1164 ProjectileInit(); 1165 ParticleInit(); 1166 TowerTryAdd(TOWER_TYPE_BASE, 0, 0); 1167 } 1168
1169 void InitGame() 1170 { 1171 TowerInit(); 1172 EnemyInit(); 1173 ProjectileInit(); 1174 ParticleInit(); 1175 PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f); 1176 1177 TowerTryAdd(TOWER_TYPE_BASE, 0, 0); 1178 TowerTryAdd(TOWER_TYPE_GUN, 2, 0); 1179 1180 for (int i = -2; i <= 2; i += 1) 1181 { 1182 TowerTryAdd(TOWER_TYPE_WALL, i, 2); 1183 TowerTryAdd(TOWER_TYPE_WALL, i, -2); 1184 TowerTryAdd(TOWER_TYPE_WALL, -2, i); 1185 } 1186 1187 EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4); 1188 } 1189 1190 //# Immediate GUI functions 1191 1192 typedef struct ButtonState { 1193 char isSelected;
1194 } ButtonState; 1195 1196 typedef struct GUIState { 1197 int isBlocked; 1198 } GUIState; 1199 1200 GUIState guiState = {0};
1201 1202 int Button(const char *text, int x, int y, int width, int height, ButtonState *state) 1203 { 1204 Rectangle bounds = {x, y, width, height}; 1205 int isPressed = 0; 1206 int isSelected = state && state->isSelected; 1207 if (CheckCollisionPointRec(GetMousePosition(), bounds)) 1208 { 1209 Color color = isSelected ? DARKGRAY : GRAY; 1210 DrawRectangle(x, y, width, height, color);
1211 if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && !guiState.isBlocked)
1212 { 1213 isPressed = 1;
1214 } 1215 guiState.isBlocked = 1;
1216 } 1217 else 1218 { 1219 Color color = isSelected ? WHITE : LIGHTGRAY; 1220 DrawRectangle(x, y, width, height, color); 1221 } 1222 Font font = GetFontDefault(); 1223 Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1); 1224 DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, BLACK); 1225 return isPressed; 1226 } 1227 1228 //# Main game loop 1229 1230 void GameUpdate() 1231 { 1232 float dt = GetFrameTime(); 1233 // cap maximum delta time to 0.1 seconds to prevent large time steps 1234 if (dt > 0.1f) dt = 0.1f; 1235 gameTime.time += dt; 1236 gameTime.deltaTime = dt; 1237 PathFindingMapUpdate(); 1238 EnemyUpdate(); 1239 TowerUpdate(); 1240 ProjectileUpdate(); 1241 ParticleUpdate(); 1242 1243 // spawn a new enemy every second 1244 if (gameTime.time >= nextSpawnTime && EnemyCount() < 50) 1245 { 1246 nextSpawnTime = gameTime.time + 0.2f; 1247 // add a new enemy at the boundary of the map 1248 int randValue = GetRandomValue(-5, 5); 1249 int randSide = GetRandomValue(0, 3); 1250 int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue; 1251 int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue; 1252 static int alternation = 0; 1253 alternation += 1; 1254 if (alternation % 3 == 0) { 1255 EnemyTryAdd(ENEMY_TYPE_MINION, 0, -5); 1256 } 1257 else if (alternation % 3 == 1) 1258 { 1259 EnemyTryAdd(ENEMY_TYPE_MINION, 0, 5); 1260 } 1261 EnemyTryAdd(ENEMY_TYPE_MINION, x, y); 1262 } 1263 } 1264 1265 int main(void) 1266 { 1267 int screenWidth, screenHeight; 1268 GetPreferredSize(&screenWidth, &screenHeight); 1269 InitWindow(screenWidth, screenHeight, "Tower defense"); 1270 SetTargetFPS(30); 1271 1272 Camera3D camera = {0}; 1273 1274 int cameraMode = 0; 1275 1276 InitGame(); 1277 1278 while (!WindowShouldClose()) 1279 { 1280 if (IsPaused()) { 1281 // canvas is not visible in browser - do nothing 1282 continue; 1283 } 1284 1285 if (cameraMode == 0) 1286 { 1287 camera.position = (Vector3){0.0f, 10.0f, -0.5f}; 1288 camera.target = (Vector3){0.0f, 0.0f, -0.5f}; 1289 camera.up = (Vector3){0.0f, 0.0f, -1.0f}; 1290 camera.fovy = 12.0f; 1291 camera.projection = CAMERA_ORTHOGRAPHIC; 1292 } 1293 else 1294 { 1295 camera.position = (Vector3){1.0f, 12.0f, 6.5f}; 1296 camera.target = (Vector3){0.0f, 0.5f, 1.0f}; 1297 camera.up = (Vector3){0.0f, 1.0f, 0.0f}; 1298 camera.fovy = 45.0f; 1299 camera.projection = CAMERA_PERSPECTIVE; 1300 } 1301 1302 BeginDrawing(); 1303 ClearBackground(DARKBLUE); 1304 1305 BeginMode3D(camera); 1306 DrawGrid(10, 1.0f); 1307 TowerDraw(); 1308 EnemyDraw(); 1309 ProjectileDraw(); 1310 // PathFindingMapDraw(); 1311 ParticleDraw(); 1312 GameUpdate(); 1313 1314 static int placementMode = TOWER_TYPE_NONE;
1315
1316 Ray ray = GetScreenToWorldRay(GetMousePosition(), camera); 1317 float planeDistance = ray.position.y / -ray.direction.y; 1318 float planeX = ray.direction.x * planeDistance + ray.position.x; 1319 float planeY = ray.direction.z * planeDistance + ray.position.z; 1320 int16_t mapX = (int16_t)floorf(planeX + 0.5f); 1321 int16_t mapY = (int16_t)floorf(planeY + 0.5f);
1322 if (placementMode && !guiState.isBlocked)
1323 { 1324 DrawCubeWires((Vector3){mapX, 0.2f, mapY}, 1.0f, 0.4f, 1.0f, RED); 1325 if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) 1326 { 1327 TowerTryAdd(placementMode, mapX, mapY); 1328 placementMode = TOWER_TYPE_NONE; 1329 }
1330 } 1331 1332 guiState.isBlocked = 0;
1333 1334 EndMode3D(); 1335 1336 const char *title = "Tower defense tutorial"; 1337 int titleWidth = MeasureText(title, 20); 1338 DrawText(title, (GetScreenWidth() - titleWidth) * 0.5f + 2, 5 + 2, 20, BLACK); 1339 DrawText(title, (GetScreenWidth() - titleWidth) * 0.5f, 5, 20, WHITE); 1340 1341 const char *buttonText = cameraMode == 0 ? "2D" : "3D"; 1342 if (Button(buttonText, 10, 10, 80, 30, 0)) 1343 { 1344 cameraMode = !cameraMode; 1345 } 1346 1347 static ButtonState buildWallButtonState = {0}; 1348 static ButtonState buildGunButtonState = {0}; 1349 buildWallButtonState.isSelected = placementMode == TOWER_TYPE_WALL; 1350 buildGunButtonState.isSelected = placementMode == TOWER_TYPE_GUN; 1351 1352 if (Button("Wall", 10, 50, 80, 30, &buildWallButtonState)) 1353 { 1354 placementMode = buildWallButtonState.isSelected ? 0 : TOWER_TYPE_WALL; 1355 } 1356 if (Button("Gun", 10, 90, 80, 30, &buildGunButtonState)) 1357 {
1358 placementMode = buildGunButtonState.isSelected ? 0 : TOWER_TYPE_GUN; 1359 } 1360 1361 if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0)) 1362 { 1363 ResetGame();
1364 } 1365 1366 EndDrawing(); 1367 } 1368 1369 CloseWindow(); 1370 1371 return 0; 1372 }
  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 #endif
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif

All right, that is already quite interesting to play around with. Placing buildings and seeing the enemies react to them is quite satisfying. But at this point, we have no way to win or lose the game. There are no rules, no resource and no goal.

Coming up with the rules and to implement them is the topic of the next part, as this is the end of this part!

Wrap up

We have a basic UI, can get the mouse position in the world, can place buildings, and the enemies react can destroy buildings. The C file has now nearly 1400 lines of code - but I hope that it is still manageable.

It is easier to prepare and show the differences between each version when having only one file, but eventually, it might make sense at some point to split the code parts that are less frequently changed into separate files so it's easier to focus on the currently changed part.

That said, I hope you enjoyed this part and to see you in the next one next week!

🍪