Simple tower defense tutorial, part 8: Animating units
In the previous part, we added healthbars to towers and enemies. But our enemies are only wired cubes so far. The archers on the towers are currently just simple billboards and they shoot fireballs.
So in this part, we want to change this: The enemies should be orcs and the archers should have a bow and a small shooting animation. The fireballs should be replaced by something that looks more like arrows and these arrows should follow a slight arc when shot.
Orcs
For the orcs, we want to use a simple sprite sheet animation. It will look like this:
We will have 6 frames that we want to use for the walking animation. The weapon is will also be its own animation overlay, so we can later modify the unit - together with a shield and different head.
But let's start with a static billboard sprite for the orc. We move the SpriteUnit struct from the tower_system.c file to the td_main.h file and specify which sprite our orc is using in the enemy.c file. In the tower system, we increase the bullet speed and also use this new projectile type for the fired shots.
  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 //# Variables
  7 GUIState guiState = {0};
  8 GameTime gameTime = {0};
  9 
 10 Level levels[] = {
 11   [0] = {
 12     .state = LEVEL_STATE_BUILDING,
 13     .initialGold = 20,
 14     .waves[0] = {
 15       .enemyType = ENEMY_TYPE_MINION,
 16       .wave = 0,
 17       .count = 10,
 18       .interval = 2.5f,
 19       .delay = 1.0f,
 20       .spawnPosition = {0, 6},
 21     },
 22     .waves[1] = {
 23       .enemyType = ENEMY_TYPE_MINION,
 24       .wave = 1,
 25       .count = 20,
 26       .interval = 1.5f,
 27       .delay = 1.0f,
 28       .spawnPosition = {0, 6},
 29     },
 30     .waves[2] = {
 31       .enemyType = ENEMY_TYPE_MINION,
 32       .wave = 2,
 33       .count = 30,
 34       .interval = 1.2f,
 35       .delay = 1.0f,
 36       .spawnPosition = {0, 6},
 37     }
 38   },
 39 };
 40 
 41 Level *currentLevel = levels;
 42 
 43 //# Game
 44 
 45 void InitLevel(Level *level)
 46 {
 47   TowerInit();
 48   EnemyInit();
 49   ProjectileInit();
 50   ParticleInit();
 51   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
 52 
 53   level->placementMode = 0;
 54   level->state = LEVEL_STATE_BUILDING;
 55   level->nextState = LEVEL_STATE_NONE;
 56   level->playerGold = level->initialGold;
 57 
 58   Camera *camera = &level->camera;
 59   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
 60   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
 61   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
 62   camera->fovy = 10.0f;
 63   camera->projection = CAMERA_ORTHOGRAPHIC;
 64 }
 65 
 66 void DrawLevelHud(Level *level)
 67 {
 68   const char *text = TextFormat("Gold: %d", level->playerGold);
 69   Font font = GetFontDefault();
 70   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
 71   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
 72 }
 73 
 74 void DrawLevelReportLostWave(Level *level)
 75 {
 76   BeginMode3D(level->camera);
 77   DrawGrid(10, 1.0f);
 78   TowerDraw();
 79   EnemyDraw();
 80   ProjectileDraw();
 81   ParticleDraw();
 82   guiState.isBlocked = 0;
 83   EndMode3D();
 84 
 85   TowerDrawHealthBars(level->camera);
 86 
 87   const char *text = "Wave lost";
 88   int textWidth = MeasureText(text, 20);
 89   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
 90 
 91   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
 92   {
 93     level->nextState = LEVEL_STATE_RESET;
 94   }
 95 }
 96 
 97 int HasLevelNextWave(Level *level)
 98 {
 99   for (int i = 0; i < 10; i++)
100   {
101     EnemyWave *wave = &level->waves[i];
102     if (wave->wave == level->currentWave)
103     {
104       return 1;
105     }
106   }
107   return 0;
108 }
109 
110 void DrawLevelReportWonWave(Level *level)
111 {
112   BeginMode3D(level->camera);
113   DrawGrid(10, 1.0f);
114   TowerDraw();
115   EnemyDraw();
116   ProjectileDraw();
117   ParticleDraw();
118   guiState.isBlocked = 0;
119   EndMode3D();
120 
121   TowerDrawHealthBars(level->camera);
122 
123   const char *text = "Wave won";
124   int textWidth = MeasureText(text, 20);
125   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
126 
127 
128   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
129   {
130     level->nextState = LEVEL_STATE_RESET;
131   }
132 
133   if (HasLevelNextWave(level))
134   {
135     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
136     {
137       level->nextState = LEVEL_STATE_BUILDING;
138     }
139   }
140   else {
141     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
142     {
143       level->nextState = LEVEL_STATE_WON_LEVEL;
144     }
145   }
146 }
147 
148 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
149 {
150   static ButtonState buttonStates[8] = {0};
151   int cost = GetTowerCosts(towerType);
152   const char *text = TextFormat("%s: %d", name, cost);
153   buttonStates[towerType].isSelected = level->placementMode == towerType;
154   buttonStates[towerType].isDisabled = level->playerGold < cost;
155   if (Button(text, x, y, width, height, &buttonStates[towerType]))
156   {
157     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
158   }
159 }
160 
161 void DrawLevelBuildingState(Level *level)
162 {
163   BeginMode3D(level->camera);
164   DrawGrid(10, 1.0f);
165   TowerDraw();
166   EnemyDraw();
167   ProjectileDraw();
168   ParticleDraw();
169 
170   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
171   float planeDistance = ray.position.y / -ray.direction.y;
172   float planeX = ray.direction.x * planeDistance + ray.position.x;
173   float planeY = ray.direction.z * planeDistance + ray.position.z;
174   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
175   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
176   if (level->placementMode && !guiState.isBlocked)
177   {
178     DrawCubeWires((Vector3){mapX, 0.2f, mapY}, 1.0f, 0.4f, 1.0f, RED);
179     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
180     {
181       if (TowerTryAdd(level->placementMode, mapX, mapY))
182       {
183         level->playerGold -= GetTowerCosts(level->placementMode);
184         level->placementMode = TOWER_TYPE_NONE;
185       }
186     }
187   }
188 
189   guiState.isBlocked = 0;
190 
191   EndMode3D();
192 
193   TowerDrawHealthBars(level->camera);
194 
195   static ButtonState buildWallButtonState = {0};
196   static ButtonState buildGunButtonState = {0};
197   buildWallButtonState.isSelected = level->placementMode == TOWER_TYPE_WALL;
198   buildGunButtonState.isSelected = level->placementMode == TOWER_TYPE_GUN;
199 
200   DrawBuildingBuildButton(level, 10, 10, 80, 30, TOWER_TYPE_WALL, "Wall");
201   DrawBuildingBuildButton(level, 10, 50, 80, 30, TOWER_TYPE_GUN, "Gun");
202 
203   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
204   {
205     level->nextState = LEVEL_STATE_RESET;
206   }
207   
208   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
209   {
210     level->nextState = LEVEL_STATE_BATTLE;
211   }
212 
213   const char *text = "Building phase";
214   int textWidth = MeasureText(text, 20);
215   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
216 }
217 
218 void InitBattleStateConditions(Level *level)
219 {
220   level->state = LEVEL_STATE_BATTLE;
221   level->nextState = LEVEL_STATE_NONE;
222   level->waveEndTimer = 0.0f;
223   for (int i = 0; i < 10; i++)
224   {
225     EnemyWave *wave = &level->waves[i];
226     wave->spawned = 0;
227     wave->timeToSpawnNext = wave->delay;
228   }
229 }
230 
231 void DrawLevelBattleState(Level *level)
232 {
233   BeginMode3D(level->camera);
234   DrawGrid(10, 1.0f);
235   TowerDraw();
236   EnemyDraw();
237   ProjectileDraw();
238   ParticleDraw();
239   guiState.isBlocked = 0;
240   EndMode3D();
241 
242   EnemyDrawHealthbars(level->camera);
243   TowerDrawHealthBars(level->camera);
244 
245   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
246   {
247     level->nextState = LEVEL_STATE_RESET;
248   }
249 
250   int maxCount = 0;
251   int remainingCount = 0;
252   for (int i = 0; i < 10; i++)
253   {
254     EnemyWave *wave = &level->waves[i];
255     if (wave->wave != level->currentWave)
256     {
257       continue;
258     }
259     maxCount += wave->count;
260     remainingCount += wave->count - wave->spawned;
261   }
262   int aliveCount = EnemyCount();
263   remainingCount += aliveCount;
264 
265   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
266   int textWidth = MeasureText(text, 20);
267   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
268 }
269 
270 void DrawLevel(Level *level)
271 {
272   switch (level->state)
273   {
274     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
275     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
276     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
277     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
278     default: break;
279   }
280 
281   DrawLevelHud(level);
282 }
283 
284 void UpdateLevel(Level *level)
285 {
286   if (level->state == LEVEL_STATE_BATTLE)
287   {
288     int activeWaves = 0;
289     for (int i = 0; i < 10; i++)
290     {
291       EnemyWave *wave = &level->waves[i];
292       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
293       {
294         continue;
295       }
296       activeWaves++;
297       wave->timeToSpawnNext -= gameTime.deltaTime;
298       if (wave->timeToSpawnNext <= 0.0f)
299       {
300         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
301         if (enemy)
302         {
303           wave->timeToSpawnNext = wave->interval;
304           wave->spawned++;
305         }
306       }
307     }
308     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
309       level->waveEndTimer += gameTime.deltaTime;
310       if (level->waveEndTimer >= 2.0f)
311       {
312         level->nextState = LEVEL_STATE_LOST_WAVE;
313       }
314     }
315     else if (activeWaves == 0 && EnemyCount() == 0)
316     {
317       level->waveEndTimer += gameTime.deltaTime;
318       if (level->waveEndTimer >= 2.0f)
319       {
320         level->nextState = LEVEL_STATE_WON_WAVE;
321       }
322     }
323   }
324 
325   PathFindingMapUpdate();
326   EnemyUpdate();
327   TowerUpdate();
328   ProjectileUpdate();
329   ParticleUpdate();
330 
331   if (level->nextState == LEVEL_STATE_RESET)
332   {
333     InitLevel(level);
334   }
335   
336   if (level->nextState == LEVEL_STATE_BATTLE)
337   {
338     InitBattleStateConditions(level);
339   }
340   
341   if (level->nextState == LEVEL_STATE_WON_WAVE)
342   {
343     level->currentWave++;
344     level->state = LEVEL_STATE_WON_WAVE;
345   }
346   
347   if (level->nextState == LEVEL_STATE_LOST_WAVE)
348   {
349     level->state = LEVEL_STATE_LOST_WAVE;
350   }
351 
352   if (level->nextState == LEVEL_STATE_BUILDING)
353   {
354     level->state = LEVEL_STATE_BUILDING;
355   }
356 
357   if (level->nextState == LEVEL_STATE_WON_LEVEL)
358   {
359     // make something of this later
360     InitLevel(level);
361   }
362 
363   level->nextState = LEVEL_STATE_NONE;
364 }
365 
366 float nextSpawnTime = 0.0f;
367 
368 void ResetGame()
369 {
370   InitLevel(currentLevel);
371 }
372 
373 void InitGame()
374 {
375   TowerInit();
376   EnemyInit();
377   ProjectileInit();
378   ParticleInit();
379   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
380 
381   currentLevel = levels;
382   InitLevel(currentLevel);
383 }
384 
385 //# Immediate GUI functions
386 
387 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor)
388 {
389   const float healthBarWidth = 40.0f;
390   const float healthBarHeight = 6.0f;
391   const float healthBarOffset = 15.0f;
392   const float inset = 2.0f;
393   const float innerWidth = healthBarWidth - inset * 2;
394   const float innerHeight = healthBarHeight - inset * 2;
395 
396   Vector2 screenPos = GetWorldToScreen(position, camera);
397   float centerX = screenPos.x - healthBarWidth * 0.5f;
398   float topY = screenPos.y - healthBarOffset;
399   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
400   float healthWidth = innerWidth * healthRatio;
401   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
402 }
403 
404 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
405 {
406   Rectangle bounds = {x, y, width, height};
407   int isPressed = 0;
408   int isSelected = state && state->isSelected;
409   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !state->isDisabled)
410   {
411     Color color = isSelected ? DARKGRAY : GRAY;
412     DrawRectangle(x, y, width, height, color);
413     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
414     {
415       isPressed = 1;
416     }
417     guiState.isBlocked = 1;
418   }
419   else
420   {
421     Color color = isSelected ? WHITE : LIGHTGRAY;
422     DrawRectangle(x, y, width, height, color);
423   }
424   Font font = GetFontDefault();
425   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
426   Color textColor = state->isDisabled ? GRAY : BLACK;
427   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
428   return isPressed;
429 }
430 
431 //# Main game loop
432 
433 void GameUpdate()
434 {
435   float dt = GetFrameTime();
436   // cap maximum delta time to 0.1 seconds to prevent large time steps
437   if (dt > 0.1f) dt = 0.1f;
438   gameTime.time += dt;
439   gameTime.deltaTime = dt;
440 
441   UpdateLevel(currentLevel);
442 }
443 
444 int main(void)
445 {
446   int screenWidth, screenHeight;
447   GetPreferredSize(&screenWidth, &screenHeight);
448   InitWindow(screenWidth, screenHeight, "Tower defense");
449   SetTargetFPS(30);
450 
451   InitGame();
452 
453   while (!WindowShouldClose())
454   {
455     if (IsPaused()) {
456       // canvas is not visible in browser - do nothing
457       continue;
458     }
459 
460     BeginDrawing();
461     ClearBackground(DARKBLUE);
462 
463     GameUpdate();
464     DrawLevel(currentLevel);
465 
466     EndDrawing();
467   }
468 
469   CloseWindow();
470 
471   return 0;
472 }  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 //# Declarations
 10 
 11 #define ENEMY_MAX_PATH_COUNT 8
 12 #define ENEMY_MAX_COUNT 400
 13 #define ENEMY_TYPE_NONE 0
 14 #define ENEMY_TYPE_MINION 1
 15 
 16 #define PARTICLE_MAX_COUNT 400
 17 #define PARTICLE_TYPE_NONE 0
 18 #define PARTICLE_TYPE_EXPLOSION 1
 19 
 20 typedef struct Particle
 21 {
 22   uint8_t particleType;
 23   float spawnTime;
 24   float lifetime;
 25   Vector3 position;
 26   Vector3 velocity;
 27 } Particle;
 28 
 29 #define TOWER_MAX_COUNT 400
 30 #define TOWER_TYPE_NONE 0
 31 #define TOWER_TYPE_BASE 1
 32 #define TOWER_TYPE_GUN 2
 33 #define TOWER_TYPE_WALL 3
 34 #define TOWER_TYPE_COUNT 4
 35 
 36 typedef struct Tower
 37 {
 38   int16_t x, y;
 39   uint8_t towerType;
 40   float cooldown;
 41   float damage;
 42 } Tower;
 43 
 44 typedef struct GameTime
 45 {
 46   float time;
 47   float deltaTime;
 48 } GameTime;
 49 
 50 typedef struct ButtonState {
 51   char isSelected;
 52   char isDisabled;
 53 } ButtonState;
 54 
 55 typedef struct GUIState {
 56   int isBlocked;
 57 } GUIState;
 58 
 59 typedef enum LevelState
 60 {
 61   LEVEL_STATE_NONE,
 62   LEVEL_STATE_BUILDING,
 63   LEVEL_STATE_BATTLE,
 64   LEVEL_STATE_WON_WAVE,
 65   LEVEL_STATE_LOST_WAVE,
 66   LEVEL_STATE_WON_LEVEL,
 67   LEVEL_STATE_RESET,
 68 } LevelState;
 69 
 70 typedef struct EnemyWave {
 71   uint8_t enemyType;
 72   uint8_t wave;
 73   uint16_t count;
 74   float interval;
 75   float delay;
 76   Vector2 spawnPosition;
 77 
 78   uint16_t spawned;
 79   float timeToSpawnNext;
 80 } EnemyWave;
 81 
 82 typedef struct Level
 83 {
 84   LevelState state;
 85   LevelState nextState;
 86   Camera3D camera;
 87   int placementMode;
 88 
 89   int initialGold;
 90   int playerGold;
 91 
 92   EnemyWave waves[10];
 93   int currentWave;
 94   float waveEndTimer;
 95 } Level;
 96 
 97 typedef struct DeltaSrc
 98 {
 99   char x, y;
100 } DeltaSrc;
101 
102 typedef struct PathfindingMap
103 {
104   int width, height;
105   float scale;
106   float *distances;
107   long *towerIndex; 
108   DeltaSrc *deltaSrc;
109   float maxDistance;
110   Matrix toMapSpace;
111   Matrix toWorldSpace;
112 } PathfindingMap;
113 
114 // when we execute the pathfinding algorithm, we need to store the active nodes
115 // in a queue. Each node has a position, a distance from the start, and the
116 // position of the node that we came from.
117 typedef struct PathfindingNode
118 {
119   int16_t x, y, fromX, fromY;
120   float distance;
121 } PathfindingNode;
122 
123 typedef struct EnemyId
124 {
125   uint16_t index;
126   uint16_t generation;
127 } EnemyId;
128 
129 typedef struct EnemyClassConfig
130 {
131   float speed;
132   float health;
133   float radius;
134   float maxAcceleration;
135   float requiredContactTime;
136   float explosionDamage;
137   float explosionRange;
138   float explosionPushbackPower;
139   int goldValue;
140 } EnemyClassConfig;
141 
142 typedef struct Enemy
143 {
144   int16_t currentX, currentY;
145   int16_t nextX, nextY;
146   Vector2 simPosition;
147   Vector2 simVelocity;
148   uint16_t generation;
149   float startMovingTime;
150   float damage, futureDamage;
151   float contactTime;
152   uint8_t enemyType;
153   uint8_t movePathCount;
154   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
155 } Enemy;
156 
157 // a unit that uses sprites to be drawn
158 typedef struct SpriteUnit
159 {
160   Rectangle srcRect;
161   Vector2 offset;
162 } SpriteUnit;
163 
164 #define PROJECTILE_MAX_COUNT 1200
165 #define PROJECTILE_TYPE_NONE 0
166 #define PROJECTILE_TYPE_BULLET 1
167 
168 typedef struct Projectile
169 {
170   uint8_t projectileType;
171   float shootTime;
172   float arrivalTime;
173   float damage;
174   Vector2 position;
175   Vector2 target;
176   Vector2 directionNormal;
177   EnemyId targetEnemy;
178 } Projectile;
179 
180 //# Function declarations
181 float TowerGetMaxHealth(Tower *tower);
182 int Button(const char *text, int x, int y, int width, int height, ButtonState *state);
183 int EnemyAddDamage(Enemy *enemy, float damage);
184 
185 //# Enemy functions
186 void EnemyInit();
187 void EnemyDraw();
188 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource);
189 void EnemyUpdate();
190 float EnemyGetCurrentMaxSpeed(Enemy *enemy);
191 float EnemyGetMaxHealth(Enemy *enemy);
192 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY);
193 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount);
194 EnemyId EnemyGetId(Enemy *enemy);
195 Enemy *EnemyTryResolve(EnemyId enemyId);
196 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY);
197 int EnemyAddDamage(Enemy *enemy, float damage);
198 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range);
199 int EnemyCount();
200 void EnemyDrawHealthbars(Camera3D camera);
201 
202 //# Tower functions
203 void TowerInit();
204 Tower *TowerGetAt(int16_t x, int16_t y);
205 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y);
206 Tower *GetTowerByType(uint8_t towerType);
207 int GetTowerCosts(uint8_t towerType);
208 float TowerGetMaxHealth(Tower *tower);
209 void TowerDraw();
210 void TowerUpdate();
211 void TowerDrawHealthBars(Camera3D camera);
212 void DrawSpriteUnit(SpriteUnit unit, Vector3 position);
213 
214 //# Particles
215 void ParticleInit();
216 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime);
217 void ParticleUpdate();
218 void ParticleDraw();
219 
220 //# Projectiles
221 void ProjectileInit();
222 void ProjectileDraw();
223 void ProjectileUpdate();
224 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage);
225 
226 //# Pathfinding map
227 void PathfindingMapInit(int width, int height, Vector3 translate, float scale);
228 float PathFindingGetDistance(int mapX, int mapY);
229 Vector2 PathFindingGetGradient(Vector3 world);
230 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY);
231 void PathFindingMapUpdate();
232 void PathFindingMapDraw();
233 
234 //# UI
235 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor);
236 
237 //# variables
238 extern Level *currentLevel;
239 extern Enemy enemies[ENEMY_MAX_COUNT];
240 extern int enemyCount;
241 extern EnemyClassConfig enemyClassConfigs[];
242 
243 extern GUIState guiState;
244 extern GameTime gameTime;
245 extern Tower towers[TOWER_MAX_COUNT];
246 extern int towerCount;
247 
248 #endif  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 EnemyClassConfig enemyClassConfigs[] = {
  7     [ENEMY_TYPE_MINION] = {
  8       .health = 10.0f, 
  9       .speed = 0.6f, 
 10       .radius = 0.25f, 
 11       .maxAcceleration = 1.0f,
 12       .explosionDamage = 1.0f,
 13       .requiredContactTime = 0.5f,
 14       .explosionRange = 1.0f,
 15       .explosionPushbackPower = 0.25f,
 16       .goldValue = 1,
 17     },
 18 };
 19 
 20 Enemy enemies[ENEMY_MAX_COUNT];
 21 int enemyCount = 0;
 22 
 23 SpriteUnit enemySprites[] = {
 24     [ENEMY_TYPE_MINION] = {
 25       .srcRect = {0, 16, 16, 16},
 26       .offset = {8.0f, 0.0f},
 27     },
 28 };
 29 
 30 void EnemyInit()
 31 {
 32   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 33   {
 34     enemies[i] = (Enemy){0};
 35   }
 36   enemyCount = 0;
 37 }
 38 
 39 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
 40 {
 41   return enemyClassConfigs[enemy->enemyType].speed;
 42 }
 43 
 44 float EnemyGetMaxHealth(Enemy *enemy)
 45 {
 46   return enemyClassConfigs[enemy->enemyType].health;
 47 }
 48 
 49 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
 50 {
 51   int16_t castleX = 0;
 52   int16_t castleY = 0;
 53   int16_t dx = castleX - currentX;
 54   int16_t dy = castleY - currentY;
 55   if (dx == 0 && dy == 0)
 56   {
 57     *nextX = currentX;
 58     *nextY = currentY;
 59     return 1;
 60   }
 61   Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY});
 62 
 63   if (gradient.x == 0 && gradient.y == 0)
 64   {
 65     *nextX = currentX;
 66     *nextY = currentY;
 67     return 1;
 68   }
 69 
 70   if (fabsf(gradient.x) > fabsf(gradient.y))
 71   {
 72     *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1);
 73     *nextY = currentY;
 74     return 0;
 75   }
 76   *nextX = currentX;
 77   *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1);
 78   return 0;
 79 }
 80 
 81 
 82 // this function predicts the movement of the unit for the next deltaT seconds
 83 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
 84 {
 85   const float pointReachedDistance = 0.25f;
 86   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
 87   const float maxSimStepTime = 0.015625f;
 88   
 89   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
 90   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
 91   int16_t nextX = enemy->nextX;
 92   int16_t nextY = enemy->nextY;
 93   Vector2 position = enemy->simPosition;
 94   int passedCount = 0;
 95   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
 96   {
 97     float stepTime = fminf(deltaT - t, maxSimStepTime);
 98     Vector2 target = (Vector2){nextX, nextY};
 99     float speed = Vector2Length(*velocity);
100     // draw the target position for debugging
101     DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
102     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
103     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
104     {
105       // we reached the target position, let's move to the next waypoint
106       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
107       target = (Vector2){nextX, nextY};
108       // track how many waypoints we passed
109       passedCount++;
110     }
111     
112     // acceleration towards the target
113     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
114     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
115     *velocity = Vector2Add(*velocity, acceleration);
116 
117     // limit the speed to the maximum speed
118     if (speed > maxSpeed)
119     {
120       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
121     }
122 
123     // move the enemy
124     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
125   }
126 
127   if (waypointPassedCount)
128   {
129     (*waypointPassedCount) = passedCount;
130   }
131 
132   return position;
133 }
134 
135 void EnemyDraw()
136 {
137   for (int i = 0; i < enemyCount; i++)
138   {
139     Enemy enemy = enemies[i];
140     if (enemy.enemyType == ENEMY_TYPE_NONE)
141     {
142       continue;
143     }
144 
145     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
146     
147     if (enemy.movePathCount > 0)
148     {
149       Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
150       DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
151     }
152     for (int j = 1; j < enemy.movePathCount; j++)
153     {
154       Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
155       Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
156       DrawLine3D(p, q, GREEN);
157     }
158 
159     switch (enemy.enemyType)
160     {
161     case ENEMY_TYPE_MINION:
162       DrawSpriteUnit(enemySprites[ENEMY_TYPE_MINION], (Vector3){position.x, 0.0f, position.y});
163       break;
164     }
165   }
166 }
167 
168 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource)
169 {
170   // damage the tower
171   float explosionDamge = enemyClassConfigs[enemy->enemyType].explosionDamage;
172   float explosionRange = enemyClassConfigs[enemy->enemyType].explosionRange;
173   float explosionPushbackPower = enemyClassConfigs[enemy->enemyType].explosionPushbackPower;
174   float explosionRange2 = explosionRange * explosionRange;
175   tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage;
176   // explode the enemy
177   if (tower->damage >= TowerGetMaxHealth(tower))
178   {
179     tower->towerType = TOWER_TYPE_NONE;
180   }
181 
182   ParticleAdd(PARTICLE_TYPE_EXPLOSION, 
183     explosionSource, 
184     (Vector3){0, 0.1f, 0}, 1.0f);
185 
186   enemy->enemyType = ENEMY_TYPE_NONE;
187 
188   // push back enemies & dealing damage
189   for (int i = 0; i < enemyCount; i++)
190   {
191     Enemy *other = &enemies[i];
192     if (other->enemyType == ENEMY_TYPE_NONE)
193     {
194       continue;
195     }
196     float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition);
197     if (distanceSqr > 0 && distanceSqr < explosionRange2)
198     {
199       Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition));
200       other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower));
201       EnemyAddDamage(other, explosionDamge);
202     }
203   }
204 }
205 
206 void EnemyUpdate()
207 {
208   const float castleX = 0;
209   const float castleY = 0;
210   const float maxPathDistance2 = 0.25f * 0.25f;
211   
212   for (int i = 0; i < enemyCount; i++)
213   {
214     Enemy *enemy = &enemies[i];
215     if (enemy->enemyType == ENEMY_TYPE_NONE)
216     {
217       continue;
218     }
219 
220     int waypointPassedCount = 0;
221     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
222     enemy->startMovingTime = gameTime.time;
223     // track path of unit
224     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
225     {
226       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
227       {
228         enemy->movePath[j] = enemy->movePath[j - 1];
229       }
230       enemy->movePath[0] = enemy->simPosition;
231       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
232       {
233         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
234       }
235     }
236 
237     if (waypointPassedCount > 0)
238     {
239       enemy->currentX = enemy->nextX;
240       enemy->currentY = enemy->nextY;
241       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) &&
242         Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
243       {
244         // enemy reached the castle; remove it
245         enemy->enemyType = ENEMY_TYPE_NONE;
246         continue;
247       }
248     }
249   }
250 
251   // handle collisions between enemies
252   for (int i = 0; i < enemyCount - 1; i++)
253   {
254     Enemy *enemyA = &enemies[i];
255     if (enemyA->enemyType == ENEMY_TYPE_NONE)
256     {
257       continue;
258     }
259     for (int j = i + 1; j < enemyCount; j++)
260     {
261       Enemy *enemyB = &enemies[j];
262       if (enemyB->enemyType == ENEMY_TYPE_NONE)
263       {
264         continue;
265       }
266       float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition);
267       float radiusA = enemyClassConfigs[enemyA->enemyType].radius;
268       float radiusB = enemyClassConfigs[enemyB->enemyType].radius;
269       float radiusSum = radiusA + radiusB;
270       if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f)
271       {
272         // collision
273         float distance = sqrtf(distanceSqr);
274         float overlap = radiusSum - distance;
275         // move the enemies apart, but softly; if we have a clog of enemies,
276         // moving them perfectly apart can cause them to jitter
277         float positionCorrection = overlap / 5.0f;
278         Vector2 direction = (Vector2){
279             (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection,
280             (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection};
281         enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction);
282         enemyB->simPosition = Vector2Add(enemyB->simPosition, direction);
283       }
284     }
285   }
286 
287   // handle collisions between enemies and towers
288   for (int i = 0; i < enemyCount; i++)
289   {
290     Enemy *enemy = &enemies[i];
291     if (enemy->enemyType == ENEMY_TYPE_NONE)
292     {
293       continue;
294     }
295     enemy->contactTime -= gameTime.deltaTime;
296     if (enemy->contactTime < 0.0f)
297     {
298       enemy->contactTime = 0.0f;
299     }
300 
301     float enemyRadius = enemyClassConfigs[enemy->enemyType].radius;
302     // linear search over towers; could be optimized by using path finding tower map,
303     // but for now, we keep it simple
304     for (int j = 0; j < towerCount; j++)
305     {
306       Tower *tower = &towers[j];
307       if (tower->towerType == TOWER_TYPE_NONE)
308       {
309         continue;
310       }
311       float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y});
312       float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1
313       if (distanceSqr > combinedRadius * combinedRadius)
314       {
315         continue;
316       }
317       // potential collision; square / circle intersection
318       float dx = tower->x - enemy->simPosition.x;
319       float dy = tower->y - enemy->simPosition.y;
320       float absDx = fabsf(dx);
321       float absDy = fabsf(dy);
322       Vector3 contactPoint = {0};
323       if (absDx <= 0.5f && absDx <= absDy) {
324         // vertical collision; push the enemy out horizontally
325         float overlap = enemyRadius + 0.5f - absDy;
326         if (overlap < 0.0f)
327         {
328           continue;
329         }
330         float direction = dy > 0.0f ? -1.0f : 1.0f;
331         enemy->simPosition.y += direction * overlap;
332         contactPoint = (Vector3){enemy->simPosition.x, 0.2f, tower->y + direction * 0.5f};
333       }
334       else if (absDy <= 0.5f && absDy <= absDx)
335       {
336         // horizontal collision; push the enemy out vertically
337         float overlap = enemyRadius + 0.5f - absDx;
338         if (overlap < 0.0f)
339         {
340           continue;
341         }
342         float direction = dx > 0.0f ? -1.0f : 1.0f;
343         enemy->simPosition.x += direction * overlap;
344         contactPoint = (Vector3){tower->x + direction * 0.5f, 0.2f, enemy->simPosition.y};
345       }
346       else
347       {
348         // possible collision with a corner
349         float cornerDX = dx > 0.0f ? -0.5f : 0.5f;
350         float cornerDY = dy > 0.0f ? -0.5f : 0.5f;
351         float cornerX = tower->x + cornerDX;
352         float cornerY = tower->y + cornerDY;
353         float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY});
354         if (cornerDistanceSqr > enemyRadius * enemyRadius)
355         {
356           continue;
357         }
358         // push the enemy out along the diagonal
359         float cornerDistance = sqrtf(cornerDistanceSqr);
360         float overlap = enemyRadius - cornerDistance;
361         float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX;
362         float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY;
363         enemy->simPosition.x -= directionX * overlap;
364         enemy->simPosition.y -= directionY * overlap;
365         contactPoint = (Vector3){cornerX, 0.2f, cornerY};
366       }
367 
368       if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f)
369       {
370         enemy->contactTime += gameTime.deltaTime * 2.0f; // * 2 to undo the subtraction above
371         if (enemy->contactTime >= enemyClassConfigs[enemy->enemyType].requiredContactTime)
372         {
373           EnemyTriggerExplode(enemy, tower, contactPoint);
374         }
375       }
376     }
377   }
378 }
379 
380 EnemyId EnemyGetId(Enemy *enemy)
381 {
382   return (EnemyId){enemy - enemies, enemy->generation};
383 }
384 
385 Enemy *EnemyTryResolve(EnemyId enemyId)
386 {
387   if (enemyId.index >= ENEMY_MAX_COUNT)
388   {
389     return 0;
390   }
391   Enemy *enemy = &enemies[enemyId.index];
392   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
393   {
394     return 0;
395   }
396   return enemy;
397 }
398 
399 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
400 {
401   Enemy *spawn = 0;
402   for (int i = 0; i < enemyCount; i++)
403   {
404     Enemy *enemy = &enemies[i];
405     if (enemy->enemyType == ENEMY_TYPE_NONE)
406     {
407       spawn = enemy;
408       break;
409     }
410   }
411 
412   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
413   {
414     spawn = &enemies[enemyCount++];
415   }
416 
417   if (spawn)
418   {
419     spawn->currentX = currentX;
420     spawn->currentY = currentY;
421     spawn->nextX = currentX;
422     spawn->nextY = currentY;
423     spawn->simPosition = (Vector2){currentX, currentY};
424     spawn->simVelocity = (Vector2){0, 0};
425     spawn->enemyType = enemyType;
426     spawn->startMovingTime = gameTime.time;
427     spawn->damage = 0.0f;
428     spawn->futureDamage = 0.0f;
429     spawn->generation++;
430     spawn->movePathCount = 0;
431   }
432 
433   return spawn;
434 }
435 
436 int EnemyAddDamage(Enemy *enemy, float damage)
437 {
438   enemy->damage += damage;
439   if (enemy->damage >= EnemyGetMaxHealth(enemy))
440   {
441     currentLevel->playerGold += enemyClassConfigs[enemy->enemyType].goldValue;
442     enemy->enemyType = ENEMY_TYPE_NONE;
443     return 1;
444   }
445 
446   return 0;
447 }
448 
449 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
450 {
451   int16_t castleX = 0;
452   int16_t castleY = 0;
453   Enemy* closest = 0;
454   int16_t closestDistance = 0;
455   float range2 = range * range;
456   for (int i = 0; i < enemyCount; i++)
457   {
458     Enemy* enemy = &enemies[i];
459     if (enemy->enemyType == ENEMY_TYPE_NONE)
460     {
461       continue;
462     }
463     float maxHealth = EnemyGetMaxHealth(enemy);
464     if (enemy->futureDamage >= maxHealth)
465     {
466       // ignore enemies that will die soon
467       continue;
468     }
469     int16_t dx = castleX - enemy->currentX;
470     int16_t dy = castleY - enemy->currentY;
471     int16_t distance = abs(dx) + abs(dy);
472     if (!closest || distance < closestDistance)
473     {
474       float tdx = towerX - enemy->currentX;
475       float tdy = towerY - enemy->currentY;
476       float tdistance2 = tdx * tdx + tdy * tdy;
477       if (tdistance2 <= range2)
478       {
479         closest = enemy;
480         closestDistance = distance;
481       }
482     }
483   }
484   return closest;
485 }
486 
487 int EnemyCount()
488 {
489   int count = 0;
490   for (int i = 0; i < enemyCount; i++)
491   {
492     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
493     {
494       count++;
495     }
496   }
497   return count;
498 }
499 
500 void EnemyDrawHealthbars(Camera3D camera)
501 {
502   for (int i = 0; i < enemyCount; i++)
503   {
504     Enemy *enemy = &enemies[i];
505     if (enemy->enemyType == ENEMY_TYPE_NONE || enemy->damage == 0.0f)
506     {
507       continue;
508     }
509     Vector3 position = (Vector3){enemy->simPosition.x, 0.5f, enemy->simPosition.y};
510     float maxHealth = EnemyGetMaxHealth(enemy);
511     float health = maxHealth - enemy->damage;
512     float healthRatio = health / maxHealth;
513     
514     DrawHealthBar(camera, position, healthRatio, GREEN);
515   }
516 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 Tower towers[TOWER_MAX_COUNT];
  5 int towerCount = 0;
  6 
  7 Model towerModels[TOWER_TYPE_COUNT];
  8 Texture2D palette, spriteSheet;
  9 
 10 // definition of our archer unit
 11 SpriteUnit archerUnit = {
 12     .srcRect = {0, 0, 16, 16},
 13     .offset = {7, 1},
 14 };
 15 
 16 void DrawSpriteUnit(SpriteUnit unit, Vector3 position)
 17 {
 18   Camera3D camera = currentLevel->camera;
 19   float size = 0.5f;
 20   Vector2 offset = (Vector2){ unit.offset.x / 16.0f * size, unit.offset.y / 16.0f * size };
 21   Vector2 scale = (Vector2){ unit.srcRect.width / 16.0f * size, unit.srcRect.height / 16.0f * size };
 22   // we want the sprite to face the camera, so we need to calculate the up vector
 23   Vector3 forward = Vector3Subtract(camera.target, camera.position);
 24   Vector3 up = {0, 1, 0};
 25   Vector3 right = Vector3CrossProduct(forward, up);
 26   up = Vector3Normalize(Vector3CrossProduct(right, forward));
 27   DrawBillboardPro(camera, spriteSheet, unit.srcRect, position, up, scale, offset, 0, WHITE);
 28 }
 29 
 30 void TowerInit()
 31 {
 32   for (int i = 0; i < TOWER_MAX_COUNT; i++)
 33   {
 34     towers[i] = (Tower){0};
 35   }
 36   towerCount = 0;
 37 
 38   // load a sprite sheet that contains all units
 39   spriteSheet = LoadTexture("data/spritesheet.png");
 40 
 41   // we'll use a palette texture to colorize the all buildings and environment art
 42   palette = LoadTexture("data/palette.png");
 43   // The texture uses gradients on very small space, so we'll enable bilinear filtering
 44   SetTextureFilter(palette, TEXTURE_FILTER_BILINEAR);
 45 
 46   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
 47 
 48   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
 49   {
 50     if (towerModels[i].materials)
 51     {
 52       // assign the palette texture to the material of the model (0 is not used afaik)
 53       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 54     }
 55   }
 56 }
 57 
 58 static void TowerGunUpdate(Tower *tower)
 59 {
 60   if (tower->cooldown <= 0)
 61   {
 62     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
 63     if (enemy)
 64     {
 65       tower->cooldown = 0.5f;
 66       // shoot the enemy; determine future position of the enemy
 67       float bulletSpeed = 1.0f;
 68       float bulletDamage = 3.0f;
 69       Vector2 velocity = enemy->simVelocity;
 70       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
 71       Vector2 towerPosition = {tower->x, tower->y};
 72       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
 73       for (int i = 0; i < 8; i++) {
 74         velocity = enemy->simVelocity;
 75         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
 76         float distance = Vector2Distance(towerPosition, futurePosition);
 77         float eta2 = distance / bulletSpeed;
 78         if (fabs(eta - eta2) < 0.01f) {
 79           break;
 80         }
 81         eta = (eta2 + eta) * 0.5f;
 82       }
 83       ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, towerPosition, futurePosition, 
 84         bulletSpeed, bulletDamage);
 85       enemy->futureDamage += bulletDamage;
 86     }
 87   }
 88   else
 89   {
 90     tower->cooldown -= gameTime.deltaTime;
 91   }
 92 }
 93 
 94 Tower *TowerGetAt(int16_t x, int16_t y)
 95 {
 96   for (int i = 0; i < towerCount; i++)
 97   {
 98     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
 99     {
100       return &towers[i];
101     }
102   }
103   return 0;
104 }
105 
106 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
107 {
108   if (towerCount >= TOWER_MAX_COUNT)
109   {
110     return 0;
111   }
112 
113   Tower *tower = TowerGetAt(x, y);
114   if (tower)
115   {
116     return 0;
117   }
118 
119   tower = &towers[towerCount++];
120   tower->x = x;
121   tower->y = y;
122   tower->towerType = towerType;
123   tower->cooldown = 0.0f;
124   tower->damage = 0.0f;
125   return tower;
126 }
127 
128 Tower *GetTowerByType(uint8_t towerType)
129 {
130   for (int i = 0; i < towerCount; i++)
131   {
132     if (towers[i].towerType == towerType)
133     {
134       return &towers[i];
135     }
136   }
137   return 0;
138 }
139 
140 int GetTowerCosts(uint8_t towerType)
141 {
142   switch (towerType)
143   {
144   case TOWER_TYPE_BASE:
145     return 0;
146   case TOWER_TYPE_GUN:
147     return 6;
148   case TOWER_TYPE_WALL:
149     return 2;
150   }
151   return 0;
152 }
153 
154 float TowerGetMaxHealth(Tower *tower)
155 {
156   switch (tower->towerType)
157   {
158   case TOWER_TYPE_BASE:
159     return 10.0f;
160   case TOWER_TYPE_GUN:
161     return 3.0f;
162   case TOWER_TYPE_WALL:
163     return 5.0f;
164   }
165   return 0.0f;
166 }
167 
168 void TowerDraw()
169 {
170   for (int i = 0; i < towerCount; i++)
171   {
172     Tower tower = towers[i];
173     if (tower.towerType == TOWER_TYPE_NONE)
174     {
175       continue;
176     }
177 
178     switch (tower.towerType)
179     {
180     case TOWER_TYPE_BASE:
181       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
182       break;
183     case TOWER_TYPE_GUN:
184       DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
185       DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y});
186       break;
187     default:
188       if (towerModels[tower.towerType].materials)
189       {
190         DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
191       } else {
192         DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
193       }
194       break;
195     }
196   }
197 }
198 
199 void TowerUpdate()
200 {
201   for (int i = 0; i < towerCount; i++)
202   {
203     Tower *tower = &towers[i];
204     switch (tower->towerType)
205     {
206     case TOWER_TYPE_GUN:
207       TowerGunUpdate(tower);
208       break;
209     }
210   }
211 }
212 
213 void TowerDrawHealthBars(Camera3D camera)
214 {
215   for (int i = 0; i < towerCount; i++)
216   {
217     Tower *tower = &towers[i];
218     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
219     {
220       continue;
221     }
222     
223     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
224     float maxHealth = TowerGetMaxHealth(tower);
225     float health = maxHealth - tower->damage;
226     float healthRatio = health / maxHealth;
227     
228     DrawHealthBar(camera, position, healthRatio, GREEN);
229   }
230 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 // The queue is a simple array of nodes, we add nodes to the end and remove
  5 // nodes from the front. We keep the array around to avoid unnecessary allocations
  6 static PathfindingNode *pathfindingNodeQueue = 0;
  7 static int pathfindingNodeQueueCount = 0;
  8 static int pathfindingNodeQueueCapacity = 0;
  9 
 10 // The pathfinding map stores the distances from the castle to each cell in the map.
 11 static PathfindingMap pathfindingMap = {0};
 12 
 13 void PathfindingMapInit(int width, int height, Vector3 translate, float scale)
 14 {
 15   // transforming between map space and world space allows us to adapt 
 16   // position and scale of the map without changing the pathfinding data
 17   pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z);
 18   pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale));
 19   pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace);
 20   pathfindingMap.width = width;
 21   pathfindingMap.height = height;
 22   pathfindingMap.scale = scale;
 23   pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float));
 24   for (int i = 0; i < width * height; i++)
 25   {
 26     pathfindingMap.distances[i] = -1.0f;
 27   }
 28 
 29   pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long));
 30   pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc));
 31 }
 32 
 33 static void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance)
 34 {
 35   if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity)
 36   {
 37     pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2;
 38     // we use MemAlloc/MemRealloc to allocate memory for the queue
 39     // I am not entirely sure if MemRealloc allows passing a null pointer
 40     // so we check if the pointer is null and use MemAlloc in that case
 41     if (pathfindingNodeQueue == 0)
 42     {
 43       pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 44     }
 45     else
 46     {
 47       pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 48     }
 49   }
 50 
 51   PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++];
 52   node->x = x;
 53   node->y = y;
 54   node->fromX = fromX;
 55   node->fromY = fromY;
 56   node->distance = distance;
 57 }
 58 
 59 static PathfindingNode *PathFindingNodePop()
 60 {
 61   if (pathfindingNodeQueueCount == 0)
 62   {
 63     return 0;
 64   }
 65   // we return the first node in the queue; we want to return a pointer to the node
 66   // so we can return 0 if the queue is empty. 
 67   // We should _not_ return a pointer to the element in the list, because the list
 68   // may be reallocated and the pointer would become invalid. Or the 
 69   // popped element is overwritten by the next push operation.
 70   // Using static here means that the variable is permanently allocated.
 71   static PathfindingNode node;
 72   node = pathfindingNodeQueue[0];
 73   // we shift all nodes one position to the front
 74   for (int i = 1; i < pathfindingNodeQueueCount; i++)
 75   {
 76     pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i];
 77   }
 78   --pathfindingNodeQueueCount;
 79   return &node;
 80 }
 81 
 82 float PathFindingGetDistance(int mapX, int mapY)
 83 {
 84   if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height)
 85   {
 86     // when outside the map, we return the manhattan distance to the castle (0,0)
 87     return fabsf((float)mapX) + fabsf((float)mapY);
 88   }
 89 
 90   return pathfindingMap.distances[mapY * pathfindingMap.width + mapX];
 91 }
 92 
 93 // transform a world position to a map position in the array; 
 94 // returns true if the position is inside the map
 95 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY)
 96 {
 97   Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace);
 98   *mapX = (int16_t)mapPosition.x;
 99   *mapY = (int16_t)mapPosition.z;
