Simple tower defense tutorial, part 9: Decorations
Let's quickly look at what we had last time:

Having graphics for the towers and enemies makes the ground look quite bad. It is time to change that!
If you have make installed, you can also provide the installation directory of raylib this way:
make RAYLIB_SRC_PATH=/path/to/raylib
Making the game look beautiful isn't really necessary to do that now, but it's a fairly easy step and one I enjoy a lot. There are lots of opinions when to work out the graphic style. Using placeholders until the gameplay is solid is the typical approach and one that makes a lot of sense in a professional environment while artists focus on finding the right style. Just know: There are work places where it is not accepted to work on graphics isn't allowed until the gameplay is solid.
For me, it's quite difficult to continue working with primitive placeholder art as it tends to distract me, so I usually spend more time on the graphics than I should (probably the reason why I tend to not finish games). But again, this isn't work for me. And one thing I learned is, that it's better to have fun with your hobby than making it a chore. That's the reason I am doing the graphics at this point - not because it needs to be done at this point but because it is something I enjoy!
Another point is: What I intend to do here isn't very complicated to do. Once you understand the concept, you should be able to apply this in different situations as well.
So the plan is:
- Adding a checkerboard pattern to the ground made of grass tiles
 - Adding decorative objects around the map using procedural object placement
 
Checkerboard ground tiles
This is easy: We use two different grass tiles and place them in a checkerboard pattern. We introduce a function to load assets more conveniently: The models I created use all the same texture, so the code should assign the texture to the model automatically when loading the model. This is done in the LoadGLBModel function in line 50. The LoadAssets function in line 62 loads all the models and textures. We will later add more models to this function. Since the game is kept small, we don't need to worry about unloading unused assets - which is a rather complex topic.
The DrawLevelGround function in line 192 is responsible for drawing the level ground. Here's the code:
  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 Model floorTileAModel = {0};
 11 Model floorTileBModel = {0};
 12 
 13 Texture2D palette, spriteSheet;
 14 
 15 Level levels[] = {
 16   [0] = {
 17     .state = LEVEL_STATE_BUILDING,
 18     .initialGold = 20,
 19     .waves[0] = {
 20       .enemyType = ENEMY_TYPE_MINION,
 21       .wave = 0,
 22       .count = 10,
 23       .interval = 2.5f,
 24       .delay = 1.0f,
 25       .spawnPosition = {0, 6},
 26     },
 27     .waves[1] = {
 28       .enemyType = ENEMY_TYPE_MINION,
 29       .wave = 1,
 30       .count = 20,
 31       .interval = 1.5f,
 32       .delay = 1.0f,
 33       .spawnPosition = {0, 6},
 34     },
 35     .waves[2] = {
 36       .enemyType = ENEMY_TYPE_MINION,
 37       .wave = 2,
 38       .count = 30,
 39       .interval = 1.2f,
 40       .delay = 1.0f,
 41       .spawnPosition = {0, 6},
 42     }
 43   },
 44 };
 45 
 46 Level *currentLevel = levels;
 47 
 48 //# Game
 49 
 50 static Model LoadGLBModel(char *filename)
 51 {
 52   Model model = LoadModel(TextFormat("data/%s.glb",filename));
 53   if (model.materialCount > 1)
 54   {
 55     model.materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 56   }
 57   return model;
 58 }
 59 
 60 void LoadAssets()
 61 {
 62   // load a sprite sheet that contains all units
 63   spriteSheet = LoadTexture("data/spritesheet.png");
 64   SetTextureFilter(spriteSheet, TEXTURE_FILTER_BILINEAR);
 65 
 66   // we'll use a palette texture to colorize the all buildings and environment art
 67   palette = LoadTexture("data/palette.png");
 68   // The texture uses gradients on very small space, so we'll enable bilinear filtering
 69   SetTextureFilter(palette, TEXTURE_FILTER_BILINEAR);
 70 
 71   floorTileAModel = LoadGLBModel("floor-tile-a");
 72   floorTileBModel = LoadGLBModel("floor-tile-b");
 73 }
 74 
 75 void InitLevel(Level *level)
 76 {
 77   TowerInit();
 78   EnemyInit();
 79   ProjectileInit();
 80   ParticleInit();
 81   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
 82 
 83   level->placementMode = 0;
 84   level->state = LEVEL_STATE_BUILDING;
 85   level->nextState = LEVEL_STATE_NONE;
 86   level->playerGold = level->initialGold;
 87   level->currentWave = 0;
 88 
 89   Camera *camera = &level->camera;
 90   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
 91   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
 92   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
 93   camera->fovy = 10.0f;
 94   camera->projection = CAMERA_ORTHOGRAPHIC;
 95 }
 96 
 97 void DrawLevelHud(Level *level)
 98 {
 99   const char *text = TextFormat("Gold: %d", level->playerGold);
100   Font font = GetFontDefault();
101   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
102   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
103 }
104 
105 void DrawLevelReportLostWave(Level *level)
106 {
107   BeginMode3D(level->camera);
108   DrawLevelGround(level);
109   TowerDraw();
110   EnemyDraw();
111   ProjectileDraw();
112   ParticleDraw();
113   guiState.isBlocked = 0;
114   EndMode3D();
115 
116   TowerDrawHealthBars(level->camera);
117 
118   const char *text = "Wave lost";
119   int textWidth = MeasureText(text, 20);
120   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
121 
122   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
123   {
124     level->nextState = LEVEL_STATE_RESET;
125   }
126 }
127 
128 int HasLevelNextWave(Level *level)
129 {
130   for (int i = 0; i < 10; i++)
131   {
132     EnemyWave *wave = &level->waves[i];
133     if (wave->wave == level->currentWave)
134     {
135       return 1;
136     }
137   }
138   return 0;
139 }
140 
141 void DrawLevelReportWonWave(Level *level)
142 {
143   BeginMode3D(level->camera);
144   DrawLevelGround(level);
145   TowerDraw();
146   EnemyDraw();
147   ProjectileDraw();
148   ParticleDraw();
149   guiState.isBlocked = 0;
150   EndMode3D();
151 
152   TowerDrawHealthBars(level->camera);
153 
154   const char *text = "Wave won";
155   int textWidth = MeasureText(text, 20);
156   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
157 
158 
159   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
160   {
161     level->nextState = LEVEL_STATE_RESET;
162   }
163 
164   if (HasLevelNextWave(level))
165   {
166     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
167     {
168       level->nextState = LEVEL_STATE_BUILDING;
169     }
170   }
171   else {
172     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
173     {
174       level->nextState = LEVEL_STATE_WON_LEVEL;
175     }
176   }
177 }
178 
179 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
180 {
181   static ButtonState buttonStates[8] = {0};
182   int cost = GetTowerCosts(towerType);
183   const char *text = TextFormat("%s: %d", name, cost);
184   buttonStates[towerType].isSelected = level->placementMode == towerType;
185   buttonStates[towerType].isDisabled = level->playerGold < cost;
186   if (Button(text, x, y, width, height, &buttonStates[towerType]))
187   {
188     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
189   }
190 }
191 
192 void DrawLevelGround(Level *level)
193 {
194   // draw checkerboard ground pattern
195   for (int x = -5; x <= 5; x += 1)
196   {
197     for (int y = -5; y <= 5; y += 1)
198     {
199       Model *model = (x + y) % 2 == 0 ? &floorTileAModel : &floorTileBModel;
200       DrawModel(*model, (Vector3){x, 0.0f, y}, 1.0f, WHITE);
201     }
202   }
203 }
204 
205 void DrawLevelBuildingState(Level *level)
206 {
207   BeginMode3D(level->camera);
208   DrawLevelGround(level);
209   TowerDraw();
210   EnemyDraw();
211   ProjectileDraw();
212   ParticleDraw();
213 
214   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
215   float planeDistance = ray.position.y / -ray.direction.y;
216   float planeX = ray.direction.x * planeDistance + ray.position.x;
217   float planeY = ray.direction.z * planeDistance + ray.position.z;
218   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
219   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
220   if (level->placementMode && !guiState.isBlocked && mapX >= -5 && mapX <= 5 && mapY >= -5 && mapY <= 5)
221   {
222     DrawCubeWires((Vector3){mapX, 0.2f, mapY}, 1.0f, 0.4f, 1.0f, RED);
223     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
224     {
225       if (TowerTryAdd(level->placementMode, mapX, mapY))
226       {
227         level->playerGold -= GetTowerCosts(level->placementMode);
228         level->placementMode = TOWER_TYPE_NONE;
229       }
230     }
231   }
232 
233   guiState.isBlocked = 0;
234 
235   EndMode3D();
236 
237   TowerDrawHealthBars(level->camera);
238 
239   static ButtonState buildWallButtonState = {0};
240   static ButtonState buildGunButtonState = {0};
241   buildWallButtonState.isSelected = level->placementMode == TOWER_TYPE_WALL;
242   buildGunButtonState.isSelected = level->placementMode == TOWER_TYPE_GUN;
243 
244   DrawBuildingBuildButton(level, 10, 10, 110, 30, TOWER_TYPE_WALL, "Wall");
245   DrawBuildingBuildButton(level, 10, 50, 110, 30, TOWER_TYPE_GUN, "Archer");
246 
247   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
248   {
249     level->nextState = LEVEL_STATE_RESET;
250   }
251   
252   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
253   {
254     level->nextState = LEVEL_STATE_BATTLE;
255   }
256 
257   const char *text = "Building phase";
258   int textWidth = MeasureText(text, 20);
259   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
260 }
261 
262 void InitBattleStateConditions(Level *level)
263 {
264   level->state = LEVEL_STATE_BATTLE;
265   level->nextState = LEVEL_STATE_NONE;
266   level->waveEndTimer = 0.0f;
267   for (int i = 0; i < 10; i++)
268   {
269     EnemyWave *wave = &level->waves[i];
270     wave->spawned = 0;
271     wave->timeToSpawnNext = wave->delay;
272   }
273 }
274 
275 void DrawLevelBattleState(Level *level)
276 {
277   BeginMode3D(level->camera);
278   DrawLevelGround(level);
279   TowerDraw();
280   EnemyDraw();
281   ProjectileDraw();
282   ParticleDraw();
283   guiState.isBlocked = 0;
284   EndMode3D();
285 
286   EnemyDrawHealthbars(level->camera);
287   TowerDrawHealthBars(level->camera);
288 
289   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
290   {
291     level->nextState = LEVEL_STATE_RESET;
292   }
293 
294   int maxCount = 0;
295   int remainingCount = 0;
296   for (int i = 0; i < 10; i++)
297   {
298     EnemyWave *wave = &level->waves[i];
299     if (wave->wave != level->currentWave)
300     {
301       continue;
302     }
303     maxCount += wave->count;
304     remainingCount += wave->count - wave->spawned;
305   }
306   int aliveCount = EnemyCount();
307   remainingCount += aliveCount;
308 
309   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
310   int textWidth = MeasureText(text, 20);
311   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
312 }
313 
314 void DrawLevel(Level *level)
315 {
316   switch (level->state)
317   {
318     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
319     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
320     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
321     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
322     default: break;
323   }
324 
325   DrawLevelHud(level);
326 }
327 
328 void UpdateLevel(Level *level)
329 {
330   if (level->state == LEVEL_STATE_BATTLE)
331   {
332     int activeWaves = 0;
333     for (int i = 0; i < 10; i++)
334     {
335       EnemyWave *wave = &level->waves[i];
336       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
337       {
338         continue;
339       }
340       activeWaves++;
341       wave->timeToSpawnNext -= gameTime.deltaTime;
342       if (wave->timeToSpawnNext <= 0.0f)
343       {
344         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
345         if (enemy)
346         {
347           wave->timeToSpawnNext = wave->interval;
348           wave->spawned++;
349         }
350       }
351     }
352     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
353       level->waveEndTimer += gameTime.deltaTime;
354       if (level->waveEndTimer >= 2.0f)
355       {
356         level->nextState = LEVEL_STATE_LOST_WAVE;
357       }
358     }
359     else if (activeWaves == 0 && EnemyCount() == 0)
360     {
361       level->waveEndTimer += gameTime.deltaTime;
362       if (level->waveEndTimer >= 2.0f)
363       {
364         level->nextState = LEVEL_STATE_WON_WAVE;
365       }
366     }
367   }
368 
369   PathFindingMapUpdate();
370   EnemyUpdate();
371   TowerUpdate();
372   ProjectileUpdate();
373   ParticleUpdate();
374 
375   if (level->nextState == LEVEL_STATE_RESET)
376   {
377     InitLevel(level);
378   }
379   
380   if (level->nextState == LEVEL_STATE_BATTLE)
381   {
382     InitBattleStateConditions(level);
383   }
384   
385   if (level->nextState == LEVEL_STATE_WON_WAVE)
386   {
387     level->currentWave++;
388     level->state = LEVEL_STATE_WON_WAVE;
389   }
390   
391   if (level->nextState == LEVEL_STATE_LOST_WAVE)
392   {
393     level->state = LEVEL_STATE_LOST_WAVE;
394   }
395 
396   if (level->nextState == LEVEL_STATE_BUILDING)
397   {
398     level->state = LEVEL_STATE_BUILDING;
399   }
400 
401   if (level->nextState == LEVEL_STATE_WON_LEVEL)
402   {
403     // make something of this later
404     InitLevel(level);
405   }
406 
407   level->nextState = LEVEL_STATE_NONE;
408 }
409 
410 float nextSpawnTime = 0.0f;
411 
412 void ResetGame()
413 {
414   InitLevel(currentLevel);
415 }
416 
417 void InitGame()
418 {
419   TowerInit();
420   EnemyInit();
421   ProjectileInit();
422   ParticleInit();
423   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
424 
425   currentLevel = levels;
426   InitLevel(currentLevel);
427 }
428 
429 //# Immediate GUI functions
430 
431 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth)
432 {
433   const float healthBarHeight = 6.0f;
434   const float healthBarOffset = 15.0f;
435   const float inset = 2.0f;
436   const float innerWidth = healthBarWidth - inset * 2;
437   const float innerHeight = healthBarHeight - inset * 2;
438 
439   Vector2 screenPos = GetWorldToScreen(position, camera);
440   float centerX = screenPos.x - healthBarWidth * 0.5f;
441   float topY = screenPos.y - healthBarOffset;
442   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
443   float healthWidth = innerWidth * healthRatio;
444   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
445 }
446 
447 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
448 {
449   Rectangle bounds = {x, y, width, height};
450   int isPressed = 0;
451   int isSelected = state && state->isSelected;
452   int isDisabled = state && state->isDisabled;
453   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !isDisabled)
454   {
455     Color color = isSelected ? DARKGRAY : GRAY;
456     DrawRectangle(x, y, width, height, color);
457     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
458     {
459       isPressed = 1;
460     }
461     guiState.isBlocked = 1;
462   }
463   else
464   {
465     Color color = isSelected ? WHITE : LIGHTGRAY;
466     DrawRectangle(x, y, width, height, color);
467   }
468   Font font = GetFontDefault();
469   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
470   Color textColor = isDisabled ? GRAY : BLACK;
471   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
472   return isPressed;
473 }
474 
475 //# Main game loop
476 
477 void GameUpdate()
478 {
479   float dt = GetFrameTime();
480   // cap maximum delta time to 0.1 seconds to prevent large time steps
481   if (dt > 0.1f) dt = 0.1f;
482   gameTime.time += dt;
483   gameTime.deltaTime = dt;
484 
485   UpdateLevel(currentLevel);
486 }
487 
488 int main(void)
489 {
490   int screenWidth, screenHeight;
491   GetPreferredSize(&screenWidth, &screenHeight);
492   InitWindow(screenWidth, screenHeight, "Tower defense");
493   SetTargetFPS(30);
494 
495   LoadAssets();
496   InitGame();
497 
498   while (!WindowShouldClose())
499   {
500     if (IsPaused()) {
501       // canvas is not visible in browser - do nothing
502       continue;
503     }
504 
505     BeginDrawing();
506     ClearBackground((Color){0x4E, 0x63, 0x26, 0xFF});
507 
508     GameUpdate();
509     DrawLevel(currentLevel);
510 
511     EndDrawing();
512   }
513 
514   CloseWindow();
515 
516   return 0;
517 }
  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 //# Level
249 void DrawLevelGround(Level *level);
250 
251 //# variables
252 extern Level *currentLevel;
253 extern Enemy enemies[ENEMY_MAX_COUNT];
254 extern int enemyCount;
255 extern EnemyClassConfig enemyClassConfigs[];
256 
257 extern GUIState guiState;
258 extern GameTime gameTime;
259 extern Tower towers[TOWER_MAX_COUNT];
260 extern int towerCount;
261 
262 extern Texture2D palette, spriteSheet;
263 
264 #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, 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 "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 
  9 // definition of our archer unit
 10 SpriteUnit archerUnit = {
 11     .srcRect = {0, 0, 16, 16},
 12     .offset = {7, 1},
 13     .frameCount = 1,
 14     .frameDuration = 0.0f,
 15     .srcWeaponIdleRect = {16, 0, 6, 16},
 16     .srcWeaponIdleOffset = {8, 0},
 17     .srcWeaponCooldownRect = {22, 0, 11, 16},
 18     .srcWeaponCooldownOffset = {10, 0},
 19 };
 20 
 21 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
 22 {
 23   float xScale = flip ? -1.0f : 1.0f;
 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 * xScale };
 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   if (flip)
 40   {
 41     srcRect.x += srcRect.width;
 42     srcRect.width = -srcRect.width;
 43   }
 44   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 45 
 46   if (phase == SPRITE_UNIT_PHASE_WEAPON_COOLDOWN && unit.srcWeaponCooldownRect.width > 0)
 47   {
 48     offset = (Vector2){ unit.srcWeaponCooldownOffset.x / 16.0f * size, unit.srcWeaponCooldownOffset.y / 16.0f * size };
 49     scale = (Vector2){ unit.srcWeaponCooldownRect.width / 16.0f * size, unit.srcWeaponCooldownRect.height / 16.0f * size };
 50     srcRect = unit.srcWeaponCooldownRect;
 51     if (flip)
 52     {
 53       // position.x = flip * scale.x * 0.5f;
 54       srcRect.x += srcRect.width;
 55       srcRect.width = -srcRect.width;
 56       offset.x = scale.x - offset.x;
 57     }
 58     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 59   }
 60   else if (phase == SPRITE_UNIT_PHASE_WEAPON_IDLE && unit.srcWeaponIdleRect.width > 0)
 61   {
 62     offset = (Vector2){ unit.srcWeaponIdleOffset.x / 16.0f * size, unit.srcWeaponIdleOffset.y / 16.0f * size };
 63     scale = (Vector2){ unit.srcWeaponIdleRect.width / 16.0f * size, unit.srcWeaponIdleRect.height / 16.0f * size };
 64     srcRect = unit.srcWeaponIdleRect;
 65     if (flip)
 66     {
 67       // position.x = flip * scale.x * 0.5f;
 68       srcRect.x += srcRect.width;
 69       srcRect.width = -srcRect.width;
 70       offset.x = scale.x - offset.x;
 71     }
 72     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 73   }
 74 }
 75 
 76 void TowerInit()
 77 {
 78   for (int i = 0; i < TOWER_MAX_COUNT; i++)
 79   {
 80     towers[i] = (Tower){0};
 81   }
 82   towerCount = 0;
 83 
 84   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
 85   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
 86 
 87   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
 88   {
 89     if (towerModels[i].materials)
 90     {
 91       // assign the palette texture to the material of the model (0 is not used afaik)
 92       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 93     }
 94   }
 95 }
 96 
 97 static void TowerGunUpdate(Tower *tower)
 98 {
 99   if (tower->cooldown <= 0)
100   {
101     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
102     if (enemy)
103     {
104       tower->cooldown = 0.5f;
105       // shoot the enemy; determine future position of the enemy
106       float bulletSpeed = 4.0f;
107       float bulletDamage = 3.0f;
108       Vector2 velocity = enemy->simVelocity;
109       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
110       Vector2 towerPosition = {tower->x, tower->y};
111       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
112       for (int i = 0; i < 8; i++) {
113         velocity = enemy->simVelocity;
114         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
115         float distance = Vector2Distance(towerPosition, futurePosition);
116         float eta2 = distance / bulletSpeed;
117         if (fabs(eta - eta2) < 0.01f) {
118           break;
119         }
120         eta = (eta2 + eta) * 0.5f;
121       }
122       ProjectileTryAdd(PROJECTILE_TYPE_ARROW, enemy, 
123         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
124         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
125         bulletSpeed, bulletDamage);
126       enemy->futureDamage += bulletDamage;
127       tower->lastTargetPosition = futurePosition;
128     }
129   }
130   else
131   {
132     tower->cooldown -= gameTime.deltaTime;
133   }
134 }
135 
136 Tower *TowerGetAt(int16_t x, int16_t y)
137 {
138   for (int i = 0; i < towerCount; i++)
139   {
140     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
141     {
142       return &towers[i];
143     }
144   }
145   return 0;
146 }
147 
148 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
149 {
150   if (towerCount >= TOWER_MAX_COUNT)
151   {
152     return 0;
153   }
154 
155   Tower *tower = TowerGetAt(x, y);
156   if (tower)
157   {
158     return 0;
159   }
160 
161   tower = &towers[towerCount++];
162   tower->x = x;
163   tower->y = y;
164   tower->towerType = towerType;
165   tower->cooldown = 0.0f;
166   tower->damage = 0.0f;
167   return tower;
168 }
169 
170 Tower *GetTowerByType(uint8_t towerType)
171 {
172   for (int i = 0; i < towerCount; i++)
173   {
174     if (towers[i].towerType == towerType)
175     {
176       return &towers[i];
177     }
178   }
179   return 0;
180 }
181 
182 int GetTowerCosts(uint8_t towerType)
183 {
184   switch (towerType)
185   {
186   case TOWER_TYPE_BASE:
187     return 0;
188   case TOWER_TYPE_GUN:
189     return 6;
190   case TOWER_TYPE_WALL:
191     return 2;
192   }
193   return 0;
194 }
195 
196 float TowerGetMaxHealth(Tower *tower)
197 {
198   switch (tower->towerType)
199   {
200   case TOWER_TYPE_BASE:
201     return 10.0f;
202   case TOWER_TYPE_GUN:
203     return 3.0f;
204   case TOWER_TYPE_WALL:
205     return 5.0f;
206   }
207   return 0.0f;
208 }
209 
210 void TowerDraw()
211 {
212   for (int i = 0; i < towerCount; i++)
213   {
214     Tower tower = towers[i];
215     if (tower.towerType == TOWER_TYPE_NONE)
216     {
217       continue;
218     }
219 
220     switch (tower.towerType)
221     {
222     case TOWER_TYPE_GUN:
223       {
224         Vector2 screenPosTower = GetWorldToScreen((Vector3){tower.x, 0.0f, tower.y}, currentLevel->camera);
225         Vector2 screenPosTarget = GetWorldToScreen((Vector3){tower.lastTargetPosition.x, 0.0f, tower.lastTargetPosition.y}, currentLevel->camera);
226         DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
227         DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0, screenPosTarget.x > screenPosTower.x, 
228           tower.cooldown > 0.2f ? SPRITE_UNIT_PHASE_WEAPON_COOLDOWN : SPRITE_UNIT_PHASE_WEAPON_IDLE);
229       }
230       break;
231     default:
232       if (towerModels[tower.towerType].materials)
233       {
234         DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
235       } else {
236         DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
237       }
238       break;
239     }
240   }
241 }
242 
243 void TowerUpdate()
244 {
245   for (int i = 0; i < towerCount; i++)
246   {
247     Tower *tower = &towers[i];
248     switch (tower->towerType)
249     {
250     case TOWER_TYPE_GUN:
251       TowerGunUpdate(tower);
252       break;
253     }
254   }
255 }
256 
257 void TowerDrawHealthBars(Camera3D camera)
258 {
259   for (int i = 0; i < towerCount; i++)
260   {
261     Tower *tower = &towers[i];
262     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
263     {
264       continue;
265     }
266     
267     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
268     float maxHealth = TowerGetMaxHealth(tower);
269     float health = maxHealth - tower->damage;
270     float healthRatio = health / maxHealth;
271     
272     DrawHealthBar(camera, position, healthRatio, GREEN, 35.0f);
273   }
274 }
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif
The function is quite simple and the level size is hardcoded to be 11x11 (from -5 to 5 on the x and z axis). The function loops over all tiles and draws the tile depending on the sum of x and y. If the sum is even, it draws the first tile, otherwise the second tile:
  1 for (int x = -5; x <= 5; x += 1)
  2 {
  3   for (int y = -5; y <= 5; y += 1)
  4   {
  5     Model *model = (x + y) % 2 == 0 ? &floorTileAModel : &floorTileBModel;
  6     DrawModel(*model, (Vector3){x, 0.0f, y}, 1.0f, WHITE);
  7   }
  8 }
The function to load the GLB models and assigning the palette texture simply assigns the texture to all materials in the model and returns the model:
  1 static Model LoadGLBModel(char *filename)
  2 {
  3   Model model = LoadModel(TextFormat("data/%s.glb",filename));
  4   if (model.materialCount > 1)
  5   {
  6     model.materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
  7   }
  8   return model;
  9 }
The LoadAssets function is now taking care of loading the texture and floor tiles. We could later also load the tower and enemy models here - or let the tower init function load the models through the LoadGLBModel function. It isn't a bad idea that the modules take care of their own assets, but for bigger projects where asset management becomes more complex, it's better to have dedicated asset management functions to avoid loading the same asset multiple times or unloading assets that are still in use. Thankfully, our game is unlikely to grow that big.
For now, let's focus on setting up the environment decorations, so let's ignore that step here:
  1 void LoadAssets()
  2 {
  3   // load a sprite sheet that contains all units
  4   spriteSheet = LoadTexture("data/spritesheet.png");
  5   SetTextureFilter(spriteSheet, TEXTURE_FILTER_BILINEAR);
  6 
  7   // we'll use a palette texture to colorize the all buildings and environment art
  8   palette = LoadTexture("data/palette.png");
  9   // The texture uses gradients on very small space, so we'll enable bilinear filtering
 10   SetTextureFilter(palette, TEXTURE_FILTER_BILINEAR);
 11 
 12   floorTileAModel = LoadGLBModel("floor-tile-a");
 13   floorTileBModel = LoadGLBModel("floor-tile-b");
 14 }
By clearing the background to a dark green color, it looks like the game map is placed on a grass field. Quite more appealing than the blue background we had!
But let's add some more decorations outside the map to make it look more natural, just for fun! We have the assets, so let's use them.
Decoration objects
The idea is to place now some decorative objects randomly around the map.
  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 Model floorTileAModel = {0};
 11 Model floorTileBModel = {0};
 12 Model grassPatchModel[1] = {0};
 13 
 14 Texture2D palette, spriteSheet;
 15 
 16 Level levels[] = {
 17   [0] = {
 18     .state = LEVEL_STATE_BUILDING,
 19     .initialGold = 20,
 20     .waves[0] = {
 21       .enemyType = ENEMY_TYPE_MINION,
 22       .wave = 0,
 23       .count = 10,
 24       .interval = 2.5f,
 25       .delay = 1.0f,
 26       .spawnPosition = {0, 6},
 27     },
 28     .waves[1] = {
 29       .enemyType = ENEMY_TYPE_MINION,
 30       .wave = 1,
 31       .count = 20,
 32       .interval = 1.5f,
 33       .delay = 1.0f,
 34       .spawnPosition = {0, 6},
 35     },
 36     .waves[2] = {
 37       .enemyType = ENEMY_TYPE_MINION,
 38       .wave = 2,
 39       .count = 30,
 40       .interval = 1.2f,
 41       .delay = 1.0f,
 42       .spawnPosition = {0, 6},
 43     }
 44   },
 45 };
 46 
 47 Level *currentLevel = levels;
 48 
 49 //# Game
 50 
 51 static Model LoadGLBModel(char *filename)
 52 {
 53   Model model = LoadModel(TextFormat("data/%s.glb",filename));
 54   if (model.materialCount > 1)
 55   {
 56     model.materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 57   }
 58   return model;
 59 }
 60 
 61 void LoadAssets()
 62 {
 63   // load a sprite sheet that contains all units
 64   spriteSheet = LoadTexture("data/spritesheet.png");
 65   SetTextureFilter(spriteSheet, TEXTURE_FILTER_BILINEAR);
 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   floorTileAModel = LoadGLBModel("floor-tile-a");
 73   floorTileBModel = LoadGLBModel("floor-tile-b");
 74   grassPatchModel[0] = LoadGLBModel("grass-patch-1");
 75 }
 76 
 77 void InitLevel(Level *level)
 78 {
 79   TowerInit();
 80   EnemyInit();
 81   ProjectileInit();
 82   ParticleInit();
 83   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
 84 
 85   level->placementMode = 0;
 86   level->state = LEVEL_STATE_BUILDING;
 87   level->nextState = LEVEL_STATE_NONE;
 88   level->playerGold = level->initialGold;
 89   level->currentWave = 0;
 90 
 91   Camera *camera = &level->camera;
 92   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
 93   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
 94   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
 95   camera->fovy = 10.0f;
 96   camera->projection = CAMERA_ORTHOGRAPHIC;
 97 }
 98 
 99 void DrawLevelHud(Level *level)
