Simple tower defense tutorial, part 2
In the previous part, we set up the basic game loop and rendering for enemies, towers and bullets. In this part, we will add the following features:
- Handling health and damage
 - Refining the shooting algorithm to predict enemy positions
 - Refining the enemy movement to be more smooth
 - Adding collision detection between enemies
 
But first, let's have a look at the current state of the game:
  1 #include "td-tut-2-main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 typedef struct GameTime
  7 {
  8   float time;
  9   float deltaTime;
 10 } GameTime;
 11 
 12 GameTime gameTime = {0};
 13 
 14 //# Enemies
 15 
 16 #define ENEMY_MAX_COUNT 400
 17 #define ENEMY_TYPE_NONE 0
 18 #define ENEMY_TYPE_MINION 1
 19 
 20 typedef struct EnemyId
 21 {
 22   uint16_t index;
 23   uint16_t generation;
 24 } EnemyId;
 25 
 26 typedef struct Enemy
 27 {
 28   int16_t currentX, currentY;
 29   int16_t nextX, nextY;
 30   uint16_t generation;
 31   float startMovingTime;
 32   uint8_t enemyType;
 33 } Enemy;
 34 
 35 Enemy enemies[ENEMY_MAX_COUNT];
 36 int enemyCount = 0;
 37 
 38 void EnemyInit()
 39 {
 40   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 41   {
 42     enemies[i] = (Enemy){0};
 43   }
 44   enemyCount = 0;
 45 }
 46 
 47 float EnemyGetCurrentSpeed(Enemy *enemy)
 48 {
 49   switch (enemy->enemyType)
 50   {
 51   case ENEMY_TYPE_MINION:
 52     return 1.0f;
 53   }
 54   return 1.0f;
 55 }
 56 
 57 void EnemyDraw()
 58 {
 59   for (int i = 0; i < enemyCount; i++)
 60   {
 61     Enemy enemy = enemies[i];
 62     float speed = EnemyGetCurrentSpeed(&enemy);
 63     float transition = (gameTime.time - enemy.startMovingTime) * speed;
 64 
 65     float x = enemy.currentX + (enemy.nextX - enemy.currentX) * transition;
 66     float y = enemy.currentY + (enemy.nextY - enemy.currentY) * transition;
 67     
 68     switch (enemy.enemyType)
 69     {
 70     case ENEMY_TYPE_MINION:
 71       DrawCube((Vector3){x, 0.2f, y}, 0.4f, 0.4f, 0.4f, GREEN);
 72       break;
 73     }
 74   }
 75 }
 76 
 77 void EnemyUpdate()
 78 {
 79   const int16_t castleX = 0;
 80   const int16_t castleY = 0;
 81 
 82   for (int i = 0; i < enemyCount; i++)
 83   {
 84     Enemy *enemy = &enemies[i];
 85     if (enemy->enemyType == ENEMY_TYPE_NONE)
 86     {
 87       continue;
 88     }
 89     float speed = EnemyGetCurrentSpeed(enemy);
 90     float transition = (gameTime.time - enemy->startMovingTime) * speed;
 91     if (transition >= 1.0f)
 92     {
 93       enemy->startMovingTime = gameTime.time;
 94       enemy->currentX = enemy->nextX;
 95       enemy->currentY = enemy->nextY;
 96       int16_t dx = castleX - enemy->currentX;
 97       int16_t dy = castleY - enemy->currentY;
 98       if (dx == 0 && dy == 0)
 99       {
100         // enemy reached the castle; remove it
101         enemy->enemyType = ENEMY_TYPE_NONE;
102         continue;
103       }
104       if (abs(dx) > abs(dy))
105       {
106         enemy->nextX = enemy->currentX + (dx > 0 ? 1 : -1);
107         enemy->nextY = enemy->currentY;
108       }
109       else
110       {
111         enemy->nextX = enemy->currentX;
112         enemy->nextY = enemy->currentY + (dy > 0 ? 1 : -1);
113       }
114     }
115   }
116 }
117 
118 EnemyId EnemyGetId(Enemy *enemy)
119 {
120   return (EnemyId){enemy - enemies, enemy->generation};
121 }
122 
123 Enemy *EnemyTryResolve(EnemyId enemyId)
124 {
125   if (enemyId.index >= ENEMY_MAX_COUNT)
126   {
127     return 0;
128   }
129   Enemy *enemy = &enemies[enemyId.index];
130   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
131   {
132     return 0;
133   }
134   return enemy;
135 }
136 
137 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
138 {
139   Enemy *spawn = 0;
140   for (int i = 0; i < enemyCount; i++)
141   {
142     Enemy *enemy = &enemies[i];
143     if (enemy->enemyType == ENEMY_TYPE_NONE)
144     {
145       spawn = enemy;
146       break;
147     }
148   }
149 
150   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
151   {
152     spawn = &enemies[enemyCount++];
153   }
154 
155   if (spawn)
156   {
157     spawn->currentX = currentX;
158     spawn->currentY = currentY;
159     spawn->nextX = currentX;
160     spawn->nextY = currentY;
161     spawn->enemyType = enemyType;
162     spawn->startMovingTime = 0;
163     spawn->generation++;
164   }
165 
166   return spawn;
167 }
168 
169 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
170 {
171   int16_t castleX = 0;
172   int16_t castleY = 0;
173   Enemy* closest = 0;
174   int16_t closestDistance = 0;
175   float range2 = range * range;
176   for (int i = 0; i < enemyCount; i++)
177   {
178     Enemy* enemy = &enemies[i];
179     if (enemy->enemyType == ENEMY_TYPE_NONE)
180     {
181       continue;
182     }
183     int16_t dx = castleX - enemy->currentX;
184     int16_t dy = castleY - enemy->currentY;
185     int16_t distance = abs(dx) + abs(dy);
186     if (!closest || distance < closestDistance)
187     {
188       float tdx = towerX - enemy->currentX;
189       float tdy = towerY - enemy->currentY;
190       float tdistance2 = tdx * tdx + tdy * tdy;
191       if (tdistance2 <= range2)
192       {
193         closest = enemy;
194         closestDistance = distance;
195       }
196     }
197   }
198   return closest;
199 }
200 
201 //# Projectiles
202 #define PROJECTILE_MAX_COUNT 1200
203 #define PROJECTILE_TYPE_NONE 0
204 #define PROJECTILE_TYPE_BULLET 1
205 
206 typedef struct Projectile
207 {
208   uint8_t projectileType;
209   float shootTime;
210   float arrivalTime;
211   float damage;
212   Vector2 position;
213   Vector2 target;
214   Vector2 directionNormal;
215   EnemyId targetEnemy;
216 } Projectile;
217 
218 Projectile projectiles[PROJECTILE_MAX_COUNT];
219 int projectileCount = 0;
220 
221 void ProjectileInit()
222 {
223   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
224   {
225     projectiles[i] = (Projectile){0};
226   }
227 }
228 
229 void ProjectileDraw()
230 {
231   for (int i = 0; i < projectileCount; i++)
232   {
233     Projectile projectile = projectiles[i];
234     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
235     {
236       continue;
237     }
238     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
239     if (transition >= 1.0f)
240     {
241       continue;
242     }
243     Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition);
244     float x = position.x;
245     float y = position.y;
246     float dx = projectile.directionNormal.x;
247     float dy = projectile.directionNormal.y;
248     for (float d = 1.0f; d > 0.0f; d -= 0.25f)
249     {
250       x -= dx * 0.1f;
251       y -= dy * 0.1f;
252       float size = 0.1f * d;
253       DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED);
254     }
255   }
256 }
257 
258 void ProjectileUpdate()
259 {
260   for (int i = 0; i < projectileCount; i++)
261   {
262     Projectile *projectile = &projectiles[i];
263     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
264     {
265       continue;
266     }
267     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
268     if (transition >= 1.0f)
269     {
270       projectile->projectileType = PROJECTILE_TYPE_NONE;
271       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
272       if (enemy)
273       {
274         enemy->enemyType = ENEMY_TYPE_NONE;
275       }
276       continue;
277     }
278   }
279 }
280 
281 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage)
282 {
283   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
284   {
285     Projectile *projectile = &projectiles[i];
286     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
287     {
288       projectile->projectileType = projectileType;
289       projectile->shootTime = gameTime.time;
290       projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed;
291       projectile->damage = damage;
292       projectile->position = position;
293       projectile->target = target;
294       projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position));
295       projectile->targetEnemy = EnemyGetId(enemy);
296       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
297       return projectile;
298     }
299   }
300   return 0;
301 }
302 
303 //# Towers
304 
305 #define TOWER_MAX_COUNT 400
306 #define TOWER_TYPE_NONE 0
307 #define TOWER_TYPE_BASE 1
308 #define TOWER_TYPE_GUN 2
309 
310 typedef struct Tower
311 {
312   int16_t x, y;
313   uint8_t towerType;
314   float cooldown;
315 } Tower;
316 
317 Tower towers[TOWER_MAX_COUNT];
318 int towerCount = 0;
319 
320 void TowerInit()
321 {
322   for (int i = 0; i < TOWER_MAX_COUNT; i++)
323   {
324     towers[i] = (Tower){0};
325   }
326   towerCount = 0;
327 }
328 
329 Tower *TowerGetAt(int16_t x, int16_t y)
330 {
331   for (int i = 0; i < towerCount; i++)
332   {
333     if (towers[i].x == x && towers[i].y == y)
334     {
335       return &towers[i];
336     }
337   }
338   return 0;
339 }
340 
341 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
342 {
343   if (towerCount >= TOWER_MAX_COUNT)
344   {
345     return 0;
346   }
347 
348   Tower *tower = TowerGetAt(x, y);
349   if (tower)
350   {
351     return 0;
352   }
353 
354   tower = &towers[towerCount++];
355   tower->x = x;
356   tower->y = y;
357   tower->towerType = towerType;
358   return tower;
359 }
360 
361 void TowerDraw()
362 {
363   for (int i = 0; i < towerCount; i++)
364   {
365     Tower tower = towers[i];
366     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
367     switch (tower.towerType)
368     {
369     case TOWER_TYPE_BASE:
370       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
371       break;
372     case TOWER_TYPE_GUN:
373       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
374       break;
375     }
376   }
377 }
378 
379 void TowerGunUpdate(Tower *tower)
380 {
381   if (tower->cooldown <= 0)
382   {
383     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
384     if (enemy)
385     {
386       tower->cooldown = 0.5f;
387       // shoot the enemy
388       ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, (Vector2){tower->x, tower->y}, (Vector2){enemy->currentX, enemy->currentY}, 5.0f, 1.0f);
389     }
390   }
391   else
392   {
393     tower->cooldown -= gameTime.deltaTime;
394   }
395 }
396 
397 void TowerUpdate()
398 {
399   for (int i = 0; i < towerCount; i++)
400   {
401     Tower *tower = &towers[i];
402     switch (tower->towerType)
403     {
404     case TOWER_TYPE_GUN:
405       TowerGunUpdate(tower);
406       break;
407     }
408   }
409 }
410 
411 //# Game
412 
413 float nextSpawnTime = 0.0f;
414 
415 void InitGame()
416 {
417   TowerInit();
418   EnemyInit();
419   ProjectileInit();
420 
421   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
422   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
423   TowerTryAdd(TOWER_TYPE_GUN, -2, 0);
424   EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4);
425 }
426 
427 void GameUpdate()
428 {
429   float dt = GetFrameTime();
430   // cap maximum delta time to 0.1 seconds to prevent large time steps
431   if (dt > 0.1f) dt = 0.1f;
432   gameTime.time += dt;
433   gameTime.deltaTime = dt;
434   EnemyUpdate();
435   TowerUpdate();
436   ProjectileUpdate();
437 
438   // spawn a new enemy every second
439   if (gameTime.time >= nextSpawnTime)
440   {
441     nextSpawnTime = gameTime.time + 1.0f;
442     // add a new enemy at the boundary of the map
443     int randValue = GetRandomValue(-5, 5);
444     int randSide = GetRandomValue(0, 3);
445     int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue;
446     int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue;
447     EnemyTryAdd(ENEMY_TYPE_MINION, x, y);
448   }
449 }
450 
451 int main(void)
452 {
453   int screenWidth, screenHeight;
454   GetPreferredSize(&screenWidth, &screenHeight);
455   InitWindow(screenWidth, screenHeight, "Tower defense");
456   SetTargetFPS(30);
457 
458   Camera3D camera = {0};
459   camera.position = (Vector3){0.0f, 10.0f, 5.0f};
460   camera.target = (Vector3){0.0f, 0.0f, 0.0f};
461   camera.up = (Vector3){0.0f, 0.0f, -1.0f};
462   camera.fovy = 45.0f;
463   camera.projection = CAMERA_PERSPECTIVE;
464 
465   InitGame();
466 
467   while (!WindowShouldClose())
468   {
469     if (IsPaused()) {
470       // canvas is not visible in browser - do nothing
471       continue;
472     }
473     BeginDrawing();
474     ClearBackground(DARKBLUE);
475 
476     BeginMode3D(camera);
477     DrawGrid(10, 1.0f);
478     TowerDraw();
479     EnemyDraw();
480     ProjectileDraw();
481     GameUpdate();
482     EndMode3D();
483 
484     DrawText("Tower defense tutorial", 5, 5, 20, WHITE);
485     EndDrawing();
486   }
487 
488   CloseWindow();
489 
490   return 0;
491 }
  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
Let's start with the health and damage system by adding enemy classes that specify their basic properties such as maximum health and speed:
  1 typedef struct EnemyClassConfig
  2 {
  3   float speed;
  4   float health;
  5 } EnemyClassConfig;
  6 
  7 EnemyClassConfig enemyClassConfigs[] = {
  8   [ENEMY_TYPE_MINION] = {.health = 3.0f, .speed = 1.0f},
  9 };
To the enemy struct, we add the sustained damage so far:
  1 typedef struct Enemy
  2 {
  3   int16_t currentX, currentY;
  4   int16_t nextX, nextY;
  5   uint16_t generation;
  6   float startMovingTime;
  7   float damage;
  8   uint8_t enemyType;
  9 } Enemy;
We could also use health and set it to the maximum health when spawning an enemy, but this is pretty much interchangeable with tracking the damage:
If we store the health, it's easy to detect if a unit has died when it sustained damage, because if it drops to zero, the unit is dead. But if we later want to display healthbars only on damaged units, we would need to calculate the damage sustained so far by subtracting the current health from the maximum.
On the other hand, if we store the damage, we can easily determine if we want to draw a healthbar. Determining if a unit is dead requires the comparison of the damage to the maximum health.
But it really doesn't matter for now - we only need some way to track the health.
There are now two functions to handle getting the speed and health of an enemy:
  1 float EnemyGetCurrentSpeed(Enemy *enemy)
  2 {
  3   return enemyClassConfigs[enemy->enemyType].speed;
  4 }
  5 
  6 float EnemyGetMaxHealth(Enemy *enemy)
  7 {
  8   return enemyClassConfigs[enemy->enemyType].health;
  9 }