100   return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height;
101 }
102 
103 void PathFindingMapUpdate()
104 {
105   const int castleX = 0, castleY = 0;
106   int16_t castleMapX, castleMapY;
107   if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY))
108   {
109     return;
110   }
111   int width = pathfindingMap.width, height = pathfindingMap.height;
112 
113   // reset the distances to -1
114   for (int i = 0; i < width * height; i++)
115   {
116     pathfindingMap.distances[i] = -1.0f;
117   }
118   // reset the tower indices
119   for (int i = 0; i < width * height; i++)
120   {
121     pathfindingMap.towerIndex[i] = -1;
122   }
123   // reset the delta src
124   for (int i = 0; i < width * height; i++)
125   {
126     pathfindingMap.deltaSrc[i].x = 0;
127     pathfindingMap.deltaSrc[i].y = 0;
128   }
129 
130   for (int i = 0; i < towerCount; i++)
131   {
132     Tower *tower = &towers[i];
133     if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE)
134     {
135       continue;
136     }
137     int16_t mapX, mapY;
138     // technically, if the tower cell scale is not in sync with the pathfinding map scale,
139     // this would not work correctly and needs to be refined to allow towers covering multiple cells
140     // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly
141     // one cell. For now.
142     if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY))
143     {
144       continue;
145     }
146     int index = mapY * width + mapX;
147     pathfindingMap.towerIndex[index] = i;
148   }
149 
150   // we start at the castle and add the castle to the queue
151   pathfindingMap.maxDistance = 0.0f;
152   pathfindingNodeQueueCount = 0;
153   PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f);
154   PathfindingNode *node = 0;
155   while ((node = PathFindingNodePop()))
156   {
157     if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height)
158     {
159       continue;
160     }
161     int index = node->y * width + node->x;
162     if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance)
163     {
164       continue;
165     }
166 
167     int deltaX = node->x - node->fromX;
168     int deltaY = node->y - node->fromY;
169     // even if the cell is blocked by a tower, we still may want to store the direction
170     // (though this might not be needed, IDK right now)
171     pathfindingMap.deltaSrc[index].x = (char) deltaX;
172     pathfindingMap.deltaSrc[index].y = (char) deltaY;
173 
174     // we skip nodes that are blocked by towers
175     if (pathfindingMap.towerIndex[index] >= 0)
176     {
177       node->distance += 8.0f;
178     }
179     pathfindingMap.distances[index] = node->distance;
180     pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance);
181     PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f);
182     PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f);
183     PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f);
184     PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f);
185   }
186 }
187 
188 void PathFindingMapDraw()
189 {
190   float cellSize = pathfindingMap.scale * 0.9f;
191   float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance);
192   for (int x = 0; x < pathfindingMap.width; x++)
193   {
194     for (int y = 0; y < pathfindingMap.height; y++)
195     {
196       float distance = pathfindingMap.distances[y * pathfindingMap.width + x];
197       float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f);
198       Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255};
199       Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace);
200       // animate the distance "wave" to show how the pathfinding algorithm expands
201       // from the castle
202       if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance)
203       {
204         color = BLACK;
205       }
206       DrawCube(position, cellSize, 0.1f, cellSize, color);
207     }
208   }
209 }
210 
211 Vector2 PathFindingGetGradient(Vector3 world)
212 {
213   int16_t mapX, mapY;
214   if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY))
215   {
216     DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX];
217     return (Vector2){(float)-delta.x, (float)-delta.y};
218   }
219   // fallback to a simple gradient calculation
220   float n = PathFindingGetDistance(mapX, mapY - 1);
221   float s = PathFindingGetDistance(mapX, mapY + 1);
222   float w = PathFindingGetDistance(mapX - 1, mapY);
223   float e = PathFindingGetDistance(mapX + 1, mapY);
224   return (Vector2){w - e + 0.25f, n - s + 0.125f};
225 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static Projectile projectiles[PROJECTILE_MAX_COUNT];
  5 static int projectileCount = 0;
  6 
  7 void ProjectileInit()
  8 {
  9   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
 10   {
 11     projectiles[i] = (Projectile){0};
 12   }
 13 }
 14 
 15 void ProjectileDraw()
 16 {
 17   for (int i = 0; i < projectileCount; i++)
 18   {
 19     Projectile projectile = projectiles[i];
 20     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
 21     {
 22       continue;
 23     }
 24     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
 25     if (transition >= 1.0f)
 26     {
 27       continue;
 28     }
 29     Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition);
 30     float x = position.x;
 31     float y = position.y;
 32     float dx = projectile.directionNormal.x;
 33     float dy = projectile.directionNormal.y;
 34     for (float d = 1.0f; d > 0.0f; d -= 0.25f)
 35     {
 36       x -= dx * 0.1f;
 37       y -= dy * 0.1f;
 38       float size = 0.1f * d;
 39       DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED);
 40     }
 41   }
 42 }
 43 
 44 void ProjectileUpdate()
 45 {
 46   for (int i = 0; i < projectileCount; i++)
 47   {
 48     Projectile *projectile = &projectiles[i];
 49     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
 50     {
 51       continue;
 52     }
 53     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
 54     if (transition >= 1.0f)
 55     {
 56       projectile->projectileType = PROJECTILE_TYPE_NONE;
 57       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
 58       if (enemy)
 59       {
 60         EnemyAddDamage(enemy, projectile->damage);
 61       }
 62       continue;
 63     }
 64   }
 65 }
 66 
 67 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage)
 68 {
 69   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
 70   {
 71     Projectile *projectile = &projectiles[i];
 72     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
 73     {
 74       projectile->projectileType = projectileType;
 75       projectile->shootTime = gameTime.time;
 76       projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed;
 77       projectile->damage = damage;
 78       projectile->position = position;
 79       projectile->target = target;
 80       projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position));
 81       projectile->targetEnemy = EnemyGetId(enemy);
 82       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
 83       return projectile;
 84     }
 85   }
 86   return 0;
 87 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static Particle particles[PARTICLE_MAX_COUNT];
  5 static int particleCount = 0;
  6 
  7 void ParticleInit()
  8 {
  9   for (int i = 0; i < PARTICLE_MAX_COUNT; i++)
 10   {
 11     particles[i] = (Particle){0};
 12   }
 13   particleCount = 0;
 14 }
 15 
 16 static void DrawExplosionParticle(Particle *particle, float transition)
 17 {
 18   float size = 1.2f * (1.0f - transition);
 19   Color startColor = WHITE;
 20   Color endColor = RED;
 21   Color color = ColorLerp(startColor, endColor, transition);
 22   DrawCube(particle->position, size, size, size, color);
 23 }
 24 
 25 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime)
 26 {
 27   if (particleCount >= PARTICLE_MAX_COUNT)
 28   {
 29     return;
 30   }
 31 
 32   int index = -1;
 33   for (int i = 0; i < particleCount; i++)
 34   {
 35     if (particles[i].particleType == PARTICLE_TYPE_NONE)
 36     {
 37       index = i;
 38       break;
 39     }
 40   }
 41 
 42   if (index == -1)
 43   {
 44     index = particleCount++;
 45   }
 46 
 47   Particle *particle = &particles[index];
 48   particle->particleType = particleType;
 49   particle->spawnTime = gameTime.time;
 50   particle->lifetime = lifetime;
 51   particle->position = position;
 52   particle->velocity = velocity;
 53 }
 54 
 55 void ParticleUpdate()
 56 {
 57   for (int i = 0; i < particleCount; i++)
 58   {
 59     Particle *particle = &particles[i];
 60     if (particle->particleType == PARTICLE_TYPE_NONE)
 61     {
 62       continue;
 63     }
 64 
 65     float age = gameTime.time - particle->spawnTime;
 66 
 67     if (particle->lifetime > age)
 68     {
 69       particle->position = Vector3Add(particle->position, Vector3Scale(particle->velocity, gameTime.deltaTime));
 70     }
 71     else {
 72       particle->particleType = PARTICLE_TYPE_NONE;
 73     }
 74   }
 75 }
 76 
 77 void ParticleDraw()
 78 {
 79   for (int i = 0; i < particleCount; i++)
 80   {
 81     Particle particle = particles[i];
 82     if (particle.particleType == PARTICLE_TYPE_NONE)
 83     {
 84       continue;
 85     }
 86 
 87     float age = gameTime.time - particle.spawnTime;
 88     float transition = age / particle.lifetime;
 89     switch (particle.particleType)
 90     {
 91     case PARTICLE_TYPE_EXPLOSION:
 92       DrawExplosionParticle(&particle, transition);
 93       break;
 94     default:
 95       DrawCube(particle.position, 0.3f, 0.5f, 0.3f, RED);
 96       break;
 97     }
 98   }
 99 }  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 #endifClick "Begin waves" to see the enemy spawning as orcs:
Again, a little screenshot:
Neat! But the archers still shoot sort of fireballs and the base is still a red cube. Let's change this to arrows and replace the base with a keep as well. We also introduce a projectile type for arrows in the td_main.h file. The projectiles should also follow a slight arc when shot - which will require us to use some math, but I'll explain this below.
  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 //# Variables
  7 GUIState guiState = {0};
  8 GameTime gameTime = {0};
  9 
 10 Level levels[] = {
 11   [0] = {
 12     .state = LEVEL_STATE_BUILDING,
 13     .initialGold = 20,
 14     .waves[0] = {
 15       .enemyType = ENEMY_TYPE_MINION,
 16       .wave = 0,
 17       .count = 10,
 18       .interval = 2.5f,
 19       .delay = 1.0f,
 20       .spawnPosition = {0, 6},
 21     },
 22     .waves[1] = {
 23       .enemyType = ENEMY_TYPE_MINION,
 24       .wave = 1,
 25       .count = 20,
 26       .interval = 1.5f,
 27       .delay = 1.0f,
 28       .spawnPosition = {0, 6},
 29     },
 30     .waves[2] = {
 31       .enemyType = ENEMY_TYPE_MINION,
 32       .wave = 2,
 33       .count = 30,
 34       .interval = 1.2f,
 35       .delay = 1.0f,
 36       .spawnPosition = {0, 6},
 37     }
 38   },
 39 };
 40 
 41 Level *currentLevel = levels;
 42 
 43 //# Game
 44 
 45 void InitLevel(Level *level)
 46 {
 47   TowerInit();
 48   EnemyInit();
 49   ProjectileInit();
 50   ParticleInit();
 51   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
 52 
 53   level->placementMode = 0;
 54   level->state = LEVEL_STATE_BUILDING;
 55   level->nextState = LEVEL_STATE_NONE;
 56   level->playerGold = level->initialGold;
 57   level->currentWave = 0;
 58 
 59   Camera *camera = &level->camera;
 60   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
 61   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
 62   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
 63   camera->fovy = 10.0f;
 64   camera->projection = CAMERA_ORTHOGRAPHIC;
 65 }
 66 
 67 void DrawLevelHud(Level *level)
 68 {
 69   const char *text = TextFormat("Gold: %d", level->playerGold);
 70   Font font = GetFontDefault();
 71   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
 72   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
 73 }
 74 
 75 void DrawLevelReportLostWave(Level *level)
 76 {
 77   BeginMode3D(level->camera);
 78   DrawGrid(10, 1.0f);
 79   TowerDraw();
 80   EnemyDraw();
 81   ProjectileDraw();
 82   ParticleDraw();
 83   guiState.isBlocked = 0;
 84   EndMode3D();
 85 
 86   TowerDrawHealthBars(level->camera);
 87 
 88   const char *text = "Wave lost";
 89   int textWidth = MeasureText(text, 20);
 90   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
 91 
 92   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
 93   {
 94     level->nextState = LEVEL_STATE_RESET;
 95   }
 96 }
 97 
 98 int HasLevelNextWave(Level *level)
 99 {
100   for (int i = 0; i < 10; i++)
101   {
102     EnemyWave *wave = &level->waves[i];
103     if (wave->wave == level->currentWave)
104     {
105       return 1;
106     }
107   }
108   return 0;
109 }
110 
111 void DrawLevelReportWonWave(Level *level)
112 {
113   BeginMode3D(level->camera);
114   DrawGrid(10, 1.0f);
115   TowerDraw();
116   EnemyDraw();
117   ProjectileDraw();
118   ParticleDraw();
119   guiState.isBlocked = 0;
120   EndMode3D();
121 
122   TowerDrawHealthBars(level->camera);
123 
124   const char *text = "Wave won";
125   int textWidth = MeasureText(text, 20);
126   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
127 
128 
129   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
130   {
131     level->nextState = LEVEL_STATE_RESET;
132   }
133 
134   if (HasLevelNextWave(level))
135   {
136     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
137     {
138       level->nextState = LEVEL_STATE_BUILDING;
139     }
140   }
141   else {
142     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
143     {
144       level->nextState = LEVEL_STATE_WON_LEVEL;
145     }
146   }
147 }
148 
149 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
150 {
151   static ButtonState buttonStates[8] = {0};
152   int cost = GetTowerCosts(towerType);
153   const char *text = TextFormat("%s: %d", name, cost);
154   buttonStates[towerType].isSelected = level->placementMode == towerType;
155   buttonStates[towerType].isDisabled = level->playerGold < cost;
156   if (Button(text, x, y, width, height, &buttonStates[towerType]))
157   {
158     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
159   }
160 }
161 
162 void DrawLevelBuildingState(Level *level)
163 {
164   BeginMode3D(level->camera);
165   DrawGrid(10, 1.0f);
166   TowerDraw();
167   EnemyDraw();
168   ProjectileDraw();
169   ParticleDraw();
170 
171   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
172   float planeDistance = ray.position.y / -ray.direction.y;
173   float planeX = ray.direction.x * planeDistance + ray.position.x;
174   float planeY = ray.direction.z * planeDistance + ray.position.z;
175   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
176   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
177   if (level->placementMode && !guiState.isBlocked)
178   {
179     DrawCubeWires((Vector3){mapX, 0.2f, mapY}, 1.0f, 0.4f, 1.0f, RED);
180     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
181     {
182       if (TowerTryAdd(level->placementMode, mapX, mapY))
183       {
184         level->playerGold -= GetTowerCosts(level->placementMode);
185         level->placementMode = TOWER_TYPE_NONE;
186       }
187     }
188   }
189 
190   guiState.isBlocked = 0;
191 
192   EndMode3D();
193 
194   TowerDrawHealthBars(level->camera);
195 
196   static ButtonState buildWallButtonState = {0};
197   static ButtonState buildGunButtonState = {0};
198   buildWallButtonState.isSelected = level->placementMode == TOWER_TYPE_WALL;
199   buildGunButtonState.isSelected = level->placementMode == TOWER_TYPE_GUN;
200 
201   DrawBuildingBuildButton(level, 10, 10, 80, 30, TOWER_TYPE_WALL, "Wall");
202   DrawBuildingBuildButton(level, 10, 50, 80, 30, TOWER_TYPE_GUN, "Gun");
203 
204   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
205   {
206     level->nextState = LEVEL_STATE_RESET;
207   }
208   
209   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
210   {
211     level->nextState = LEVEL_STATE_BATTLE;
212   }
213 
214   const char *text = "Building phase";
215   int textWidth = MeasureText(text, 20);
216   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
217 }
218 
219 void InitBattleStateConditions(Level *level)
220 {
221   level->state = LEVEL_STATE_BATTLE;
222   level->nextState = LEVEL_STATE_NONE;
223   level->waveEndTimer = 0.0f;
224   for (int i = 0; i < 10; i++)
225   {
226     EnemyWave *wave = &level->waves[i];
227     wave->spawned = 0;
228     wave->timeToSpawnNext = wave->delay;
229   }
230 }
231 
232 void DrawLevelBattleState(Level *level)
233 {
234   BeginMode3D(level->camera);
235   DrawGrid(10, 1.0f);
236   TowerDraw();
237   EnemyDraw();
238   ProjectileDraw();
239   ParticleDraw();
240   guiState.isBlocked = 0;
241   EndMode3D();
242 
243   EnemyDrawHealthbars(level->camera);
244   TowerDrawHealthBars(level->camera);
245 
246   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
247   {
248     level->nextState = LEVEL_STATE_RESET;
249   }
250 
251   int maxCount = 0;
252   int remainingCount = 0;
253   for (int i = 0; i < 10; i++)
254   {
255     EnemyWave *wave = &level->waves[i];
256     if (wave->wave != level->currentWave)
257     {
258       continue;
259     }
260     maxCount += wave->count;
261     remainingCount += wave->count - wave->spawned;
262   }
263   int aliveCount = EnemyCount();
264   remainingCount += aliveCount;
265 
266   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
267   int textWidth = MeasureText(text, 20);
268   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
269 }
270 
271 void DrawLevel(Level *level)
272 {
273   switch (level->state)
274   {
275     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
276     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
277     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
278     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
279     default: break;
280   }
281 
282   DrawLevelHud(level);
283 }
284 
285 void UpdateLevel(Level *level)
286 {
287   if (level->state == LEVEL_STATE_BATTLE)
288   {
289     int activeWaves = 0;
290     for (int i = 0; i < 10; i++)
291     {
292       EnemyWave *wave = &level->waves[i];
293       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
294       {
295         continue;
296       }
297       activeWaves++;
298       wave->timeToSpawnNext -= gameTime.deltaTime;
299       if (wave->timeToSpawnNext <= 0.0f)
300       {
301         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
302         if (enemy)
303         {
304           wave->timeToSpawnNext = wave->interval;
305           wave->spawned++;
306         }
307       }
308     }
309     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
310       level->waveEndTimer += gameTime.deltaTime;
311       if (level->waveEndTimer >= 2.0f)
312       {
313         level->nextState = LEVEL_STATE_LOST_WAVE;
314       }
315     }
316     else if (activeWaves == 0 && EnemyCount() == 0)
317     {
318       level->waveEndTimer += gameTime.deltaTime;
319       if (level->waveEndTimer >= 2.0f)
320       {
321         level->nextState = LEVEL_STATE_WON_WAVE;
322       }
323     }
324   }
325 
326   PathFindingMapUpdate();
327   EnemyUpdate();
328   TowerUpdate();
329   ProjectileUpdate();
330   ParticleUpdate();
331 
332   if (level->nextState == LEVEL_STATE_RESET)
333   {
334     InitLevel(level);
335   }
336   
337   if (level->nextState == LEVEL_STATE_BATTLE)
338   {
339     InitBattleStateConditions(level);
340   }
341   
342   if (level->nextState == LEVEL_STATE_WON_WAVE)
343   {
344     level->currentWave++;
345     level->state = LEVEL_STATE_WON_WAVE;
346   }
347   
348   if (level->nextState == LEVEL_STATE_LOST_WAVE)
349   {
350     level->state = LEVEL_STATE_LOST_WAVE;
351   }
352 
353   if (level->nextState == LEVEL_STATE_BUILDING)
354   {
355     level->state = LEVEL_STATE_BUILDING;
356   }
357 
358   if (level->nextState == LEVEL_STATE_WON_LEVEL)
359   {
360     // make something of this later
361     InitLevel(level);
362   }
363 
364   level->nextState = LEVEL_STATE_NONE;
365 }
366 
367 float nextSpawnTime = 0.0f;
368 
369 void ResetGame()
370 {
371   InitLevel(currentLevel);
372 }
373 
374 void InitGame()
375 {
376   TowerInit();
377   EnemyInit();
378   ProjectileInit();
379   ParticleInit();
380   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
381 
382   currentLevel = levels;
383   InitLevel(currentLevel);
384 }
385 
386 //# Immediate GUI functions
387 
388 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor)
389 {
390   const float healthBarWidth = 40.0f;
391   const float healthBarHeight = 6.0f;
392   const float healthBarOffset = 15.0f;
393   const float inset = 2.0f;
394   const float innerWidth = healthBarWidth - inset * 2;
395   const float innerHeight = healthBarHeight - inset * 2;
396 
397   Vector2 screenPos = GetWorldToScreen(position, camera);
398   float centerX = screenPos.x - healthBarWidth * 0.5f;
399   float topY = screenPos.y - healthBarOffset;
400   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
401   float healthWidth = innerWidth * healthRatio;
402   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
403 }
404 
405 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
406 {
407   Rectangle bounds = {x, y, width, height};
408   int isPressed = 0;
409   int isSelected = state && state->isSelected;
410   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !state->isDisabled)
411   {
412     Color color = isSelected ? DARKGRAY : GRAY;
413     DrawRectangle(x, y, width, height, color);
414     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
415     {
416       isPressed = 1;
417     }
418     guiState.isBlocked = 1;
419   }
420   else
421   {
422     Color color = isSelected ? WHITE : LIGHTGRAY;
423     DrawRectangle(x, y, width, height, color);
424   }
425   Font font = GetFontDefault();
426   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
427   Color textColor = state->isDisabled ? GRAY : BLACK;
428   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
429   return isPressed;
430 }
431 
432 //# Main game loop
433 
434 void GameUpdate()
435 {
436   float dt = GetFrameTime();
437   // cap maximum delta time to 0.1 seconds to prevent large time steps
438   if (dt > 0.1f) dt = 0.1f;
439   gameTime.time += dt;
440   gameTime.deltaTime = dt;
441 
442   UpdateLevel(currentLevel);
443 }
444 
445 int main(void)
446 {
447   int screenWidth, screenHeight;
448   GetPreferredSize(&screenWidth, &screenHeight);
449   InitWindow(screenWidth, screenHeight, "Tower defense");
450   SetTargetFPS(30);
451 
452   InitGame();
453 
454   while (!WindowShouldClose())
455   {
456     if (IsPaused()) {
457       // canvas is not visible in browser - do nothing
458       continue;
459     }
460 
461     BeginDrawing();
462     ClearBackground(DARKBLUE);
463 
464     GameUpdate();
465     DrawLevel(currentLevel);
466 
467     EndDrawing();
468   }
469 
470   CloseWindow();
471 
472   return 0;
473 }  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 //# Declarations
 10 
 11 #define ENEMY_MAX_PATH_COUNT 8
 12 #define ENEMY_MAX_COUNT 400
 13 #define ENEMY_TYPE_NONE 0
 14 #define ENEMY_TYPE_MINION 1
 15 
 16 #define PARTICLE_MAX_COUNT 400
 17 #define PARTICLE_TYPE_NONE 0
 18 #define PARTICLE_TYPE_EXPLOSION 1
 19 
 20 typedef struct Particle
 21 {
 22   uint8_t particleType;
 23   float spawnTime;
 24   float lifetime;
 25   Vector3 position;
 26   Vector3 velocity;
 27 } Particle;
 28 
 29 #define TOWER_MAX_COUNT 400
 30 #define TOWER_TYPE_NONE 0
 31 #define TOWER_TYPE_BASE 1
 32 #define TOWER_TYPE_GUN 2
 33 #define TOWER_TYPE_WALL 3
 34 #define TOWER_TYPE_COUNT 4
 35 
 36 typedef struct Tower
 37 {
 38   int16_t x, y;
 39   uint8_t towerType;
 40   float cooldown;
 41   float damage;
 42 } Tower;
 43 
 44 typedef struct GameTime
 45 {
 46   float time;
 47   float deltaTime;
 48 } GameTime;
 49 
 50 typedef struct ButtonState {
 51   char isSelected;
 52   char isDisabled;
 53 } ButtonState;
 54 
 55 typedef struct GUIState {
 56   int isBlocked;
 57 } GUIState;
 58 
 59 typedef enum LevelState
 60 {
 61   LEVEL_STATE_NONE,
 62   LEVEL_STATE_BUILDING,
 63   LEVEL_STATE_BATTLE,
 64   LEVEL_STATE_WON_WAVE,
 65   LEVEL_STATE_LOST_WAVE,
 66   LEVEL_STATE_WON_LEVEL,
 67   LEVEL_STATE_RESET,
 68 } LevelState;
 69 
 70 typedef struct EnemyWave {
 71   uint8_t enemyType;
 72   uint8_t wave;
 73   uint16_t count;
 74   float interval;
 75   float delay;
 76   Vector2 spawnPosition;
 77 
 78   uint16_t spawned;
 79   float timeToSpawnNext;
 80 } EnemyWave;
 81 
 82 typedef struct Level
 83 {
 84   LevelState state;
 85   LevelState nextState;
 86   Camera3D camera;
 87   int placementMode;
 88 
 89   int initialGold;
 90   int playerGold;
 91 
 92   EnemyWave waves[10];
 93   int currentWave;
 94   float waveEndTimer;
 95 } Level;
 96 
 97 typedef struct DeltaSrc
 98 {
 99   char x, y;
100 } DeltaSrc;
101 
102 typedef struct PathfindingMap
103 {
104   int width, height;
105   float scale;
106   float *distances;
107   long *towerIndex; 
108   DeltaSrc *deltaSrc;
109   float maxDistance;
110   Matrix toMapSpace;
111   Matrix toWorldSpace;
112 } PathfindingMap;
113 
114 // when we execute the pathfinding algorithm, we need to store the active nodes
115 // in a queue. Each node has a position, a distance from the start, and the
116 // position of the node that we came from.
117 typedef struct PathfindingNode
118 {
119   int16_t x, y, fromX, fromY;
120   float distance;
121 } PathfindingNode;
122 
123 typedef struct EnemyId
124 {
125   uint16_t index;
126   uint16_t generation;
127 } EnemyId;
128 
129 typedef struct EnemyClassConfig
130 {
131   float speed;
132   float health;
133   float radius;
134   float maxAcceleration;
135   float requiredContactTime;
136   float explosionDamage;
137   float explosionRange;
138   float explosionPushbackPower;
139   int goldValue;
140 } EnemyClassConfig;
141 
142 typedef struct Enemy
143 {
144   int16_t currentX, currentY;
145   int16_t nextX, nextY;
146   Vector2 simPosition;
147   Vector2 simVelocity;
148   uint16_t generation;
149   float startMovingTime;
150   float damage, futureDamage;
151   float contactTime;
152   uint8_t enemyType;
153   uint8_t movePathCount;
154   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
155 } Enemy;
156 
157 // a unit that uses sprites to be drawn
158 typedef struct SpriteUnit
159 {
160   Rectangle srcRect;
161   Vector2 offset;
162 } SpriteUnit;
163 
164 #define PROJECTILE_MAX_COUNT 1200
165 #define PROJECTILE_TYPE_NONE 0
166 #define PROJECTILE_TYPE_ARROW 1
167 
168 typedef struct Projectile
169 {
170   uint8_t projectileType;
171   float shootTime;
172   float arrivalTime;
173   float distance;
174   float damage;
175   Vector3 position;
176   Vector3 target;
177   Vector3 directionNormal;
178   EnemyId targetEnemy;
179 } Projectile;
180 
181 //# Function declarations
182 float TowerGetMaxHealth(Tower *tower);
183 int Button(const char *text, int x, int y, int width, int height, ButtonState *state);
184 int EnemyAddDamage(Enemy *enemy, float damage);
185 
186 //# Enemy functions
187 void EnemyInit();
188 void EnemyDraw();
189 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource);
190 void EnemyUpdate();
191 float EnemyGetCurrentMaxSpeed(Enemy *enemy);
192 float EnemyGetMaxHealth(Enemy *enemy);
193 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY);
194 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount);
195 EnemyId EnemyGetId(Enemy *enemy);
196 Enemy *EnemyTryResolve(EnemyId enemyId);
197 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY);
198 int EnemyAddDamage(Enemy *enemy, float damage);
199 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range);
200 int EnemyCount();
201 void EnemyDrawHealthbars(Camera3D camera);
202 
203 //# Tower functions
204 void TowerInit();
205 Tower *TowerGetAt(int16_t x, int16_t y);
206 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y);
207 Tower *GetTowerByType(uint8_t towerType);
208 int GetTowerCosts(uint8_t towerType);
209 float TowerGetMaxHealth(Tower *tower);
210 void TowerDraw();
211 void TowerUpdate();
212 void TowerDrawHealthBars(Camera3D camera);
213 void DrawSpriteUnit(SpriteUnit unit, Vector3 position);
214 
215 //# Particles
216 void ParticleInit();
217 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime);
218 void ParticleUpdate();
219 void ParticleDraw();
220 
221 //# Projectiles
222 void ProjectileInit();
223 void ProjectileDraw();
224 void ProjectileUpdate();
225 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, float damage);
226 
227 //# Pathfinding map
228 void PathfindingMapInit(int width, int height, Vector3 translate, float scale);
229 float PathFindingGetDistance(int mapX, int mapY);
230 Vector2 PathFindingGetGradient(Vector3 world);
231 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY);
232 void PathFindingMapUpdate();
233 void PathFindingMapDraw();
234 
235 //# UI
236 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor);
237 
238 //# variables
239 extern Level *currentLevel;
240 extern Enemy enemies[ENEMY_MAX_COUNT];
241 extern int enemyCount;
242 extern EnemyClassConfig enemyClassConfigs[];
243 
244 extern GUIState guiState;
245 extern GameTime gameTime;
246 extern Tower towers[TOWER_MAX_COUNT];
247 extern int towerCount;
248 
249 #endif  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 EnemyClassConfig enemyClassConfigs[] = {
  7     [ENEMY_TYPE_MINION] = {
  8       .health = 10.0f, 
  9       .speed = 0.6f, 
 10       .radius = 0.25f, 
 11       .maxAcceleration = 1.0f,
 12       .explosionDamage = 1.0f,
 13       .requiredContactTime = 0.5f,
 14       .explosionRange = 1.0f,
 15       .explosionPushbackPower = 0.25f,
 16       .goldValue = 1,
 17     },
 18 };
 19 
 20 Enemy enemies[ENEMY_MAX_COUNT];
 21 int enemyCount = 0;
 22 
 23 SpriteUnit enemySprites[] = {
 24     [ENEMY_TYPE_MINION] = {
 25       .srcRect = {0, 16, 16, 16},
 26       .offset = {8.0f, 0.0f},
 27     },
 28 };
 29 
 30 void EnemyInit()
 31 {
 32   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 33   {
 34     enemies[i] = (Enemy){0};
 35   }
 36   enemyCount = 0;
 37 }
 38 
 39 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
 40 {
 41   return enemyClassConfigs[enemy->enemyType].speed;
 42 }
 43 
 44 float EnemyGetMaxHealth(Enemy *enemy)
 45 {
 46   return enemyClassConfigs[enemy->enemyType].health;
 47 }
 48 
 49 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
 50 {
 51   int16_t castleX = 0;
 52   int16_t castleY = 0;
 53   int16_t dx = castleX - currentX;
 54   int16_t dy = castleY - currentY;
 55   if (dx == 0 && dy == 0)
 56   {
 57     *nextX = currentX;
 58     *nextY = currentY;
 59     return 1;
 60   }
 61   Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY});
 62 
 63   if (gradient.x == 0 && gradient.y == 0)
 64   {
 65     *nextX = currentX;
 66     *nextY = currentY;
 67     return 1;
 68   }
 69 
 70   if (fabsf(gradient.x) > fabsf(gradient.y))
 71   {
 72     *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1);
 73     *nextY = currentY;
 74     return 0;
 75   }
 76   *nextX = currentX;
 77   *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1);
 78   return 0;
 79 }
 80 
 81 
 82 // this function predicts the movement of the unit for the next deltaT seconds
 83 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
 84 {
 85   const float pointReachedDistance = 0.25f;
 86   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
 87   const float maxSimStepTime = 0.015625f;
 88   
 89   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
 90   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
 91   int16_t nextX = enemy->nextX;
 92   int16_t nextY = enemy->nextY;
 93   Vector2 position = enemy->simPosition;
 94   int passedCount = 0;
 95   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
 96   {
 97     float stepTime = fminf(deltaT - t, maxSimStepTime);
 98     Vector2 target = (Vector2){nextX, nextY};
 99     float speed = Vector2Length(*velocity);
100     // draw the target position for debugging
101     DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
102     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
103     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
104     {
105       // we reached the target position, let's move to the next waypoint
106       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
107       target = (Vector2){nextX, nextY};
108       // track how many waypoints we passed
109       passedCount++;
110     }
111     
112     // acceleration towards the target
113     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
114     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
115     *velocity = Vector2Add(*velocity, acceleration);
116 
117     // limit the speed to the maximum speed
118     if (speed > maxSpeed)
119     {
120       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
121     }
122 
123     // move the enemy
124     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
125   }
126 
127   if (waypointPassedCount)
128   {
129     (*waypointPassedCount) = passedCount;
130   }
131 
132   return position;
133 }
134 
135 void EnemyDraw()
136 {
137   for (int i = 0; i < enemyCount; i++)
138   {
139     Enemy enemy = enemies[i];
140     if (enemy.enemyType == ENEMY_TYPE_NONE)
141     {
142       continue;
143     }
144 
145     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
146     
147     if (enemy.movePathCount > 0)
148     {
149       Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
150       DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
151     }
152     for (int j = 1; j < enemy.movePathCount; j++)
153     {
154       Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
155       Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
156       DrawLine3D(p, q, GREEN);
157     }
158 
159     switch (enemy.enemyType)
160     {
161     case ENEMY_TYPE_MINION:
162       DrawSpriteUnit(enemySprites[ENEMY_TYPE_MINION], (Vector3){position.x, 0.0f, position.y});
163       break;
164     }
165   }
166 }
167 
168 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource)
169 {
170   // damage the tower
171   float explosionDamge = enemyClassConfigs[enemy->enemyType].explosionDamage;
172   float explosionRange = enemyClassConfigs[enemy->enemyType].explosionRange;
173   float explosionPushbackPower = enemyClassConfigs[enemy->enemyType].explosionPushbackPower;
174   float explosionRange2 = explosionRange * explosionRange;
175   tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage;
176   // explode the enemy
177   if (tower->damage >= TowerGetMaxHealth(tower))
178   {
179     tower->towerType = TOWER_TYPE_NONE;
180   }
181 
182   ParticleAdd(PARTICLE_TYPE_EXPLOSION, 
183     explosionSource, 
184     (Vector3){0, 0.1f, 0}, 1.0f);
185 
186   enemy->enemyType = ENEMY_TYPE_NONE;
187 
188   // push back enemies & dealing damage
189   for (int i = 0; i < enemyCount; i++)
190   {
191     Enemy *other = &enemies[i];
192     if (other->enemyType == ENEMY_TYPE_NONE)
193     {
194       continue;
195     }
196     float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition);
197     if (distanceSqr > 0 && distanceSqr < explosionRange2)
198     {
199       Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition));
200       other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower));
201       EnemyAddDamage(other, explosionDamge);
202     }
203   }
204 }
205 
206 void EnemyUpdate()
207 {
208   const float castleX = 0;
209   const float castleY = 0;
210   const float maxPathDistance2 = 0.25f * 0.25f;
211   
212   for (int i = 0; i < enemyCount; i++)
213   {
214     Enemy *enemy = &enemies[i];
215     if (enemy->enemyType == ENEMY_TYPE_NONE)
216     {
217       continue;
218     }
219 
220     int waypointPassedCount = 0;
221     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
222     enemy->startMovingTime = gameTime.time;
223     // track path of unit
224     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
225     {
226       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
227       {
228         enemy->movePath[j] = enemy->movePath[j - 1];
229       }
230       enemy->movePath[0] = enemy->simPosition;
231       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
232       {
233         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
234       }
235     }
236 
237     if (waypointPassedCount > 0)
238     {
239       enemy->currentX = enemy->nextX;
240       enemy->currentY = enemy->nextY;
241       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) &&
242         Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
243       {
244         // enemy reached the castle; remove it
245         enemy->enemyType = ENEMY_TYPE_NONE;
246         continue;
247       }
248     }
249   }
250 
251   // handle collisions between enemies
252   for (int i = 0; i < enemyCount - 1; i++)
253   {
254     Enemy *enemyA = &enemies[i];
255     if (enemyA->enemyType == ENEMY_TYPE_NONE)
256     {
257       continue;
258     }
259     for (int j = i + 1; j < enemyCount; j++)
260     {
261       Enemy *enemyB = &enemies[j];
262       if (enemyB->enemyType == ENEMY_TYPE_NONE)
263       {
264         continue;
265       }
266       float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition);
267       float radiusA = enemyClassConfigs[enemyA->enemyType].radius;
268       float radiusB = enemyClassConfigs[enemyB->enemyType].radius;
269       float radiusSum = radiusA + radiusB;
270       if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f)
271       {
272         // collision
273         float distance = sqrtf(distanceSqr);
274         float overlap = radiusSum - distance;
275         // move the enemies apart, but softly; if we have a clog of enemies,
276         // moving them perfectly apart can cause them to jitter
277         float positionCorrection = overlap / 5.0f;
278         Vector2 direction = (Vector2){
279             (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection,
280             (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection};
281         enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction);
282         enemyB->simPosition = Vector2Add(enemyB->simPosition, direction);
283       }
284     }
285   }
286 
287   // handle collisions between enemies and towers
288   for (int i = 0; i < enemyCount; i++)
289   {
290     Enemy *enemy = &enemies[i];
291     if (enemy->enemyType == ENEMY_TYPE_NONE)
292     {
293       continue;
294     }
295     enemy->contactTime -= gameTime.deltaTime;
296     if (enemy->contactTime < 0.0f)
297     {
298       enemy->contactTime = 0.0f;
299     }
300 
301     float enemyRadius = enemyClassConfigs[enemy->enemyType].radius;
302     // linear search over towers; could be optimized by using path finding tower map,
303     // but for now, we keep it simple
304     for (int j = 0; j < towerCount; j++)
305     {
306       Tower *tower = &towers[j];
307       if (tower->towerType == TOWER_TYPE_NONE)
308       {
309         continue;
310       }
311       float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y});
312       float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1
313       if (distanceSqr > combinedRadius * combinedRadius)
314       {
315         continue;
316       }
317       // potential collision; square / circle intersection
318       float dx = tower->x - enemy->simPosition.x;
319       float dy = tower->y - enemy->simPosition.y;
320       float absDx = fabsf(dx);
321       float absDy = fabsf(dy);
322       Vector3 contactPoint = {0};
323       if (absDx <= 0.5f && absDx <= absDy) {
324         // vertical collision; push the enemy out horizontally
325         float overlap = enemyRadius + 0.5f - absDy;
326         if (overlap < 0.0f)
327         {
328           continue;
329         }
330         float direction = dy > 0.0f ? -1.0f : 1.0f;
331         enemy->simPosition.y += direction * overlap;
332         contactPoint = (Vector3){enemy->simPosition.x, 0.2f, tower->y + direction * 0.5f};
333       }
334       else if (absDy <= 0.5f && absDy <= absDx)
335       {
336         // horizontal collision; push the enemy out vertically
337         float overlap = enemyRadius + 0.5f - absDx;
338         if (overlap < 0.0f)
339         {
340           continue;
341         }
342         float direction = dx > 0.0f ? -1.0f : 1.0f;
343         enemy->simPosition.x += direction * overlap;
344         contactPoint = (Vector3){tower->x + direction * 0.5f, 0.2f, enemy->simPosition.y};
345       }
346       else
347       {
348         // possible collision with a corner
349         float cornerDX = dx > 0.0f ? -0.5f : 0.5f;
350         float cornerDY = dy > 0.0f ? -0.5f : 0.5f;
351         float cornerX = tower->x + cornerDX;
352         float cornerY = tower->y + cornerDY;
353         float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY});
354         if (cornerDistanceSqr > enemyRadius * enemyRadius)
355         {
356           continue;
357         }
358         // push the enemy out along the diagonal
359         float cornerDistance = sqrtf(cornerDistanceSqr);
360         float overlap = enemyRadius - cornerDistance;
361         float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX;
362         float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY;
363         enemy->simPosition.x -= directionX * overlap;
364         enemy->simPosition.y -= directionY * overlap;
365         contactPoint = (Vector3){cornerX, 0.2f, cornerY};
366       }
367 
368       if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f)
369       {
370         enemy->contactTime += gameTime.deltaTime * 2.0f; // * 2 to undo the subtraction above
371         if (enemy->contactTime >= enemyClassConfigs[enemy->enemyType].requiredContactTime)
372         {
373           EnemyTriggerExplode(enemy, tower, contactPoint);
374         }
375       }
376     }
377   }
378 }
379 
380 EnemyId EnemyGetId(Enemy *enemy)
381 {
382   return (EnemyId){enemy - enemies, enemy->generation};
383 }
384 
385 Enemy *EnemyTryResolve(EnemyId enemyId)
386 {
387   if (enemyId.index >= ENEMY_MAX_COUNT)
388   {
389     return 0;
390   }
391   Enemy *enemy = &enemies[enemyId.index];
392   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
393   {
394     return 0;
395   }
396   return enemy;
397 }
398 
399 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
400 {
401   Enemy *spawn = 0;
402   for (int i = 0; i < enemyCount; i++)
403   {
404     Enemy *enemy = &enemies[i];
405     if (enemy->enemyType == ENEMY_TYPE_NONE)
406     {
407       spawn = enemy;
408       break;
409     }
410   }
411 
412   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
413   {
414     spawn = &enemies[enemyCount++];
415   }
416 
417   if (spawn)
418   {
419     spawn->currentX = currentX;
420     spawn->currentY = currentY;
421     spawn->nextX = currentX;
422     spawn->nextY = currentY;
423     spawn->simPosition = (Vector2){currentX, currentY};
424     spawn->simVelocity = (Vector2){0, 0};
425     spawn->enemyType = enemyType;
426     spawn->startMovingTime = gameTime.time;
427     spawn->damage = 0.0f;
428     spawn->futureDamage = 0.0f;
429     spawn->generation++;
430     spawn->movePathCount = 0;
431   }
432 
433   return spawn;
434 }
435 
436 int EnemyAddDamage(Enemy *enemy, float damage)
437 {
438   enemy->damage += damage;
439   if (enemy->damage >= EnemyGetMaxHealth(enemy))
440   {
441     currentLevel->playerGold += enemyClassConfigs[enemy->enemyType].goldValue;
442     enemy->enemyType = ENEMY_TYPE_NONE;
443     return 1;
444   }
445 
446   return 0;
447 }
448 
449 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
450 {
451   int16_t castleX = 0;
452   int16_t castleY = 0;
453   Enemy* closest = 0;
454   int16_t closestDistance = 0;
455   float range2 = range * range;
456   for (int i = 0; i < enemyCount; i++)
457   {
458     Enemy* enemy = &enemies[i];
459     if (enemy->enemyType == ENEMY_TYPE_NONE)
460     {
461       continue;
462     }
463     float maxHealth = EnemyGetMaxHealth(enemy);
464     if (enemy->futureDamage >= maxHealth)
465     {
466       // ignore enemies that will die soon
467       continue;
468     }
469     int16_t dx = castleX - enemy->currentX;
470     int16_t dy = castleY - enemy->currentY;
471     int16_t distance = abs(dx) + abs(dy);
472     if (!closest || distance < closestDistance)
473     {
474       float tdx = towerX - enemy->currentX;
475       float tdy = towerY - enemy->currentY;
476       float tdistance2 = tdx * tdx + tdy * tdy;
477       if (tdistance2 <= range2)
478       {
479         closest = enemy;
480         closestDistance = distance;
481       }
482     }
483   }
484   return closest;
485 }
486 
487 int EnemyCount()
488 {
489   int count = 0;
490   for (int i = 0; i < enemyCount; i++)
491   {
492     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
493     {
494       count++;
495     }
496   }
497   return count;
498 }
499 
500 void EnemyDrawHealthbars(Camera3D camera)
501 {
502   for (int i = 0; i < enemyCount; i++)
503   {
504     Enemy *enemy = &enemies[i];
505     if (enemy->enemyType == ENEMY_TYPE_NONE || enemy->damage == 0.0f)
506     {
507       continue;
508     }
509     Vector3 position = (Vector3){enemy->simPosition.x, 0.5f, enemy->simPosition.y};
510     float maxHealth = EnemyGetMaxHealth(enemy);
511     float health = maxHealth - enemy->damage;
512     float healthRatio = health / maxHealth;
513     
514     DrawHealthBar(camera, position, healthRatio, GREEN);
515   }
516 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static Projectile projectiles[PROJECTILE_MAX_COUNT];
  5 static int projectileCount = 0;
  6 
  7 void ProjectileInit()
  8 {
  9   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
 10   {
 11     projectiles[i] = (Projectile){0};
 12   }
 13 }
 14 
 15 void ProjectileDraw()
 16 {
 17   for (int i = 0; i < projectileCount; i++)
 18   {
 19     Projectile projectile = projectiles[i];
 20     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
 21     {
 22       continue;
 23     }
 24     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
 25     if (transition >= 1.0f)
 26     {
 27       continue;
 28     }
 29     for (float transitionOffset = 0.0f; transitionOffset < 1.0f; transitionOffset += 0.1f)
 30     {
 31       float t = transition + transitionOffset * 0.3f;
 32       if (t > 1.0f)
 33       {
 34         break;
 35       }
 36       Vector3 position = Vector3Lerp(projectile.position, projectile.target, t);
 37       Color color = RED;
 38       if (projectile.projectileType == PROJECTILE_TYPE_ARROW)
 39       {
 40         // make tip red but quickly fade to brown
 41         color = ColorLerp(BROWN, RED, transitionOffset * transitionOffset);
 42         // fake a ballista flight path using parabola equation
 43         float parabolaT = t - 0.5f;
 44         parabolaT = 1.0f - 4.0f * parabolaT * parabolaT;
 45         position.y += 0.15f * parabolaT * projectile.distance;
 46       }
 47 
 48       float size = 0.06f * (transitionOffset + 0.25f);
 49       DrawCube(position, size, size, size, color);
 50     }
 51   }
 52 }
 53 
 54 void ProjectileUpdate()
 55 {
 56   for (int i = 0; i < projectileCount; i++)
 57   {
 58     Projectile *projectile = &projectiles[i];
 59     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
 60     {
 61       continue;
 62     }
 63     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
 64     if (transition >= 1.0f)
 65     {
 66       projectile->projectileType = PROJECTILE_TYPE_NONE;
 67       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
 68       if (enemy)
 69       {
 70         EnemyAddDamage(enemy, projectile->damage);
 71       }
 72       continue;
 73     }
 74   }
 75 }
 76 
 77 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, float damage)
 78 {
 79   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
 80   {
 81     Projectile *projectile = &projectiles[i];
 82     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
 83     {
 84       projectile->projectileType = projectileType;
 85       projectile->shootTime = gameTime.time;
 86       float distance = Vector3Distance(position, target);
 87       projectile->arrivalTime = gameTime.time + distance / speed;
 88       projectile->damage = damage;
 89       projectile->position = position;
 90       projectile->target = target;
 91       projectile->directionNormal = Vector3Scale(Vector3Subtract(target, position), 1.0f / distance);
 92       projectile->distance = distance;
 93       projectile->targetEnemy = EnemyGetId(enemy);
 94       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
 95       return projectile;
 96     }
 97   }
 98   return 0;
 99 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 Tower towers[TOWER_MAX_COUNT];
  5 int towerCount = 0;
  6 
  7 Model towerModels[TOWER_TYPE_COUNT];
  8 Texture2D palette, spriteSheet;
  9 
 10 // definition of our archer unit
 11 SpriteUnit archerUnit = {
 12     .srcRect = {0, 0, 16, 16},
 13     .offset = {7, 1},
 14 };
 15 
 16 void DrawSpriteUnit(SpriteUnit unit, Vector3 position)
 17 {
 18   Camera3D camera = currentLevel->camera;
 19   float size = 0.5f;
 20   Vector2 offset = (Vector2){ unit.offset.x / 16.0f * size, unit.offset.y / 16.0f * size };
 21   Vector2 scale = (Vector2){ unit.srcRect.width / 16.0f * size, unit.srcRect.height / 16.0f * size };
 22   // we want the sprite to face the camera, so we need to calculate the up vector
 23   Vector3 forward = Vector3Subtract(camera.target, camera.position);
 24   Vector3 up = {0, 1, 0};
 25   Vector3 right = Vector3CrossProduct(forward, up);
 26   up = Vector3Normalize(Vector3CrossProduct(right, forward));
 27   DrawBillboardPro(camera, spriteSheet, unit.srcRect, position, up, scale, offset, 0, WHITE);
 28 }
 29 
 30 void TowerInit()
 31 {
 32   for (int i = 0; i < TOWER_MAX_COUNT; i++)
 33   {
 34     towers[i] = (Tower){0};
 35   }
 36   towerCount = 0;
 37 
 38   // load a sprite sheet that contains all units
 39   spriteSheet = LoadTexture("data/spritesheet.png");
 40 
 41   // we'll use a palette texture to colorize the all buildings and environment art
 42   palette = LoadTexture("data/palette.png");
 43   // The texture uses gradients on very small space, so we'll enable bilinear filtering
 44   SetTextureFilter(palette, TEXTURE_FILTER_BILINEAR);
 45 
 46   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
 47   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
 48 
 49   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
 50   {
 51     if (towerModels[i].materials)
 52     {
 53       // assign the palette texture to the material of the model (0 is not used afaik)
 54       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 55     }
 56   }
 57 }
 58 
 59 static void TowerGunUpdate(Tower *tower)
 60 {
 61   if (tower->cooldown <= 0)
 62   {
 63     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
 64     if (enemy)
 65     {
 66       tower->cooldown = 0.5f;
 67       // shoot the enemy; determine future position of the enemy
 68       float bulletSpeed = 4.0f;
 69       float bulletDamage = 3.0f;
 70       Vector2 velocity = enemy->simVelocity;
 71       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
 72       Vector2 towerPosition = {tower->x, tower->y};
 73       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
 74       for (int i = 0; i < 8; i++) {
 75         velocity = enemy->simVelocity;
 76         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
 77         float distance = Vector2Distance(towerPosition, futurePosition);
 78         float eta2 = distance / bulletSpeed;
 79         if (fabs(eta - eta2) < 0.01f) {
 80           break;
 81         }
 82         eta = (eta2 + eta) * 0.5f;
 83       }
 84       ProjectileTryAdd(PROJECTILE_TYPE_ARROW, enemy, 
 85         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
 86         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
 87         bulletSpeed, bulletDamage);
 88       enemy->futureDamage += bulletDamage;
 89     }
 90   }
 91   else
 92   {
 93     tower->cooldown -= gameTime.deltaTime;
 94   }
 95 }
 96 
 97 Tower *TowerGetAt(int16_t x, int16_t y)
 98 {
 99   for (int i = 0; i < towerCount; i++)
100   {
101     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
102     {
103       return &towers[i];
104     }
105   }
106   return 0;
107 }
108 
109 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
110 {
111   if (towerCount >= TOWER_MAX_COUNT)
112   {
113     return 0;
114   }
115 
116   Tower *tower = TowerGetAt(x, y);
117   if (tower)
118   {
119     return 0;
120   }
121 
122   tower = &towers[towerCount++];
123   tower->x = x;
124   tower->y = y;
125   tower->towerType = towerType;
126   tower->cooldown = 0.0f;
127   tower->damage = 0.0f;
128   return tower;
129 }
130 
131 Tower *GetTowerByType(uint8_t towerType)
132 {
133   for (int i = 0; i < towerCount; i++)
134   {
135     if (towers[i].towerType == towerType)
136     {
137       return &towers[i];
138     }
139   }
140   return 0;
141 }
142 
143 int GetTowerCosts(uint8_t towerType)
144 {
145   switch (towerType)
146   {
147   case TOWER_TYPE_BASE:
148     return 0;
149   case TOWER_TYPE_GUN:
150     return 6;
151   case TOWER_TYPE_WALL:
152     return 2;
153   }
154   return 0;
155 }
156 
157 float TowerGetMaxHealth(Tower *tower)
158 {
159   switch (tower->towerType)
160   {
161   case TOWER_TYPE_BASE:
162     return 10.0f;
163   case TOWER_TYPE_GUN:
164     return 3.0f;
165   case TOWER_TYPE_WALL:
166     return 5.0f;
167   }
168   return 0.0f;
169 }
170 
171 void TowerDraw()
172 {
173   for (int i = 0; i < towerCount; i++)
174   {
175     Tower tower = towers[i];
176     if (tower.towerType == TOWER_TYPE_NONE)
177     {
178       continue;
179     }
180 
181     switch (tower.towerType)
182     {
183     case TOWER_TYPE_GUN:
184       DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
185       DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y});
186       break;
187     default:
188       if (towerModels[tower.towerType].materials)
189       {
190         DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
191       } else {
192         DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
193       }
194       break;
195     }
196   }
197 }
198 
199 void TowerUpdate()
200 {
201   for (int i = 0; i < towerCount; i++)
202   {
203     Tower *tower = &towers[i];
204     switch (tower->towerType)
205     {
206     case TOWER_TYPE_GUN:
207       TowerGunUpdate(tower);
208       break;
209     }
210   }
211 }
212 
213 void TowerDrawHealthBars(Camera3D camera)
214 {
215   for (int i = 0; i < towerCount; i++)
216   {
217     Tower *tower = &towers[i];
218     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
219     {
220       continue;
221     }
222     
223     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
224     float maxHealth = TowerGetMaxHealth(tower);
225     float health = maxHealth - tower->damage;
226     float healthRatio = health / maxHealth;
227     
228     DrawHealthBar(camera, position, healthRatio, GREEN);
229   }
230 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 // The queue is a simple array of nodes, we add nodes to the end and remove
  5 // nodes from the front. We keep the array around to avoid unnecessary allocations
  6 static PathfindingNode *pathfindingNodeQueue = 0;
  7 static int pathfindingNodeQueueCount = 0;
  8 static int pathfindingNodeQueueCapacity = 0;
  9 
 10 // The pathfinding map stores the distances from the castle to each cell in the map.
 11 static PathfindingMap pathfindingMap = {0};
 12 
 13 void PathfindingMapInit(int width, int height, Vector3 translate, float scale)
 14 {
 15   // transforming between map space and world space allows us to adapt 
 16   // position and scale of the map without changing the pathfinding data
 17   pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z);
 18   pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale));
 19   pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace);
 20   pathfindingMap.width = width;
 21   pathfindingMap.height = height;
 22   pathfindingMap.scale = scale;
 23   pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float));
 24   for (int i = 0; i < width * height; i++)
 25   {
 26     pathfindingMap.distances[i] = -1.0f;
 27   }
 28 
 29   pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long));
 30   pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc));
 31 }
 32 
 33 static void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance)
 34 {
 35   if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity)
 36   {
 37     pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2;
 38     // we use MemAlloc/MemRealloc to allocate memory for the queue
 39     // I am not entirely sure if MemRealloc allows passing a null pointer
 40     // so we check if the pointer is null and use MemAlloc in that case
 41     if (pathfindingNodeQueue == 0)
 42     {
 43       pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 44     }
 45     else
 46     {
 47       pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 48     }
 49   }
 50 
 51   PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++];
 52   node->x = x;
 53   node->y = y;
 54   node->fromX = fromX;
 55   node->fromY = fromY;
 56   node->distance = distance;
 57 }
 58 
 59 static PathfindingNode *PathFindingNodePop()
 60 {
 61   if (pathfindingNodeQueueCount == 0)
 62   {
 63     return 0;
 64   }
 65   // we return the first node in the queue; we want to return a pointer to the node
 66   // so we can return 0 if the queue is empty. 
 67   // We should _not_ return a pointer to the element in the list, because the list
 68   // may be reallocated and the pointer would become invalid. Or the 
 69   // popped element is overwritten by the next push operation.
 70   // Using static here means that the variable is permanently allocated.
 71   static PathfindingNode node;
 72   node = pathfindingNodeQueue[0];
 73   // we shift all nodes one position to the front
 74   for (int i = 1; i < pathfindingNodeQueueCount; i++)
 75   {
 76     pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i];
 77   }
 78   --pathfindingNodeQueueCount;
 79   return &node;
 80 }
 81 
 82 float PathFindingGetDistance(int mapX, int mapY)
 83 {
 84   if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height)
 85   {
 86     // when outside the map, we return the manhattan distance to the castle (0,0)
 87     return fabsf((float)mapX) + fabsf((float)mapY);
 88   }
 89 
 90   return pathfindingMap.distances[mapY * pathfindingMap.width + mapX];
 91 }
 92 
 93 // transform a world position to a map position in the array; 
 94 // returns true if the position is inside the map
 95 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY)
 96 {
 97   Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace);
 98   *mapX = (int16_t)mapPosition.x;
 99   *mapY = (int16_t)mapPosition.z;
100   return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height;
101 }
102 
103 void PathFindingMapUpdate()
104 {
105   const int castleX = 0, castleY = 0;
106   int16_t castleMapX, castleMapY;
107   if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY))
108   {
109     return;
110   }
111   int width = pathfindingMap.width, height = pathfindingMap.height;
112 
113   // reset the distances to -1
114   for (int i = 0; i < width * height; i++)
115   {
116     pathfindingMap.distances[i] = -1.0f;
117   }
118   // reset the tower indices
119   for (int i = 0; i < width * height; i++)
120   {
121     pathfindingMap.towerIndex[i] = -1;
122   }
123   // reset the delta src
124   for (int i = 0; i < width * height; i++)
125   {
126     pathfindingMap.deltaSrc[i].x = 0;
127     pathfindingMap.deltaSrc[i].y = 0;
128   }
129 
130   for (int i = 0; i < towerCount; i++)
131   {
132     Tower *tower = &towers[i];
133     if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE)
134     {
135       continue;
136     }
137     int16_t mapX, mapY;
138     // technically, if the tower cell scale is not in sync with the pathfinding map scale,
139     // this would not work correctly and needs to be refined to allow towers covering multiple cells
140     // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly
141     // one cell. For now.
142     if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY))
143     {
144       continue;
145     }
146     int index = mapY * width + mapX;
147     pathfindingMap.towerIndex[index] = i;
148   }
149 
150   // we start at the castle and add the castle to the queue
151   pathfindingMap.maxDistance = 0.0f;
152   pathfindingNodeQueueCount = 0;
153   PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f);
154   PathfindingNode *node = 0;
155   while ((node = PathFindingNodePop()))
156   {
157     if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height)
158     {
159       continue;
160     }
161     int index = node->y * width + node->x;
162     if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance)
163     {
164       continue;
165     }
166 
167     int deltaX = node->x - node->fromX;
168     int deltaY = node->y - node->fromY;
169     // even if the cell is blocked by a tower, we still may want to store the direction
170     // (though this might not be needed, IDK right now)
171     pathfindingMap.deltaSrc[index].x = (char) deltaX;
172     pathfindingMap.deltaSrc[index].y = (char) deltaY;
173 
174     // we skip nodes that are blocked by towers
175     if (pathfindingMap.towerIndex[index] >= 0)
176     {
177       node->distance += 8.0f;
178     }
179     pathfindingMap.distances[index] = node->distance;
180     pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance);
181     PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f);
182     PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f);
183     PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f);
184     PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f);
185   }
186 }
187 
188 void PathFindingMapDraw()
189 {
190   float cellSize = pathfindingMap.scale * 0.9f;
191   float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance);
192   for (int x = 0; x < pathfindingMap.width; x++)
193   {
194     for (int y = 0; y < pathfindingMap.height; y++)
195     {
196       float distance = pathfindingMap.distances[y * pathfindingMap.width + x];
197       float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f);
198       Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255};
199       Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace);
200       // animate the distance "wave" to show how the pathfinding algorithm expands
201       // from the castle
202       if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance)
203       {
204         color = BLACK;
205       }
206       DrawCube(position, cellSize, 0.1f, cellSize, color);
207     }
208   }
209 }
210 
211 Vector2 PathFindingGetGradient(Vector3 world)
212 {
213   int16_t mapX, mapY;
214   if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY))
215   {
216     DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX];
217     return (Vector2){(float)-delta.x, (float)-delta.y};
218   }
219   // fallback to a simple gradient calculation
220   float n = PathFindingGetDistance(mapX, mapY - 1);
221   float s = PathFindingGetDistance(mapX, mapY + 1);
222   float w = PathFindingGetDistance(mapX - 1, mapY);
223   float e = PathFindingGetDistance(mapX + 1, mapY);
224   return (Vector2){w - e + 0.25f, n - s + 0.125f};
225 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static Particle particles[PARTICLE_MAX_COUNT];
  5 static int particleCount = 0;
  6 
  7 void ParticleInit()
  8 {
  9   for (int i = 0; i < PARTICLE_MAX_COUNT; i++)
 10   {
 11     particles[i] = (Particle){0};
 12   }
 13   particleCount = 0;
 14 }
 15 
 16 static void DrawExplosionParticle(Particle *particle, float transition)
 17 {
 18   float size = 1.2f * (1.0f - transition);
 19   Color startColor = WHITE;
 20   Color endColor = RED;
 21   Color color = ColorLerp(startColor, endColor, transition);
 22   DrawCube(particle->position, size, size, size, color);
 23 }
 24 
 25 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime)
 26 {
 27   if (particleCount >= PARTICLE_MAX_COUNT)
 28   {
 29     return;
 30   }
 31 
 32   int index = -1;
 33   for (int i = 0; i < particleCount; i++)
 34   {
 35     if (particles[i].particleType == PARTICLE_TYPE_NONE)
 36     {
 37       index = i;
 38       break;
 39     }
 40   }
 41 
 42   if (index == -1)
 43   {
 44     index = particleCount++;
 45   }
 46 
 47   Particle *particle = &particles[index];
 48   particle->particleType = particleType;
 49   particle->spawnTime = gameTime.time;
 50   particle->lifetime = lifetime;
 51   particle->position = position;
 52   particle->velocity = velocity;
 53 }
 54 
 55 void ParticleUpdate()
 56 {
 57   for (int i = 0; i < particleCount; i++)
 58   {
 59     Particle *particle = &particles[i];
 60     if (particle->particleType == PARTICLE_TYPE_NONE)
 61     {
 62       continue;
 63     }
 64 
 65     float age = gameTime.time - particle->spawnTime;
 66 
 67     if (particle->lifetime > age)
 68     {
 69       particle->position = Vector3Add(particle->position, Vector3Scale(particle->velocity, gameTime.deltaTime));
 70     }
 71     else {
 72       particle->particleType = PARTICLE_TYPE_NONE;
 73     }
 74   }
 75 }
 76 
 77 void ParticleDraw()
 78 {
 79   for (int i = 0; i < particleCount; i++)
 80   {
 81     Particle particle = particles[i];
 82     if (particle.particleType == PARTICLE_TYPE_NONE)
 83     {
 84       continue;
 85     }
 86 
 87     float age = gameTime.time - particle.spawnTime;
 88     float transition = age / particle.lifetime;
 89     switch (particle.particleType)
 90     {
 91     case PARTICLE_TYPE_EXPLOSION:
 92       DrawExplosionParticle(&particle, transition);
 93       break;
 94     default:
 95       DrawCube(particle.position, 0.3f, 0.5f, 0.3f, RED);
 96       break;
 97     }
 98   }
 99 }  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 #endifThis is what it looks like when placing the "gun" towers and starting a wave:
So far so good. Let's break down the indivdual steps to see how this is all achieved:
In the tower system, we load a model for the keep and deleted the code to draw the red cube:
  1 towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");Another minor change in the tower system is an increase of the bullet speet and spawning an arrow type projectile at a reasonable height that matches the archer's position.
The more complex change is that the projectiles follow a slight arc now - let's see how this is done.
The respective code part where this is hiding is this piece here:
  1 for (float transitionOffset = 0.0f; transitionOffset < 1.0f; transitionOffset += 0.1f)
  2 {
  3   float t = transition + transitionOffset * 0.3f;
  4   if (t > 1.0f)
  5   {
  6     break;
  7   }
  8   Vector3 position = Vector3Lerp(projectile.position, projectile.target, t);
  9   Color color = RED;
 10   if (projectile.projectileType == PROJECTILE_TYPE_ARROW)
 11   {
 12     // make tip red but quickly fade to brown
 13     color = ColorLerp(BROWN, RED, transitionOffset * transitionOffset);
 14     // fake a ballista flight path using parabola equation
 15     t = t - 0.5f;
 16     t = 1.0f - 4.0f * t * t;
 17     position.y += 0.15f * t * projectile.distance;
 18   }
 19 
 20   float size = 0.06f * (transitionOffset + 0.25f);
 21   DrawCube(position, size, size, size, color);
 22 }Quite a bit of code, so let's take it apart even more. Starting with this line here:
  1 for (float transitionOffset = 0.0f; transitionOffset < 1.0f; transitionOffset += 0.1f)The reason for this loop is that we want to draw a trail of the projectile drawing a number of smaller cubes. The transitionOffset is the offset to each cube we draw - so this loop is handling the trail effect by repeatedly drawing the arrow cubes with slightly different position, size and color. The next line is this one:
  1 float t = transition + transitionOffset * 0.3fThe way the projectile is drawn is by interpolating between the start and end position of the projectile and its trail.
Value t is the concrete transition point at which we want to draw the projectile.
The "0.3f" is the offset to the transition value and means that we look 30% further into the future when drawing the projectile and its trail. So the end of the trail is the actual position of the projectile, while the start of the trail is 30% in the future. That also means, that the hit is registered when the trail's end is at the target position - which explains why the projectiles appear instantly at the archer together with the trail but fully hit the target only when the trail is at the target (you can observe this in the GIF). This could be fixed by adjusting the transition time, but for now, this is fine.
So to get the current position, we use a transition value that goes from 0 to 1. We use this value to linearly interpolate between the start and end position of the projectile. When the value is > 1, we break the loop, since the projectile has reached its target.
  1 if (t > 1.0f)
  2 {
  3   break; // nothing to draw anymore
  4 }
  5 Vector3 position = Vector3Lerp(projectile.position, projectile.target, t);But linear interpolation means, that the projectile would fly in a straight line. To make it look more like a flying arrow, we have to modify the y position of the projectile, reflecting the rise and fall of the arrow. We know that a flight path in vacuum is a parabola, so we can use the calculation t * t to get a parabola, though we have to adjust the values so it's peak is at the middle of the flight path, which happens here:
  1 if (projectile.projectileType == PROJECTILE_TYPE_ARROW)
  2 {
  3   // make tip red but quickly fade to brown
  4   color = ColorLerp(BROWN, RED, transitionOffset * transitionOffset);
  5   // fake a ballista flight path using parabola equation
  6   float parabolaT = t - 0.5f;
  7   parabolaT = 1.0f - 4.0f * parabolaT * parabolaT;
  8   position.y += 0.15f * parabolaT * projectile.distance;
  9 }The coloring is simply the way we want to color the "arrow" - it starts brown and ends red. The color lerp is transitionOffset², just so that only the tip of the arrow is red but the rest quickly fades to brown. Just to clarify what I mean: If the transitionOffset is 0, the color is brown, when it is 0.5, the color is 25% red and 75% brown and when it is 1, the color is red.