100 {
101   const char *text = TextFormat("Gold: %d", level->playerGold);
102   Font font = GetFontDefault();
103   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
104   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
105 }
106 
107 void DrawLevelReportLostWave(Level *level)
108 {
109   BeginMode3D(level->camera);
110   DrawLevelGround(level);
111   TowerDraw();
112   EnemyDraw();
113   ProjectileDraw();
114   ParticleDraw();
115   guiState.isBlocked = 0;
116   EndMode3D();
117 
118   TowerDrawHealthBars(level->camera);
119 
120   const char *text = "Wave lost";
121   int textWidth = MeasureText(text, 20);
122   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
123 
124   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
125   {
126     level->nextState = LEVEL_STATE_RESET;
127   }
128 }
129 
130 int HasLevelNextWave(Level *level)
131 {
132   for (int i = 0; i < 10; i++)
133   {
134     EnemyWave *wave = &level->waves[i];
135     if (wave->wave == level->currentWave)
136     {
137       return 1;
138     }
139   }
140   return 0;
141 }
142 
143 void DrawLevelReportWonWave(Level *level)
144 {
145   BeginMode3D(level->camera);
146   DrawLevelGround(level);
147   TowerDraw();
148   EnemyDraw();
149   ProjectileDraw();
150   ParticleDraw();
151   guiState.isBlocked = 0;
152   EndMode3D();
153 
154   TowerDrawHealthBars(level->camera);
155 
156   const char *text = "Wave won";
157   int textWidth = MeasureText(text, 20);
158   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
159 
160 
161   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
162   {
163     level->nextState = LEVEL_STATE_RESET;
164   }
165 
166   if (HasLevelNextWave(level))
167   {
168     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
169     {
170       level->nextState = LEVEL_STATE_BUILDING;
171     }
172   }
173   else {
174     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
175     {
176       level->nextState = LEVEL_STATE_WON_LEVEL;
177     }
178   }
179 }
180 
181 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
182 {
183   static ButtonState buttonStates[8] = {0};
184   int cost = GetTowerCosts(towerType);
185   const char *text = TextFormat("%s: %d", name, cost);
186   buttonStates[towerType].isSelected = level->placementMode == towerType;
187   buttonStates[towerType].isDisabled = level->playerGold < cost;
188   if (Button(text, x, y, width, height, &buttonStates[towerType]))
189   {
190     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
191   }
192 }
193 
194 void DrawLevelGround(Level *level)
195 {
196   // draw checkerboard ground pattern
197   for (int x = -5; x <= 5; x += 1)
198   {
199     for (int y = -5; y <= 5; y += 1)
200     {
201       Model *model = (x + y) % 2 == 0 ? &floorTileAModel : &floorTileBModel;
202       DrawModel(*model, (Vector3){x, 0.0f, y}, 1.0f, WHITE);
203     }
204   }
205 
206   // draw grass patches around the edges
207   const int layerCount = 2;
208   for (int layer = 0; layer < layerCount; layer++)
209   {
210     int layerPos = 6 + layer;
211     for (int x = -6 + layer; x <= 6 + layer; x += 1)
212     {
213       DrawModel(grassPatchModel[0], 
214         (Vector3){x, 0.0f, -layerPos}, 
215         1.0f, WHITE);
216       DrawModel(grassPatchModel[0], 
217         (Vector3){x, 0.0f, layerPos}, 
218         1.0f, WHITE);
219     }
220 
221     for (int z = -5 + layer; z <= 5 + layer; z += 1)
222     {
223       DrawModel(grassPatchModel[0], 
224         (Vector3){-layerPos, 0.0f, z}, 
225         1.0f, WHITE);
226       DrawModel(grassPatchModel[0], 
227         (Vector3){layerPos, 0.0f, z}, 
228         1.0f, WHITE);
229     }
230   }
231 }
232 
233 void DrawLevelBuildingState(Level *level)
234 {
235   BeginMode3D(level->camera);
236   DrawLevelGround(level);
237   TowerDraw();
238   EnemyDraw();
239   ProjectileDraw();
240   ParticleDraw();
241 
242   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
243   float planeDistance = ray.position.y / -ray.direction.y;
244   float planeX = ray.direction.x * planeDistance + ray.position.x;
245   float planeY = ray.direction.z * planeDistance + ray.position.z;
246   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
247   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
248   if (level->placementMode && !guiState.isBlocked && mapX >= -5 && mapX <= 5 && mapY >= -5 && mapY <= 5)
249   {
250     DrawCubeWires((Vector3){mapX, 0.2f, mapY}, 1.0f, 0.4f, 1.0f, RED);
251     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
252     {
253       if (TowerTryAdd(level->placementMode, mapX, mapY))
254       {
255         level->playerGold -= GetTowerCosts(level->placementMode);
256         level->placementMode = TOWER_TYPE_NONE;
257       }
258     }
259   }
260 
261   guiState.isBlocked = 0;
262 
263   EndMode3D();
264 
265   TowerDrawHealthBars(level->camera);
266 
267   static ButtonState buildWallButtonState = {0};
268   static ButtonState buildGunButtonState = {0};
269   buildWallButtonState.isSelected = level->placementMode == TOWER_TYPE_WALL;
270   buildGunButtonState.isSelected = level->placementMode == TOWER_TYPE_GUN;
271 
272   DrawBuildingBuildButton(level, 10, 10, 110, 30, TOWER_TYPE_WALL, "Wall");
273   DrawBuildingBuildButton(level, 10, 50, 110, 30, TOWER_TYPE_GUN, "Archer");
274 
275   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
276   {
277     level->nextState = LEVEL_STATE_RESET;
278   }
279   
280   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
281   {
282     level->nextState = LEVEL_STATE_BATTLE;
283   }
284 
285   const char *text = "Building phase";
286   int textWidth = MeasureText(text, 20);
287   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
288 }
289 
290 void InitBattleStateConditions(Level *level)
291 {
292   level->state = LEVEL_STATE_BATTLE;
293   level->nextState = LEVEL_STATE_NONE;
294   level->waveEndTimer = 0.0f;
295   for (int i = 0; i < 10; i++)
296   {
297     EnemyWave *wave = &level->waves[i];
298     wave->spawned = 0;
299     wave->timeToSpawnNext = wave->delay;
300   }
301 }
302 
303 void DrawLevelBattleState(Level *level)
304 {
305   BeginMode3D(level->camera);
306   DrawLevelGround(level);
307   TowerDraw();
308   EnemyDraw();
309   ProjectileDraw();
310   ParticleDraw();
311   guiState.isBlocked = 0;
312   EndMode3D();
313 
314   EnemyDrawHealthbars(level->camera);
315   TowerDrawHealthBars(level->camera);
316 
317   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
318   {
319     level->nextState = LEVEL_STATE_RESET;
320   }
321 
322   int maxCount = 0;
323   int remainingCount = 0;
324   for (int i = 0; i < 10; i++)
325   {
326     EnemyWave *wave = &level->waves[i];
327     if (wave->wave != level->currentWave)
328     {
329       continue;
330     }
331     maxCount += wave->count;
332     remainingCount += wave->count - wave->spawned;
333   }
334   int aliveCount = EnemyCount();
335   remainingCount += aliveCount;
336 
337   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
338   int textWidth = MeasureText(text, 20);
339   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
340 }
341 
342 void DrawLevel(Level *level)
343 {
344   switch (level->state)
345   {
346     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
347     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
348     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
349     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
350     default: break;
351   }
352 
353   DrawLevelHud(level);
354 }
355 
356 void UpdateLevel(Level *level)
357 {
358   if (level->state == LEVEL_STATE_BATTLE)
359   {
360     int activeWaves = 0;
361     for (int i = 0; i < 10; i++)
362     {
363       EnemyWave *wave = &level->waves[i];
364       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
365       {
366         continue;
367       }
368       activeWaves++;
369       wave->timeToSpawnNext -= gameTime.deltaTime;
370       if (wave->timeToSpawnNext <= 0.0f)
371       {
372         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
373         if (enemy)
374         {
375           wave->timeToSpawnNext = wave->interval;
376           wave->spawned++;
377         }
378       }
379     }
380     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
381       level->waveEndTimer += gameTime.deltaTime;
382       if (level->waveEndTimer >= 2.0f)
383       {
384         level->nextState = LEVEL_STATE_LOST_WAVE;
385       }
386     }
387     else if (activeWaves == 0 && EnemyCount() == 0)
388     {
389       level->waveEndTimer += gameTime.deltaTime;
390       if (level->waveEndTimer >= 2.0f)
391       {
392         level->nextState = LEVEL_STATE_WON_WAVE;
393       }
394     }
395   }
396 
397   PathFindingMapUpdate();
398   EnemyUpdate();
399   TowerUpdate();
400   ProjectileUpdate();
401   ParticleUpdate();
402 
403   if (level->nextState == LEVEL_STATE_RESET)
404   {
405     InitLevel(level);
406   }
407   
408   if (level->nextState == LEVEL_STATE_BATTLE)
409   {
410     InitBattleStateConditions(level);
411   }
412   
413   if (level->nextState == LEVEL_STATE_WON_WAVE)
414   {
415     level->currentWave++;
416     level->state = LEVEL_STATE_WON_WAVE;
417   }
418   
419   if (level->nextState == LEVEL_STATE_LOST_WAVE)
420   {
421     level->state = LEVEL_STATE_LOST_WAVE;
422   }
423 
424   if (level->nextState == LEVEL_STATE_BUILDING)
425   {
426     level->state = LEVEL_STATE_BUILDING;
427   }
428 
429   if (level->nextState == LEVEL_STATE_WON_LEVEL)
430   {
431     // make something of this later
432     InitLevel(level);
433   }
434 
435   level->nextState = LEVEL_STATE_NONE;
436 }
437 
438 float nextSpawnTime = 0.0f;
439 
440 void ResetGame()
441 {
442   InitLevel(currentLevel);
443 }
444 
445 void InitGame()
446 {
447   TowerInit();
448   EnemyInit();
449   ProjectileInit();
450   ParticleInit();
451   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
452 
453   currentLevel = levels;
454   InitLevel(currentLevel);
455 }
456 
457 //# Immediate GUI functions
458 
459 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth)
460 {
461   const float healthBarHeight = 6.0f;
462   const float healthBarOffset = 15.0f;
463   const float inset = 2.0f;
464   const float innerWidth = healthBarWidth - inset * 2;
465   const float innerHeight = healthBarHeight - inset * 2;
466 
467   Vector2 screenPos = GetWorldToScreen(position, camera);
468   float centerX = screenPos.x - healthBarWidth * 0.5f;
469   float topY = screenPos.y - healthBarOffset;
470   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
471   float healthWidth = innerWidth * healthRatio;
472   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
473 }
474 
475 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
476 {
477   Rectangle bounds = {x, y, width, height};
478   int isPressed = 0;
479   int isSelected = state && state->isSelected;
480   int isDisabled = state && state->isDisabled;
481   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !isDisabled)
482   {
483     Color color = isSelected ? DARKGRAY : GRAY;
484     DrawRectangle(x, y, width, height, color);
485     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
486     {
487       isPressed = 1;
488     }
489     guiState.isBlocked = 1;
490   }
491   else
492   {
493     Color color = isSelected ? WHITE : LIGHTGRAY;
494     DrawRectangle(x, y, width, height, color);
495   }
496   Font font = GetFontDefault();
497   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
498   Color textColor = isDisabled ? GRAY : BLACK;
499   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
500   return isPressed;
501 }
502 
503 //# Main game loop
504 
505 void GameUpdate()
506 {
507   float dt = GetFrameTime();
508   // cap maximum delta time to 0.1 seconds to prevent large time steps
509   if (dt > 0.1f) dt = 0.1f;
510   gameTime.time += dt;
511   gameTime.deltaTime = dt;
512 
513   UpdateLevel(currentLevel);
514 }
515 
516 int main(void)
517 {
518   int screenWidth, screenHeight;
519   GetPreferredSize(&screenWidth, &screenHeight);
520   InitWindow(screenWidth, screenHeight, "Tower defense");
521   SetTargetFPS(30);
522 
523   LoadAssets();
524   InitGame();
525 
526   while (!WindowShouldClose())
527   {
528     if (IsPaused()) {
529       // canvas is not visible in browser - do nothing
530       continue;
531     }
532 
533     BeginDrawing();
534     ClearBackground((Color){0x4E, 0x63, 0x26, 0xFF});
535 
536     GameUpdate();
537     DrawLevel(currentLevel);
538 
539     EndDrawing();
540   }
541 
542   CloseWindow();
543 
544   return 0;
545 }
  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 //# Level
249 void DrawLevelGround(Level *level);
250 
251 //# variables
252 extern Level *currentLevel;
253 extern Enemy enemies[ENEMY_MAX_COUNT];
254 extern int enemyCount;
255 extern EnemyClassConfig enemyClassConfigs[];
256 
257 extern GUIState guiState;
258 extern GameTime gameTime;
259 extern Tower towers[TOWER_MAX_COUNT];
260 extern int towerCount;
261 
262 extern Texture2D palette, spriteSheet;
263 
264 #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, 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 "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 
  9 // definition of our archer unit
 10 SpriteUnit archerUnit = {
 11     .srcRect = {0, 0, 16, 16},
 12     .offset = {7, 1},
 13     .frameCount = 1,
 14     .frameDuration = 0.0f,
 15     .srcWeaponIdleRect = {16, 0, 6, 16},
 16     .srcWeaponIdleOffset = {8, 0},
 17     .srcWeaponCooldownRect = {22, 0, 11, 16},
 18     .srcWeaponCooldownOffset = {10, 0},
 19 };
 20 
 21 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
 22 {
 23   float xScale = flip ? -1.0f : 1.0f;
 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 * xScale };
 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   if (flip)
 40   {
 41     srcRect.x += srcRect.width;
 42     srcRect.width = -srcRect.width;
 43   }
 44   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 45 
 46   if (phase == SPRITE_UNIT_PHASE_WEAPON_COOLDOWN && unit.srcWeaponCooldownRect.width > 0)
 47   {
 48     offset = (Vector2){ unit.srcWeaponCooldownOffset.x / 16.0f * size, unit.srcWeaponCooldownOffset.y / 16.0f * size };
 49     scale = (Vector2){ unit.srcWeaponCooldownRect.width / 16.0f * size, unit.srcWeaponCooldownRect.height / 16.0f * size };
 50     srcRect = unit.srcWeaponCooldownRect;
 51     if (flip)
 52     {
 53       // position.x = flip * scale.x * 0.5f;
 54       srcRect.x += srcRect.width;
 55       srcRect.width = -srcRect.width;
 56       offset.x = scale.x - offset.x;
 57     }
 58     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 59   }
 60   else if (phase == SPRITE_UNIT_PHASE_WEAPON_IDLE && unit.srcWeaponIdleRect.width > 0)
 61   {
 62     offset = (Vector2){ unit.srcWeaponIdleOffset.x / 16.0f * size, unit.srcWeaponIdleOffset.y / 16.0f * size };
 63     scale = (Vector2){ unit.srcWeaponIdleRect.width / 16.0f * size, unit.srcWeaponIdleRect.height / 16.0f * size };
 64     srcRect = unit.srcWeaponIdleRect;
 65     if (flip)
 66     {
 67       // position.x = flip * scale.x * 0.5f;
 68       srcRect.x += srcRect.width;
 69       srcRect.width = -srcRect.width;
 70       offset.x = scale.x - offset.x;
 71     }
 72     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 73   }
 74 }
 75 
 76 void TowerInit()
 77 {
 78   for (int i = 0; i < TOWER_MAX_COUNT; i++)
 79   {
 80     towers[i] = (Tower){0};
 81   }
 82   towerCount = 0;
 83 
 84   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
 85   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
 86 
 87   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
 88   {
 89     if (towerModels[i].materials)
 90     {
 91       // assign the palette texture to the material of the model (0 is not used afaik)
 92       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 93     }
 94   }
 95 }
 96 
 97 static void TowerGunUpdate(Tower *tower)
 98 {
 99   if (tower->cooldown <= 0)
100   {
101     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
102     if (enemy)
103     {
104       tower->cooldown = 0.5f;
105       // shoot the enemy; determine future position of the enemy
106       float bulletSpeed = 4.0f;
107       float bulletDamage = 3.0f;
108       Vector2 velocity = enemy->simVelocity;
109       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
110       Vector2 towerPosition = {tower->x, tower->y};
111       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
112       for (int i = 0; i < 8; i++) {
113         velocity = enemy->simVelocity;
114         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
115         float distance = Vector2Distance(towerPosition, futurePosition);
116         float eta2 = distance / bulletSpeed;
117         if (fabs(eta - eta2) < 0.01f) {
118           break;
119         }
120         eta = (eta2 + eta) * 0.5f;
121       }
122       ProjectileTryAdd(PROJECTILE_TYPE_ARROW, enemy, 
123         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
124         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
125         bulletSpeed, bulletDamage);
126       enemy->futureDamage += bulletDamage;
127       tower->lastTargetPosition = futurePosition;
128     }
129   }
130   else
131   {
132     tower->cooldown -= gameTime.deltaTime;
133   }
134 }
135 
136 Tower *TowerGetAt(int16_t x, int16_t y)
137 {
138   for (int i = 0; i < towerCount; i++)
139   {
140     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
141     {
142       return &towers[i];
143     }
144   }
145   return 0;
146 }
147 
148 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
149 {
150   if (towerCount >= TOWER_MAX_COUNT)
151   {
152     return 0;
153   }
154 
155   Tower *tower = TowerGetAt(x, y);
156   if (tower)
157   {
158     return 0;
159   }
160 
161   tower = &towers[towerCount++];
162   tower->x = x;
163   tower->y = y;
164   tower->towerType = towerType;
165   tower->cooldown = 0.0f;
166   tower->damage = 0.0f;
167   return tower;
168 }
169 
170 Tower *GetTowerByType(uint8_t towerType)
171 {
172   for (int i = 0; i < towerCount; i++)
173   {
174     if (towers[i].towerType == towerType)
175     {
176       return &towers[i];
177     }
178   }
179   return 0;
180 }
181 
182 int GetTowerCosts(uint8_t towerType)
183 {
184   switch (towerType)
185   {
186   case TOWER_TYPE_BASE:
187     return 0;
188   case TOWER_TYPE_GUN:
189     return 6;
190   case TOWER_TYPE_WALL:
191     return 2;
192   }
193   return 0;
194 }
195 
196 float TowerGetMaxHealth(Tower *tower)
197 {
198   switch (tower->towerType)
199   {
200   case TOWER_TYPE_BASE:
201     return 10.0f;
202   case TOWER_TYPE_GUN:
203     return 3.0f;
204   case TOWER_TYPE_WALL:
205     return 5.0f;
206   }
207   return 0.0f;
208 }
209 
210 void TowerDraw()
211 {
212   for (int i = 0; i < towerCount; i++)
213   {
214     Tower tower = towers[i];
215     if (tower.towerType == TOWER_TYPE_NONE)
216     {
217       continue;
218     }
219 
220     switch (tower.towerType)
221     {
222     case TOWER_TYPE_GUN:
223       {
224         Vector2 screenPosTower = GetWorldToScreen((Vector3){tower.x, 0.0f, tower.y}, currentLevel->camera);
225         Vector2 screenPosTarget = GetWorldToScreen((Vector3){tower.lastTargetPosition.x, 0.0f, tower.lastTargetPosition.y}, currentLevel->camera);
226         DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
227         DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0, screenPosTarget.x > screenPosTower.x, 
228           tower.cooldown > 0.2f ? SPRITE_UNIT_PHASE_WEAPON_COOLDOWN : SPRITE_UNIT_PHASE_WEAPON_IDLE);
229       }
230       break;
231     default:
232       if (towerModels[tower.towerType].materials)
233       {
234         DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
235       } else {
236         DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
237       }
238       break;
239     }
240   }
241 }
242 
243 void TowerUpdate()
244 {
245   for (int i = 0; i < towerCount; i++)
246   {
247     Tower *tower = &towers[i];
248     switch (tower->towerType)
249     {
250     case TOWER_TYPE_GUN:
251       TowerGunUpdate(tower);
252       break;
253     }
254   }
255 }
256 
257 void TowerDrawHealthBars(Camera3D camera)
258 {
259   for (int i = 0; i < towerCount; i++)
260   {
261     Tower *tower = &towers[i];
262     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
263     {
264       continue;
265     }
266     
267     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
268     float maxHealth = TowerGetMaxHealth(tower);
269     float health = maxHealth - tower->damage;
270     float healthRatio = health / maxHealth;
271     
272     DrawHealthBar(camera, position, healthRatio, GREEN, 35.0f);
273   }
274 }
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif
The first change we do is to load an additional model and draw it around the map. The placement of the objects uses two nested loops constructions:
  1   const int layerCount = 2;
  2   for (int layer = 0; layer < layerCount; layer++)
  3   {
  4     int layerPos = 6 + layer;
  5     for (int x = -6 + layer; x <= 6 + layer; x += 1)
  6     {
  7       DrawModel(grassPatchModel[0], 
  8         (Vector3){x, 0.0f, -layerPos}, 
  9         1.0f, WHITE);
 10       DrawModel(grassPatchModel[0], 
 11         (Vector3){x, 0.0f, layerPos}, 
 12         1.0f, WHITE);
 13     }
 14 
 15     for (int z = -5 + layer; z <= 5 + layer; z += 1)
 16     {
 17       DrawModel(grassPatchModel[0], 
 18         (Vector3){-layerPos, 0.0f, z}, 
 19         1.0f, WHITE);
 20       DrawModel(grassPatchModel[0], 
 21         (Vector3){layerPos, 0.0f, z}, 
 22         1.0f, WHITE);
 23     }
 24   }
The layerCount variable determines how many layers of objects are placed around the map. The layerPos variable is the distance from the center of the map - either in x or z direction (since our map is a square, one variable can be used for both axes).
The purpose of the two inner loops is to place objects along the edges of the x and z axis. The first loop places the models along the x axis on the left and right side of the map. The second loop places the models along the z axis on the top and bottom side of the map.
However, the objects are placed in a very strict grid. It would be better to place them in some random fashion to make it look more natural. Let's do that next and use a random value to offset the position of the objects a little bit.
  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 Model floorTileAModel = {0};
 11 Model floorTileBModel = {0};
 12 Model grassPatchModel[1] = {0};
 13 
 14 Texture2D palette, spriteSheet;
 15 
 16 Level levels[] = {
 17   [0] = {
 18     .state = LEVEL_STATE_BUILDING,
 19     .initialGold = 20,
 20     .waves[0] = {
 21       .enemyType = ENEMY_TYPE_MINION,
 22       .wave = 0,
 23       .count = 10,
 24       .interval = 2.5f,
 25       .delay = 1.0f,
 26       .spawnPosition = {0, 6},
 27     },
 28     .waves[1] = {
 29       .enemyType = ENEMY_TYPE_MINION,
 30       .wave = 1,
 31       .count = 20,
 32       .interval = 1.5f,
 33       .delay = 1.0f,
 34       .spawnPosition = {0, 6},
 35     },
 36     .waves[2] = {
 37       .enemyType = ENEMY_TYPE_MINION,
 38       .wave = 2,
 39       .count = 30,
 40       .interval = 1.2f,
 41       .delay = 1.0f,
 42       .spawnPosition = {0, 6},
 43     }
 44   },
 45 };
 46 
 47 Level *currentLevel = levels;
 48 
 49 //# Game
 50 
 51 static Model LoadGLBModel(char *filename)
 52 {
 53   Model model = LoadModel(TextFormat("data/%s.glb",filename));
 54   if (model.materialCount > 1)
 55   {
 56     model.materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 57   }
 58   return model;
 59 }
 60 
 61 void LoadAssets()
 62 {
 63   // load a sprite sheet that contains all units
 64   spriteSheet = LoadTexture("data/spritesheet.png");
 65   SetTextureFilter(spriteSheet, TEXTURE_FILTER_BILINEAR);
 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   floorTileAModel = LoadGLBModel("floor-tile-a");
 73   floorTileBModel = LoadGLBModel("floor-tile-b");
 74   grassPatchModel[0] = LoadGLBModel("grass-patch-1");
 75 }
 76 
 77 void InitLevel(Level *level)
 78 {
 79   TowerInit();
 80   EnemyInit();
 81   ProjectileInit();
 82   ParticleInit();
 83   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
 84 
 85   level->placementMode = 0;
 86   level->state = LEVEL_STATE_BUILDING;
 87   level->nextState = LEVEL_STATE_NONE;
 88   level->playerGold = level->initialGold;
 89   level->currentWave = 0;
 90 
 91   Camera *camera = &level->camera;
 92   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
 93   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
 94   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
 95   camera->fovy = 10.0f;
 96   camera->projection = CAMERA_ORTHOGRAPHIC;
 97 }
 98 
 99 void DrawLevelHud(Level *level)
100 {
101   const char *text = TextFormat("Gold: %d", level->playerGold);
102   Font font = GetFontDefault();
103   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
104   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
105 }
106 
107 void DrawLevelReportLostWave(Level *level)
108 {
109   BeginMode3D(level->camera);
110   DrawLevelGround(level);
111   TowerDraw();
112   EnemyDraw();
113   ProjectileDraw();
114   ParticleDraw();
115   guiState.isBlocked = 0;
116   EndMode3D();
117 
118   TowerDrawHealthBars(level->camera);
119 
120   const char *text = "Wave lost";
121   int textWidth = MeasureText(text, 20);
122   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
123 
124   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
125   {
126     level->nextState = LEVEL_STATE_RESET;
127   }
128 }
129 
130 int HasLevelNextWave(Level *level)
131 {
132   for (int i = 0; i < 10; i++)
133   {
134     EnemyWave *wave = &level->waves[i];
135     if (wave->wave == level->currentWave)
136     {
137       return 1;
138     }
139   }
140   return 0;
141 }
142 
143 void DrawLevelReportWonWave(Level *level)
144 {
145   BeginMode3D(level->camera);
146   DrawLevelGround(level);
147   TowerDraw();
148   EnemyDraw();
149   ProjectileDraw();
150   ParticleDraw();
151   guiState.isBlocked = 0;
152   EndMode3D();
153 
154   TowerDrawHealthBars(level->camera);
155 
156   const char *text = "Wave won";
157   int textWidth = MeasureText(text, 20);
158   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
159 
160 
161   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
162   {
163     level->nextState = LEVEL_STATE_RESET;
164   }
165 
166   if (HasLevelNextWave(level))
167   {
168     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
169     {
170       level->nextState = LEVEL_STATE_BUILDING;
171     }
172   }
173   else {
174     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
175     {
176       level->nextState = LEVEL_STATE_WON_LEVEL;
177     }
178   }
179 }
180 
181 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
182 {
183   static ButtonState buttonStates[8] = {0};
184   int cost = GetTowerCosts(towerType);
185   const char *text = TextFormat("%s: %d", name, cost);
186   buttonStates[towerType].isSelected = level->placementMode == towerType;
187   buttonStates[towerType].isDisabled = level->playerGold < cost;
188   if (Button(text, x, y, width, height, &buttonStates[towerType]))
189   {
190     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
191   }
192 }
193 
194 void DrawLevelGround(Level *level)
195 {
196   // draw checkerboard ground pattern
197   for (int x = -5; x <= 5; x += 1)
198   {
199     for (int y = -5; y <= 5; y += 1)
200     {
201       Model *model = (x + y) % 2 == 0 ? &floorTileAModel : &floorTileBModel;
202       DrawModel(*model, (Vector3){x, 0.0f, y}, 1.0f, WHITE);
203     }
204   }
205 
206   // draw grass patches around the edges
207   const int layerCount = 2;
208   for (int layer = 0; layer < layerCount; layer++)
209   {
210     int layerPos = 6 + layer;
211     for (int x = -6 + layer; x <= 6 + layer; x += 1)
212     {
213       DrawModel(grassPatchModel[0], 
214         (Vector3){x + (float)GetRandomValue(-25,25) / 100.0f, 0.0f, -layerPos + (float)GetRandomValue(-25,25) / 100.0f}, 
215         1.0f, WHITE);
216       DrawModel(grassPatchModel[0], 
217         (Vector3){x + (float)GetRandomValue(-25,25) / 100.0f, 0.0f, layerPos + (float)GetRandomValue(-25,25) / 100.0f}, 
218         1.0f, WHITE);
219     }
220 
221     for (int z = -5 + layer; z <= 5 + layer; z += 1)
222     {
223       DrawModel(grassPatchModel[0], 
224         (Vector3){-layerPos + (float)GetRandomValue(-25,25) / 100.0f, 0.0f, z + (float)GetRandomValue(-25,25) / 100.0f}, 
225         1.0f, WHITE);
226       DrawModel(grassPatchModel[0], 
227         (Vector3){layerPos + (float)GetRandomValue(-25,25) / 100.0f, 0.0f, z + (float)GetRandomValue(-25,25) / 100.0f}, 
228         1.0f, WHITE);
229     }
230   }
231 }
232 
233 void DrawLevelBuildingState(Level *level)
234 {
235   BeginMode3D(level->camera);
236   DrawLevelGround(level);
237   TowerDraw();
238   EnemyDraw();
239   ProjectileDraw();
240   ParticleDraw();
241 
242   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
243   float planeDistance = ray.position.y / -ray.direction.y;
244   float planeX = ray.direction.x * planeDistance + ray.position.x;
245   float planeY = ray.direction.z * planeDistance + ray.position.z;
246   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
247   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
248   if (level->placementMode && !guiState.isBlocked && mapX >= -5 && mapX <= 5 && mapY >= -5 && mapY <= 5)
249   {
250     DrawCubeWires((Vector3){mapX, 0.2f, mapY}, 1.0f, 0.4f, 1.0f, RED);
251     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
252     {
253       if (TowerTryAdd(level->placementMode, mapX, mapY))
254       {
255         level->playerGold -= GetTowerCosts(level->placementMode);
256         level->placementMode = TOWER_TYPE_NONE;
257       }
258     }
259   }
260 
261   guiState.isBlocked = 0;
262 
263   EndMode3D();
264 
265   TowerDrawHealthBars(level->camera);
266 
267   static ButtonState buildWallButtonState = {0};
268   static ButtonState buildGunButtonState = {0};
269   buildWallButtonState.isSelected = level->placementMode == TOWER_TYPE_WALL;
270   buildGunButtonState.isSelected = level->placementMode == TOWER_TYPE_GUN;
271 
272   DrawBuildingBuildButton(level, 10, 10, 110, 30, TOWER_TYPE_WALL, "Wall");
273   DrawBuildingBuildButton(level, 10, 50, 110, 30, TOWER_TYPE_GUN, "Archer");
274 
275   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
276   {
277     level->nextState = LEVEL_STATE_RESET;
278   }
279   
280   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
281   {
282     level->nextState = LEVEL_STATE_BATTLE;
283   }
284 
285   const char *text = "Building phase";
286   int textWidth = MeasureText(text, 20);
287   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
288 }
289 
290 void InitBattleStateConditions(Level *level)
291 {
292   level->state = LEVEL_STATE_BATTLE;
293   level->nextState = LEVEL_STATE_NONE;
294   level->waveEndTimer = 0.0f;
295   for (int i = 0; i < 10; i++)
296   {
297     EnemyWave *wave = &level->waves[i];
298     wave->spawned = 0;
299     wave->timeToSpawnNext = wave->delay;
300   }
301 }
302 
303 void DrawLevelBattleState(Level *level)
304 {
305   BeginMode3D(level->camera);
306   DrawLevelGround(level);
307   TowerDraw();
308   EnemyDraw();
309   ProjectileDraw();
310   ParticleDraw();
311   guiState.isBlocked = 0;
312   EndMode3D();
313 
314   EnemyDrawHealthbars(level->camera);
315   TowerDrawHealthBars(level->camera);
316 
317   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
318   {
319     level->nextState = LEVEL_STATE_RESET;
320   }
321 
322   int maxCount = 0;
323   int remainingCount = 0;
324   for (int i = 0; i < 10; i++)
325   {
326     EnemyWave *wave = &level->waves[i];
327     if (wave->wave != level->currentWave)
328     {
329       continue;
330     }
331     maxCount += wave->count;
332     remainingCount += wave->count - wave->spawned;
333   }
334   int aliveCount = EnemyCount();
335   remainingCount += aliveCount;
336 
337   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
338   int textWidth = MeasureText(text, 20);
339   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
340 }
341 
342 void DrawLevel(Level *level)
343 {
344   switch (level->state)
345   {
346     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
347     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
348     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
349     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
350     default: break;
351   }
352 
353   DrawLevelHud(level);
354 }
355 
356 void UpdateLevel(Level *level)
357 {
358   if (level->state == LEVEL_STATE_BATTLE)
359   {
360     int activeWaves = 0;
361     for (int i = 0; i < 10; i++)
362     {
363       EnemyWave *wave = &level->waves[i];
364       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
365       {
366         continue;
367       }
368       activeWaves++;
369       wave->timeToSpawnNext -= gameTime.deltaTime;
370       if (wave->timeToSpawnNext <= 0.0f)
371       {
372         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
373         if (enemy)
374         {
375           wave->timeToSpawnNext = wave->interval;
376           wave->spawned++;
377         }
378       }
379     }
380     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
381       level->waveEndTimer += gameTime.deltaTime;
382       if (level->waveEndTimer >= 2.0f)
383       {
384         level->nextState = LEVEL_STATE_LOST_WAVE;
385       }
386     }
387     else if (activeWaves == 0 && EnemyCount() == 0)
388     {
389       level->waveEndTimer += gameTime.deltaTime;
390       if (level->waveEndTimer >= 2.0f)
391       {
392         level->nextState = LEVEL_STATE_WON_WAVE;
393       }
394     }
395   }
396 
397   PathFindingMapUpdate();
398   EnemyUpdate();
399   TowerUpdate();
400   ProjectileUpdate();
401   ParticleUpdate();
402 
403   if (level->nextState == LEVEL_STATE_RESET)
404   {
405     InitLevel(level);
406   }
407   
408   if (level->nextState == LEVEL_STATE_BATTLE)
409   {
410     InitBattleStateConditions(level);
411   }
412   
413   if (level->nextState == LEVEL_STATE_WON_WAVE)
414   {
415     level->currentWave++;
416     level->state = LEVEL_STATE_WON_WAVE;
417   }
418   
419   if (level->nextState == LEVEL_STATE_LOST_WAVE)
420   {
421     level->state = LEVEL_STATE_LOST_WAVE;
422   }
423 
424   if (level->nextState == LEVEL_STATE_BUILDING)
425   {
426     level->state = LEVEL_STATE_BUILDING;
427   }
428 
429   if (level->nextState == LEVEL_STATE_WON_LEVEL)
430   {
431     // make something of this later
432     InitLevel(level);
433   }
434 
435   level->nextState = LEVEL_STATE_NONE;
436 }
437 
438 float nextSpawnTime = 0.0f;
439 
440 void ResetGame()
441 {
442   InitLevel(currentLevel);
443 }
444 
445 void InitGame()
446 {
447   TowerInit();
448   EnemyInit();
449   ProjectileInit();
450   ParticleInit();
451   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
452 
453   currentLevel = levels;
454   InitLevel(currentLevel);
455 }
456 
457 //# Immediate GUI functions
458 
459 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth)
460 {
461   const float healthBarHeight = 6.0f;
462   const float healthBarOffset = 15.0f;
463   const float inset = 2.0f;
464   const float innerWidth = healthBarWidth - inset * 2;
465   const float innerHeight = healthBarHeight - inset * 2;
466 
467   Vector2 screenPos = GetWorldToScreen(position, camera);
468   float centerX = screenPos.x - healthBarWidth * 0.5f;
469   float topY = screenPos.y - healthBarOffset;
470   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
471   float healthWidth = innerWidth * healthRatio;
472   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
473 }
474 
475 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
476 {
477   Rectangle bounds = {x, y, width, height};
478   int isPressed = 0;
479   int isSelected = state && state->isSelected;
480   int isDisabled = state && state->isDisabled;
481   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !isDisabled)
482   {
483     Color color = isSelected ? DARKGRAY : GRAY;
484     DrawRectangle(x, y, width, height, color);
485     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
486     {
487       isPressed = 1;
488     }
489     guiState.isBlocked = 1;
490   }
491   else
492   {
493     Color color = isSelected ? WHITE : LIGHTGRAY;
494     DrawRectangle(x, y, width, height, color);
495   }
496   Font font = GetFontDefault();
497   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
498   Color textColor = isDisabled ? GRAY : BLACK;
499   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
500   return isPressed;
501 }
502 
503 //# Main game loop
504 
505 void GameUpdate()
506 {
507   float dt = GetFrameTime();
508   // cap maximum delta time to 0.1 seconds to prevent large time steps
509   if (dt > 0.1f) dt = 0.1f;
510   gameTime.time += dt;
511   gameTime.deltaTime = dt;
512 
513   UpdateLevel(currentLevel);
514 }
515 
516 int main(void)
517 {
518   int screenWidth, screenHeight;
519   GetPreferredSize(&screenWidth, &screenHeight);
520   InitWindow(screenWidth, screenHeight, "Tower defense");
521   SetTargetFPS(30);
522 
523   LoadAssets();
524   InitGame();
525 
526   while (!WindowShouldClose())
527   {
528     if (IsPaused()) {
529       // canvas is not visible in browser - do nothing
530       continue;
531     }
532 
533     BeginDrawing();
534     ClearBackground((Color){0x4E, 0x63, 0x26, 0xFF});
535 
536     GameUpdate();
537     DrawLevel(currentLevel);
538 
539     EndDrawing();
540   }
541 
542   CloseWindow();
543 
544   return 0;
545 }
  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 //# Level