Using a getter function here might be useful later in case we want to add buffs and debuffs, in which case we can modify the health and speed temporarily.
Since we have now a bit more health for the enemies, let's ramp up the shooting frequency and see how it looks:
  1 #include "td-tut-2-main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 typedef struct GameTime
  7 {
  8   float time;
  9   float deltaTime;
 10 } GameTime;
 11 
 12 GameTime gameTime = {0};
 13 
 14 //# Enemies
 15 
 16 #define ENEMY_MAX_COUNT 400
 17 #define ENEMY_TYPE_NONE 0
 18 #define ENEMY_TYPE_MINION 1
 19 
 20 typedef struct EnemyId
 21 {
 22   uint16_t index;
 23   uint16_t generation;
 24 } EnemyId;
 25 
 26 typedef struct EnemyClassConfig
 27 {
 28   float speed;
 29   float health;
 30 } EnemyClassConfig;
 31 
 32 typedef struct Enemy
 33 {
 34   int16_t currentX, currentY;
 35   int16_t nextX, nextY;
 36   uint16_t generation;
 37   float startMovingTime;
 38   float damage;
 39   uint8_t enemyType;
 40 } Enemy;
 41 
 42 Enemy enemies[ENEMY_MAX_COUNT];
 43 int enemyCount = 0;
 44 
 45 EnemyClassConfig enemyClassConfigs[] = {
 46     [ENEMY_TYPE_MINION] = {.health = 3.0f, .speed = 1.0f},
 47 };
 48 
 49 void EnemyInit()
 50 {
 51   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 52   {
 53     enemies[i] = (Enemy){0};
 54   }
 55   enemyCount = 0;
 56 }
 57 
 58 float EnemyGetCurrentSpeed(Enemy *enemy)
 59 {
 60   return enemyClassConfigs[enemy->enemyType].speed;
 61 }
 62 
 63 float EnemyGetMaxHealth(Enemy *enemy)
 64 {
 65   return enemyClassConfigs[enemy->enemyType].health;
 66 }
 67 
 68 void EnemyDraw()
 69 {
 70   for (int i = 0; i < enemyCount; i++)
 71   {
 72     Enemy enemy = enemies[i];
 73     float speed = EnemyGetCurrentSpeed(&enemy);
 74     float transition = (gameTime.time - enemy.startMovingTime) * speed;
 75 
 76     float x = enemy.currentX + (enemy.nextX - enemy.currentX) * transition;
 77     float y = enemy.currentY + (enemy.nextY - enemy.currentY) * transition;
 78     
 79     switch (enemy.enemyType)
 80     {
 81     case ENEMY_TYPE_MINION:
 82       DrawCube((Vector3){x, 0.2f, y}, 0.4f, 0.4f, 0.4f, GREEN);
 83       break;
 84     }
 85   }
 86 }
 87 
 88 void EnemyUpdate()
 89 {
 90   const int16_t castleX = 0;
 91   const int16_t castleY = 0;
 92 
 93   for (int i = 0; i < enemyCount; i++)
 94   {
 95     Enemy *enemy = &enemies[i];
 96     if (enemy->enemyType == ENEMY_TYPE_NONE)
 97     {
 98       continue;
 99     }
100     float speed = EnemyGetCurrentSpeed(enemy);
101     float transition = (gameTime.time - enemy->startMovingTime) * speed;
102     if (transition >= 1.0f)
103     {
104       enemy->startMovingTime = gameTime.time;
105       enemy->currentX = enemy->nextX;
106       enemy->currentY = enemy->nextY;
107       int16_t dx = castleX - enemy->currentX;
108       int16_t dy = castleY - enemy->currentY;
109       if (dx == 0 && dy == 0)
110       {
111         // enemy reached the castle; remove it
112         enemy->enemyType = ENEMY_TYPE_NONE;
113         continue;
114       }
115       if (abs(dx) > abs(dy))
116       {
117         enemy->nextX = enemy->currentX + (dx > 0 ? 1 : -1);
118         enemy->nextY = enemy->currentY;
119       }
120       else
121       {
122         enemy->nextX = enemy->currentX;
123         enemy->nextY = enemy->currentY + (dy > 0 ? 1 : -1);
124       }
125     }
126   }
127 }
128 
129 EnemyId EnemyGetId(Enemy *enemy)
130 {
131   return (EnemyId){enemy - enemies, enemy->generation};
132 }
133 
134 Enemy *EnemyTryResolve(EnemyId enemyId)
135 {
136   if (enemyId.index >= ENEMY_MAX_COUNT)
137   {
138     return 0;
139   }
140   Enemy *enemy = &enemies[enemyId.index];
141   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
142   {
143     return 0;
144   }
145   return enemy;
146 }
147 
148 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
149 {
150   Enemy *spawn = 0;
151   for (int i = 0; i < enemyCount; i++)
152   {
153     Enemy *enemy = &enemies[i];
154     if (enemy->enemyType == ENEMY_TYPE_NONE)
155     {
156       spawn = enemy;
157       break;
158     }
159   }
160 
161   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
162   {
163     spawn = &enemies[enemyCount++];
164   }
165 
166   if (spawn)
167   {
168     spawn->currentX = currentX;
169     spawn->currentY = currentY;
170     spawn->nextX = currentX;
171     spawn->nextY = currentY;
172     spawn->enemyType = enemyType;
173     spawn->startMovingTime = 0;
174     spawn->damage = 0.0f;
175     spawn->generation++;
176   }
177 
178   return spawn;
179 }
180 
181 int EnemyAddDamage(Enemy *enemy, float damage)
182 {
183   enemy->damage += damage;
184   if (enemy->damage >= EnemyGetMaxHealth(enemy))
185   {
186     enemy->enemyType = ENEMY_TYPE_NONE;
187     return 1;
188   }
189 
190   return 0;
191 }
192 
193 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
194 {
195   int16_t castleX = 0;
196   int16_t castleY = 0;
197   Enemy* closest = 0;
198   int16_t closestDistance = 0;
199   float range2 = range * range;
200   for (int i = 0; i < enemyCount; i++)
201   {
202     Enemy* enemy = &enemies[i];
203     if (enemy->enemyType == ENEMY_TYPE_NONE)
204     {
205       continue;
206     }
207     int16_t dx = castleX - enemy->currentX;
208     int16_t dy = castleY - enemy->currentY;
209     int16_t distance = abs(dx) + abs(dy);
210     if (!closest || distance < closestDistance)
211     {
212       float tdx = towerX - enemy->currentX;
213       float tdy = towerY - enemy->currentY;
214       float tdistance2 = tdx * tdx + tdy * tdy;
215       if (tdistance2 <= range2)
216       {
217         closest = enemy;
218         closestDistance = distance;
219       }
220     }
221   }
222   return closest;
223 }
224 
225 //# Projectiles
226 #define PROJECTILE_MAX_COUNT 1200
227 #define PROJECTILE_TYPE_NONE 0
228 #define PROJECTILE_TYPE_BULLET 1
229 
230 typedef struct Projectile
231 {
232   uint8_t projectileType;
233   float shootTime;
234   float arrivalTime;
235   float damage;
236   Vector2 position;
237   Vector2 target;
238   Vector2 directionNormal;
239   EnemyId targetEnemy;
240 } Projectile;
241 
242 Projectile projectiles[PROJECTILE_MAX_COUNT];
243 int projectileCount = 0;
244 
245 void ProjectileInit()
246 {
247   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
248   {
249     projectiles[i] = (Projectile){0};
250   }
251 }
252 
253 void ProjectileDraw()
254 {
255   for (int i = 0; i < projectileCount; i++)
256   {
257     Projectile projectile = projectiles[i];
258     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
259     {
260       continue;
261     }
262     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
263     if (transition >= 1.0f)
264     {
265       continue;
266     }
267     Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition);
268     float x = position.x;
269     float y = position.y;
270     float dx = projectile.directionNormal.x;
271     float dy = projectile.directionNormal.y;
272     for (float d = 1.0f; d > 0.0f; d -= 0.25f)
273     {
274       x -= dx * 0.1f;
275       y -= dy * 0.1f;
276       float size = 0.1f * d;
277       DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED);
278     }
279   }
280 }
281 
282 void ProjectileUpdate()
283 {
284   for (int i = 0; i < projectileCount; i++)
285   {
286     Projectile *projectile = &projectiles[i];
287     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
288     {
289       continue;
290     }
291     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
292     if (transition >= 1.0f)
293     {
294       projectile->projectileType = PROJECTILE_TYPE_NONE;
295       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
296       if (enemy)
297       {
298         EnemyAddDamage(enemy, projectile->damage);
299       }
300       continue;
301     }
302   }
303 }
304 
305 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage)
306 {
307   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
308   {
309     Projectile *projectile = &projectiles[i];
310     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
311     {
312       projectile->projectileType = projectileType;
313       projectile->shootTime = gameTime.time;
314       projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed;
315       projectile->damage = damage;
316       projectile->position = position;
317       projectile->target = target;
318       projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position));
319       projectile->targetEnemy = EnemyGetId(enemy);
320       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
321       return projectile;
322     }
323   }
324   return 0;
325 }
326 
327 //# Towers
328 
329 #define TOWER_MAX_COUNT 400
330 #define TOWER_TYPE_NONE 0
331 #define TOWER_TYPE_BASE 1
332 #define TOWER_TYPE_GUN 2
333 
334 typedef struct Tower
335 {
336   int16_t x, y;
337   uint8_t towerType;
338   float cooldown;
339 } Tower;
340 
341 Tower towers[TOWER_MAX_COUNT];
342 int towerCount = 0;
343 
344 void TowerInit()
345 {
346   for (int i = 0; i < TOWER_MAX_COUNT; i++)
347   {
348     towers[i] = (Tower){0};
349   }
350   towerCount = 0;
351 }
352 
353 Tower *TowerGetAt(int16_t x, int16_t y)
354 {
355   for (int i = 0; i < towerCount; i++)
356   {
357     if (towers[i].x == x && towers[i].y == y)
358     {
359       return &towers[i];
360     }
361   }
362   return 0;
363 }
364 
365 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
366 {
367   if (towerCount >= TOWER_MAX_COUNT)
368   {
369     return 0;
370   }
371 
372   Tower *tower = TowerGetAt(x, y);
373   if (tower)
374   {
375     return 0;
376   }
377 
378   tower = &towers[towerCount++];
379   tower->x = x;
380   tower->y = y;
381   tower->towerType = towerType;
382   return tower;
383 }
384 
385 void TowerDraw()
386 {
387   for (int i = 0; i < towerCount; i++)
388   {
389     Tower tower = towers[i];
390     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
391     switch (tower.towerType)
392     {
393     case TOWER_TYPE_BASE:
394       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
395       break;
396     case TOWER_TYPE_GUN:
397       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
398       break;
399     }
400   }
401 }
402 
403 void TowerGunUpdate(Tower *tower)
404 {
405   if (tower->cooldown <= 0)
406   {
407     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
408     if (enemy)
409     {
410       tower->cooldown = 0.25f;
411       // shoot the enemy
412       ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, (Vector2){tower->x, tower->y}, (Vector2){enemy->currentX, enemy->currentY}, 5.0f, 1.0f);
413     }
414   }
415   else
416   {
417     tower->cooldown -= gameTime.deltaTime;
418   }
419 }
420 
421 void TowerUpdate()
422 {
423   for (int i = 0; i < towerCount; i++)
424   {
425     Tower *tower = &towers[i];
426     switch (tower->towerType)
427     {
428     case TOWER_TYPE_GUN:
429       TowerGunUpdate(tower);
430       break;
431     }
432   }
433 }
434 
435 //# Game
436 
437 float nextSpawnTime = 0.0f;
438 
439 void InitGame()
440 {
441   TowerInit();
442   EnemyInit();
443   ProjectileInit();
444 
445   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
446   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
447   TowerTryAdd(TOWER_TYPE_GUN, -2, 0);
448   EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4);
449 }
450 
451 void GameUpdate()
452 {
453   float dt = GetFrameTime();
454   // cap maximum delta time to 0.1 seconds to prevent large time steps
455   if (dt > 0.1f) dt = 0.1f;
456   gameTime.time += dt;
457   gameTime.deltaTime = dt;
458   EnemyUpdate();
459   TowerUpdate();
460   ProjectileUpdate();
461 
462   // spawn a new enemy every second
463   if (gameTime.time >= nextSpawnTime)
464   {
465     nextSpawnTime = gameTime.time + 1.0f;
466     // add a new enemy at the boundary of the map
467     int randValue = GetRandomValue(-5, 5);
468     int randSide = GetRandomValue(0, 3);
469     int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue;
470     int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue;
471     EnemyTryAdd(ENEMY_TYPE_MINION, x, y);
472   }
473 }
474 
475 int main(void)
476 {
477   int screenWidth, screenHeight;
478   GetPreferredSize(&screenWidth, &screenHeight);
479   InitWindow(screenWidth, screenHeight, "Tower defense");
480   SetTargetFPS(30);
481 
482   Camera3D camera = {0};
483   camera.position = (Vector3){0.0f, 10.0f, 5.0f};
484   camera.target = (Vector3){0.0f, 0.0f, 0.0f};
485   camera.up = (Vector3){0.0f, 0.0f, -1.0f};
486   camera.fovy = 45.0f;
487   camera.projection = CAMERA_PERSPECTIVE;
488 
489   InitGame();
490 
491   while (!WindowShouldClose())
492   {
493     if (IsPaused()) {
494       // canvas is not visible in browser - do nothing
495       continue;
496     }
497     BeginDrawing();
498     ClearBackground(DARKBLUE);
499 
500     BeginMode3D(camera);
501     DrawGrid(10, 1.0f);
502     TowerDraw();
503     EnemyDraw();
504     ProjectileDraw();
505     GameUpdate();
506     EndMode3D();
507 
508     DrawText("Tower defense tutorial", 5, 5, 20, WHITE);
509     EndDrawing();
510   }
511 
512   CloseWindow();
513 
514   return 0;
515 }
  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
With the higher health and higher shooting frequency, we can see now that the projectiles don't really hit the enemies. They currently aim at the integer position of the enemy, but since we interpolate the enemy position, the projectile will almost always not really hit the enemy.
What we want is a function to calculate the position of the enemy based on the shooting distannce and the speed of the projectile. For straight moving objects, this can be done analytically, but since our enemies can change direction, this isn't so simple. So instead, we can use a simple iterative approach to find the position of the enemy at the time the projectile hits the enemy:
- Make initial guess for enemy position at projectile impact time (use t=0)
 - Calculate estimated projectile time of arrival (eta) for that position
 - Calculate enemy position for given projectile eta
 - The 2 eta times will now differ; create the average, use it as new eta and repeat for a few times
 - We can stop after a fixed amount of iterations or when the difference becomes small
Note: When the enemy is moving away at significant speed compared to the bullet (or even faster), this approach will not work well. The same is true for complex enemy movements - but as long as we don't intend to have these in our game, this should be fine. 
Here's the code that is doing this:
  1 float bulletSpeed = 1.0f;
  2 // calculate the current position of our enemy and assume it's the future position
  3 Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime);
  4 Vector2 towerPosition = {tower->x, tower->y};
  5 // how long does the projectile need to hit that position?
  6 float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
  7 for (int i = 0; i < 8; i++) {
  8   // We know: The enemy will move forward until the arrival of the bullet. We will use
  9   // this information to calculate the future position of the enemy at the time the bullet.
 10   futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta);
 11 
 12   // However, our travel time will now be different. Let's recalculate the time
 13   float distance = Vector2Distance(towerPosition, futurePosition);
 14   float eta2 = distance / bulletSpeed;
 15 
 16   // Compare the two times; if they are close, we won't get much better and we can stop
 17   if (fabs(eta - eta2) < 0.01f) {
 18     break;
 19   }
 20   // Otherwise, we take the average of the two times and repeat - the 
 21   // solution should be between the two times - unless the enemy is faster than 
 22   // the bullet, then it will only get worse, but let's assume this'll never happen
 23   // in our game.
 24   eta = (eta2 + eta) * 0.5f;
 25 } 