The parabola calculation isn't too complex either: The assignment of t - 0.5f to parabolaT is done to center the peak of the parabola at the middle of the flight path. The range for that our variable parabolaT is therefore between -0.5 and 0.5.
When we run the calculation parabolaT * parabolaT, the result is a value that is therefore between 0 and 0.25 since 0.5 * 0.5 = 0.25. We want to have the peak at 1, so we multiply the result by 4 and negate it, because the parabola should point up, not down. We can plot this function, which could be expressed as
f(parabolaT) = 1 - 4 * parabolaT * parabolaT
and it looks like this:
To reiterate, for the transition value of 1 and 0, the result is 0, while at 0.5 it is 1.
We can now take this value and multiply it with the distance of the projectile, so the arc's height is proportional to the distance the projectile has to travel:
  1 position.y += 0.15f * parabolaT * projectile.distanceThe constant 0.15f is used to scale the arc to a reasonable height: At the peak of its flight, the arrow is 15% higher than the distance it has to travel. If we don't use the distance, the arc would always be the same height, regardless of the distance the arrow has to travel - so for long distances, the arc would be nearly flat and for short distances, the arc would be comically high.
Since this might be all a bit complicated to imagine, here's an interactive example of how the arc changes with the distance and how it is applied to different start and end positions:
For an arcvalue of 0, the arc is flat - just like the linear interpolation would be, while a large value makes the arc go very high. At a later point, we may come back at this formula to adjust it. For example, when having a mortar tower that shoots in a very high arc, we may want to adjust the formula so the slowdown at the peak is more pronounced. This can be achieved by manipulating input value t.
But for now, we are done with the arc.
Archer & orc animation
Currently, the archers don't have bows or even arms right now and the orcs are just static sprites that float over the ground (should have used ghosts, I guess). So let's change this: The archers should have a bow and the orcs should have a walking animation.
First, we'll animating the orc movement. It's quite simple: based on the walked distance, we select the frame of the sprite sheet to draw. The frame selection is happening in the DrawSpriteUnit function in the tower_system.c file. It uses a few new fields in the SpriteUnit struct to obtain the frame count and the frame duration. These changes are done in the td_main.h file while the configuration of the orc sprite is done in the enemy.c file.
  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 //# Variables
  7 GUIState guiState = {0};
  8 GameTime gameTime = {0};
  9 
 10 Level levels[] = {
 11   [0] = {
 12     .state = LEVEL_STATE_BUILDING,
 13     .initialGold = 20,
 14     .waves[0] = {
 15       .enemyType = ENEMY_TYPE_MINION,
 16       .wave = 0,
 17       .count = 10,
 18       .interval = 2.5f,
 19       .delay = 1.0f,
 20       .spawnPosition = {0, 6},
 21     },
 22     .waves[1] = {
 23       .enemyType = ENEMY_TYPE_MINION,
 24       .wave = 1,
 25       .count = 20,
 26       .interval = 1.5f,
 27       .delay = 1.0f,
 28       .spawnPosition = {0, 6},
 29     },
 30     .waves[2] = {
 31       .enemyType = ENEMY_TYPE_MINION,
 32       .wave = 2,
 33       .count = 30,
 34       .interval = 1.2f,
 35       .delay = 1.0f,
 36       .spawnPosition = {0, 6},
 37     }
 38   },
 39 };
 40 
 41 Level *currentLevel = levels;
 42 
 43 //# Game
 44 
 45 void InitLevel(Level *level)
 46 {
 47   TowerInit();
 48   EnemyInit();
 49   ProjectileInit();
 50   ParticleInit();
 51   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
 52 
 53   level->placementMode = 0;
 54   level->state = LEVEL_STATE_BUILDING;
 55   level->nextState = LEVEL_STATE_NONE;
 56   level->playerGold = level->initialGold;
 57   level->currentWave = 0;
 58 
 59   Camera *camera = &level->camera;
 60   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
 61   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
 62   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
 63   camera->fovy = 10.0f;
 64   camera->projection = CAMERA_ORTHOGRAPHIC;
 65 }
 66 
 67 void DrawLevelHud(Level *level)
 68 {
 69   const char *text = TextFormat("Gold: %d", level->playerGold);
 70   Font font = GetFontDefault();
 71   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
 72   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
 73 }
 74 
 75 void DrawLevelReportLostWave(Level *level)
 76 {
 77   BeginMode3D(level->camera);
 78   DrawGrid(10, 1.0f);
 79   TowerDraw();
 80   EnemyDraw();
 81   ProjectileDraw();
 82   ParticleDraw();
 83   guiState.isBlocked = 0;
 84   EndMode3D();
 85 
 86   TowerDrawHealthBars(level->camera);
 87 
 88   const char *text = "Wave lost";
 89   int textWidth = MeasureText(text, 20);
 90   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
 91 
 92   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
 93   {
 94     level->nextState = LEVEL_STATE_RESET;
 95   }
 96 }
 97 
 98 int HasLevelNextWave(Level *level)
 99 {
100   for (int i = 0; i < 10; i++)
101   {
102     EnemyWave *wave = &level->waves[i];
103     if (wave->wave == level->currentWave)
104     {
105       return 1;
106     }
107   }
108   return 0;
109 }
110 
111 void DrawLevelReportWonWave(Level *level)
112 {
113   BeginMode3D(level->camera);
114   DrawGrid(10, 1.0f);
115   TowerDraw();
116   EnemyDraw();
117   ProjectileDraw();
118   ParticleDraw();
119   guiState.isBlocked = 0;
120   EndMode3D();
121 
122   TowerDrawHealthBars(level->camera);
123 
124   const char *text = "Wave won";
125   int textWidth = MeasureText(text, 20);
126   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
127 
128 
129   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
130   {
131     level->nextState = LEVEL_STATE_RESET;
132   }
133 
134   if (HasLevelNextWave(level))
135   {
136     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
137     {
138       level->nextState = LEVEL_STATE_BUILDING;
139     }
140   }
141   else {
142     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
143     {
144       level->nextState = LEVEL_STATE_WON_LEVEL;
145     }
146   }
147 }
148 
149 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
150 {
151   static ButtonState buttonStates[8] = {0};
152   int cost = GetTowerCosts(towerType);
153   const char *text = TextFormat("%s: %d", name, cost);
154   buttonStates[towerType].isSelected = level->placementMode == towerType;
155   buttonStates[towerType].isDisabled = level->playerGold < cost;
156   if (Button(text, x, y, width, height, &buttonStates[towerType]))
157   {
158     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
159   }
160 }
161 
162 void DrawLevelBuildingState(Level *level)
163 {
164   BeginMode3D(level->camera);
165   DrawGrid(10, 1.0f);
166   TowerDraw();
167   EnemyDraw();
168   ProjectileDraw();
169   ParticleDraw();
170 
171   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
172   float planeDistance = ray.position.y / -ray.direction.y;
173   float planeX = ray.direction.x * planeDistance + ray.position.x;
174   float planeY = ray.direction.z * planeDistance + ray.position.z;
175   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
176   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
177   if (level->placementMode && !guiState.isBlocked)
178   {
179     DrawCubeWires((Vector3){mapX, 0.2f, mapY}, 1.0f, 0.4f, 1.0f, RED);
180     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
181     {
182       if (TowerTryAdd(level->placementMode, mapX, mapY))
183       {
184         level->playerGold -= GetTowerCosts(level->placementMode);
185         level->placementMode = TOWER_TYPE_NONE;
186       }
187     }
188   }
189 
190   guiState.isBlocked = 0;
191 
192   EndMode3D();
193 
194   TowerDrawHealthBars(level->camera);
195 
196   static ButtonState buildWallButtonState = {0};
197   static ButtonState buildGunButtonState = {0};
198   buildWallButtonState.isSelected = level->placementMode == TOWER_TYPE_WALL;
199   buildGunButtonState.isSelected = level->placementMode == TOWER_TYPE_GUN;
200 
201   DrawBuildingBuildButton(level, 10, 10, 80, 30, TOWER_TYPE_WALL, "Wall");
202   DrawBuildingBuildButton(level, 10, 50, 80, 30, TOWER_TYPE_GUN, "Gun");
203 
204   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
205   {
206     level->nextState = LEVEL_STATE_RESET;
207   }
208   
209   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
210   {
211     level->nextState = LEVEL_STATE_BATTLE;
212   }
213 
214   const char *text = "Building phase";
215   int textWidth = MeasureText(text, 20);
216   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
217 }
218 
219 void InitBattleStateConditions(Level *level)
220 {
221   level->state = LEVEL_STATE_BATTLE;
222   level->nextState = LEVEL_STATE_NONE;
223   level->waveEndTimer = 0.0f;
224   for (int i = 0; i < 10; i++)
225   {
226     EnemyWave *wave = &level->waves[i];
227     wave->spawned = 0;
228     wave->timeToSpawnNext = wave->delay;
229   }
230 }
231 
232 void DrawLevelBattleState(Level *level)
233 {
234   BeginMode3D(level->camera);
235   DrawGrid(10, 1.0f);
236   TowerDraw();
237   EnemyDraw();
238   ProjectileDraw();
239   ParticleDraw();
240   guiState.isBlocked = 0;
241   EndMode3D();
242 
243   EnemyDrawHealthbars(level->camera);
244   TowerDrawHealthBars(level->camera);
245 
246   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
247   {
248     level->nextState = LEVEL_STATE_RESET;
249   }
250 
251   int maxCount = 0;
252   int remainingCount = 0;
253   for (int i = 0; i < 10; i++)
254   {
255     EnemyWave *wave = &level->waves[i];
256     if (wave->wave != level->currentWave)
257     {
258       continue;
259     }
260     maxCount += wave->count;
261     remainingCount += wave->count - wave->spawned;
262   }
263   int aliveCount = EnemyCount();
264   remainingCount += aliveCount;
265 
266   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
267   int textWidth = MeasureText(text, 20);
268   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
269 }
270 
271 void DrawLevel(Level *level)
272 {
273   switch (level->state)
274   {
275     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
276     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
277     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
278     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
279     default: break;
280   }
281 
282   DrawLevelHud(level);
283 }
284 
285 void UpdateLevel(Level *level)
286 {
287   if (level->state == LEVEL_STATE_BATTLE)
288   {
289     int activeWaves = 0;
290     for (int i = 0; i < 10; i++)
291     {
292       EnemyWave *wave = &level->waves[i];
293       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
294       {
295         continue;
296       }
297       activeWaves++;
298       wave->timeToSpawnNext -= gameTime.deltaTime;
299       if (wave->timeToSpawnNext <= 0.0f)
300       {
301         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
302         if (enemy)
303         {
304           wave->timeToSpawnNext = wave->interval;
305           wave->spawned++;
306         }
307       }
308     }
309     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
310       level->waveEndTimer += gameTime.deltaTime;
311       if (level->waveEndTimer >= 2.0f)
312       {
313         level->nextState = LEVEL_STATE_LOST_WAVE;
314       }
315     }
316     else if (activeWaves == 0 && EnemyCount() == 0)
317     {
318       level->waveEndTimer += gameTime.deltaTime;
319       if (level->waveEndTimer >= 2.0f)
320       {
321         level->nextState = LEVEL_STATE_WON_WAVE;
322       }
323     }
324   }
325 
326   PathFindingMapUpdate();
327   EnemyUpdate();
328   TowerUpdate();
329   ProjectileUpdate();
330   ParticleUpdate();
331 
332   if (level->nextState == LEVEL_STATE_RESET)
333   {
334     InitLevel(level);
335   }
336   
337   if (level->nextState == LEVEL_STATE_BATTLE)
338   {
339     InitBattleStateConditions(level);
340   }
341   
342   if (level->nextState == LEVEL_STATE_WON_WAVE)
343   {
344     level->currentWave++;
345     level->state = LEVEL_STATE_WON_WAVE;
346   }
347   
348   if (level->nextState == LEVEL_STATE_LOST_WAVE)
349   {
350     level->state = LEVEL_STATE_LOST_WAVE;
351   }
352 
353   if (level->nextState == LEVEL_STATE_BUILDING)
354   {
355     level->state = LEVEL_STATE_BUILDING;
356   }
357 
358   if (level->nextState == LEVEL_STATE_WON_LEVEL)
359   {
360     // make something of this later
361     InitLevel(level);
362   }
363 
364   level->nextState = LEVEL_STATE_NONE;
365 }
366 
367 float nextSpawnTime = 0.0f;
368 
369 void ResetGame()
370 {
371   InitLevel(currentLevel);
372 }
373 
374 void InitGame()
375 {
376   TowerInit();
377   EnemyInit();
378   ProjectileInit();
379   ParticleInit();
380   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
381 
382   currentLevel = levels;
383   InitLevel(currentLevel);
384 }
385 
386 //# Immediate GUI functions
387 
388 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor)
389 {
390   const float healthBarWidth = 40.0f;
391   const float healthBarHeight = 6.0f;
392   const float healthBarOffset = 15.0f;
393   const float inset = 2.0f;
394   const float innerWidth = healthBarWidth - inset * 2;
395   const float innerHeight = healthBarHeight - inset * 2;
396 
397   Vector2 screenPos = GetWorldToScreen(position, camera);
398   float centerX = screenPos.x - healthBarWidth * 0.5f;
399   float topY = screenPos.y - healthBarOffset;
400   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
401   float healthWidth = innerWidth * healthRatio;
402   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
403 }
404 
405 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
406 {
407   Rectangle bounds = {x, y, width, height};
408   int isPressed = 0;
409   int isSelected = state && state->isSelected;
410   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !state->isDisabled)
411   {
412     Color color = isSelected ? DARKGRAY : GRAY;
413     DrawRectangle(x, y, width, height, color);
414     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
415     {
416       isPressed = 1;
417     }
418     guiState.isBlocked = 1;
419   }
420   else
421   {
422     Color color = isSelected ? WHITE : LIGHTGRAY;
423     DrawRectangle(x, y, width, height, color);
424   }
425   Font font = GetFontDefault();
426   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
427   Color textColor = state->isDisabled ? GRAY : BLACK;
428   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
429   return isPressed;
430 }
431 
432 //# Main game loop
433 
434 void GameUpdate()
435 {
436   float dt = GetFrameTime();
437   // cap maximum delta time to 0.1 seconds to prevent large time steps
438   if (dt > 0.1f) dt = 0.1f;
439   gameTime.time += dt;
440   gameTime.deltaTime = dt;
441 
442   UpdateLevel(currentLevel);
443 }
444 
445 int main(void)
446 {
447   int screenWidth, screenHeight;
448   GetPreferredSize(&screenWidth, &screenHeight);
449   InitWindow(screenWidth, screenHeight, "Tower defense");
450   SetTargetFPS(30);
451 
452   InitGame();
453 
454   while (!WindowShouldClose())
455   {
456     if (IsPaused()) {
457       // canvas is not visible in browser - do nothing
458       continue;
459     }
460 
461     BeginDrawing();
462     ClearBackground(DARKBLUE);
463 
464     GameUpdate();
465     DrawLevel(currentLevel);
466 
467     EndDrawing();
468   }
469 
470   CloseWindow();
471 
472   return 0;
473 }  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 //# Declarations
 10 
 11 #define ENEMY_MAX_PATH_COUNT 8
 12 #define ENEMY_MAX_COUNT 400
 13 #define ENEMY_TYPE_NONE 0
 14 #define ENEMY_TYPE_MINION 1
 15 
 16 #define PARTICLE_MAX_COUNT 400
 17 #define PARTICLE_TYPE_NONE 0
 18 #define PARTICLE_TYPE_EXPLOSION 1
 19 
 20 typedef struct Particle
 21 {
 22   uint8_t particleType;
 23   float spawnTime;
 24   float lifetime;
 25   Vector3 position;
 26   Vector3 velocity;
 27 } Particle;
 28 
 29 #define TOWER_MAX_COUNT 400
 30 #define TOWER_TYPE_NONE 0
 31 #define TOWER_TYPE_BASE 1
 32 #define TOWER_TYPE_GUN 2
 33 #define TOWER_TYPE_WALL 3
 34 #define TOWER_TYPE_COUNT 4
 35 
 36 typedef struct Tower
 37 {
 38   int16_t x, y;
 39   uint8_t towerType;
 40   float cooldown;
 41   float damage;
 42 } Tower;
 43 
 44 typedef struct GameTime
 45 {
 46   float time;
 47   float deltaTime;
 48 } GameTime;
 49 
 50 typedef struct ButtonState {
 51   char isSelected;
 52   char isDisabled;
 53 } ButtonState;
 54 
 55 typedef struct GUIState {
 56   int isBlocked;
 57 } GUIState;
 58 
 59 typedef enum LevelState
 60 {
 61   LEVEL_STATE_NONE,
 62   LEVEL_STATE_BUILDING,
 63   LEVEL_STATE_BATTLE,
 64   LEVEL_STATE_WON_WAVE,
 65   LEVEL_STATE_LOST_WAVE,
 66   LEVEL_STATE_WON_LEVEL,
 67   LEVEL_STATE_RESET,
 68 } LevelState;
 69 
 70 typedef struct EnemyWave {
 71   uint8_t enemyType;
 72   uint8_t wave;
 73   uint16_t count;
 74   float interval;
 75   float delay;
 76   Vector2 spawnPosition;
 77 
 78   uint16_t spawned;
 79   float timeToSpawnNext;
 80 } EnemyWave;
 81 
 82 typedef struct Level
 83 {
 84   LevelState state;
 85   LevelState nextState;
 86   Camera3D camera;
 87   int placementMode;
 88 
 89   int initialGold;
 90   int playerGold;
 91 
 92   EnemyWave waves[10];
 93   int currentWave;
 94   float waveEndTimer;
 95 } Level;
 96 
 97 typedef struct DeltaSrc
 98 {
 99   char x, y;
100 } DeltaSrc;
101 
102 typedef struct PathfindingMap
103 {
104   int width, height;
105   float scale;
106   float *distances;
107   long *towerIndex; 
108   DeltaSrc *deltaSrc;
109   float maxDistance;
110   Matrix toMapSpace;
111   Matrix toWorldSpace;
112 } PathfindingMap;
113 
114 // when we execute the pathfinding algorithm, we need to store the active nodes
115 // in a queue. Each node has a position, a distance from the start, and the
116 // position of the node that we came from.
117 typedef struct PathfindingNode
118 {
119   int16_t x, y, fromX, fromY;
120   float distance;
121 } PathfindingNode;
122 
123 typedef struct EnemyId
124 {
125   uint16_t index;
126   uint16_t generation;
127 } EnemyId;
128 
129 typedef struct EnemyClassConfig
130 {
131   float speed;
132   float health;
133   float radius;
134   float maxAcceleration;
135   float requiredContactTime;
136   float explosionDamage;
137   float explosionRange;
138   float explosionPushbackPower;
139   int goldValue;
140 } EnemyClassConfig;
141 
142 typedef struct Enemy
143 {
144   int16_t currentX, currentY;
145   int16_t nextX, nextY;
146   Vector2 simPosition;
147   Vector2 simVelocity;
148   uint16_t generation;
149   float walkedDistance;
150   float startMovingTime;
151   float damage, futureDamage;
152   float contactTime;
153   uint8_t enemyType;
154   uint8_t movePathCount;
155   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
156 } Enemy;
157 
158 // a unit that uses sprites to be drawn
159 typedef struct SpriteUnit
160 {
161   Rectangle srcRect;
162   Vector2 offset;
163   int frameCount;
164   float frameDuration;
165 } SpriteUnit;
166 
167 #define PROJECTILE_MAX_COUNT 1200
168 #define PROJECTILE_TYPE_NONE 0
169 #define PROJECTILE_TYPE_ARROW 1
170 
171 typedef struct Projectile
172 {
173   uint8_t projectileType;
174   float shootTime;
175   float arrivalTime;
176   float distance;
177   float damage;
178   Vector3 position;
179   Vector3 target;
180   Vector3 directionNormal;
181   EnemyId targetEnemy;
182 } Projectile;
183 
184 //# Function declarations
185 float TowerGetMaxHealth(Tower *tower);
186 int Button(const char *text, int x, int y, int width, int height, ButtonState *state);
187 int EnemyAddDamage(Enemy *enemy, float damage);
188 
189 //# Enemy functions
190 void EnemyInit();
191 void EnemyDraw();
192 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource);
193 void EnemyUpdate();
194 float EnemyGetCurrentMaxSpeed(Enemy *enemy);
195 float EnemyGetMaxHealth(Enemy *enemy);
196 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY);
197 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount);
198 EnemyId EnemyGetId(Enemy *enemy);
199 Enemy *EnemyTryResolve(EnemyId enemyId);
200 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY);
201 int EnemyAddDamage(Enemy *enemy, float damage);
202 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range);
203 int EnemyCount();
204 void EnemyDrawHealthbars(Camera3D camera);
205 
206 //# Tower functions
207 void TowerInit();
208 Tower *TowerGetAt(int16_t x, int16_t y);
209 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y);
210 Tower *GetTowerByType(uint8_t towerType);
211 int GetTowerCosts(uint8_t towerType);
212 float TowerGetMaxHealth(Tower *tower);
213 void TowerDraw();
214 void TowerUpdate();
215 void TowerDrawHealthBars(Camera3D camera);
216 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t);
217 
218 //# Particles
219 void ParticleInit();
220 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime);
221 void ParticleUpdate();
222 void ParticleDraw();
223 
224 //# Projectiles
225 void ProjectileInit();
226 void ProjectileDraw();
227 void ProjectileUpdate();
228 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, float damage);
229 
230 //# Pathfinding map
231 void PathfindingMapInit(int width, int height, Vector3 translate, float scale);
232 float PathFindingGetDistance(int mapX, int mapY);
233 Vector2 PathFindingGetGradient(Vector3 world);
234 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY);
235 void PathFindingMapUpdate();
236 void PathFindingMapDraw();
237 
238 //# UI
239 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor);
240 
241 //# variables
242 extern Level *currentLevel;
243 extern Enemy enemies[ENEMY_MAX_COUNT];
244 extern int enemyCount;
245 extern EnemyClassConfig enemyClassConfigs[];
246 
247 extern GUIState guiState;
248 extern GameTime gameTime;
249 extern Tower towers[TOWER_MAX_COUNT];
250 extern int towerCount;
251 
252 #endif  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 EnemyClassConfig enemyClassConfigs[] = {
  7     [ENEMY_TYPE_MINION] = {
  8       .health = 10.0f, 
  9       .speed = 0.6f, 
 10       .radius = 0.25f, 
 11       .maxAcceleration = 1.0f,
 12       .explosionDamage = 1.0f,
 13       .requiredContactTime = 0.5f,
 14       .explosionRange = 1.0f,
 15       .explosionPushbackPower = 0.25f,
 16       .goldValue = 1,
 17     },
 18 };
 19 
 20 Enemy enemies[ENEMY_MAX_COUNT];
 21 int enemyCount = 0;
 22 
 23 SpriteUnit enemySprites[] = {
 24     [ENEMY_TYPE_MINION] = {
 25       .srcRect = {0, 16, 16, 16},
 26       .offset = {8.0f, 0.0f},
 27       .frameCount = 6,
 28       .frameDuration = 0.1f,
 29     },
 30 };
 31 
 32 void EnemyInit()
 33 {
 34   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 35   {
 36     enemies[i] = (Enemy){0};
 37   }
 38   enemyCount = 0;
 39 }
 40 
 41 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
 42 {
 43   return enemyClassConfigs[enemy->enemyType].speed;
 44 }
 45 
 46 float EnemyGetMaxHealth(Enemy *enemy)
 47 {
 48   return enemyClassConfigs[enemy->enemyType].health;
 49 }
 50 
 51 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
 52 {
 53   int16_t castleX = 0;
 54   int16_t castleY = 0;
 55   int16_t dx = castleX - currentX;
 56   int16_t dy = castleY - currentY;
 57   if (dx == 0 && dy == 0)
 58   {
 59     *nextX = currentX;
 60     *nextY = currentY;
 61     return 1;
 62   }
 63   Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY});
 64 
 65   if (gradient.x == 0 && gradient.y == 0)
 66   {
 67     *nextX = currentX;
 68     *nextY = currentY;
 69     return 1;
 70   }
 71 
 72   if (fabsf(gradient.x) > fabsf(gradient.y))
 73   {
 74     *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1);
 75     *nextY = currentY;
 76     return 0;
 77   }
 78   *nextX = currentX;
 79   *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1);
 80   return 0;
 81 }
 82 
 83 
 84 // this function predicts the movement of the unit for the next deltaT seconds
 85 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
 86 {
 87   const float pointReachedDistance = 0.25f;
 88   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
 89   const float maxSimStepTime = 0.015625f;
 90   
 91   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
 92   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
 93   int16_t nextX = enemy->nextX;
 94   int16_t nextY = enemy->nextY;
 95   Vector2 position = enemy->simPosition;
 96   int passedCount = 0;
 97   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
 98   {
 99     float stepTime = fminf(deltaT - t, maxSimStepTime);
100     Vector2 target = (Vector2){nextX, nextY};
101     float speed = Vector2Length(*velocity);
102     // draw the target position for debugging
103     DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
104     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
105     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
106     {
107       // we reached the target position, let's move to the next waypoint
108       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
109       target = (Vector2){nextX, nextY};
110       // track how many waypoints we passed
111       passedCount++;
112     }
113     
114     // acceleration towards the target
115     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
116     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
117     *velocity = Vector2Add(*velocity, acceleration);
118 
119     // limit the speed to the maximum speed
120     if (speed > maxSpeed)
121     {
122       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
123     }
124 
125     // move the enemy
126     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
127   }
128 
129   if (waypointPassedCount)
130   {
131     (*waypointPassedCount) = passedCount;
132   }
133 
134   return position;
135 }
136 
137 void EnemyDraw()
138 {
139   for (int i = 0; i < enemyCount; i++)
140   {
141     Enemy enemy = enemies[i];
142     if (enemy.enemyType == ENEMY_TYPE_NONE)
143     {
144       continue;
145     }
146 
147     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
148     
149     // don't draw any trails for now; might replace this with footprints later
150     // if (enemy.movePathCount > 0)
151     // {
152     //   Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
153     //   DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
154     // }
155     // for (int j = 1; j < enemy.movePathCount; j++)
156     // {
157     //   Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
158     //   Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
159     //   DrawLine3D(p, q, GREEN);
160     // }
161 
162     switch (enemy.enemyType)
163     {
164     case ENEMY_TYPE_MINION:
165       DrawSpriteUnit(enemySprites[ENEMY_TYPE_MINION], (Vector3){position.x, 0.0f, position.y}, 
166         enemy.walkedDistance);
167       break;
168     }
169   }
170 }
171 
172 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource)
173 {
174   // damage the tower
175   float explosionDamge = enemyClassConfigs[enemy->enemyType].explosionDamage;
176   float explosionRange = enemyClassConfigs[enemy->enemyType].explosionRange;
177   float explosionPushbackPower = enemyClassConfigs[enemy->enemyType].explosionPushbackPower;
178   float explosionRange2 = explosionRange * explosionRange;
179   tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage;
180   // explode the enemy
181   if (tower->damage >= TowerGetMaxHealth(tower))
182   {
183     tower->towerType = TOWER_TYPE_NONE;
184   }
185 
186   ParticleAdd(PARTICLE_TYPE_EXPLOSION, 
187     explosionSource, 
188     (Vector3){0, 0.1f, 0}, 1.0f);
189 
190   enemy->enemyType = ENEMY_TYPE_NONE;
191 
192   // push back enemies & dealing damage
193   for (int i = 0; i < enemyCount; i++)
194   {
195     Enemy *other = &enemies[i];
196     if (other->enemyType == ENEMY_TYPE_NONE)
197     {
198       continue;
199     }
200     float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition);
201     if (distanceSqr > 0 && distanceSqr < explosionRange2)
202     {
203       Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition));
204       other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower));
205       EnemyAddDamage(other, explosionDamge);
206     }
207   }
208 }
209 
210 void EnemyUpdate()
211 {
212   const float castleX = 0;
213   const float castleY = 0;
214   const float maxPathDistance2 = 0.25f * 0.25f;
215   
216   for (int i = 0; i < enemyCount; i++)
217   {
218     Enemy *enemy = &enemies[i];
219     if (enemy->enemyType == ENEMY_TYPE_NONE)
220     {
221       continue;
222     }
223 
224     int waypointPassedCount = 0;
225     Vector2 prevPosition = enemy->simPosition;
226     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
227     enemy->startMovingTime = gameTime.time;
228     enemy->walkedDistance += Vector2Distance(prevPosition, enemy->simPosition);
229     // track path of unit
230     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
231     {
232       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
233       {
234         enemy->movePath[j] = enemy->movePath[j - 1];
235       }
236       enemy->movePath[0] = enemy->simPosition;
237       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
238       {
239         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
240       }
241     }
242 
243     if (waypointPassedCount > 0)
244     {
245       enemy->currentX = enemy->nextX;
246       enemy->currentY = enemy->nextY;
247       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) &&
248         Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
249       {
250         // enemy reached the castle; remove it
251         enemy->enemyType = ENEMY_TYPE_NONE;
252         continue;
253       }
254     }
255   }
256 
257   // handle collisions between enemies
258   for (int i = 0; i < enemyCount - 1; i++)
259   {
260     Enemy *enemyA = &enemies[i];
261     if (enemyA->enemyType == ENEMY_TYPE_NONE)
262     {
263       continue;
264     }
265     for (int j = i + 1; j < enemyCount; j++)
266     {
267       Enemy *enemyB = &enemies[j];
268       if (enemyB->enemyType == ENEMY_TYPE_NONE)
269       {
270         continue;
271       }
272       float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition);
273       float radiusA = enemyClassConfigs[enemyA->enemyType].radius;
274       float radiusB = enemyClassConfigs[enemyB->enemyType].radius;
275       float radiusSum = radiusA + radiusB;
276       if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f)
277       {
278         // collision
279         float distance = sqrtf(distanceSqr);
280         float overlap = radiusSum - distance;
281         // move the enemies apart, but softly; if we have a clog of enemies,
282         // moving them perfectly apart can cause them to jitter
283         float positionCorrection = overlap / 5.0f;
284         Vector2 direction = (Vector2){
285             (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection,
286             (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection};
287         enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction);
288         enemyB->simPosition = Vector2Add(enemyB->simPosition, direction);
289       }
290     }
291   }
292 
293   // handle collisions between enemies and towers
294   for (int i = 0; i < enemyCount; i++)
295   {
296     Enemy *enemy = &enemies[i];
297     if (enemy->enemyType == ENEMY_TYPE_NONE)
298     {
299       continue;
300     }
301     enemy->contactTime -= gameTime.deltaTime;
302     if (enemy->contactTime < 0.0f)
303     {
304       enemy->contactTime = 0.0f;
305     }
306 
307     float enemyRadius = enemyClassConfigs[enemy->enemyType].radius;
308     // linear search over towers; could be optimized by using path finding tower map,
309     // but for now, we keep it simple
310     for (int j = 0; j < towerCount; j++)
311     {
312       Tower *tower = &towers[j];
313       if (tower->towerType == TOWER_TYPE_NONE)
314       {
315         continue;
316       }
317       float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y});
318       float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1
319       if (distanceSqr > combinedRadius * combinedRadius)
320       {
321         continue;
322       }
323       // potential collision; square / circle intersection
324       float dx = tower->x - enemy->simPosition.x;
325       float dy = tower->y - enemy->simPosition.y;
326       float absDx = fabsf(dx);
327       float absDy = fabsf(dy);
328       Vector3 contactPoint = {0};
329       if (absDx <= 0.5f && absDx <= absDy) {
330         // vertical collision; push the enemy out horizontally
331         float overlap = enemyRadius + 0.5f - absDy;
332         if (overlap < 0.0f)
333         {
334           continue;
335         }
336         float direction = dy > 0.0f ? -1.0f : 1.0f;
337         enemy->simPosition.y += direction * overlap;
338         contactPoint = (Vector3){enemy->simPosition.x, 0.2f, tower->y + direction * 0.5f};
339       }
340       else if (absDy <= 0.5f && absDy <= absDx)
341       {
342         // horizontal collision; push the enemy out vertically
343         float overlap = enemyRadius + 0.5f - absDx;
344         if (overlap < 0.0f)
345         {
346           continue;
347         }
348         float direction = dx > 0.0f ? -1.0f : 1.0f;
349         enemy->simPosition.x += direction * overlap;
350         contactPoint = (Vector3){tower->x + direction * 0.5f, 0.2f, enemy->simPosition.y};
351       }
352       else
353       {
354         // possible collision with a corner
355         float cornerDX = dx > 0.0f ? -0.5f : 0.5f;
356         float cornerDY = dy > 0.0f ? -0.5f : 0.5f;
357         float cornerX = tower->x + cornerDX;
358         float cornerY = tower->y + cornerDY;
359         float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY});
360         if (cornerDistanceSqr > enemyRadius * enemyRadius)
361         {
362           continue;
363         }
364         // push the enemy out along the diagonal
365         float cornerDistance = sqrtf(cornerDistanceSqr);
366         float overlap = enemyRadius - cornerDistance;
367         float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX;
368         float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY;
369         enemy->simPosition.x -= directionX * overlap;
370         enemy->simPosition.y -= directionY * overlap;
371         contactPoint = (Vector3){cornerX, 0.2f, cornerY};
372       }
373 
374       if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f)
375       {
376         enemy->contactTime += gameTime.deltaTime * 2.0f; // * 2 to undo the subtraction above
377         if (enemy->contactTime >= enemyClassConfigs[enemy->enemyType].requiredContactTime)
378         {
379           EnemyTriggerExplode(enemy, tower, contactPoint);
380         }
381       }
382     }
383   }
384 }
385 
386 EnemyId EnemyGetId(Enemy *enemy)
387 {
388   return (EnemyId){enemy - enemies, enemy->generation};
389 }
390 
391 Enemy *EnemyTryResolve(EnemyId enemyId)
392 {
393   if (enemyId.index >= ENEMY_MAX_COUNT)
394   {
395     return 0;
396   }
397   Enemy *enemy = &enemies[enemyId.index];
398   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
399   {
400     return 0;
401   }
402   return enemy;
403 }
404 
405 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
406 {
407   Enemy *spawn = 0;
408   for (int i = 0; i < enemyCount; i++)
409   {
410     Enemy *enemy = &enemies[i];
411     if (enemy->enemyType == ENEMY_TYPE_NONE)
412     {
413       spawn = enemy;
414       break;
415     }
416   }
417 
418   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
419   {
420     spawn = &enemies[enemyCount++];
421   }
422 
423   if (spawn)
424   {
425     spawn->currentX = currentX;
426     spawn->currentY = currentY;
427     spawn->nextX = currentX;
428     spawn->nextY = currentY;
429     spawn->simPosition = (Vector2){currentX, currentY};
430     spawn->simVelocity = (Vector2){0, 0};
431     spawn->enemyType = enemyType;
432     spawn->startMovingTime = gameTime.time;
433     spawn->damage = 0.0f;
434     spawn->futureDamage = 0.0f;
435     spawn->generation++;
436     spawn->movePathCount = 0;
437     spawn->walkedDistance = 0.0f;
438   }
439 
440   return spawn;
441 }
442 
443 int EnemyAddDamage(Enemy *enemy, float damage)
444 {
445   enemy->damage += damage;
446   if (enemy->damage >= EnemyGetMaxHealth(enemy))
447   {
448     currentLevel->playerGold += enemyClassConfigs[enemy->enemyType].goldValue;
449     enemy->enemyType = ENEMY_TYPE_NONE;
450     return 1;
451   }
452 
453   return 0;
454 }
455 
456 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
457 {
458   int16_t castleX = 0;
459   int16_t castleY = 0;
460   Enemy* closest = 0;
461   int16_t closestDistance = 0;
462   float range2 = range * range;
463   for (int i = 0; i < enemyCount; i++)
464   {
465     Enemy* enemy = &enemies[i];
466     if (enemy->enemyType == ENEMY_TYPE_NONE)
467     {
468       continue;
469     }
470     float maxHealth = EnemyGetMaxHealth(enemy);
471     if (enemy->futureDamage >= maxHealth)
472     {
473       // ignore enemies that will die soon
474       continue;
475     }
476     int16_t dx = castleX - enemy->currentX;
477     int16_t dy = castleY - enemy->currentY;
478     int16_t distance = abs(dx) + abs(dy);
479     if (!closest || distance < closestDistance)
480     {
481       float tdx = towerX - enemy->currentX;
482       float tdy = towerY - enemy->currentY;
483       float tdistance2 = tdx * tdx + tdy * tdy;
484       if (tdistance2 <= range2)
485       {
486         closest = enemy;
487         closestDistance = distance;
488       }
489     }
490   }
491   return closest;
492 }
493 
494 int EnemyCount()
495 {
496   int count = 0;
497   for (int i = 0; i < enemyCount; i++)
498   {
499     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
500     {
501       count++;
502     }
503   }
504   return count;
505 }
506 
507 void EnemyDrawHealthbars(Camera3D camera)
508 {
509   for (int i = 0; i < enemyCount; i++)
510   {
511     Enemy *enemy = &enemies[i];
512     if (enemy->enemyType == ENEMY_TYPE_NONE || enemy->damage == 0.0f)
513     {
514       continue;
515     }
516     Vector3 position = (Vector3){enemy->simPosition.x, 0.5f, enemy->simPosition.y};
517     float maxHealth = EnemyGetMaxHealth(enemy);
518     float health = maxHealth - enemy->damage;
519     float healthRatio = health / maxHealth;
520     
521     DrawHealthBar(camera, position, healthRatio, GREEN);
522   }
523 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 Tower towers[TOWER_MAX_COUNT];
  5 int towerCount = 0;
  6 
  7 Model towerModels[TOWER_TYPE_COUNT];
  8 Texture2D palette, spriteSheet;
  9 
 10 // definition of our archer unit
 11 SpriteUnit archerUnit = {
 12     .srcRect = {0, 0, 16, 16},
 13     .offset = {7, 1},
 14 };
 15 
 16 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t)
 17 {
 18   Camera3D camera = currentLevel->camera;
 19   float size = 0.5f;
 20   Vector2 offset = (Vector2){ unit.offset.x / 16.0f * size, unit.offset.y / 16.0f * size };
 21   Vector2 scale = (Vector2){ unit.srcRect.width / 16.0f * size, unit.srcRect.height / 16.0f * size };
 22   // we want the sprite to face the camera, so we need to calculate the up vector
 23   Vector3 forward = Vector3Subtract(camera.target, camera.position);
 24   Vector3 up = {0, 1, 0};
 25   Vector3 right = Vector3CrossProduct(forward, up);
 26   up = Vector3Normalize(Vector3CrossProduct(right, forward));
 27 
 28   Rectangle srcRect = unit.srcRect;
 29   if (unit.frameCount > 1)
 30   {
 31     srcRect.x += (int)(t / unit.frameDuration) % unit.frameCount * srcRect.width;
 32   }
 33 
 34   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 35 }
 36 
 37 void TowerInit()
 38 {
 39   for (int i = 0; i < TOWER_MAX_COUNT; i++)
 40   {
 41     towers[i] = (Tower){0};
 42   }
 43   towerCount = 0;
 44 
 45   // load a sprite sheet that contains all units
 46   spriteSheet = LoadTexture("data/spritesheet.png");
 47 
 48   // we'll use a palette texture to colorize the all buildings and environment art
 49   palette = LoadTexture("data/palette.png");
 50   // The texture uses gradients on very small space, so we'll enable bilinear filtering
 51   SetTextureFilter(palette, TEXTURE_FILTER_BILINEAR);
 52 
 53   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
 54   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
 55 
 56   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
 57   {
 58     if (towerModels[i].materials)
 59     {
 60       // assign the palette texture to the material of the model (0 is not used afaik)
 61       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 62     }
 63   }
 64 }
 65 
 66 static void TowerGunUpdate(Tower *tower)
 67 {
 68   if (tower->cooldown <= 0)
 69   {
 70     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
 71     if (enemy)
 72     {
 73       tower->cooldown = 0.5f;
 74       // shoot the enemy; determine future position of the enemy
 75       float bulletSpeed = 4.0f;
 76       float bulletDamage = 3.0f;
 77       Vector2 velocity = enemy->simVelocity;
 78       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
 79       Vector2 towerPosition = {tower->x, tower->y};
 80       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
 81       for (int i = 0; i < 8; i++) {
 82         velocity = enemy->simVelocity;
 83         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
 84         float distance = Vector2Distance(towerPosition, futurePosition);
 85         float eta2 = distance / bulletSpeed;
 86         if (fabs(eta - eta2) < 0.01f) {
 87           break;
 88         }
 89         eta = (eta2 + eta) * 0.5f;
 90       }
 91       ProjectileTryAdd(PROJECTILE_TYPE_ARROW, enemy, 
 92         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
 93         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
 94         bulletSpeed, bulletDamage);
 95       enemy->futureDamage += bulletDamage;
 96     }
 97   }
 98   else
 99   {
100     tower->cooldown -= gameTime.deltaTime;
101   }
102 }
103 
104 Tower *TowerGetAt(int16_t x, int16_t y)
105 {
106   for (int i = 0; i < towerCount; i++)
107   {
108     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
109     {
110       return &towers[i];
111     }
112   }
113   return 0;
114 }
115 
116 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
117 {
118   if (towerCount >= TOWER_MAX_COUNT)
119   {
120     return 0;
121   }
122 
123   Tower *tower = TowerGetAt(x, y);
124   if (tower)
125   {
126     return 0;
127   }
128 
129   tower = &towers[towerCount++];
130   tower->x = x;
131   tower->y = y;
132   tower->towerType = towerType;
133   tower->cooldown = 0.0f;
134   tower->damage = 0.0f;
135   return tower;
136 }
137 
138 Tower *GetTowerByType(uint8_t towerType)
139 {
140   for (int i = 0; i < towerCount; i++)
141   {
142     if (towers[i].towerType == towerType)
143     {
144       return &towers[i];
145     }
146   }
147   return 0;
148 }
149 
150 int GetTowerCosts(uint8_t towerType)
151 {
152   switch (towerType)
153   {
154   case TOWER_TYPE_BASE:
155     return 0;
156   case TOWER_TYPE_GUN:
157     return 6;
158   case TOWER_TYPE_WALL:
159     return 2;
160   }
161   return 0;
162 }
163 
164 float TowerGetMaxHealth(Tower *tower)
165 {
166   switch (tower->towerType)
167   {
168   case TOWER_TYPE_BASE:
169     return 10.0f;
170   case TOWER_TYPE_GUN:
171     return 3.0f;
172   case TOWER_TYPE_WALL:
173     return 5.0f;
174   }
175   return 0.0f;
176 }
177 
178 void TowerDraw()
179 {
180   for (int i = 0; i < towerCount; i++)
181   {
182     Tower tower = towers[i];
183     if (tower.towerType == TOWER_TYPE_NONE)
184     {
185       continue;
186     }
187 
188     switch (tower.towerType)
189     {
190     case TOWER_TYPE_GUN:
191       DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
192       DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0);
193       break;
194     default:
195       if (towerModels[tower.towerType].materials)
196       {
197         DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
198       } else {
199         DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
200       }
201       break;
202     }
203   }
204 }
205 
206 void TowerUpdate()
207 {
208   for (int i = 0; i < towerCount; i++)
209   {
210     Tower *tower = &towers[i];
211     switch (tower->towerType)
212     {
213     case TOWER_TYPE_GUN:
214       TowerGunUpdate(tower);
215       break;
216     }
217   }
218 }
219 
220 void TowerDrawHealthBars(Camera3D camera)
221 {
222   for (int i = 0; i < towerCount; i++)
223   {
224     Tower *tower = &towers[i];
225     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
226     {
227       continue;
228     }
229     
230     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
231     float maxHealth = TowerGetMaxHealth(tower);
232     float health = maxHealth - tower->damage;
233     float healthRatio = health / maxHealth;
234     
235     DrawHealthBar(camera, position, healthRatio, GREEN);
236   }
237 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 // The queue is a simple array of nodes, we add nodes to the end and remove
  5 // nodes from the front. We keep the array around to avoid unnecessary allocations
  6 static PathfindingNode *pathfindingNodeQueue = 0;
  7 static int pathfindingNodeQueueCount = 0;
  8 static int pathfindingNodeQueueCapacity = 0;
  9 
 10 // The pathfinding map stores the distances from the castle to each cell in the map.
 11 static PathfindingMap pathfindingMap = {0};
 12 
 13 void PathfindingMapInit(int width, int height, Vector3 translate, float scale)
 14 {
 15   // transforming between map space and world space allows us to adapt 
 16   // position and scale of the map without changing the pathfinding data
 17   pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z);
 18   pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale));
 19   pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace);
 20   pathfindingMap.width = width;
 21   pathfindingMap.height = height;
 22   pathfindingMap.scale = scale;
 23   pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float));
 24   for (int i = 0; i < width * height; i++)
 25   {
 26     pathfindingMap.distances[i] = -1.0f;
 27   }
 28 
 29   pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long));
 30   pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc));
 31 }
 32 
 33 static void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance)
 34 {
 35   if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity)
 36   {
 37     pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2;
 38     // we use MemAlloc/MemRealloc to allocate memory for the queue
 39     // I am not entirely sure if MemRealloc allows passing a null pointer
 40     // so we check if the pointer is null and use MemAlloc in that case
 41     if (pathfindingNodeQueue == 0)
 42     {
 43       pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 44     }
 45     else
 46     {
 47       pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 48     }
 49   }
 50 
 51   PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++];
 52   node->x = x;
 53   node->y = y;
 54   node->fromX = fromX;
 55   node->fromY = fromY;
 56   node->distance = distance;
 57 }
 58 
 59 static PathfindingNode *PathFindingNodePop()
 60 {
 61   if (pathfindingNodeQueueCount == 0)
 62   {
 63     return 0;
 64   }
 65   // we return the first node in the queue; we want to return a pointer to the node
 66   // so we can return 0 if the queue is empty. 
 67   // We should _not_ return a pointer to the element in the list, because the list
 68   // may be reallocated and the pointer would become invalid. Or the 
 69   // popped element is overwritten by the next push operation.
 70   // Using static here means that the variable is permanently allocated.
 71   static PathfindingNode node;
 72   node = pathfindingNodeQueue[0];
 73   // we shift all nodes one position to the front
 74   for (int i = 1; i < pathfindingNodeQueueCount; i++)
 75   {
 76     pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i];
 77   }
 78   --pathfindingNodeQueueCount;
 79   return &node;
 80 }
 81 
 82 float PathFindingGetDistance(int mapX, int mapY)
 83 {
 84   if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height)
 85   {
 86     // when outside the map, we return the manhattan distance to the castle (0,0)
 87     return fabsf((float)mapX) + fabsf((float)mapY);
 88   }
 89 
 90   return pathfindingMap.distances[mapY * pathfindingMap.width + mapX];
 91 }
 92 
 93 // transform a world position to a map position in the array; 
 94 // returns true if the position is inside the map
 95 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY)
 96 {
 97   Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace);
 98   *mapX = (int16_t)mapPosition.x;
 99   *mapY = (int16_t)mapPosition.z;