249 void DrawLevelGround(Level *level);
250 
251 //# variables
252 extern Level *currentLevel;
253 extern Enemy enemies[ENEMY_MAX_COUNT];
254 extern int enemyCount;
255 extern EnemyClassConfig enemyClassConfigs[];
256 
257 extern GUIState guiState;
258 extern GameTime gameTime;
259 extern Tower towers[TOWER_MAX_COUNT];
260 extern int towerCount;
261 
262 extern Texture2D palette, spriteSheet;
263 
264 #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, 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 "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 
  9 // definition of our archer unit
 10 SpriteUnit archerUnit = {
 11     .srcRect = {0, 0, 16, 16},
 12     .offset = {7, 1},
 13     .frameCount = 1,
 14     .frameDuration = 0.0f,
 15     .srcWeaponIdleRect = {16, 0, 6, 16},
 16     .srcWeaponIdleOffset = {8, 0},
 17     .srcWeaponCooldownRect = {22, 0, 11, 16},
 18     .srcWeaponCooldownOffset = {10, 0},
 19 };
 20 
 21 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
 22 {
 23   float xScale = flip ? -1.0f : 1.0f;
 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 * xScale };
 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   if (flip)
 40   {
 41     srcRect.x += srcRect.width;
 42     srcRect.width = -srcRect.width;
 43   }
 44   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 45 
 46   if (phase == SPRITE_UNIT_PHASE_WEAPON_COOLDOWN && unit.srcWeaponCooldownRect.width > 0)
 47   {
 48     offset = (Vector2){ unit.srcWeaponCooldownOffset.x / 16.0f * size, unit.srcWeaponCooldownOffset.y / 16.0f * size };
 49     scale = (Vector2){ unit.srcWeaponCooldownRect.width / 16.0f * size, unit.srcWeaponCooldownRect.height / 16.0f * size };
 50     srcRect = unit.srcWeaponCooldownRect;
 51     if (flip)
 52     {
 53       // position.x = flip * scale.x * 0.5f;
 54       srcRect.x += srcRect.width;
 55       srcRect.width = -srcRect.width;
 56       offset.x = scale.x - offset.x;
 57     }
 58     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 59   }
 60   else if (phase == SPRITE_UNIT_PHASE_WEAPON_IDLE && unit.srcWeaponIdleRect.width > 0)
 61   {
 62     offset = (Vector2){ unit.srcWeaponIdleOffset.x / 16.0f * size, unit.srcWeaponIdleOffset.y / 16.0f * size };
 63     scale = (Vector2){ unit.srcWeaponIdleRect.width / 16.0f * size, unit.srcWeaponIdleRect.height / 16.0f * size };
 64     srcRect = unit.srcWeaponIdleRect;
 65     if (flip)
 66     {
 67       // position.x = flip * scale.x * 0.5f;
 68       srcRect.x += srcRect.width;
 69       srcRect.width = -srcRect.width;
 70       offset.x = scale.x - offset.x;
 71     }
 72     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 73   }
 74 }
 75 
 76 void TowerInit()
 77 {
 78   for (int i = 0; i < TOWER_MAX_COUNT; i++)
 79   {
 80     towers[i] = (Tower){0};
 81   }
 82   towerCount = 0;
 83 
 84   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
 85   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
 86 
 87   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
 88   {
 89     if (towerModels[i].materials)
 90     {
 91       // assign the palette texture to the material of the model (0 is not used afaik)
 92       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 93     }
 94   }
 95 }
 96 
 97 static void TowerGunUpdate(Tower *tower)
 98 {
 99   if (tower->cooldown <= 0)
100   {
101     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
102     if (enemy)
103     {
104       tower->cooldown = 0.5f;
105       // shoot the enemy; determine future position of the enemy
106       float bulletSpeed = 4.0f;
107       float bulletDamage = 3.0f;
108       Vector2 velocity = enemy->simVelocity;
109       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
110       Vector2 towerPosition = {tower->x, tower->y};
111       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
112       for (int i = 0; i < 8; i++) {
113         velocity = enemy->simVelocity;
114         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
115         float distance = Vector2Distance(towerPosition, futurePosition);
116         float eta2 = distance / bulletSpeed;
117         if (fabs(eta - eta2) < 0.01f) {
118           break;
119         }
120         eta = (eta2 + eta) * 0.5f;
121       }
122       ProjectileTryAdd(PROJECTILE_TYPE_ARROW, enemy, 
123         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
124         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
125         bulletSpeed, bulletDamage);
126       enemy->futureDamage += bulletDamage;
127       tower->lastTargetPosition = futurePosition;
128     }
129   }
130   else
131   {
132     tower->cooldown -= gameTime.deltaTime;
133   }
134 }
135 
136 Tower *TowerGetAt(int16_t x, int16_t y)
137 {
138   for (int i = 0; i < towerCount; i++)
139   {
140     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
141     {
142       return &towers[i];
143     }
144   }
145   return 0;
146 }
147 
148 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
149 {
150   if (towerCount >= TOWER_MAX_COUNT)
151   {
152     return 0;
153   }
154 
155   Tower *tower = TowerGetAt(x, y);
156   if (tower)
157   {
158     return 0;
159   }
160 
161   tower = &towers[towerCount++];
162   tower->x = x;
163   tower->y = y;
164   tower->towerType = towerType;
165   tower->cooldown = 0.0f;
166   tower->damage = 0.0f;
167   return tower;
168 }
169 
170 Tower *GetTowerByType(uint8_t towerType)
171 {
172   for (int i = 0; i < towerCount; i++)
173   {
174     if (towers[i].towerType == towerType)
175     {
176       return &towers[i];
177     }
178   }
179   return 0;
180 }
181 
182 int GetTowerCosts(uint8_t towerType)
183 {
184   switch (towerType)
185   {
186   case TOWER_TYPE_BASE:
187     return 0;
188   case TOWER_TYPE_GUN:
189     return 6;
190   case TOWER_TYPE_WALL:
191     return 2;
192   }
193   return 0;
194 }
195 
196 float TowerGetMaxHealth(Tower *tower)
197 {
198   switch (tower->towerType)
199   {
200   case TOWER_TYPE_BASE:
201     return 10.0f;
202   case TOWER_TYPE_GUN:
203     return 3.0f;
204   case TOWER_TYPE_WALL:
205     return 5.0f;
206   }
207   return 0.0f;
208 }
209 
210 void TowerDraw()
211 {
212   for (int i = 0; i < towerCount; i++)
213   {
214     Tower tower = towers[i];
215     if (tower.towerType == TOWER_TYPE_NONE)
216     {
217       continue;
218     }
219 
220     switch (tower.towerType)
221     {
222     case TOWER_TYPE_GUN:
223       {
224         Vector2 screenPosTower = GetWorldToScreen((Vector3){tower.x, 0.0f, tower.y}, currentLevel->camera);
225         Vector2 screenPosTarget = GetWorldToScreen((Vector3){tower.lastTargetPosition.x, 0.0f, tower.lastTargetPosition.y}, currentLevel->camera);
226         DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
227         DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0, screenPosTarget.x > screenPosTower.x, 
228           tower.cooldown > 0.2f ? SPRITE_UNIT_PHASE_WEAPON_COOLDOWN : SPRITE_UNIT_PHASE_WEAPON_IDLE);
229       }
230       break;
231     default:
232       if (towerModels[tower.towerType].materials)
233       {
234         DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
235       } else {
236         DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
237       }
238       break;
239     }
240   }
241 }
242 
243 void TowerUpdate()
244 {
245   for (int i = 0; i < towerCount; i++)
246   {
247     Tower *tower = &towers[i];
248     switch (tower->towerType)
249     {
250     case TOWER_TYPE_GUN:
251       TowerGunUpdate(tower);
252       break;
253     }
254   }
255 }
256 
257 void TowerDrawHealthBars(Camera3D camera)
258 {
259   for (int i = 0; i < towerCount; i++)
260   {
261     Tower *tower = &towers[i];
262     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
263     {
264       continue;
265     }
266     
267     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
268     float maxHealth = TowerGetMaxHealth(tower);
269     float health = maxHealth - tower->damage;
270     float healthRatio = health / maxHealth;
271     
272     DrawHealthBar(camera, position, healthRatio, GREEN, 35.0f);
273   }
274 }
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif
Random object placement
The objects are now offsetted by a random value:
  1 (float)GetRandomValue(-25,25) / 100.0f
But the graphics are jumping all over the place! This is not what we want here.
The problem is, that we draw the objects every frame and the random value is ... well, random. Every time. That is why they jump each frame and it looks just like noise. We need to make sure that the random value is the same every frame:
  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 Model floorTileAModel = {0};
 11 Model floorTileBModel = {0};
 12 Model grassPatchModel[1] = {0};
 13 
 14 Texture2D palette, spriteSheet;
 15 
 16 Level levels[] = {
 17   [0] = {
 18     .state = LEVEL_STATE_BUILDING,
 19     .initialGold = 20,
 20     .waves[0] = {
 21       .enemyType = ENEMY_TYPE_MINION,
 22       .wave = 0,
 23       .count = 10,
 24       .interval = 2.5f,
 25       .delay = 1.0f,
 26       .spawnPosition = {0, 6},
 27     },
 28     .waves[1] = {
 29       .enemyType = ENEMY_TYPE_MINION,
 30       .wave = 1,
 31       .count = 20,
 32       .interval = 1.5f,
 33       .delay = 1.0f,
 34       .spawnPosition = {0, 6},
 35     },
 36     .waves[2] = {
 37       .enemyType = ENEMY_TYPE_MINION,
 38       .wave = 2,
 39       .count = 30,
 40       .interval = 1.2f,
 41       .delay = 1.0f,
 42       .spawnPosition = {0, 6},
 43     }
 44   },
 45 };
 46 
 47 Level *currentLevel = levels;
 48 
 49 //# Game
 50 
 51 static Model LoadGLBModel(char *filename)
 52 {
 53   Model model = LoadModel(TextFormat("data/%s.glb",filename));
 54   if (model.materialCount > 1)
 55   {
 56     model.materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 57   }
 58   return model;
 59 }
 60 
 61 void LoadAssets()
 62 {
 63   // load a sprite sheet that contains all units
 64   spriteSheet = LoadTexture("data/spritesheet.png");
 65   SetTextureFilter(spriteSheet, TEXTURE_FILTER_BILINEAR);
 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   floorTileAModel = LoadGLBModel("floor-tile-a");
 73   floorTileBModel = LoadGLBModel("floor-tile-b");
 74   grassPatchModel[0] = LoadGLBModel("grass-patch-1");
 75 }
 76 
 77 void InitLevel(Level *level)
 78 {
 79   TowerInit();
 80   EnemyInit();
 81   ProjectileInit();
 82   ParticleInit();
 83   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
 84 
 85   level->placementMode = 0;
 86   level->state = LEVEL_STATE_BUILDING;
 87   level->nextState = LEVEL_STATE_NONE;
 88   level->playerGold = level->initialGold;
 89   level->currentWave = 0;
 90 
 91   Camera *camera = &level->camera;
 92   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
 93   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
 94   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
 95   camera->fovy = 10.0f;
 96   camera->projection = CAMERA_ORTHOGRAPHIC;
 97 }
 98 
 99 void DrawLevelHud(Level *level)
100 {
101   const char *text = TextFormat("Gold: %d", level->playerGold);
102   Font font = GetFontDefault();
103   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
104   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
105 }
106 
107 void DrawLevelReportLostWave(Level *level)
108 {
109   BeginMode3D(level->camera);
110   DrawLevelGround(level);
111   TowerDraw();
112   EnemyDraw();
113   ProjectileDraw();
114   ParticleDraw();
115   guiState.isBlocked = 0;
116   EndMode3D();
117 
118   TowerDrawHealthBars(level->camera);
119 
120   const char *text = "Wave lost";
121   int textWidth = MeasureText(text, 20);
122   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
123 
124   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
125   {
126     level->nextState = LEVEL_STATE_RESET;
127   }
128 }
129 
130 int HasLevelNextWave(Level *level)
131 {
132   for (int i = 0; i < 10; i++)
133   {
134     EnemyWave *wave = &level->waves[i];
135     if (wave->wave == level->currentWave)
136     {
137       return 1;
138     }
139   }
140   return 0;
141 }
142 
143 void DrawLevelReportWonWave(Level *level)
144 {
145   BeginMode3D(level->camera);
146   DrawLevelGround(level);
147   TowerDraw();
148   EnemyDraw();
149   ProjectileDraw();
150   ParticleDraw();
151   guiState.isBlocked = 0;
152   EndMode3D();
153 
154   TowerDrawHealthBars(level->camera);
155 
156   const char *text = "Wave won";
157   int textWidth = MeasureText(text, 20);
158   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
159 
160 
161   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
162   {
163     level->nextState = LEVEL_STATE_RESET;
164   }
165 
166   if (HasLevelNextWave(level))
167   {
168     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
169     {
170       level->nextState = LEVEL_STATE_BUILDING;
171     }
172   }
173   else {
174     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
175     {
176       level->nextState = LEVEL_STATE_WON_LEVEL;
177     }
178   }
179 }
180 
181 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
182 {
183   static ButtonState buttonStates[8] = {0};
184   int cost = GetTowerCosts(towerType);
185   const char *text = TextFormat("%s: %d", name, cost);
186   buttonStates[towerType].isSelected = level->placementMode == towerType;
187   buttonStates[towerType].isDisabled = level->playerGold < cost;
188   if (Button(text, x, y, width, height, &buttonStates[towerType]))
189   {
190     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
191   }
192 }
193 
194 void DrawLevelGround(Level *level)
195 {
196   // draw checkerboard ground pattern
197   for (int x = -5; x <= 5; x += 1)
198   {
199     for (int y = -5; y <= 5; y += 1)
200     {
201       Model *model = (x + y) % 2 == 0 ? &floorTileAModel : &floorTileBModel;
202       DrawModel(*model, (Vector3){x, 0.0f, y}, 1.0f, WHITE);
203     }
204   }
205 
206   // draw grass patches around the edges
207   const int layerCount = 2;
208   SetRandomSeed(123);
209   for (int layer = 0; layer < layerCount; layer++)
210   {
211     int layerPos = 6 + layer;
212     for (int x = -6 + layer; x <= 6 + layer; x += 1)
213     {
214       DrawModel(grassPatchModel[0], 
215         (Vector3){x + (float)GetRandomValue(-25,25) / 100.0f, 0.0f, -layerPos + (float)GetRandomValue(-25,25) / 100.0f}, 
216         1.0f, WHITE);
217       DrawModel(grassPatchModel[0], 
218         (Vector3){x + (float)GetRandomValue(-25,25) / 100.0f, 0.0f, layerPos + (float)GetRandomValue(-25,25) / 100.0f}, 
219         1.0f, WHITE);
220     }
221 
222     for (int z = -5 + layer; z <= 5 + layer; z += 1)
223     {
224       DrawModel(grassPatchModel[0], 
225         (Vector3){-layerPos + (float)GetRandomValue(-25,25) / 100.0f, 0.0f, z + (float)GetRandomValue(-25,25) / 100.0f}, 
226         1.0f, WHITE);
227       DrawModel(grassPatchModel[0], 
228         (Vector3){layerPos + (float)GetRandomValue(-25,25) / 100.0f, 0.0f, z + (float)GetRandomValue(-25,25) / 100.0f}, 
229         1.0f, WHITE);
230     }
231   }
232 }
233 
234 void DrawLevelBuildingState(Level *level)
235 {
236   BeginMode3D(level->camera);
237   DrawLevelGround(level);
238   TowerDraw();
239   EnemyDraw();
240   ProjectileDraw();
241   ParticleDraw();
242 
243   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
244   float planeDistance = ray.position.y / -ray.direction.y;
245   float planeX = ray.direction.x * planeDistance + ray.position.x;
246   float planeY = ray.direction.z * planeDistance + ray.position.z;
247   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
248   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
249   if (level->placementMode && !guiState.isBlocked && mapX >= -5 && mapX <= 5 && mapY >= -5 && mapY <= 5)
250   {
251     DrawCubeWires((Vector3){mapX, 0.2f, mapY}, 1.0f, 0.4f, 1.0f, RED);
252     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
253     {
254       if (TowerTryAdd(level->placementMode, mapX, mapY))
255       {
256         level->playerGold -= GetTowerCosts(level->placementMode);
257         level->placementMode = TOWER_TYPE_NONE;
258       }
259     }
260   }
261 
262   guiState.isBlocked = 0;
263 
264   EndMode3D();
265 
266   TowerDrawHealthBars(level->camera);
267 
268   static ButtonState buildWallButtonState = {0};
269   static ButtonState buildGunButtonState = {0};
270   buildWallButtonState.isSelected = level->placementMode == TOWER_TYPE_WALL;
271   buildGunButtonState.isSelected = level->placementMode == TOWER_TYPE_GUN;
272 
273   DrawBuildingBuildButton(level, 10, 10, 110, 30, TOWER_TYPE_WALL, "Wall");
274   DrawBuildingBuildButton(level, 10, 50, 110, 30, TOWER_TYPE_GUN, "Archer");
275 
276   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
277   {
278     level->nextState = LEVEL_STATE_RESET;
279   }
280   
281   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
282   {
283     level->nextState = LEVEL_STATE_BATTLE;
284   }
285 
286   const char *text = "Building phase";
287   int textWidth = MeasureText(text, 20);
288   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
289 }
290 
291 void InitBattleStateConditions(Level *level)
292 {
293   level->state = LEVEL_STATE_BATTLE;
294   level->nextState = LEVEL_STATE_NONE;
295   level->waveEndTimer = 0.0f;
296   for (int i = 0; i < 10; i++)
297   {
298     EnemyWave *wave = &level->waves[i];
299     wave->spawned = 0;
300     wave->timeToSpawnNext = wave->delay;
301   }
302 }
303 
304 void DrawLevelBattleState(Level *level)
305 {
306   BeginMode3D(level->camera);
307   DrawLevelGround(level);
308   TowerDraw();
309   EnemyDraw();
310   ProjectileDraw();
311   ParticleDraw();
312   guiState.isBlocked = 0;
313   EndMode3D();
314 
315   EnemyDrawHealthbars(level->camera);
316   TowerDrawHealthBars(level->camera);
317 
318   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
319   {
320     level->nextState = LEVEL_STATE_RESET;
321   }
322 
323   int maxCount = 0;
324   int remainingCount = 0;
325   for (int i = 0; i < 10; i++)
326   {
327     EnemyWave *wave = &level->waves[i];
328     if (wave->wave != level->currentWave)
329     {
330       continue;
331     }
332     maxCount += wave->count;
333     remainingCount += wave->count - wave->spawned;
334   }
335   int aliveCount = EnemyCount();
336   remainingCount += aliveCount;
337 
338   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
339   int textWidth = MeasureText(text, 20);
340   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
341 }
342 
343 void DrawLevel(Level *level)
344 {
345   switch (level->state)
346   {
347     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
348     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
349     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
350     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
351     default: break;
352   }
353 
354   DrawLevelHud(level);
355 }
356 
357 void UpdateLevel(Level *level)
358 {
359   if (level->state == LEVEL_STATE_BATTLE)
360   {
361     int activeWaves = 0;
362     for (int i = 0; i < 10; i++)
363     {
364       EnemyWave *wave = &level->waves[i];
365       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
366       {
367         continue;
368       }
369       activeWaves++;
370       wave->timeToSpawnNext -= gameTime.deltaTime;
371       if (wave->timeToSpawnNext <= 0.0f)
372       {
373         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
374         if (enemy)
375         {
376           wave->timeToSpawnNext = wave->interval;
377           wave->spawned++;
378         }
379       }
380     }
381     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
382       level->waveEndTimer += gameTime.deltaTime;
383       if (level->waveEndTimer >= 2.0f)
384       {
385         level->nextState = LEVEL_STATE_LOST_WAVE;
386       }
387     }
388     else if (activeWaves == 0 && EnemyCount() == 0)
389     {
390       level->waveEndTimer += gameTime.deltaTime;
391       if (level->waveEndTimer >= 2.0f)
392       {
393         level->nextState = LEVEL_STATE_WON_WAVE;
394       }
395     }
396   }
397 
398   PathFindingMapUpdate();
399   EnemyUpdate();
400   TowerUpdate();
401   ProjectileUpdate();
402   ParticleUpdate();
403 
404   if (level->nextState == LEVEL_STATE_RESET)
405   {
406     InitLevel(level);
407   }
408   
409   if (level->nextState == LEVEL_STATE_BATTLE)
410   {
411     InitBattleStateConditions(level);
412   }
413   
414   if (level->nextState == LEVEL_STATE_WON_WAVE)
415   {
416     level->currentWave++;
417     level->state = LEVEL_STATE_WON_WAVE;
418   }
419   
420   if (level->nextState == LEVEL_STATE_LOST_WAVE)
421   {
422     level->state = LEVEL_STATE_LOST_WAVE;
423   }
424 
425   if (level->nextState == LEVEL_STATE_BUILDING)
426   {
427     level->state = LEVEL_STATE_BUILDING;
428   }
429 
430   if (level->nextState == LEVEL_STATE_WON_LEVEL)
431   {
432     // make something of this later
433     InitLevel(level);
434   }
435 
436   level->nextState = LEVEL_STATE_NONE;
437 }
438 
439 float nextSpawnTime = 0.0f;
440 
441 void ResetGame()
442 {
443   InitLevel(currentLevel);
444 }
445 
446 void InitGame()
447 {
448   TowerInit();
449   EnemyInit();
450   ProjectileInit();
451   ParticleInit();
452   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
453 
454   currentLevel = levels;
455   InitLevel(currentLevel);
456 }
457 
458 //# Immediate GUI functions
459 
460 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth)
461 {
462   const float healthBarHeight = 6.0f;
463   const float healthBarOffset = 15.0f;
464   const float inset = 2.0f;
465   const float innerWidth = healthBarWidth - inset * 2;
466   const float innerHeight = healthBarHeight - inset * 2;
467 
468   Vector2 screenPos = GetWorldToScreen(position, camera);
469   float centerX = screenPos.x - healthBarWidth * 0.5f;
470   float topY = screenPos.y - healthBarOffset;
471   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
472   float healthWidth = innerWidth * healthRatio;
473   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
474 }
475 
476 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
477 {
478   Rectangle bounds = {x, y, width, height};
479   int isPressed = 0;
480   int isSelected = state && state->isSelected;
481   int isDisabled = state && state->isDisabled;
482   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !isDisabled)
483   {
484     Color color = isSelected ? DARKGRAY : GRAY;
485     DrawRectangle(x, y, width, height, color);
486     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
487     {
488       isPressed = 1;
489     }
490     guiState.isBlocked = 1;
491   }
492   else
493   {
494     Color color = isSelected ? WHITE : LIGHTGRAY;
495     DrawRectangle(x, y, width, height, color);
496   }
497   Font font = GetFontDefault();
498   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
499   Color textColor = isDisabled ? GRAY : BLACK;
500   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
501   return isPressed;
502 }
503 
504 //# Main game loop
505 
506 void GameUpdate()
507 {
508   float dt = GetFrameTime();
509   // cap maximum delta time to 0.1 seconds to prevent large time steps
510   if (dt > 0.1f) dt = 0.1f;
511   gameTime.time += dt;
512   gameTime.deltaTime = dt;
513 
514   UpdateLevel(currentLevel);
515 }
516 
517 int main(void)
518 {
519   int screenWidth, screenHeight;
520   GetPreferredSize(&screenWidth, &screenHeight);
521   InitWindow(screenWidth, screenHeight, "Tower defense");
522   SetTargetFPS(30);
523 
524   LoadAssets();
525   InitGame();
526 
527   while (!WindowShouldClose())
528   {
529     if (IsPaused()) {
530       // canvas is not visible in browser - do nothing
531       continue;
532     }
533 
534     BeginDrawing();
535     ClearBackground((Color){0x4E, 0x63, 0x26, 0xFF});
536 
537     GameUpdate();
538     DrawLevel(currentLevel);
539 
540     EndDrawing();
541   }
542 
543   CloseWindow();
544 
545   return 0;
546 }
  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 //# Level
249 void DrawLevelGround(Level *level);
250 
251 //# variables
252 extern Level *currentLevel;
253 extern Enemy enemies[ENEMY_MAX_COUNT];
254 extern int enemyCount;
255 extern EnemyClassConfig enemyClassConfigs[];
256 
257 extern GUIState guiState;
258 extern GameTime gameTime;
259 extern Tower towers[TOWER_MAX_COUNT];
260 extern int towerCount;
261 
262 extern Texture2D palette, spriteSheet;
263 
264 #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, 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 "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 
  9 // definition of our archer unit
 10 SpriteUnit archerUnit = {
 11     .srcRect = {0, 0, 16, 16},
 12     .offset = {7, 1},
 13     .frameCount = 1,
 14     .frameDuration = 0.0f,
 15     .srcWeaponIdleRect = {16, 0, 6, 16},
 16     .srcWeaponIdleOffset = {8, 0},
 17     .srcWeaponCooldownRect = {22, 0, 11, 16},
 18     .srcWeaponCooldownOffset = {10, 0},
 19 };
 20 
 21 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
 22 {
 23   float xScale = flip ? -1.0f : 1.0f;
 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 * xScale };
 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   if (flip)
 40   {
 41     srcRect.x += srcRect.width;
 42     srcRect.width = -srcRect.width;
 43   }
 44   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 45 
 46   if (phase == SPRITE_UNIT_PHASE_WEAPON_COOLDOWN && unit.srcWeaponCooldownRect.width > 0)
 47   {
 48     offset = (Vector2){ unit.srcWeaponCooldownOffset.x / 16.0f * size, unit.srcWeaponCooldownOffset.y / 16.0f * size };
 49     scale = (Vector2){ unit.srcWeaponCooldownRect.width / 16.0f * size, unit.srcWeaponCooldownRect.height / 16.0f * size };
 50     srcRect = unit.srcWeaponCooldownRect;
 51     if (flip)
 52     {
 53       // position.x = flip * scale.x * 0.5f;
 54       srcRect.x += srcRect.width;
 55       srcRect.width = -srcRect.width;
 56       offset.x = scale.x - offset.x;
 57     }
 58     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 59   }
 60   else if (phase == SPRITE_UNIT_PHASE_WEAPON_IDLE && unit.srcWeaponIdleRect.width > 0)
 61   {
 62     offset = (Vector2){ unit.srcWeaponIdleOffset.x / 16.0f * size, unit.srcWeaponIdleOffset.y / 16.0f * size };
 63     scale = (Vector2){ unit.srcWeaponIdleRect.width / 16.0f * size, unit.srcWeaponIdleRect.height / 16.0f * size };
 64     srcRect = unit.srcWeaponIdleRect;
 65     if (flip)
 66     {
 67       // position.x = flip * scale.x * 0.5f;
 68       srcRect.x += srcRect.width;
 69       srcRect.width = -srcRect.width;
 70       offset.x = scale.x - offset.x;
 71     }
 72     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 73   }
 74 }
 75 
 76 void TowerInit()
 77 {
 78   for (int i = 0; i < TOWER_MAX_COUNT; i++)
 79   {
 80     towers[i] = (Tower){0};
 81   }
 82   towerCount = 0;
 83 
 84   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
 85   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
 86 
 87   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
 88   {
 89     if (towerModels[i].materials)
 90     {
 91       // assign the palette texture to the material of the model (0 is not used afaik)
 92       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 93     }
 94   }
 95 }
 96 
 97 static void TowerGunUpdate(Tower *tower)
 98 {
 99   if (tower->cooldown <= 0)
100   {
101     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
102     if (enemy)
103     {
104       tower->cooldown = 0.5f;
105       // shoot the enemy; determine future position of the enemy
106       float bulletSpeed = 4.0f;
107       float bulletDamage = 3.0f;
108       Vector2 velocity = enemy->simVelocity;
109       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
110       Vector2 towerPosition = {tower->x, tower->y};
111       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
112       for (int i = 0; i < 8; i++) {
113         velocity = enemy->simVelocity;
114         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
115         float distance = Vector2Distance(towerPosition, futurePosition);
116         float eta2 = distance / bulletSpeed;
117         if (fabs(eta - eta2) < 0.01f) {
118           break;
119         }
120         eta = (eta2 + eta) * 0.5f;
121       }
122       ProjectileTryAdd(PROJECTILE_TYPE_ARROW, enemy, 
123         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
124         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
125         bulletSpeed, bulletDamage);
126       enemy->futureDamage += bulletDamage;
127       tower->lastTargetPosition = futurePosition;
128     }
129   }
130   else
131   {
132     tower->cooldown -= gameTime.deltaTime;
133   }
134 }
135 
136 Tower *TowerGetAt(int16_t x, int16_t y)
137 {
138   for (int i = 0; i < towerCount; i++)
139   {
140     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
141     {
142       return &towers[i];
143     }
144   }
145   return 0;
146 }
147 
148 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
149 {
150   if (towerCount >= TOWER_MAX_COUNT)
151   {
152     return 0;
153   }
154 
155   Tower *tower = TowerGetAt(x, y);
156   if (tower)
157   {
158     return 0;
159   }
160 
161   tower = &towers[towerCount++];
162   tower->x = x;
163   tower->y = y;
164   tower->towerType = towerType;
165   tower->cooldown = 0.0f;
166   tower->damage = 0.0f;
167   return tower;
168 }
169 
170 Tower *GetTowerByType(uint8_t towerType)
171 {
172   for (int i = 0; i < towerCount; i++)
173   {
174     if (towers[i].towerType == towerType)
175     {
176       return &towers[i];
177     }
178   }
179   return 0;
180 }
181 
182 int GetTowerCosts(uint8_t towerType)
183 {
184   switch (towerType)
185   {
186   case TOWER_TYPE_BASE:
187     return 0;
188   case TOWER_TYPE_GUN:
189     return 6;
190   case TOWER_TYPE_WALL:
191     return 2;
192   }
193   return 0;
194 }
195 
196 float TowerGetMaxHealth(Tower *tower)
197 {
198   switch (tower->towerType)
199   {
200   case TOWER_TYPE_BASE:
201     return 10.0f;
202   case TOWER_TYPE_GUN:
203     return 3.0f;
204   case TOWER_TYPE_WALL:
205     return 5.0f;
206   }
207   return 0.0f;
208 }
209 
210 void TowerDraw()
211 {
212   for (int i = 0; i < towerCount; i++)
213   {
214     Tower tower = towers[i];
215     if (tower.towerType == TOWER_TYPE_NONE)
216     {
217       continue;
218     }
219 
220     switch (tower.towerType)
221     {
222     case TOWER_TYPE_GUN:
223       {
224         Vector2 screenPosTower = GetWorldToScreen((Vector3){tower.x, 0.0f, tower.y}, currentLevel->camera);
225         Vector2 screenPosTarget = GetWorldToScreen((Vector3){tower.lastTargetPosition.x, 0.0f, tower.lastTargetPosition.y}, currentLevel->camera);
226         DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
227         DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0, screenPosTarget.x > screenPosTower.x, 
228           tower.cooldown > 0.2f ? SPRITE_UNIT_PHASE_WEAPON_COOLDOWN : SPRITE_UNIT_PHASE_WEAPON_IDLE);
229       }
230       break;
231     default:
232       if (towerModels[tower.towerType].materials)
233       {
234         DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
235       } else {
236         DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
237       }
238       break;
239     }
240   }
241 }
242 
243 void TowerUpdate()
244 {
245   for (int i = 0; i < towerCount; i++)
246   {
247     Tower *tower = &towers[i];
248     switch (tower->towerType)
249     {
250     case TOWER_TYPE_GUN:
251       TowerGunUpdate(tower);
252       break;
253     }
254   }
255 }
256 
257 void TowerDrawHealthBars(Camera3D camera)
258 {
259   for (int i = 0; i < towerCount; i++)
260   {
261     Tower *tower = &towers[i];
262     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
263     {
264       continue;
265     }
266     
267     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
268     float maxHealth = TowerGetMaxHealth(tower);
269     float health = maxHealth - tower->damage;
270     float healthRatio = health / maxHealth;
271     
272     DrawHealthBar(camera, position, healthRatio, GREEN, 35.0f);
273   }
274 }
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif
By calling SetRandomSeed(123) we make sure that the random values are the same every frame. So the 3rd call to GetRandomValue will always return the same value. Hardly random, but that's why it is called a pseudo-random number generator, or PRNG: It's a deterministic sequence of numbers based on a seed. The seed here is 123. Deterministic means that when repeating the same actions, the results will be the same. So even when the application is restarted or loaded on a different machine, the sequence of random numbers will be the same (unless the PRNG implementation is OS dependent, then it will produce different looks).
The code we injected has however side effects that could later cause highly annoying problems:
So how do we fix this? There is no way in raylib to obtain the current seed.
But this is not a problem: We only need a random seed. Given the current random generation sequence is still random, we can obtain a seed for later use by calling GetRandomValue(0, 0xfffffff) before we set the seed:
  1   int oldSeed = GetRandomValue(0, 0xfffffff);
  2   SetRandomSeed(123);
  3   ...
  4   SetRandomSeed(oldSeed);