After logging the results, it seems it tends to stop the loop after 2-4 iterations - which is good enough for our purposes.
We can test now the quality of the predictions by reducing the bullet speed and ramping up the damage the towers do:
  1 #include "td-tut-2-main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 typedef struct GameTime
  7 {
  8   float time;
  9   float deltaTime;
 10 } GameTime;
 11 
 12 GameTime gameTime = {0};
 13 
 14 //# Enemies
 15 
 16 #define ENEMY_MAX_COUNT 400
 17 #define ENEMY_TYPE_NONE 0
 18 #define ENEMY_TYPE_MINION 1
 19 
 20 typedef struct EnemyId
 21 {
 22   uint16_t index;
 23   uint16_t generation;
 24 } EnemyId;
 25 
 26 typedef struct EnemyClassConfig
 27 {
 28   float speed;
 29   float health;
 30 } EnemyClassConfig;
 31 
 32 typedef struct Enemy
 33 {
 34   int16_t currentX, currentY;
 35   int16_t nextX, nextY;
 36   uint16_t generation;
 37   float startMovingTime;
 38   float damage;
 39   uint8_t enemyType;
 40 } Enemy;
 41 
 42 Enemy enemies[ENEMY_MAX_COUNT];
 43 int enemyCount = 0;
 44 
 45 EnemyClassConfig enemyClassConfigs[] = {
 46     [ENEMY_TYPE_MINION] = {.health = 3.0f, .speed = 1.0f},
 47 };
 48 
 49 void EnemyInit()
 50 {
 51   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 52   {
 53     enemies[i] = (Enemy){0};
 54   }
 55   enemyCount = 0;
 56 }
 57 
 58 float EnemyGetCurrentSpeed(Enemy *enemy)
 59 {
 60   return enemyClassConfigs[enemy->enemyType].speed;
 61 }
 62 
 63 float EnemyGetMaxHealth(Enemy *enemy)
 64 {
 65   return enemyClassConfigs[enemy->enemyType].health;
 66 }
 67 
 68 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
 69 {
 70   int16_t castleX = 0;
 71   int16_t castleY = 0;
 72   int16_t dx = castleX - currentX;
 73   int16_t dy = castleY - currentY;
 74   if (dx == 0 && dy == 0)
 75   {
 76     *nextX = currentX;
 77     *nextY = currentY;
 78     return 1;
 79   }
 80   if (abs(dx) > abs(dy))
 81   {
 82     *nextX = currentX + (dx > 0 ? 1 : -1);
 83     *nextY = currentY;
 84   }
 85   else
 86   {
 87     *nextX = currentX;
 88     *nextY = currentY + (dy > 0 ? 1 : -1);
 89   }
 90   return 0;
 91 }
 92 
 93 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT)
 94 {
 95   float speed = deltaT * EnemyGetCurrentSpeed(enemy);
 96   int16_t currentX = enemy->currentX;
 97   int16_t currentY = enemy->currentY;
 98   int16_t nextX = enemy->nextX;
 99   int16_t nextY = enemy->nextY;
100   while (speed > 1.0f)
101   {
102     speed -= 1.0f;
103     currentX = nextX;
104     currentY = nextY;
105     if (EnemyGetNextPosition(currentX, currentY, &nextX, &nextY))
106     {
107       return (Vector2){currentX, currentY};
108     }
109   }
110   return Vector2Lerp((Vector2){currentX, currentY}, (Vector2){nextX, nextY}, speed);
111 }
112 
113 void EnemyDraw()
114 {
115   for (int i = 0; i < enemyCount; i++)
116   {
117     Enemy enemy = enemies[i];
118     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime);
119     
120     switch (enemy.enemyType)
121     {
122     case ENEMY_TYPE_MINION:
123       DrawCube((Vector3){position.x, 0.2f, position.y}, 0.4f, 0.4f, 0.4f, GREEN);
124       break;
125     }
126   }
127 }
128 
129 void EnemyUpdate()
130 {
131   for (int i = 0; i < enemyCount; i++)
132   {
133     Enemy *enemy = &enemies[i];
134     if (enemy->enemyType == ENEMY_TYPE_NONE)
135     {
136       continue;
137     }
138     float speed = EnemyGetCurrentSpeed(enemy);
139     float transition = (gameTime.time - enemy->startMovingTime) * speed;
140     if (transition >= 1.0f)
141     {
142       enemy->startMovingTime = gameTime.time;
143       enemy->currentX = enemy->nextX;
144       enemy->currentY = enemy->nextY;
145       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY))
146       {
147         // enemy reached the castle; remove it
148         enemy->enemyType = ENEMY_TYPE_NONE;
149         continue;
150       }
151     }
152   }
153 }
154 
155 EnemyId EnemyGetId(Enemy *enemy)
156 {
157   return (EnemyId){enemy - enemies, enemy->generation};
158 }
159 
160 Enemy *EnemyTryResolve(EnemyId enemyId)
161 {
162   if (enemyId.index >= ENEMY_MAX_COUNT)
163   {
164     return 0;
165   }
166   Enemy *enemy = &enemies[enemyId.index];
167   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
168   {
169     return 0;
170   }
171   return enemy;
172 }
173 
174 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
175 {
176   Enemy *spawn = 0;
177   for (int i = 0; i < enemyCount; i++)
178   {
179     Enemy *enemy = &enemies[i];
180     if (enemy->enemyType == ENEMY_TYPE_NONE)
181     {
182       spawn = enemy;
183       break;
184     }
185   }
186 
187   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
188   {
189     spawn = &enemies[enemyCount++];
190   }
191 
192   if (spawn)
193   {
194     spawn->currentX = currentX;
195     spawn->currentY = currentY;
196     spawn->nextX = currentX;
197     spawn->nextY = currentY;
198     spawn->enemyType = enemyType;
199     spawn->startMovingTime = gameTime.time;
200     spawn->damage = 0.0f;
201     spawn->generation++;
202   }
203 
204   return spawn;
205 }
206 
207 int EnemyAddDamage(Enemy *enemy, float damage)
208 {
209   enemy->damage += damage;
210   if (enemy->damage >= EnemyGetMaxHealth(enemy))
211   {
212     enemy->enemyType = ENEMY_TYPE_NONE;
213     return 1;
214   }
215 
216   return 0;
217 }
218 
219 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
220 {
221   int16_t castleX = 0;
222   int16_t castleY = 0;
223   Enemy* closest = 0;
224   int16_t closestDistance = 0;
225   float range2 = range * range;
226   for (int i = 0; i < enemyCount; i++)
227   {
228     Enemy* enemy = &enemies[i];
229     if (enemy->enemyType == ENEMY_TYPE_NONE)
230     {
231       continue;
232     }
233     int16_t dx = castleX - enemy->currentX;
234     int16_t dy = castleY - enemy->currentY;
235     int16_t distance = abs(dx) + abs(dy);
236     if (!closest || distance < closestDistance)
237     {
238       float tdx = towerX - enemy->currentX;
239       float tdy = towerY - enemy->currentY;
240       float tdistance2 = tdx * tdx + tdy * tdy;
241       if (tdistance2 <= range2)
242       {
243         closest = enemy;
244         closestDistance = distance;
245       }
246     }
247   }
248   return closest;
249 }
250 
251 //# Projectiles
252 #define PROJECTILE_MAX_COUNT 1200
253 #define PROJECTILE_TYPE_NONE 0
254 #define PROJECTILE_TYPE_BULLET 1
255 
256 typedef struct Projectile
257 {
258   uint8_t projectileType;
259   float shootTime;
260   float arrivalTime;
261   float damage;
262   Vector2 position;
263   Vector2 target;
264   Vector2 directionNormal;
265   EnemyId targetEnemy;
266 } Projectile;
267 
268 Projectile projectiles[PROJECTILE_MAX_COUNT];
269 int projectileCount = 0;
270 
271 void ProjectileInit()
272 {
273   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
274   {
275     projectiles[i] = (Projectile){0};
276   }
277 }
278 
279 void ProjectileDraw()
280 {
281   for (int i = 0; i < projectileCount; i++)
282   {
283     Projectile projectile = projectiles[i];
284     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
285     {
286       continue;
287     }
288     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
289     if (transition >= 1.0f)
290     {
291       continue;
292     }
293     Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition);
294     float x = position.x;
295     float y = position.y;
296     float dx = projectile.directionNormal.x;
297     float dy = projectile.directionNormal.y;
298     for (float d = 1.0f; d > 0.0f; d -= 0.25f)
299     {
300       x -= dx * 0.1f;
301       y -= dy * 0.1f;
302       float size = 0.1f * d;
303       DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED);
304     }
305   }
306 }
307 
308 void ProjectileUpdate()
309 {
310   for (int i = 0; i < projectileCount; i++)
311   {
312     Projectile *projectile = &projectiles[i];
313     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
314     {
315       continue;
316     }
317     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
318     if (transition >= 1.0f)
319     {
320       projectile->projectileType = PROJECTILE_TYPE_NONE;
321       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
322       if (enemy)
323       {
324         EnemyAddDamage(enemy, projectile->damage);
325       }
326       continue;
327     }
328   }
329 }
330 
331 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage)
332 {
333   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
334   {
335     Projectile *projectile = &projectiles[i];
336     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
337     {
338       projectile->projectileType = projectileType;
339       projectile->shootTime = gameTime.time;
340       projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed;
341       projectile->damage = damage;
342       projectile->position = position;
343       projectile->target = target;
344       projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position));
345       projectile->targetEnemy = EnemyGetId(enemy);
346       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
347       return projectile;
348     }
349   }
350   return 0;
351 }
352 
353 //# Towers
354 
355 #define TOWER_MAX_COUNT 400
356 #define TOWER_TYPE_NONE 0
357 #define TOWER_TYPE_BASE 1
358 #define TOWER_TYPE_GUN 2
359 
360 typedef struct Tower
361 {
362   int16_t x, y;
363   uint8_t towerType;
364   float cooldown;
365 } Tower;
366 
367 Tower towers[TOWER_MAX_COUNT];
368 int towerCount = 0;
369 
370 void TowerInit()
371 {
372   for (int i = 0; i < TOWER_MAX_COUNT; i++)
373   {
374     towers[i] = (Tower){0};
375   }
376   towerCount = 0;
377 }
378 
379 Tower *TowerGetAt(int16_t x, int16_t y)
380 {
381   for (int i = 0; i < towerCount; i++)
382   {
383     if (towers[i].x == x && towers[i].y == y)
384     {
385       return &towers[i];
386     }
387   }
388   return 0;
389 }
390 
391 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
392 {
393   if (towerCount >= TOWER_MAX_COUNT)
394   {
395     return 0;
396   }
397 
398   Tower *tower = TowerGetAt(x, y);
399   if (tower)
400   {
401     return 0;
402   }
403 
404   tower = &towers[towerCount++];
405   tower->x = x;
406   tower->y = y;
407   tower->towerType = towerType;
408   return tower;
409 }
410 
411 void TowerDraw()
412 {
413   for (int i = 0; i < towerCount; i++)
414   {
415     Tower tower = towers[i];
416     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
417     switch (tower.towerType)
418     {
419     case TOWER_TYPE_BASE:
420       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
421       break;
422     case TOWER_TYPE_GUN:
423       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
424       break;
425     }
426   }
427 }
428 
429 void TowerGunUpdate(Tower *tower)
430 {
431   if (tower->cooldown <= 0)
432   {
433     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
434     if (enemy)
435     {
436       tower->cooldown = 0.25f;
437       // shoot the enemy; determine future position of the enemy
438       float bulletSpeed = 1.0f;
439       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime);
440       Vector2 towerPosition = {tower->x, tower->y};
441       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
442       for (int i = 0; i < 8; i++) {
443         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta);
444         float distance = Vector2Distance(towerPosition, futurePosition);
445         float eta2 = distance / bulletSpeed;
446         if (fabs(eta - eta2) < 0.01f) {
447           break;
448         }
449         eta = (eta2 + eta) * 0.5f;
450       } 
451       ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, towerPosition, futurePosition, bulletSpeed, 3.0f);
452     }
453   }
454   else
455   {
456     tower->cooldown -= gameTime.deltaTime;
457   }
458 }
459 
460 void TowerUpdate()
461 {
462   for (int i = 0; i < towerCount; i++)
463   {
464     Tower *tower = &towers[i];
465     switch (tower->towerType)
466     {
467     case TOWER_TYPE_GUN:
468       TowerGunUpdate(tower);
469       break;
470     }
471   }
472 }
473 
474 //# Game
475 
476 float nextSpawnTime = 0.0f;
477 
478 void InitGame()
479 {
480   TowerInit();
481   EnemyInit();
482   ProjectileInit();
483 
484   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
485   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
486   TowerTryAdd(TOWER_TYPE_GUN, -2, 0);
487   EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4);
488 }
489 
490 void GameUpdate()
491 {
492   float dt = GetFrameTime();
493   // cap maximum delta time to 0.1 seconds to prevent large time steps
494   if (dt > 0.1f) dt = 0.1f;
495   gameTime.time += dt;
496   gameTime.deltaTime = dt;
497   EnemyUpdate();
498   TowerUpdate();
499   ProjectileUpdate();
500 
501   // spawn a new enemy every second
502   if (gameTime.time >= nextSpawnTime)
503   {
504     nextSpawnTime = gameTime.time + 1.0f;
505     // add a new enemy at the boundary of the map
506     int randValue = GetRandomValue(-5, 5);
507     int randSide = GetRandomValue(0, 3);
508     int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue;
509     int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue;
510     EnemyTryAdd(ENEMY_TYPE_MINION, x, y);
511   }
512 }
513 
514 int main(void)
515 {
516   int screenWidth, screenHeight;
517   GetPreferredSize(&screenWidth, &screenHeight);
518   InitWindow(screenWidth, screenHeight, "Tower defense");
519   SetTargetFPS(30);
520 
521   Camera3D camera = {0};
522   camera.position = (Vector3){0.0f, 10.0f, 5.0f};
523   camera.target = (Vector3){0.0f, 0.0f, 0.0f};
524   camera.up = (Vector3){0.0f, 0.0f, -1.0f};
525   camera.fovy = 45.0f;
526   camera.projection = CAMERA_PERSPECTIVE;
527 
528   InitGame();
529 
530   while (!WindowShouldClose())
531   {
532     if (IsPaused()) {
533       // canvas is not visible in browser - do nothing
534       continue;
535     }
536     BeginDrawing();
537     ClearBackground(DARKBLUE);
538 
539     BeginMode3D(camera);
540     DrawGrid(10, 1.0f);
541     TowerDraw();
542     EnemyDraw();
543     ProjectileDraw();
544     GameUpdate();
545     EndMode3D();
546 
547     DrawText("Tower defense tutorial", 5, 5, 20, WHITE);
548     EndDrawing();
549   }
550 
551   CloseWindow();
552 
553   return 0;
554 }
  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
Now that the bullets take so much time to arrive, we see another problem: Overdamage. The towers can shoot multiple times at the same enemy when they could have started targeting another enemy. We can add a 2nd damage factor to the enemy struct that indicates how much damage the enemy will sustain in the near future and the tower target selection can then take this into account:
  1 #include "td-tut-2-main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 typedef struct GameTime
  7 {
  8   float time;
  9   float deltaTime;
 10 } GameTime;
 11 
 12 GameTime gameTime = {0};
 13 
 14 //# Enemies
 15 
 16 #define ENEMY_MAX_COUNT 400
 17 #define ENEMY_TYPE_NONE 0
 18 #define ENEMY_TYPE_MINION 1
 19 
 20 typedef struct EnemyId
 21 {
 22   uint16_t index;
 23   uint16_t generation;
 24 } EnemyId;
 25 
 26 typedef struct EnemyClassConfig
 27 {
 28   float speed;
 29   float health;
 30 } EnemyClassConfig;
 31 
 32 typedef struct Enemy
 33 {
 34   int16_t currentX, currentY;
 35   int16_t nextX, nextY;
 36   uint16_t generation;
 37   float startMovingTime;
 38   float damage, futureDamage;
 39   uint8_t enemyType;
 40 } Enemy;
 41 
 42 Enemy enemies[ENEMY_MAX_COUNT];
 43 int enemyCount = 0;
 44 
 45 EnemyClassConfig enemyClassConfigs[] = {
 46     [ENEMY_TYPE_MINION] = {.health = 3.0f, .speed = 1.0f},
 47 };
 48 
 49 void EnemyInit()
 50 {
 51   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 52   {
 53     enemies[i] = (Enemy){0};
 54   }
 55   enemyCount = 0;
 56 }
 57 
 58 float EnemyGetCurrentSpeed(Enemy *enemy)
 59 {
 60   return enemyClassConfigs[enemy->enemyType].speed;
 61 }
 62 
 63 float EnemyGetMaxHealth(Enemy *enemy)
 64 {
 65   return enemyClassConfigs[enemy->enemyType].health;
 66 }
 67 
 68 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
 69 {
 70   int16_t castleX = 0;
 71   int16_t castleY = 0;
 72   int16_t dx = castleX - currentX;
 73   int16_t dy = castleY - currentY;
 74   if (dx == 0 && dy == 0)
 75   {
 76     *nextX = currentX;
 77     *nextY = currentY;
 78     return 1;
 79   }
 80   if (abs(dx) > abs(dy))
 81   {
 82     *nextX = currentX + (dx > 0 ? 1 : -1);
 83     *nextY = currentY;
 84   }
 85   else
 86   {
 87     *nextX = currentX;
 88     *nextY = currentY + (dy > 0 ? 1 : -1);
 89   }
 90   return 0;
 91 }
 92 
 93 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT)
 94 {
 95   float speed = deltaT * EnemyGetCurrentSpeed(enemy);
 96   int16_t currentX = enemy->currentX;
 97   int16_t currentY = enemy->currentY;
 98   int16_t nextX = enemy->nextX;
 99   int16_t nextY = enemy->nextY;
100   while (speed > 1.0f)
101   {
102     speed -= 1.0f;
103     currentX = nextX;
104     currentY = nextY;
105     if (EnemyGetNextPosition(currentX, currentY, &nextX, &nextY))
106     {
107       return (Vector2){currentX, currentY};
108     }
109   }
110   return Vector2Lerp((Vector2){currentX, currentY}, (Vector2){nextX, nextY}, speed);
111 }
112 
113 void EnemyDraw()
114 {
115   for (int i = 0; i < enemyCount; i++)
116   {
117     Enemy enemy = enemies[i];
118     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime);
119     
120     switch (enemy.enemyType)
121     {
122     case ENEMY_TYPE_MINION:
123       DrawCube((Vector3){position.x, 0.2f, position.y}, 0.4f, 0.4f, 0.4f, GREEN);
124       break;
125     }
126   }
127 }
128 
129 void EnemyUpdate()
130 {
131   for (int i = 0; i < enemyCount; i++)
132   {
133     Enemy *enemy = &enemies[i];
134     if (enemy->enemyType == ENEMY_TYPE_NONE)
135     {
136       continue;
137     }
138     float speed = EnemyGetCurrentSpeed(enemy);
139     float transition = (gameTime.time - enemy->startMovingTime) * speed;
140     if (transition >= 1.0f)
141     {
142       enemy->startMovingTime = gameTime.time;
143       enemy->currentX = enemy->nextX;
144       enemy->currentY = enemy->nextY;
145       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY))
146       {
147         // enemy reached the castle; remove it
148         enemy->enemyType = ENEMY_TYPE_NONE;
149         continue;
150       }
151     }
152   }
153 }
154 
155 EnemyId EnemyGetId(Enemy *enemy)
156 {
157   return (EnemyId){enemy - enemies, enemy->generation};
158 }
159 
160 Enemy *EnemyTryResolve(EnemyId enemyId)
161 {
162   if (enemyId.index >= ENEMY_MAX_COUNT)
163   {
164     return 0;
165   }
166   Enemy *enemy = &enemies[enemyId.index];
167   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
168   {
169     return 0;
170   }
171   return enemy;
172 }
173 
174 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
175 {
176   Enemy *spawn = 0;
177   for (int i = 0; i < enemyCount; i++)
178   {
179     Enemy *enemy = &enemies[i];
180     if (enemy->enemyType == ENEMY_TYPE_NONE)
181     {
182       spawn = enemy;
183       break;
184     }
185   }
186 
187   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
188   {
189     spawn = &enemies[enemyCount++];
190   }
191 
192   if (spawn)
193   {
194     spawn->currentX = currentX;
195     spawn->currentY = currentY;
196     spawn->nextX = currentX;
197     spawn->nextY = currentY;
198     spawn->enemyType = enemyType;
199     spawn->startMovingTime = gameTime.time;
200     spawn->damage = 0.0f;
201     spawn->futureDamage = 0.0f;
202     spawn->generation++;
203   }
204 
205   return spawn;
206 }
207 
208 int EnemyAddDamage(Enemy *enemy, float damage)
209 {
210   enemy->damage += damage;
211   if (enemy->damage >= EnemyGetMaxHealth(enemy))
212   {
213     enemy->enemyType = ENEMY_TYPE_NONE;
214     return 1;
215   }
216 
217   return 0;
218 }
219 
220 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
221 {
222   int16_t castleX = 0;
223   int16_t castleY = 0;
224   Enemy* closest = 0;
225   int16_t closestDistance = 0;
226   float range2 = range * range;
227   for (int i = 0; i < enemyCount; i++)
228   {
229     Enemy* enemy = &enemies[i];
230     if (enemy->enemyType == ENEMY_TYPE_NONE)
231     {
232       continue;
233     }
234     float maxHealth = EnemyGetMaxHealth(enemy);
235     if (enemy->futureDamage >= maxHealth)
236     {
237       // ignore enemies that will die soon
238       continue;
239     }
240     int16_t dx = castleX - enemy->currentX;
241     int16_t dy = castleY - enemy->currentY;
242     int16_t distance = abs(dx) + abs(dy);
243     if (!closest || distance < closestDistance)
244     {
245       float tdx = towerX - enemy->currentX;
246       float tdy = towerY - enemy->currentY;
247       float tdistance2 = tdx * tdx + tdy * tdy;
248       if (tdistance2 <= range2)
249       {
250         closest = enemy;
251         closestDistance = distance;
252       }
253     }
254   }
255   return closest;
256 }
257 
258 //# Projectiles
259 #define PROJECTILE_MAX_COUNT 1200
260 #define PROJECTILE_TYPE_NONE 0
261 #define PROJECTILE_TYPE_BULLET 1
262 
263 typedef struct Projectile
264 {
265   uint8_t projectileType;
266   float shootTime;
267   float arrivalTime;
268   float damage;
269   Vector2 position;
270   Vector2 target;
271   Vector2 directionNormal;
272   EnemyId targetEnemy;
273 } Projectile;
274 
275 Projectile projectiles[PROJECTILE_MAX_COUNT];
276 int projectileCount = 0;
277 
278 void ProjectileInit()
279 {
280   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
281   {
282     projectiles[i] = (Projectile){0};
283   }
284 }
285 
286 void ProjectileDraw()
287 {
288   for (int i = 0; i < projectileCount; i++)
289   {
290     Projectile projectile = projectiles[i];
291     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
292     {
293       continue;
294     }
295     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
296     if (transition >= 1.0f)
297     {
298       continue;
299     }
300     Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition);
301     float x = position.x;
302     float y = position.y;
303     float dx = projectile.directionNormal.x;
304     float dy = projectile.directionNormal.y;
305     for (float d = 1.0f; d > 0.0f; d -= 0.25f)
306     {
307       x -= dx * 0.1f;
308       y -= dy * 0.1f;
309       float size = 0.1f * d;
310       DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED);
311     }
312   }
313 }
314 
315 void ProjectileUpdate()
316 {
317   for (int i = 0; i < projectileCount; i++)
318   {
319     Projectile *projectile = &projectiles[i];
320     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
321     {
322       continue;
323     }
324     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
325     if (transition >= 1.0f)
326     {
327       projectile->projectileType = PROJECTILE_TYPE_NONE;
328       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
329       if (enemy)
330       {
331         EnemyAddDamage(enemy, projectile->damage);
332       }
333       continue;
334     }
335   }
336 }
337 
338 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage)
339 {
340   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
341   {
342     Projectile *projectile = &projectiles[i];
343     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
344     {
345       projectile->projectileType = projectileType;
346       projectile->shootTime = gameTime.time;
347       projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed;
348       projectile->damage = damage;
349       projectile->position = position;
350       projectile->target = target;
351       projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position));
352       projectile->targetEnemy = EnemyGetId(enemy);
353       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
354       return projectile;
355     }
356   }
357   return 0;
358 }
359 
360 //# Towers
361 
362 #define TOWER_MAX_COUNT 400
363 #define TOWER_TYPE_NONE 0
364 #define TOWER_TYPE_BASE 1
365 #define TOWER_TYPE_GUN 2
366 
367 typedef struct Tower
368 {
369   int16_t x, y;
370   uint8_t towerType;
371   float cooldown;
372 } Tower;
373 
374 Tower towers[TOWER_MAX_COUNT];
375 int towerCount = 0;
376 
377 void TowerInit()
378 {
379   for (int i = 0; i < TOWER_MAX_COUNT; i++)
380   {
381     towers[i] = (Tower){0};
382   }
383   towerCount = 0;
384 }
385 
386 Tower *TowerGetAt(int16_t x, int16_t y)
387 {
388   for (int i = 0; i < towerCount; i++)
389   {
390     if (towers[i].x == x && towers[i].y == y)
391     {
392       return &towers[i];
393     }
394   }
395   return 0;
396 }
397 
398 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
399 {
400   if (towerCount >= TOWER_MAX_COUNT)
401   {
402     return 0;
403   }
404 
405   Tower *tower = TowerGetAt(x, y);
406   if (tower)
407   {
408     return 0;
409   }
410 
411   tower = &towers[towerCount++];
412   tower->x = x;
413   tower->y = y;
414   tower->towerType = towerType;
415   return tower;
416 }
417 
418 void TowerDraw()
419 {
420   for (int i = 0; i < towerCount; i++)
421   {
422     Tower tower = towers[i];
423     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
424     switch (tower.towerType)
425     {
426     case TOWER_TYPE_BASE:
427       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
428       break;
429     case TOWER_TYPE_GUN:
430       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
431       break;
432     }
433   }
434 }
435 
436 void TowerGunUpdate(Tower *tower)
437 {
438   if (tower->cooldown <= 0)
439   {
440     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
441     if (enemy)
442     {
443       tower->cooldown = 0.25f;
444       // shoot the enemy; determine future position of the enemy
445       float bulletSpeed = 1.0f;
446       float bulletDamage = 3.0f;
447       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime);
448       Vector2 towerPosition = {tower->x, tower->y};
449       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
450       for (int i = 0; i < 8; i++) {
451         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta);
452         float distance = Vector2Distance(towerPosition, futurePosition);
453         float eta2 = distance / bulletSpeed;
454         if (fabs(eta - eta2) < 0.01f) {
455           break;
456         }
457         eta = (eta2 + eta) * 0.5f;
458       }
459       ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, towerPosition, futurePosition, 
460         bulletSpeed, bulletDamage);
461       enemy->futureDamage += bulletDamage;
462     }
463   }
464   else
465   {
466     tower->cooldown -= gameTime.deltaTime;
467   }
468 }
469 
470 void TowerUpdate()
471 {
472   for (int i = 0; i < towerCount; i++)
473   {
474     Tower *tower = &towers[i];
475     switch (tower->towerType)
476     {
477     case TOWER_TYPE_GUN:
478       TowerGunUpdate(tower);
479       break;
480     }
481   }
482 }
483 
484 //# Game
485 
486 float nextSpawnTime = 0.0f;
487 
488 void InitGame()
489 {
490   TowerInit();
491   EnemyInit();
492   ProjectileInit();
493 
494   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
495   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
496   TowerTryAdd(TOWER_TYPE_GUN, -2, 0);
497   EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4);
498 }
499 
500 void GameUpdate()
501 {
502   float dt = GetFrameTime();
503   // cap maximum delta time to 0.1 seconds to prevent large time steps
504   if (dt > 0.1f) dt = 0.1f;
505   gameTime.time += dt;
506   gameTime.deltaTime = dt;
507   EnemyUpdate();
508   TowerUpdate();
509   ProjectileUpdate();
510 
511   // spawn a new enemy every second
512   if (gameTime.time >= nextSpawnTime)
513   {
514     nextSpawnTime = gameTime.time + 1.0f;
515     // add a new enemy at the boundary of the map
516     int randValue = GetRandomValue(-5, 5);
517     int randSide = GetRandomValue(0, 3);
518     int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue;
519     int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue;
520     EnemyTryAdd(ENEMY_TYPE_MINION, x, y);
521   }
522 }
523 
524 int main(void)
525 {
526   int screenWidth, screenHeight;
527   GetPreferredSize(&screenWidth, &screenHeight);
528   InitWindow(screenWidth, screenHeight, "Tower defense");
529   SetTargetFPS(30);
530 
531   Camera3D camera = {0};
532   camera.position = (Vector3){0.0f, 10.0f, 5.0f};
533   camera.target = (Vector3){0.0f, 0.0f, 0.0f};
534   camera.up = (Vector3){0.0f, 0.0f, -1.0f};
535   camera.fovy = 45.0f;
536   camera.projection = CAMERA_PERSPECTIVE;
537 
538   InitGame();
539 
540   while (!WindowShouldClose())
541   {
542     if (IsPaused()) {
543       // canvas is not visible in browser - do nothing
544       continue;
545     }
546     BeginDrawing();
547     ClearBackground(DARKBLUE);
548 
549     BeginMode3D(camera);
550     DrawGrid(10, 1.0f);
551     TowerDraw();
552     EnemyDraw();
553     ProjectileDraw();
554     GameUpdate();
555     EndMode3D();
556 
557     DrawText("Tower defense tutorial", 5, 5, 20, WHITE);
558     EndDrawing();
559   }
560 
561   CloseWindow();
562 
563   return 0;
564 }
  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