100   return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height;
101 }
102 
103 void PathFindingMapUpdate()
104 {
105   const int castleX = 0, castleY = 0;
106   int16_t castleMapX, castleMapY;
107   if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY))
108   {
109     return;
110   }
111   int width = pathfindingMap.width, height = pathfindingMap.height;
112 
113   // reset the distances to -1
114   for (int i = 0; i < width * height; i++)
115   {
116     pathfindingMap.distances[i] = -1.0f;
117   }
118   // reset the tower indices
119   for (int i = 0; i < width * height; i++)
120   {
121     pathfindingMap.towerIndex[i] = -1;
122   }
123   // reset the delta src
124   for (int i = 0; i < width * height; i++)
125   {
126     pathfindingMap.deltaSrc[i].x = 0;
127     pathfindingMap.deltaSrc[i].y = 0;
128   }
129 
130   for (int i = 0; i < towerCount; i++)
131   {
132     Tower *tower = &towers[i];
133     if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE)
134     {
135       continue;
136     }
137     int16_t mapX, mapY;
138     // technically, if the tower cell scale is not in sync with the pathfinding map scale,
139     // this would not work correctly and needs to be refined to allow towers covering multiple cells
140     // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly
141     // one cell. For now.
142     if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY))
143     {
144       continue;
145     }
146     int index = mapY * width + mapX;
147     pathfindingMap.towerIndex[index] = i;
148   }
149 
150   // we start at the castle and add the castle to the queue
151   pathfindingMap.maxDistance = 0.0f;
152   pathfindingNodeQueueCount = 0;
153   PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f);
154   PathfindingNode *node = 0;
155   while ((node = PathFindingNodePop()))
156   {
157     if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height)
158     {
159       continue;
160     }
161     int index = node->y * width + node->x;
162     if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance)
163     {
164       continue;
165     }
166 
167     int deltaX = node->x - node->fromX;
168     int deltaY = node->y - node->fromY;
169     // even if the cell is blocked by a tower, we still may want to store the direction
170     // (though this might not be needed, IDK right now)
171     pathfindingMap.deltaSrc[index].x = (char) deltaX;
172     pathfindingMap.deltaSrc[index].y = (char) deltaY;
173 
174     // we skip nodes that are blocked by towers
175     if (pathfindingMap.towerIndex[index] >= 0)
176     {
177       node->distance += 8.0f;
178     }
179     pathfindingMap.distances[index] = node->distance;
180     pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance);
181     PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f);
182     PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f);
183     PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f);
184     PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f);
185   }
186 }
187 
188 void PathFindingMapDraw()
189 {
190   float cellSize = pathfindingMap.scale * 0.9f;
191   float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance);
192   for (int x = 0; x < pathfindingMap.width; x++)
193   {
194     for (int y = 0; y < pathfindingMap.height; y++)
195     {
196       float distance = pathfindingMap.distances[y * pathfindingMap.width + x];
197       float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f);
198       Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255};
199       Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace);
200       // animate the distance "wave" to show how the pathfinding algorithm expands
201       // from the castle
202       if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance)
203       {
204         color = BLACK;
205       }
206       DrawCube(position, cellSize, 0.1f, cellSize, color);
207     }
208   }
209 }
210 
211 Vector2 PathFindingGetGradient(Vector3 world)
212 {
213   int16_t mapX, mapY;
214   if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY))
215   {
216     DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX];
217     return (Vector2){(float)-delta.x, (float)-delta.y};
218   }
219   // fallback to a simple gradient calculation
220   float n = PathFindingGetDistance(mapX, mapY - 1);
221   float s = PathFindingGetDistance(mapX, mapY + 1);
222   float w = PathFindingGetDistance(mapX - 1, mapY);
223   float e = PathFindingGetDistance(mapX + 1, mapY);
224   return (Vector2){w - e + 0.25f, n - s + 0.125f};
225 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static Projectile projectiles[PROJECTILE_MAX_COUNT];
  5 static int projectileCount = 0;
  6 
  7 void ProjectileInit()
  8 {
  9   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
 10   {
 11     projectiles[i] = (Projectile){0};
 12   }
 13 }
 14 
 15 void ProjectileDraw()
 16 {
 17   for (int i = 0; i < projectileCount; i++)
 18   {
 19     Projectile projectile = projectiles[i];
 20     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
 21     {
 22       continue;
 23     }
 24     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
 25     if (transition >= 1.0f)
 26     {
 27       continue;
 28     }
 29     for (float transitionOffset = 0.0f; transitionOffset < 1.0f; transitionOffset += 0.1f)
 30     {
 31       float t = transition + transitionOffset * 0.3f;
 32       if (t > 1.0f)
 33       {
 34         break;
 35       }
 36       Vector3 position = Vector3Lerp(projectile.position, projectile.target, t);
 37       Color color = RED;
 38       if (projectile.projectileType == PROJECTILE_TYPE_ARROW)
 39       {
 40         // make tip red but quickly fade to brown
 41         color = ColorLerp(BROWN, RED, transitionOffset * transitionOffset);
 42         // fake a ballista flight path using parabola equation
 43         float parabolaT = t - 0.5f;
 44         parabolaT = 1.0f - 4.0f * parabolaT * parabolaT;
 45         position.y += 0.15f * parabolaT * projectile.distance;
 46       }
 47 
 48       float size = 0.06f * (transitionOffset + 0.25f);
 49       DrawCube(position, size, size, size, color);
 50     }
 51   }
 52 }
 53 
 54 void ProjectileUpdate()
 55 {
 56   for (int i = 0; i < projectileCount; i++)
 57   {
 58     Projectile *projectile = &projectiles[i];
 59     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
 60     {
 61       continue;
 62     }
 63     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
 64     if (transition >= 1.0f)
 65     {
 66       projectile->projectileType = PROJECTILE_TYPE_NONE;
 67       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
 68       if (enemy)
 69       {
 70         EnemyAddDamage(enemy, projectile->damage);
 71       }
 72       continue;
 73     }
 74   }
 75 }
 76 
 77 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, float damage)
 78 {
 79   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
 80   {
 81     Projectile *projectile = &projectiles[i];
 82     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
 83     {
 84       projectile->projectileType = projectileType;
 85       projectile->shootTime = gameTime.time;
 86       float distance = Vector3Distance(position, target);
 87       projectile->arrivalTime = gameTime.time + distance / speed;
 88       projectile->damage = damage;
 89       projectile->position = position;
 90       projectile->target = target;
 91       projectile->directionNormal = Vector3Scale(Vector3Subtract(target, position), 1.0f / distance);
 92       projectile->distance = distance;
 93       projectile->targetEnemy = EnemyGetId(enemy);
 94       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
 95       return projectile;
 96     }
 97   }
 98   return 0;
 99 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static Particle particles[PARTICLE_MAX_COUNT];
  5 static int particleCount = 0;
  6 
  7 void ParticleInit()
  8 {
  9   for (int i = 0; i < PARTICLE_MAX_COUNT; i++)
 10   {
 11     particles[i] = (Particle){0};
 12   }
 13   particleCount = 0;
 14 }
 15 
 16 static void DrawExplosionParticle(Particle *particle, float transition)
 17 {
 18   float size = 1.2f * (1.0f - transition);
 19   Color startColor = WHITE;
 20   Color endColor = RED;
 21   Color color = ColorLerp(startColor, endColor, transition);
 22   DrawCube(particle->position, size, size, size, color);
 23 }
 24 
 25 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime)
 26 {
 27   if (particleCount >= PARTICLE_MAX_COUNT)
 28   {
 29     return;
 30   }
 31 
 32   int index = -1;
 33   for (int i = 0; i < particleCount; i++)
 34   {
 35     if (particles[i].particleType == PARTICLE_TYPE_NONE)
 36     {
 37       index = i;
 38       break;
 39     }
 40   }
 41 
 42   if (index == -1)
 43   {
 44     index = particleCount++;
 45   }
 46 
 47   Particle *particle = &particles[index];
 48   particle->particleType = particleType;
 49   particle->spawnTime = gameTime.time;
 50   particle->lifetime = lifetime;
 51   particle->position = position;
 52   particle->velocity = velocity;
 53 }
 54 
 55 void ParticleUpdate()
 56 {
 57   for (int i = 0; i < particleCount; i++)
 58   {
 59     Particle *particle = &particles[i];
 60     if (particle->particleType == PARTICLE_TYPE_NONE)
 61     {
 62       continue;
 63     }
 64 
 65     float age = gameTime.time - particle->spawnTime;
 66 
 67     if (particle->lifetime > age)
 68     {
 69       particle->position = Vector3Add(particle->position, Vector3Scale(particle->velocity, gameTime.deltaTime));
 70     }
 71     else {
 72       particle->particleType = PARTICLE_TYPE_NONE;
 73     }
 74   }
 75 }
 76 
 77 void ParticleDraw()
 78 {
 79   for (int i = 0; i < particleCount; i++)
 80   {
 81     Particle particle = particles[i];
 82     if (particle.particleType == PARTICLE_TYPE_NONE)
 83     {
 84       continue;
 85     }
 86 
 87     float age = gameTime.time - particle.spawnTime;
 88     float transition = age / particle.lifetime;
 89     switch (particle.particleType)
 90     {
 91     case PARTICLE_TYPE_EXPLOSION:
 92       DrawExplosionParticle(&particle, transition);
 93       break;
 94     default:
 95       DrawCube(particle.position, 0.3f, 0.5f, 0.3f, RED);
 96       break;
 97     }
 98   }
 99 }  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 #endifWhen you press "Begin waves", you can see the orcs walking animations - but without weapons. Remember that I removed the weapon from orc's spritesheet so we can later replace the weapons without having to change the animation frames of the orc. Let's quickly review these few scattered changes;
  1 // in td_main.h
  2 typedef struct Enemy
  3 {
  4   ...
  5   float walkedDistance;
  6   ...
  7 } Enemy;
  8 
  9 // a unit that uses sprites to be drawn
 10 typedef struct SpriteUnit
 11 {
 12   Rectangle srcRect;
 13   Vector2 offset;
 14   int frameCount;
 15   float frameDuration;
 16 } SpriteUnit;The SpriteUnit struct formerly had only the srcRect and offset, but now we added the frameCount and frameDuration. For the walking animation, the frameDuration maps to walked distance per frame. This can be seen here:
  1  // enemy.c
  2 switch (enemy.enemyType)
  3 {
  4 case ENEMY_TYPE_MINION:
  5   DrawSpriteUnit(enemySprites[ENEMY_TYPE_MINION], (Vector3){position.x, 0.0f, position.y}, 
  6     enemy.walkedDistance);
  7   break;
  8 }As pointed out above, the DrawSpriteUnit function is responsible for drawing the sprite, which is done by selecting the frame based on the walked distance:
  1 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t)
  2 {
  3   Camera3D camera = currentLevel->camera;
  4   float size = 0.5f;
  5   Vector2 offset = (Vector2){ unit.offset.x / 16.0f * size, unit.offset.y / 16.0f * size };
  6   Vector2 scale = (Vector2){ unit.srcRect.width / 16.0f * size, unit.srcRect.height / 16.0f * size };
  7   // we want the sprite to face the camera, so we need to calculate the up vector
  8   Vector3 forward = Vector3Subtract(camera.target, camera.position);
  9   Vector3 up = {0, 1, 0};
 10   Vector3 right = Vector3CrossProduct(forward, up);
 11   up = Vector3Normalize(Vector3CrossProduct(right, forward));
 12 
 13   Rectangle srcRect = unit.srcRect;
 14   if (unit.frameCount > 1)
 15   {
 16     srcRect.x += (int)(t / unit.frameDuration) % unit.frameCount * srcRect.width;
 17   }
 18 
 19   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 20 }All in all, this were not many changes to have a walking animation for the orcs.