Now we have a random seed for the next time we need it.
I can not stress this enough that whenever a function has a global state, you have to be aware that any call you do will have side effects that need to be considered. Most raylib functions don't have such side effects, though some do have internal states like caches, it does not affect the behavior of the function calls.
The random number generator is one of the few functions that have a global state where this is intentional, but for beginners and even experienced developers, it is not always obvious that this has to be taken care of.
I also want to mention that there are other ways to randomize the seed of the PRNG. The way I did it here is the most simple and efficient way to do it.
Another popular approach is to produce a random seed is to use the current time as a seed, but this has two quite obvious drawbacks: The first is that the seed is not really random because it is based on a predictable value (time). For game functions, this tends to be not a huge problem, but for cryptographic functions, this can introduce security issues (though cryptographic library usually don't use default PRNGs). The bigger problem for games is that the time function call is way more expensive than requesting a random number from the PRNG.
There is even a hidden need to use the previous PRNG state to produce a future seed: It stays deterministic. This is not only a good thing for debugging and testing. In fact, for procedural generated content, this is even critical:
Imagine you have a function A that does some deterministic procedural random generation and you call another function B. This function B is now also doing some procedural random generation. If B does not produce a deterministic new seed for the PRNG after it has finished, A will no longer produce deterministic results and you broke the chain of deterministic procedural random generation.
Speaking of determinism: At the moment, when we restart the level, the decorations look the same every time. This is because the seed is using a fixed value. We can change this by storing a random seed in the level struct and using this seed to set the random seed. This way, each level will have a different look:
  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 Model floorTileAModel = {0};
 11 Model floorTileBModel = {0};
 12 Model grassPatchModel[1] = {0};
 13 
 14 Texture2D palette, spriteSheet;
 15 
 16 Level levels[] = {
 17   [0] = {
 18     .state = LEVEL_STATE_BUILDING,
 19     .initialGold = 20,
 20     .waves[0] = {
 21       .enemyType = ENEMY_TYPE_MINION,
 22       .wave = 0,
 23       .count = 10,
 24       .interval = 2.5f,
 25       .delay = 1.0f,
 26       .spawnPosition = {0, 6},
 27     },
 28     .waves[1] = {
 29       .enemyType = ENEMY_TYPE_MINION,
 30       .wave = 1,
 31       .count = 20,
 32       .interval = 1.5f,
 33       .delay = 1.0f,
 34       .spawnPosition = {0, 6},
 35     },
 36     .waves[2] = {
 37       .enemyType = ENEMY_TYPE_MINION,
 38       .wave = 2,
 39       .count = 30,
 40       .interval = 1.2f,
 41       .delay = 1.0f,
 42       .spawnPosition = {0, 6},
 43     }
 44   },
 45 };
 46 
 47 Level *currentLevel = levels;
 48 
 49 //# Game
 50 
 51 static Model LoadGLBModel(char *filename)
 52 {
 53   Model model = LoadModel(TextFormat("data/%s.glb",filename));
 54   if (model.materialCount > 1)
 55   {
 56     model.materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 57   }
 58   return model;
 59 }
 60 
 61 void LoadAssets()
 62 {
 63   // load a sprite sheet that contains all units
 64   spriteSheet = LoadTexture("data/spritesheet.png");
 65   SetTextureFilter(spriteSheet, TEXTURE_FILTER_BILINEAR);
 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   floorTileAModel = LoadGLBModel("floor-tile-a");
 73   floorTileBModel = LoadGLBModel("floor-tile-b");
 74   grassPatchModel[0] = LoadGLBModel("grass-patch-1");
 75 }
 76 
 77 void InitLevel(Level *level)
 78 {
 79   level->seed = (int)(GetTime() * 100.0f);
 80 
 81   TowerInit();
 82   EnemyInit();
 83   ProjectileInit();
 84   ParticleInit();
 85   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
 86 
 87   level->placementMode = 0;
 88   level->state = LEVEL_STATE_BUILDING;
 89   level->nextState = LEVEL_STATE_NONE;
 90   level->playerGold = level->initialGold;
 91   level->currentWave = 0;
 92 
 93   Camera *camera = &level->camera;
 94   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
 95   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
 96   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
 97   camera->fovy = 10.0f;
 98   camera->projection = CAMERA_ORTHOGRAPHIC;
 99 }
100 
101 void DrawLevelHud(Level *level)
102 {
103   const char *text = TextFormat("Gold: %d", level->playerGold);
104   Font font = GetFontDefault();
105   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
106   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
107 }
108 
109 void DrawLevelReportLostWave(Level *level)
110 {
111   BeginMode3D(level->camera);
112   DrawLevelGround(level);
113   TowerDraw();
114   EnemyDraw();
115   ProjectileDraw();
116   ParticleDraw();
117   guiState.isBlocked = 0;
118   EndMode3D();
119 
120   TowerDrawHealthBars(level->camera);
121 
122   const char *text = "Wave lost";
123   int textWidth = MeasureText(text, 20);
124   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
125 
126   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
127   {
128     level->nextState = LEVEL_STATE_RESET;
129   }
130 }
131 
132 int HasLevelNextWave(Level *level)
133 {
134   for (int i = 0; i < 10; i++)
135   {
136     EnemyWave *wave = &level->waves[i];
137     if (wave->wave == level->currentWave)
138     {
139       return 1;
140     }
141   }
142   return 0;
143 }
144 
145 void DrawLevelReportWonWave(Level *level)
146 {
147   BeginMode3D(level->camera);
148   DrawLevelGround(level);
149   TowerDraw();
150   EnemyDraw();
151   ProjectileDraw();
152   ParticleDraw();
153   guiState.isBlocked = 0;
154   EndMode3D();
155 
156   TowerDrawHealthBars(level->camera);
157 
158   const char *text = "Wave won";
159   int textWidth = MeasureText(text, 20);
160   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
161 
162 
163   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
164   {
165     level->nextState = LEVEL_STATE_RESET;
166   }
167 
168   if (HasLevelNextWave(level))
169   {
170     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
171     {
172       level->nextState = LEVEL_STATE_BUILDING;
173     }
174   }
175   else {
176     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
177     {
178       level->nextState = LEVEL_STATE_WON_LEVEL;
179     }
180   }
181 }
182 
183 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
184 {
185   static ButtonState buttonStates[8] = {0};
186   int cost = GetTowerCosts(towerType);
187   const char *text = TextFormat("%s: %d", name, cost);
188   buttonStates[towerType].isSelected = level->placementMode == towerType;
189   buttonStates[towerType].isDisabled = level->playerGold < cost;
190   if (Button(text, x, y, width, height, &buttonStates[towerType]))
191   {
192     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
193   }
194 }
195 
196 void DrawLevelGround(Level *level)
197 {
198   // draw checkerboard ground pattern
199   for (int x = -5; x <= 5; x += 1)
200   {
201     for (int y = -5; y <= 5; y += 1)
202     {
203       Model *model = (x + y) % 2 == 0 ? &floorTileAModel : &floorTileBModel;
204       DrawModel(*model, (Vector3){x, 0.0f, y}, 1.0f, WHITE);
205     }
206   }
207 
208   // draw grass patches around the edges
209   const int layerCount = 2;
210   unsigned int oldSeed = GetRandomValue(0, 0xfffffff);
211   SetRandomSeed(level->seed);
212   for (int layer = 0; layer < layerCount; layer++)
213   {
214     int layerPos = 6 + layer;
215     for (int x = -6 + layer; x <= 6 + layer; x += 1)
216     {
217       DrawModel(grassPatchModel[0], 
218         (Vector3){x + (float)GetRandomValue(-25,25) / 100.0f, 0.0f, -layerPos + (float)GetRandomValue(-25,25) / 100.0f}, 
219         1.0f, WHITE);
220       DrawModel(grassPatchModel[0], 
221         (Vector3){x + (float)GetRandomValue(-25,25) / 100.0f, 0.0f, layerPos + (float)GetRandomValue(-25,25) / 100.0f}, 
222         1.0f, WHITE);
223     }
224 
225     for (int z = -5 + layer; z <= 5 + layer; z += 1)
226     {
227       DrawModel(grassPatchModel[0], 
228         (Vector3){-layerPos + (float)GetRandomValue(-25,25) / 100.0f, 0.0f, z + (float)GetRandomValue(-25,25) / 100.0f}, 
229         1.0f, WHITE);
230       DrawModel(grassPatchModel[0], 
231         (Vector3){layerPos + (float)GetRandomValue(-25,25) / 100.0f, 0.0f, z + (float)GetRandomValue(-25,25) / 100.0f}, 
232         1.0f, WHITE);
233     }
234   }
235   SetRandomSeed(oldSeed);
236 }
237 
238 void DrawLevelBuildingState(Level *level)
239 {
240   BeginMode3D(level->camera);
241   DrawLevelGround(level);
242   TowerDraw();
243   EnemyDraw();
244   ProjectileDraw();
245   ParticleDraw();
246 
247   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
248   float planeDistance = ray.position.y / -ray.direction.y;
249   float planeX = ray.direction.x * planeDistance + ray.position.x;
250   float planeY = ray.direction.z * planeDistance + ray.position.z;
251   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
252   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
253   if (level->placementMode && !guiState.isBlocked && mapX >= -5 && mapX <= 5 && mapY >= -5 && mapY <= 5)
254   {
255     DrawCubeWires((Vector3){mapX, 0.2f, mapY}, 1.0f, 0.4f, 1.0f, RED);
256     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
257     {
258       if (TowerTryAdd(level->placementMode, mapX, mapY))
259       {
260         level->playerGold -= GetTowerCosts(level->placementMode);
261         level->placementMode = TOWER_TYPE_NONE;
262       }
263     }
264   }
265 
266   guiState.isBlocked = 0;
267 
268   EndMode3D();
269 
270   TowerDrawHealthBars(level->camera);
271 
272   static ButtonState buildWallButtonState = {0};
273   static ButtonState buildGunButtonState = {0};
274   buildWallButtonState.isSelected = level->placementMode == TOWER_TYPE_WALL;
275   buildGunButtonState.isSelected = level->placementMode == TOWER_TYPE_GUN;
276 
277   DrawBuildingBuildButton(level, 10, 10, 110, 30, TOWER_TYPE_WALL, "Wall");
278   DrawBuildingBuildButton(level, 10, 50, 110, 30, TOWER_TYPE_GUN, "Archer");
279 
280   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
281   {
282     level->nextState = LEVEL_STATE_RESET;
283   }
284   
285   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
286   {
287     level->nextState = LEVEL_STATE_BATTLE;
288   }
289 
290   const char *text = "Building phase";
291   int textWidth = MeasureText(text, 20);
292   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
293 }
294 
295 void InitBattleStateConditions(Level *level)
296 {
297   level->state = LEVEL_STATE_BATTLE;
298   level->nextState = LEVEL_STATE_NONE;
299   level->waveEndTimer = 0.0f;
300   for (int i = 0; i < 10; i++)
301   {
302     EnemyWave *wave = &level->waves[i];
303     wave->spawned = 0;
304     wave->timeToSpawnNext = wave->delay;
305   }
306 }
307 
308 void DrawLevelBattleState(Level *level)
309 {
310   BeginMode3D(level->camera);
311   DrawLevelGround(level);
312   TowerDraw();
313   EnemyDraw();
314   ProjectileDraw();
315   ParticleDraw();
316   guiState.isBlocked = 0;
317   EndMode3D();
318 
319   EnemyDrawHealthbars(level->camera);
320   TowerDrawHealthBars(level->camera);
321 
322   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
323   {
324     level->nextState = LEVEL_STATE_RESET;
325   }
326 
327   int maxCount = 0;
328   int remainingCount = 0;
329   for (int i = 0; i < 10; i++)
330   {
331     EnemyWave *wave = &level->waves[i];
332     if (wave->wave != level->currentWave)
333     {
334       continue;
335     }
336     maxCount += wave->count;
337     remainingCount += wave->count - wave->spawned;
338   }
339   int aliveCount = EnemyCount();
340   remainingCount += aliveCount;
341 
342   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
343   int textWidth = MeasureText(text, 20);
344   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
345 }
346 
347 void DrawLevel(Level *level)
348 {
349   switch (level->state)
350   {
351     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
352     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
353     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
354     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
355     default: break;
356   }
357 
358   DrawLevelHud(level);
359 }
360 
361 void UpdateLevel(Level *level)
362 {
363   if (level->state == LEVEL_STATE_BATTLE)
364   {
365     int activeWaves = 0;
366     for (int i = 0; i < 10; i++)
367     {
368       EnemyWave *wave = &level->waves[i];
369       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
370       {
371         continue;
372       }
373       activeWaves++;
374       wave->timeToSpawnNext -= gameTime.deltaTime;
375       if (wave->timeToSpawnNext <= 0.0f)
376       {
377         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
378         if (enemy)
379         {
380           wave->timeToSpawnNext = wave->interval;
381           wave->spawned++;
382         }
383       }
384     }
385     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
386       level->waveEndTimer += gameTime.deltaTime;
387       if (level->waveEndTimer >= 2.0f)
388       {
389         level->nextState = LEVEL_STATE_LOST_WAVE;
390       }
391     }
392     else if (activeWaves == 0 && EnemyCount() == 0)
393     {
394       level->waveEndTimer += gameTime.deltaTime;
395       if (level->waveEndTimer >= 2.0f)
396       {
397         level->nextState = LEVEL_STATE_WON_WAVE;
398       }
399     }
400   }
401 
402   PathFindingMapUpdate();
403   EnemyUpdate();
404   TowerUpdate();
405   ProjectileUpdate();
406   ParticleUpdate();
407 
408   if (level->nextState == LEVEL_STATE_RESET)
409   {
410     InitLevel(level);
411   }
412   
413   if (level->nextState == LEVEL_STATE_BATTLE)
414   {
415     InitBattleStateConditions(level);
416   }
417   
418   if (level->nextState == LEVEL_STATE_WON_WAVE)
419   {
420     level->currentWave++;
421     level->state = LEVEL_STATE_WON_WAVE;
422   }
423   
424   if (level->nextState == LEVEL_STATE_LOST_WAVE)
425   {
426     level->state = LEVEL_STATE_LOST_WAVE;
427   }
428 
429   if (level->nextState == LEVEL_STATE_BUILDING)
430   {
431     level->state = LEVEL_STATE_BUILDING;
432   }
433 
434   if (level->nextState == LEVEL_STATE_WON_LEVEL)
435   {
436     // make something of this later
437     InitLevel(level);
438   }
439 
440   level->nextState = LEVEL_STATE_NONE;
441 }
442 
443 float nextSpawnTime = 0.0f;
444 
445 void ResetGame()
446 {
447   InitLevel(currentLevel);
448 }
449 
450 void InitGame()
451 {
452   TowerInit();
453   EnemyInit();
454   ProjectileInit();
455   ParticleInit();
456   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
457 
458   currentLevel = levels;
459   InitLevel(currentLevel);
460 }
461 
462 //# Immediate GUI functions
463 
464 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth)
465 {
466   const float healthBarHeight = 6.0f;
467   const float healthBarOffset = 15.0f;
468   const float inset = 2.0f;
469   const float innerWidth = healthBarWidth - inset * 2;
470   const float innerHeight = healthBarHeight - inset * 2;
471 
472   Vector2 screenPos = GetWorldToScreen(position, camera);
473   float centerX = screenPos.x - healthBarWidth * 0.5f;
474   float topY = screenPos.y - healthBarOffset;
475   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
476   float healthWidth = innerWidth * healthRatio;
477   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
478 }
479 
480 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
481 {
482   Rectangle bounds = {x, y, width, height};
483   int isPressed = 0;
484   int isSelected = state && state->isSelected;
485   int isDisabled = state && state->isDisabled;
486   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !isDisabled)
487   {
488     Color color = isSelected ? DARKGRAY : GRAY;
489     DrawRectangle(x, y, width, height, color);
490     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
491     {
492       isPressed = 1;
493     }
494     guiState.isBlocked = 1;
495   }
496   else
497   {
498     Color color = isSelected ? WHITE : LIGHTGRAY;
499     DrawRectangle(x, y, width, height, color);
500   }
501   Font font = GetFontDefault();
502   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
503   Color textColor = isDisabled ? GRAY : BLACK;
504   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
505   return isPressed;
506 }
507 
508 //# Main game loop
509 
510 void GameUpdate()
511 {
512   float dt = GetFrameTime();
513   // cap maximum delta time to 0.1 seconds to prevent large time steps
514   if (dt > 0.1f) dt = 0.1f;
515   gameTime.time += dt;
516   gameTime.deltaTime = dt;
517 
518   UpdateLevel(currentLevel);
519 }
520 
521 int main(void)
522 {
523   int screenWidth, screenHeight;
524   GetPreferredSize(&screenWidth, &screenHeight);
525   InitWindow(screenWidth, screenHeight, "Tower defense");
526   SetTargetFPS(30);
527 
528   LoadAssets();
529   InitGame();
530 
531   while (!WindowShouldClose())
532   {
533     if (IsPaused()) {
534       // canvas is not visible in browser - do nothing
535       continue;
536     }
537 
538     BeginDrawing();
539     ClearBackground((Color){0x4E, 0x63, 0x26, 0xFF});
540 
541     GameUpdate();
542     DrawLevel(currentLevel);
543 
544     EndDrawing();
545   }
546 
547   CloseWindow();
548 
549   return 0;
550 }
  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   int seed;
 86   LevelState state;
 87   LevelState nextState;
 88   Camera3D camera;
 89   int placementMode;
 90 
 91   int initialGold;
 92   int playerGold;
 93 
 94   EnemyWave waves[10];
 95   int currentWave;
 96   float waveEndTimer;
 97 } Level;
 98 
 99 typedef struct DeltaSrc
100 {
101   char x, y;
102 } DeltaSrc;
103 
104 typedef struct PathfindingMap
105 {
106   int width, height;
107   float scale;
108   float *distances;
109   long *towerIndex; 
110   DeltaSrc *deltaSrc;
111   float maxDistance;
112   Matrix toMapSpace;
113   Matrix toWorldSpace;
114 } PathfindingMap;
115 
116 // when we execute the pathfinding algorithm, we need to store the active nodes
117 // in a queue. Each node has a position, a distance from the start, and the
118 // position of the node that we came from.
119 typedef struct PathfindingNode
120 {
121   int16_t x, y, fromX, fromY;
122   float distance;
123 } PathfindingNode;
124 
125 typedef struct EnemyId
126 {
127   uint16_t index;
128   uint16_t generation;
129 } EnemyId;
130 
131 typedef struct EnemyClassConfig
132 {
133   float speed;
134   float health;
135   float radius;
136   float maxAcceleration;
137   float requiredContactTime;
138   float explosionDamage;
139   float explosionRange;
140   float explosionPushbackPower;
141   int goldValue;
142 } EnemyClassConfig;
143 
144 typedef struct Enemy
145 {
146   int16_t currentX, currentY;
147   int16_t nextX, nextY;
148   Vector2 simPosition;
149   Vector2 simVelocity;
150   uint16_t generation;
151   float walkedDistance;
152   float startMovingTime;
153   float damage, futureDamage;
154   float contactTime;
155   uint8_t enemyType;
156   uint8_t movePathCount;
157   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
158 } Enemy;
159 
160 // a unit that uses sprites to be drawn
161 #define SPRITE_UNIT_PHASE_WEAPON_IDLE 0
162 #define SPRITE_UNIT_PHASE_WEAPON_COOLDOWN 1
163 typedef struct SpriteUnit
164 {
165   Rectangle srcRect;
166   Vector2 offset;
167   int frameCount;
168   float frameDuration;
169   Rectangle srcWeaponIdleRect;
170   Vector2 srcWeaponIdleOffset;
171   Rectangle srcWeaponCooldownRect;
172   Vector2 srcWeaponCooldownOffset;
173 } SpriteUnit;
174 
175 #define PROJECTILE_MAX_COUNT 1200
176 #define PROJECTILE_TYPE_NONE 0
177 #define PROJECTILE_TYPE_ARROW 1
178 
179 typedef struct Projectile
180 {
181   uint8_t projectileType;
182   float shootTime;
183   float arrivalTime;
184   float distance;
185   float damage;
186   Vector3 position;
187   Vector3 target;
188   Vector3 directionNormal;
189   EnemyId targetEnemy;
190 } Projectile;
191 
192 //# Function declarations
193 float TowerGetMaxHealth(Tower *tower);
194 int Button(const char *text, int x, int y, int width, int height, ButtonState *state);
195 int EnemyAddDamage(Enemy *enemy, float damage);
196 
197 //# Enemy functions
198 void EnemyInit();
199 void EnemyDraw();
200 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource);
201 void EnemyUpdate();
202 float EnemyGetCurrentMaxSpeed(Enemy *enemy);
203 float EnemyGetMaxHealth(Enemy *enemy);
204 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY);
205 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount);
206 EnemyId EnemyGetId(Enemy *enemy);
207 Enemy *EnemyTryResolve(EnemyId enemyId);
208 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY);
209 int EnemyAddDamage(Enemy *enemy, float damage);
210 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range);
211 int EnemyCount();
212 void EnemyDrawHealthbars(Camera3D camera);
213 
214 //# Tower functions
215 void TowerInit();
216 Tower *TowerGetAt(int16_t x, int16_t y);
217 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y);
218 Tower *GetTowerByType(uint8_t towerType);
219 int GetTowerCosts(uint8_t towerType);
220 float TowerGetMaxHealth(Tower *tower);
221 void TowerDraw();
222 void TowerUpdate();
223 void TowerDrawHealthBars(Camera3D camera);
224 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase);
225 
226 //# Particles
227 void ParticleInit();
228 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime);
229 void ParticleUpdate();
230 void ParticleDraw();
231 
232 //# Projectiles
233 void ProjectileInit();
234 void ProjectileDraw();
235 void ProjectileUpdate();
236 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, float damage);
237 
238 //# Pathfinding map
239 void PathfindingMapInit(int width, int height, Vector3 translate, float scale);
240 float PathFindingGetDistance(int mapX, int mapY);
241 Vector2 PathFindingGetGradient(Vector3 world);
242 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY);
243 void PathFindingMapUpdate();
244 void PathFindingMapDraw();
245 
246 //# UI
247 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth);
248 
249 //# Level
250 void DrawLevelGround(Level *level);
251 
252 //# variables
253 extern Level *currentLevel;
254 extern Enemy enemies[ENEMY_MAX_COUNT];
255 extern int enemyCount;
256 extern EnemyClassConfig enemyClassConfigs[];
257 
258 extern GUIState guiState;
259 extern GameTime gameTime;
260 extern Tower towers[TOWER_MAX_COUNT];
261 extern int towerCount;
262 
263 extern Texture2D palette, spriteSheet;
264 
265 #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, 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 "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 
  9 // definition of our archer unit
 10 SpriteUnit archerUnit = {
 11     .srcRect = {0, 0, 16, 16},
 12     .offset = {7, 1},
 13     .frameCount = 1,
 14     .frameDuration = 0.0f,
 15     .srcWeaponIdleRect = {16, 0, 6, 16},
 16     .srcWeaponIdleOffset = {8, 0},
 17     .srcWeaponCooldownRect = {22, 0, 11, 16},
 18     .srcWeaponCooldownOffset = {10, 0},
 19 };
 20 
 21 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
 22 {
 23   float xScale = flip ? -1.0f : 1.0f;
 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 * xScale };
 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   if (flip)
 40   {
 41     srcRect.x += srcRect.width;
 42     srcRect.width = -srcRect.width;
 43   }
 44   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 45 
 46   if (phase == SPRITE_UNIT_PHASE_WEAPON_COOLDOWN && unit.srcWeaponCooldownRect.width > 0)
 47   {
 48     offset = (Vector2){ unit.srcWeaponCooldownOffset.x / 16.0f * size, unit.srcWeaponCooldownOffset.y / 16.0f * size };
 49     scale = (Vector2){ unit.srcWeaponCooldownRect.width / 16.0f * size, unit.srcWeaponCooldownRect.height / 16.0f * size };
 50     srcRect = unit.srcWeaponCooldownRect;
 51     if (flip)
 52     {
 53       // position.x = flip * scale.x * 0.5f;
 54       srcRect.x += srcRect.width;
 55       srcRect.width = -srcRect.width;
 56       offset.x = scale.x - offset.x;
 57     }
 58     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 59   }
 60   else if (phase == SPRITE_UNIT_PHASE_WEAPON_IDLE && unit.srcWeaponIdleRect.width > 0)
 61   {
 62     offset = (Vector2){ unit.srcWeaponIdleOffset.x / 16.0f * size, unit.srcWeaponIdleOffset.y / 16.0f * size };
 63     scale = (Vector2){ unit.srcWeaponIdleRect.width / 16.0f * size, unit.srcWeaponIdleRect.height / 16.0f * size };
 64     srcRect = unit.srcWeaponIdleRect;
 65     if (flip)
 66     {
 67       // position.x = flip * scale.x * 0.5f;
 68       srcRect.x += srcRect.width;
 69       srcRect.width = -srcRect.width;
 70       offset.x = scale.x - offset.x;
 71     }
 72     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 73   }
 74 }
 75 
 76 void TowerInit()
 77 {
 78   for (int i = 0; i < TOWER_MAX_COUNT; i++)
 79   {
 80     towers[i] = (Tower){0};
 81   }
 82   towerCount = 0;
 83 
 84   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
 85   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
 86 
 87   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
 88   {
 89     if (towerModels[i].materials)
 90     {
 91       // assign the palette texture to the material of the model (0 is not used afaik)
 92       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 93     }
 94   }
 95 }
 96 
 97 static void TowerGunUpdate(Tower *tower)
 98 {
 99   if (tower->cooldown <= 0)
100   {
101     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
102     if (enemy)
103     {
104       tower->cooldown = 0.5f;
105       // shoot the enemy; determine future position of the enemy
106       float bulletSpeed = 4.0f;
107       float bulletDamage = 3.0f;
108       Vector2 velocity = enemy->simVelocity;
109       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
110       Vector2 towerPosition = {tower->x, tower->y};
111       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
112       for (int i = 0; i < 8; i++) {
113         velocity = enemy->simVelocity;
114         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
115         float distance = Vector2Distance(towerPosition, futurePosition);
116         float eta2 = distance / bulletSpeed;
117         if (fabs(eta - eta2) < 0.01f) {
118           break;
119         }
120         eta = (eta2 + eta) * 0.5f;
121       }
122       ProjectileTryAdd(PROJECTILE_TYPE_ARROW, enemy, 
123         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
124         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
125         bulletSpeed, bulletDamage);
126       enemy->futureDamage += bulletDamage;
127       tower->lastTargetPosition = futurePosition;
128     }
129   }
130   else
131   {
132     tower->cooldown -= gameTime.deltaTime;
133   }
134 }
135 
136 Tower *TowerGetAt(int16_t x, int16_t y)
137 {
138   for (int i = 0; i < towerCount; i++)
139   {
140     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
141     {
142       return &towers[i];
143     }
144   }
145   return 0;
146 }
147 
148 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
149 {
150   if (towerCount >= TOWER_MAX_COUNT)
151   {
152     return 0;
153   }
154 
155   Tower *tower = TowerGetAt(x, y);
156   if (tower)
157   {
158     return 0;
159   }
160 
161   tower = &towers[towerCount++];
162   tower->x = x;
163   tower->y = y;
164   tower->towerType = towerType;
165   tower->cooldown = 0.0f;
166   tower->damage = 0.0f;
167   return tower;
168 }
169 
170 Tower *GetTowerByType(uint8_t towerType)
171 {
172   for (int i = 0; i < towerCount; i++)
173   {
174     if (towers[i].towerType == towerType)
175     {
176       return &towers[i];
177     }
178   }
179   return 0;
180 }
181 
182 int GetTowerCosts(uint8_t towerType)
183 {
184   switch (towerType)
185   {
186   case TOWER_TYPE_BASE:
187     return 0;
188   case TOWER_TYPE_GUN:
189     return 6;
190   case TOWER_TYPE_WALL:
191     return 2;
192   }
193   return 0;
194 }
195 
196 float TowerGetMaxHealth(Tower *tower)
197 {
198   switch (tower->towerType)
199   {
200   case TOWER_TYPE_BASE:
201     return 10.0f;
202   case TOWER_TYPE_GUN:
203     return 3.0f;
204   case TOWER_TYPE_WALL:
205     return 5.0f;
206   }
207   return 0.0f;
208 }
209 
210 void TowerDraw()
211 {
212   for (int i = 0; i < towerCount; i++)
213   {
214     Tower tower = towers[i];
215     if (tower.towerType == TOWER_TYPE_NONE)
216     {
217       continue;
218     }
219 
220     switch (tower.towerType)
221     {
222     case TOWER_TYPE_GUN:
223       {
224         Vector2 screenPosTower = GetWorldToScreen((Vector3){tower.x, 0.0f, tower.y}, currentLevel->camera);
225         Vector2 screenPosTarget = GetWorldToScreen((Vector3){tower.lastTargetPosition.x, 0.0f, tower.lastTargetPosition.y}, currentLevel->camera);
226         DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
227         DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0, screenPosTarget.x > screenPosTower.x, 
228           tower.cooldown > 0.2f ? SPRITE_UNIT_PHASE_WEAPON_COOLDOWN : SPRITE_UNIT_PHASE_WEAPON_IDLE);
229       }
230       break;
231     default:
232       if (towerModels[tower.towerType].materials)
233       {
234         DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
235       } else {
236         DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
237       }
238       break;
239     }
240   }
241 }
242 
243 void TowerUpdate()
244 {
245   for (int i = 0; i < towerCount; i++)
246   {
247     Tower *tower = &towers[i];
248     switch (tower->towerType)
249     {
250     case TOWER_TYPE_GUN:
251       TowerGunUpdate(tower);
252       break;
253     }
254   }
255 }
256 
257 void TowerDrawHealthBars(Camera3D camera)
258 {
259   for (int i = 0; i < towerCount; i++)
260   {
261     Tower *tower = &towers[i];
262     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
263     {
264       continue;
265     }
266     
267     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
268     float maxHealth = TowerGetMaxHealth(tower);
269     float health = maxHealth - tower->damage;
270     float healthRatio = health / maxHealth;
271     
272     DrawHealthBar(camera, position, healthRatio, GREEN, 35.0f);
273   }
274 }
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif
In the InitLevel function, we now set the seed to the current time. In this case, using the time as a seed is desired: We want the game to always look different when we restart the level. So this is a conscious decision to use the time as a seed.
So let's continue: Another thing we'd want is to rotate the objects randomly, as it still looks quite orderly. We also should add a random function that returns a random value within a certain range for more convenient use. So let's add some rotation to the objects:
  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 Model floorTileAModel = {0};
 11 Model floorTileBModel = {0};
 12 Model grassPatchModel[1] = {0};
 13 
 14 Texture2D palette, spriteSheet;
 15 
 16 Level levels[] = {
 17   [0] = {
 18     .state = LEVEL_STATE_BUILDING,
 19     .initialGold = 20,
 20     .waves[0] = {
 21       .enemyType = ENEMY_TYPE_MINION,
 22       .wave = 0,
 23       .count = 10,
 24       .interval = 2.5f,
 25       .delay = 1.0f,
 26       .spawnPosition = {0, 6},
 27     },
 28     .waves[1] = {
 29       .enemyType = ENEMY_TYPE_MINION,
 30       .wave = 1,
 31       .count = 20,
 32       .interval = 1.5f,
 33       .delay = 1.0f,
 34       .spawnPosition = {0, 6},
 35     },
 36     .waves[2] = {
 37       .enemyType = ENEMY_TYPE_MINION,
 38       .wave = 2,
 39       .count = 30,
 40       .interval = 1.2f,
 41       .delay = 1.0f,
 42       .spawnPosition = {0, 6},
 43     }
 44   },
 45 };
 46 
 47 Level *currentLevel = levels;
 48 
 49 //# Game
 50 
 51 static Model LoadGLBModel(char *filename)
 52 {
 53   Model model = LoadModel(TextFormat("data/%s.glb",filename));
 54   if (model.materialCount > 1)
 55   {
 56     model.materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 57   }
 58   return model;
 59 }
 60 
 61 void LoadAssets()
 62 {
 63   // load a sprite sheet that contains all units
 64   spriteSheet = LoadTexture("data/spritesheet.png");
 65   SetTextureFilter(spriteSheet, TEXTURE_FILTER_BILINEAR);
 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   floorTileAModel = LoadGLBModel("floor-tile-a");
 73   floorTileBModel = LoadGLBModel("floor-tile-b");
 74   grassPatchModel[0] = LoadGLBModel("grass-patch-1");
 75 }
 76 
 77 void InitLevel(Level *level)
 78 {
 79   level->seed = (int)(GetTime() * 100.0f);
 80 
 81   TowerInit();
 82   EnemyInit();
 83   ProjectileInit();
 84   ParticleInit();
 85   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
 86 
 87   level->placementMode = 0;
 88   level->state = LEVEL_STATE_BUILDING;
 89   level->nextState = LEVEL_STATE_NONE;
 90   level->playerGold = level->initialGold;
 91   level->currentWave = 0;
 92 
 93   Camera *camera = &level->camera;
 94   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
 95   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
 96   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
 97   camera->fovy = 10.0f;
 98   camera->projection = CAMERA_ORTHOGRAPHIC;
 99 }