We can see now much clearer that the bullet prediction is working well: The impact positions seem to match the enemy positions quite well.
Path movement
The next thing we want to improve is enemy movement. Currently, the enemies have some kind of waypoints between they linearly interpolate.
We want to modify the movement to handle following things:
- Smooth movement around corners
 - Acceleration and deceleration
 - Collision between enemies
 
If this sounds like a physics simulation, then this is correct; it is simulating a simple physics system where the enemies are moving spheres.
While we could try a simpler interpolation approach, let's look into the approach to use physics simulation math for this: By using velocity and acceleration, we want to update the position while we use the velocity to guide the direction of the enemy. This is going to be a bit more complex and it usually involves lots of try and error - and I want to reflect this in the following steps to show, how a solution can be found by iterating over the problem, by showing what type of problems can occur and how they can be solved.
The heart of the movement is handled function called "EnemyGetPosition". It simulates the steps to produce a new position based on the current position and the time passed.
On a high level, what we want to do is this:
- Change the velocity so we will move towards the next waypoint
 - Limit the velocity to the maximum speed of the unit
 - Add the velocity to the current position
 - Check if we reached the next waypoint and if we reached it, move on to the next one
 
The first iteration of the code looks now like this:
  1 #include "td-tut-2-main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 typedef struct GameTime
  7 {
  8   float time;
  9   float deltaTime;
 10 } GameTime;
 11 
 12 GameTime gameTime = {0};
 13 
 14 //# Enemies
 15 
 16 #define ENEMY_MAX_COUNT 400
 17 #define ENEMY_TYPE_NONE 0
 18 #define ENEMY_TYPE_MINION 1
 19 
 20 typedef struct EnemyId
 21 {
 22   uint16_t index;
 23   uint16_t generation;
 24 } EnemyId;
 25 
 26 typedef struct EnemyClassConfig
 27 {
 28   float speed;
 29   float health;
 30   float radius;
 31   float maxAcceleration;
 32 } EnemyClassConfig;
 33 
 34 typedef struct Enemy
 35 {
 36   int16_t currentX, currentY;
 37   int16_t nextX, nextY;
 38   Vector2 simPosition;
 39   Vector2 simVelocity;
 40   uint16_t generation;
 41   float startMovingTime;
 42   float damage, futureDamage;
 43   uint8_t enemyType;
 44 } Enemy;
 45 
 46 Enemy enemies[ENEMY_MAX_COUNT];
 47 int enemyCount = 0;
 48 
 49 EnemyClassConfig enemyClassConfigs[] = {
 50     [ENEMY_TYPE_MINION] = {.health = 3.0f, .speed = 1.0f, .radius = 0.25f, .maxAcceleration = 1.0f},
 51 };
 52 
 53 void EnemyInit()
 54 {
 55   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 56   {
 57     enemies[i] = (Enemy){0};
 58   }
 59   enemyCount = 0;
 60 }
 61 
 62 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
 63 {
 64   return enemyClassConfigs[enemy->enemyType].speed;
 65 }
 66 
 67 float EnemyGetMaxHealth(Enemy *enemy)
 68 {
 69   return enemyClassConfigs[enemy->enemyType].health;
 70 }
 71 
 72 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
 73 {
 74   int16_t castleX = 0;
 75   int16_t castleY = 0;
 76   int16_t dx = castleX - currentX;
 77   int16_t dy = castleY - currentY;
 78   if (dx == 0 && dy == 0)
 79   {
 80     *nextX = currentX;
 81     *nextY = currentY;
 82     return 1;
 83   }
 84   if (abs(dx) > abs(dy))
 85   {
 86     *nextX = currentX + (dx > 0 ? 1 : -1);
 87     *nextY = currentY;
 88   }
 89   else
 90   {
 91     *nextX = currentX;
 92     *nextY = currentY + (dy > 0 ? 1 : -1);
 93   }
 94   return 0;
 95 }
 96 
 97 // this function predicts the movement of the unit for the next deltaT seconds
 98 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
 99 {
100   const float pointReachedDistance = 0.25f;
101   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
102   const float maxSimStepTime = 0.015625f;
103   
104   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
105   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
106   int16_t nextX = enemy->nextX;
107   int16_t nextY = enemy->nextY;
108   Vector2 position = enemy->simPosition;
109   int passedCount = 0;
110   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
111   {
112     float stepTime = fminf(deltaT - t, maxSimStepTime);
113     Vector2 target = (Vector2){nextX, nextY};
114     // draw the target position for debugging
115     DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
116     if (Vector2DistanceSqr(target, position) <= pointReachedDistance2)
117     {
118       // we reached the target position, let's move to the next waypoint
119       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
120       target = (Vector2){nextX, nextY};
121       // track how many waypoints we passed
122       passedCount++;
123     }
124     
125     // acceleration towards the target
126     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, position));
127     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
128     *velocity = Vector2Add(*velocity, acceleration);
129 
130     // limit the speed to the maximum speed
131     float speed = Vector2Length(*velocity);
132     if (speed > maxSpeed)
133     {
134       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
135     }
136 
137     // move the enemy
138     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
139   }
140 
141   if (waypointPassedCount)
142   {
143     (*waypointPassedCount) = passedCount;
144   }
145 
146   return position;
147 }
148 
149 void EnemyDraw()
150 {
151   for (int i = 0; i < enemyCount; i++)
152   {
153     Enemy enemy = enemies[i];
154     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
155     
156     switch (enemy.enemyType)
157     {
158     case ENEMY_TYPE_MINION:
159       DrawCubeWires((Vector3){position.x, 0.2f, position.y}, 0.4f, 0.4f, 0.4f, GREEN);
160       break;
161     }
162   }
163 }
164 
165 void EnemyUpdate()
166 {
167   for (int i = 0; i < enemyCount; i++)
168   {
169     Enemy *enemy = &enemies[i];
170     if (enemy->enemyType == ENEMY_TYPE_NONE)
171     {
172       continue;
173     }
174 
175     int waypointPassedCount = 0;
176     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
177     enemy->startMovingTime = gameTime.time;
178     if (waypointPassedCount > 0)
179     {
180       enemy->currentX = enemy->nextX;
181       enemy->currentY = enemy->nextY;
182       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY))
183       {
184         // enemy reached the castle; remove it
185         enemy->enemyType = ENEMY_TYPE_NONE;
186         continue;
187       }
188     }
189 
190   }
191 }
192 
193 EnemyId EnemyGetId(Enemy *enemy)
194 {
195   return (EnemyId){enemy - enemies, enemy->generation};
196 }
197 
198 Enemy *EnemyTryResolve(EnemyId enemyId)
199 {
200   if (enemyId.index >= ENEMY_MAX_COUNT)
201   {
202     return 0;
203   }
204   Enemy *enemy = &enemies[enemyId.index];
205   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
206   {
207     return 0;
208   }
209   return enemy;
210 }
211 
212 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
213 {
214   Enemy *spawn = 0;
215   for (int i = 0; i < enemyCount; i++)
216   {
217     Enemy *enemy = &enemies[i];
218     if (enemy->enemyType == ENEMY_TYPE_NONE)
219     {
220       spawn = enemy;
221       break;
222     }
223   }
224 
225   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
226   {
227     spawn = &enemies[enemyCount++];
228   }
229 
230   if (spawn)
231   {
232     spawn->currentX = currentX;
233     spawn->currentY = currentY;
234     spawn->nextX = currentX;
235     spawn->nextY = currentY;
236     spawn->simPosition = (Vector2){currentX, currentY};
237     spawn->simVelocity = (Vector2){0, 0};
238     spawn->enemyType = enemyType;
239     spawn->startMovingTime = gameTime.time;
240     spawn->damage = 0.0f;
241     spawn->futureDamage = 0.0f;
242     spawn->generation++;
243   }
244 
245   return spawn;
246 }
247 
248 int EnemyAddDamage(Enemy *enemy, float damage)
249 {
250   enemy->damage += damage;
251   if (enemy->damage >= EnemyGetMaxHealth(enemy))
252   {
253     enemy->enemyType = ENEMY_TYPE_NONE;
254     return 1;
255   }
256 
257   return 0;
258 }
259 
260 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
261 {
262   int16_t castleX = 0;
263   int16_t castleY = 0;
264   Enemy* closest = 0;
265   int16_t closestDistance = 0;
266   float range2 = range * range;
267   for (int i = 0; i < enemyCount; i++)
268   {
269     Enemy* enemy = &enemies[i];
270     if (enemy->enemyType == ENEMY_TYPE_NONE)
271     {
272       continue;
273     }
274     float maxHealth = EnemyGetMaxHealth(enemy);
275     if (enemy->futureDamage >= maxHealth)
276     {
277       // ignore enemies that will die soon
278       continue;
279     }
280     int16_t dx = castleX - enemy->currentX;
281     int16_t dy = castleY - enemy->currentY;
282     int16_t distance = abs(dx) + abs(dy);
283     if (!closest || distance < closestDistance)
284     {
285       float tdx = towerX - enemy->currentX;
286       float tdy = towerY - enemy->currentY;
287       float tdistance2 = tdx * tdx + tdy * tdy;
288       if (tdistance2 <= range2)
289       {
290         closest = enemy;
291         closestDistance = distance;
292       }
293     }
294   }
295   return closest;
296 }
297 
298 int EnemyCount()
299 {
300   int count = 0;
301   for (int i = 0; i < enemyCount; i++)
302   {
303     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
304     {
305       count++;
306     }
307   }
308   return count;
309 }
310 
311 //# Projectiles
312 #define PROJECTILE_MAX_COUNT 1200
313 #define PROJECTILE_TYPE_NONE 0
314 #define PROJECTILE_TYPE_BULLET 1
315 
316 typedef struct Projectile
317 {
318   uint8_t projectileType;
319   float shootTime;
320   float arrivalTime;
321   float damage;
322   Vector2 position;
323   Vector2 target;
324   Vector2 directionNormal;
325   EnemyId targetEnemy;
326 } Projectile;
327 
328 Projectile projectiles[PROJECTILE_MAX_COUNT];
329 int projectileCount = 0;
330 
331 void ProjectileInit()
332 {
333   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
334   {
335     projectiles[i] = (Projectile){0};
336   }
337 }
338 
339 void ProjectileDraw()
340 {
341   for (int i = 0; i < projectileCount; i++)
342   {
343     Projectile projectile = projectiles[i];
344     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
345     {
346       continue;
347     }
348     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
349     if (transition >= 1.0f)
350     {
351       continue;
352     }
353     Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition);
354     float x = position.x;
355     float y = position.y;
356     float dx = projectile.directionNormal.x;
357     float dy = projectile.directionNormal.y;
358     for (float d = 1.0f; d > 0.0f; d -= 0.25f)
359     {
360       x -= dx * 0.1f;
361       y -= dy * 0.1f;
362       float size = 0.1f * d;
363       DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED);
364     }
365   }
366 }
367 
368 void ProjectileUpdate()
369 {
370   for (int i = 0; i < projectileCount; i++)
371   {
372     Projectile *projectile = &projectiles[i];
373     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
374     {
375       continue;
376     }
377     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
378     if (transition >= 1.0f)
379     {
380       projectile->projectileType = PROJECTILE_TYPE_NONE;
381       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
382       if (enemy)
383       {
384         EnemyAddDamage(enemy, projectile->damage);
385       }
386       continue;
387     }
388   }
389 }
390 
391 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage)
392 {
393   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
394   {
395     Projectile *projectile = &projectiles[i];
396     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
397     {
398       projectile->projectileType = projectileType;
399       projectile->shootTime = gameTime.time;
400       projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed;
401       projectile->damage = damage;
402       projectile->position = position;
403       projectile->target = target;
404       projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position));
405       projectile->targetEnemy = EnemyGetId(enemy);
406       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
407       return projectile;
408     }
409   }
410   return 0;
411 }
412 
413 //# Towers
414 
415 #define TOWER_MAX_COUNT 400
416 #define TOWER_TYPE_NONE 0
417 #define TOWER_TYPE_BASE 1
418 #define TOWER_TYPE_GUN 2
419 
420 typedef struct Tower
421 {
422   int16_t x, y;
423   uint8_t towerType;
424   float cooldown;
425 } Tower;
426 
427 Tower towers[TOWER_MAX_COUNT];
428 int towerCount = 0;
429 
430 void TowerInit()
431 {
432   for (int i = 0; i < TOWER_MAX_COUNT; i++)
433   {
434     towers[i] = (Tower){0};
435   }
436   towerCount = 0;
437 }
438 
439 Tower *TowerGetAt(int16_t x, int16_t y)
440 {
441   for (int i = 0; i < towerCount; i++)
442   {
443     if (towers[i].x == x && towers[i].y == y)
444     {
445       return &towers[i];
446     }
447   }
448   return 0;
449 }
450 
451 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
452 {
453   if (towerCount >= TOWER_MAX_COUNT)
454   {
455     return 0;
456   }
457 
458   Tower *tower = TowerGetAt(x, y);
459   if (tower)
460   {
461     return 0;
462   }
463 
464   tower = &towers[towerCount++];
465   tower->x = x;
466   tower->y = y;
467   tower->towerType = towerType;
468   return tower;
469 }
470 
471 void TowerDraw()
472 {
473   for (int i = 0; i < towerCount; i++)
474   {
475     Tower tower = towers[i];
476     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
477     switch (tower.towerType)
478     {
479     case TOWER_TYPE_BASE:
480       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
481       break;
482     case TOWER_TYPE_GUN:
483       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
484       break;
485     }
486   }
487 }
488 
489 void TowerGunUpdate(Tower *tower)
490 {
491   if (tower->cooldown <= 0)
492   {
493     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
494     if (enemy)
495     {
496       tower->cooldown = 0.25f;
497       // shoot the enemy; determine future position of the enemy
498       float bulletSpeed = 1.0f;
499       float bulletDamage = 3.0f;
500       Vector2 velocity = enemy->simVelocity;
501       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
502       Vector2 towerPosition = {tower->x, tower->y};
503       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
504       for (int i = 0; i < 8; i++) {
505         velocity = enemy->simVelocity;
506         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
507         float distance = Vector2Distance(towerPosition, futurePosition);
508         float eta2 = distance / bulletSpeed;
509         if (fabs(eta - eta2) < 0.01f) {
510           break;
511         }
512         eta = (eta2 + eta) * 0.5f;
513       }
514       ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, towerPosition, futurePosition, 
515         bulletSpeed, bulletDamage);
516       enemy->futureDamage += bulletDamage;
517     }
518   }
519   else
520   {
521     tower->cooldown -= gameTime.deltaTime;
522   }
523 }
524 
525 void TowerUpdate()
526 {
527   for (int i = 0; i < towerCount; i++)
528   {
529     Tower *tower = &towers[i];
530     switch (tower->towerType)
531     {
532     case TOWER_TYPE_GUN:
533       TowerGunUpdate(tower);
534       break;
535     }
536   }
537 }
538 
539 //# Game
540 
541 float nextSpawnTime = 0.0f;
542 
543 void InitGame()
544 {
545   TowerInit();
546   EnemyInit();
547   ProjectileInit();
548 
549   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
550   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
551   TowerTryAdd(TOWER_TYPE_GUN, -2, 0);
552   EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4);
553 }
554 
555 void GameUpdate()
556 {
557   float dt = GetFrameTime();
558   // cap maximum delta time to 0.1 seconds to prevent large time steps
559   if (dt > 0.1f) dt = 0.1f;
560   gameTime.time += dt;
561   gameTime.deltaTime = dt;
562   EnemyUpdate();
563   TowerUpdate();
564   ProjectileUpdate();
565 
566   // spawn a new enemy every second
567   if (gameTime.time >= nextSpawnTime && EnemyCount() < 1)
568   {
569     nextSpawnTime = gameTime.time + 1.0f;
570     // add a new enemy at the boundary of the map
571     int randValue = GetRandomValue(-5, 5);
572     int randSide = GetRandomValue(0, 3);
573     int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue;
574     int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue;
575     EnemyTryAdd(ENEMY_TYPE_MINION, x, y);
576   }
577 }
578 
579 int main(void)
580 {
581   int screenWidth, screenHeight;
582   GetPreferredSize(&screenWidth, &screenHeight);
583   InitWindow(screenWidth, screenHeight, "Tower defense");
584   SetTargetFPS(30);
585 
586   Camera3D camera = {0};
587   camera.position = (Vector3){0.0f, 10.0f, 5.0f};
588   camera.target = (Vector3){0.0f, 0.0f, 0.0f};
589   camera.up = (Vector3){0.0f, 0.0f, -1.0f};
590   camera.fovy = 45.0f;
591   camera.projection = CAMERA_PERSPECTIVE;
592 
593   InitGame();
594 
595   while (!WindowShouldClose())
596   {
597     if (IsPaused()) {
598       // canvas is not visible in browser - do nothing
599       continue;
600     }
601     BeginDrawing();
602     ClearBackground(DARKBLUE);
603 
604     BeginMode3D(camera);
605     DrawGrid(10, 1.0f);
606     TowerDraw();
607     EnemyDraw();
608     ProjectileDraw();
609     GameUpdate();
610     EndMode3D();
611 
612     DrawText("Tower defense tutorial", 5, 5, 20, WHITE);
613     EndDrawing();
614   }
615 
616   CloseWindow();
617 
618   return 0;
619 }
  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