Now for the archers, we'll have to extend the sprite unit struct a little more to support the carrying weapon information (the bow). We also have 2 different states for the archer: Loading and shooting. This is reflected in the phase variable.
  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 //# Variables
  7 GUIState guiState = {0};
  8 GameTime gameTime = {0};
  9 
 10 Level levels[] = {
 11   [0] = {
 12     .state = LEVEL_STATE_BUILDING,
 13     .initialGold = 20,
 14     .waves[0] = {
 15       .enemyType = ENEMY_TYPE_MINION,
 16       .wave = 0,
 17       .count = 10,
 18       .interval = 2.5f,
 19       .delay = 1.0f,
 20       .spawnPosition = {0, 6},
 21     },
 22     .waves[1] = {
 23       .enemyType = ENEMY_TYPE_MINION,
 24       .wave = 1,
 25       .count = 20,
 26       .interval = 1.5f,
 27       .delay = 1.0f,
 28       .spawnPosition = {0, 6},
 29     },
 30     .waves[2] = {
 31       .enemyType = ENEMY_TYPE_MINION,
 32       .wave = 2,
 33       .count = 30,
 34       .interval = 1.2f,
 35       .delay = 1.0f,
 36       .spawnPosition = {0, 6},
 37     }
 38   },
 39 };
 40 
 41 Level *currentLevel = levels;
 42 
 43 //# Game
 44 
 45 void InitLevel(Level *level)
 46 {
 47   TowerInit();
 48   EnemyInit();
 49   ProjectileInit();
 50   ParticleInit();
 51   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
 52 
 53   level->placementMode = 0;
 54   level->state = LEVEL_STATE_BUILDING;
 55   level->nextState = LEVEL_STATE_NONE;
 56   level->playerGold = level->initialGold;
 57   level->currentWave = 0;
 58 
 59   Camera *camera = &level->camera;
 60   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
 61   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
 62   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
 63   camera->fovy = 10.0f;
 64   camera->projection = CAMERA_ORTHOGRAPHIC;
 65 }
 66 
 67 void DrawLevelHud(Level *level)
 68 {
 69   const char *text = TextFormat("Gold: %d", level->playerGold);
 70   Font font = GetFontDefault();
 71   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
 72   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
 73 }
 74 
 75 void DrawLevelReportLostWave(Level *level)
 76 {
 77   BeginMode3D(level->camera);
 78   DrawGrid(10, 1.0f);
 79   TowerDraw();
 80   EnemyDraw();
 81   ProjectileDraw();
 82   ParticleDraw();
 83   guiState.isBlocked = 0;
 84   EndMode3D();
 85 
 86   TowerDrawHealthBars(level->camera);
 87 
 88   const char *text = "Wave lost";
 89   int textWidth = MeasureText(text, 20);
 90   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
 91 
 92   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
 93   {
 94     level->nextState = LEVEL_STATE_RESET;
 95   }
 96 }
 97 
 98 int HasLevelNextWave(Level *level)
 99 {
100   for (int i = 0; i < 10; i++)
101   {
102     EnemyWave *wave = &level->waves[i];
103     if (wave->wave == level->currentWave)
104     {
105       return 1;
106     }
107   }
108   return 0;
109 }
110 
111 void DrawLevelReportWonWave(Level *level)
112 {
113   BeginMode3D(level->camera);
114   DrawGrid(10, 1.0f);
115   TowerDraw();
116   EnemyDraw();
117   ProjectileDraw();
118   ParticleDraw();
119   guiState.isBlocked = 0;
120   EndMode3D();
121 
122   TowerDrawHealthBars(level->camera);
123 
124   const char *text = "Wave won";
125   int textWidth = MeasureText(text, 20);
126   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
127 
128 
129   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
130   {
131     level->nextState = LEVEL_STATE_RESET;
132   }
133 
134   if (HasLevelNextWave(level))
135   {
136     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
137     {
138       level->nextState = LEVEL_STATE_BUILDING;
139     }
140   }
141   else {
142     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
143     {
144       level->nextState = LEVEL_STATE_WON_LEVEL;
145     }
146   }
147 }
148 
149 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
150 {
151   static ButtonState buttonStates[8] = {0};
152   int cost = GetTowerCosts(towerType);
153   const char *text = TextFormat("%s: %d", name, cost);
154   buttonStates[towerType].isSelected = level->placementMode == towerType;
155   buttonStates[towerType].isDisabled = level->playerGold < cost;
156   if (Button(text, x, y, width, height, &buttonStates[towerType]))
157   {
158     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
159   }
160 }
161 
162 void DrawLevelBuildingState(Level *level)
163 {
164   BeginMode3D(level->camera);
165   DrawGrid(10, 1.0f);
166   TowerDraw();
167   EnemyDraw();
168   ProjectileDraw();
169   ParticleDraw();
170 
171   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
172   float planeDistance = ray.position.y / -ray.direction.y;
173   float planeX = ray.direction.x * planeDistance + ray.position.x;
174   float planeY = ray.direction.z * planeDistance + ray.position.z;
175   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
176   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
177   if (level->placementMode && !guiState.isBlocked)
178   {
179     DrawCubeWires((Vector3){mapX, 0.2f, mapY}, 1.0f, 0.4f, 1.0f, RED);
180     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
181     {
182       if (TowerTryAdd(level->placementMode, mapX, mapY))
183       {
184         level->playerGold -= GetTowerCosts(level->placementMode);
185         level->placementMode = TOWER_TYPE_NONE;
186       }
187     }
188   }
189 
190   guiState.isBlocked = 0;
191 
192   EndMode3D();
193 
194   TowerDrawHealthBars(level->camera);
195 
196   static ButtonState buildWallButtonState = {0};
197   static ButtonState buildGunButtonState = {0};
198   buildWallButtonState.isSelected = level->placementMode == TOWER_TYPE_WALL;
199   buildGunButtonState.isSelected = level->placementMode == TOWER_TYPE_GUN;
200 
201   DrawBuildingBuildButton(level, 10, 10, 80, 30, TOWER_TYPE_WALL, "Wall");
202   DrawBuildingBuildButton(level, 10, 50, 80, 30, TOWER_TYPE_GUN, "Gun");
203 
204   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
205   {
206     level->nextState = LEVEL_STATE_RESET;
207   }
208   
209   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
210   {
211     level->nextState = LEVEL_STATE_BATTLE;
212   }
213 
214   const char *text = "Building phase";
215   int textWidth = MeasureText(text, 20);
216   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
217 }
218 
219 void InitBattleStateConditions(Level *level)
220 {
221   level->state = LEVEL_STATE_BATTLE;
222   level->nextState = LEVEL_STATE_NONE;
223   level->waveEndTimer = 0.0f;
224   for (int i = 0; i < 10; i++)
225   {
226     EnemyWave *wave = &level->waves[i];
227     wave->spawned = 0;
228     wave->timeToSpawnNext = wave->delay;
229   }
230 }
231 
232 void DrawLevelBattleState(Level *level)
233 {
234   BeginMode3D(level->camera);
235   DrawGrid(10, 1.0f);
236   TowerDraw();
237   EnemyDraw();
238   ProjectileDraw();
239   ParticleDraw();
240   guiState.isBlocked = 0;
241   EndMode3D();
242 
243   EnemyDrawHealthbars(level->camera);
244   TowerDrawHealthBars(level->camera);
245 
246   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
247   {
248     level->nextState = LEVEL_STATE_RESET;
249   }
250 
251   int maxCount = 0;
252   int remainingCount = 0;
253   for (int i = 0; i < 10; i++)
254   {
255     EnemyWave *wave = &level->waves[i];
256     if (wave->wave != level->currentWave)
257     {
258       continue;
259     }
260     maxCount += wave->count;
261     remainingCount += wave->count - wave->spawned;
262   }
263   int aliveCount = EnemyCount();
264   remainingCount += aliveCount;
265 
266   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
267   int textWidth = MeasureText(text, 20);
268   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
269 }
270 
271 void DrawLevel(Level *level)
272 {
273   switch (level->state)
274   {
275     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
276     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
277     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
278     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
279     default: break;
280   }
281 
282   DrawLevelHud(level);
283 }
284 
285 void UpdateLevel(Level *level)
286 {
287   if (level->state == LEVEL_STATE_BATTLE)
288   {
289     int activeWaves = 0;
290     for (int i = 0; i < 10; i++)
291     {
292       EnemyWave *wave = &level->waves[i];
293       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
294       {
295         continue;
296       }
297       activeWaves++;
298       wave->timeToSpawnNext -= gameTime.deltaTime;
299       if (wave->timeToSpawnNext <= 0.0f)
300       {
301         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
302         if (enemy)
303         {
304           wave->timeToSpawnNext = wave->interval;
305           wave->spawned++;
306         }
307       }
308     }
309     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
310       level->waveEndTimer += gameTime.deltaTime;
311       if (level->waveEndTimer >= 2.0f)
312       {
313         level->nextState = LEVEL_STATE_LOST_WAVE;
314       }
315     }
316     else if (activeWaves == 0 && EnemyCount() == 0)
317     {
318       level->waveEndTimer += gameTime.deltaTime;
319       if (level->waveEndTimer >= 2.0f)
320       {
321         level->nextState = LEVEL_STATE_WON_WAVE;
322       }
323     }
324   }
325 
326   PathFindingMapUpdate();
327   EnemyUpdate();
328   TowerUpdate();
329   ProjectileUpdate();
330   ParticleUpdate();
331 
332   if (level->nextState == LEVEL_STATE_RESET)
333   {
334     InitLevel(level);
335   }
336   
337   if (level->nextState == LEVEL_STATE_BATTLE)
338   {
339     InitBattleStateConditions(level);
340   }
341   
342   if (level->nextState == LEVEL_STATE_WON_WAVE)
343   {
344     level->currentWave++;
345     level->state = LEVEL_STATE_WON_WAVE;
346   }
347   
348   if (level->nextState == LEVEL_STATE_LOST_WAVE)
349   {
350     level->state = LEVEL_STATE_LOST_WAVE;
351   }
352 
353   if (level->nextState == LEVEL_STATE_BUILDING)
354   {
355     level->state = LEVEL_STATE_BUILDING;
356   }
357 
358   if (level->nextState == LEVEL_STATE_WON_LEVEL)
359   {
360     // make something of this later
361     InitLevel(level);
362   }
363 
364   level->nextState = LEVEL_STATE_NONE;
365 }
366 
367 float nextSpawnTime = 0.0f;
368 
369 void ResetGame()
370 {
371   InitLevel(currentLevel);
372 }
373 
374 void InitGame()
375 {
376   TowerInit();
377   EnemyInit();
378   ProjectileInit();
379   ParticleInit();
380   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
381 
382   currentLevel = levels;
383   InitLevel(currentLevel);
384 }
385 
386 //# Immediate GUI functions
387 
388 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor)
389 {
390   const float healthBarWidth = 40.0f;
391   const float healthBarHeight = 6.0f;
392   const float healthBarOffset = 15.0f;
393   const float inset = 2.0f;
394   const float innerWidth = healthBarWidth - inset * 2;
395   const float innerHeight = healthBarHeight - inset * 2;
396 
397   Vector2 screenPos = GetWorldToScreen(position, camera);
398   float centerX = screenPos.x - healthBarWidth * 0.5f;
399   float topY = screenPos.y - healthBarOffset;
400   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
401   float healthWidth = innerWidth * healthRatio;
402   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
403 }
404 
405 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
406 {
407   Rectangle bounds = {x, y, width, height};
408   int isPressed = 0;
409   int isSelected = state && state->isSelected;
410   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !state->isDisabled)
411   {
412     Color color = isSelected ? DARKGRAY : GRAY;
413     DrawRectangle(x, y, width, height, color);
414     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
415     {
416       isPressed = 1;
417     }
418     guiState.isBlocked = 1;
419   }
420   else
421   {
422     Color color = isSelected ? WHITE : LIGHTGRAY;
423     DrawRectangle(x, y, width, height, color);
424   }
425   Font font = GetFontDefault();
426   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
427   Color textColor = state->isDisabled ? GRAY : BLACK;
428   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
429   return isPressed;
430 }
431 
432 //# Main game loop
433 
434 void GameUpdate()
435 {
436   float dt = GetFrameTime();
437   // cap maximum delta time to 0.1 seconds to prevent large time steps
438   if (dt > 0.1f) dt = 0.1f;
439   gameTime.time += dt;
440   gameTime.deltaTime = dt;
441 
442   UpdateLevel(currentLevel);
443 }
444 
445 int main(void)
446 {
447   int screenWidth, screenHeight;
448   GetPreferredSize(&screenWidth, &screenHeight);
449   InitWindow(screenWidth, screenHeight, "Tower defense");
450   SetTargetFPS(30);
451 
452   InitGame();
453 
454   while (!WindowShouldClose())
455   {
456     if (IsPaused()) {
457       // canvas is not visible in browser - do nothing
458       continue;
459     }
460 
461     BeginDrawing();
462     ClearBackground(DARKBLUE);
463 
464     GameUpdate();
465     DrawLevel(currentLevel);
466 
467     EndDrawing();
468   }
469 
470   CloseWindow();
471 
472   return 0;
473 }  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 //# Declarations
 10 
 11 #define ENEMY_MAX_PATH_COUNT 8
 12 #define ENEMY_MAX_COUNT 400
 13 #define ENEMY_TYPE_NONE 0
 14 #define ENEMY_TYPE_MINION 1
 15 
 16 #define PARTICLE_MAX_COUNT 400
 17 #define PARTICLE_TYPE_NONE 0
 18 #define PARTICLE_TYPE_EXPLOSION 1
 19 
 20 typedef struct Particle
 21 {
 22   uint8_t particleType;
 23   float spawnTime;
 24   float lifetime;
 25   Vector3 position;
 26   Vector3 velocity;
 27 } Particle;
 28 
 29 #define TOWER_MAX_COUNT 400
 30 #define TOWER_TYPE_NONE 0
 31 #define TOWER_TYPE_BASE 1
 32 #define TOWER_TYPE_GUN 2
 33 #define TOWER_TYPE_WALL 3
 34 #define TOWER_TYPE_COUNT 4
 35 
 36 typedef struct Tower
 37 {
 38   int16_t x, y;
 39   uint8_t towerType;
 40   float cooldown;
 41   float damage;
 42 } Tower;
 43 
 44 typedef struct GameTime
 45 {
 46   float time;
 47   float deltaTime;
 48 } GameTime;
 49 
 50 typedef struct ButtonState {
 51   char isSelected;
 52   char isDisabled;
 53 } ButtonState;
 54 
 55 typedef struct GUIState {
 56   int isBlocked;
 57 } GUIState;
 58 
 59 typedef enum LevelState
 60 {
 61   LEVEL_STATE_NONE,
 62   LEVEL_STATE_BUILDING,
 63   LEVEL_STATE_BATTLE,
 64   LEVEL_STATE_WON_WAVE,
 65   LEVEL_STATE_LOST_WAVE,
 66   LEVEL_STATE_WON_LEVEL,
 67   LEVEL_STATE_RESET,
 68 } LevelState;
 69 
 70 typedef struct EnemyWave {
 71   uint8_t enemyType;
 72   uint8_t wave;
 73   uint16_t count;
 74   float interval;
 75   float delay;
 76   Vector2 spawnPosition;
 77 
 78   uint16_t spawned;
 79   float timeToSpawnNext;
 80 } EnemyWave;
 81 
 82 typedef struct Level
 83 {
 84   LevelState state;
 85   LevelState nextState;
 86   Camera3D camera;
 87   int placementMode;
 88 
 89   int initialGold;
 90   int playerGold;
 91 
 92   EnemyWave waves[10];
 93   int currentWave;
 94   float waveEndTimer;
 95 } Level;
 96 
 97 typedef struct DeltaSrc
 98 {
 99   char x, y;
100 } DeltaSrc;
101 
102 typedef struct PathfindingMap
103 {
104   int width, height;
105   float scale;
106   float *distances;
107   long *towerIndex; 
108   DeltaSrc *deltaSrc;
109   float maxDistance;
110   Matrix toMapSpace;
111   Matrix toWorldSpace;
112 } PathfindingMap;
113 
114 // when we execute the pathfinding algorithm, we need to store the active nodes
115 // in a queue. Each node has a position, a distance from the start, and the
116 // position of the node that we came from.
117 typedef struct PathfindingNode
118 {
119   int16_t x, y, fromX, fromY;
120   float distance;
121 } PathfindingNode;
122 
123 typedef struct EnemyId
124 {
125   uint16_t index;
126   uint16_t generation;
127 } EnemyId;
128 
129 typedef struct EnemyClassConfig
130 {
131   float speed;
132   float health;
133   float radius;
134   float maxAcceleration;
135   float requiredContactTime;
136   float explosionDamage;
137   float explosionRange;
138   float explosionPushbackPower;
139   int goldValue;
140 } EnemyClassConfig;
141 
142 typedef struct Enemy
143 {
144   int16_t currentX, currentY;
145   int16_t nextX, nextY;
146   Vector2 simPosition;
147   Vector2 simVelocity;
148   uint16_t generation;
149   float walkedDistance;
150   float startMovingTime;
151   float damage, futureDamage;
152   float contactTime;
153   uint8_t enemyType;
154   uint8_t movePathCount;
155   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
156 } Enemy;
157 
158 // a unit that uses sprites to be drawn
159 #define SPRITE_UNIT_PHASE_WEAPON_IDLE 0
160 #define SPRITE_UNIT_PHASE_WEAPON_COOLDOWN 1
161 typedef struct SpriteUnit
162 {
163   Rectangle srcRect;
164   Vector2 offset;
165   int frameCount;
166   float frameDuration;
167   Rectangle srcWeaponIdleRect;
168   Vector2 srcWeaponIdleOffset;
169   Rectangle srcWeaponCooldownRect;
170   Vector2 srcWeaponCooldownOffset;
171 } SpriteUnit;
172 
173 #define PROJECTILE_MAX_COUNT 1200
174 #define PROJECTILE_TYPE_NONE 0
175 #define PROJECTILE_TYPE_ARROW 1
176 
177 typedef struct Projectile
178 {
179   uint8_t projectileType;
180   float shootTime;
181   float arrivalTime;
182   float distance;
183   float damage;
184   Vector3 position;
185   Vector3 target;
186   Vector3 directionNormal;
187   EnemyId targetEnemy;
188 } Projectile;
189 
190 //# Function declarations
191 float TowerGetMaxHealth(Tower *tower);
192 int Button(const char *text, int x, int y, int width, int height, ButtonState *state);
193 int EnemyAddDamage(Enemy *enemy, float damage);
194 
195 //# Enemy functions
196 void EnemyInit();
197 void EnemyDraw();
198 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource);
199 void EnemyUpdate();
200 float EnemyGetCurrentMaxSpeed(Enemy *enemy);
201 float EnemyGetMaxHealth(Enemy *enemy);
202 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY);
203 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount);
204 EnemyId EnemyGetId(Enemy *enemy);
205 Enemy *EnemyTryResolve(EnemyId enemyId);
206 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY);
207 int EnemyAddDamage(Enemy *enemy, float damage);
208 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range);
209 int EnemyCount();
210 void EnemyDrawHealthbars(Camera3D camera);
211 
212 //# Tower functions
213 void TowerInit();
214 Tower *TowerGetAt(int16_t x, int16_t y);
215 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y);
216 Tower *GetTowerByType(uint8_t towerType);
217 int GetTowerCosts(uint8_t towerType);
218 float TowerGetMaxHealth(Tower *tower);
219 void TowerDraw();
220 void TowerUpdate();
221 void TowerDrawHealthBars(Camera3D camera);
222 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase);
223 
224 //# Particles
225 void ParticleInit();
226 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime);
227 void ParticleUpdate();
228 void ParticleDraw();
229 
230 //# Projectiles
231 void ProjectileInit();
232 void ProjectileDraw();
233 void ProjectileUpdate();
234 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, float damage);
235 
236 //# Pathfinding map
237 void PathfindingMapInit(int width, int height, Vector3 translate, float scale);
238 float PathFindingGetDistance(int mapX, int mapY);
239 Vector2 PathFindingGetGradient(Vector3 world);
240 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY);
241 void PathFindingMapUpdate();
242 void PathFindingMapDraw();
243 
244 //# UI
245 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor);
246 
247 //# variables
248 extern Level *currentLevel;
249 extern Enemy enemies[ENEMY_MAX_COUNT];
250 extern int enemyCount;
251 extern EnemyClassConfig enemyClassConfigs[];
252 
253 extern GUIState guiState;
254 extern GameTime gameTime;
255 extern Tower towers[TOWER_MAX_COUNT];
256 extern int towerCount;
257 
258 #endif  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 Tower towers[TOWER_MAX_COUNT];
  5 int towerCount = 0;
  6 
  7 Model towerModels[TOWER_TYPE_COUNT];
  8 Texture2D palette, spriteSheet;
  9 
 10 // definition of our archer unit
 11 SpriteUnit archerUnit = {
 12     .srcRect = {0, 0, 16, 16},
 13     .offset = {7, 1},
 14     .frameCount = 1,
 15     .frameDuration = 0.0f,
 16     .srcWeaponIdleRect = {16, 0, 6, 16},
 17     .srcWeaponIdleOffset = {8, 0},
 18     .srcWeaponCooldownRect = {22, 0, 11, 16},
 19     .srcWeaponCooldownOffset = {10, 0},
 20 };
 21 
 22 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
 23 {
 24   Camera3D camera = currentLevel->camera;
 25   float size = 0.5f;
 26   Vector2 offset = (Vector2){ unit.offset.x / 16.0f * size, unit.offset.y / 16.0f * size };
 27   Vector2 scale = (Vector2){ unit.srcRect.width / 16.0f * size, unit.srcRect.height / 16.0f * size };
 28   // we want the sprite to face the camera, so we need to calculate the up vector
 29   Vector3 forward = Vector3Subtract(camera.target, camera.position);
 30   Vector3 up = {0, 1, 0};
 31   Vector3 right = Vector3CrossProduct(forward, up);
 32   up = Vector3Normalize(Vector3CrossProduct(right, forward));
 33 
 34   Rectangle srcRect = unit.srcRect;
 35   if (unit.frameCount > 1)
 36   {
 37     srcRect.x += (int)(t / unit.frameDuration) % unit.frameCount * srcRect.width;
 38   }
 39 
 40   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 41 
 42   if (phase == SPRITE_UNIT_PHASE_WEAPON_COOLDOWN && unit.srcWeaponCooldownRect.width > 0)
 43   {
 44     offset = (Vector2){ unit.srcWeaponCooldownOffset.x / 16.0f * size, unit.srcWeaponCooldownOffset.y / 16.0f * size };
 45     scale = (Vector2){ unit.srcWeaponCooldownRect.width / 16.0f * size, unit.srcWeaponCooldownRect.height / 16.0f * size };
 46     DrawBillboardPro(camera, spriteSheet, unit.srcWeaponCooldownRect, position, up, scale, offset, 0, WHITE);
 47   }
 48   else if (phase == SPRITE_UNIT_PHASE_WEAPON_IDLE && unit.srcWeaponIdleRect.width > 0)
 49   {
 50     offset = (Vector2){ unit.srcWeaponIdleOffset.x / 16.0f * size, unit.srcWeaponIdleOffset.y / 16.0f * size };
 51     scale = (Vector2){ unit.srcWeaponIdleRect.width / 16.0f * size, unit.srcWeaponIdleRect.height / 16.0f * size };
 52     DrawBillboardPro(camera, spriteSheet, unit.srcWeaponIdleRect, position, up, scale, offset, 0, WHITE);
 53   }
 54 }
 55 
 56 void TowerInit()
 57 {
 58   for (int i = 0; i < TOWER_MAX_COUNT; i++)
 59   {
 60     towers[i] = (Tower){0};
 61   }
 62   towerCount = 0;
 63 
 64   // load a sprite sheet that contains all units
 65   spriteSheet = LoadTexture("data/spritesheet.png");
 66 
 67   // we'll use a palette texture to colorize the all buildings and environment art
 68   palette = LoadTexture("data/palette.png");
 69   // The texture uses gradients on very small space, so we'll enable bilinear filtering
 70   SetTextureFilter(palette, TEXTURE_FILTER_BILINEAR);
 71 
 72   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
 73   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
 74 
 75   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
 76   {
 77     if (towerModels[i].materials)
 78     {
 79       // assign the palette texture to the material of the model (0 is not used afaik)
 80       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 81     }
 82   }
 83 }
 84 
 85 static void TowerGunUpdate(Tower *tower)
 86 {
 87   if (tower->cooldown <= 0)
 88   {
 89     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
 90     if (enemy)
 91     {
 92       tower->cooldown = 0.5f;
 93       // shoot the enemy; determine future position of the enemy
 94       float bulletSpeed = 4.0f;
 95       float bulletDamage = 3.0f;
 96       Vector2 velocity = enemy->simVelocity;
 97       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
 98       Vector2 towerPosition = {tower->x, tower->y};
 99       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
100       for (int i = 0; i < 8; i++) {
101         velocity = enemy->simVelocity;
102         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
103         float distance = Vector2Distance(towerPosition, futurePosition);
104         float eta2 = distance / bulletSpeed;
105         if (fabs(eta - eta2) < 0.01f) {
106           break;
107         }
108         eta = (eta2 + eta) * 0.5f;
109       }
110       ProjectileTryAdd(PROJECTILE_TYPE_ARROW, enemy, 
111         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
112         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
113         bulletSpeed, bulletDamage);
114       enemy->futureDamage += bulletDamage;
115     }
116   }
117   else
118   {
119     tower->cooldown -= gameTime.deltaTime;
120   }
121 }
122 
123 Tower *TowerGetAt(int16_t x, int16_t y)
124 {
125   for (int i = 0; i < towerCount; i++)
126   {
127     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
128     {
129       return &towers[i];
130     }
131   }
132   return 0;
133 }
134 
135 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
136 {
137   if (towerCount >= TOWER_MAX_COUNT)
138   {
139     return 0;
140   }
141 
142   Tower *tower = TowerGetAt(x, y);
143   if (tower)
144   {
145     return 0;
146   }
147 
148   tower = &towers[towerCount++];
149   tower->x = x;
150   tower->y = y;
151   tower->towerType = towerType;
152   tower->cooldown = 0.0f;
153   tower->damage = 0.0f;
154   return tower;
155 }
156 
157 Tower *GetTowerByType(uint8_t towerType)
158 {
159   for (int i = 0; i < towerCount; i++)
160   {
161     if (towers[i].towerType == towerType)
162     {
163       return &towers[i];
164     }
165   }
166   return 0;
167 }
168 
169 int GetTowerCosts(uint8_t towerType)
170 {
171   switch (towerType)
172   {
173   case TOWER_TYPE_BASE:
174     return 0;
175   case TOWER_TYPE_GUN:
176     return 6;
177   case TOWER_TYPE_WALL:
178     return 2;
179   }
180   return 0;
181 }
182 
183 float TowerGetMaxHealth(Tower *tower)
184 {
185   switch (tower->towerType)
186   {
187   case TOWER_TYPE_BASE:
188     return 10.0f;
189   case TOWER_TYPE_GUN:
190     return 3.0f;
191   case TOWER_TYPE_WALL:
192     return 5.0f;
193   }
194   return 0.0f;
195 }
196 
197 void TowerDraw()
198 {
199   for (int i = 0; i < towerCount; i++)
200   {
201     Tower tower = towers[i];
202     if (tower.towerType == TOWER_TYPE_NONE)
203     {
204       continue;
205     }
206 
207     switch (tower.towerType)
208     {
209     case TOWER_TYPE_GUN:
210       DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
211       DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0, 0, 
212         tower.cooldown > 0.2f ? SPRITE_UNIT_PHASE_WEAPON_COOLDOWN : SPRITE_UNIT_PHASE_WEAPON_IDLE);
213       break;
214     default:
215       if (towerModels[tower.towerType].materials)
216       {
217         DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
218       } else {
219         DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
220       }
221       break;
222     }
223   }
224 }
225 
226 void TowerUpdate()
227 {
228   for (int i = 0; i < towerCount; i++)
229   {
230     Tower *tower = &towers[i];
231     switch (tower->towerType)
232     {
233     case TOWER_TYPE_GUN:
234       TowerGunUpdate(tower);
235       break;
236     }
237   }
238 }
239 
240 void TowerDrawHealthBars(Camera3D camera)
241 {
242   for (int i = 0; i < towerCount; i++)
243   {
244     Tower *tower = &towers[i];
245     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
246     {
247       continue;
248     }
249     
250     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
251     float maxHealth = TowerGetMaxHealth(tower);
252     float health = maxHealth - tower->damage;
253     float healthRatio = health / maxHealth;
254     
255     DrawHealthBar(camera, position, healthRatio, GREEN);
256   }
257 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 EnemyClassConfig enemyClassConfigs[] = {
  7     [ENEMY_TYPE_MINION] = {
  8       .health = 10.0f, 
  9       .speed = 0.6f, 
 10       .radius = 0.25f, 
 11       .maxAcceleration = 1.0f,
 12       .explosionDamage = 1.0f,
 13       .requiredContactTime = 0.5f,
 14       .explosionRange = 1.0f,
 15       .explosionPushbackPower = 0.25f,
 16       .goldValue = 1,
 17     },
 18 };
 19 
 20 Enemy enemies[ENEMY_MAX_COUNT];
 21 int enemyCount = 0;
 22 
 23 SpriteUnit enemySprites[] = {
 24     [ENEMY_TYPE_MINION] = {
 25       .srcRect = {0, 16, 16, 16},
 26       .offset = {8.0f, 0.0f},
 27       .frameCount = 6,
 28       .frameDuration = 0.1f,
 29     },
 30 };
 31 
 32 void EnemyInit()
 33 {
 34   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 35   {
 36     enemies[i] = (Enemy){0};
 37   }
 38   enemyCount = 0;
 39 }
 40 
 41 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
 42 {
 43   return enemyClassConfigs[enemy->enemyType].speed;
 44 }
 45 
 46 float EnemyGetMaxHealth(Enemy *enemy)
 47 {
 48   return enemyClassConfigs[enemy->enemyType].health;
 49 }
 50 
 51 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
 52 {
 53   int16_t castleX = 0;
 54   int16_t castleY = 0;
 55   int16_t dx = castleX - currentX;
 56   int16_t dy = castleY - currentY;
 57   if (dx == 0 && dy == 0)
 58   {
 59     *nextX = currentX;
 60     *nextY = currentY;
 61     return 1;
 62   }
 63   Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY});
 64 
 65   if (gradient.x == 0 && gradient.y == 0)
 66   {
 67     *nextX = currentX;
 68     *nextY = currentY;
 69     return 1;
 70   }
 71 
 72   if (fabsf(gradient.x) > fabsf(gradient.y))
 73   {
 74     *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1);
 75     *nextY = currentY;
 76     return 0;
 77   }
 78   *nextX = currentX;
 79   *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1);
 80   return 0;
 81 }
 82 
 83 
 84 // this function predicts the movement of the unit for the next deltaT seconds
 85 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
 86 {
 87   const float pointReachedDistance = 0.25f;
 88   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
 89   const float maxSimStepTime = 0.015625f;
 90   
 91   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
 92   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
 93   int16_t nextX = enemy->nextX;
 94   int16_t nextY = enemy->nextY;
 95   Vector2 position = enemy->simPosition;
 96   int passedCount = 0;
 97   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
 98   {
 99     float stepTime = fminf(deltaT - t, maxSimStepTime);
100     Vector2 target = (Vector2){nextX, nextY};
101     float speed = Vector2Length(*velocity);
102     // draw the target position for debugging
103     DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
104     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
105     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
106     {
107       // we reached the target position, let's move to the next waypoint
108       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
109       target = (Vector2){nextX, nextY};
110       // track how many waypoints we passed
111       passedCount++;
112     }
113     
114     // acceleration towards the target
115     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
116     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
117     *velocity = Vector2Add(*velocity, acceleration);
118 
119     // limit the speed to the maximum speed
120     if (speed > maxSpeed)
121     {
122       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
123     }
124 
125     // move the enemy
126     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
127   }
128 
129   if (waypointPassedCount)
130   {
131     (*waypointPassedCount) = passedCount;
132   }
133 
134   return position;
135 }
136 
137 void EnemyDraw()
138 {
139   for (int i = 0; i < enemyCount; i++)
140   {
141     Enemy enemy = enemies[i];
142     if (enemy.enemyType == ENEMY_TYPE_NONE)
143     {
144       continue;
145     }
146 
147     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
148     
149     // don't draw any trails for now; might replace this with footprints later
150     // if (enemy.movePathCount > 0)
151     // {
152     //   Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
153     //   DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
154     // }
155     // for (int j = 1; j < enemy.movePathCount; j++)
156     // {
157     //   Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
158     //   Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
159     //   DrawLine3D(p, q, GREEN);
160     // }
161 
162     switch (enemy.enemyType)
163     {
164     case ENEMY_TYPE_MINION:
165       DrawSpriteUnit(enemySprites[ENEMY_TYPE_MINION], (Vector3){position.x, 0.0f, position.y}, 
166         enemy.walkedDistance, 0, 0);
167       break;
168     }
169   }
170 }
171 
172 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource)
173 {
174   // damage the tower
175   float explosionDamge = enemyClassConfigs[enemy->enemyType].explosionDamage;
176   float explosionRange = enemyClassConfigs[enemy->enemyType].explosionRange;
177   float explosionPushbackPower = enemyClassConfigs[enemy->enemyType].explosionPushbackPower;
178   float explosionRange2 = explosionRange * explosionRange;
179   tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage;
180   // explode the enemy
181   if (tower->damage >= TowerGetMaxHealth(tower))
182   {
183     tower->towerType = TOWER_TYPE_NONE;
184   }
185 
186   ParticleAdd(PARTICLE_TYPE_EXPLOSION, 
187     explosionSource, 
188     (Vector3){0, 0.1f, 0}, 1.0f);
189 
190   enemy->enemyType = ENEMY_TYPE_NONE;
191 
192   // push back enemies & dealing damage
193   for (int i = 0; i < enemyCount; i++)
194   {
195     Enemy *other = &enemies[i];
196     if (other->enemyType == ENEMY_TYPE_NONE)
197     {
198       continue;
199     }
200     float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition);
201     if (distanceSqr > 0 && distanceSqr < explosionRange2)
202     {
203       Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition));
204       other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower));
205       EnemyAddDamage(other, explosionDamge);
206     }
207   }
208 }
209 
210 void EnemyUpdate()
211 {
212   const float castleX = 0;
213   const float castleY = 0;
214   const float maxPathDistance2 = 0.25f * 0.25f;
215   
216   for (int i = 0; i < enemyCount; i++)
217   {
218     Enemy *enemy = &enemies[i];
219     if (enemy->enemyType == ENEMY_TYPE_NONE)
220     {
221       continue;
222     }
223 
224     int waypointPassedCount = 0;
225     Vector2 prevPosition = enemy->simPosition;
226     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
227     enemy->startMovingTime = gameTime.time;
228     enemy->walkedDistance += Vector2Distance(prevPosition, enemy->simPosition);
229     // track path of unit
230     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
231     {
232       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
233       {
234         enemy->movePath[j] = enemy->movePath[j - 1];
235       }
236       enemy->movePath[0] = enemy->simPosition;
237       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
238       {
239         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
240       }
241     }
242 
243     if (waypointPassedCount > 0)
244     {
245       enemy->currentX = enemy->nextX;
246       enemy->currentY = enemy->nextY;
247       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) &&
248         Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
249       {
250         // enemy reached the castle; remove it
251         enemy->enemyType = ENEMY_TYPE_NONE;
252         continue;
253       }
254     }
255   }
256 
257   // handle collisions between enemies
258   for (int i = 0; i < enemyCount - 1; i++)
259   {
260     Enemy *enemyA = &enemies[i];
261     if (enemyA->enemyType == ENEMY_TYPE_NONE)
262     {
263       continue;
264     }
265     for (int j = i + 1; j < enemyCount; j++)
266     {
267       Enemy *enemyB = &enemies[j];
268       if (enemyB->enemyType == ENEMY_TYPE_NONE)
269       {
270         continue;
271       }
272       float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition);
273       float radiusA = enemyClassConfigs[enemyA->enemyType].radius;
274       float radiusB = enemyClassConfigs[enemyB->enemyType].radius;
275       float radiusSum = radiusA + radiusB;
276       if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f)
277       {
278         // collision
279         float distance = sqrtf(distanceSqr);
280         float overlap = radiusSum - distance;
281         // move the enemies apart, but softly; if we have a clog of enemies,
282         // moving them perfectly apart can cause them to jitter
283         float positionCorrection = overlap / 5.0f;
284         Vector2 direction = (Vector2){
285             (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection,
286             (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection};
287         enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction);
288         enemyB->simPosition = Vector2Add(enemyB->simPosition, direction);
289       }
290     }
291   }
292 
293   // handle collisions between enemies and towers
294   for (int i = 0; i < enemyCount; i++)
295   {
296     Enemy *enemy = &enemies[i];
297     if (enemy->enemyType == ENEMY_TYPE_NONE)
298     {
299       continue;
300     }
301     enemy->contactTime -= gameTime.deltaTime;
302     if (enemy->contactTime < 0.0f)
303     {
304       enemy->contactTime = 0.0f;
305     }
306 
307     float enemyRadius = enemyClassConfigs[enemy->enemyType].radius;
308     // linear search over towers; could be optimized by using path finding tower map,
309     // but for now, we keep it simple
310     for (int j = 0; j < towerCount; j++)
311     {
312       Tower *tower = &towers[j];
313       if (tower->towerType == TOWER_TYPE_NONE)
314       {
315         continue;
316       }
317       float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y});
318       float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1
319       if (distanceSqr > combinedRadius * combinedRadius)
320       {
321         continue;
322       }
323       // potential collision; square / circle intersection
324       float dx = tower->x - enemy->simPosition.x;
325       float dy = tower->y - enemy->simPosition.y;
326       float absDx = fabsf(dx);
327       float absDy = fabsf(dy);
328       Vector3 contactPoint = {0};
329       if (absDx <= 0.5f && absDx <= absDy) {
330         // vertical collision; push the enemy out horizontally
331         float overlap = enemyRadius + 0.5f - absDy;
332         if (overlap < 0.0f)
333         {
334           continue;
335         }
336         float direction = dy > 0.0f ? -1.0f : 1.0f;
337         enemy->simPosition.y += direction * overlap;
338         contactPoint = (Vector3){enemy->simPosition.x, 0.2f, tower->y + direction * 0.5f};
339       }
340       else if (absDy <= 0.5f && absDy <= absDx)
341       {
342         // horizontal collision; push the enemy out vertically
343         float overlap = enemyRadius + 0.5f - absDx;
344         if (overlap < 0.0f)
345         {
346           continue;
347         }
348         float direction = dx > 0.0f ? -1.0f : 1.0f;
349         enemy->simPosition.x += direction * overlap;
350         contactPoint = (Vector3){tower->x + direction * 0.5f, 0.2f, enemy->simPosition.y};
351       }
352       else
353       {
354         // possible collision with a corner
355         float cornerDX = dx > 0.0f ? -0.5f : 0.5f;
356         float cornerDY = dy > 0.0f ? -0.5f : 0.5f;
357         float cornerX = tower->x + cornerDX;
358         float cornerY = tower->y + cornerDY;
359         float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY});
360         if (cornerDistanceSqr > enemyRadius * enemyRadius)
361         {
362           continue;
363         }
364         // push the enemy out along the diagonal
365         float cornerDistance = sqrtf(cornerDistanceSqr);
366         float overlap = enemyRadius - cornerDistance;
367         float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX;
368         float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY;
369         enemy->simPosition.x -= directionX * overlap;
370         enemy->simPosition.y -= directionY * overlap;
371         contactPoint = (Vector3){cornerX, 0.2f, cornerY};
372       }
373 
374       if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f)
375       {
376         enemy->contactTime += gameTime.deltaTime * 2.0f; // * 2 to undo the subtraction above
377         if (enemy->contactTime >= enemyClassConfigs[enemy->enemyType].requiredContactTime)
378         {
379           EnemyTriggerExplode(enemy, tower, contactPoint);
380         }
381       }
382     }
383   }
384 }
385 
386 EnemyId EnemyGetId(Enemy *enemy)
387 {
388   return (EnemyId){enemy - enemies, enemy->generation};
389 }
390 
391 Enemy *EnemyTryResolve(EnemyId enemyId)
392 {
393   if (enemyId.index >= ENEMY_MAX_COUNT)
394   {
395     return 0;
396   }
397   Enemy *enemy = &enemies[enemyId.index];
398   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
399   {
400     return 0;
401   }
402   return enemy;
403 }
404 
405 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
406 {
407   Enemy *spawn = 0;
408   for (int i = 0; i < enemyCount; i++)
409   {
410     Enemy *enemy = &enemies[i];
411     if (enemy->enemyType == ENEMY_TYPE_NONE)
412     {
413       spawn = enemy;
414       break;
415     }
416   }
417 
418   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
419   {
420     spawn = &enemies[enemyCount++];
421   }
422 
423   if (spawn)
424   {
425     spawn->currentX = currentX;
426     spawn->currentY = currentY;
427     spawn->nextX = currentX;
428     spawn->nextY = currentY;
429     spawn->simPosition = (Vector2){currentX, currentY};
430     spawn->simVelocity = (Vector2){0, 0};
431     spawn->enemyType = enemyType;
432     spawn->startMovingTime = gameTime.time;
433     spawn->damage = 0.0f;
434     spawn->futureDamage = 0.0f;
435     spawn->generation++;
436     spawn->movePathCount = 0;
437     spawn->walkedDistance = 0.0f;
438   }
439 
440   return spawn;
441 }
442 
443 int EnemyAddDamage(Enemy *enemy, float damage)
444 {
445   enemy->damage += damage;
446   if (enemy->damage >= EnemyGetMaxHealth(enemy))
447   {
448     currentLevel->playerGold += enemyClassConfigs[enemy->enemyType].goldValue;
449     enemy->enemyType = ENEMY_TYPE_NONE;
450     return 1;
451   }
452 
453   return 0;
454 }
455 
456 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
457 {
458   int16_t castleX = 0;
459   int16_t castleY = 0;
460   Enemy* closest = 0;
461   int16_t closestDistance = 0;
462   float range2 = range * range;
463   for (int i = 0; i < enemyCount; i++)
464   {
465     Enemy* enemy = &enemies[i];
466     if (enemy->enemyType == ENEMY_TYPE_NONE)
467     {
468       continue;
469     }
470     float maxHealth = EnemyGetMaxHealth(enemy);
471     if (enemy->futureDamage >= maxHealth)
472     {
473       // ignore enemies that will die soon
474       continue;
475     }
476     int16_t dx = castleX - enemy->currentX;
477     int16_t dy = castleY - enemy->currentY;
478     int16_t distance = abs(dx) + abs(dy);
479     if (!closest || distance < closestDistance)
480     {
481       float tdx = towerX - enemy->currentX;
482       float tdy = towerY - enemy->currentY;
483       float tdistance2 = tdx * tdx + tdy * tdy;
484       if (tdistance2 <= range2)
485       {
486         closest = enemy;
487         closestDistance = distance;
488       }
489     }
490   }
491   return closest;
492 }
493 
494 int EnemyCount()
495 {
496   int count = 0;
497   for (int i = 0; i < enemyCount; i++)
498   {
499     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
500     {
501       count++;
502     }
503   }
504   return count;
505 }
506 
507 void EnemyDrawHealthbars(Camera3D camera)
508 {
509   for (int i = 0; i < enemyCount; i++)
510   {
511     Enemy *enemy = &enemies[i];
512     if (enemy->enemyType == ENEMY_TYPE_NONE || enemy->damage == 0.0f)
513     {
514       continue;
515     }
516     Vector3 position = (Vector3){enemy->simPosition.x, 0.5f, enemy->simPosition.y};
517     float maxHealth = EnemyGetMaxHealth(enemy);
518     float health = maxHealth - enemy->damage;
519     float healthRatio = health / maxHealth;
520     
521     DrawHealthBar(camera, position, healthRatio, GREEN);
522   }
523 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 // The queue is a simple array of nodes, we add nodes to the end and remove
  5 // nodes from the front. We keep the array around to avoid unnecessary allocations
  6 static PathfindingNode *pathfindingNodeQueue = 0;
  7 static int pathfindingNodeQueueCount = 0;
  8 static int pathfindingNodeQueueCapacity = 0;
  9 
 10 // The pathfinding map stores the distances from the castle to each cell in the map.
 11 static PathfindingMap pathfindingMap = {0};
 12 
 13 void PathfindingMapInit(int width, int height, Vector3 translate, float scale)
 14 {
 15   // transforming between map space and world space allows us to adapt 
 16   // position and scale of the map without changing the pathfinding data
 17   pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z);
 18   pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale));
 19   pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace);
 20   pathfindingMap.width = width;
 21   pathfindingMap.height = height;
 22   pathfindingMap.scale = scale;
 23   pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float));
 24   for (int i = 0; i < width * height; i++)
 25   {
 26     pathfindingMap.distances[i] = -1.0f;
 27   }
 28 
 29   pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long));
 30   pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc));
 31 }
 32 
 33 static void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance)
 34 {
 35   if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity)
 36   {
 37     pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2;
 38     // we use MemAlloc/MemRealloc to allocate memory for the queue
 39     // I am not entirely sure if MemRealloc allows passing a null pointer
 40     // so we check if the pointer is null and use MemAlloc in that case
 41     if (pathfindingNodeQueue == 0)
 42     {
 43       pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 44     }
 45     else
 46     {
 47       pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 48     }
 49   }
 50 
 51   PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++];
 52   node->x = x;
 53   node->y = y;
 54   node->fromX = fromX;
 55   node->fromY = fromY;
 56   node->distance = distance;
 57 }
 58 
 59 static PathfindingNode *PathFindingNodePop()
 60 {
 61   if (pathfindingNodeQueueCount == 0)
 62   {
 63     return 0;
 64   }
 65   // we return the first node in the queue; we want to return a pointer to the node
 66   // so we can return 0 if the queue is empty. 
 67   // We should _not_ return a pointer to the element in the list, because the list
 68   // may be reallocated and the pointer would become invalid. Or the 
 69   // popped element is overwritten by the next push operation.
 70   // Using static here means that the variable is permanently allocated.
 71   static PathfindingNode node;
 72   node = pathfindingNodeQueue[0];
 73   // we shift all nodes one position to the front
 74   for (int i = 1; i < pathfindingNodeQueueCount; i++)
 75   {
 76     pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i];
 77   }
 78   --pathfindingNodeQueueCount;
 79   return &node;
 80 }
 81 
 82 float PathFindingGetDistance(int mapX, int mapY)
 83 {
 84   if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height)
 85   {
 86     // when outside the map, we return the manhattan distance to the castle (0,0)
 87     return fabsf((float)mapX) + fabsf((float)mapY);
 88   }
 89 
 90   return pathfindingMap.distances[mapY * pathfindingMap.width + mapX];
 91 }
 92 
 93 // transform a world position to a map position in the array; 
 94 // returns true if the position is inside the map
 95 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY)
 96 {
 97   Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace);
 98   *mapX = (int16_t)mapPosition.x;
 99   *mapY = (int16_t)mapPosition.z;