100 
101 void DrawLevelHud(Level *level)
102 {
103   const char *text = TextFormat("Gold: %d", level->playerGold);
104   Font font = GetFontDefault();
105   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
106   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
107 }
108 
109 void DrawLevelReportLostWave(Level *level)
110 {
111   BeginMode3D(level->camera);
112   DrawLevelGround(level);
113   TowerDraw();
114   EnemyDraw();
115   ProjectileDraw();
116   ParticleDraw();
117   guiState.isBlocked = 0;
118   EndMode3D();
119 
120   TowerDrawHealthBars(level->camera);
121 
122   const char *text = "Wave lost";
123   int textWidth = MeasureText(text, 20);
124   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
125 
126   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
127   {
128     level->nextState = LEVEL_STATE_RESET;
129   }
130 }
131 
132 int HasLevelNextWave(Level *level)
133 {
134   for (int i = 0; i < 10; i++)
135   {
136     EnemyWave *wave = &level->waves[i];
137     if (wave->wave == level->currentWave)
138     {
139       return 1;
140     }
141   }
142   return 0;
143 }
144 
145 void DrawLevelReportWonWave(Level *level)
146 {
147   BeginMode3D(level->camera);
148   DrawLevelGround(level);
149   TowerDraw();
150   EnemyDraw();
151   ProjectileDraw();
152   ParticleDraw();
153   guiState.isBlocked = 0;
154   EndMode3D();
155 
156   TowerDrawHealthBars(level->camera);
157 
158   const char *text = "Wave won";
159   int textWidth = MeasureText(text, 20);
160   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
161 
162 
163   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
164   {
165     level->nextState = LEVEL_STATE_RESET;
166   }
167 
168   if (HasLevelNextWave(level))
169   {
170     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
171     {
172       level->nextState = LEVEL_STATE_BUILDING;
173     }
174   }
175   else {
176     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
177     {
178       level->nextState = LEVEL_STATE_WON_LEVEL;
179     }
180   }
181 }
182 
183 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
184 {
185   static ButtonState buttonStates[8] = {0};
186   int cost = GetTowerCosts(towerType);
187   const char *text = TextFormat("%s: %d", name, cost);
188   buttonStates[towerType].isSelected = level->placementMode == towerType;
189   buttonStates[towerType].isDisabled = level->playerGold < cost;
190   if (Button(text, x, y, width, height, &buttonStates[towerType]))
191   {
192     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
193   }
194 }
195 
196 float GetRandomFloat(float min, float max)
197 {
198   int random = GetRandomValue(0, 0xfffffff);
199   return ((float)random / (float)0xfffffff) * (max - min) + min;
200 }
201 
202 void DrawLevelGround(Level *level)
203 {
204   // draw checkerboard ground pattern
205   for (int x = -5; x <= 5; x += 1)
206   {
207     for (int y = -5; y <= 5; y += 1)
208     {
209       Model *model = (x + y) % 2 == 0 ? &floorTileAModel : &floorTileBModel;
210       DrawModel(*model, (Vector3){x, 0.0f, y}, 1.0f, WHITE);
211     }
212   }
213 
214   // draw grass patches around the edges
215   const Vector3 up = {0, 1, 0};
216   const int layerCount = 2;
217   unsigned int oldSeed = GetRandomValue(0, 0xfffffff);
218   const float wiggle = 0.25f;
219   SetRandomSeed(level->seed);
220   for (int layer = 0; layer < layerCount; layer++)
221   {
222     int layerPos = 6 + layer;
223     for (int x = -6 + layer; x <= 6 + layer; x += 1)
224     {
225       DrawModelEx(grassPatchModel[0], 
226         (Vector3){x + GetRandomFloat(-wiggle, wiggle), 0.0f, -layerPos + GetRandomFloat(-wiggle, wiggle)}, 
227         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
228       DrawModelEx(grassPatchModel[0], 
229         (Vector3){x + GetRandomFloat(-wiggle, wiggle), 0.0f, layerPos + GetRandomFloat(-wiggle, wiggle)}, 
230         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
231     }
232 
233     for (int z = -5 + layer; z <= 5 + layer; z += 1)
234     {
235       DrawModelEx(grassPatchModel[0], 
236         (Vector3){-layerPos + GetRandomFloat(-wiggle, wiggle), 0.0f, z + GetRandomFloat(-wiggle, wiggle)}, 
237         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
238       DrawModelEx(grassPatchModel[0], 
239         (Vector3){layerPos + GetRandomFloat(-wiggle, wiggle), 0.0f, z + GetRandomFloat(-wiggle, wiggle)}, 
240         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
241     }
242   }
243   SetRandomSeed(oldSeed);
244 }
245 
246 void DrawLevelBuildingState(Level *level)
247 {
248   BeginMode3D(level->camera);
249   DrawLevelGround(level);
250   TowerDraw();
251   EnemyDraw();
252   ProjectileDraw();
253   ParticleDraw();
254 
255   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
256   float planeDistance = ray.position.y / -ray.direction.y;
257   float planeX = ray.direction.x * planeDistance + ray.position.x;
258   float planeY = ray.direction.z * planeDistance + ray.position.z;
259   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
260   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
261   if (level->placementMode && !guiState.isBlocked && mapX >= -5 && mapX <= 5 && mapY >= -5 && mapY <= 5)
262   {
263     DrawCubeWires((Vector3){mapX, 0.2f, mapY}, 1.0f, 0.4f, 1.0f, RED);
264     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
265     {
266       if (TowerTryAdd(level->placementMode, mapX, mapY))
267       {
268         level->playerGold -= GetTowerCosts(level->placementMode);
269         level->placementMode = TOWER_TYPE_NONE;
270       }
271     }
272   }
273 
274   guiState.isBlocked = 0;
275 
276   EndMode3D();
277 
278   TowerDrawHealthBars(level->camera);
279 
280   static ButtonState buildWallButtonState = {0};
281   static ButtonState buildGunButtonState = {0};
282   buildWallButtonState.isSelected = level->placementMode == TOWER_TYPE_WALL;
283   buildGunButtonState.isSelected = level->placementMode == TOWER_TYPE_GUN;
284 
285   DrawBuildingBuildButton(level, 10, 10, 110, 30, TOWER_TYPE_WALL, "Wall");
286   DrawBuildingBuildButton(level, 10, 50, 110, 30, TOWER_TYPE_GUN, "Archer");
287 
288   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
289   {
290     level->nextState = LEVEL_STATE_RESET;
291   }
292   
293   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
294   {
295     level->nextState = LEVEL_STATE_BATTLE;
296   }
297 
298   const char *text = "Building phase";
299   int textWidth = MeasureText(text, 20);
300   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
301 }
302 
303 void InitBattleStateConditions(Level *level)
304 {
305   level->state = LEVEL_STATE_BATTLE;
306   level->nextState = LEVEL_STATE_NONE;
307   level->waveEndTimer = 0.0f;
308   for (int i = 0; i < 10; i++)
309   {
310     EnemyWave *wave = &level->waves[i];
311     wave->spawned = 0;
312     wave->timeToSpawnNext = wave->delay;
313   }
314 }
315 
316 void DrawLevelBattleState(Level *level)
317 {
318   BeginMode3D(level->camera);
319   DrawLevelGround(level);
320   TowerDraw();
321   EnemyDraw();
322   ProjectileDraw();
323   ParticleDraw();
324   guiState.isBlocked = 0;
325   EndMode3D();
326 
327   EnemyDrawHealthbars(level->camera);
328   TowerDrawHealthBars(level->camera);
329 
330   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
331   {
332     level->nextState = LEVEL_STATE_RESET;
333   }
334 
335   int maxCount = 0;
336   int remainingCount = 0;
337   for (int i = 0; i < 10; i++)
338   {
339     EnemyWave *wave = &level->waves[i];
340     if (wave->wave != level->currentWave)
341     {
342       continue;
343     }
344     maxCount += wave->count;
345     remainingCount += wave->count - wave->spawned;
346   }
347   int aliveCount = EnemyCount();
348   remainingCount += aliveCount;
349 
350   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
351   int textWidth = MeasureText(text, 20);
352   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
353 }
354 
355 void DrawLevel(Level *level)
356 {
357   switch (level->state)
358   {
359     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
360     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
361     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
362     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
363     default: break;
364   }
365 
366   DrawLevelHud(level);
367 }
368 
369 void UpdateLevel(Level *level)
370 {
371   if (level->state == LEVEL_STATE_BATTLE)
372   {
373     int activeWaves = 0;
374     for (int i = 0; i < 10; i++)
375     {
376       EnemyWave *wave = &level->waves[i];
377       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
378       {
379         continue;
380       }
381       activeWaves++;
382       wave->timeToSpawnNext -= gameTime.deltaTime;
383       if (wave->timeToSpawnNext <= 0.0f)
384       {
385         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
386         if (enemy)
387         {
388           wave->timeToSpawnNext = wave->interval;
389           wave->spawned++;
390         }
391       }
392     }
393     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
394       level->waveEndTimer += gameTime.deltaTime;
395       if (level->waveEndTimer >= 2.0f)
396       {
397         level->nextState = LEVEL_STATE_LOST_WAVE;
398       }
399     }
400     else if (activeWaves == 0 && EnemyCount() == 0)
401     {
402       level->waveEndTimer += gameTime.deltaTime;
403       if (level->waveEndTimer >= 2.0f)
404       {
405         level->nextState = LEVEL_STATE_WON_WAVE;
406       }
407     }
408   }
409 
410   PathFindingMapUpdate();
411   EnemyUpdate();
412   TowerUpdate();
413   ProjectileUpdate();
414   ParticleUpdate();
415 
416   if (level->nextState == LEVEL_STATE_RESET)
417   {
418     InitLevel(level);
419   }
420   
421   if (level->nextState == LEVEL_STATE_BATTLE)
422   {
423     InitBattleStateConditions(level);
424   }
425   
426   if (level->nextState == LEVEL_STATE_WON_WAVE)
427   {
428     level->currentWave++;
429     level->state = LEVEL_STATE_WON_WAVE;
430   }
431   
432   if (level->nextState == LEVEL_STATE_LOST_WAVE)
433   {
434     level->state = LEVEL_STATE_LOST_WAVE;
435   }
436 
437   if (level->nextState == LEVEL_STATE_BUILDING)
438   {
439     level->state = LEVEL_STATE_BUILDING;
440   }
441 
442   if (level->nextState == LEVEL_STATE_WON_LEVEL)
443   {
444     // make something of this later
445     InitLevel(level);
446   }
447 
448   level->nextState = LEVEL_STATE_NONE;
449 }
450 
451 float nextSpawnTime = 0.0f;
452 
453 void ResetGame()
454 {
455   InitLevel(currentLevel);
456 }
457 
458 void InitGame()
459 {
460   TowerInit();
461   EnemyInit();
462   ProjectileInit();
463   ParticleInit();
464   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
465 
466   currentLevel = levels;
467   InitLevel(currentLevel);
468 }
469 
470 //# Immediate GUI functions
471 
472 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth)
473 {
474   const float healthBarHeight = 6.0f;
475   const float healthBarOffset = 15.0f;
476   const float inset = 2.0f;
477   const float innerWidth = healthBarWidth - inset * 2;
478   const float innerHeight = healthBarHeight - inset * 2;
479 
480   Vector2 screenPos = GetWorldToScreen(position, camera);
481   float centerX = screenPos.x - healthBarWidth * 0.5f;
482   float topY = screenPos.y - healthBarOffset;
483   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
484   float healthWidth = innerWidth * healthRatio;
485   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
486 }
487 
488 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
489 {
490   Rectangle bounds = {x, y, width, height};
491   int isPressed = 0;
492   int isSelected = state && state->isSelected;
493   int isDisabled = state && state->isDisabled;
494   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !isDisabled)
495   {
496     Color color = isSelected ? DARKGRAY : GRAY;
497     DrawRectangle(x, y, width, height, color);
498     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
499     {
500       isPressed = 1;
501     }
502     guiState.isBlocked = 1;
503   }
504   else
505   {
506     Color color = isSelected ? WHITE : LIGHTGRAY;
507     DrawRectangle(x, y, width, height, color);
508   }
509   Font font = GetFontDefault();
510   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
511   Color textColor = isDisabled ? GRAY : BLACK;
512   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
513   return isPressed;
514 }
515 
516 //# Main game loop
517 
518 void GameUpdate()
519 {
520   float dt = GetFrameTime();
521   // cap maximum delta time to 0.1 seconds to prevent large time steps
522   if (dt > 0.1f) dt = 0.1f;
523   gameTime.time += dt;
524   gameTime.deltaTime = dt;
525 
526   UpdateLevel(currentLevel);
527 }
528 
529 int main(void)
530 {
531   int screenWidth, screenHeight;
532   GetPreferredSize(&screenWidth, &screenHeight);
533   InitWindow(screenWidth, screenHeight, "Tower defense");
534   SetTargetFPS(30);
535 
536   LoadAssets();
537   InitGame();
538 
539   while (!WindowShouldClose())
540   {
541     if (IsPaused()) {
542       // canvas is not visible in browser - do nothing
543       continue;
544     }
545 
546     BeginDrawing();
547     ClearBackground((Color){0x4E, 0x63, 0x26, 0xFF});
548 
549     GameUpdate();
550     DrawLevel(currentLevel);
551 
552     EndDrawing();
553   }
554 
555   CloseWindow();
556 
557   return 0;
558 }
  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   int seed;
 86   LevelState state;
 87   LevelState nextState;
 88   Camera3D camera;
 89   int placementMode;
 90 
 91   int initialGold;
 92   int playerGold;
 93 
 94   EnemyWave waves[10];
 95   int currentWave;
 96   float waveEndTimer;
 97 } Level;
 98 
 99 typedef struct DeltaSrc
100 {
101   char x, y;
102 } DeltaSrc;
103 
104 typedef struct PathfindingMap
105 {
106   int width, height;
107   float scale;
108   float *distances;
109   long *towerIndex; 
110   DeltaSrc *deltaSrc;
111   float maxDistance;
112   Matrix toMapSpace;
113   Matrix toWorldSpace;
114 } PathfindingMap;
115 
116 // when we execute the pathfinding algorithm, we need to store the active nodes
117 // in a queue. Each node has a position, a distance from the start, and the
118 // position of the node that we came from.
119 typedef struct PathfindingNode
120 {
121   int16_t x, y, fromX, fromY;
122   float distance;
123 } PathfindingNode;
124 
125 typedef struct EnemyId
126 {
127   uint16_t index;
128   uint16_t generation;
129 } EnemyId;
130 
131 typedef struct EnemyClassConfig
132 {
133   float speed;
134   float health;
135   float radius;
136   float maxAcceleration;
137   float requiredContactTime;
138   float explosionDamage;
139   float explosionRange;
140   float explosionPushbackPower;
141   int goldValue;
142 } EnemyClassConfig;
143 
144 typedef struct Enemy
145 {
146   int16_t currentX, currentY;
147   int16_t nextX, nextY;
148   Vector2 simPosition;
149   Vector2 simVelocity;
150   uint16_t generation;
151   float walkedDistance;
152   float startMovingTime;
153   float damage, futureDamage;
154   float contactTime;
155   uint8_t enemyType;
156   uint8_t movePathCount;
157   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
158 } Enemy;
159 
160 // a unit that uses sprites to be drawn
161 #define SPRITE_UNIT_PHASE_WEAPON_IDLE 0
162 #define SPRITE_UNIT_PHASE_WEAPON_COOLDOWN 1
163 typedef struct SpriteUnit
164 {
165   Rectangle srcRect;
166   Vector2 offset;
167   int frameCount;
168   float frameDuration;
169   Rectangle srcWeaponIdleRect;
170   Vector2 srcWeaponIdleOffset;
171   Rectangle srcWeaponCooldownRect;
172   Vector2 srcWeaponCooldownOffset;
173 } SpriteUnit;
174 
175 #define PROJECTILE_MAX_COUNT 1200
176 #define PROJECTILE_TYPE_NONE 0
177 #define PROJECTILE_TYPE_ARROW 1
178 
179 typedef struct Projectile
180 {
181   uint8_t projectileType;
182   float shootTime;
183   float arrivalTime;
184   float distance;
185   float damage;
186   Vector3 position;
187   Vector3 target;
188   Vector3 directionNormal;
189   EnemyId targetEnemy;
190 } Projectile;
191 
192 //# Function declarations
193 float TowerGetMaxHealth(Tower *tower);
194 int Button(const char *text, int x, int y, int width, int height, ButtonState *state);
195 int EnemyAddDamage(Enemy *enemy, float damage);
196 
197 //# Enemy functions
198 void EnemyInit();
199 void EnemyDraw();
200 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource);
201 void EnemyUpdate();
202 float EnemyGetCurrentMaxSpeed(Enemy *enemy);
203 float EnemyGetMaxHealth(Enemy *enemy);
204 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY);
205 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount);
206 EnemyId EnemyGetId(Enemy *enemy);
207 Enemy *EnemyTryResolve(EnemyId enemyId);
208 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY);
209 int EnemyAddDamage(Enemy *enemy, float damage);
210 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range);
211 int EnemyCount();
212 void EnemyDrawHealthbars(Camera3D camera);
213 
214 //# Tower functions
215 void TowerInit();
216 Tower *TowerGetAt(int16_t x, int16_t y);
217 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y);
218 Tower *GetTowerByType(uint8_t towerType);
219 int GetTowerCosts(uint8_t towerType);
220 float TowerGetMaxHealth(Tower *tower);
221 void TowerDraw();
222 void TowerUpdate();
223 void TowerDrawHealthBars(Camera3D camera);
224 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase);
225 
226 //# Particles
227 void ParticleInit();
228 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime);
229 void ParticleUpdate();
230 void ParticleDraw();
231 
232 //# Projectiles
233 void ProjectileInit();
234 void ProjectileDraw();
235 void ProjectileUpdate();
236 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, float damage);
237 
238 //# Pathfinding map
239 void PathfindingMapInit(int width, int height, Vector3 translate, float scale);
240 float PathFindingGetDistance(int mapX, int mapY);
241 Vector2 PathFindingGetGradient(Vector3 world);
242 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY);
243 void PathFindingMapUpdate();
244 void PathFindingMapDraw();
245 
246 //# UI
247 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth);
248 
249 //# Level
250 void DrawLevelGround(Level *level);
251 
252 //# variables
253 extern Level *currentLevel;
254 extern Enemy enemies[ENEMY_MAX_COUNT];
255 extern int enemyCount;
256 extern EnemyClassConfig enemyClassConfigs[];
257 
258 extern GUIState guiState;
259 extern GameTime gameTime;
260 extern Tower towers[TOWER_MAX_COUNT];
261 extern int towerCount;
262 
263 extern Texture2D palette, spriteSheet;
264 
265 #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, 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 "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 
  9 // definition of our archer unit
 10 SpriteUnit archerUnit = {
 11     .srcRect = {0, 0, 16, 16},
 12     .offset = {7, 1},
 13     .frameCount = 1,
 14     .frameDuration = 0.0f,
 15     .srcWeaponIdleRect = {16, 0, 6, 16},
 16     .srcWeaponIdleOffset = {8, 0},
 17     .srcWeaponCooldownRect = {22, 0, 11, 16},
 18     .srcWeaponCooldownOffset = {10, 0},
 19 };
 20 
 21 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
 22 {
 23   float xScale = flip ? -1.0f : 1.0f;
 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 * xScale };
 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   if (flip)
 40   {
 41     srcRect.x += srcRect.width;
 42     srcRect.width = -srcRect.width;
 43   }
 44   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 45 
 46   if (phase == SPRITE_UNIT_PHASE_WEAPON_COOLDOWN && unit.srcWeaponCooldownRect.width > 0)
 47   {
 48     offset = (Vector2){ unit.srcWeaponCooldownOffset.x / 16.0f * size, unit.srcWeaponCooldownOffset.y / 16.0f * size };
 49     scale = (Vector2){ unit.srcWeaponCooldownRect.width / 16.0f * size, unit.srcWeaponCooldownRect.height / 16.0f * size };
 50     srcRect = unit.srcWeaponCooldownRect;
 51     if (flip)
 52     {
 53       // position.x = flip * scale.x * 0.5f;
 54       srcRect.x += srcRect.width;
 55       srcRect.width = -srcRect.width;
 56       offset.x = scale.x - offset.x;
 57     }
 58     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 59   }
 60   else if (phase == SPRITE_UNIT_PHASE_WEAPON_IDLE && unit.srcWeaponIdleRect.width > 0)
 61   {
 62     offset = (Vector2){ unit.srcWeaponIdleOffset.x / 16.0f * size, unit.srcWeaponIdleOffset.y / 16.0f * size };
 63     scale = (Vector2){ unit.srcWeaponIdleRect.width / 16.0f * size, unit.srcWeaponIdleRect.height / 16.0f * size };
 64     srcRect = unit.srcWeaponIdleRect;
 65     if (flip)
 66     {
 67       // position.x = flip * scale.x * 0.5f;
 68       srcRect.x += srcRect.width;
 69       srcRect.width = -srcRect.width;
 70       offset.x = scale.x - offset.x;
 71     }
 72     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 73   }
 74 }
 75 
 76 void TowerInit()
 77 {
 78   for (int i = 0; i < TOWER_MAX_COUNT; i++)
 79   {
 80     towers[i] = (Tower){0};
 81   }
 82   towerCount = 0;
 83 
 84   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
 85   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
 86 
 87   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
 88   {
 89     if (towerModels[i].materials)
 90     {
 91       // assign the palette texture to the material of the model (0 is not used afaik)
 92       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 93     }
 94   }
 95 }
 96 
 97 static void TowerGunUpdate(Tower *tower)
 98 {
 99   if (tower->cooldown <= 0)
100   {
101     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
102     if (enemy)
103     {
104       tower->cooldown = 0.5f;
105       // shoot the enemy; determine future position of the enemy
106       float bulletSpeed = 4.0f;
107       float bulletDamage = 3.0f;
108       Vector2 velocity = enemy->simVelocity;
109       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
110       Vector2 towerPosition = {tower->x, tower->y};
111       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
112       for (int i = 0; i < 8; i++) {
113         velocity = enemy->simVelocity;
114         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
115         float distance = Vector2Distance(towerPosition, futurePosition);
116         float eta2 = distance / bulletSpeed;
117         if (fabs(eta - eta2) < 0.01f) {
118           break;
119         }
120         eta = (eta2 + eta) * 0.5f;
121       }
122       ProjectileTryAdd(PROJECTILE_TYPE_ARROW, enemy, 
123         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
124         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
125         bulletSpeed, bulletDamage);
126       enemy->futureDamage += bulletDamage;
127       tower->lastTargetPosition = futurePosition;
128     }
129   }
130   else
131   {
132     tower->cooldown -= gameTime.deltaTime;
133   }
134 }
135 
136 Tower *TowerGetAt(int16_t x, int16_t y)
137 {
138   for (int i = 0; i < towerCount; i++)
139   {
140     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
141     {
142       return &towers[i];
143     }
144   }
145   return 0;
146 }
147 
148 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
149 {
150   if (towerCount >= TOWER_MAX_COUNT)
151   {
152     return 0;
153   }
154 
155   Tower *tower = TowerGetAt(x, y);
156   if (tower)
157   {
158     return 0;
159   }
160 
161   tower = &towers[towerCount++];
162   tower->x = x;
163   tower->y = y;
164   tower->towerType = towerType;
165   tower->cooldown = 0.0f;
166   tower->damage = 0.0f;
167   return tower;
168 }
169 
170 Tower *GetTowerByType(uint8_t towerType)
171 {
172   for (int i = 0; i < towerCount; i++)
173   {
174     if (towers[i].towerType == towerType)
175     {
176       return &towers[i];
177     }
178   }
179   return 0;
180 }
181 
182 int GetTowerCosts(uint8_t towerType)
183 {
184   switch (towerType)
185   {
186   case TOWER_TYPE_BASE:
187     return 0;
188   case TOWER_TYPE_GUN:
189     return 6;
190   case TOWER_TYPE_WALL:
191     return 2;
192   }
193   return 0;
194 }
195 
196 float TowerGetMaxHealth(Tower *tower)
197 {
198   switch (tower->towerType)
199   {
200   case TOWER_TYPE_BASE:
201     return 10.0f;
202   case TOWER_TYPE_GUN:
203     return 3.0f;
204   case TOWER_TYPE_WALL:
205     return 5.0f;
206   }
207   return 0.0f;
208 }
209 
210 void TowerDraw()
211 {
212   for (int i = 0; i < towerCount; i++)
213   {
214     Tower tower = towers[i];
215     if (tower.towerType == TOWER_TYPE_NONE)
216     {
217       continue;
218     }
219 
220     switch (tower.towerType)
221     {
222     case TOWER_TYPE_GUN:
223       {
224         Vector2 screenPosTower = GetWorldToScreen((Vector3){tower.x, 0.0f, tower.y}, currentLevel->camera);
225         Vector2 screenPosTarget = GetWorldToScreen((Vector3){tower.lastTargetPosition.x, 0.0f, tower.lastTargetPosition.y}, currentLevel->camera);
226         DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
227         DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0, screenPosTarget.x > screenPosTower.x, 
228           tower.cooldown > 0.2f ? SPRITE_UNIT_PHASE_WEAPON_COOLDOWN : SPRITE_UNIT_PHASE_WEAPON_IDLE);
229       }
230       break;
231     default:
232       if (towerModels[tower.towerType].materials)
233       {
234         DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
235       } else {
236         DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
237       }
238       break;
239     }
240   }
241 }
242 
243 void TowerUpdate()
244 {
245   for (int i = 0; i < towerCount; i++)
246   {
247     Tower *tower = &towers[i];
248     switch (tower->towerType)
249     {
250     case TOWER_TYPE_GUN:
251       TowerGunUpdate(tower);
252       break;
253     }
254   }
255 }
256 
257 void TowerDrawHealthBars(Camera3D camera)
258 {
259   for (int i = 0; i < towerCount; i++)
260   {
261     Tower *tower = &towers[i];
262     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
263     {
264       continue;
265     }
266     
267     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
268     float maxHealth = TowerGetMaxHealth(tower);
269     float health = maxHealth - tower->damage;
270     float healthRatio = health / maxHealth;
271     
272     DrawHealthBar(camera, position, healthRatio, GREEN, 35.0f);
273   }
274 }
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif
Amazing! There is almost no visible pattern any more! But it's all the same model. Let's add a second model to the decoration set and choose randomly between the two models:
  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 Model floorTileAModel = {0};
 11 Model floorTileBModel = {0};
 12 Model treeModel[2] = {0};
 13 Model grassPatchModel[1] = {0};
 14 
 15 Texture2D palette, spriteSheet;
 16 
 17 Level levels[] = {
 18   [0] = {
 19     .state = LEVEL_STATE_BUILDING,
 20     .initialGold = 20,
 21     .waves[0] = {
 22       .enemyType = ENEMY_TYPE_MINION,
 23       .wave = 0,
 24       .count = 10,
 25       .interval = 2.5f,
 26       .delay = 1.0f,
 27       .spawnPosition = {0, 6},
 28     },
 29     .waves[1] = {
 30       .enemyType = ENEMY_TYPE_MINION,
 31       .wave = 1,
 32       .count = 20,
 33       .interval = 1.5f,
 34       .delay = 1.0f,
 35       .spawnPosition = {0, 6},
 36     },
 37     .waves[2] = {
 38       .enemyType = ENEMY_TYPE_MINION,
 39       .wave = 2,
 40       .count = 30,
 41       .interval = 1.2f,
 42       .delay = 1.0f,
 43       .spawnPosition = {0, 6},
 44     }
 45   },
 46 };
 47 
 48 Level *currentLevel = levels;
 49 
 50 //# Game
 51 
 52 static Model LoadGLBModel(char *filename)
 53 {
 54   Model model = LoadModel(TextFormat("data/%s.glb",filename));
 55   if (model.materialCount > 1)
 56   {
 57     model.materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 58   }
 59   return model;
 60 }
 61 
 62 void LoadAssets()
 63 {
 64   // load a sprite sheet that contains all units
 65   spriteSheet = LoadTexture("data/spritesheet.png");
 66   SetTextureFilter(spriteSheet, TEXTURE_FILTER_BILINEAR);
 67 
 68   // we'll use a palette texture to colorize the all buildings and environment art
 69   palette = LoadTexture("data/palette.png");
 70   // The texture uses gradients on very small space, so we'll enable bilinear filtering
 71   SetTextureFilter(palette, TEXTURE_FILTER_BILINEAR);
 72 
 73   floorTileAModel = LoadGLBModel("floor-tile-a");
 74   floorTileBModel = LoadGLBModel("floor-tile-b");
 75   treeModel[0] = LoadGLBModel("leaftree-large-1-a");
 76   treeModel[1] = LoadGLBModel("leaftree-large-1-b");
 77   grassPatchModel[0] = LoadGLBModel("grass-patch-1");
 78 }
 79 
 80 void InitLevel(Level *level)
 81 {
 82   level->seed = (int)(GetTime() * 100.0f);
 83 
 84   TowerInit();
 85   EnemyInit();
 86   ProjectileInit();
 87   ParticleInit();
 88   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
 89 
 90   level->placementMode = 0;
 91   level->state = LEVEL_STATE_BUILDING;
 92   level->nextState = LEVEL_STATE_NONE;
 93   level->playerGold = level->initialGold;
 94   level->currentWave = 0;
 95 
 96   Camera *camera = &level->camera;
 97   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
 98   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
 99   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
100   camera->fovy = 10.0f;
101   camera->projection = CAMERA_ORTHOGRAPHIC;
102 }
103 
104 void DrawLevelHud(Level *level)
105 {
106   const char *text = TextFormat("Gold: %d", level->playerGold);
107   Font font = GetFontDefault();
108   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
109   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
110 }
111 
112 void DrawLevelReportLostWave(Level *level)
113 {
114   BeginMode3D(level->camera);
115   DrawLevelGround(level);
116   TowerDraw();
117   EnemyDraw();
118   ProjectileDraw();
119   ParticleDraw();
120   guiState.isBlocked = 0;
121   EndMode3D();
122 
123   TowerDrawHealthBars(level->camera);
124 
125   const char *text = "Wave lost";
126   int textWidth = MeasureText(text, 20);
127   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
128 
129   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
130   {
131     level->nextState = LEVEL_STATE_RESET;
132   }
133 }
134 
135 int HasLevelNextWave(Level *level)
136 {
137   for (int i = 0; i < 10; i++)
138   {
139     EnemyWave *wave = &level->waves[i];
140     if (wave->wave == level->currentWave)
141     {
142       return 1;
143     }
144   }
145   return 0;
146 }
147 
148 void DrawLevelReportWonWave(Level *level)
149 {
150   BeginMode3D(level->camera);
151   DrawLevelGround(level);
152   TowerDraw();
153   EnemyDraw();
154   ProjectileDraw();
155   ParticleDraw();
156   guiState.isBlocked = 0;
157   EndMode3D();
158 
159   TowerDrawHealthBars(level->camera);
160 
161   const char *text = "Wave won";
162   int textWidth = MeasureText(text, 20);
163   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
164 
165 
166   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
167   {
168     level->nextState = LEVEL_STATE_RESET;
169   }
170 
171   if (HasLevelNextWave(level))
172   {
173     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
174     {
175       level->nextState = LEVEL_STATE_BUILDING;
176     }
177   }
178   else {
179     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
180     {
181       level->nextState = LEVEL_STATE_WON_LEVEL;
182     }
183   }
184 }
185 
186 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
187 {
188   static ButtonState buttonStates[8] = {0};
189   int cost = GetTowerCosts(towerType);
190   const char *text = TextFormat("%s: %d", name, cost);
191   buttonStates[towerType].isSelected = level->placementMode == towerType;
192   buttonStates[towerType].isDisabled = level->playerGold < cost;
193   if (Button(text, x, y, width, height, &buttonStates[towerType]))
194   {
195     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
196   }
197 }
198 
199 float GetRandomFloat(float min, float max)
200 {
201   int random = GetRandomValue(0, 0xfffffff);
202   return ((float)random / (float)0xfffffff) * (max - min) + min;
203 }
204 
205 void DrawLevelGround(Level *level)
206 {
207   // draw checkerboard ground pattern
208   for (int x = -5; x <= 5; x += 1)
209   {
210     for (int y = -5; y <= 5; y += 1)
211     {
212       Model *model = (x + y) % 2 == 0 ? &floorTileAModel : &floorTileBModel;
213       DrawModel(*model, (Vector3){x, 0.0f, y}, 1.0f, WHITE);
214     }
215   }
216 
217   Model borderModels[2];
218   borderModels[0] = grassPatchModel[0];
219   borderModels[1] = treeModel[1];
220 
221   // draw grass patches around the edges
222   const Vector3 up = {0, 1, 0};
223   const int layerCount = 2;
224   unsigned int oldSeed = GetRandomValue(0, 0xfffffff);
225   const float wiggle = 0.25f;
226   SetRandomSeed(level->seed);
227   for (int layer = 0; layer < layerCount; layer++)
228   {
229     int layerPos = 6 + layer;
230     for (int x = -6 + layer; x <= 6 + layer; x += 1)
231     {
232       DrawModelEx(borderModels[GetRandomValue(0, 1)], 
233         (Vector3){x + GetRandomFloat(-wiggle, wiggle), 0.0f, -layerPos + GetRandomFloat(-wiggle, wiggle)}, 
234         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
235       DrawModelEx(borderModels[GetRandomValue(0, 1)], 
236         (Vector3){x + GetRandomFloat(-wiggle, wiggle), 0.0f, layerPos + GetRandomFloat(-wiggle, wiggle)}, 
237         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
238     }
239 
240     for (int z = -5 + layer; z <= 5 + layer; z += 1)
241     {
242       DrawModelEx(borderModels[GetRandomValue(0, 1)], 
243         (Vector3){-layerPos + GetRandomFloat(-wiggle, wiggle), 0.0f, z + GetRandomFloat(-wiggle, wiggle)}, 
244         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
245       DrawModelEx(borderModels[GetRandomValue(0, 1)], 
246         (Vector3){layerPos + GetRandomFloat(-wiggle, wiggle), 0.0f, z + GetRandomFloat(-wiggle, wiggle)}, 
247         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
248     }
249   }
250   SetRandomSeed(oldSeed);
251 }
252 
253 void DrawLevelBuildingState(Level *level)
254 {
255   BeginMode3D(level->camera);
256   DrawLevelGround(level);
257   TowerDraw();
258   EnemyDraw();
259   ProjectileDraw();
260   ParticleDraw();
261 
262   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
263   float planeDistance = ray.position.y / -ray.direction.y;
264   float planeX = ray.direction.x * planeDistance + ray.position.x;
265   float planeY = ray.direction.z * planeDistance + ray.position.z;
266   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
267   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
268   if (level->placementMode && !guiState.isBlocked && mapX >= -5 && mapX <= 5 && mapY >= -5 && mapY <= 5)
269   {
270     DrawCubeWires((Vector3){mapX, 0.2f, mapY}, 1.0f, 0.4f, 1.0f, RED);
271     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
272     {
273       if (TowerTryAdd(level->placementMode, mapX, mapY))
274       {
275         level->playerGold -= GetTowerCosts(level->placementMode);
276         level->placementMode = TOWER_TYPE_NONE;
277       }
278     }
279   }
280 
281   guiState.isBlocked = 0;
282 
283   EndMode3D();
284 
285   TowerDrawHealthBars(level->camera);
286 
287   static ButtonState buildWallButtonState = {0};
288   static ButtonState buildGunButtonState = {0};
289   buildWallButtonState.isSelected = level->placementMode == TOWER_TYPE_WALL;
290   buildGunButtonState.isSelected = level->placementMode == TOWER_TYPE_GUN;
291 
292   DrawBuildingBuildButton(level, 10, 10, 110, 30, TOWER_TYPE_WALL, "Wall");
293   DrawBuildingBuildButton(level, 10, 50, 110, 30, TOWER_TYPE_GUN, "Archer");
294 
295   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
296   {
297     level->nextState = LEVEL_STATE_RESET;
298   }
299   
300   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
301   {
302     level->nextState = LEVEL_STATE_BATTLE;
303   }
304 
305   const char *text = "Building phase";
306   int textWidth = MeasureText(text, 20);
307   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
308 }
309 
310 void InitBattleStateConditions(Level *level)
311 {
312   level->state = LEVEL_STATE_BATTLE;
313   level->nextState = LEVEL_STATE_NONE;
314   level->waveEndTimer = 0.0f;
315   for (int i = 0; i < 10; i++)
316   {
317     EnemyWave *wave = &level->waves[i];
318     wave->spawned = 0;
319     wave->timeToSpawnNext = wave->delay;
320   }
321 }
322 
323 void DrawLevelBattleState(Level *level)
324 {
325   BeginMode3D(level->camera);
326   DrawLevelGround(level);
327   TowerDraw();
328   EnemyDraw();
329   ProjectileDraw();
330   ParticleDraw();
331   guiState.isBlocked = 0;
332   EndMode3D();
333 
334   EnemyDrawHealthbars(level->camera);
335   TowerDrawHealthBars(level->camera);
336 
337   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
338   {
339     level->nextState = LEVEL_STATE_RESET;
340   }
341 
342   int maxCount = 0;
343   int remainingCount = 0;
344   for (int i = 0; i < 10; i++)
345   {
346     EnemyWave *wave = &level->waves[i];
347     if (wave->wave != level->currentWave)
348     {
349       continue;
350     }
351     maxCount += wave->count;
352     remainingCount += wave->count - wave->spawned;
353   }
354   int aliveCount = EnemyCount();
355   remainingCount += aliveCount;
356 
357   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
358   int textWidth = MeasureText(text, 20);
359   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
360 }
361 
362 void DrawLevel(Level *level)
363 {
364   switch (level->state)
365   {
366     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
367     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
368     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
369     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
370     default: break;
371   }
372 
373   DrawLevelHud(level);
374 }
375 
376 void UpdateLevel(Level *level)
377 {
378   if (level->state == LEVEL_STATE_BATTLE)
379   {
380     int activeWaves = 0;
381     for (int i = 0; i < 10; i++)
382     {
383       EnemyWave *wave = &level->waves[i];
384       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
385       {
386         continue;
387       }
388       activeWaves++;
389       wave->timeToSpawnNext -= gameTime.deltaTime;
390       if (wave->timeToSpawnNext <= 0.0f)
391       {
392         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
393         if (enemy)
394         {
395           wave->timeToSpawnNext = wave->interval;
396           wave->spawned++;
397         }
398       }
399     }
400     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
401       level->waveEndTimer += gameTime.deltaTime;
402       if (level->waveEndTimer >= 2.0f)
403       {
404         level->nextState = LEVEL_STATE_LOST_WAVE;
405       }
406     }
407     else if (activeWaves == 0 && EnemyCount() == 0)
408     {
409       level->waveEndTimer += gameTime.deltaTime;
410       if (level->waveEndTimer >= 2.0f)
411       {
412         level->nextState = LEVEL_STATE_WON_WAVE;
413       }
414     }
415   }
416 
417   PathFindingMapUpdate();
418   EnemyUpdate();
419   TowerUpdate();
420   ProjectileUpdate();
421   ParticleUpdate();
422 
423   if (level->nextState == LEVEL_STATE_RESET)
424   {
425     InitLevel(level);
426   }
427   
428   if (level->nextState == LEVEL_STATE_BATTLE)
429   {
430     InitBattleStateConditions(level);
431   }
432   
433   if (level->nextState == LEVEL_STATE_WON_WAVE)
434   {
435     level->currentWave++;
436     level->state = LEVEL_STATE_WON_WAVE;
437   }
438   
439   if (level->nextState == LEVEL_STATE_LOST_WAVE)
440   {
441     level->state = LEVEL_STATE_LOST_WAVE;
442   }
443 
444   if (level->nextState == LEVEL_STATE_BUILDING)
445   {
446     level->state = LEVEL_STATE_BUILDING;
447   }
448 
449   if (level->nextState == LEVEL_STATE_WON_LEVEL)
450   {
451     // make something of this later
452     InitLevel(level);
453   }
454 
455   level->nextState = LEVEL_STATE_NONE;
456 }
457 
458 float nextSpawnTime = 0.0f;
459 
460 void ResetGame()
461 {
462   InitLevel(currentLevel);
463 }
464 
465 void InitGame()
466 {
467   TowerInit();
468   EnemyInit();
469   ProjectileInit();
470   ParticleInit();
471   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
472 
473   currentLevel = levels;
474   InitLevel(currentLevel);
475 }
476 
477 //# Immediate GUI functions
478 
479 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth)
480 {
481   const float healthBarHeight = 6.0f;
482   const float healthBarOffset = 15.0f;
483   const float inset = 2.0f;
484   const float innerWidth = healthBarWidth - inset * 2;
485   const float innerHeight = healthBarHeight - inset * 2;
486 
487   Vector2 screenPos = GetWorldToScreen(position, camera);
488   float centerX = screenPos.x - healthBarWidth * 0.5f;
489   float topY = screenPos.y - healthBarOffset;
490   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
491   float healthWidth = innerWidth * healthRatio;
492   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
493 }
494 
495 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
496 {
497   Rectangle bounds = {x, y, width, height};
498   int isPressed = 0;
499   int isSelected = state && state->isSelected;
500   int isDisabled = state && state->isDisabled;
501   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !isDisabled)
502   {
503     Color color = isSelected ? DARKGRAY : GRAY;
504     DrawRectangle(x, y, width, height, color);
505     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
506     {
507       isPressed = 1;
508     }
509     guiState.isBlocked = 1;
510   }
511   else
512   {
513     Color color = isSelected ? WHITE : LIGHTGRAY;
514     DrawRectangle(x, y, width, height, color);
515   }
516   Font font = GetFontDefault();
517   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
518   Color textColor = isDisabled ? GRAY : BLACK;
519   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
520   return isPressed;
521 }
522 
523 //# Main game loop
524 
525 void GameUpdate()
526 {
527   float dt = GetFrameTime();
528   // cap maximum delta time to 0.1 seconds to prevent large time steps
529   if (dt > 0.1f) dt = 0.1f;
530   gameTime.time += dt;
531   gameTime.deltaTime = dt;
532 
533   UpdateLevel(currentLevel);
534 }
535 
536 int main(void)
537 {
538   int screenWidth, screenHeight;
539   GetPreferredSize(&screenWidth, &screenHeight);
540   InitWindow(screenWidth, screenHeight, "Tower defense");
541   SetTargetFPS(30);
542 
543   LoadAssets();
544   InitGame();
545 
546   while (!WindowShouldClose())
547   {
548     if (IsPaused()) {
549       // canvas is not visible in browser - do nothing
550       continue;
551     }
552 
553     BeginDrawing();
554     ClearBackground((Color){0x4E, 0x63, 0x26, 0xFF});
555 
556     GameUpdate();
557     DrawLevel(currentLevel);
558 
559     EndDrawing();
560   }
561 
562   CloseWindow();
563 
564   return 0;
565 }
  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   int seed;
 86   LevelState state;
 87   LevelState nextState;
 88   Camera3D camera;
 89   int placementMode;
 90 
 91   int initialGold;
 92   int playerGold;
 93 
 94   EnemyWave waves[10];
 95   int currentWave;
 96   float waveEndTimer;
 97 } Level;
 98 
 99 typedef struct DeltaSrc