The behavior we're observing is not quite working: The enemy circles the next waypoint and is never reaching it. This is because while the acceleration is pointing towards the waypoint, our current forward velocity is keeping us on a tangent. We need to take this into account when we calculate the acceleration.
There are all kinds of ways how this can be solved, but the most simple way I have found is to extrapolate the position based on the current velocity and use that position for calculating the new acceleration vector. If our enemy moves on a tangent, this will automatically introduce a deceleration that counters the forward velocity:
When we'll take a future point for calculating the acceleration, how far should we project the future point? In this case, I found out by experimentation that taking the current speed and using this as the time factor for the future point works quite well. My thinking is that the greater the speed in comparison to the maximum acceleration is, the further we'll have to project the future point - since we'll need more time to counteract the current velocity.
Let's see how this'll look:
  1 #include "td-tut-2-main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 typedef struct GameTime
  7 {
  8   float time;
  9   float deltaTime;
 10 } GameTime;
 11 
 12 GameTime gameTime = {0};
 13 
 14 //# Enemies
 15 
 16 #define ENEMY_MAX_COUNT 400
 17 #define ENEMY_TYPE_NONE 0
 18 #define ENEMY_TYPE_MINION 1
 19 
 20 typedef struct EnemyId
 21 {
 22   uint16_t index;
 23   uint16_t generation;
 24 } EnemyId;
 25 
 26 typedef struct EnemyClassConfig
 27 {
 28   float speed;
 29   float health;
 30   float radius;
 31   float maxAcceleration;
 32 } EnemyClassConfig;
 33 
 34 typedef struct Enemy
 35 {
 36   int16_t currentX, currentY;
 37   int16_t nextX, nextY;
 38   Vector2 simPosition;
 39   Vector2 simVelocity;
 40   uint16_t generation;
 41   float startMovingTime;
 42   float damage, futureDamage;
 43   uint8_t enemyType;
 44 } Enemy;
 45 
 46 Enemy enemies[ENEMY_MAX_COUNT];
 47 int enemyCount = 0;
 48 
 49 EnemyClassConfig enemyClassConfigs[] = {
 50     [ENEMY_TYPE_MINION] = {.health = 3.0f, .speed = 1.0f, .radius = 0.25f, .maxAcceleration = 1.0f},
 51 };
 52 
 53 void EnemyInit()
 54 {
 55   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 56   {
 57     enemies[i] = (Enemy){0};
 58   }
 59   enemyCount = 0;
 60 }
 61 
 62 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
 63 {
 64   return enemyClassConfigs[enemy->enemyType].speed;
 65 }
 66 
 67 float EnemyGetMaxHealth(Enemy *enemy)
 68 {
 69   return enemyClassConfigs[enemy->enemyType].health;
 70 }
 71 
 72 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
 73 {
 74   int16_t castleX = 0;
 75   int16_t castleY = 0;
 76   int16_t dx = castleX - currentX;
 77   int16_t dy = castleY - currentY;
 78   if (dx == 0 && dy == 0)
 79   {
 80     *nextX = currentX;
 81     *nextY = currentY;
 82     return 1;
 83   }
 84   if (abs(dx) > abs(dy))
 85   {
 86     *nextX = currentX + (dx > 0 ? 1 : -1);
 87     *nextY = currentY;
 88   }
 89   else
 90   {
 91     *nextX = currentX;
 92     *nextY = currentY + (dy > 0 ? 1 : -1);
 93   }
 94   return 0;
 95 }
 96 
 97 
 98 // this function predicts the movement of the unit for the next deltaT seconds
 99 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
100 {
101   const float pointReachedDistance = 0.25f;
102   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
103   const float maxSimStepTime = 0.015625f;
104   
105   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
106   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
107   int16_t nextX = enemy->nextX;
108   int16_t nextY = enemy->nextY;
109   Vector2 position = enemy->simPosition;
110   int passedCount = 0;
111   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
112   {
113     float stepTime = fminf(deltaT - t, maxSimStepTime);
114     Vector2 target = (Vector2){nextX, nextY};
115     float speed = Vector2Length(*velocity);
116     // draw the target position for debugging
117     DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
118     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
119     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
120     {
121       // we reached the target position, let's move to the next waypoint
122       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
123       target = (Vector2){nextX, nextY};
124       // track how many waypoints we passed
125       passedCount++;
126     }
127     
128     // acceleration towards the target
129     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
130     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
131     *velocity = Vector2Add(*velocity, acceleration);
132 
133     // limit the speed to the maximum speed
134     if (speed > maxSpeed)
135     {
136       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
137     }
138 
139     // move the enemy
140     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
141   }
142 
143   if (waypointPassedCount)
144   {
145     (*waypointPassedCount) = passedCount;
146   }
147 
148   return position;
149 }
150 
151 void EnemyDraw()
152 {
153   for (int i = 0; i < enemyCount; i++)
154   {
155     Enemy enemy = enemies[i];
156     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
157     
158     switch (enemy.enemyType)
159     {
160     case ENEMY_TYPE_MINION:
161       DrawCubeWires((Vector3){position.x, 0.2f, position.y}, 0.4f, 0.4f, 0.4f, GREEN);
162       break;
163     }
164   }
165 }
166 
167 void EnemyUpdate()
168 {
169   for (int i = 0; i < enemyCount; i++)
170   {
171     Enemy *enemy = &enemies[i];
172     if (enemy->enemyType == ENEMY_TYPE_NONE)
173     {
174       continue;
175     }
176 
177     int waypointPassedCount = 0;
178     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
179     enemy->startMovingTime = gameTime.time;
180     if (waypointPassedCount > 0)
181     {
182       enemy->currentX = enemy->nextX;
183       enemy->currentY = enemy->nextY;
184       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY))
185       {
186         // enemy reached the castle; remove it
187         enemy->enemyType = ENEMY_TYPE_NONE;
188         continue;
189       }
190     }
191 
192   }
193 }
194 
195 EnemyId EnemyGetId(Enemy *enemy)
196 {
197   return (EnemyId){enemy - enemies, enemy->generation};
198 }
199 
200 Enemy *EnemyTryResolve(EnemyId enemyId)
201 {
202   if (enemyId.index >= ENEMY_MAX_COUNT)
203   {
204     return 0;
205   }
206   Enemy *enemy = &enemies[enemyId.index];
207   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
208   {
209     return 0;
210   }
211   return enemy;
212 }
213 
214 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
215 {
216   Enemy *spawn = 0;
217   for (int i = 0; i < enemyCount; i++)
218   {
219     Enemy *enemy = &enemies[i];
220     if (enemy->enemyType == ENEMY_TYPE_NONE)
221     {
222       spawn = enemy;
223       break;
224     }
225   }
226 
227   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
228   {
229     spawn = &enemies[enemyCount++];
230   }
231 
232   if (spawn)
233   {
234     spawn->currentX = currentX;
235     spawn->currentY = currentY;
236     spawn->nextX = currentX;
237     spawn->nextY = currentY;
238     spawn->simPosition = (Vector2){currentX, currentY};
239     spawn->simVelocity = (Vector2){0, 0};
240     spawn->enemyType = enemyType;
241     spawn->startMovingTime = gameTime.time;
242     spawn->damage = 0.0f;
243     spawn->futureDamage = 0.0f;
244     spawn->generation++;
245   }
246 
247   return spawn;
248 }
249 
250 int EnemyAddDamage(Enemy *enemy, float damage)
251 {
252   enemy->damage += damage;
253   if (enemy->damage >= EnemyGetMaxHealth(enemy))
254   {
255     enemy->enemyType = ENEMY_TYPE_NONE;
256     return 1;
257   }
258 
259   return 0;
260 }
261 
262 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
263 {
264   int16_t castleX = 0;
265   int16_t castleY = 0;
266   Enemy* closest = 0;
267   int16_t closestDistance = 0;
268   float range2 = range * range;
269   for (int i = 0; i < enemyCount; i++)
270   {
271     Enemy* enemy = &enemies[i];
272     if (enemy->enemyType == ENEMY_TYPE_NONE)
273     {
274       continue;
275     }
276     float maxHealth = EnemyGetMaxHealth(enemy);
277     if (enemy->futureDamage >= maxHealth)
278     {
279       // ignore enemies that will die soon
280       continue;
281     }
282     int16_t dx = castleX - enemy->currentX;
283     int16_t dy = castleY - enemy->currentY;
284     int16_t distance = abs(dx) + abs(dy);
285     if (!closest || distance < closestDistance)
286     {
287       float tdx = towerX - enemy->currentX;
288       float tdy = towerY - enemy->currentY;
289       float tdistance2 = tdx * tdx + tdy * tdy;
290       if (tdistance2 <= range2)
291       {
292         closest = enemy;
293         closestDistance = distance;
294       }
295     }
296   }
297   return closest;
298 }
299 
300 int EnemyCount()
301 {
302   int count = 0;
303   for (int i = 0; i < enemyCount; i++)
304   {
305     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
306     {
307       count++;
308     }
309   }
310   return count;
311 }
312 
313 //# Projectiles
314 #define PROJECTILE_MAX_COUNT 1200
315 #define PROJECTILE_TYPE_NONE 0
316 #define PROJECTILE_TYPE_BULLET 1
317 
318 typedef struct Projectile
319 {
320   uint8_t projectileType;
321   float shootTime;
322   float arrivalTime;
323   float damage;
324   Vector2 position;
325   Vector2 target;
326   Vector2 directionNormal;
327   EnemyId targetEnemy;
328 } Projectile;
329 
330 Projectile projectiles[PROJECTILE_MAX_COUNT];
331 int projectileCount = 0;
332 
333 void ProjectileInit()
334 {
335   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
336   {
337     projectiles[i] = (Projectile){0};
338   }
339 }
340 
341 void ProjectileDraw()
342 {
343   for (int i = 0; i < projectileCount; i++)
344   {
345     Projectile projectile = projectiles[i];
346     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
347     {
348       continue;
349     }
350     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
351     if (transition >= 1.0f)
352     {
353       continue;
354     }
355     Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition);
356     float x = position.x;
357     float y = position.y;
358     float dx = projectile.directionNormal.x;
359     float dy = projectile.directionNormal.y;
360     for (float d = 1.0f; d > 0.0f; d -= 0.25f)
361     {
362       x -= dx * 0.1f;
363       y -= dy * 0.1f;
364       float size = 0.1f * d;
365       DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED);
366     }
367   }
368 }
369 
370 void ProjectileUpdate()
371 {
372   for (int i = 0; i < projectileCount; i++)
373   {
374     Projectile *projectile = &projectiles[i];
375     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
376     {
377       continue;
378     }
379     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
380     if (transition >= 1.0f)
381     {
382       projectile->projectileType = PROJECTILE_TYPE_NONE;
383       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
384       if (enemy)
385       {
386         EnemyAddDamage(enemy, projectile->damage);
387       }
388       continue;
389     }
390   }
391 }
392 
393 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage)
394 {
395   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
396   {
397     Projectile *projectile = &projectiles[i];
398     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
399     {
400       projectile->projectileType = projectileType;
401       projectile->shootTime = gameTime.time;
402       projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed;
403       projectile->damage = damage;
404       projectile->position = position;
405       projectile->target = target;
406       projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position));
407       projectile->targetEnemy = EnemyGetId(enemy);
408       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
409       return projectile;
410     }
411   }
412   return 0;
413 }
414 
415 //# Towers
416 
417 #define TOWER_MAX_COUNT 400
418 #define TOWER_TYPE_NONE 0
419 #define TOWER_TYPE_BASE 1
420 #define TOWER_TYPE_GUN 2
421 
422 typedef struct Tower
423 {
424   int16_t x, y;
425   uint8_t towerType;
426   float cooldown;
427 } Tower;
428 
429 Tower towers[TOWER_MAX_COUNT];
430 int towerCount = 0;
431 
432 void TowerInit()
433 {
434   for (int i = 0; i < TOWER_MAX_COUNT; i++)
435   {
436     towers[i] = (Tower){0};
437   }
438   towerCount = 0;
439 }
440 
441 Tower *TowerGetAt(int16_t x, int16_t y)
442 {
443   for (int i = 0; i < towerCount; i++)
444   {
445     if (towers[i].x == x && towers[i].y == y)
446     {
447       return &towers[i];
448     }
449   }
450   return 0;
451 }
452 
453 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
454 {
455   if (towerCount >= TOWER_MAX_COUNT)
456   {
457     return 0;
458   }
459 
460   Tower *tower = TowerGetAt(x, y);
461   if (tower)
462   {
463     return 0;
464   }
465 
466   tower = &towers[towerCount++];
467   tower->x = x;
468   tower->y = y;
469   tower->towerType = towerType;
470   return tower;
471 }
472 
473 void TowerDraw()
474 {
475   for (int i = 0; i < towerCount; i++)
476   {
477     Tower tower = towers[i];
478     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
479     switch (tower.towerType)
480     {
481     case TOWER_TYPE_BASE:
482       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
483       break;
484     case TOWER_TYPE_GUN:
485       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
486       break;
487     }
488   }
489 }
490 
491 void TowerGunUpdate(Tower *tower)
492 {
493   if (tower->cooldown <= 0)
494   {
495     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
496     if (enemy)
497     {
498       tower->cooldown = 0.25f;
499       // shoot the enemy; determine future position of the enemy
500       float bulletSpeed = 1.0f;
501       float bulletDamage = 3.0f;
502       Vector2 velocity = enemy->simVelocity;
503       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
504       Vector2 towerPosition = {tower->x, tower->y};
505       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
506       for (int i = 0; i < 8; i++) {
507         velocity = enemy->simVelocity;
508         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
509         float distance = Vector2Distance(towerPosition, futurePosition);
510         float eta2 = distance / bulletSpeed;
511         if (fabs(eta - eta2) < 0.01f) {
512           break;
513         }
514         eta = (eta2 + eta) * 0.5f;
515       }
516       ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, towerPosition, futurePosition, 
517         bulletSpeed, bulletDamage);
518       enemy->futureDamage += bulletDamage;
519     }
520   }
521   else
522   {
523     tower->cooldown -= gameTime.deltaTime;
524   }
525 }
526 
527 void TowerUpdate()
528 {
529   for (int i = 0; i < towerCount; i++)
530   {
531     Tower *tower = &towers[i];
532     switch (tower->towerType)
533     {
534     case TOWER_TYPE_GUN:
535       TowerGunUpdate(tower);
536       break;
537     }
538   }
539 }
540 
541 //# Game
542 
543 float nextSpawnTime = 0.0f;
544 
545 void InitGame()
546 {
547   TowerInit();
548   EnemyInit();
549   ProjectileInit();
550 
551   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
552   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
553   TowerTryAdd(TOWER_TYPE_GUN, -2, 0);
554   EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4);
555 }
556 
557 void GameUpdate()
558 {
559   float dt = GetFrameTime();
560   // cap maximum delta time to 0.1 seconds to prevent large time steps
561   if (dt > 0.1f) dt = 0.1f;
562   gameTime.time += dt;
563   gameTime.deltaTime = dt;
564   EnemyUpdate();
565   TowerUpdate();
566   ProjectileUpdate();
567 
568   // spawn a new enemy every second
569   if (gameTime.time >= nextSpawnTime && EnemyCount() < 1)
570   {
571     nextSpawnTime = gameTime.time + 1.0f;
572     // add a new enemy at the boundary of the map
573     int randValue = GetRandomValue(-5, 5);
574     int randSide = GetRandomValue(0, 3);
575     int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue;
576     int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue;
577     EnemyTryAdd(ENEMY_TYPE_MINION, x, y);
578   }
579 }
580 
581 int main(void)
582 {
583   int screenWidth, screenHeight;
584   GetPreferredSize(&screenWidth, &screenHeight);
585   InitWindow(screenWidth, screenHeight, "Tower defense");
586   SetTargetFPS(30);
587 
588   Camera3D camera = {0};
589   camera.position = (Vector3){0.0f, 10.0f, 5.0f};
590   camera.target = (Vector3){0.0f, 0.0f, 0.0f};
591   camera.up = (Vector3){0.0f, 0.0f, -1.0f};
592   camera.fovy = 45.0f;
593   camera.projection = CAMERA_PERSPECTIVE;
594 
595   InitGame();
596 
597   while (!WindowShouldClose())
598   {
599     if (IsPaused()) {
600       // canvas is not visible in browser - do nothing
601       continue;
602     }
603     BeginDrawing();
604     ClearBackground(DARKBLUE);
605 
606     BeginMode3D(camera);
607     DrawGrid(10, 1.0f);
608     TowerDraw();
609     EnemyDraw();
610     ProjectileDraw();
611     GameUpdate();
612     EndMode3D();
613 
614     DrawText("Tower defense tutorial", 5, 5, 20, WHITE);
615     EndDrawing();
616   }
617 
618   CloseWindow();
619 
620   return 0;
621 }
  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 working surprisingly well! The enemy is now almost on a straight line when moving diagonally and maintains a steady speed. The projectiles seem to hit the enemy as well, though the camera perspective makes this a little difficult to judge. It makes sense to change the camera perspective to a top-down view and using an orthogonal projection for now. Also, how about drawing the path of the enemy to see how it moves? This might be useful for debugging and maybe some future effects.