100   return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height;
101 }
102 
103 void PathFindingMapUpdate()
104 {
105   const int castleX = 0, castleY = 0;
106   int16_t castleMapX, castleMapY;
107   if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY))
108   {
109     return;
110   }
111   int width = pathfindingMap.width, height = pathfindingMap.height;
112 
113   // reset the distances to -1
114   for (int i = 0; i < width * height; i++)
115   {
116     pathfindingMap.distances[i] = -1.0f;
117   }
118   // reset the tower indices
119   for (int i = 0; i < width * height; i++)
120   {
121     pathfindingMap.towerIndex[i] = -1;
122   }
123   // reset the delta src
124   for (int i = 0; i < width * height; i++)
125   {
126     pathfindingMap.deltaSrc[i].x = 0;
127     pathfindingMap.deltaSrc[i].y = 0;
128   }
129 
130   for (int i = 0; i < towerCount; i++)
131   {
132     Tower *tower = &towers[i];
133     if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE)
134     {
135       continue;
136     }
137     int16_t mapX, mapY;
138     // technically, if the tower cell scale is not in sync with the pathfinding map scale,
139     // this would not work correctly and needs to be refined to allow towers covering multiple cells
140     // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly
141     // one cell. For now.
142     if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY))
143     {
144       continue;
145     }
146     int index = mapY * width + mapX;
147     pathfindingMap.towerIndex[index] = i;
148   }
149 
150   // we start at the castle and add the castle to the queue
151   pathfindingMap.maxDistance = 0.0f;
152   pathfindingNodeQueueCount = 0;
153   PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f);
154   PathfindingNode *node = 0;
155   while ((node = PathFindingNodePop()))
156   {
157     if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height)
158     {
159       continue;
160     }
161     int index = node->y * width + node->x;
162     if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance)
163     {
164       continue;
165     }
166 
167     int deltaX = node->x - node->fromX;
168     int deltaY = node->y - node->fromY;
169     // even if the cell is blocked by a tower, we still may want to store the direction
170     // (though this might not be needed, IDK right now)
171     pathfindingMap.deltaSrc[index].x = (char) deltaX;
172     pathfindingMap.deltaSrc[index].y = (char) deltaY;
173 
174     // we skip nodes that are blocked by towers
175     if (pathfindingMap.towerIndex[index] >= 0)
176     {
177       node->distance += 8.0f;
178     }
179     pathfindingMap.distances[index] = node->distance;
180     pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance);
181     PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f);
182     PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f);
183     PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f);
184     PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f);
185   }
186 }
187 
188 void PathFindingMapDraw()
189 {
190   float cellSize = pathfindingMap.scale * 0.9f;
191   float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance);
192   for (int x = 0; x < pathfindingMap.width; x++)
193   {
194     for (int y = 0; y < pathfindingMap.height; y++)
195     {
196       float distance = pathfindingMap.distances[y * pathfindingMap.width + x];
197       float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f);
198       Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255};
199       Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace);
200       // animate the distance "wave" to show how the pathfinding algorithm expands
201       // from the castle
202       if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance)
203       {
204         color = BLACK;
205       }
206       DrawCube(position, cellSize, 0.1f, cellSize, color);
207     }
208   }
209 }
210 
211 Vector2 PathFindingGetGradient(Vector3 world)
212 {
213   int16_t mapX, mapY;
214   if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY))
215   {
216     DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX];
217     return (Vector2){(float)-delta.x, (float)-delta.y};
218   }
219   // fallback to a simple gradient calculation
220   float n = PathFindingGetDistance(mapX, mapY - 1);
221   float s = PathFindingGetDistance(mapX, mapY + 1);
222   float w = PathFindingGetDistance(mapX - 1, mapY);
223   float e = PathFindingGetDistance(mapX + 1, mapY);
224   return (Vector2){w - e + 0.25f, n - s + 0.125f};
225 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static Projectile projectiles[PROJECTILE_MAX_COUNT];
  5 static int projectileCount = 0;
  6 
  7 void ProjectileInit()
  8 {
  9   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
 10   {
 11     projectiles[i] = (Projectile){0};
 12   }
 13 }
 14 
 15 void ProjectileDraw()
 16 {
 17   for (int i = 0; i < projectileCount; i++)
 18   {
 19     Projectile projectile = projectiles[i];
 20     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
 21     {
 22       continue;
 23     }
 24     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
 25     if (transition >= 1.0f)
 26     {
 27       continue;
 28     }
 29     for (float transitionOffset = 0.0f; transitionOffset < 1.0f; transitionOffset += 0.1f)
 30     {
 31       float t = transition + transitionOffset * 0.3f;
 32       if (t > 1.0f)
 33       {
 34         break;
 35       }
 36       Vector3 position = Vector3Lerp(projectile.position, projectile.target, t);
 37       Color color = RED;
 38       if (projectile.projectileType == PROJECTILE_TYPE_ARROW)
 39       {
 40         // make tip red but quickly fade to brown
 41         color = ColorLerp(BROWN, RED, transitionOffset * transitionOffset);
 42         // fake a ballista flight path using parabola equation
 43         float parabolaT = t - 0.5f;
 44         parabolaT = 1.0f - 4.0f * parabolaT * parabolaT;
 45         position.y += 0.15f * parabolaT * projectile.distance;
 46       }
 47 
 48       float size = 0.06f * (transitionOffset + 0.25f);
 49       DrawCube(position, size, size, size, color);
 50     }
 51   }
 52 }
 53 
 54 void ProjectileUpdate()
 55 {
 56   for (int i = 0; i < projectileCount; i++)
 57   {
 58     Projectile *projectile = &projectiles[i];
 59     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
 60     {
 61       continue;
 62     }
 63     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
 64     if (transition >= 1.0f)
 65     {
 66       projectile->projectileType = PROJECTILE_TYPE_NONE;
 67       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
 68       if (enemy)
 69       {
 70         EnemyAddDamage(enemy, projectile->damage);
 71       }
 72       continue;
 73     }
 74   }
 75 }
 76 
 77 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, float damage)
 78 {
 79   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
 80   {
 81     Projectile *projectile = &projectiles[i];
 82     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
 83     {
 84       projectile->projectileType = projectileType;
 85       projectile->shootTime = gameTime.time;
 86       float distance = Vector3Distance(position, target);
 87       projectile->arrivalTime = gameTime.time + distance / speed;
 88       projectile->damage = damage;
 89       projectile->position = position;
 90       projectile->target = target;
 91       projectile->directionNormal = Vector3Scale(Vector3Subtract(target, position), 1.0f / distance);
 92       projectile->distance = distance;
 93       projectile->targetEnemy = EnemyGetId(enemy);
 94       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
 95       return projectile;
 96     }
 97   }
 98   return 0;
 99 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static Particle particles[PARTICLE_MAX_COUNT];
  5 static int particleCount = 0;
  6 
  7 void ParticleInit()
  8 {
  9   for (int i = 0; i < PARTICLE_MAX_COUNT; i++)
 10   {
 11     particles[i] = (Particle){0};
 12   }
 13   particleCount = 0;
 14 }
 15 
 16 static void DrawExplosionParticle(Particle *particle, float transition)
 17 {
 18   float size = 1.2f * (1.0f - transition);
 19   Color startColor = WHITE;
 20   Color endColor = RED;
 21   Color color = ColorLerp(startColor, endColor, transition);
 22   DrawCube(particle->position, size, size, size, color);
 23 }
 24 
 25 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime)
 26 {
 27   if (particleCount >= PARTICLE_MAX_COUNT)
 28   {
 29     return;
 30   }
 31 
 32   int index = -1;
 33   for (int i = 0; i < particleCount; i++)
 34   {
 35     if (particles[i].particleType == PARTICLE_TYPE_NONE)
 36     {
 37       index = i;
 38       break;
 39     }
 40   }
 41 
 42   if (index == -1)
 43   {
 44     index = particleCount++;
 45   }
 46 
 47   Particle *particle = &particles[index];
 48   particle->particleType = particleType;
 49   particle->spawnTime = gameTime.time;
 50   particle->lifetime = lifetime;
 51   particle->position = position;
 52   particle->velocity = velocity;
 53 }
 54 
 55 void ParticleUpdate()
 56 {
 57   for (int i = 0; i < particleCount; i++)
 58   {
 59     Particle *particle = &particles[i];
 60     if (particle->particleType == PARTICLE_TYPE_NONE)
 61     {
 62       continue;
 63     }
 64 
 65     float age = gameTime.time - particle->spawnTime;
 66 
 67     if (particle->lifetime > age)
 68     {
 69       particle->position = Vector3Add(particle->position, Vector3Scale(particle->velocity, gameTime.deltaTime));
 70     }
 71     else {
 72       particle->particleType = PARTICLE_TYPE_NONE;
 73     }
 74   }
 75 }
 76 
 77 void ParticleDraw()
 78 {
 79   for (int i = 0; i < particleCount; i++)
 80   {
 81     Particle particle = particles[i];
 82     if (particle.particleType == PARTICLE_TYPE_NONE)
 83     {
 84       continue;
 85     }
 86 
 87     float age = gameTime.time - particle.spawnTime;
 88     float transition = age / particle.lifetime;
 89     switch (particle.particleType)
 90     {
 91     case PARTICLE_TYPE_EXPLOSION:
 92       DrawExplosionParticle(&particle, transition);
 93       break;
 94     default:
 95       DrawCube(particle.position, 0.3f, 0.5f, 0.3f, RED);
 96       break;
 97     }
 98   }
 99 }  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 #endifOne issue, however: The archer orientation is just one sided, so when the enemies come from the wrong side, the archers shoot the wrong way (they still hit the enemies, apparently they are highly competent, even if it looks incompetent :D).