100 {
101   char x, y;
102 } DeltaSrc;
103 
104 typedef struct PathfindingMap
105 {
106   int width, height;
107   float scale;
108   float *distances;
109   long *towerIndex; 
110   DeltaSrc *deltaSrc;
111   float maxDistance;
112   Matrix toMapSpace;
113   Matrix toWorldSpace;
114 } PathfindingMap;
115 
116 // when we execute the pathfinding algorithm, we need to store the active nodes
117 // in a queue. Each node has a position, a distance from the start, and the
118 // position of the node that we came from.
119 typedef struct PathfindingNode
120 {
121   int16_t x, y, fromX, fromY;
122   float distance;
123 } PathfindingNode;
124 
125 typedef struct EnemyId
126 {
127   uint16_t index;
128   uint16_t generation;
129 } EnemyId;
130 
131 typedef struct EnemyClassConfig
132 {
133   float speed;
134   float health;
135   float radius;
136   float maxAcceleration;
137   float requiredContactTime;
138   float explosionDamage;
139   float explosionRange;
140   float explosionPushbackPower;
141   int goldValue;
142 } EnemyClassConfig;
143 
144 typedef struct Enemy
145 {
146   int16_t currentX, currentY;
147   int16_t nextX, nextY;
148   Vector2 simPosition;
149   Vector2 simVelocity;
150   uint16_t generation;
151   float walkedDistance;
152   float startMovingTime;
153   float damage, futureDamage;
154   float contactTime;
155   uint8_t enemyType;
156   uint8_t movePathCount;
157   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
158 } Enemy;
159 
160 // a unit that uses sprites to be drawn
161 #define SPRITE_UNIT_PHASE_WEAPON_IDLE 0
162 #define SPRITE_UNIT_PHASE_WEAPON_COOLDOWN 1
163 typedef struct SpriteUnit
164 {
165   Rectangle srcRect;
166   Vector2 offset;
167   int frameCount;
168   float frameDuration;
169   Rectangle srcWeaponIdleRect;
170   Vector2 srcWeaponIdleOffset;
171   Rectangle srcWeaponCooldownRect;
172   Vector2 srcWeaponCooldownOffset;
173 } SpriteUnit;
174 
175 #define PROJECTILE_MAX_COUNT 1200
176 #define PROJECTILE_TYPE_NONE 0
177 #define PROJECTILE_TYPE_ARROW 1
178 
179 typedef struct Projectile
180 {
181   uint8_t projectileType;
182   float shootTime;
183   float arrivalTime;
184   float distance;
185   float damage;
186   Vector3 position;
187   Vector3 target;
188   Vector3 directionNormal;
189   EnemyId targetEnemy;
190 } Projectile;
191 
192 //# Function declarations
193 float TowerGetMaxHealth(Tower *tower);
194 int Button(const char *text, int x, int y, int width, int height, ButtonState *state);
195 int EnemyAddDamage(Enemy *enemy, float damage);
196 
197 //# Enemy functions
198 void EnemyInit();
199 void EnemyDraw();
200 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource);
201 void EnemyUpdate();
202 float EnemyGetCurrentMaxSpeed(Enemy *enemy);
203 float EnemyGetMaxHealth(Enemy *enemy);
204 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY);
205 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount);
206 EnemyId EnemyGetId(Enemy *enemy);
207 Enemy *EnemyTryResolve(EnemyId enemyId);
208 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY);
209 int EnemyAddDamage(Enemy *enemy, float damage);
210 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range);
211 int EnemyCount();
212 void EnemyDrawHealthbars(Camera3D camera);
213 
214 //# Tower functions
215 void TowerInit();
216 Tower *TowerGetAt(int16_t x, int16_t y);
217 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y);
218 Tower *GetTowerByType(uint8_t towerType);
219 int GetTowerCosts(uint8_t towerType);
220 float TowerGetMaxHealth(Tower *tower);
221 void TowerDraw();
222 void TowerUpdate();
223 void TowerDrawHealthBars(Camera3D camera);
224 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase);
225 
226 //# Particles
227 void ParticleInit();
228 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime);
229 void ParticleUpdate();
230 void ParticleDraw();
231 
232 //# Projectiles
233 void ProjectileInit();
234 void ProjectileDraw();
235 void ProjectileUpdate();
236 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, float damage);
237 
238 //# Pathfinding map
239 void PathfindingMapInit(int width, int height, Vector3 translate, float scale);
240 float PathFindingGetDistance(int mapX, int mapY);
241 Vector2 PathFindingGetGradient(Vector3 world);
242 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY);
243 void PathFindingMapUpdate();
244 void PathFindingMapDraw();
245 
246 //# UI
247 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth);
248 
249 //# Level
250 void DrawLevelGround(Level *level);
251 
252 //# variables
253 extern Level *currentLevel;
254 extern Enemy enemies[ENEMY_MAX_COUNT];
255 extern int enemyCount;
256 extern EnemyClassConfig enemyClassConfigs[];
257 
258 extern GUIState guiState;
259 extern GameTime gameTime;
260 extern Tower towers[TOWER_MAX_COUNT];
261 extern int towerCount;
262 
263 extern Texture2D palette, spriteSheet;
264 
265 #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, 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 "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 
  9 // definition of our archer unit
 10 SpriteUnit archerUnit = {
 11     .srcRect = {0, 0, 16, 16},
 12     .offset = {7, 1},
 13     .frameCount = 1,
 14     .frameDuration = 0.0f,
 15     .srcWeaponIdleRect = {16, 0, 6, 16},
 16     .srcWeaponIdleOffset = {8, 0},
 17     .srcWeaponCooldownRect = {22, 0, 11, 16},
 18     .srcWeaponCooldownOffset = {10, 0},
 19 };
 20 
 21 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
 22 {
 23   float xScale = flip ? -1.0f : 1.0f;
 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 * xScale };
 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   if (flip)
 40   {
 41     srcRect.x += srcRect.width;
 42     srcRect.width = -srcRect.width;
 43   }
 44   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 45 
 46   if (phase == SPRITE_UNIT_PHASE_WEAPON_COOLDOWN && unit.srcWeaponCooldownRect.width > 0)
 47   {
 48     offset = (Vector2){ unit.srcWeaponCooldownOffset.x / 16.0f * size, unit.srcWeaponCooldownOffset.y / 16.0f * size };
 49     scale = (Vector2){ unit.srcWeaponCooldownRect.width / 16.0f * size, unit.srcWeaponCooldownRect.height / 16.0f * size };
 50     srcRect = unit.srcWeaponCooldownRect;
 51     if (flip)
 52     {
 53       // position.x = flip * scale.x * 0.5f;
 54       srcRect.x += srcRect.width;
 55       srcRect.width = -srcRect.width;
 56       offset.x = scale.x - offset.x;
 57     }
 58     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 59   }
 60   else if (phase == SPRITE_UNIT_PHASE_WEAPON_IDLE && unit.srcWeaponIdleRect.width > 0)
 61   {
 62     offset = (Vector2){ unit.srcWeaponIdleOffset.x / 16.0f * size, unit.srcWeaponIdleOffset.y / 16.0f * size };
 63     scale = (Vector2){ unit.srcWeaponIdleRect.width / 16.0f * size, unit.srcWeaponIdleRect.height / 16.0f * size };
 64     srcRect = unit.srcWeaponIdleRect;
 65     if (flip)
 66     {
 67       // position.x = flip * scale.x * 0.5f;
 68       srcRect.x += srcRect.width;
 69       srcRect.width = -srcRect.width;
 70       offset.x = scale.x - offset.x;
 71     }
 72     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 73   }
 74 }
 75 
 76 void TowerInit()
 77 {
 78   for (int i = 0; i < TOWER_MAX_COUNT; i++)
 79   {
 80     towers[i] = (Tower){0};
 81   }
 82   towerCount = 0;
 83 
 84   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
 85   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
 86 
 87   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
 88   {
 89     if (towerModels[i].materials)
 90     {
 91       // assign the palette texture to the material of the model (0 is not used afaik)
 92       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 93     }
 94   }
 95 }
 96 
 97 static void TowerGunUpdate(Tower *tower)
 98 {
 99   if (tower->cooldown <= 0)
100   {
101     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
102     if (enemy)
103     {
104       tower->cooldown = 0.5f;
105       // shoot the enemy; determine future position of the enemy
106       float bulletSpeed = 4.0f;
107       float bulletDamage = 3.0f;
108       Vector2 velocity = enemy->simVelocity;
109       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
110       Vector2 towerPosition = {tower->x, tower->y};
111       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
112       for (int i = 0; i < 8; i++) {
113         velocity = enemy->simVelocity;
114         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
115         float distance = Vector2Distance(towerPosition, futurePosition);
116         float eta2 = distance / bulletSpeed;
117         if (fabs(eta - eta2) < 0.01f) {
118           break;
119         }
120         eta = (eta2 + eta) * 0.5f;
121       }
122       ProjectileTryAdd(PROJECTILE_TYPE_ARROW, enemy, 
123         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
124         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
125         bulletSpeed, bulletDamage);
126       enemy->futureDamage += bulletDamage;
127       tower->lastTargetPosition = futurePosition;
128     }
129   }
130   else
131   {
132     tower->cooldown -= gameTime.deltaTime;
133   }
134 }
135 
136 Tower *TowerGetAt(int16_t x, int16_t y)
137 {
138   for (int i = 0; i < towerCount; i++)
139   {
140     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
141     {
142       return &towers[i];
143     }
144   }
145   return 0;
146 }
147 
148 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
149 {
150   if (towerCount >= TOWER_MAX_COUNT)
151   {
152     return 0;
153   }
154 
155   Tower *tower = TowerGetAt(x, y);
156   if (tower)
157   {
158     return 0;
159   }
160 
161   tower = &towers[towerCount++];
162   tower->x = x;
163   tower->y = y;
164   tower->towerType = towerType;
165   tower->cooldown = 0.0f;
166   tower->damage = 0.0f;
167   return tower;
168 }
169 
170 Tower *GetTowerByType(uint8_t towerType)
171 {
172   for (int i = 0; i < towerCount; i++)
173   {
174     if (towers[i].towerType == towerType)
175     {
176       return &towers[i];
177     }
178   }
179   return 0;
180 }
181 
182 int GetTowerCosts(uint8_t towerType)
183 {
184   switch (towerType)
185   {
186   case TOWER_TYPE_BASE:
187     return 0;
188   case TOWER_TYPE_GUN:
189     return 6;
190   case TOWER_TYPE_WALL:
191     return 2;
192   }
193   return 0;
194 }
195 
196 float TowerGetMaxHealth(Tower *tower)
197 {
198   switch (tower->towerType)
199   {
200   case TOWER_TYPE_BASE:
201     return 10.0f;
202   case TOWER_TYPE_GUN:
203     return 3.0f;
204   case TOWER_TYPE_WALL:
205     return 5.0f;
206   }
207   return 0.0f;
208 }
209 
210 void TowerDraw()
211 {
212   for (int i = 0; i < towerCount; i++)
213   {
214     Tower tower = towers[i];
215     if (tower.towerType == TOWER_TYPE_NONE)
216     {
217       continue;
218     }
219 
220     switch (tower.towerType)
221     {
222     case TOWER_TYPE_GUN:
223       {
224         Vector2 screenPosTower = GetWorldToScreen((Vector3){tower.x, 0.0f, tower.y}, currentLevel->camera);
225         Vector2 screenPosTarget = GetWorldToScreen((Vector3){tower.lastTargetPosition.x, 0.0f, tower.lastTargetPosition.y}, currentLevel->camera);
226         DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
227         DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0, screenPosTarget.x > screenPosTower.x, 
228           tower.cooldown > 0.2f ? SPRITE_UNIT_PHASE_WEAPON_COOLDOWN : SPRITE_UNIT_PHASE_WEAPON_IDLE);
229       }
230       break;
231     default:
232       if (towerModels[tower.towerType].materials)
233       {
234         DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
235       } else {
236         DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
237       }
238       break;
239     }
240   }
241 }
242 
243 void TowerUpdate()
244 {
245   for (int i = 0; i < towerCount; i++)
246   {
247     Tower *tower = &towers[i];
248     switch (tower->towerType)
249     {
250     case TOWER_TYPE_GUN:
251       TowerGunUpdate(tower);
252       break;
253     }
254   }
255 }
256 
257 void TowerDrawHealthBars(Camera3D camera)
258 {
259   for (int i = 0; i < towerCount; i++)
260   {
261     Tower *tower = &towers[i];
262     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
263     {
264       continue;
265     }
266     
267     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
268     float maxHealth = TowerGetMaxHealth(tower);
269     float health = maxHealth - tower->damage;
270     float healthRatio = health / maxHealth;
271     
272     DrawHealthBar(camera, position, healthRatio, GREEN, 35.0f);
273   }
274 }
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif
What we did here is to create an array of models that we can choose from. The draw loop now selects a random model from the array and draws it. Nothing fancy, but it looks quite nice. Let's extend it with all we have so far:
  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 Model floorTileAModel = {0};
 11 Model floorTileBModel = {0};
 12 Model treeModel[2] = {0};
 13 Model firTreeModel[2] = {0};
 14 Model rockModels[5] = {0};
 15 Model grassPatchModel[1] = {0};
 16 
 17 Texture2D palette, spriteSheet;
 18 
 19 Level levels[] = {
 20   [0] = {
 21     .state = LEVEL_STATE_BUILDING,
 22     .initialGold = 20,
 23     .waves[0] = {
 24       .enemyType = ENEMY_TYPE_MINION,
 25       .wave = 0,
 26       .count = 10,
 27       .interval = 2.5f,
 28       .delay = 1.0f,
 29       .spawnPosition = {0, 6},
 30     },
 31     .waves[1] = {
 32       .enemyType = ENEMY_TYPE_MINION,
 33       .wave = 1,
 34       .count = 20,
 35       .interval = 1.5f,
 36       .delay = 1.0f,
 37       .spawnPosition = {0, 6},
 38     },
 39     .waves[2] = {
 40       .enemyType = ENEMY_TYPE_MINION,
 41       .wave = 2,
 42       .count = 30,
 43       .interval = 1.2f,
 44       .delay = 1.0f,
 45       .spawnPosition = {0, 6},
 46     }
 47   },
 48 };
 49 
 50 Level *currentLevel = levels;
 51 
 52 //# Game
 53 
 54 static Model LoadGLBModel(char *filename)
 55 {
 56   Model model = LoadModel(TextFormat("data/%s.glb",filename));
 57   if (model.materialCount > 1)
 58   {
 59     model.materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 60   }
 61   return model;
 62 }
 63 
 64 void LoadAssets()
 65 {
 66   // load a sprite sheet that contains all units
 67   spriteSheet = LoadTexture("data/spritesheet.png");
 68   SetTextureFilter(spriteSheet, TEXTURE_FILTER_BILINEAR);
 69 
 70   // we'll use a palette texture to colorize the all buildings and environment art
 71   palette = LoadTexture("data/palette.png");
 72   // The texture uses gradients on very small space, so we'll enable bilinear filtering
 73   SetTextureFilter(palette, TEXTURE_FILTER_BILINEAR);
 74 
 75   floorTileAModel = LoadGLBModel("floor-tile-a");
 76   floorTileBModel = LoadGLBModel("floor-tile-b");
 77   treeModel[0] = LoadGLBModel("leaftree-large-1-a");
 78   treeModel[1] = LoadGLBModel("leaftree-large-1-b");
 79   firTreeModel[0] = LoadGLBModel("firtree-1-a");
 80   firTreeModel[1] = LoadGLBModel("firtree-1-b");
 81   rockModels[0] = LoadGLBModel("rock-1");
 82   rockModels[1] = LoadGLBModel("rock-2");
 83   rockModels[2] = LoadGLBModel("rock-3");
 84   rockModels[3] = LoadGLBModel("rock-4");
 85   rockModels[4] = LoadGLBModel("rock-5");
 86   grassPatchModel[0] = LoadGLBModel("grass-patch-1");
 87 }
 88 
 89 void InitLevel(Level *level)
 90 {
 91   level->seed = (int)(GetTime() * 100.0f);
 92 
 93   TowerInit();
 94   EnemyInit();
 95   ProjectileInit();
 96   ParticleInit();
 97   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
 98 
 99   level->placementMode = 0;
100   level->state = LEVEL_STATE_BUILDING;
101   level->nextState = LEVEL_STATE_NONE;
102   level->playerGold = level->initialGold;
103   level->currentWave = 0;
104 
105   Camera *camera = &level->camera;
106   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
107   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
108   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
109   camera->fovy = 10.0f;
110   camera->projection = CAMERA_ORTHOGRAPHIC;
111 }
112 
113 void DrawLevelHud(Level *level)
114 {
115   const char *text = TextFormat("Gold: %d", level->playerGold);
116   Font font = GetFontDefault();
117   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
118   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
119 }
120 
121 void DrawLevelReportLostWave(Level *level)
122 {
123   BeginMode3D(level->camera);
124   DrawLevelGround(level);
125   TowerDraw();
126   EnemyDraw();
127   ProjectileDraw();
128   ParticleDraw();
129   guiState.isBlocked = 0;
130   EndMode3D();
131 
132   TowerDrawHealthBars(level->camera);
133 
134   const char *text = "Wave lost";
135   int textWidth = MeasureText(text, 20);
136   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
137 
138   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
139   {
140     level->nextState = LEVEL_STATE_RESET;
141   }
142 }
143 
144 int HasLevelNextWave(Level *level)
145 {
146   for (int i = 0; i < 10; i++)
147   {
148     EnemyWave *wave = &level->waves[i];
149     if (wave->wave == level->currentWave)
150     {
151       return 1;
152     }
153   }
154   return 0;
155 }
156 
157 void DrawLevelReportWonWave(Level *level)
158 {
159   BeginMode3D(level->camera);
160   DrawLevelGround(level);
161   TowerDraw();
162   EnemyDraw();
163   ProjectileDraw();
164   ParticleDraw();
165   guiState.isBlocked = 0;
166   EndMode3D();
167 
168   TowerDrawHealthBars(level->camera);
169 
170   const char *text = "Wave won";
171   int textWidth = MeasureText(text, 20);
172   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
173 
174 
175   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
176   {
177     level->nextState = LEVEL_STATE_RESET;
178   }
179 
180   if (HasLevelNextWave(level))
181   {
182     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
183     {
184       level->nextState = LEVEL_STATE_BUILDING;
185     }
186   }
187   else {
188     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
189     {
190       level->nextState = LEVEL_STATE_WON_LEVEL;
191     }
192   }
193 }
194 
195 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
196 {
197   static ButtonState buttonStates[8] = {0};
198   int cost = GetTowerCosts(towerType);
199   const char *text = TextFormat("%s: %d", name, cost);
200   buttonStates[towerType].isSelected = level->placementMode == towerType;
201   buttonStates[towerType].isDisabled = level->playerGold < cost;
202   if (Button(text, x, y, width, height, &buttonStates[towerType]))
203   {
204     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
205   }
206 }
207 
208 float GetRandomFloat(float min, float max)
209 {
210   int random = GetRandomValue(0, 0xfffffff);
211   return ((float)random / (float)0xfffffff) * (max - min) + min;
212 }
213 
214 void DrawLevelGround(Level *level)
215 {
216   // draw checkerboard ground pattern
217   for (int x = -5; x <= 5; x += 1)
218   {
219     for (int y = -5; y <= 5; y += 1)
220     {
221       Model *model = (x + y) % 2 == 0 ? &floorTileAModel : &floorTileBModel;
222       DrawModel(*model, (Vector3){x, 0.0f, y}, 1.0f, WHITE);
223     }
224   }
225 
226   Model borderModels[64];
227   int modelCount = 0;
228   int rockCount = 5;
229   int firTreeCount = 2;
230   int leafTreeCount = 2;
231 
232   borderModels[modelCount++] = grassPatchModel[0];
233   for (int i = 0; i < rockCount; i++)
234   {
235     borderModels[modelCount++] = rockModels[i];
236   }
237   for (int i = 0; i < firTreeCount; i++)
238   {
239     borderModels[modelCount++] = firTreeModel[i];
240   }
241   for (int i = 0; i < leafTreeCount; i++)
242   {
243     borderModels[modelCount++] = treeModel[i];
244   }
245 
246   // draw grass patches around the edges
247   const Vector3 up = {0, 1, 0};
248   const int layerCount = 2;
249   unsigned int oldSeed = GetRandomValue(0, 0xfffffff);
250   const float wiggle = 0.25f;
251   SetRandomSeed(level->seed);
252   for (int layer = 0; layer < layerCount; layer++)
253   {
254     int layerPos = 6 + layer;
255     for (int x = -6 + layer; x <= 6 + layer; x += 1)
256     {
257       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
258         (Vector3){x + GetRandomFloat(-wiggle, wiggle), 0.0f, -layerPos + GetRandomFloat(-wiggle, wiggle)}, 
259         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
260       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
261         (Vector3){x + GetRandomFloat(-wiggle, wiggle), 0.0f, layerPos + GetRandomFloat(-wiggle, wiggle)}, 
262         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
263     }
264 
265     for (int z = -5 + layer; z <= 5 + layer; z += 1)
266     {
267       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
268         (Vector3){-layerPos + GetRandomFloat(-wiggle, wiggle), 0.0f, z + GetRandomFloat(-wiggle, wiggle)}, 
269         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
270       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
271         (Vector3){layerPos + GetRandomFloat(-wiggle, wiggle), 0.0f, z + GetRandomFloat(-wiggle, wiggle)}, 
272         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
273     }
274   }
275   SetRandomSeed(oldSeed);
276 }
277 
278 void DrawLevelBuildingState(Level *level)
279 {
280   BeginMode3D(level->camera);
281   DrawLevelGround(level);
282   TowerDraw();
283   EnemyDraw();
284   ProjectileDraw();
285   ParticleDraw();
286 
287   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
288   float planeDistance = ray.position.y / -ray.direction.y;
289   float planeX = ray.direction.x * planeDistance + ray.position.x;
290   float planeY = ray.direction.z * planeDistance + ray.position.z;
291   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
292   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
293   if (level->placementMode && !guiState.isBlocked && mapX >= -5 && mapX <= 5 && mapY >= -5 && mapY <= 5)
294   {
295     DrawCubeWires((Vector3){mapX, 0.2f, mapY}, 1.0f, 0.4f, 1.0f, RED);
296     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
297     {
298       if (TowerTryAdd(level->placementMode, mapX, mapY))
299       {
300         level->playerGold -= GetTowerCosts(level->placementMode);
301         level->placementMode = TOWER_TYPE_NONE;
302       }
303     }
304   }
305 
306   guiState.isBlocked = 0;
307 
308   EndMode3D();
309 
310   TowerDrawHealthBars(level->camera);
311 
312   static ButtonState buildWallButtonState = {0};
313   static ButtonState buildGunButtonState = {0};
314   buildWallButtonState.isSelected = level->placementMode == TOWER_TYPE_WALL;
315   buildGunButtonState.isSelected = level->placementMode == TOWER_TYPE_GUN;
316 
317   DrawBuildingBuildButton(level, 10, 10, 110, 30, TOWER_TYPE_WALL, "Wall");
318   DrawBuildingBuildButton(level, 10, 50, 110, 30, TOWER_TYPE_GUN, "Archer");
319 
320   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
321   {
322     level->nextState = LEVEL_STATE_RESET;
323   }
324   
325   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
326   {
327     level->nextState = LEVEL_STATE_BATTLE;
328   }
329 
330   const char *text = "Building phase";
331   int textWidth = MeasureText(text, 20);
332   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
333 }
334 
335 void InitBattleStateConditions(Level *level)
336 {
337   level->state = LEVEL_STATE_BATTLE;
338   level->nextState = LEVEL_STATE_NONE;
339   level->waveEndTimer = 0.0f;
340   for (int i = 0; i < 10; i++)
341   {
342     EnemyWave *wave = &level->waves[i];
343     wave->spawned = 0;
344     wave->timeToSpawnNext = wave->delay;
345   }
346 }
347 
348 void DrawLevelBattleState(Level *level)
349 {
350   BeginMode3D(level->camera);
351   DrawLevelGround(level);
352   TowerDraw();
353   EnemyDraw();
354   ProjectileDraw();
355   ParticleDraw();
356   guiState.isBlocked = 0;
357   EndMode3D();
358 
359   EnemyDrawHealthbars(level->camera);
360   TowerDrawHealthBars(level->camera);
361 
362   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
363   {
364     level->nextState = LEVEL_STATE_RESET;
365   }
366 
367   int maxCount = 0;
368   int remainingCount = 0;
369   for (int i = 0; i < 10; i++)
370   {
371     EnemyWave *wave = &level->waves[i];
372     if (wave->wave != level->currentWave)
373     {
374       continue;
375     }
376     maxCount += wave->count;
377     remainingCount += wave->count - wave->spawned;
378   }
379   int aliveCount = EnemyCount();
380   remainingCount += aliveCount;
381 
382   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
383   int textWidth = MeasureText(text, 20);
384   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
385 }
386 
387 void DrawLevel(Level *level)
388 {
389   switch (level->state)
390   {
391     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
392     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
393     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
394     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
395     default: break;
396   }
397 
398   DrawLevelHud(level);
399 }
400 
401 void UpdateLevel(Level *level)
402 {
403   if (level->state == LEVEL_STATE_BATTLE)
404   {
405     int activeWaves = 0;
406     for (int i = 0; i < 10; i++)
407     {
408       EnemyWave *wave = &level->waves[i];
409       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
410       {
411         continue;
412       }
413       activeWaves++;
414       wave->timeToSpawnNext -= gameTime.deltaTime;
415       if (wave->timeToSpawnNext <= 0.0f)
416       {
417         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
418         if (enemy)
419         {
420           wave->timeToSpawnNext = wave->interval;
421           wave->spawned++;
422         }
423       }
424     }
425     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
426       level->waveEndTimer += gameTime.deltaTime;
427       if (level->waveEndTimer >= 2.0f)
428       {
429         level->nextState = LEVEL_STATE_LOST_WAVE;
430       }
431     }
432     else if (activeWaves == 0 && EnemyCount() == 0)
433     {
434       level->waveEndTimer += gameTime.deltaTime;
435       if (level->waveEndTimer >= 2.0f)
436       {
437         level->nextState = LEVEL_STATE_WON_WAVE;
438       }
439     }
440   }
441 
442   PathFindingMapUpdate();
443   EnemyUpdate();
444   TowerUpdate();
445   ProjectileUpdate();
446   ParticleUpdate();
447 
448   if (level->nextState == LEVEL_STATE_RESET)
449   {
450     InitLevel(level);
451   }
452   
453   if (level->nextState == LEVEL_STATE_BATTLE)
454   {
455     InitBattleStateConditions(level);
456   }
457   
458   if (level->nextState == LEVEL_STATE_WON_WAVE)
459   {
460     level->currentWave++;
461     level->state = LEVEL_STATE_WON_WAVE;
462   }
463   
464   if (level->nextState == LEVEL_STATE_LOST_WAVE)
465   {
466     level->state = LEVEL_STATE_LOST_WAVE;
467   }
468 
469   if (level->nextState == LEVEL_STATE_BUILDING)
470   {
471     level->state = LEVEL_STATE_BUILDING;
472   }
473 
474   if (level->nextState == LEVEL_STATE_WON_LEVEL)
475   {
476     // make something of this later
477     InitLevel(level);
478   }
479 
480   level->nextState = LEVEL_STATE_NONE;
481 }
482 
483 float nextSpawnTime = 0.0f;
484 
485 void ResetGame()
486 {
487   InitLevel(currentLevel);
488 }
489 
490 void InitGame()
491 {
492   TowerInit();
493   EnemyInit();
494   ProjectileInit();
495   ParticleInit();
496   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
497 
498   currentLevel = levels;
499   InitLevel(currentLevel);
500 }
501 
502 //# Immediate GUI functions
503 
504 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth)
505 {
506   const float healthBarHeight = 6.0f;
507   const float healthBarOffset = 15.0f;
508   const float inset = 2.0f;
509   const float innerWidth = healthBarWidth - inset * 2;
510   const float innerHeight = healthBarHeight - inset * 2;
511 
512   Vector2 screenPos = GetWorldToScreen(position, camera);
513   float centerX = screenPos.x - healthBarWidth * 0.5f;
514   float topY = screenPos.y - healthBarOffset;
515   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
516   float healthWidth = innerWidth * healthRatio;
517   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
518 }
519 
520 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
521 {
522   Rectangle bounds = {x, y, width, height};
523   int isPressed = 0;
524   int isSelected = state && state->isSelected;
525   int isDisabled = state && state->isDisabled;
526   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !isDisabled)
527   {
528     Color color = isSelected ? DARKGRAY : GRAY;
529     DrawRectangle(x, y, width, height, color);
530     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
531     {
532       isPressed = 1;
533     }
534     guiState.isBlocked = 1;
535   }
536   else
537   {
538     Color color = isSelected ? WHITE : LIGHTGRAY;
539     DrawRectangle(x, y, width, height, color);
540   }
541   Font font = GetFontDefault();
542   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
543   Color textColor = isDisabled ? GRAY : BLACK;
544   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
545   return isPressed;
546 }
547 
548 //# Main game loop
549 
550 void GameUpdate()
551 {
552   float dt = GetFrameTime();
553   // cap maximum delta time to 0.1 seconds to prevent large time steps
554   if (dt > 0.1f) dt = 0.1f;
555   gameTime.time += dt;
556   gameTime.deltaTime = dt;
557 
558   UpdateLevel(currentLevel);
559 }
560 
561 int main(void)
562 {
563   int screenWidth, screenHeight;
564   GetPreferredSize(&screenWidth, &screenHeight);
565   InitWindow(screenWidth, screenHeight, "Tower defense");
566   SetTargetFPS(30);
567 
568   LoadAssets();
569   InitGame();
570 
571   while (!WindowShouldClose())
572   {
573     if (IsPaused()) {
574       // canvas is not visible in browser - do nothing
575       continue;
576     }
577 
578     BeginDrawing();
579     ClearBackground((Color){0x4E, 0x63, 0x26, 0xFF});
580 
581     GameUpdate();
582     DrawLevel(currentLevel);
583 
584     EndDrawing();
585   }
586 
587   CloseWindow();
588 
589   return 0;
590 }
  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   int seed;
 86   LevelState state;
 87   LevelState nextState;
 88   Camera3D camera;
 89   int placementMode;
 90 
 91   int initialGold;
 92   int playerGold;
 93 
 94   EnemyWave waves[10];
 95   int currentWave;
 96   float waveEndTimer;
 97 } Level;
 98 
 99 typedef struct DeltaSrc