For tracking the path, we'll add a fixed number of points to the enemy struct; when the enemy has moved a certain distance, we'll add the current position to the path. 8 points should be enough for our purpose.
  1 #include "td-tut-2-main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 typedef struct GameTime
  7 {
  8   float time;
  9   float deltaTime;
 10 } GameTime;
 11 
 12 GameTime gameTime = {0};
 13 
 14 //# Enemies
 15 
 16 #define ENEMY_MAX_PATH_COUNT 8
 17 #define ENEMY_MAX_COUNT 400
 18 #define ENEMY_TYPE_NONE 0
 19 #define ENEMY_TYPE_MINION 1
 20 
 21 typedef struct EnemyId
 22 {
 23   uint16_t index;
 24   uint16_t generation;
 25 } EnemyId;
 26 
 27 typedef struct EnemyClassConfig
 28 {
 29   float speed;
 30   float health;
 31   float radius;
 32   float maxAcceleration;
 33 } EnemyClassConfig;
 34 
 35 typedef struct Enemy
 36 {
 37   int16_t currentX, currentY;
 38   int16_t nextX, nextY;
 39   Vector2 simPosition;
 40   Vector2 simVelocity;
 41   uint16_t generation;
 42   float startMovingTime;
 43   float damage, futureDamage;
 44   uint8_t enemyType;
 45   uint8_t movePathCount;
 46   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
 47 } Enemy;
 48 
 49 Enemy enemies[ENEMY_MAX_COUNT];
 50 int enemyCount = 0;
 51 
 52 EnemyClassConfig enemyClassConfigs[] = {
 53     [ENEMY_TYPE_MINION] = {.health = 3.0f, .speed = 1.0f, .radius = 0.25f, .maxAcceleration = 1.0f},
 54 };
 55 
 56 void EnemyInit()
 57 {
 58   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 59   {
 60     enemies[i] = (Enemy){0};
 61   }
 62   enemyCount = 0;
 63 }
 64 
 65 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
 66 {
 67   return enemyClassConfigs[enemy->enemyType].speed;
 68 }
 69 
 70 float EnemyGetMaxHealth(Enemy *enemy)
 71 {
 72   return enemyClassConfigs[enemy->enemyType].health;
 73 }
 74 
 75 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
 76 {
 77   int16_t castleX = 0;
 78   int16_t castleY = 0;
 79   int16_t dx = castleX - currentX;
 80   int16_t dy = castleY - currentY;
 81   if (dx == 0 && dy == 0)
 82   {
 83     *nextX = currentX;
 84     *nextY = currentY;
 85     return 1;
 86   }
 87   if (abs(dx) > abs(dy))
 88   {
 89     *nextX = currentX + (dx > 0 ? 1 : -1);
 90     *nextY = currentY;
 91   }
 92   else
 93   {
 94     *nextX = currentX;
 95     *nextY = currentY + (dy > 0 ? 1 : -1);
 96   }
 97   return 0;
 98 }
 99 
100 
101 // this function predicts the movement of the unit for the next deltaT seconds
102 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
103 {
104   const float pointReachedDistance = 0.25f;
105   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
106   const float maxSimStepTime = 0.015625f;
107   
108   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
109   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
110   int16_t nextX = enemy->nextX;
111   int16_t nextY = enemy->nextY;
112   Vector2 position = enemy->simPosition;
113   int passedCount = 0;
114   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
115   {
116     float stepTime = fminf(deltaT - t, maxSimStepTime);
117     Vector2 target = (Vector2){nextX, nextY};
118     float speed = Vector2Length(*velocity);
119     // draw the target position for debugging
120     DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
121     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
122     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
123     {
124       // we reached the target position, let's move to the next waypoint
125       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
126       target = (Vector2){nextX, nextY};
127       // track how many waypoints we passed
128       passedCount++;
129     }
130     
131     // acceleration towards the target
132     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
133     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
134     *velocity = Vector2Add(*velocity, acceleration);
135 
136     // limit the speed to the maximum speed
137     if (speed > maxSpeed)
138     {
139       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
140     }
141 
142     // move the enemy
143     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
144   }
145 
146   if (waypointPassedCount)
147   {
148     (*waypointPassedCount) = passedCount;
149   }
150 
151   return position;
152 }
153 
154 void EnemyDraw()
155 {
156   for (int i = 0; i < enemyCount; i++)
157   {
158     Enemy enemy = enemies[i];
159     if (enemy.enemyType == ENEMY_TYPE_NONE)
160     {
161       continue;
162     }
163 
164     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
165     
166     if (enemy.movePathCount > 0)
167     {
168       Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
169       DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
170     }
171     for (int j = 1; j < enemy.movePathCount; j++)
172     {
173       Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
174       Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
175       DrawLine3D(p, q, GREEN);
176     }
177 
178     switch (enemy.enemyType)
179     {
180     case ENEMY_TYPE_MINION:
181       DrawCubeWires((Vector3){position.x, 0.2f, position.y}, 0.4f, 0.4f, 0.4f, GREEN);
182       break;
183     }
184   }
185 }
186 
187 void EnemyUpdate()
188 {
189   const float maxPathDistance2 = 0.25f * 0.25f;
190   for (int i = 0; i < enemyCount; i++)
191   {
192     Enemy *enemy = &enemies[i];
193     if (enemy->enemyType == ENEMY_TYPE_NONE)
194     {
195       continue;
196     }
197 
198     int waypointPassedCount = 0;
199     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
200     enemy->startMovingTime = gameTime.time;
201     // track path of unit
202     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
203     {
204       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
205       {
206         enemy->movePath[j] = enemy->movePath[j - 1];
207       }
208       enemy->movePath[0] = enemy->simPosition;
209       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
210       {
211         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
212       }
213     }
214 
215     if (waypointPassedCount > 0)
216     {
217       enemy->currentX = enemy->nextX;
218       enemy->currentY = enemy->nextY;
219       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY))
220       {
221         // enemy reached the castle; remove it
222         enemy->enemyType = ENEMY_TYPE_NONE;
223         continue;
224       }
225     }
226 
227   }
228 }
229 
230 EnemyId EnemyGetId(Enemy *enemy)
231 {
232   return (EnemyId){enemy - enemies, enemy->generation};
233 }
234 
235 Enemy *EnemyTryResolve(EnemyId enemyId)
236 {
237   if (enemyId.index >= ENEMY_MAX_COUNT)
238   {
239     return 0;
240   }
241   Enemy *enemy = &enemies[enemyId.index];
242   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
243   {
244     return 0;
245   }
246   return enemy;
247 }
248 
249 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
250 {
251   Enemy *spawn = 0;
252   for (int i = 0; i < enemyCount; i++)
253   {
254     Enemy *enemy = &enemies[i];
255     if (enemy->enemyType == ENEMY_TYPE_NONE)
256     {
257       spawn = enemy;
258       break;
259     }
260   }
261 
262   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
263   {
264     spawn = &enemies[enemyCount++];
265   }
266 
267   if (spawn)
268   {
269     spawn->currentX = currentX;
270     spawn->currentY = currentY;
271     spawn->nextX = currentX;
272     spawn->nextY = currentY;
273     spawn->simPosition = (Vector2){currentX, currentY};
274     spawn->simVelocity = (Vector2){0, 0};
275     spawn->enemyType = enemyType;
276     spawn->startMovingTime = gameTime.time;
277     spawn->damage = 0.0f;
278     spawn->futureDamage = 0.0f;
279     spawn->generation++;
280     spawn->movePathCount = 0;
281   }
282 
283   return spawn;
284 }
285 
286 int EnemyAddDamage(Enemy *enemy, float damage)
287 {
288   enemy->damage += damage;
289   if (enemy->damage >= EnemyGetMaxHealth(enemy))
290   {
291     enemy->enemyType = ENEMY_TYPE_NONE;
292     return 1;
293   }
294 
295   return 0;
296 }
297 
298 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
299 {
300   int16_t castleX = 0;
301   int16_t castleY = 0;
302   Enemy* closest = 0;
303   int16_t closestDistance = 0;
304   float range2 = range * range;
305   for (int i = 0; i < enemyCount; i++)
306   {
307     Enemy* enemy = &enemies[i];
308     if (enemy->enemyType == ENEMY_TYPE_NONE)
309     {
310       continue;
311     }
312     float maxHealth = EnemyGetMaxHealth(enemy);
313     if (enemy->futureDamage >= maxHealth)
314     {
315       // ignore enemies that will die soon
316       continue;
317     }
318     int16_t dx = castleX - enemy->currentX;
319     int16_t dy = castleY - enemy->currentY;
320     int16_t distance = abs(dx) + abs(dy);
321     if (!closest || distance < closestDistance)
322     {
323       float tdx = towerX - enemy->currentX;
324       float tdy = towerY - enemy->currentY;
325       float tdistance2 = tdx * tdx + tdy * tdy;
326       if (tdistance2 <= range2)
327       {
328         closest = enemy;
329         closestDistance = distance;
330       }
331     }
332   }
333   return closest;
334 }
335 
336 int EnemyCount()
337 {
338   int count = 0;
339   for (int i = 0; i < enemyCount; i++)
340   {
341     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
342     {
343       count++;
344     }
345   }
346   return count;
347 }
348 
349 //# Projectiles
350 #define PROJECTILE_MAX_COUNT 1200
351 #define PROJECTILE_TYPE_NONE 0
352 #define PROJECTILE_TYPE_BULLET 1
353 
354 typedef struct Projectile
355 {
356   uint8_t projectileType;
357   float shootTime;
358   float arrivalTime;
359   float damage;
360   Vector2 position;
361   Vector2 target;
362   Vector2 directionNormal;
363   EnemyId targetEnemy;
364 } Projectile;
365 
366 Projectile projectiles[PROJECTILE_MAX_COUNT];
367 int projectileCount = 0;
368 
369 void ProjectileInit()
370 {
371   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
372   {
373     projectiles[i] = (Projectile){0};
374   }
375 }
376 
377 void ProjectileDraw()
378 {
379   for (int i = 0; i < projectileCount; i++)
380   {
381     Projectile projectile = projectiles[i];
382     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
383     {
384       continue;
385     }
386     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
387     if (transition >= 1.0f)
388     {
389       continue;
390     }
391     Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition);
392     float x = position.x;
393     float y = position.y;
394     float dx = projectile.directionNormal.x;
395     float dy = projectile.directionNormal.y;
396     for (float d = 1.0f; d > 0.0f; d -= 0.25f)
397     {
398       x -= dx * 0.1f;
399       y -= dy * 0.1f;
400       float size = 0.1f * d;
401       DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED);
402     }
403   }
404 }
405 
406 void ProjectileUpdate()
407 {
408   for (int i = 0; i < projectileCount; i++)
409   {
410     Projectile *projectile = &projectiles[i];
411     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
412     {
413       continue;
414     }
415     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
416     if (transition >= 1.0f)
417     {
418       projectile->projectileType = PROJECTILE_TYPE_NONE;
419       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
420       if (enemy)
421       {
422         EnemyAddDamage(enemy, projectile->damage);
423       }
424       continue;
425     }
426   }
427 }
428 
429 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage)
430 {
431   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
432   {
433     Projectile *projectile = &projectiles[i];
434     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
435     {
436       projectile->projectileType = projectileType;
437       projectile->shootTime = gameTime.time;
438       projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed;
439       projectile->damage = damage;
440       projectile->position = position;
441       projectile->target = target;
442       projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position));
443       projectile->targetEnemy = EnemyGetId(enemy);
444       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
445       return projectile;
446     }
447   }
448   return 0;
449 }
450 
451 //# Towers
452 
453 #define TOWER_MAX_COUNT 400
454 #define TOWER_TYPE_NONE 0
455 #define TOWER_TYPE_BASE 1
456 #define TOWER_TYPE_GUN 2
457 
458 typedef struct Tower
459 {
460   int16_t x, y;
461   uint8_t towerType;
462   float cooldown;
463 } Tower;
464 
465 Tower towers[TOWER_MAX_COUNT];
466 int towerCount = 0;
467 
468 void TowerInit()
469 {
470   for (int i = 0; i < TOWER_MAX_COUNT; i++)
471   {
472     towers[i] = (Tower){0};
473   }
474   towerCount = 0;
475 }
476 
477 Tower *TowerGetAt(int16_t x, int16_t y)
478 {
479   for (int i = 0; i < towerCount; i++)
480   {
481     if (towers[i].x == x && towers[i].y == y)
482     {
483       return &towers[i];
484     }
485   }
486   return 0;
487 }
488 
489 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
490 {
491   if (towerCount >= TOWER_MAX_COUNT)
492   {
493     return 0;
494   }
495 
496   Tower *tower = TowerGetAt(x, y);
497   if (tower)
498   {
499     return 0;
500   }
501 
502   tower = &towers[towerCount++];
503   tower->x = x;
504   tower->y = y;
505   tower->towerType = towerType;
506   return tower;
507 }
508 
509 void TowerDraw()
510 {
511   for (int i = 0; i < towerCount; i++)
512   {
513     Tower tower = towers[i];
514     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
515     switch (tower.towerType)
516     {
517     case TOWER_TYPE_BASE:
518       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
519       break;
520     case TOWER_TYPE_GUN:
521       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
522       break;
523     }
524   }
525 }
526 
527 void TowerGunUpdate(Tower *tower)
528 {
529   if (tower->cooldown <= 0)
530   {
531     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
532     if (enemy)
533     {
534       tower->cooldown = 0.25f;
535       // shoot the enemy; determine future position of the enemy
536       float bulletSpeed = 1.0f;
537       float bulletDamage = 3.0f;
538       Vector2 velocity = enemy->simVelocity;
539       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
540       Vector2 towerPosition = {tower->x, tower->y};
541       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
542       for (int i = 0; i < 8; i++) {
543         velocity = enemy->simVelocity;
544         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
545         float distance = Vector2Distance(towerPosition, futurePosition);
546         float eta2 = distance / bulletSpeed;
547         if (fabs(eta - eta2) < 0.01f) {
548           break;
549         }
550         eta = (eta2 + eta) * 0.5f;
551       }
552       ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, towerPosition, futurePosition, 
553         bulletSpeed, bulletDamage);
554       enemy->futureDamage += bulletDamage;
555     }
556   }
557   else
558   {
559     tower->cooldown -= gameTime.deltaTime;
560   }
561 }
562 
563 void TowerUpdate()
564 {
565   for (int i = 0; i < towerCount; i++)
566   {
567     Tower *tower = &towers[i];
568     switch (tower->towerType)
569     {
570     case TOWER_TYPE_GUN:
571       TowerGunUpdate(tower);
572       break;
573     }
574   }
575 }
576 
577 //# Game
578 
579 float nextSpawnTime = 0.0f;
580 
581 void InitGame()
582 {
583   TowerInit();
584   EnemyInit();
585   ProjectileInit();
586 
587   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
588   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
589   TowerTryAdd(TOWER_TYPE_GUN, -2, 0);
590   EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4);
591 }
592 
593 void GameUpdate()
594 {
595   float dt = GetFrameTime();
596   // cap maximum delta time to 0.1 seconds to prevent large time steps
597   if (dt > 0.1f) dt = 0.1f;
598   gameTime.time += dt;
599   gameTime.deltaTime = dt;
600   EnemyUpdate();
601   TowerUpdate();
602   ProjectileUpdate();
603 
604   // spawn a new enemy every second
605   if (gameTime.time >= nextSpawnTime && EnemyCount() < 10)
606   {
607     nextSpawnTime = gameTime.time + 1.0f;
608     // add a new enemy at the boundary of the map
609     int randValue = GetRandomValue(-5, 5);
610     int randSide = GetRandomValue(0, 3);
611     int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue;
612     int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue;
613     static int alternation = 0;
614     alternation += 1;
615     if (alternation % 3 == 0) {
616       EnemyTryAdd(ENEMY_TYPE_MINION, 0, -5);
617     }
618     else if (alternation % 3 == 1)
619     {
620       EnemyTryAdd(ENEMY_TYPE_MINION, 0, 5);
621     }
622     EnemyTryAdd(ENEMY_TYPE_MINION, x, y);
623   }
624 }
625 
626 int main(void)
627 {
628   int screenWidth, screenHeight;
629   GetPreferredSize(&screenWidth, &screenHeight);
630   InitWindow(screenWidth, screenHeight, "Tower defense");
631   SetTargetFPS(30);
632 
633   Camera3D camera = {0};
634   camera.position = (Vector3){0.0f, 10.0f, 0.0f};
635   camera.target = (Vector3){0.0f, 0.0f, 0.0f};
636   camera.up = (Vector3){0.0f, 0.0f, -1.0f};
637   camera.fovy = 10.0f;
638   camera.projection = CAMERA_ORTHOGRAPHIC;
639 
640   InitGame();
641 
642   while (!WindowShouldClose())
643   {
644     if (IsPaused()) {
645       // canvas is not visible in browser - do nothing
646       continue;
647     }
648     BeginDrawing();
649     ClearBackground(DARKBLUE);
650 
651     BeginMode3D(camera);
652     DrawGrid(10, 1.0f);
653     TowerDraw();
654     EnemyDraw();
655     ProjectileDraw();
656     GameUpdate();
657     EndMode3D();
658 
659     DrawText("Tower defense tutorial", 5, 5, 20, WHITE);
660     EndDrawing();
661   }
662 
663   CloseWindow();
664 
665   return 0;
666 }
  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