The function already has a flipped argument that is currently not used. We also have to figure out when to flip it. The sprite is not a 3d model, so 3d world space coordinates are not meaningful: We have to look at the direction from the screen space perspective. But this is fairly simple; we'll use the same function as for the healthbars: GetWorldToScreen. We compare the archer's screen position with the enemy's screen position and flip the sprite if the enemy is to the right of the archer. Let's do this:
  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 //# Variables
  7 GUIState guiState = {0};
  8 GameTime gameTime = {0};
  9 
 10 Level levels[] = {
 11   [0] = {
 12     .state = LEVEL_STATE_BUILDING,
 13     .initialGold = 20,
 14     .waves[0] = {
 15       .enemyType = ENEMY_TYPE_MINION,
 16       .wave = 0,
 17       .count = 10,
 18       .interval = 2.5f,
 19       .delay = 1.0f,
 20       .spawnPosition = {0, 6},
 21     },
 22     .waves[1] = {
 23       .enemyType = ENEMY_TYPE_MINION,
 24       .wave = 1,
 25       .count = 20,
 26       .interval = 1.5f,
 27       .delay = 1.0f,
 28       .spawnPosition = {0, 6},
 29     },
 30     .waves[2] = {
 31       .enemyType = ENEMY_TYPE_MINION,
 32       .wave = 2,
 33       .count = 30,
 34       .interval = 1.2f,
 35       .delay = 1.0f,
 36       .spawnPosition = {0, 6},
 37     }
 38   },
 39 };
 40 
 41 Level *currentLevel = levels;
 42 
 43 //# Game
 44 
 45 void InitLevel(Level *level)
 46 {
 47   TowerInit();
 48   EnemyInit();
 49   ProjectileInit();
 50   ParticleInit();
 51   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
 52 
 53   level->placementMode = 0;
 54   level->state = LEVEL_STATE_BUILDING;
 55   level->nextState = LEVEL_STATE_NONE;
 56   level->playerGold = level->initialGold;
 57   level->currentWave = 0;
 58 
 59   Camera *camera = &level->camera;
 60   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
 61   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
 62   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
 63   camera->fovy = 10.0f;
 64   camera->projection = CAMERA_ORTHOGRAPHIC;
 65 }
 66 
 67 void DrawLevelHud(Level *level)
 68 {
 69   const char *text = TextFormat("Gold: %d", level->playerGold);
 70   Font font = GetFontDefault();
 71   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
 72   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
 73 }
 74 
 75 void DrawLevelReportLostWave(Level *level)
 76 {
 77   BeginMode3D(level->camera);
 78   DrawGrid(10, 1.0f);
 79   TowerDraw();
 80   EnemyDraw();
 81   ProjectileDraw();
 82   ParticleDraw();
 83   guiState.isBlocked = 0;
 84   EndMode3D();
 85 
 86   TowerDrawHealthBars(level->camera);
 87 
 88   const char *text = "Wave lost";
 89   int textWidth = MeasureText(text, 20);
 90   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
 91 
 92   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
 93   {
 94     level->nextState = LEVEL_STATE_RESET;
 95   }
 96 }
 97 
 98 int HasLevelNextWave(Level *level)
 99 {
100   for (int i = 0; i < 10; i++)
101   {
102     EnemyWave *wave = &level->waves[i];
103     if (wave->wave == level->currentWave)
104     {
105       return 1;
106     }
107   }
108   return 0;
109 }
110 
111 void DrawLevelReportWonWave(Level *level)
112 {
113   BeginMode3D(level->camera);
114   DrawGrid(10, 1.0f);
115   TowerDraw();
116   EnemyDraw();
117   ProjectileDraw();
118   ParticleDraw();
119   guiState.isBlocked = 0;
120   EndMode3D();
121 
122   TowerDrawHealthBars(level->camera);
123 
124   const char *text = "Wave won";
125   int textWidth = MeasureText(text, 20);
126   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
127 
128 
129   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
130   {
131     level->nextState = LEVEL_STATE_RESET;
132   }
133 
134   if (HasLevelNextWave(level))
135   {
136     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
137     {
138       level->nextState = LEVEL_STATE_BUILDING;
139     }
140   }
141   else {
142     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
143     {
144       level->nextState = LEVEL_STATE_WON_LEVEL;
145     }
146   }
147 }
148 
149 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
150 {
151   static ButtonState buttonStates[8] = {0};
152   int cost = GetTowerCosts(towerType);
153   const char *text = TextFormat("%s: %d", name, cost);
154   buttonStates[towerType].isSelected = level->placementMode == towerType;
155   buttonStates[towerType].isDisabled = level->playerGold < cost;
156   if (Button(text, x, y, width, height, &buttonStates[towerType]))
157   {
158     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
159   }
160 }
161 
162 void DrawLevelBuildingState(Level *level)
163 {
164   BeginMode3D(level->camera);
165   DrawGrid(10, 1.0f);
166   TowerDraw();
167   EnemyDraw();
168   ProjectileDraw();
169   ParticleDraw();
170 
171   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
172   float planeDistance = ray.position.y / -ray.direction.y;
173   float planeX = ray.direction.x * planeDistance + ray.position.x;
174   float planeY = ray.direction.z * planeDistance + ray.position.z;
175   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
176   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
177   if (level->placementMode && !guiState.isBlocked)
178   {
179     DrawCubeWires((Vector3){mapX, 0.2f, mapY}, 1.0f, 0.4f, 1.0f, RED);
180     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
181     {
182       if (TowerTryAdd(level->placementMode, mapX, mapY))
183       {
184         level->playerGold -= GetTowerCosts(level->placementMode);
185         level->placementMode = TOWER_TYPE_NONE;
186       }
187     }
188   }
189 
190   guiState.isBlocked = 0;
191 
192   EndMode3D();
193 
194   TowerDrawHealthBars(level->camera);
195 
196   static ButtonState buildWallButtonState = {0};
197   static ButtonState buildGunButtonState = {0};
198   buildWallButtonState.isSelected = level->placementMode == TOWER_TYPE_WALL;
199   buildGunButtonState.isSelected = level->placementMode == TOWER_TYPE_GUN;
200 
201   DrawBuildingBuildButton(level, 10, 10, 80, 30, TOWER_TYPE_WALL, "Wall");
202   DrawBuildingBuildButton(level, 10, 50, 80, 30, TOWER_TYPE_GUN, "Gun");
203 
204   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
205   {
206     level->nextState = LEVEL_STATE_RESET;
207   }
208   
209   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
210   {
211     level->nextState = LEVEL_STATE_BATTLE;
212   }
213 
214   const char *text = "Building phase";
215   int textWidth = MeasureText(text, 20);
216   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
217 }
218 
219 void InitBattleStateConditions(Level *level)
220 {
221   level->state = LEVEL_STATE_BATTLE;
222   level->nextState = LEVEL_STATE_NONE;
223   level->waveEndTimer = 0.0f;
224   for (int i = 0; i < 10; i++)
225   {
226     EnemyWave *wave = &level->waves[i];
227     wave->spawned = 0;
228     wave->timeToSpawnNext = wave->delay;
229   }
230 }
231 
232 void DrawLevelBattleState(Level *level)
233 {
234   BeginMode3D(level->camera);
235   DrawGrid(10, 1.0f);
236   TowerDraw();
237   EnemyDraw();
238   ProjectileDraw();
239   ParticleDraw();
240   guiState.isBlocked = 0;
241   EndMode3D();
242 
243   EnemyDrawHealthbars(level->camera);
244   TowerDrawHealthBars(level->camera);
245 
246   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
247   {
248     level->nextState = LEVEL_STATE_RESET;
249   }
250 
251   int maxCount = 0;
252   int remainingCount = 0;
253   for (int i = 0; i < 10; i++)
254   {
255     EnemyWave *wave = &level->waves[i];
256     if (wave->wave != level->currentWave)
257     {
258       continue;
259     }
260     maxCount += wave->count;
261     remainingCount += wave->count - wave->spawned;
262   }
263   int aliveCount = EnemyCount();
264   remainingCount += aliveCount;
265 
266   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
267   int textWidth = MeasureText(text, 20);
268   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
269 }
270 
271 void DrawLevel(Level *level)
272 {
273   switch (level->state)
274   {
275     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
276     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
277     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
278     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
279     default: break;
280   }
281 
282   DrawLevelHud(level);
283 }
284 
285 void UpdateLevel(Level *level)
286 {
287   if (level->state == LEVEL_STATE_BATTLE)
288   {
289     int activeWaves = 0;
290     for (int i = 0; i < 10; i++)
291     {
292       EnemyWave *wave = &level->waves[i];
293       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
294       {
295         continue;
296       }
297       activeWaves++;
298       wave->timeToSpawnNext -= gameTime.deltaTime;
299       if (wave->timeToSpawnNext <= 0.0f)
300       {
301         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
302         if (enemy)
303         {
304           wave->timeToSpawnNext = wave->interval;
305           wave->spawned++;
306         }
307       }
308     }
309     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
310       level->waveEndTimer += gameTime.deltaTime;
311       if (level->waveEndTimer >= 2.0f)
312       {
313         level->nextState = LEVEL_STATE_LOST_WAVE;
314       }
315     }
316     else if (activeWaves == 0 && EnemyCount() == 0)
317     {
318       level->waveEndTimer += gameTime.deltaTime;
319       if (level->waveEndTimer >= 2.0f)
320       {
321         level->nextState = LEVEL_STATE_WON_WAVE;
322       }
323     }
324   }
325 
326   PathFindingMapUpdate();
327   EnemyUpdate();
328   TowerUpdate();
329   ProjectileUpdate();
330   ParticleUpdate();
331 
332   if (level->nextState == LEVEL_STATE_RESET)
333   {
334     InitLevel(level);
335   }
336   
337   if (level->nextState == LEVEL_STATE_BATTLE)
338   {
339     InitBattleStateConditions(level);
340   }
341   
342   if (level->nextState == LEVEL_STATE_WON_WAVE)
343   {
344     level->currentWave++;
345     level->state = LEVEL_STATE_WON_WAVE;
346   }
347   
348   if (level->nextState == LEVEL_STATE_LOST_WAVE)
349   {
350     level->state = LEVEL_STATE_LOST_WAVE;
351   }
352 
353   if (level->nextState == LEVEL_STATE_BUILDING)
354   {
355     level->state = LEVEL_STATE_BUILDING;
356   }
357 
358   if (level->nextState == LEVEL_STATE_WON_LEVEL)
359   {
360     // make something of this later
361     InitLevel(level);
362   }
363 
364   level->nextState = LEVEL_STATE_NONE;
365 }
366 
367 float nextSpawnTime = 0.0f;
368 
369 void ResetGame()
370 {
371   InitLevel(currentLevel);
372 }
373 
374 void InitGame()
375 {
376   TowerInit();
377   EnemyInit();
378   ProjectileInit();
379   ParticleInit();
380   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
381 
382   currentLevel = levels;
383   InitLevel(currentLevel);
384 }
385 
386 //# Immediate GUI functions
387 
388 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth)
389 {
390   const float healthBarHeight = 6.0f;
391   const float healthBarOffset = 15.0f;
392   const float inset = 2.0f;
393   const float innerWidth = healthBarWidth - inset * 2;
394   const float innerHeight = healthBarHeight - inset * 2;
395 
396   Vector2 screenPos = GetWorldToScreen(position, camera);
397   float centerX = screenPos.x - healthBarWidth * 0.5f;
398   float topY = screenPos.y - healthBarOffset;
399   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
400   float healthWidth = innerWidth * healthRatio;
401   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
402 }
403 
404 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
405 {
406   Rectangle bounds = {x, y, width, height};
407   int isPressed = 0;
408   int isSelected = state && state->isSelected;
409   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !state->isDisabled)
410   {
411     Color color = isSelected ? DARKGRAY : GRAY;
412     DrawRectangle(x, y, width, height, color);
413     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
414     {
415       isPressed = 1;
416     }
417     guiState.isBlocked = 1;
418   }
419   else
420   {
421     Color color = isSelected ? WHITE : LIGHTGRAY;
422     DrawRectangle(x, y, width, height, color);
423   }
424   Font font = GetFontDefault();
425   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
426   Color textColor = state->isDisabled ? GRAY : BLACK;
427   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
428   return isPressed;
429 }
430 
431 //# Main game loop
432 
433 void GameUpdate()
434 {
435   float dt = GetFrameTime();
436   // cap maximum delta time to 0.1 seconds to prevent large time steps
437   if (dt > 0.1f) dt = 0.1f;
438   gameTime.time += dt;
439   gameTime.deltaTime = dt;
440 
441   UpdateLevel(currentLevel);
442 }
443 
444 int main(void)
445 {
446   int screenWidth, screenHeight;
447   GetPreferredSize(&screenWidth, &screenHeight);
448   InitWindow(screenWidth, screenHeight, "Tower defense");
449   SetTargetFPS(30);
450 
451   InitGame();
452 
453   while (!WindowShouldClose())
454   {
455     if (IsPaused()) {
456       // canvas is not visible in browser - do nothing
457       continue;
458     }
459 
460     BeginDrawing();
461     ClearBackground(DARKBLUE);
462 
463     GameUpdate();
464     DrawLevel(currentLevel);
465 
466     EndDrawing();
467   }
468 
469   CloseWindow();
470 
471   return 0;
472 }  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 //# Declarations
 10 
 11 #define ENEMY_MAX_PATH_COUNT 8
 12 #define ENEMY_MAX_COUNT 400
 13 #define ENEMY_TYPE_NONE 0
 14 #define ENEMY_TYPE_MINION 1
 15 
 16 #define PARTICLE_MAX_COUNT 400
 17 #define PARTICLE_TYPE_NONE 0
 18 #define PARTICLE_TYPE_EXPLOSION 1
 19 
 20 typedef struct Particle
 21 {
 22   uint8_t particleType;
 23   float spawnTime;
 24   float lifetime;
 25   Vector3 position;
 26   Vector3 velocity;
 27 } Particle;
 28 
 29 #define TOWER_MAX_COUNT 400
 30 #define TOWER_TYPE_NONE 0
 31 #define TOWER_TYPE_BASE 1
 32 #define TOWER_TYPE_GUN 2
 33 #define TOWER_TYPE_WALL 3
 34 #define TOWER_TYPE_COUNT 4
 35 
 36 typedef struct Tower
 37 {
 38   int16_t x, y;
 39   uint8_t towerType;
 40   Vector2 lastTargetPosition;
 41   float cooldown;
 42   float damage;
 43 } Tower;
 44 
 45 typedef struct GameTime
 46 {
 47   float time;
 48   float deltaTime;
 49 } GameTime;
 50 
 51 typedef struct ButtonState {
 52   char isSelected;
 53   char isDisabled;
 54 } ButtonState;
 55 
 56 typedef struct GUIState {
 57   int isBlocked;
 58 } GUIState;
 59 
 60 typedef enum LevelState
 61 {
 62   LEVEL_STATE_NONE,
 63   LEVEL_STATE_BUILDING,
 64   LEVEL_STATE_BATTLE,
 65   LEVEL_STATE_WON_WAVE,
 66   LEVEL_STATE_LOST_WAVE,
 67   LEVEL_STATE_WON_LEVEL,
 68   LEVEL_STATE_RESET,
 69 } LevelState;
 70 
 71 typedef struct EnemyWave {
 72   uint8_t enemyType;
 73   uint8_t wave;
 74   uint16_t count;
 75   float interval;
 76   float delay;
 77   Vector2 spawnPosition;
 78 
 79   uint16_t spawned;
 80   float timeToSpawnNext;
 81 } EnemyWave;
 82 
 83 typedef struct Level
 84 {
 85   LevelState state;
 86   LevelState nextState;
 87   Camera3D camera;
 88   int placementMode;
 89 
 90   int initialGold;
 91   int playerGold;
 92 
 93   EnemyWave waves[10];
 94   int currentWave;
 95   float waveEndTimer;
 96 } Level;
 97 
 98 typedef struct DeltaSrc
 99 {
100   char x, y;
101 } DeltaSrc;
102 
103 typedef struct PathfindingMap
104 {
105   int width, height;
106   float scale;
107   float *distances;
108   long *towerIndex; 
109   DeltaSrc *deltaSrc;
110   float maxDistance;
111   Matrix toMapSpace;
112   Matrix toWorldSpace;
113 } PathfindingMap;
114 
115 // when we execute the pathfinding algorithm, we need to store the active nodes
116 // in a queue. Each node has a position, a distance from the start, and the
117 // position of the node that we came from.
118 typedef struct PathfindingNode
119 {
120   int16_t x, y, fromX, fromY;
121   float distance;
122 } PathfindingNode;
123 
124 typedef struct EnemyId
125 {
126   uint16_t index;
127   uint16_t generation;
128 } EnemyId;
129 
130 typedef struct EnemyClassConfig
131 {
132   float speed;
133   float health;
134   float radius;
135   float maxAcceleration;
136   float requiredContactTime;
137   float explosionDamage;
138   float explosionRange;
139   float explosionPushbackPower;
140   int goldValue;
141 } EnemyClassConfig;
142 
143 typedef struct Enemy
144 {
145   int16_t currentX, currentY;
146   int16_t nextX, nextY;
147   Vector2 simPosition;
148   Vector2 simVelocity;
149   uint16_t generation;
150   float walkedDistance;
151   float startMovingTime;
152   float damage, futureDamage;
153   float contactTime;
154   uint8_t enemyType;
155   uint8_t movePathCount;
156   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
157 } Enemy;
158 
159 // a unit that uses sprites to be drawn
160 #define SPRITE_UNIT_PHASE_WEAPON_IDLE 0
161 #define SPRITE_UNIT_PHASE_WEAPON_COOLDOWN 1
162 typedef struct SpriteUnit
163 {
164   Rectangle srcRect;
165   Vector2 offset;
166   int frameCount;
167   float frameDuration;
168   Rectangle srcWeaponIdleRect;
169   Vector2 srcWeaponIdleOffset;
170   Rectangle srcWeaponCooldownRect;
171   Vector2 srcWeaponCooldownOffset;
172 } SpriteUnit;
173 
174 #define PROJECTILE_MAX_COUNT 1200
175 #define PROJECTILE_TYPE_NONE 0
176 #define PROJECTILE_TYPE_ARROW 1
177 
178 typedef struct Projectile
179 {
180   uint8_t projectileType;
181   float shootTime;
182   float arrivalTime;
183   float distance;
184   float damage;
185   Vector3 position;
186   Vector3 target;
187   Vector3 directionNormal;
188   EnemyId targetEnemy;
189 } Projectile;
190 
191 //# Function declarations
192 float TowerGetMaxHealth(Tower *tower);
193 int Button(const char *text, int x, int y, int width, int height, ButtonState *state);
194 int EnemyAddDamage(Enemy *enemy, float damage);
195 
196 //# Enemy functions
197 void EnemyInit();
198 void EnemyDraw();
199 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource);
200 void EnemyUpdate();
201 float EnemyGetCurrentMaxSpeed(Enemy *enemy);
202 float EnemyGetMaxHealth(Enemy *enemy);
203 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY);
204 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount);
205 EnemyId EnemyGetId(Enemy *enemy);
206 Enemy *EnemyTryResolve(EnemyId enemyId);
207 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY);
208 int EnemyAddDamage(Enemy *enemy, float damage);
209 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range);
210 int EnemyCount();
211 void EnemyDrawHealthbars(Camera3D camera);
212 
213 //# Tower functions
214 void TowerInit();
215 Tower *TowerGetAt(int16_t x, int16_t y);
216 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y);
217 Tower *GetTowerByType(uint8_t towerType);
218 int GetTowerCosts(uint8_t towerType);
219 float TowerGetMaxHealth(Tower *tower);
220 void TowerDraw();
221 void TowerUpdate();
222 void TowerDrawHealthBars(Camera3D camera);
223 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase);
224 
225 //# Particles
226 void ParticleInit();
227 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime);
228 void ParticleUpdate();
229 void ParticleDraw();
230 
231 //# Projectiles
232 void ProjectileInit();
233 void ProjectileDraw();
234 void ProjectileUpdate();
235 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, float damage);
236 
237 //# Pathfinding map
238 void PathfindingMapInit(int width, int height, Vector3 translate, float scale);
239 float PathFindingGetDistance(int mapX, int mapY);
240 Vector2 PathFindingGetGradient(Vector3 world);
241 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY);
242 void PathFindingMapUpdate();
243 void PathFindingMapDraw();
244 
245 //# UI
246 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth);
247 
248 //# variables
249 extern Level *currentLevel;
250 extern Enemy enemies[ENEMY_MAX_COUNT];
251 extern int enemyCount;
252 extern EnemyClassConfig enemyClassConfigs[];
253 
254 extern GUIState guiState;
255 extern GameTime gameTime;
256 extern Tower towers[TOWER_MAX_COUNT];
257 extern int towerCount;
258 
259 #endif  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 Tower towers[TOWER_MAX_COUNT];
  5 int towerCount = 0;
  6 
  7 Model towerModels[TOWER_TYPE_COUNT];
  8 Texture2D palette, spriteSheet;
  9 
 10 // definition of our archer unit
 11 SpriteUnit archerUnit = {
 12     .srcRect = {0, 0, 16, 16},
 13     .offset = {7, 1},
 14     .frameCount = 1,
 15     .frameDuration = 0.0f,
 16     .srcWeaponIdleRect = {16, 0, 6, 16},
 17     .srcWeaponIdleOffset = {8, 0},
 18     .srcWeaponCooldownRect = {22, 0, 11, 16},
 19     .srcWeaponCooldownOffset = {10, 0},
 20 };
 21 
 22 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
 23 {
 24   float xScale = flip ? -1.0f : 1.0f;
 25   Camera3D camera = currentLevel->camera;
 26   float size = 0.5f;
 27   Vector2 offset = (Vector2){ unit.offset.x / 16.0f * size, unit.offset.y / 16.0f * size * xScale };
 28   Vector2 scale = (Vector2){ unit.srcRect.width / 16.0f * size, unit.srcRect.height / 16.0f * size };
 29   // we want the sprite to face the camera, so we need to calculate the up vector
 30   Vector3 forward = Vector3Subtract(camera.target, camera.position);
 31   Vector3 up = {0, 1, 0};
 32   Vector3 right = Vector3CrossProduct(forward, up);
 33   up = Vector3Normalize(Vector3CrossProduct(right, forward));
 34 
 35   Rectangle srcRect = unit.srcRect;
 36   if (unit.frameCount > 1)
 37   {
 38     srcRect.x += (int)(t / unit.frameDuration) % unit.frameCount * srcRect.width;
 39   }
 40   if (flip)
 41   {
 42     srcRect.x += srcRect.width;
 43     srcRect.width = -srcRect.width;
 44   }
 45   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 46 
 47   if (phase == SPRITE_UNIT_PHASE_WEAPON_COOLDOWN && unit.srcWeaponCooldownRect.width > 0)
 48   {
 49     offset = (Vector2){ unit.srcWeaponCooldownOffset.x / 16.0f * size, unit.srcWeaponCooldownOffset.y / 16.0f * size };
 50     scale = (Vector2){ unit.srcWeaponCooldownRect.width / 16.0f * size, unit.srcWeaponCooldownRect.height / 16.0f * size };
 51     srcRect = unit.srcWeaponCooldownRect;
 52     if (flip)
 53     {
 54       // position.x = flip * scale.x * 0.5f;
 55       srcRect.x += srcRect.width;
 56       srcRect.width = -srcRect.width;
 57       offset.x = scale.x - offset.x;
 58     }
 59     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 60   }
 61   else if (phase == SPRITE_UNIT_PHASE_WEAPON_IDLE && unit.srcWeaponIdleRect.width > 0)
 62   {
 63     offset = (Vector2){ unit.srcWeaponIdleOffset.x / 16.0f * size, unit.srcWeaponIdleOffset.y / 16.0f * size };
 64     scale = (Vector2){ unit.srcWeaponIdleRect.width / 16.0f * size, unit.srcWeaponIdleRect.height / 16.0f * size };
 65     srcRect = unit.srcWeaponIdleRect;
 66     if (flip)
 67     {
 68       // position.x = flip * scale.x * 0.5f;
 69       srcRect.x += srcRect.width;
 70       srcRect.width = -srcRect.width;
 71       offset.x = scale.x - offset.x;
 72     }
 73     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 74   }
 75 }
 76 
 77 void TowerInit()
 78 {
 79   for (int i = 0; i < TOWER_MAX_COUNT; i++)
 80   {
 81     towers[i] = (Tower){0};
 82   }
 83   towerCount = 0;
 84 
 85   // load a sprite sheet that contains all units
 86   spriteSheet = LoadTexture("data/spritesheet.png");
 87   SetTextureFilter(spriteSheet, TEXTURE_FILTER_BILINEAR);
 88 
 89   // we'll use a palette texture to colorize the all buildings and environment art
 90   palette = LoadTexture("data/palette.png");
 91   // The texture uses gradients on very small space, so we'll enable bilinear filtering
 92   SetTextureFilter(palette, TEXTURE_FILTER_BILINEAR);
 93 
 94   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
 95   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
 96 
 97   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
 98   {
 99     if (towerModels[i].materials)
100     {
101       // assign the palette texture to the material of the model (0 is not used afaik)
102       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
103     }
104   }
105 }
106 
107 static void TowerGunUpdate(Tower *tower)
108 {
109   if (tower->cooldown <= 0)
110   {
111     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
112     if (enemy)
113     {
114       tower->cooldown = 0.5f;
115       // shoot the enemy; determine future position of the enemy
116       float bulletSpeed = 4.0f;
117       float bulletDamage = 3.0f;
118       Vector2 velocity = enemy->simVelocity;
119       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
120       Vector2 towerPosition = {tower->x, tower->y};
121       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
122       for (int i = 0; i < 8; i++) {
123         velocity = enemy->simVelocity;
124         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
125         float distance = Vector2Distance(towerPosition, futurePosition);
126         float eta2 = distance / bulletSpeed;
127         if (fabs(eta - eta2) < 0.01f) {
128           break;
129         }
130         eta = (eta2 + eta) * 0.5f;
131       }
132       ProjectileTryAdd(PROJECTILE_TYPE_ARROW, enemy, 
133         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
134         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
135         bulletSpeed, bulletDamage);
136       enemy->futureDamage += bulletDamage;
137       tower->lastTargetPosition = futurePosition;
138     }
139   }
140   else
141   {
142     tower->cooldown -= gameTime.deltaTime;
143   }
144 }
145 
146 Tower *TowerGetAt(int16_t x, int16_t y)
147 {
148   for (int i = 0; i < towerCount; i++)
149   {
150     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
151     {
152       return &towers[i];
153     }
154   }
155   return 0;
156 }
157 
158 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
159 {
160   if (towerCount >= TOWER_MAX_COUNT)
161   {
162     return 0;
163   }
164 
165   Tower *tower = TowerGetAt(x, y);
166   if (tower)
167   {
168     return 0;
169   }
170 
171   tower = &towers[towerCount++];
172   tower->x = x;
173   tower->y = y;
174   tower->towerType = towerType;
175   tower->cooldown = 0.0f;
176   tower->damage = 0.0f;
177   return tower;
178 }
179 
180 Tower *GetTowerByType(uint8_t towerType)
181 {
182   for (int i = 0; i < towerCount; i++)
183   {
184     if (towers[i].towerType == towerType)
185     {
186       return &towers[i];
187     }
188   }
189   return 0;
190 }
191 
192 int GetTowerCosts(uint8_t towerType)
193 {
194   switch (towerType)
195   {
196   case TOWER_TYPE_BASE:
197     return 0;
198   case TOWER_TYPE_GUN:
199     return 6;
200   case TOWER_TYPE_WALL:
201     return 2;
202   }
203   return 0;
204 }
205 
206 float TowerGetMaxHealth(Tower *tower)
207 {
208   switch (tower->towerType)
209   {
210   case TOWER_TYPE_BASE:
211     return 10.0f;
212   case TOWER_TYPE_GUN:
213     return 3.0f;
214   case TOWER_TYPE_WALL:
215     return 5.0f;
216   }
217   return 0.0f;
218 }
219 
220 void TowerDraw()
221 {
222   for (int i = 0; i < towerCount; i++)
223   {
224     Tower tower = towers[i];
225     if (tower.towerType == TOWER_TYPE_NONE)
226     {
227       continue;
228     }
229 
230     switch (tower.towerType)
231     {
232     case TOWER_TYPE_GUN:
233       {
234         Vector2 screenPosTower = GetWorldToScreen((Vector3){tower.x, 0.0f, tower.y}, currentLevel->camera);
235         Vector2 screenPosTarget = GetWorldToScreen((Vector3){tower.lastTargetPosition.x, 0.0f, tower.lastTargetPosition.y}, currentLevel->camera);
236         DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
237         DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0, screenPosTarget.x > screenPosTower.x, 
238           tower.cooldown > 0.2f ? SPRITE_UNIT_PHASE_WEAPON_COOLDOWN : SPRITE_UNIT_PHASE_WEAPON_IDLE);
239       }
240       break;
241     default:
242       if (towerModels[tower.towerType].materials)
243       {
244         DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
245       } else {
246         DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
247       }
248       break;
249     }
250   }
251 }
252 
253 void TowerUpdate()
254 {
255   for (int i = 0; i < towerCount; i++)
256   {
257     Tower *tower = &towers[i];
258     switch (tower->towerType)
259     {
260     case TOWER_TYPE_GUN:
261       TowerGunUpdate(tower);
262       break;
263     }
264   }
265 }
266 
267 void TowerDrawHealthBars(Camera3D camera)
268 {
269   for (int i = 0; i < towerCount; i++)
270   {
271     Tower *tower = &towers[i];
272     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
273     {
274       continue;
275     }
276     
277     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
278     float maxHealth = TowerGetMaxHealth(tower);
279     float health = maxHealth - tower->damage;
280     float healthRatio = health / maxHealth;
281     
282     DrawHealthBar(camera, position, healthRatio, GREEN, 35.0f);
283   }
284 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 EnemyClassConfig enemyClassConfigs[] = {
  7     [ENEMY_TYPE_MINION] = {
  8       .health = 10.0f, 
  9       .speed = 0.6f, 
 10       .radius = 0.25f, 
 11       .maxAcceleration = 1.0f,
 12       .explosionDamage = 1.0f,
 13       .requiredContactTime = 0.5f,
 14       .explosionRange = 1.0f,
 15       .explosionPushbackPower = 0.25f,
 16       .goldValue = 1,
 17     },
 18 };
 19 
 20 Enemy enemies[ENEMY_MAX_COUNT];
 21 int enemyCount = 0;
 22 
 23 SpriteUnit enemySprites[] = {
 24     [ENEMY_TYPE_MINION] = {
 25       .srcRect = {0, 16, 16, 16},
 26       .offset = {8.0f, 0.0f},
 27       .frameCount = 6,
 28       .frameDuration = 0.1f,
 29     },
 30 };
 31 
 32 void EnemyInit()
 33 {
 34   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 35   {
 36     enemies[i] = (Enemy){0};
 37   }
 38   enemyCount = 0;
 39 }
 40 
 41 float EnemyGetCurrentMaxSpeed(Enemy *enemy)
 42 {
 43   return enemyClassConfigs[enemy->enemyType].speed;
 44 }
 45 
 46 float EnemyGetMaxHealth(Enemy *enemy)
 47 {
 48   return enemyClassConfigs[enemy->enemyType].health;
 49 }
 50 
 51 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY)
 52 {
 53   int16_t castleX = 0;
 54   int16_t castleY = 0;
 55   int16_t dx = castleX - currentX;
 56   int16_t dy = castleY - currentY;
 57   if (dx == 0 && dy == 0)
 58   {
 59     *nextX = currentX;
 60     *nextY = currentY;
 61     return 1;
 62   }
 63   Vector2 gradient = PathFindingGetGradient((Vector3){currentX, 0, currentY});
 64 
 65   if (gradient.x == 0 && gradient.y == 0)
 66   {
 67     *nextX = currentX;
 68     *nextY = currentY;
 69     return 1;
 70   }
 71 
 72   if (fabsf(gradient.x) > fabsf(gradient.y))
 73   {
 74     *nextX = currentX + (int16_t)(gradient.x > 0.0f ? 1 : -1);
 75     *nextY = currentY;
 76     return 0;
 77   }
 78   *nextX = currentX;
 79   *nextY = currentY + (int16_t)(gradient.y > 0.0f ? 1 : -1);
 80   return 0;
 81 }
 82 
 83 
 84 // this function predicts the movement of the unit for the next deltaT seconds
 85 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount)
 86 {
 87   const float pointReachedDistance = 0.25f;
 88   const float pointReachedDistance2 = pointReachedDistance * pointReachedDistance;
 89   const float maxSimStepTime = 0.015625f;
 90   
 91   float maxAcceleration = enemyClassConfigs[enemy->enemyType].maxAcceleration;
 92   float maxSpeed = EnemyGetCurrentMaxSpeed(enemy);
 93   int16_t nextX = enemy->nextX;
 94   int16_t nextY = enemy->nextY;
 95   Vector2 position = enemy->simPosition;
 96   int passedCount = 0;
 97   for (float t = 0.0f; t < deltaT; t += maxSimStepTime)
 98   {
 99     float stepTime = fminf(deltaT - t, maxSimStepTime);
100     Vector2 target = (Vector2){nextX, nextY};
101     float speed = Vector2Length(*velocity);
102     // draw the target position for debugging
103     DrawCubeWires((Vector3){target.x, 0.2f, target.y}, 0.1f, 0.4f, 0.1f, RED);
104     Vector2 lookForwardPos = Vector2Add(position, Vector2Scale(*velocity, speed));
105     if (Vector2DistanceSqr(target, lookForwardPos) <= pointReachedDistance2)
106     {
107       // we reached the target position, let's move to the next waypoint
108       EnemyGetNextPosition(nextX, nextY, &nextX, &nextY);
109       target = (Vector2){nextX, nextY};
110       // track how many waypoints we passed
111       passedCount++;
112     }
113     
114     // acceleration towards the target
115     Vector2 unitDirection = Vector2Normalize(Vector2Subtract(target, lookForwardPos));
116     Vector2 acceleration = Vector2Scale(unitDirection, maxAcceleration * stepTime);
117     *velocity = Vector2Add(*velocity, acceleration);
118 
119     // limit the speed to the maximum speed
120     if (speed > maxSpeed)
121     {
122       *velocity = Vector2Scale(*velocity, maxSpeed / speed);
123     }
124 
125     // move the enemy
126     position = Vector2Add(position, Vector2Scale(*velocity, stepTime));
127   }
128 
129   if (waypointPassedCount)
130   {
131     (*waypointPassedCount) = passedCount;
132   }
133 
134   return position;
135 }
136 
137 void EnemyDraw()
138 {
139   for (int i = 0; i < enemyCount; i++)
140   {
141     Enemy enemy = enemies[i];
142     if (enemy.enemyType == ENEMY_TYPE_NONE)
143     {
144       continue;
145     }
146 
147     Vector2 position = EnemyGetPosition(&enemy, gameTime.time - enemy.startMovingTime, &enemy.simVelocity, 0);
148     
149     // don't draw any trails for now; might replace this with footprints later
150     // if (enemy.movePathCount > 0)
151     // {
152     //   Vector3 p = {enemy.movePath[0].x, 0.2f, enemy.movePath[0].y};
153     //   DrawLine3D(p, (Vector3){position.x, 0.2f, position.y}, GREEN);
154     // }
155     // for (int j = 1; j < enemy.movePathCount; j++)
156     // {
157     //   Vector3 p = {enemy.movePath[j - 1].x, 0.2f, enemy.movePath[j - 1].y};
158     //   Vector3 q = {enemy.movePath[j].x, 0.2f, enemy.movePath[j].y};
159     //   DrawLine3D(p, q, GREEN);
160     // }
161 
162     switch (enemy.enemyType)
163     {
164     case ENEMY_TYPE_MINION:
165       DrawSpriteUnit(enemySprites[ENEMY_TYPE_MINION], (Vector3){position.x, 0.0f, position.y}, 
166         enemy.walkedDistance, 0, 0);
167       break;
168     }
169   }
170 }
171 
172 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource)
173 {
174   // damage the tower
175   float explosionDamge = enemyClassConfigs[enemy->enemyType].explosionDamage;
176   float explosionRange = enemyClassConfigs[enemy->enemyType].explosionRange;
177   float explosionPushbackPower = enemyClassConfigs[enemy->enemyType].explosionPushbackPower;
178   float explosionRange2 = explosionRange * explosionRange;
179   tower->damage += enemyClassConfigs[enemy->enemyType].explosionDamage;
180   // explode the enemy
181   if (tower->damage >= TowerGetMaxHealth(tower))
182   {
183     tower->towerType = TOWER_TYPE_NONE;
184   }
185 
186   ParticleAdd(PARTICLE_TYPE_EXPLOSION, 
187     explosionSource, 
188     (Vector3){0, 0.1f, 0}, 1.0f);
189 
190   enemy->enemyType = ENEMY_TYPE_NONE;
191 
192   // push back enemies & dealing damage
193   for (int i = 0; i < enemyCount; i++)
194   {
195     Enemy *other = &enemies[i];
196     if (other->enemyType == ENEMY_TYPE_NONE)
197     {
198       continue;
199     }
200     float distanceSqr = Vector2DistanceSqr(enemy->simPosition, other->simPosition);
201     if (distanceSqr > 0 && distanceSqr < explosionRange2)
202     {
203       Vector2 direction = Vector2Normalize(Vector2Subtract(other->simPosition, enemy->simPosition));
204       other->simPosition = Vector2Add(other->simPosition, Vector2Scale(direction, explosionPushbackPower));
205       EnemyAddDamage(other, explosionDamge);
206     }
207   }
208 }
209 
210 void EnemyUpdate()
211 {
212   const float castleX = 0;
213   const float castleY = 0;
214   const float maxPathDistance2 = 0.25f * 0.25f;
215   
216   for (int i = 0; i < enemyCount; i++)
217   {
218     Enemy *enemy = &enemies[i];
219     if (enemy->enemyType == ENEMY_TYPE_NONE)
220     {
221       continue;
222     }
223 
224     int waypointPassedCount = 0;
225     Vector2 prevPosition = enemy->simPosition;
226     enemy->simPosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &enemy->simVelocity, &waypointPassedCount);
227     enemy->startMovingTime = gameTime.time;
228     enemy->walkedDistance += Vector2Distance(prevPosition, enemy->simPosition);
229     // track path of unit
230     if (enemy->movePathCount == 0 || Vector2DistanceSqr(enemy->simPosition, enemy->movePath[0]) > maxPathDistance2)
231     {
232       for (int j = ENEMY_MAX_PATH_COUNT - 1; j > 0; j--)
233       {
234         enemy->movePath[j] = enemy->movePath[j - 1];
235       }
236       enemy->movePath[0] = enemy->simPosition;
237       if (++enemy->movePathCount > ENEMY_MAX_PATH_COUNT)
238       {
239         enemy->movePathCount = ENEMY_MAX_PATH_COUNT;
240       }
241     }
242 
243     if (waypointPassedCount > 0)
244     {
245       enemy->currentX = enemy->nextX;
246       enemy->currentY = enemy->nextY;
247       if (EnemyGetNextPosition(enemy->currentX, enemy->currentY, &enemy->nextX, &enemy->nextY) &&
248         Vector2DistanceSqr(enemy->simPosition, (Vector2){castleX, castleY}) <= 0.25f * 0.25f)
249       {
250         // enemy reached the castle; remove it
251         enemy->enemyType = ENEMY_TYPE_NONE;
252         continue;
253       }
254     }
255   }
256 
257   // handle collisions between enemies
258   for (int i = 0; i < enemyCount - 1; i++)
259   {
260     Enemy *enemyA = &enemies[i];
261     if (enemyA->enemyType == ENEMY_TYPE_NONE)
262     {
263       continue;
264     }
265     for (int j = i + 1; j < enemyCount; j++)
266     {
267       Enemy *enemyB = &enemies[j];
268       if (enemyB->enemyType == ENEMY_TYPE_NONE)
269       {
270         continue;
271       }
272       float distanceSqr = Vector2DistanceSqr(enemyA->simPosition, enemyB->simPosition);
273       float radiusA = enemyClassConfigs[enemyA->enemyType].radius;
274       float radiusB = enemyClassConfigs[enemyB->enemyType].radius;
275       float radiusSum = radiusA + radiusB;
276       if (distanceSqr < radiusSum * radiusSum && distanceSqr > 0.001f)
277       {
278         // collision
279         float distance = sqrtf(distanceSqr);
280         float overlap = radiusSum - distance;
281         // move the enemies apart, but softly; if we have a clog of enemies,
282         // moving them perfectly apart can cause them to jitter
283         float positionCorrection = overlap / 5.0f;
284         Vector2 direction = (Vector2){
285             (enemyB->simPosition.x - enemyA->simPosition.x) / distance * positionCorrection,
286             (enemyB->simPosition.y - enemyA->simPosition.y) / distance * positionCorrection};
287         enemyA->simPosition = Vector2Subtract(enemyA->simPosition, direction);
288         enemyB->simPosition = Vector2Add(enemyB->simPosition, direction);
289       }
290     }
291   }
292 
293   // handle collisions between enemies and towers
294   for (int i = 0; i < enemyCount; i++)
295   {
296     Enemy *enemy = &enemies[i];
297     if (enemy->enemyType == ENEMY_TYPE_NONE)
298     {
299       continue;
300     }
301     enemy->contactTime -= gameTime.deltaTime;
302     if (enemy->contactTime < 0.0f)
303     {
304       enemy->contactTime = 0.0f;
305     }
306 
307     float enemyRadius = enemyClassConfigs[enemy->enemyType].radius;
308     // linear search over towers; could be optimized by using path finding tower map,
309     // but for now, we keep it simple
310     for (int j = 0; j < towerCount; j++)
311     {
312       Tower *tower = &towers[j];
313       if (tower->towerType == TOWER_TYPE_NONE)
314       {
315         continue;
316       }
317       float distanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){tower->x, tower->y});
318       float combinedRadius = enemyRadius + 0.708; // sqrt(0.5^2 + 0.5^2), corner-center distance of square with side length 1
319       if (distanceSqr > combinedRadius * combinedRadius)
320       {
321         continue;
322       }
323       // potential collision; square / circle intersection
324       float dx = tower->x - enemy->simPosition.x;
325       float dy = tower->y - enemy->simPosition.y;
326       float absDx = fabsf(dx);
327       float absDy = fabsf(dy);
328       Vector3 contactPoint = {0};
329       if (absDx <= 0.5f && absDx <= absDy) {
330         // vertical collision; push the enemy out horizontally
331         float overlap = enemyRadius + 0.5f - absDy;
332         if (overlap < 0.0f)
333         {
334           continue;
335         }
336         float direction = dy > 0.0f ? -1.0f : 1.0f;
337         enemy->simPosition.y += direction * overlap;
338         contactPoint = (Vector3){enemy->simPosition.x, 0.2f, tower->y + direction * 0.5f};
339       }
340       else if (absDy <= 0.5f && absDy <= absDx)
341       {
342         // horizontal collision; push the enemy out vertically
343         float overlap = enemyRadius + 0.5f - absDx;
344         if (overlap < 0.0f)
345         {
346           continue;
347         }
348         float direction = dx > 0.0f ? -1.0f : 1.0f;
349         enemy->simPosition.x += direction * overlap;
350         contactPoint = (Vector3){tower->x + direction * 0.5f, 0.2f, enemy->simPosition.y};
351       }
352       else
353       {
354         // possible collision with a corner
355         float cornerDX = dx > 0.0f ? -0.5f : 0.5f;
356         float cornerDY = dy > 0.0f ? -0.5f : 0.5f;
357         float cornerX = tower->x + cornerDX;
358         float cornerY = tower->y + cornerDY;
359         float cornerDistanceSqr = Vector2DistanceSqr(enemy->simPosition, (Vector2){cornerX, cornerY});
360         if (cornerDistanceSqr > enemyRadius * enemyRadius)
361         {
362           continue;
363         }
364         // push the enemy out along the diagonal
365         float cornerDistance = sqrtf(cornerDistanceSqr);
366         float overlap = enemyRadius - cornerDistance;
367         float directionX = cornerDistance > 0.0f ? (cornerX - enemy->simPosition.x) / cornerDistance : -cornerDX;
368         float directionY = cornerDistance > 0.0f ? (cornerY - enemy->simPosition.y) / cornerDistance : -cornerDY;
369         enemy->simPosition.x -= directionX * overlap;
370         enemy->simPosition.y -= directionY * overlap;
371         contactPoint = (Vector3){cornerX, 0.2f, cornerY};
372       }
373 
374       if (enemyClassConfigs[enemy->enemyType].explosionDamage > 0.0f)
375       {
376         enemy->contactTime += gameTime.deltaTime * 2.0f; // * 2 to undo the subtraction above
377         if (enemy->contactTime >= enemyClassConfigs[enemy->enemyType].requiredContactTime)
378         {
379           EnemyTriggerExplode(enemy, tower, contactPoint);
380         }
381       }
382     }
383   }
384 }
385 
386 EnemyId EnemyGetId(Enemy *enemy)
387 {
388   return (EnemyId){enemy - enemies, enemy->generation};
389 }
390 
391 Enemy *EnemyTryResolve(EnemyId enemyId)
392 {
393   if (enemyId.index >= ENEMY_MAX_COUNT)
394   {
395     return 0;
396   }
397   Enemy *enemy = &enemies[enemyId.index];
398   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
399   {
400     return 0;
401   }
402   return enemy;
403 }
404 
405 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
406 {
407   Enemy *spawn = 0;
408   for (int i = 0; i < enemyCount; i++)
409   {
410     Enemy *enemy = &enemies[i];
411     if (enemy->enemyType == ENEMY_TYPE_NONE)
412     {
413       spawn = enemy;
414       break;
415     }
416   }
417 
418   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
419   {
420     spawn = &enemies[enemyCount++];
421   }
422 
423   if (spawn)
424   {
425     spawn->currentX = currentX;
426     spawn->currentY = currentY;
427     spawn->nextX = currentX;
428     spawn->nextY = currentY;
429     spawn->simPosition = (Vector2){currentX, currentY};
430     spawn->simVelocity = (Vector2){0, 0};
431     spawn->enemyType = enemyType;
432     spawn->startMovingTime = gameTime.time;
433     spawn->damage = 0.0f;
434     spawn->futureDamage = 0.0f;
435     spawn->generation++;
436     spawn->movePathCount = 0;
437     spawn->walkedDistance = 0.0f;
438   }
439 
440   return spawn;
441 }
442 
443 int EnemyAddDamage(Enemy *enemy, float damage)
444 {
445   enemy->damage += damage;
446   if (enemy->damage >= EnemyGetMaxHealth(enemy))
447   {
448     currentLevel->playerGold += enemyClassConfigs[enemy->enemyType].goldValue;
449     enemy->enemyType = ENEMY_TYPE_NONE;
450     return 1;
451   }
452 
453   return 0;
454 }
455 
456 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
457 {
458   int16_t castleX = 0;
459   int16_t castleY = 0;
460   Enemy* closest = 0;
461   int16_t closestDistance = 0;
462   float range2 = range * range;
463   for (int i = 0; i < enemyCount; i++)
464   {
465     Enemy* enemy = &enemies[i];
466     if (enemy->enemyType == ENEMY_TYPE_NONE)
467     {
468       continue;
469     }
470     float maxHealth = EnemyGetMaxHealth(enemy);
471     if (enemy->futureDamage >= maxHealth)
472     {
473       // ignore enemies that will die soon
474       continue;
475     }
476     int16_t dx = castleX - enemy->currentX;
477     int16_t dy = castleY - enemy->currentY;
478     int16_t distance = abs(dx) + abs(dy);
479     if (!closest || distance < closestDistance)
480     {
481       float tdx = towerX - enemy->currentX;
482       float tdy = towerY - enemy->currentY;
483       float tdistance2 = tdx * tdx + tdy * tdy;
484       if (tdistance2 <= range2)
485       {
486         closest = enemy;
487         closestDistance = distance;
488       }
489     }
490   }
491   return closest;
492 }
493 
494 int EnemyCount()
495 {
496   int count = 0;
497   for (int i = 0; i < enemyCount; i++)
498   {
499     if (enemies[i].enemyType != ENEMY_TYPE_NONE)
500     {
501       count++;
502     }
503   }
504   return count;
505 }
506 
507 void EnemyDrawHealthbars(Camera3D camera)
508 {
509   for (int i = 0; i < enemyCount; i++)
510   {
511     Enemy *enemy = &enemies[i];
512     if (enemy->enemyType == ENEMY_TYPE_NONE || enemy->damage == 0.0f)
513     {
514       continue;
515     }
516     Vector3 position = (Vector3){enemy->simPosition.x, 0.5f, enemy->simPosition.y};
517     float maxHealth = EnemyGetMaxHealth(enemy);
518     float health = maxHealth - enemy->damage;
519     float healthRatio = health / maxHealth;
520     
521     DrawHealthBar(camera, position, healthRatio, GREEN, 15.0f);
522   }
523 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 // The queue is a simple array of nodes, we add nodes to the end and remove
  5 // nodes from the front. We keep the array around to avoid unnecessary allocations
  6 static PathfindingNode *pathfindingNodeQueue = 0;
  7 static int pathfindingNodeQueueCount = 0;
  8 static int pathfindingNodeQueueCapacity = 0;
  9 
 10 // The pathfinding map stores the distances from the castle to each cell in the map.
 11 static PathfindingMap pathfindingMap = {0};
 12 
 13 void PathfindingMapInit(int width, int height, Vector3 translate, float scale)
 14 {
 15   // transforming between map space and world space allows us to adapt 
 16   // position and scale of the map without changing the pathfinding data
 17   pathfindingMap.toWorldSpace = MatrixTranslate(translate.x, translate.y, translate.z);
 18   pathfindingMap.toWorldSpace = MatrixMultiply(pathfindingMap.toWorldSpace, MatrixScale(scale, scale, scale));
 19   pathfindingMap.toMapSpace = MatrixInvert(pathfindingMap.toWorldSpace);
 20   pathfindingMap.width = width;
 21   pathfindingMap.height = height;
 22   pathfindingMap.scale = scale;
 23   pathfindingMap.distances = (float *)MemAlloc(width * height * sizeof(float));
 24   for (int i = 0; i < width * height; i++)
 25   {
 26     pathfindingMap.distances[i] = -1.0f;
 27   }
 28 
 29   pathfindingMap.towerIndex = (long *)MemAlloc(width * height * sizeof(long));
 30   pathfindingMap.deltaSrc = (DeltaSrc *)MemAlloc(width * height * sizeof(DeltaSrc));
 31 }
 32 
 33 static void PathFindingNodePush(int16_t x, int16_t y, int16_t fromX, int16_t fromY, float distance)
 34 {
 35   if (pathfindingNodeQueueCount >= pathfindingNodeQueueCapacity)
 36   {
 37     pathfindingNodeQueueCapacity = pathfindingNodeQueueCapacity == 0 ? 256 : pathfindingNodeQueueCapacity * 2;
 38     // we use MemAlloc/MemRealloc to allocate memory for the queue
 39     // I am not entirely sure if MemRealloc allows passing a null pointer
 40     // so we check if the pointer is null and use MemAlloc in that case
 41     if (pathfindingNodeQueue == 0)
 42     {
 43       pathfindingNodeQueue = (PathfindingNode *)MemAlloc(pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 44     }
 45     else
 46     {
 47       pathfindingNodeQueue = (PathfindingNode *)MemRealloc(pathfindingNodeQueue, pathfindingNodeQueueCapacity * sizeof(PathfindingNode));
 48     }
 49   }
 50 
 51   PathfindingNode *node = &pathfindingNodeQueue[pathfindingNodeQueueCount++];
 52   node->x = x;
 53   node->y = y;
 54   node->fromX = fromX;
 55   node->fromY = fromY;
 56   node->distance = distance;
 57 }
 58 
 59 static PathfindingNode *PathFindingNodePop()
 60 {
 61   if (pathfindingNodeQueueCount == 0)
 62   {
 63     return 0;
 64   }
 65   // we return the first node in the queue; we want to return a pointer to the node
 66   // so we can return 0 if the queue is empty. 
 67   // We should _not_ return a pointer to the element in the list, because the list
 68   // may be reallocated and the pointer would become invalid. Or the 
 69   // popped element is overwritten by the next push operation.
 70   // Using static here means that the variable is permanently allocated.
 71   static PathfindingNode node;
 72   node = pathfindingNodeQueue[0];
 73   // we shift all nodes one position to the front
 74   for (int i = 1; i < pathfindingNodeQueueCount; i++)
 75   {
 76     pathfindingNodeQueue[i - 1] = pathfindingNodeQueue[i];
 77   }
 78   --pathfindingNodeQueueCount;
 79   return &node;
 80 }
 81 
 82 float PathFindingGetDistance(int mapX, int mapY)
 83 {
 84   if (mapX < 0 || mapX >= pathfindingMap.width || mapY < 0 || mapY >= pathfindingMap.height)
 85   {
 86     // when outside the map, we return the manhattan distance to the castle (0,0)
 87     return fabsf((float)mapX) + fabsf((float)mapY);
 88   }
 89 
 90   return pathfindingMap.distances[mapY * pathfindingMap.width + mapX];
 91 }
 92 
 93 // transform a world position to a map position in the array; 
 94 // returns true if the position is inside the map
 95 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY)
 96 {
 97   Vector3 mapPosition = Vector3Transform(worldPosition, pathfindingMap.toMapSpace);
 98   *mapX = (int16_t)mapPosition.x;
 99   *mapY = (int16_t)mapPosition.z;
100   return *mapX >= 0 && *mapX < pathfindingMap.width && *mapY >= 0 && *mapY < pathfindingMap.height;
101 }
102 
103 void PathFindingMapUpdate()
104 {
105   const int castleX = 0, castleY = 0;
106   int16_t castleMapX, castleMapY;
107   if (!PathFindingFromWorldToMapPosition((Vector3){castleX, 0.0f, castleY}, &castleMapX, &castleMapY))
108   {
109     return;
110   }
111   int width = pathfindingMap.width, height = pathfindingMap.height;
112 
113   // reset the distances to -1
114   for (int i = 0; i < width * height; i++)
115   {
116     pathfindingMap.distances[i] = -1.0f;
117   }
118   // reset the tower indices
119   for (int i = 0; i < width * height; i++)
120   {
121     pathfindingMap.towerIndex[i] = -1;
122   }
123   // reset the delta src
124   for (int i = 0; i < width * height; i++)
125   {
126     pathfindingMap.deltaSrc[i].x = 0;
127     pathfindingMap.deltaSrc[i].y = 0;
128   }
129 
130   for (int i = 0; i < towerCount; i++)
131   {
132     Tower *tower = &towers[i];
133     if (tower->towerType == TOWER_TYPE_NONE || tower->towerType == TOWER_TYPE_BASE)
134     {
135       continue;
136     }
137     int16_t mapX, mapY;
138     // technically, if the tower cell scale is not in sync with the pathfinding map scale,
139     // this would not work correctly and needs to be refined to allow towers covering multiple cells
140     // or having multiple towers in one cell; for simplicity, we assume that the tower covers exactly
141     // one cell. For now.
142     if (!PathFindingFromWorldToMapPosition((Vector3){tower->x, 0.0f, tower->y}, &mapX, &mapY))
143     {
144       continue;
145     }
146     int index = mapY * width + mapX;
147     pathfindingMap.towerIndex[index] = i;
148   }
149 
150   // we start at the castle and add the castle to the queue
151   pathfindingMap.maxDistance = 0.0f;
152   pathfindingNodeQueueCount = 0;
153   PathFindingNodePush(castleMapX, castleMapY, castleMapX, castleMapY, 0.0f);
154   PathfindingNode *node = 0;
155   while ((node = PathFindingNodePop()))
156   {
157     if (node->x < 0 || node->x >= width || node->y < 0 || node->y >= height)
158     {
159       continue;
160     }
161     int index = node->y * width + node->x;
162     if (pathfindingMap.distances[index] >= 0 && pathfindingMap.distances[index] <= node->distance)
163     {
164       continue;
165     }
166 
167     int deltaX = node->x - node->fromX;
168     int deltaY = node->y - node->fromY;
169     // even if the cell is blocked by a tower, we still may want to store the direction
170     // (though this might not be needed, IDK right now)
171     pathfindingMap.deltaSrc[index].x = (char) deltaX;
172     pathfindingMap.deltaSrc[index].y = (char) deltaY;
173 
174     // we skip nodes that are blocked by towers
175     if (pathfindingMap.towerIndex[index] >= 0)
176     {
177       node->distance += 8.0f;
178     }
179     pathfindingMap.distances[index] = node->distance;
180     pathfindingMap.maxDistance = fmaxf(pathfindingMap.maxDistance, node->distance);
181     PathFindingNodePush(node->x, node->y + 1, node->x, node->y, node->distance + 1.0f);
182     PathFindingNodePush(node->x, node->y - 1, node->x, node->y, node->distance + 1.0f);
183     PathFindingNodePush(node->x + 1, node->y, node->x, node->y, node->distance + 1.0f);
184     PathFindingNodePush(node->x - 1, node->y, node->x, node->y, node->distance + 1.0f);
185   }
186 }
187 
188 void PathFindingMapDraw()
189 {
190   float cellSize = pathfindingMap.scale * 0.9f;
191   float highlightDistance = fmodf(GetTime() * 4.0f, pathfindingMap.maxDistance);
192   for (int x = 0; x < pathfindingMap.width; x++)
193   {
194     for (int y = 0; y < pathfindingMap.height; y++)
195     {
196       float distance = pathfindingMap.distances[y * pathfindingMap.width + x];
197       float colorV = distance < 0 ? 0 : fminf(distance / pathfindingMap.maxDistance, 1.0f);
198       Color color = distance < 0 ? BLUE : (Color){fminf(colorV, 1.0f) * 255, 0, 0, 255};
199       Vector3 position = Vector3Transform((Vector3){x, -0.25f, y}, pathfindingMap.toWorldSpace);
200       // animate the distance "wave" to show how the pathfinding algorithm expands
201       // from the castle
202       if (distance + 0.5f > highlightDistance && distance - 0.5f < highlightDistance)
203       {
204         color = BLACK;
205       }
206       DrawCube(position, cellSize, 0.1f, cellSize, color);
207     }
208   }
209 }
210 
211 Vector2 PathFindingGetGradient(Vector3 world)
212 {
213   int16_t mapX, mapY;
214   if (PathFindingFromWorldToMapPosition(world, &mapX, &mapY))
215   {
216     DeltaSrc delta = pathfindingMap.deltaSrc[mapY * pathfindingMap.width + mapX];
217     return (Vector2){(float)-delta.x, (float)-delta.y};
218   }
219   // fallback to a simple gradient calculation
220   float n = PathFindingGetDistance(mapX, mapY - 1);
221   float s = PathFindingGetDistance(mapX, mapY + 1);
222   float w = PathFindingGetDistance(mapX - 1, mapY);
223   float e = PathFindingGetDistance(mapX + 1, mapY);
224   return (Vector2){w - e + 0.25f, n - s + 0.125f};
225 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static Projectile projectiles[PROJECTILE_MAX_COUNT];
  5 static int projectileCount = 0;
  6 
  7 void ProjectileInit()
  8 {
  9   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
 10   {
 11     projectiles[i] = (Projectile){0};
 12   }
 13 }
 14 
 15 void ProjectileDraw()
 16 {
 17   for (int i = 0; i < projectileCount; i++)
 18   {
 19     Projectile projectile = projectiles[i];
 20     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
 21     {
 22       continue;
 23     }
 24     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
 25     if (transition >= 1.0f)
 26     {
 27       continue;
 28     }
 29     for (float transitionOffset = 0.0f; transitionOffset < 1.0f; transitionOffset += 0.1f)
 30     {
 31       float t = transition + transitionOffset * 0.3f;
 32       if (t > 1.0f)
 33       {
 34         break;
 35       }
 36       Vector3 position = Vector3Lerp(projectile.position, projectile.target, t);
 37       Color color = RED;
 38       if (projectile.projectileType == PROJECTILE_TYPE_ARROW)
 39       {
 40         // make tip red but quickly fade to brown
 41         color = ColorLerp(BROWN, RED, transitionOffset * transitionOffset);
 42         // fake a ballista flight path using parabola equation
 43         float parabolaT = t - 0.5f;
 44         parabolaT = 1.0f - 4.0f * parabolaT * parabolaT;
 45         position.y += 0.15f * parabolaT * projectile.distance;
 46       }
 47 
 48       float size = 0.06f * (transitionOffset + 0.25f);
 49       DrawCube(position, size, size, size, color);
 50     }
 51   }
 52 }
 53 
 54 void ProjectileUpdate()
 55 {
 56   for (int i = 0; i < projectileCount; i++)
 57   {
 58     Projectile *projectile = &projectiles[i];
 59     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
 60     {
 61       continue;
 62     }
 63     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
 64     if (transition >= 1.0f)
 65     {
 66       projectile->projectileType = PROJECTILE_TYPE_NONE;
 67       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
 68       if (enemy)
 69       {
 70         EnemyAddDamage(enemy, projectile->damage);
 71       }
 72       continue;
 73     }
 74   }
 75 }
 76 
 77 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, float damage)
 78 {
 79   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
 80   {
 81     Projectile *projectile = &projectiles[i];
 82     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
 83     {
 84       projectile->projectileType = projectileType;
 85       projectile->shootTime = gameTime.time;
 86       float distance = Vector3Distance(position, target);
 87       projectile->arrivalTime = gameTime.time + distance / speed;
 88       projectile->damage = damage;
 89       projectile->position = position;
 90       projectile->target = target;
 91       projectile->directionNormal = Vector3Scale(Vector3Subtract(target, position), 1.0f / distance);
 92       projectile->distance = distance;
 93       projectile->targetEnemy = EnemyGetId(enemy);
 94       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
 95       return projectile;
 96     }
 97   }
 98   return 0;
 99 }  1 #include "td_main.h"
  2 #include <raymath.h>
  3 
  4 static Particle particles[PARTICLE_MAX_COUNT];
  5 static int particleCount = 0;
  6 
  7 void ParticleInit()
  8 {
  9   for (int i = 0; i < PARTICLE_MAX_COUNT; i++)
 10   {
 11     particles[i] = (Particle){0};
 12   }
 13   particleCount = 0;
 14 }
 15 
 16 static void DrawExplosionParticle(Particle *particle, float transition)
 17 {
 18   float size = 1.2f * (1.0f - transition);
 19   Color startColor = WHITE;
 20   Color endColor = RED;
 21   Color color = ColorLerp(startColor, endColor, transition);
 22   DrawCube(particle->position, size, size, size, color);
 23 }
 24 
 25 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime)
 26 {
 27   if (particleCount >= PARTICLE_MAX_COUNT)
 28   {
 29     return;
 30   }
 31 
 32   int index = -1;
 33   for (int i = 0; i < particleCount; i++)
 34   {
 35     if (particles[i].particleType == PARTICLE_TYPE_NONE)
 36     {
 37       index = i;
 38       break;
 39     }
 40   }
 41 
 42   if (index == -1)
 43   {
 44     index = particleCount++;
 45   }
 46 
 47   Particle *particle = &particles[index];
 48   particle->particleType = particleType;
 49   particle->spawnTime = gameTime.time;
 50   particle->lifetime = lifetime;
 51   particle->position = position;
 52   particle->velocity = velocity;
 53 }
 54 
 55 void ParticleUpdate()
 56 {
 57   for (int i = 0; i < particleCount; i++)
 58   {
 59     Particle *particle = &particles[i];
 60     if (particle->particleType == PARTICLE_TYPE_NONE)
 61     {
 62       continue;
 63     }
 64 
 65     float age = gameTime.time - particle->spawnTime;
 66 
 67     if (particle->lifetime > age)
 68     {
 69       particle->position = Vector3Add(particle->position, Vector3Scale(particle->velocity, gameTime.deltaTime));
 70     }
 71     else {
 72       particle->particleType = PARTICLE_TYPE_NONE;
 73     }
 74   }
 75 }
 76 
 77 void ParticleDraw()
 78 {
 79   for (int i = 0; i < particleCount; i++)
 80   {
 81     Particle particle = particles[i];
 82     if (particle.particleType == PARTICLE_TYPE_NONE)
 83     {
 84       continue;
 85     }
 86 
 87     float age = gameTime.time - particle.spawnTime;
 88     float transition = age / particle.lifetime;
 89     switch (particle.particleType)
 90     {
 91     case PARTICLE_TYPE_EXPLOSION:
 92       DrawExplosionParticle(&particle, transition);
 93       break;
 94     default:
 95       DrawCube(particle.position, 0.3f, 0.5f, 0.3f, RED);
 96       break;
 97     }
 98   }
 99 }  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 #endifThe major change here happens again in the DrawSpriteUnit function in the tower_system.c file:
  1 
  2 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
  3 {
  4   float xScale = flip ? -1.0f : 1.0f;
  5   Camera3D camera = currentLevel->camera;
  6   float size = 0.5f;
  7   Vector2 offset = (Vector2){ unit.offset.x / 16.0f * size, unit.offset.y / 16.0f * size * xScale };
  8   Vector2 scale = (Vector2){ unit.srcRect.width / 16.0f * size, unit.srcRect.height / 16.0f * size };
  9   // we want the sprite to face the camera, so we need to calculate the up vector
 10   Vector3 forward = Vector3Subtract(camera.target, camera.position);
 11   Vector3 up = {0, 1, 0};
 12   Vector3 right = Vector3CrossProduct(forward, up);
 13   up = Vector3Normalize(Vector3CrossProduct(right, forward));
 14 
 15   Rectangle srcRect = unit.srcRect;
 16   if (unit.frameCount > 1)
 17   {
 18     srcRect.x += (int)(t / unit.frameDuration) % unit.frameCount * srcRect.width;
 19   }
 20   if (flip)
 21   {
 22     srcRect.x += srcRect.width;
 23     srcRect.width = -srcRect.width;
 24   }
 25   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 26 
 27   if (phase == SPRITE_UNIT_PHASE_WEAPON_COOLDOWN && unit.srcWeaponCooldownRect.width > 0)
 28   {
 29     offset = (Vector2){ unit.srcWeaponCooldownOffset.x / 16.0f * size, unit.srcWeaponCooldownOffset.y / 16.0f * size };
 30     scale = (Vector2){ unit.srcWeaponCooldownRect.width / 16.0f * size, unit.srcWeaponCooldownRect.height / 16.0f * size };
 31     srcRect = unit.srcWeaponCooldownRect;
 32     if (flip)
 33     {
 34       // position.x = flip * scale.x * 0.5f;
 35       srcRect.x += srcRect.width;
 36       srcRect.width = -srcRect.width;
 37       offset.x = scale.x - offset.x;
 38     }
 39     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 40   }
 41   else if (phase == SPRITE_UNIT_PHASE_WEAPON_IDLE && unit.srcWeaponIdleRect.width > 0)
 42   {
 43     offset = (Vector2){ unit.srcWeaponIdleOffset.x / 16.0f * size, unit.srcWeaponIdleOffset.y / 16.0f * size };
 44     scale = (Vector2){ unit.srcWeaponIdleRect.width / 16.0f * size, unit.srcWeaponIdleRect.height / 16.0f * size };
 45     srcRect = unit.srcWeaponIdleRect;
 46     if (flip)
 47     {
 48       // position.x = flip * scale.x * 0.5f;
 49       srcRect.x += srcRect.width;
 50       srcRect.width = -srcRect.width;
 51       offset.x = scale.x - offset.x;
 52     }
 53     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 54   }
 55 }The function has become quite long by now, though it's reasonably simple still; the flip argument is used to flip the sprite and we have to mirror the graphics by adjusting the source rectangle and its offset and width.
The xscale is set to -1 when the flip argument is true, so the sprite is mirrored. In consequence however, various offsets and widths have to be adjusted as well. In this version, I still have 2 codepaths for the weapon idle and weapon cooldown phase, but it could be worthwhile at a later point to merge the two codepaths into one by abstracting the data. I would leave it like this for now.
A minor change I did next to that: I also changed the spritesheet filtering from point to bilinear:
Not an optimal choice; the problem with point filtering is that the sprites don't map 1:1 to the pixels on the screen and the result is that the pixels are sharp but sometimes doubled or missing. Bilinear filtering smooths the pixels, but the result is quite blurry - but it still looks better. For now, this is good enough.
At this point I'd like to defer the weapon animations and 3d decorations to the next post.
Wrap up
In this part, we continued adding graphics to the game. The archer now has a bow, shoots arrows that fly in a slight arc and the orcs have a walking animation. The archer's orientation is now correct and the spritesheet filtering is set to bilinear.
In the next part, we'll add more decorative elements to the game and make it look a bit more polished this way before looking into how to introduce more complex towers that use 3d models and animations.
In the meantime, I will also look into adding a makefile to the example zip files so compilation becomes easier.