100 {
101   char x, y;
102 } DeltaSrc;
103 
104 typedef struct PathfindingMap
105 {
106   int width, height;
107   float scale;
108   float *distances;
109   long *towerIndex; 
110   DeltaSrc *deltaSrc;
111   float maxDistance;
112   Matrix toMapSpace;
113   Matrix toWorldSpace;
114 } PathfindingMap;
115 
116 // when we execute the pathfinding algorithm, we need to store the active nodes
117 // in a queue. Each node has a position, a distance from the start, and the
118 // position of the node that we came from.
119 typedef struct PathfindingNode
120 {
121   int16_t x, y, fromX, fromY;
122   float distance;
123 } PathfindingNode;
124 
125 typedef struct EnemyId
126 {
127   uint16_t index;
128   uint16_t generation;
129 } EnemyId;
130 
131 typedef struct EnemyClassConfig
132 {
133   float speed;
134   float health;
135   float radius;
136   float maxAcceleration;
137   float requiredContactTime;
138   float explosionDamage;
139   float explosionRange;
140   float explosionPushbackPower;
141   int goldValue;
142 } EnemyClassConfig;
143 
144 typedef struct Enemy
145 {
146   int16_t currentX, currentY;
147   int16_t nextX, nextY;
148   Vector2 simPosition;
149   Vector2 simVelocity;
150   uint16_t generation;
151   float walkedDistance;
152   float startMovingTime;
153   float damage, futureDamage;
154   float contactTime;
155   uint8_t enemyType;
156   uint8_t movePathCount;
157   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
158 } Enemy;
159 
160 // a unit that uses sprites to be drawn
161 #define SPRITE_UNIT_PHASE_WEAPON_IDLE 0
162 #define SPRITE_UNIT_PHASE_WEAPON_COOLDOWN 1
163 typedef struct SpriteUnit
164 {
165   Rectangle srcRect;
166   Vector2 offset;
167   int frameCount;
168   float frameDuration;
169   Rectangle srcWeaponIdleRect;
170   Vector2 srcWeaponIdleOffset;
171   Rectangle srcWeaponCooldownRect;
172   Vector2 srcWeaponCooldownOffset;
173 } SpriteUnit;
174 
175 #define PROJECTILE_MAX_COUNT 1200
176 #define PROJECTILE_TYPE_NONE 0
177 #define PROJECTILE_TYPE_ARROW 1
178 
179 typedef struct Projectile
180 {
181   uint8_t projectileType;
182   float shootTime;
183   float arrivalTime;
184   float distance;
185   float damage;
186   Vector3 position;
187   Vector3 target;
188   Vector3 directionNormal;
189   EnemyId targetEnemy;
190 } Projectile;
191 
192 //# Function declarations
193 float TowerGetMaxHealth(Tower *tower);
194 int Button(const char *text, int x, int y, int width, int height, ButtonState *state);
195 int EnemyAddDamage(Enemy *enemy, float damage);
196 
197 //# Enemy functions
198 void EnemyInit();
199 void EnemyDraw();
200 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource);
201 void EnemyUpdate();
202 float EnemyGetCurrentMaxSpeed(Enemy *enemy);
203 float EnemyGetMaxHealth(Enemy *enemy);
204 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY);
205 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount);
206 EnemyId EnemyGetId(Enemy *enemy);
207 Enemy *EnemyTryResolve(EnemyId enemyId);
208 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY);
209 int EnemyAddDamage(Enemy *enemy, float damage);
210 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range);
211 int EnemyCount();
212 void EnemyDrawHealthbars(Camera3D camera);
213 
214 //# Tower functions
215 void TowerInit();
216 Tower *TowerGetAt(int16_t x, int16_t y);
217 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y);
218 Tower *GetTowerByType(uint8_t towerType);
219 int GetTowerCosts(uint8_t towerType);
220 float TowerGetMaxHealth(Tower *tower);
221 void TowerDraw();
222 void TowerUpdate();
223 void TowerDrawHealthBars(Camera3D camera);
224 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase);
225 
226 //# Particles
227 void ParticleInit();
228 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime);
229 void ParticleUpdate();
230 void ParticleDraw();
231 
232 //# Projectiles
233 void ProjectileInit();
234 void ProjectileDraw();
235 void ProjectileUpdate();
236 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, float damage);
237 
238 //# Pathfinding map
239 void PathfindingMapInit(int width, int height, Vector3 translate, float scale);
240 float PathFindingGetDistance(int mapX, int mapY);
241 Vector2 PathFindingGetGradient(Vector3 world);
242 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY);
243 void PathFindingMapUpdate();
244 void PathFindingMapDraw();
245 
246 //# UI
247 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth);
248 
249 //# Level
250 void DrawLevelGround(Level *level);
251 
252 //# variables
253 extern Level *currentLevel;
254 extern Enemy enemies[ENEMY_MAX_COUNT];
255 extern int enemyCount;
256 extern EnemyClassConfig enemyClassConfigs[];
257 
258 extern GUIState guiState;
259 extern GameTime gameTime;
260 extern Tower towers[TOWER_MAX_COUNT];
261 extern int towerCount;
262 
263 extern Texture2D palette, spriteSheet;
264 
265 #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, 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 "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 
  9 // definition of our archer unit
 10 SpriteUnit archerUnit = {
 11     .srcRect = {0, 0, 16, 16},
 12     .offset = {7, 1},
 13     .frameCount = 1,
 14     .frameDuration = 0.0f,
 15     .srcWeaponIdleRect = {16, 0, 6, 16},
 16     .srcWeaponIdleOffset = {8, 0},
 17     .srcWeaponCooldownRect = {22, 0, 11, 16},
 18     .srcWeaponCooldownOffset = {10, 0},
 19 };
 20 
 21 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
 22 {
 23   float xScale = flip ? -1.0f : 1.0f;
 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 * xScale };
 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   if (flip)
 40   {
 41     srcRect.x += srcRect.width;
 42     srcRect.width = -srcRect.width;
 43   }
 44   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 45 
 46   if (phase == SPRITE_UNIT_PHASE_WEAPON_COOLDOWN && unit.srcWeaponCooldownRect.width > 0)
 47   {
 48     offset = (Vector2){ unit.srcWeaponCooldownOffset.x / 16.0f * size, unit.srcWeaponCooldownOffset.y / 16.0f * size };
 49     scale = (Vector2){ unit.srcWeaponCooldownRect.width / 16.0f * size, unit.srcWeaponCooldownRect.height / 16.0f * size };
 50     srcRect = unit.srcWeaponCooldownRect;
 51     if (flip)
 52     {
 53       // position.x = flip * scale.x * 0.5f;
 54       srcRect.x += srcRect.width;
 55       srcRect.width = -srcRect.width;
 56       offset.x = scale.x - offset.x;
 57     }
 58     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 59   }
 60   else if (phase == SPRITE_UNIT_PHASE_WEAPON_IDLE && unit.srcWeaponIdleRect.width > 0)
 61   {
 62     offset = (Vector2){ unit.srcWeaponIdleOffset.x / 16.0f * size, unit.srcWeaponIdleOffset.y / 16.0f * size };
 63     scale = (Vector2){ unit.srcWeaponIdleRect.width / 16.0f * size, unit.srcWeaponIdleRect.height / 16.0f * size };
 64     srcRect = unit.srcWeaponIdleRect;
 65     if (flip)
 66     {
 67       // position.x = flip * scale.x * 0.5f;
 68       srcRect.x += srcRect.width;
 69       srcRect.width = -srcRect.width;
 70       offset.x = scale.x - offset.x;
 71     }
 72     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 73   }
 74 }
 75 
 76 void TowerInit()
 77 {
 78   for (int i = 0; i < TOWER_MAX_COUNT; i++)
 79   {
 80     towers[i] = (Tower){0};
 81   }
 82   towerCount = 0;
 83 
 84   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
 85   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
 86 
 87   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
 88   {
 89     if (towerModels[i].materials)
 90     {
 91       // assign the palette texture to the material of the model (0 is not used afaik)
 92       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 93     }
 94   }
 95 }
 96 
 97 static void TowerGunUpdate(Tower *tower)
 98 {
 99   if (tower->cooldown <= 0)
100   {
101     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
102     if (enemy)
103     {
104       tower->cooldown = 0.5f;
105       // shoot the enemy; determine future position of the enemy
106       float bulletSpeed = 4.0f;
107       float bulletDamage = 3.0f;
108       Vector2 velocity = enemy->simVelocity;
109       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
110       Vector2 towerPosition = {tower->x, tower->y};
111       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
112       for (int i = 0; i < 8; i++) {
113         velocity = enemy->simVelocity;
114         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
115         float distance = Vector2Distance(towerPosition, futurePosition);
116         float eta2 = distance / bulletSpeed;
117         if (fabs(eta - eta2) < 0.01f) {
118           break;
119         }
120         eta = (eta2 + eta) * 0.5f;
121       }
122       ProjectileTryAdd(PROJECTILE_TYPE_ARROW, enemy, 
123         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
124         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
125         bulletSpeed, bulletDamage);
126       enemy->futureDamage += bulletDamage;
127       tower->lastTargetPosition = futurePosition;
128     }
129   }
130   else
131   {
132     tower->cooldown -= gameTime.deltaTime;
133   }
134 }
135 
136 Tower *TowerGetAt(int16_t x, int16_t y)
137 {
138   for (int i = 0; i < towerCount; i++)
139   {
140     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
141     {
142       return &towers[i];
143     }
144   }
145   return 0;
146 }
147 
148 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
149 {
150   if (towerCount >= TOWER_MAX_COUNT)
151   {
152     return 0;
153   }
154 
155   Tower *tower = TowerGetAt(x, y);
156   if (tower)
157   {
158     return 0;
159   }
160 
161   tower = &towers[towerCount++];
162   tower->x = x;
163   tower->y = y;
164   tower->towerType = towerType;
165   tower->cooldown = 0.0f;
166   tower->damage = 0.0f;
167   return tower;
168 }
169 
170 Tower *GetTowerByType(uint8_t towerType)
171 {
172   for (int i = 0; i < towerCount; i++)
173   {
174     if (towers[i].towerType == towerType)
175     {
176       return &towers[i];
177     }
178   }
179   return 0;
180 }
181 
182 int GetTowerCosts(uint8_t towerType)
183 {
184   switch (towerType)
185   {
186   case TOWER_TYPE_BASE:
187     return 0;
188   case TOWER_TYPE_GUN:
189     return 6;
190   case TOWER_TYPE_WALL:
191     return 2;
192   }
193   return 0;
194 }
195 
196 float TowerGetMaxHealth(Tower *tower)
197 {
198   switch (tower->towerType)
199   {
200   case TOWER_TYPE_BASE:
201     return 10.0f;
202   case TOWER_TYPE_GUN:
203     return 3.0f;
204   case TOWER_TYPE_WALL:
205     return 5.0f;
206   }
207   return 0.0f;
208 }
209 
210 void TowerDraw()
211 {
212   for (int i = 0; i < towerCount; i++)
213   {
214     Tower tower = towers[i];
215     if (tower.towerType == TOWER_TYPE_NONE)
216     {
217       continue;
218     }
219 
220     switch (tower.towerType)
221     {
222     case TOWER_TYPE_GUN:
223       {
224         Vector2 screenPosTower = GetWorldToScreen((Vector3){tower.x, 0.0f, tower.y}, currentLevel->camera);
225         Vector2 screenPosTarget = GetWorldToScreen((Vector3){tower.lastTargetPosition.x, 0.0f, tower.lastTargetPosition.y}, currentLevel->camera);
226         DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
227         DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0, screenPosTarget.x > screenPosTower.x, 
228           tower.cooldown > 0.2f ? SPRITE_UNIT_PHASE_WEAPON_COOLDOWN : SPRITE_UNIT_PHASE_WEAPON_IDLE);
229       }
230       break;
231     default:
232       if (towerModels[tower.towerType].materials)
233       {
234         DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
235       } else {
236         DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
237       }
238       break;
239     }
240   }
241 }
242 
243 void TowerUpdate()
244 {
245   for (int i = 0; i < towerCount; i++)
246   {
247     Tower *tower = &towers[i];
248     switch (tower->towerType)
249     {
250     case TOWER_TYPE_GUN:
251       TowerGunUpdate(tower);
252       break;
253     }
254   }
255 }
256 
257 void TowerDrawHealthBars(Camera3D camera)
258 {
259   for (int i = 0; i < towerCount; i++)
260   {
261     Tower *tower = &towers[i];
262     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
263     {
264       continue;
265     }
266     
267     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
268     float maxHealth = TowerGetMaxHealth(tower);
269     float health = maxHealth - tower->damage;
270     float healthRatio = health / maxHealth;
271     
272     DrawHealthBar(camera, position, healthRatio, GREEN, 35.0f);
273   }
274 }
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif
Now things are really coming together. Each drawframe (this is a bit inefficient, but let's ignore that for now) we fill a model array with all the models we have:
  1 Model borderModels[64];
  2 int modelCount = 0;
  3 int rockCount = 5;
  4 int firTreeCount = 2;
  5 int leafTreeCount = 2;
  6 
  7 borderModels[modelCount++] = grassPatchModel[0];
  8 for (int i = 0; i < rockCount; i++)
  9 {
 10   borderModels[modelCount++] = rockModels[i];
 11 }
 12 for (int i = 0; i < firTreeCount; i++)
 13 {
 14   borderModels[modelCount++] = firTreeModel[i];
 15 }
 16 for (int i = 0; i < leafTreeCount; i++)
 17 {
 18   borderModels[modelCount++] = treeModel[i];
 19 }
The borderModels array is our set, and the modelCount variable tells us how many models we have. The count of 64 is arbitrary, it just needs to be big enough to hold all models.
In the loop, we pick a random model through calling borderModels[GetRandomValue(0, modelCount - 1)]. It's important to subtract 1, otherwise we could randomly exceed the array bounds.
It's already looking quite fancy, but ... every time we reset the level, it kinda looks the same, even though the models and positions change. That's because the set of models is the same: The chance to pick a rock or a tree is always the same. There is currently also very little grass because we only have a single grass model. Let's give this a final touch and randomize the set of models we use in a way that the level looks sometimes more like a meadow and sometimes more like a rocky area:
  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 Model floorTileAModel = {0};
 11 Model floorTileBModel = {0};
 12 Model treeModel[2] = {0};
 13 Model firTreeModel[2] = {0};
 14 Model rockModels[5] = {0};
 15 Model grassPatchModel[1] = {0};
 16 
 17 Texture2D palette, spriteSheet;
 18 
 19 Level levels[] = {
 20   [0] = {
 21     .state = LEVEL_STATE_BUILDING,
 22     .initialGold = 20,
 23     .waves[0] = {
 24       .enemyType = ENEMY_TYPE_MINION,
 25       .wave = 0,
 26       .count = 10,
 27       .interval = 2.5f,
 28       .delay = 1.0f,
 29       .spawnPosition = {0, 6},
 30     },
 31     .waves[1] = {
 32       .enemyType = ENEMY_TYPE_MINION,
 33       .wave = 1,
 34       .count = 20,
 35       .interval = 1.5f,
 36       .delay = 1.0f,
 37       .spawnPosition = {0, 6},
 38     },
 39     .waves[2] = {
 40       .enemyType = ENEMY_TYPE_MINION,
 41       .wave = 2,
 42       .count = 30,
 43       .interval = 1.2f,
 44       .delay = 1.0f,
 45       .spawnPosition = {0, 6},
 46     }
 47   },
 48 };
 49 
 50 Level *currentLevel = levels;
 51 
 52 //# Game
 53 
 54 static Model LoadGLBModel(char *filename)
 55 {
 56   Model model = LoadModel(TextFormat("data/%s.glb",filename));
 57   if (model.materialCount > 1)
 58   {
 59     model.materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 60   }
 61   return model;
 62 }
 63 
 64 void LoadAssets()
 65 {
 66   // load a sprite sheet that contains all units
 67   spriteSheet = LoadTexture("data/spritesheet.png");
 68   SetTextureFilter(spriteSheet, TEXTURE_FILTER_BILINEAR);
 69 
 70   // we'll use a palette texture to colorize the all buildings and environment art
 71   palette = LoadTexture("data/palette.png");
 72   // The texture uses gradients on very small space, so we'll enable bilinear filtering
 73   SetTextureFilter(palette, TEXTURE_FILTER_BILINEAR);
 74 
 75   floorTileAModel = LoadGLBModel("floor-tile-a");
 76   floorTileBModel = LoadGLBModel("floor-tile-b");
 77   treeModel[0] = LoadGLBModel("leaftree-large-1-a");
 78   treeModel[1] = LoadGLBModel("leaftree-large-1-b");
 79   firTreeModel[0] = LoadGLBModel("firtree-1-a");
 80   firTreeModel[1] = LoadGLBModel("firtree-1-b");
 81   rockModels[0] = LoadGLBModel("rock-1");
 82   rockModels[1] = LoadGLBModel("rock-2");
 83   rockModels[2] = LoadGLBModel("rock-3");
 84   rockModels[3] = LoadGLBModel("rock-4");
 85   rockModels[4] = LoadGLBModel("rock-5");
 86   grassPatchModel[0] = LoadGLBModel("grass-patch-1");
 87 }
 88 
 89 void InitLevel(Level *level)
 90 {
 91   level->seed = (int)(GetTime() * 100.0f);
 92 
 93   TowerInit();
 94   EnemyInit();
 95   ProjectileInit();
 96   ParticleInit();
 97   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
 98 
 99   level->placementMode = 0;
100   level->state = LEVEL_STATE_BUILDING;
101   level->nextState = LEVEL_STATE_NONE;
102   level->playerGold = level->initialGold;
103   level->currentWave = 0;
104 
105   Camera *camera = &level->camera;
106   camera->position = (Vector3){4.0f, 8.0f, 8.0f};
107   camera->target = (Vector3){0.0f, 0.0f, 0.0f};
108   camera->up = (Vector3){0.0f, 1.0f, 0.0f};
109   camera->fovy = 10.0f;
110   camera->projection = CAMERA_ORTHOGRAPHIC;
111 }
112 
113 void DrawLevelHud(Level *level)
114 {
115   const char *text = TextFormat("Gold: %d", level->playerGold);
116   Font font = GetFontDefault();
117   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 120, 10}, font.baseSize * 2.0f, 2.0f, BLACK);
118   DrawTextEx(font, text, (Vector2){GetScreenWidth() - 122, 8}, font.baseSize * 2.0f, 2.0f, YELLOW);
119 }
120 
121 void DrawLevelReportLostWave(Level *level)
122 {
123   BeginMode3D(level->camera);
124   DrawLevelGround(level);
125   TowerDraw();
126   EnemyDraw();
127   ProjectileDraw();
128   ParticleDraw();
129   guiState.isBlocked = 0;
130   EndMode3D();
131 
132   TowerDrawHealthBars(level->camera);
133 
134   const char *text = "Wave lost";
135   int textWidth = MeasureText(text, 20);
136   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
137 
138   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
139   {
140     level->nextState = LEVEL_STATE_RESET;
141   }
142 }
143 
144 int HasLevelNextWave(Level *level)
145 {
146   for (int i = 0; i < 10; i++)
147   {
148     EnemyWave *wave = &level->waves[i];
149     if (wave->wave == level->currentWave)
150     {
151       return 1;
152     }
153   }
154   return 0;
155 }
156 
157 void DrawLevelReportWonWave(Level *level)
158 {
159   BeginMode3D(level->camera);
160   DrawLevelGround(level);
161   TowerDraw();
162   EnemyDraw();
163   ProjectileDraw();
164   ParticleDraw();
165   guiState.isBlocked = 0;
166   EndMode3D();
167 
168   TowerDrawHealthBars(level->camera);
169 
170   const char *text = "Wave won";
171   int textWidth = MeasureText(text, 20);
172   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
173 
174 
175   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
176   {
177     level->nextState = LEVEL_STATE_RESET;
178   }
179 
180   if (HasLevelNextWave(level))
181   {
182     if (Button("Prepare for next wave", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
183     {
184       level->nextState = LEVEL_STATE_BUILDING;
185     }
186   }
187   else {
188     if (Button("Level won", GetScreenWidth() - 300, GetScreenHeight() - 40, 300, 30, 0))
189     {
190       level->nextState = LEVEL_STATE_WON_LEVEL;
191     }
192   }
193 }
194 
195 void DrawBuildingBuildButton(Level *level, int x, int y, int width, int height, uint8_t towerType, const char *name)
196 {
197   static ButtonState buttonStates[8] = {0};
198   int cost = GetTowerCosts(towerType);
199   const char *text = TextFormat("%s: %d", name, cost);
200   buttonStates[towerType].isSelected = level->placementMode == towerType;
201   buttonStates[towerType].isDisabled = level->playerGold < cost;
202   if (Button(text, x, y, width, height, &buttonStates[towerType]))
203   {
204     level->placementMode = buttonStates[towerType].isSelected ? 0 : towerType;
205   }
206 }
207 
208 float GetRandomFloat(float min, float max)
209 {
210   int random = GetRandomValue(0, 0xfffffff);
211   return ((float)random / (float)0xfffffff) * (max - min) + min;
212 }
213 
214 void DrawLevelGround(Level *level)
215 {
216   // draw checkerboard ground pattern
217   for (int x = -5; x <= 5; x += 1)
218   {
219     for (int y = -5; y <= 5; y += 1)
220     {
221       Model *model = (x + y) % 2 == 0 ? &floorTileAModel : &floorTileBModel;
222       DrawModel(*model, (Vector3){x, 0.0f, y}, 1.0f, WHITE);
223     }
224   }
225 
226   int oldSeed = GetRandomValue(0, 0xfffffff);
227   SetRandomSeed(level->seed);
228   // increase probability for trees via duplicated entries
229   Model borderModels[64];
230   int maxRockCount = GetRandomValue(2, 6);
231   int maxTreeCount = GetRandomValue(10, 20);
232   int maxFirTreeCount = GetRandomValue(5, 10);
233   int maxLeafTreeCount = maxTreeCount - maxFirTreeCount;
234   int grassPatchCount = GetRandomValue(5, 30);
235 
236   int modelCount = 0;
237   for (int i = 0; i < maxRockCount && modelCount < 63; i++)
238   {
239     borderModels[modelCount++] = rockModels[GetRandomValue(0, 5)];
240   }
241   for (int i = 0; i < maxLeafTreeCount && modelCount < 63; i++)
242   {
243     borderModels[modelCount++] = treeModel[GetRandomValue(0, 1)];
244   }
245   for (int i = 0; i < maxFirTreeCount && modelCount < 63; i++)
246   {
247     borderModels[modelCount++] = firTreeModel[GetRandomValue(0, 1)];
248   }
249   for (int i = 0; i < grassPatchCount && modelCount < 63; i++)
250   {
251     borderModels[modelCount++] = grassPatchModel[0];
252   }
253 
254   // draw some objects around the border of the map
255   Vector3 up = {0, 1, 0};
256   // a pseudo random number generator to get the same result every time
257   const float wiggle = 0.75f;
258   const int layerCount = 3;
259   for (int layer = 0; layer < layerCount; layer++)
260   {
261     int layerPos = 6 + layer;
262     for (int x = -6 + layer; x <= 6 + layer; x += 1)
263     {
264       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
265         (Vector3){x + GetRandomFloat(0.0f, wiggle), 0.0f, -layerPos + GetRandomFloat(0.0f, wiggle)}, 
266         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
267       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
268         (Vector3){x + GetRandomFloat(0.0f, wiggle), 0.0f, layerPos + GetRandomFloat(0.0f, wiggle)}, 
269         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
270     }
271 
272     for (int z = -5 + layer; z <= 5 + layer; z += 1)
273     {
274       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
275         (Vector3){-layerPos + GetRandomFloat(0.0f, wiggle), 0.0f, z + GetRandomFloat(0.0f, wiggle)}, 
276         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
277       DrawModelEx(borderModels[GetRandomValue(0, modelCount - 1)], 
278         (Vector3){layerPos + GetRandomFloat(0.0f, wiggle), 0.0f, z + GetRandomFloat(0.0f, wiggle)}, 
279         up, GetRandomFloat(0.0f, 360), Vector3One(), WHITE);
280     }
281   }
282 
283   SetRandomSeed(oldSeed);
284 }
285 
286 void DrawLevelBuildingState(Level *level)
287 {
288   BeginMode3D(level->camera);
289   DrawLevelGround(level);
290   TowerDraw();
291   EnemyDraw();
292   ProjectileDraw();
293   ParticleDraw();
294 
295   Ray ray = GetScreenToWorldRay(GetMousePosition(), level->camera);
296   float planeDistance = ray.position.y / -ray.direction.y;
297   float planeX = ray.direction.x * planeDistance + ray.position.x;
298   float planeY = ray.direction.z * planeDistance + ray.position.z;
299   int16_t mapX = (int16_t)floorf(planeX + 0.5f);
300   int16_t mapY = (int16_t)floorf(planeY + 0.5f);
301   if (level->placementMode && !guiState.isBlocked && mapX >= -5 && mapX <= 5 && mapY >= -5 && mapY <= 5)
302   {
303     DrawCubeWires((Vector3){mapX, 0.2f, mapY}, 1.0f, 0.4f, 1.0f, RED);
304     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
305     {
306       if (TowerTryAdd(level->placementMode, mapX, mapY))
307       {
308         level->playerGold -= GetTowerCosts(level->placementMode);
309         level->placementMode = TOWER_TYPE_NONE;
310       }
311     }
312   }
313 
314   guiState.isBlocked = 0;
315 
316   EndMode3D();
317 
318   TowerDrawHealthBars(level->camera);
319 
320   static ButtonState buildWallButtonState = {0};
321   static ButtonState buildGunButtonState = {0};
322   buildWallButtonState.isSelected = level->placementMode == TOWER_TYPE_WALL;
323   buildGunButtonState.isSelected = level->placementMode == TOWER_TYPE_GUN;
324 
325   DrawBuildingBuildButton(level, 10, 10, 110, 30, TOWER_TYPE_WALL, "Wall");
326   DrawBuildingBuildButton(level, 10, 50, 110, 30, TOWER_TYPE_GUN, "Archer");
327 
328   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
329   {
330     level->nextState = LEVEL_STATE_RESET;
331   }
332   
333   if (Button("Begin waves", GetScreenWidth() - 160, GetScreenHeight() - 40, 160, 30, 0))
334   {
335     level->nextState = LEVEL_STATE_BATTLE;
336   }
337 
338   const char *text = "Building phase";
339   int textWidth = MeasureText(text, 20);
340   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
341 }
342 
343 void InitBattleStateConditions(Level *level)
344 {
345   level->state = LEVEL_STATE_BATTLE;
346   level->nextState = LEVEL_STATE_NONE;
347   level->waveEndTimer = 0.0f;
348   for (int i = 0; i < 10; i++)
349   {
350     EnemyWave *wave = &level->waves[i];
351     wave->spawned = 0;
352     wave->timeToSpawnNext = wave->delay;
353   }
354 }
355 
356 void DrawLevelBattleState(Level *level)
357 {
358   BeginMode3D(level->camera);
359   DrawLevelGround(level);
360   TowerDraw();
361   EnemyDraw();
362   ProjectileDraw();
363   ParticleDraw();
364   guiState.isBlocked = 0;
365   EndMode3D();
366 
367   EnemyDrawHealthbars(level->camera);
368   TowerDrawHealthBars(level->camera);
369 
370   if (Button("Reset level", 20, GetScreenHeight() - 40, 160, 30, 0))
371   {
372     level->nextState = LEVEL_STATE_RESET;
373   }
374 
375   int maxCount = 0;
376   int remainingCount = 0;
377   for (int i = 0; i < 10; i++)
378   {
379     EnemyWave *wave = &level->waves[i];
380     if (wave->wave != level->currentWave)
381     {
382       continue;
383     }
384     maxCount += wave->count;
385     remainingCount += wave->count - wave->spawned;
386   }
387   int aliveCount = EnemyCount();
388   remainingCount += aliveCount;
389 
390   const char *text = TextFormat("Battle phase: %03d%%", 100 - remainingCount * 100 / maxCount);
391   int textWidth = MeasureText(text, 20);
392   DrawText(text, (GetScreenWidth() - textWidth) * 0.5f, 20, 20, WHITE);
393 }
394 
395 void DrawLevel(Level *level)
396 {
397   switch (level->state)
398   {
399     case LEVEL_STATE_BUILDING: DrawLevelBuildingState(level); break;
400     case LEVEL_STATE_BATTLE: DrawLevelBattleState(level); break;
401     case LEVEL_STATE_WON_WAVE: DrawLevelReportWonWave(level); break;
402     case LEVEL_STATE_LOST_WAVE: DrawLevelReportLostWave(level); break;
403     default: break;
404   }
405 
406   DrawLevelHud(level);
407 }
408 
409 void UpdateLevel(Level *level)
410 {
411   if (level->state == LEVEL_STATE_BATTLE)
412   {
413     int activeWaves = 0;
414     for (int i = 0; i < 10; i++)
415     {
416       EnemyWave *wave = &level->waves[i];
417       if (wave->spawned >= wave->count || wave->wave != level->currentWave)
418       {
419         continue;
420       }
421       activeWaves++;
422       wave->timeToSpawnNext -= gameTime.deltaTime;
423       if (wave->timeToSpawnNext <= 0.0f)
424       {
425         Enemy *enemy = EnemyTryAdd(wave->enemyType, wave->spawnPosition.x, wave->spawnPosition.y);
426         if (enemy)
427         {
428           wave->timeToSpawnNext = wave->interval;
429           wave->spawned++;
430         }
431       }
432     }
433     if (GetTowerByType(TOWER_TYPE_BASE) == 0) {
434       level->waveEndTimer += gameTime.deltaTime;
435       if (level->waveEndTimer >= 2.0f)
436       {
437         level->nextState = LEVEL_STATE_LOST_WAVE;
438       }
439     }
440     else if (activeWaves == 0 && EnemyCount() == 0)
441     {
442       level->waveEndTimer += gameTime.deltaTime;
443       if (level->waveEndTimer >= 2.0f)
444       {
445         level->nextState = LEVEL_STATE_WON_WAVE;
446       }
447     }
448   }
449 
450   PathFindingMapUpdate();
451   EnemyUpdate();
452   TowerUpdate();
453   ProjectileUpdate();
454   ParticleUpdate();
455 
456   if (level->nextState == LEVEL_STATE_RESET)
457   {
458     InitLevel(level);
459   }
460   
461   if (level->nextState == LEVEL_STATE_BATTLE)
462   {
463     InitBattleStateConditions(level);
464   }
465   
466   if (level->nextState == LEVEL_STATE_WON_WAVE)
467   {
468     level->currentWave++;
469     level->state = LEVEL_STATE_WON_WAVE;
470   }
471   
472   if (level->nextState == LEVEL_STATE_LOST_WAVE)
473   {
474     level->state = LEVEL_STATE_LOST_WAVE;
475   }
476 
477   if (level->nextState == LEVEL_STATE_BUILDING)
478   {
479     level->state = LEVEL_STATE_BUILDING;
480   }
481 
482   if (level->nextState == LEVEL_STATE_WON_LEVEL)
483   {
484     // make something of this later
485     InitLevel(level);
486   }
487 
488   level->nextState = LEVEL_STATE_NONE;
489 }
490 
491 float nextSpawnTime = 0.0f;
492 
493 void ResetGame()
494 {
495   InitLevel(currentLevel);
496 }
497 
498 void InitGame()
499 {
500   TowerInit();
501   EnemyInit();
502   ProjectileInit();
503   ParticleInit();
504   PathfindingMapInit(20, 20, (Vector3){-10.0f, 0.0f, -10.0f}, 1.0f);
505 
506   currentLevel = levels;
507   InitLevel(currentLevel);
508 }
509 
510 //# Immediate GUI functions
511 
512 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth)
513 {
514   const float healthBarHeight = 6.0f;
515   const float healthBarOffset = 15.0f;
516   const float inset = 2.0f;
517   const float innerWidth = healthBarWidth - inset * 2;
518   const float innerHeight = healthBarHeight - inset * 2;
519 
520   Vector2 screenPos = GetWorldToScreen(position, camera);
521   float centerX = screenPos.x - healthBarWidth * 0.5f;
522   float topY = screenPos.y - healthBarOffset;
523   DrawRectangle(centerX, topY, healthBarWidth, healthBarHeight, BLACK);
524   float healthWidth = innerWidth * healthRatio;
525   DrawRectangle(centerX + inset, topY + inset, healthWidth, innerHeight, barColor);
526 }
527 
528 int Button(const char *text, int x, int y, int width, int height, ButtonState *state)
529 {
530   Rectangle bounds = {x, y, width, height};
531   int isPressed = 0;
532   int isSelected = state && state->isSelected;
533   int isDisabled = state && state->isDisabled;
534   if (CheckCollisionPointRec(GetMousePosition(), bounds) && !guiState.isBlocked && !isDisabled)
535   {
536     Color color = isSelected ? DARKGRAY : GRAY;
537     DrawRectangle(x, y, width, height, color);
538     if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
539     {
540       isPressed = 1;
541     }
542     guiState.isBlocked = 1;
543   }
544   else
545   {
546     Color color = isSelected ? WHITE : LIGHTGRAY;
547     DrawRectangle(x, y, width, height, color);
548   }
549   Font font = GetFontDefault();
550   Vector2 textSize = MeasureTextEx(font, text, font.baseSize * 2.0f, 1);
551   Color textColor = isDisabled ? GRAY : BLACK;
552   DrawTextEx(font, text, (Vector2){x + width / 2 - textSize.x / 2, y + height / 2 - textSize.y / 2}, font.baseSize * 2.0f, 1, textColor);
553   return isPressed;
554 }
555 
556 //# Main game loop
557 
558 void GameUpdate()
559 {
560   float dt = GetFrameTime();
561   // cap maximum delta time to 0.1 seconds to prevent large time steps
562   if (dt > 0.1f) dt = 0.1f;
563   gameTime.time += dt;
564   gameTime.deltaTime = dt;
565 
566   UpdateLevel(currentLevel);
567 }
568 
569 int main(void)
570 {
571   int screenWidth, screenHeight;
572   GetPreferredSize(&screenWidth, &screenHeight);
573   InitWindow(screenWidth, screenHeight, "Tower defense");
574   SetTargetFPS(30);
575 
576   LoadAssets();
577   InitGame();
578 
579   while (!WindowShouldClose())
580   {
581     if (IsPaused()) {
582       // canvas is not visible in browser - do nothing
583       continue;
584     }
585 
586     BeginDrawing();
587     ClearBackground((Color){0x4E, 0x63, 0x26, 0xFF});
588 
589     GameUpdate();
590     DrawLevel(currentLevel);
591 
592     EndDrawing();
593   }
594 
595   CloseWindow();
596 
597   return 0;
598 }
  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   int seed;
 86   LevelState state;
 87   LevelState nextState;
 88   Camera3D camera;
 89   int placementMode;
 90 
 91   int initialGold;
 92   int playerGold;
 93 
 94   EnemyWave waves[10];
 95   int currentWave;
 96   float waveEndTimer;
 97 } Level;
 98 
 99 typedef struct DeltaSrc