The path is now visible and we can see how the enemy moves quite well from above. What confused me first, was that when the enemy is moving in a vertical straight line towards the tower, the projectile is not hitting the enemy because the enemy disappears well before that. But the reason is, that the detection if the enemy has reached the castle is now flawed and must be fixed:
  1 #include "td-tut-2-main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 typedef struct GameTime
  7 {
  8   float time;
  9   float deltaTime;
 10 } GameTime;
 11 
 12 GameTime gameTime = {0};
 13 
 14 //# Enemies
 15 
 16 #define ENEMY_MAX_PATH_COUNT 8
 17 #define ENEMY_MAX_COUNT 400
 18 #define ENEMY_TYPE_NONE 0
 19 #define ENEMY_TYPE_MINION 1
 20 
 21 typedef struct EnemyId
 22 {
 23   uint16_t index;
 24   uint16_t generation;
 25 } EnemyId;
 26 
 27 typedef struct EnemyClassConfig
 28 {
 29   float speed;
 30   float health;
 31   float radius;
 32   float maxAcceleration;
 33 } EnemyClassConfig;
 34 
 35 typedef struct Enemy
 36 {
 37   int16_t currentX, currentY;
 38   int16_t nextX, nextY;
 39   Vector2 simPosition;
 40   Vector2 simVelocity;
 41   uint16_t generation;
 42   float startMovingTime;
 43   float damage, futureDamage;
 44   uint8_t enemyType;
 45   uint8_t movePathCount;
 46   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
 47 } Enemy;
 48 
 49 Enemy enemies[ENEMY_MAX_COUNT];
 50 int enemyCount = 0;
 51 
 52 EnemyClassConfig enemyClassConfigs[] = {
 53     [ENEMY_TYPE_MINION] = {.health = 3.0f, .speed = 1.0f, .radius = 0.25f, .maxAcceleration = 1.0f},
 54 };
 55 
 56 void EnemyInit()
 57 {
 58   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 59   {
 60     enemies[i] = (Enemy){0};
 61   }
 62   enemyCount = 0;
 63 }
 64 
 65 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
 66 {
 67   return enemyClassConfigs[enemy->enemyType].speed;
 68 }
 69 
 70 float EnemyGetMaxHealth(Enemy *enemy)
 71 {
 72   return enemyClassConfigs[enemy->enemyType].health;
 73 }
 74 
 75 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
 76 {
 77   int16_t castleX = 0;
 78   int16_t castleY = 0;
 79   int16_t dx = castleX - currentX;
 80   int16_t dy = castleY - currentY;
 81   if (dx == 0 && dy == 0)
 82   {
 83     *nextX = currentX;
 84     *nextY = currentY;
 85     return 1;
 86   }
 87   if (abs(dx) > abs(dy))
 88   {
 89     *nextX = currentX + (dx > 0 ? 1 : -1);
 90     *nextY = currentY;
 91   }
 92   else
 93   {
 94     *nextX = currentX;
 95     *nextY = currentY + (dy > 0 ? 1 : -1);
 96   }
 97   return 0;
 98 }
 99 
100 
101 // this function predicts the movement of the unit for the next deltaT seconds
102 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
103 {
104   const float pointReachedDistance = 0.25f;
105   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
106   const float maxSimStepTime = 0.015625f;
107   
108   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
109   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
110   int16_t nextX = enemy->nextX;
111   int16_t nextY = enemy->nextY;
112   Vector2 position = enemy->simPosition;
113   int passedCount = 0;
114   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
115   {
116     float stepTime = fminf(deltaT - t, maxSimStepTime);
117     Vector2 target = (Vector2){nextX, nextY};
118     float speed = Vector2Length(*velocity);
119     // draw the target position for debugging
120     DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
121     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
122     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
123     {
124       // we reached the target position, let's move to the next waypoint
125       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
126       target = (Vector2){nextX, nextY};
127       // track how many waypoints we passed
128       passedCount++;
129     }
130     
131     // acceleration towards the target
132     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
133     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
134     *velocity = Vector2Add(*velocity, acceleration);
135 
136     // limit the speed to the maximum speed
137     if (speed > maxSpeed)
138     {
139       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
140     }
141 
142     // move the enemy
143     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
144   }
145 
146   if (waypointPassedCount)
147   {
148     (*waypointPassedCount) = passedCount;
149   }
150 
151   return position;
152 }
153 
154 void EnemyDraw()
155 {
156   for (int i = 0; i < enemyCount; i++)
157   {
158     Enemy enemy = enemies[i];
159     if (enemy.enemyType == ENEMY_TYPE_NONE)
160     {
161       continue;
162     }
163 
164     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
165     
166     if (enemy.movePathCount > 0)
167     {
168       Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
169       DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
170     }
171     for (int j = 1; j < enemy.movePathCount; j++)
172     {
173       Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
174       Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
175       DrawLine3D(p, q, GREEN);
176     }
177 
178     switch (enemy.enemyType)
179     {
180     case ENEMY_TYPE_MINION:
181       DrawCubeWires((Vector3){position.x, 0.2f, position.y}, 0.4f, 0.4f, 0.4f, GREEN);
182       break;
183     }
184   }
185 }
186 
187 void EnemyUpdate()
188 {
189   const float castleX = 0;
190   const float castleY = 0;
191   const float maxPathDistance2 = 0.25f * 0.25f;
192   
193   for (int i = 0; i < enemyCount; i++)
194   {
195     Enemy *enemy = &enemies[i];
196     if (enemy->enemyType == ENEMY_TYPE_NONE)
197     {
198       continue;
199     }
200 
201     int waypointPassedCount = 0;
202     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
203     enemy->startMovingTime = gameTime.time;
204     // track path of unit
205     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
206     {
207       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
208       {
209         enemy->movePath[j] = enemy->movePath[j - 1];
210       }
211       enemy->movePath[0] = enemy->simPosition;
212       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
213       {
214         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
215       }
216     }
217 
218     if (waypointPassedCount > 0)
219     {
220       enemy->currentX = enemy->nextX;
221       enemy->currentY = enemy->nextY;
222       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) &&
223         Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
224       {
225         // enemy reached the castle; remove it
226         enemy->enemyType = ENEMY_TYPE_NONE;
227         continue;
228       }
229     }
230 
231   }
232 }
233 
234 EnemyId EnemyGetId(Enemy *enemy)
235 {
236   return (EnemyId){enemy - enemies, enemy->generation};
237 }
238 
239 Enemy *EnemyTryResolve(EnemyId enemyId)
240 {
241   if (enemyId.index >= ENEMY_MAX_COUNT)
242   {
243     return 0;
244   }
245   Enemy *enemy = &enemies[enemyId.index];
246   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
247   {
248     return 0;
249   }
250   return enemy;
251 }
252 
253 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
254 {
255   Enemy *spawn = 0;
256   for (int i = 0; i < enemyCount; i++)
257   {
258     Enemy *enemy = &enemies[i];
259     if (enemy->enemyType == ENEMY_TYPE_NONE)
260     {
261       spawn = enemy;
262       break;
263     }
264   }
265 
266   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
267   {
268     spawn = &enemies[enemyCount++];
269   }
270 
271   if (spawn)
272   {
273     spawn->currentX = currentX;
274     spawn->currentY = currentY;
275     spawn->nextX = currentX;
276     spawn->nextY = currentY;
277     spawn->simPosition = (Vector2){currentX, currentY};
278     spawn->simVelocity = (Vector2){0, 0};
279     spawn->enemyType = enemyType;
280     spawn->startMovingTime = gameTime.time;
281     spawn->damage = 0.0f;
282     spawn->futureDamage = 0.0f;
283     spawn->generation++;
284     spawn->movePathCount = 0;
285   }
286 
287   return spawn;
288 }
289 
290 int EnemyAddDamage(Enemy *enemy, float damage)
291 {
292   enemy->damage += damage;
293   if (enemy->damage >= EnemyGetMaxHealth(enemy))
294   {
295     enemy->enemyType = ENEMY_TYPE_NONE;
296     return 1;
297   }
298 
299   return 0;
300 }
301 
302 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
303 {
304   int16_t castleX = 0;
305   int16_t castleY = 0;
306   Enemy* closest = 0;
307   int16_t closestDistance = 0;
308   float range2 = range * range;
309   for (int i = 0; i < enemyCount; i++)
310   {
311     Enemy* enemy = &enemies[i];
312     if (enemy->enemyType == ENEMY_TYPE_NONE)
313     {
314       continue;
315     }
316     float maxHealth = EnemyGetMaxHealth(enemy);
317     if (enemy->futureDamage >= maxHealth)
318     {
319       // ignore enemies that will die soon
320       continue;
321     }
322     int16_t dx = castleX - enemy->currentX;
323     int16_t dy = castleY - enemy->currentY;
324     int16_t distance = abs(dx) + abs(dy);
325     if (!closest || distance < closestDistance)
326     {
327       float tdx = towerX - enemy->currentX;
328       float tdy = towerY - enemy->currentY;
329       float tdistance2 = tdx * tdx + tdy * tdy;
330       if (tdistance2 <= range2)
331       {
332         closest = enemy;
333         closestDistance = distance;
334       }
335     }
336   }
337   return closest;
338 }
339 
340 int EnemyCount()
341 {
342   int count = 0;
343   for (int i = 0; i < enemyCount; i++)
344   {
345     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
346     {
347       count++;
348     }
349   }
350   return count;
351 }
352 
353 //# Projectiles
354 #define PROJECTILE_MAX_COUNT 1200
355 #define PROJECTILE_TYPE_NONE 0
356 #define PROJECTILE_TYPE_BULLET 1
357 
358 typedef struct Projectile
359 {
360   uint8_t projectileType;
361   float shootTime;
362   float arrivalTime;
363   float damage;
364   Vector2 position;
365   Vector2 target;
366   Vector2 directionNormal;
367   EnemyId targetEnemy;
368 } Projectile;
369 
370 Projectile projectiles[PROJECTILE_MAX_COUNT];
371 int projectileCount = 0;
372 
373 void ProjectileInit()
374 {
375   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
376   {
377     projectiles[i] = (Projectile){0};
378   }
379 }
380 
381 void ProjectileDraw()
382 {
383   for (int i = 0; i < projectileCount; i++)
384   {
385     Projectile projectile = projectiles[i];
386     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
387     {
388       continue;
389     }
390     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
391     if (transition >= 1.0f)
392     {
393       continue;
394     }
395     Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition);
396     float x = position.x;
397     float y = position.y;
398     float dx = projectile.directionNormal.x;
399     float dy = projectile.directionNormal.y;
400     for (float d = 1.0f; d > 0.0f; d -= 0.25f)
401     {
402       x -= dx * 0.1f;
403       y -= dy * 0.1f;
404       float size = 0.1f * d;
405       DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED);
406     }
407   }
408 }
409 
410 void ProjectileUpdate()
411 {
412   for (int i = 0; i < projectileCount; i++)
413   {
414     Projectile *projectile = &projectiles[i];
415     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
416     {
417       continue;
418     }
419     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
420     if (transition >= 1.0f)
421     {
422       projectile->projectileType = PROJECTILE_TYPE_NONE;
423       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
424       if (enemy)
425       {
426         EnemyAddDamage(enemy, projectile->damage);
427       }
428       continue;
429     }
430   }
431 }
432 
433 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage)
434 {
435   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
436   {
437     Projectile *projectile = &projectiles[i];
438     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
439     {
440       projectile->projectileType = projectileType;
441       projectile->shootTime = gameTime.time;
442       projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed;
443       projectile->damage = damage;
444       projectile->position = position;
445       projectile->target = target;
446       projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position));
447       projectile->targetEnemy = EnemyGetId(enemy);
448       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
449       return projectile;
450     }
451   }
452   return 0;
453 }
454 
455 //# Towers
456 
457 #define TOWER_MAX_COUNT 400
458 #define TOWER_TYPE_NONE 0
459 #define TOWER_TYPE_BASE 1
460 #define TOWER_TYPE_GUN 2
461 
462 typedef struct Tower
463 {
464   int16_t x, y;
465   uint8_t towerType;
466   float cooldown;
467 } Tower;
468 
469 Tower towers[TOWER_MAX_COUNT];
470 int towerCount = 0;
471 
472 void TowerInit()
473 {
474   for (int i = 0; i < TOWER_MAX_COUNT; i++)
475   {
476     towers[i] = (Tower){0};
477   }
478   towerCount = 0;
479 }
480 
481 Tower *TowerGetAt(int16_t x, int16_t y)
482 {
483   for (int i = 0; i < towerCount; i++)
484   {
485     if (towers[i].x == x && towers[i].y == y)
486     {
487       return &towers[i];
488     }
489   }
490   return 0;
491 }
492 
493 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
494 {
495   if (towerCount >= TOWER_MAX_COUNT)
496   {
497     return 0;
498   }
499 
500   Tower *tower = TowerGetAt(x, y);
501   if (tower)
502   {
503     return 0;
504   }
505 
506   tower = &towers[towerCount++];
507   tower->x = x;
508   tower->y = y;
509   tower->towerType = towerType;
510   return tower;
511 }
512 
513 void TowerDraw()
514 {
515   for (int i = 0; i < towerCount; i++)
516   {
517     Tower tower = towers[i];
518     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
519     switch (tower.towerType)
520     {
521     case TOWER_TYPE_BASE:
522       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
523       break;
524     case TOWER_TYPE_GUN:
525       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
526       break;
527     }
528   }
529 }
530 
531 void TowerGunUpdate(Tower *tower)
532 {
533   if (tower->cooldown <= 0)
534   {
535     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
536     if (enemy)
537     {
538       tower->cooldown = 0.25f;
539       // shoot the enemy; determine future position of the enemy
540       float bulletSpeed = 1.0f;
541       float bulletDamage = 3.0f;
542       Vector2 velocity = enemy->simVelocity;
543       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
544       Vector2 towerPosition = {tower->x, tower->y};
545       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
546       for (int i = 0; i < 8; i++) {
547         velocity = enemy->simVelocity;
548         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
549         float distance = Vector2Distance(towerPosition, futurePosition);
550         float eta2 = distance / bulletSpeed;
551         if (fabs(eta - eta2) < 0.01f) {
552           break;
553         }
554         eta = (eta2 + eta) * 0.5f;
555       }
556       ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, towerPosition, futurePosition, 
557         bulletSpeed, bulletDamage);
558       enemy->futureDamage += bulletDamage;
559     }
560   }
561   else
562   {
563     tower->cooldown -= gameTime.deltaTime;
564   }
565 }
566 
567 void TowerUpdate()
568 {
569   for (int i = 0; i < towerCount; i++)
570   {
571     Tower *tower = &towers[i];
572     switch (tower->towerType)
573     {
574     case TOWER_TYPE_GUN:
575       TowerGunUpdate(tower);
576       break;
577     }
578   }
579 }
580 
581 //# Game
582 
583 float nextSpawnTime = 0.0f;
584 
585 void InitGame()
586 {
587   TowerInit();
588   EnemyInit();
589   ProjectileInit();
590 
591   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
592   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
593   TowerTryAdd(TOWER_TYPE_GUN, -2, 0);
594   EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4);
595 }
596 
597 void GameUpdate()
598 {
599   float dt = GetFrameTime();
600   // cap maximum delta time to 0.1 seconds to prevent large time steps
601   if (dt > 0.1f) dt = 0.1f;
602   gameTime.time += dt;
603   gameTime.deltaTime = dt;
604   EnemyUpdate();
605   TowerUpdate();
606   ProjectileUpdate();
607 
608   // spawn a new enemy every second
609   if (gameTime.time >= nextSpawnTime && EnemyCount() < 10)
610   {
611     nextSpawnTime = gameTime.time + 1.0f;
612     // add a new enemy at the boundary of the map
613     int randValue = GetRandomValue(-5, 5);
614     int randSide = GetRandomValue(0, 3);
615     int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue;
616     int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue;
617     static int alternation = 0;
618     alternation += 1;
619     if (alternation % 3 == 0) {
620       EnemyTryAdd(ENEMY_TYPE_MINION, 0, -5);
621     }
622     else if (alternation % 3 == 1)
623     {
624       EnemyTryAdd(ENEMY_TYPE_MINION, 0, 5);
625     }
626     EnemyTryAdd(ENEMY_TYPE_MINION, x, y);
627   }
628 }
629 
630 int main(void)
631 {
632   int screenWidth, screenHeight;
633   GetPreferredSize(&screenWidth, &screenHeight);
634   InitWindow(screenWidth, screenHeight, "Tower defense");
635   SetTargetFPS(30);
636 
637   Camera3D camera = {0};
638   camera.position = (Vector3){0.0f, 10.0f, 0.0f};
639   camera.target = (Vector3){0.0f, 0.0f, 0.0f};
640   camera.up = (Vector3){0.0f, 0.0f, -1.0f};
641   camera.fovy = 10.0f;
642   camera.projection = CAMERA_ORTHOGRAPHIC;
643 
644   InitGame();
645 
646   while (!WindowShouldClose())
647   {
648     if (IsPaused()) {
649       // canvas is not visible in browser - do nothing
650       continue;
651     }
652     BeginDrawing();
653     ClearBackground(DARKBLUE);
654 
655     BeginMode3D(camera);
656     DrawGrid(10, 1.0f);
657     TowerDraw();
658     EnemyDraw();
659     ProjectileDraw();
660     GameUpdate();
661     EndMode3D();
662 
663     DrawText("Tower defense tutorial", 5, 5, 20, WHITE);
664     EndDrawing();
665   }
666 
667   CloseWindow();
668 
669   return 0;
670 }
  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
Now that the movement is physics based, we can use this to introduce some interesting effects.
The first thing to add is to add enemy collision detection and handling. When two enemy circles overlap, we'll move them apart in a way that they don't overlap (so much) anymore.
Why don't we resolve the collision perfectly? This works well for 2 colliding circles, but when we have 3 circles that are in proximity, this can easily lead to oscillating behavior: A collides with B, so B is moved away. Now B collides with C, so C moves B back again, leading to a collision with A. The more objects are involved in this process, the more jittery it becomes. If we however fix the collision by moving the object 50% of the full way, this is balancing out way smoother. This may be difficult to understand when reading it, but here's how it looks like by comparison:
The left side shows the effect when resolving the collision perfectly. We can see how it permanently jitters because of the chain of collisions resolving steps explained above.
The right side is using the 50% resolution and we can see how the objects are moving towards the center but come to a quite stable rest. The repulsion is in balance with the attraction force towards the center. And even though we correct the error by only 50%, there's not much overlapping. And the 100% collision resolution is not even achieving a perfect result - the objects are still overlapping!
There are many strategies to handle such kind of problems, but as a general rule, you can take away this:
Simulating stiffness is difficult and expensive. Especially when strong forces (=much movement per frame) and many objects are involved.
Back to our code: To see if it is working, we will increase the number of enemies spawned while also increasing the shooting frequency of the towers:
  1 #include "td-tut-2-main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 typedef struct GameTime
  7 {
  8   float time;
  9   float deltaTime;
 10 } GameTime;
 11 
 12 GameTime gameTime = {0};
 13 
 14 //# Enemies
 15 
 16 #define ENEMY_MAX_PATH_COUNT 8
 17 #define ENEMY_MAX_COUNT 400
 18 #define ENEMY_TYPE_NONE 0
 19 #define ENEMY_TYPE_MINION 1
 20 
 21 typedef struct EnemyId
 22 {
 23   uint16_t index;
 24   uint16_t generation;
 25 } EnemyId;
 26 
 27 typedef struct EnemyClassConfig
 28 {
 29   float speed;
 30   float health;
 31   float radius;
 32   float maxAcceleration;
 33 } EnemyClassConfig;
 34 
 35 typedef struct Enemy
 36 {
 37   int16_t currentX, currentY;
 38   int16_t nextX, nextY;
 39   Vector2 simPosition;
 40   Vector2 simVelocity;
 41   uint16_t generation;
 42   float startMovingTime;
 43   float damage, futureDamage;
 44   uint8_t enemyType;
 45   uint8_t movePathCount;
 46   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
 47 } Enemy;
 48 
 49 Enemy enemies[ENEMY_MAX_COUNT];
 50 int enemyCount = 0;
 51 
 52 EnemyClassConfig enemyClassConfigs[] = {
 53     [ENEMY_TYPE_MINION] = {.health = 3.0f, .speed = 1.0f, .radius = 0.25f, .maxAcceleration = 1.0f},
 54 };
 55 
 56 void EnemyInit()
 57 {
 58   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 59   {
 60     enemies[i] = (Enemy){0};
 61   }
 62   enemyCount = 0;
 63 }
 64 
 65 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
 66 {
 67   return enemyClassConfigs[enemy->enemyType].speed;
 68 }
 69 
 70 float EnemyGetMaxHealth(Enemy *enemy)
 71 {
 72   return enemyClassConfigs[enemy->enemyType].health;
 73 }
 74 
 75 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
 76 {
 77   int16_t castleX = 0;
 78   int16_t castleY = 0;
 79   int16_t dx = castleX - currentX;
 80   int16_t dy = castleY - currentY;
 81   if (dx == 0 && dy == 0)
 82   {
 83     *nextX = currentX;
 84     *nextY = currentY;
 85     return 1;
 86   }
 87   if (abs(dx) > abs(dy))
 88   {
 89     *nextX = currentX + (dx > 0 ? 1 : -1);
 90     *nextY = currentY;
 91   }
 92   else
 93   {
 94     *nextX = currentX;
 95     *nextY = currentY + (dy > 0 ? 1 : -1);
 96   }
 97   return 0;
 98 }
 99 
100 
101 // this function predicts the movement of the unit for the next deltaT seconds
102 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
103 {
104   const float pointReachedDistance = 0.25f;
105   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
106   const float maxSimStepTime = 0.015625f;
107   
108   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
109   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
110   int16_t nextX = enemy->nextX;
111   int16_t nextY = enemy->nextY;
112   Vector2 position = enemy->simPosition;
113   int passedCount = 0;
114   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
115   {
116     float stepTime = fminf(deltaT - t, maxSimStepTime);
117     Vector2 target = (Vector2){nextX, nextY};
118     float speed = Vector2Length(*velocity);
119     // draw the target position for debugging
120     DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
121     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
122     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
123     {
124       // we reached the target position, let's move to the next waypoint
125       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
126       target = (Vector2){nextX, nextY};
127       // track how many waypoints we passed
128       passedCount++;
129     }
130     
131     // acceleration towards the target
132     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
133     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
134     *velocity = Vector2Add(*velocity, acceleration);
135 
136     // limit the speed to the maximum speed
137     if (speed > maxSpeed)
138     {
139       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
140     }
141 
142     // move the enemy
143     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
144   }
145 
146   if (waypointPassedCount)
147   {
148     (*waypointPassedCount) = passedCount;
149   }
150 
151   return position;
152 }
153 
154 void EnemyDraw()
155 {
156   for (int i = 0; i < enemyCount; i++)
157   {
158     Enemy enemy = enemies[i];
159     if (enemy.enemyType == ENEMY_TYPE_NONE)
160     {
161       continue;
162     }
163 
164     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
165     
166     if (enemy.movePathCount > 0)
167     {
168       Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
169       DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
170     }
171     for (int j = 1; j < enemy.movePathCount; j++)
172     {
173       Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
174       Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
175       DrawLine3D(p, q, GREEN);
176     }
177 
178     switch (enemy.enemyType)
179     {
180     case ENEMY_TYPE_MINION:
181       DrawCubeWires((Vector3){position.x, 0.2f, position.y}, 0.4f, 0.4f, 0.4f, GREEN);
182       break;
183     }
184   }
185 }
186 
187 void EnemyUpdate()
188 {
189   const float castleX = 0;
190   const float castleY = 0;
191   const float maxPathDistance2 = 0.25f * 0.25f;
192   
193   for (int i = 0; i < enemyCount; i++)
194   {
195     Enemy *enemy = &enemies[i];
196     if (enemy->enemyType == ENEMY_TYPE_NONE)
197     {
198       continue;
199     }
200 
201     int waypointPassedCount = 0;
202     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
203     enemy->startMovingTime = gameTime.time;
204     // track path of unit
205     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
206     {
207       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
208       {
209         enemy->movePath[j] = enemy->movePath[j - 1];
210       }
211       enemy->movePath[0] = enemy->simPosition;
212       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
213       {
214         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
215       }
216     }
217 
218     if (waypointPassedCount > 0)
219     {
220       enemy->currentX = enemy->nextX;
221       enemy->currentY = enemy->nextY;
222       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) &&
223         Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
224       {
225         // enemy reached the castle; remove it
226         enemy->enemyType = ENEMY_TYPE_NONE;
227         continue;
228       }
229     }
230   }
231 
232   // handle collisions
233   for (int i = 0; i < enemyCount - 1; i++)
234   {
235     Enemy *enemyA = &enemies[i];
236     if (enemyA->enemyType == ENEMY_TYPE_NONE)
237     {
238       continue;
239     }
240     for (int j = i + 1; j < enemyCount; j++)
241     {
242       Enemy *enemyB = &enemies[j];
243       if (enemyB->enemyType == ENEMY_TYPE_NONE)
244       {
245         continue;
246       }
247       float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition);
248       float radiusA = enemyClassConfigs[enemyA->enemyType].radius;
249       float radiusB = enemyClassConfigs[enemyB->enemyType].radius;
250       float radiusSum = radiusA + radiusB;
251       if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f)
252       {
253         // collision
254         float distance = sqrtf(distanceSqr);
255         float overlap = radiusSum - distance;
256         // move the enemies apart, but softly; if we have a clog of enemies,
257         // moving them perfectly apart can cause them to jitter
258         float positionCorrection = overlap / 5.0f;
259         Vector2 direction = (Vector2){
260             (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection,
261             (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection};
262         enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction);
263         enemyB->simPosition = Vector2Add(enemyB->simPosition, direction);
264       }
265     }
266   }
267 }
268 
269 EnemyId EnemyGetId(Enemy *enemy)
270 {
271   return (EnemyId){enemy - enemies, enemy->generation};
272 }
273 
274 Enemy *EnemyTryResolve(EnemyId enemyId)
275 {
276   if (enemyId.index >= ENEMY_MAX_COUNT)
277   {
278     return 0;
279   }
280   Enemy *enemy = &enemies[enemyId.index];
281   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
282   {
283     return 0;
284   }
285   return enemy;
286 }
287 
288 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
289 {
290   Enemy *spawn = 0;
291   for (int i = 0; i < enemyCount; i++)
292   {
293     Enemy *enemy = &enemies[i];
294     if (enemy->enemyType == ENEMY_TYPE_NONE)
295     {
296       spawn = enemy;
297       break;
298     }
299   }
300 
301   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
302   {
303     spawn = &enemies[enemyCount++];
304   }
305 
306   if (spawn)
307   {
308     spawn->currentX = currentX;
309     spawn->currentY = currentY;
310     spawn->nextX = currentX;
311     spawn->nextY = currentY;
312     spawn->simPosition = (Vector2){currentX, currentY};
313     spawn->simVelocity = (Vector2){0, 0};
314     spawn->enemyType = enemyType;
315     spawn->startMovingTime = gameTime.time;
316     spawn->damage = 0.0f;
317     spawn->futureDamage = 0.0f;
318     spawn->generation++;
319     spawn->movePathCount = 0;
320   }
321 
322   return spawn;
323 }
324 
325 int EnemyAddDamage(Enemy *enemy, float damage)
326 {
327   enemy->damage += damage;
328   if (enemy->damage >= EnemyGetMaxHealth(enemy))
329   {
330     enemy->enemyType = ENEMY_TYPE_NONE;
331     return 1;
332   }
333 
334   return 0;
335 }
336 
337 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
338 {
339   int16_t castleX = 0;
340   int16_t castleY = 0;
341   Enemy* closest = 0;
342   int16_t closestDistance = 0;
343   float range2 = range * range;
344   for (int i = 0; i < enemyCount; i++)
345   {
346     Enemy* enemy = &enemies[i];
347     if (enemy->enemyType == ENEMY_TYPE_NONE)
348     {
349       continue;
350     }
351     float maxHealth = EnemyGetMaxHealth(enemy);
352     if (enemy->futureDamage >= maxHealth)
353     {
354       // ignore enemies that will die soon
355       continue;
356     }
357     int16_t dx = castleX - enemy->currentX;
358     int16_t dy = castleY - enemy->currentY;
359     int16_t distance = abs(dx) + abs(dy);
360     if (!closest || distance < closestDistance)
361     {
362       float tdx = towerX - enemy->currentX;
363       float tdy = towerY - enemy->currentY;
364       float tdistance2 = tdx * tdx + tdy * tdy;
365       if (tdistance2 <= range2)
366       {
367         closest = enemy;
368         closestDistance = distance;
369       }
370     }
371   }
372   return closest;
373 }
374 
375 int EnemyCount()
376 {
377   int count = 0;
378   for (int i = 0; i < enemyCount; i++)
379   {
380     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
381     {
382       count++;
383     }
384   }
385   return count;
386 }
387 
388 //# Projectiles
389 #define PROJECTILE_MAX_COUNT 1200
390 #define PROJECTILE_TYPE_NONE 0
391 #define PROJECTILE_TYPE_BULLET 1
392 
393 typedef struct Projectile
394 {
395   uint8_t projectileType;
396   float shootTime;
397   float arrivalTime;
398   float damage;
399   Vector2 position;
400   Vector2 target;
401   Vector2 directionNormal;
402   EnemyId targetEnemy;
403 } Projectile;
404 
405 Projectile projectiles[PROJECTILE_MAX_COUNT];
406 int projectileCount = 0;
407 
408 void ProjectileInit()
409 {
410   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
411   {
412     projectiles[i] = (Projectile){0};
413   }
414 }
415 
416 void ProjectileDraw()
417 {
418   for (int i = 0; i < projectileCount; i++)
419   {
420     Projectile projectile = projectiles[i];
421     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
422     {
423       continue;
424     }
425     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
426     if (transition >= 1.0f)
427     {
428       continue;
429     }
430     Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition);
431     float x = position.x;
432     float y = position.y;
433     float dx = projectile.directionNormal.x;
434     float dy = projectile.directionNormal.y;
435     for (float d = 1.0f; d > 0.0f; d -= 0.25f)
436     {
437       x -= dx * 0.1f;
438       y -= dy * 0.1f;
439       float size = 0.1f * d;
440       DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED);
441     }
442   }
443 }
444 
445 void ProjectileUpdate()
446 {
447   for (int i = 0; i < projectileCount; i++)
448   {
449     Projectile *projectile = &projectiles[i];
450     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
451     {
452       continue;
453     }
454     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
455     if (transition >= 1.0f)
456     {
457       projectile->projectileType = PROJECTILE_TYPE_NONE;
458       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
459       if (enemy)
460       {
461         EnemyAddDamage(enemy, projectile->damage);
462       }
463       continue;
464     }
465   }
466 }
467 
468 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage)
469 {
470   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
471   {
472     Projectile *projectile = &projectiles[i];
473     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
474     {
475       projectile->projectileType = projectileType;
476       projectile->shootTime = gameTime.time;
477       projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed;
478       projectile->damage = damage;
479       projectile->position = position;
480       projectile->target = target;
481       projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position));
482       projectile->targetEnemy = EnemyGetId(enemy);
483       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
484       return projectile;
485     }
486   }
487   return 0;
488 }
489 
490 //# Towers
491 
492 #define TOWER_MAX_COUNT 400
493 #define TOWER_TYPE_NONE 0
494 #define TOWER_TYPE_BASE 1
495 #define TOWER_TYPE_GUN 2
496 
497 typedef struct Tower
498 {
499   int16_t x, y;
500   uint8_t towerType;
501   float cooldown;
502 } Tower;
503 
504 Tower towers[TOWER_MAX_COUNT];
505 int towerCount = 0;
506 
507 void TowerInit()
508 {
509   for (int i = 0; i < TOWER_MAX_COUNT; i++)
510   {
511     towers[i] = (Tower){0};
512   }
513   towerCount = 0;
514 }
515 
516 Tower *TowerGetAt(int16_t x, int16_t y)
517 {
518   for (int i = 0; i < towerCount; i++)
519   {
520     if (towers[i].x == x && towers[i].y == y)
521     {
522       return &towers[i];
523     }
524   }
525   return 0;
526 }
527 
528 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
529 {
530   if (towerCount >= TOWER_MAX_COUNT)
531   {
532     return 0;
533   }
534 
535   Tower *tower = TowerGetAt(x, y);
536   if (tower)
537   {
538     return 0;
539   }
540 
541   tower = &towers[towerCount++];
542   tower->x = x;
543   tower->y = y;
544   tower->towerType = towerType;
545   return tower;
546 }
547 
548 void TowerDraw()
549 {
550   for (int i = 0; i < towerCount; i++)
551   {
552     Tower tower = towers[i];
553     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
554     switch (tower.towerType)
555     {
556     case TOWER_TYPE_BASE:
557       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
558       break;
559     case TOWER_TYPE_GUN:
560       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
561       break;
562     }
563   }
564 }
565 
566 void TowerGunUpdate(Tower *tower)
567 {
568   if (tower->cooldown <= 0)
569   {
570     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
571     if (enemy)
572     {
573       tower->cooldown = 0.125f;
574       // shoot the enemy; determine future position of the enemy
575       float bulletSpeed = 1.0f;
576       float bulletDamage = 3.0f;
577       Vector2 velocity = enemy->simVelocity;
578       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
579       Vector2 towerPosition = {tower->x, tower->y};
580       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
581       for (int i = 0; i < 8; i++) {
582         velocity = enemy->simVelocity;
583         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
584         float distance = Vector2Distance(towerPosition, futurePosition);
585         float eta2 = distance / bulletSpeed;
586         if (fabs(eta - eta2) < 0.01f) {
587           break;
588         }
589         eta = (eta2 + eta) * 0.5f;
590       }
591       ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, towerPosition, futurePosition, 
592         bulletSpeed, bulletDamage);
593       enemy->futureDamage += bulletDamage;
594     }
595   }
596   else
597   {
598     tower->cooldown -= gameTime.deltaTime;
599   }
600 }
601 
602 void TowerUpdate()
603 {
604   for (int i = 0; i < towerCount; i++)
605   {
606     Tower *tower = &towers[i];
607     switch (tower->towerType)
608     {
609     case TOWER_TYPE_GUN:
610       TowerGunUpdate(tower);
611       break;
612     }
613   }
614 }
615 
616 //# Game
617 
618 float nextSpawnTime = 0.0f;
619 
620 void InitGame()
621 {
622   TowerInit();
623   EnemyInit();
624   ProjectileInit();
625 
626   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
627   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
628   TowerTryAdd(TOWER_TYPE_GUN, -2, 0);
629   EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4);
630 }
631 
632 void GameUpdate()
633 {
634   float dt = GetFrameTime();
635   // cap maximum delta time to 0.1 seconds to prevent large time steps
636   if (dt > 0.1f) dt = 0.1f;
637   gameTime.time += dt;
638   gameTime.deltaTime = dt;
639   EnemyUpdate();
640   TowerUpdate();
641   ProjectileUpdate();
642 
643   // spawn a new enemy every second
644   if (gameTime.time >= nextSpawnTime && EnemyCount() < 50)
645   {
646     nextSpawnTime = gameTime.time + 0.2f;
647     // add a new enemy at the boundary of the map
648     int randValue = GetRandomValue(-5, 5);
649     int randSide = GetRandomValue(0, 3);
650     int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue;
651     int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue;
652     static int alternation = 0;
653     alternation += 1;
654     if (alternation % 3 == 0) {
655       EnemyTryAdd(ENEMY_TYPE_MINION, 0, -5);
656     }
657     else if (alternation % 3 == 1)
658     {
659       EnemyTryAdd(ENEMY_TYPE_MINION, 0, 5);
660     }
661     EnemyTryAdd(ENEMY_TYPE_MINION, x, y);
662   }
663 }
664 
665 int main(void)
666 {
667   int screenWidth, screenHeight;
668   GetPreferredSize(&screenWidth, &screenHeight);
669   InitWindow(screenWidth, screenHeight, "Tower defense");
670   SetTargetFPS(30);
671 
672   Camera3D camera = {0};
673   camera.position = (Vector3){0.0f, 10.0f, -0.5f};
674   camera.target = (Vector3){0.0f, 0.0f, -0.5f};
675   camera.up = (Vector3){0.0f, 0.0f, -1.0f};
676   camera.fovy = 12.0f;
677   camera.projection = CAMERA_ORTHOGRAPHIC;
678 
679   InitGame();
680 
681   while (!WindowShouldClose())
682   {
683     if (IsPaused()) {
684       // canvas is not visible in browser - do nothing
685       continue;
686     }
687     BeginDrawing();
688     ClearBackground(DARKBLUE);
689 
690     BeginMode3D(camera);
691     DrawGrid(10, 1.0f);
692     TowerDraw();
693     EnemyDraw();
694     ProjectileDraw();
695     GameUpdate();
696     EndMode3D();
697 
698     const char *title = "Tower defense tutorial";
699     int titleWidth = MeasureText(title, 20);
700     DrawText(title, (GetScreenWidth()  - titleWidth) * 0.5f + 2, 5 + 2, 20, BLACK);
701     DrawText(title, (GetScreenWidth()  - titleWidth) * 0.5f, 5, 20, WHITE);
702     EndDrawing();
703   }
704 
705   CloseWindow();
706 
707   return 0;
708 }
  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
Wrapup
In this part, we've improved the shooting algorithm to predict enemy positions. The enemies have now health and the movement is now physics based. Moreover, the enemies are now colliding with each other and we can see the path they are taking.
In the next part, we will add path finding for the enemies. I will publish it on the 29th of November.