100 {
101   char x, y;
102 } DeltaSrc;
103 
104 typedef struct PathfindingMap
105 {
106   int width, height;
107   float scale;
108   float *distances;
109   long *towerIndex; 
110   DeltaSrc *deltaSrc;
111   float maxDistance;
112   Matrix toMapSpace;
113   Matrix toWorldSpace;
114 } PathfindingMap;
115 
116 // when we execute the pathfinding algorithm, we need to store the active nodes
117 // in a queue. Each node has a position, a distance from the start, and the
118 // position of the node that we came from.
119 typedef struct PathfindingNode
120 {
121   int16_t x, y, fromX, fromY;
122   float distance;
123 } PathfindingNode;
124 
125 typedef struct EnemyId
126 {
127   uint16_t index;
128   uint16_t generation;
129 } EnemyId;
130 
131 typedef struct EnemyClassConfig
132 {
133   float speed;
134   float health;
135   float radius;
136   float maxAcceleration;
137   float requiredContactTime;
138   float explosionDamage;
139   float explosionRange;
140   float explosionPushbackPower;
141   int goldValue;
142 } EnemyClassConfig;
143 
144 typedef struct Enemy
145 {
146   int16_t currentX, currentY;
147   int16_t nextX, nextY;
148   Vector2 simPosition;
149   Vector2 simVelocity;
150   uint16_t generation;
151   float walkedDistance;
152   float startMovingTime;
153   float damage, futureDamage;
154   float contactTime;
155   uint8_t enemyType;
156   uint8_t movePathCount;
157   Vector2 movePath[ENEMY_MAX_PATH_COUNT];
158 } Enemy;
159 
160 // a unit that uses sprites to be drawn
161 #define SPRITE_UNIT_PHASE_WEAPON_IDLE 0
162 #define SPRITE_UNIT_PHASE_WEAPON_COOLDOWN 1
163 typedef struct SpriteUnit
164 {
165   Rectangle srcRect;
166   Vector2 offset;
167   int frameCount;
168   float frameDuration;
169   Rectangle srcWeaponIdleRect;
170   Vector2 srcWeaponIdleOffset;
171   Rectangle srcWeaponCooldownRect;
172   Vector2 srcWeaponCooldownOffset;
173 } SpriteUnit;
174 
175 #define PROJECTILE_MAX_COUNT 1200
176 #define PROJECTILE_TYPE_NONE 0
177 #define PROJECTILE_TYPE_ARROW 1
178 
179 typedef struct Projectile
180 {
181   uint8_t projectileType;
182   float shootTime;
183   float arrivalTime;
184   float distance;
185   float damage;
186   Vector3 position;
187   Vector3 target;
188   Vector3 directionNormal;
189   EnemyId targetEnemy;
190 } Projectile;
191 
192 //# Function declarations
193 float TowerGetMaxHealth(Tower *tower);
194 int Button(const char *text, int x, int y, int width, int height, ButtonState *state);
195 int EnemyAddDamage(Enemy *enemy, float damage);
196 
197 //# Enemy functions
198 void EnemyInit();
199 void EnemyDraw();
200 void EnemyTriggerExplode(Enemy *enemy, Tower *tower, Vector3 explosionSource);
201 void EnemyUpdate();
202 float EnemyGetCurrentMaxSpeed(Enemy *enemy);
203 float EnemyGetMaxHealth(Enemy *enemy);
204 int EnemyGetNextPosition(int16_t currentX, int16_t currentY, int16_t *nextX, int16_t *nextY);
205 Vector2 EnemyGetPosition(Enemy *enemy, float deltaT, Vector2 *velocity, int *waypointPassedCount);
206 EnemyId EnemyGetId(Enemy *enemy);
207 Enemy *EnemyTryResolve(EnemyId enemyId);
208 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY);
209 int EnemyAddDamage(Enemy *enemy, float damage);
210 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range);
211 int EnemyCount();
212 void EnemyDrawHealthbars(Camera3D camera);
213 
214 //# Tower functions
215 void TowerInit();
216 Tower *TowerGetAt(int16_t x, int16_t y);
217 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y);
218 Tower *GetTowerByType(uint8_t towerType);
219 int GetTowerCosts(uint8_t towerType);
220 float TowerGetMaxHealth(Tower *tower);
221 void TowerDraw();
222 void TowerUpdate();
223 void TowerDrawHealthBars(Camera3D camera);
224 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase);
225 
226 //# Particles
227 void ParticleInit();
228 void ParticleAdd(uint8_t particleType, Vector3 position, Vector3 velocity, float lifetime);
229 void ParticleUpdate();
230 void ParticleDraw();
231 
232 //# Projectiles
233 void ProjectileInit();
234 void ProjectileDraw();
235 void ProjectileUpdate();
236 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector3 position, Vector3 target, float speed, float damage);
237 
238 //# Pathfinding map
239 void PathfindingMapInit(int width, int height, Vector3 translate, float scale);
240 float PathFindingGetDistance(int mapX, int mapY);
241 Vector2 PathFindingGetGradient(Vector3 world);
242 int PathFindingFromWorldToMapPosition(Vector3 worldPosition, int16_t *mapX, int16_t *mapY);
243 void PathFindingMapUpdate();
244 void PathFindingMapDraw();
245 
246 //# UI
247 void DrawHealthBar(Camera3D camera, Vector3 position, float healthRatio, Color barColor, float healthBarWidth);
248 
249 //# Level
250 void DrawLevelGround(Level *level);
251 
252 //# variables
253 extern Level *currentLevel;
254 extern Enemy enemies[ENEMY_MAX_COUNT];
255 extern int enemyCount;
256 extern EnemyClassConfig enemyClassConfigs[];
257 
258 extern GUIState guiState;
259 extern GameTime gameTime;
260 extern Tower towers[TOWER_MAX_COUNT];
261 extern int towerCount;
262 
263 extern Texture2D palette, spriteSheet;
264 
265 #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, 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 "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 
  9 // definition of our archer unit
 10 SpriteUnit archerUnit = {
 11     .srcRect = {0, 0, 16, 16},
 12     .offset = {7, 1},
 13     .frameCount = 1,
 14     .frameDuration = 0.0f,
 15     .srcWeaponIdleRect = {16, 0, 6, 16},
 16     .srcWeaponIdleOffset = {8, 0},
 17     .srcWeaponCooldownRect = {22, 0, 11, 16},
 18     .srcWeaponCooldownOffset = {10, 0},
 19 };
 20 
 21 void DrawSpriteUnit(SpriteUnit unit, Vector3 position, float t, int flip, int phase)
 22 {
 23   float xScale = flip ? -1.0f : 1.0f;
 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 * xScale };
 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   if (flip)
 40   {
 41     srcRect.x += srcRect.width;
 42     srcRect.width = -srcRect.width;
 43   }
 44   DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 45 
 46   if (phase == SPRITE_UNIT_PHASE_WEAPON_COOLDOWN && unit.srcWeaponCooldownRect.width > 0)
 47   {
 48     offset = (Vector2){ unit.srcWeaponCooldownOffset.x / 16.0f * size, unit.srcWeaponCooldownOffset.y / 16.0f * size };
 49     scale = (Vector2){ unit.srcWeaponCooldownRect.width / 16.0f * size, unit.srcWeaponCooldownRect.height / 16.0f * size };
 50     srcRect = unit.srcWeaponCooldownRect;
 51     if (flip)
 52     {
 53       // position.x = flip * scale.x * 0.5f;
 54       srcRect.x += srcRect.width;
 55       srcRect.width = -srcRect.width;
 56       offset.x = scale.x - offset.x;
 57     }
 58     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 59   }
 60   else if (phase == SPRITE_UNIT_PHASE_WEAPON_IDLE && unit.srcWeaponIdleRect.width > 0)
 61   {
 62     offset = (Vector2){ unit.srcWeaponIdleOffset.x / 16.0f * size, unit.srcWeaponIdleOffset.y / 16.0f * size };
 63     scale = (Vector2){ unit.srcWeaponIdleRect.width / 16.0f * size, unit.srcWeaponIdleRect.height / 16.0f * size };
 64     srcRect = unit.srcWeaponIdleRect;
 65     if (flip)
 66     {
 67       // position.x = flip * scale.x * 0.5f;
 68       srcRect.x += srcRect.width;
 69       srcRect.width = -srcRect.width;
 70       offset.x = scale.x - offset.x;
 71     }
 72     DrawBillboardPro(camera, spriteSheet, srcRect, position, up, scale, offset, 0, WHITE);
 73   }
 74 }
 75 
 76 void TowerInit()
 77 {
 78   for (int i = 0; i < TOWER_MAX_COUNT; i++)
 79   {
 80     towers[i] = (Tower){0};
 81   }
 82   towerCount = 0;
 83 
 84   towerModels[TOWER_TYPE_BASE] = LoadModel("data/keep.glb");
 85   towerModels[TOWER_TYPE_WALL] = LoadModel("data/wall-0000.glb");
 86 
 87   for (int i = 0; i < TOWER_TYPE_COUNT; i++)
 88   {
 89     if (towerModels[i].materials)
 90     {
 91       // assign the palette texture to the material of the model (0 is not used afaik)
 92       towerModels[i].materials[1].maps[MATERIAL_MAP_DIFFUSE].texture = palette;
 93     }
 94   }
 95 }
 96 
 97 static void TowerGunUpdate(Tower *tower)
 98 {
 99   if (tower->cooldown <= 0)
100   {
101     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
102     if (enemy)
103     {
104       tower->cooldown = 0.5f;
105       // shoot the enemy; determine future position of the enemy
106       float bulletSpeed = 4.0f;
107       float bulletDamage = 3.0f;
108       Vector2 velocity = enemy->simVelocity;
109       Vector2 futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime, &velocity, 0);
110       Vector2 towerPosition = {tower->x, tower->y};
111       float eta = Vector2Distance(towerPosition, futurePosition) / bulletSpeed;
112       for (int i = 0; i < 8; i++) {
113         velocity = enemy->simVelocity;
114         futurePosition = EnemyGetPosition(enemy, gameTime.time - enemy->startMovingTime + eta, &velocity, 0);
115         float distance = Vector2Distance(towerPosition, futurePosition);
116         float eta2 = distance / bulletSpeed;
117         if (fabs(eta - eta2) < 0.01f) {
118           break;
119         }
120         eta = (eta2 + eta) * 0.5f;
121       }
122       ProjectileTryAdd(PROJECTILE_TYPE_ARROW, enemy, 
123         (Vector3){towerPosition.x, 1.33f, towerPosition.y}, 
124         (Vector3){futurePosition.x, 0.25f, futurePosition.y},
125         bulletSpeed, bulletDamage);
126       enemy->futureDamage += bulletDamage;
127       tower->lastTargetPosition = futurePosition;
128     }
129   }
130   else
131   {
132     tower->cooldown -= gameTime.deltaTime;
133   }
134 }
135 
136 Tower *TowerGetAt(int16_t x, int16_t y)
137 {
138   for (int i = 0; i < towerCount; i++)
139   {
140     if (towers[i].x == x && towers[i].y == y && towers[i].towerType != TOWER_TYPE_NONE)
141     {
142       return &towers[i];
143     }
144   }
145   return 0;
146 }
147 
148 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
149 {
150   if (towerCount >= TOWER_MAX_COUNT)
151   {
152     return 0;
153   }
154 
155   Tower *tower = TowerGetAt(x, y);
156   if (tower)
157   {
158     return 0;
159   }
160 
161   tower = &towers[towerCount++];
162   tower->x = x;
163   tower->y = y;
164   tower->towerType = towerType;
165   tower->cooldown = 0.0f;
166   tower->damage = 0.0f;
167   return tower;
168 }
169 
170 Tower *GetTowerByType(uint8_t towerType)
171 {
172   for (int i = 0; i < towerCount; i++)
173   {
174     if (towers[i].towerType == towerType)
175     {
176       return &towers[i];
177     }
178   }
179   return 0;
180 }
181 
182 int GetTowerCosts(uint8_t towerType)
183 {
184   switch (towerType)
185   {
186   case TOWER_TYPE_BASE:
187     return 0;
188   case TOWER_TYPE_GUN:
189     return 6;
190   case TOWER_TYPE_WALL:
191     return 2;
192   }
193   return 0;
194 }
195 
196 float TowerGetMaxHealth(Tower *tower)
197 {
198   switch (tower->towerType)
199   {
200   case TOWER_TYPE_BASE:
201     return 10.0f;
202   case TOWER_TYPE_GUN:
203     return 3.0f;
204   case TOWER_TYPE_WALL:
205     return 5.0f;
206   }
207   return 0.0f;
208 }
209 
210 void TowerDraw()
211 {
212   for (int i = 0; i < towerCount; i++)
213   {
214     Tower tower = towers[i];
215     if (tower.towerType == TOWER_TYPE_NONE)
216     {
217       continue;
218     }
219 
220     switch (tower.towerType)
221     {
222     case TOWER_TYPE_GUN:
223       {
224         Vector2 screenPosTower = GetWorldToScreen((Vector3){tower.x, 0.0f, tower.y}, currentLevel->camera);
225         Vector2 screenPosTarget = GetWorldToScreen((Vector3){tower.lastTargetPosition.x, 0.0f, tower.lastTargetPosition.y}, currentLevel->camera);
226         DrawModel(towerModels[TOWER_TYPE_WALL], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
227         DrawSpriteUnit(archerUnit, (Vector3){tower.x, 1.0f, tower.y}, 0, screenPosTarget.x > screenPosTower.x, 
228           tower.cooldown > 0.2f ? SPRITE_UNIT_PHASE_WEAPON_COOLDOWN : SPRITE_UNIT_PHASE_WEAPON_IDLE);
229       }
230       break;
231     default:
232       if (towerModels[tower.towerType].materials)
233       {
234         DrawModel(towerModels[tower.towerType], (Vector3){tower.x, 0.0f, tower.y}, 1.0f, WHITE);
235       } else {
236         DrawCube((Vector3){tower.x, 0.5f, tower.y}, 1.0f, 1.0f, 1.0f, LIGHTGRAY);
237       }
238       break;
239     }
240   }
241 }
242 
243 void TowerUpdate()
244 {
245   for (int i = 0; i < towerCount; i++)
246   {
247     Tower *tower = &towers[i];
248     switch (tower->towerType)
249     {
250     case TOWER_TYPE_GUN:
251       TowerGunUpdate(tower);
252       break;
253     }
254   }
255 }
256 
257 void TowerDrawHealthBars(Camera3D camera)
258 {
259   for (int i = 0; i < towerCount; i++)
260   {
261     Tower *tower = &towers[i];
262     if (tower->towerType == TOWER_TYPE_NONE || tower->damage <= 0.0f)
263     {
264       continue;
265     }
266     
267     Vector3 position = (Vector3){tower->x, 0.5f, tower->y};
268     float maxHealth = TowerGetMaxHealth(tower);
269     float health = maxHealth - tower->damage;
270     float healthRatio = health / maxHealth;
271     
272     DrawHealthBar(camera, position, healthRatio, GREEN, 35.0f);
273   }
274 }
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif
When you reset the level a few times, you can see that the general look of the level can change quite a bit. Sometimes it's more grassy, sometimes it can be a thick forest.
The trick is to manipulate the counts of the models, eventually duplicating some entries to increase the probability of a certain model to be picked for drawing:
  1 Model borderModels[64];
  2 int maxRockCount = GetRandomValue(2, 6);
  3 int maxTreeCount = GetRandomValue(10, 20);
  4 int maxFirTreeCount = GetRandomValue(5, 10);
  5 int maxLeafTreeCount = maxTreeCount - maxFirTreeCount;
  6 int grassPatchCount = GetRandomValue(5, 30);
  7 
  8 int modelCount = 0;
  9 for (int i = 0; i < maxRockCount && modelCount < 63; i++)
 10 {
 11   borderModels[modelCount++] = rockModels[GetRandomValue(0, 5)];
 12 }
 13 for (int i = 0; i < maxLeafTreeCount && modelCount < 63; i++)
 14 {
 15   borderModels[modelCount++] = treeModel[GetRandomValue(0, 1)];
 16 }
 17 for (int i = 0; i < maxFirTreeCount && modelCount < 63; i++)
 18 {
 19   borderModels[modelCount++] = firTreeModel[GetRandomValue(0, 1)];
 20 }
 21 for (int i = 0; i < grassPatchCount && modelCount < 63; i++)
 22 {
 23   borderModels[modelCount++] = grassPatchModel[0];
 24 }
So in the beginning, we set how many models we will use for rocks, trees, fir trees, etc.. If, for example, the maxRockCount is set to 6, we will have a lot more rocks in the scene than if it is set to 2. There is also a balance between the different types of trees: The tree count is the sum of fir trees and leaf trees, so any forest will have a certain balance between the two types of trees.
Wrap up
The result is a quite natural looking environment. It's already much better looking than what we need at this point. The procedural placement here is only scratching the surface of what is possible, but I hope you get the idea of how this kind of random generation works. There is much more to explore, but for this game tutorial, I would say we are done.
In the next parts, I want to add more tower type graphics. This is quite a bit more complicated than what we've done so far:
Loading and manipulating 3d animated model files is actually quite complex. The raylib API is lacking a few features we will need here, which is why the next parts will be considerably more difficult (it also takes me much longer to prepare, but I hope I can keep the schedule). But my idea is to wrap the complicated parts into a few functions that are easy to use. It's a bit of a challenge, especially since I currently fear that this could be cause performance problems. But let's see how it goes. Until then: Happy coding!