Simple tower defense tutorial, part 1
With the last blog update, I added a straight forward way to my blog to build raylib WASM files from C code as part of my blog build process. Having this now as a tool, I would like to use this system to show how to write a simple tower defense game in C using raylib. This is going to be a series of blog posts, where I will show how to add features to the game step by step. I will try to keep the code as simple and short as possible and will explain the concepts behind the code.
Difficult wise, you need to be able to understand C and know a bit how raylib works. If you managed to compile a hello world project and maybe doing a bit more programming already, you should be able to follow. If anything is unclear, please let me know and I will try to improve the blog posts.
That being said, here's how the game looks and runs at the end of this blog post:
The game should work as following:
- A 3D top-down view
 - A grid-based map, 20x20
 - A castle that is placed in the middle of the map
 - Enemies spawning from configurable spawn points
 - Enemies moving towards the castle
 - When an enemy reaches the castle, the player loses health
 - The player can place towers on the map
 - Towers shoot at enemies in range
 - Enemies have health and die when they reach 0 health
 
Let's start
  1 #include "td-tut-1-main.h"
  2 
  3 int main(void)
  4 {
  5   int screenWidth, screenHeight;
  6   GetPreferredSize(&screenWidth, &screenHeight);
  7   InitWindow(screenWidth, screenHeight, "Tower defense");
  8   SetTargetFPS(30);
  9 
 10   while (!WindowShouldClose())
 11   {
 12     if (IsPaused()) {
 13       // canvas is not visible in browser - do nothing
 14       continue;
 15     }
 16     BeginDrawing();
 17     ClearBackground(DARKBLUE);
 18     DrawText("Hello Tower Defense Tutorial", 190, 200, 20, WHITE);
 19     EndDrawing();
 20   }
 21 
 22   CloseWindow();
 23 
 24   return 0;
 25 }
  1 #ifndef TD_TUT_1_MAIN_H
  2 #define TD_TUT_1_MAIN_H
  3 
  4 #include "raylib.h"
  5 #include "preferred_size.h"
  6 
  7 #endif
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif
We start out with a classic hello world program. Once this runs, we can start adding what we need for the game. A 3d grid and top down view makes sense to do next; note how the newly added or modified parts are highlighted in green. When you read the tutorial samples one after another, you can focus on those parts to see what has changed:
  1 #include "td-tut-2-main.h"
  2 
  3 int main(void)
  4 {
  5   int screenWidth, screenHeight;
  6   GetPreferredSize(&screenWidth, &screenHeight);
  7   InitWindow(screenWidth, screenHeight, "Tower defense");
  8   SetTargetFPS(30);
  9 
 10   Camera3D camera = {0};
 11   camera.position = (Vector3){0.0f, 10.0f, 5.0f};
 12   camera.target = (Vector3){0.0f, 0.0f, 0.0f};
 13   camera.up = (Vector3){0.0f, 0.0f, -1.0f};
 14   camera.fovy = 45.0f;
 15   camera.projection = CAMERA_PERSPECTIVE;
 16 
 17   while (!WindowShouldClose())
 18   {
 19     if (IsPaused()) {
 20       // canvas is not visible in browser - do nothing
 21       continue;
 22     }
 23     BeginDrawing();
 24     ClearBackground(DARKBLUE);
 25 
 26     BeginMode3D(camera);
 27     DrawGrid(10, 1.0f);
 28     EndMode3D();
 29 
 30     DrawText("Tower defense tutorial", 5, 5, 20, WHITE);
 31     EndDrawing();
 32   }
 33 
 34   CloseWindow();
 35 
 36   return 0;
 37 }
  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 #endif
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif
We start out with raylib's grid drawing functionality, which is quite straight forward. We will later replace it with our own grid drawing function. But it helps us to get a quick orientation of where we are looking at.
Now we need to think forward a little;
The grid.
- Kevin Flynn, Tron
Just like in the movie Tron, we have to picture what our information looks like. The process is important, since early decisions tend to have much impact on the later development.
There is much to consider and inevitably, we will make wrong decisions. This is something we should embrace - it's part of the process of developing games. In my experience, we should not try to write our code in a way that it can handle all future cases; instead, we should focus on making the code easily replaceable: Looking at code as modules and APIs between them, minimizing the dependencies between them. This reduces the amount of change needed when we have to replace a module.
But this is something that is to be learned over time. For now, let's look at what we need:
- We want to have a grid
 - Each cell is a square containing a tile
 - Each tile can be empty or contain a tower
 - Each tower has its own behavior
 - Enemies walk over the grid. One tile can potentially contain multiple enemies.
 
For the towers, let's consider two ways how to implement it: Either we have the grid array and each cell contains the tower data or we have a separate array that contains the towers with their position. The first approach has the problem that we always have to iterate over the whole grid to update each tower. If our grid is empty and is 20x20, we have to iterate over 400 cells just to do nothing. The second approach is more efficient, but we have to make sure that we don't place towers on top of each other and we also have to manage the list - deletion and insertion.
Another point to consider is, if a tower can occupy multiple cells. In that case, the first approach is more complicated: We'd need to block all cells the tower occupies, but only one cell should contain the tower data. The second approach is more straight forward: We just have to make sure that the tower is not placed on top of another tower.
We will go with the second approach: An array that contains the towers and their position. The idea that our towers could be tetris like shapes is interesting and I would like to keep the option open to explore this idea further. We may come back later to the idea to have a 2d array that contains all our towers so we can quickly look up towers by position, but starting out with a simple array is simple, also because we don't have too overthink things.
To keep things simple and short, let's use global variables for our data structures:
  1 #define TOWER_MAX_COUNT 400
  2 #define TOWER_TYPE_NONE 0
  3 #define TOWER_TYPE_BASE 1
  4 
  5 typedef struct Tower {
  6   uint16_t x, y;
  7   uint8_t towerType;
  8 } Tower;
  9 
 10 Tower towers[TOWER_MAX_COUNT];
 11 int towerCount = 0;
Our first goal is to place a tower on the grid, making sure that we don't place it on top of another tower and render a cube at the tower's position:
  1 #include "td-tut-2-main.h"
  2 
  3 #define TOWER_MAX_COUNT 400
  4 #define TOWER_TYPE_NONE 0
  5 #define TOWER_TYPE_BASE 1
  6 #define TOWER_TYPE_GUN 2
  7 
  8 typedef struct Tower
  9 {
 10   int16_t x, y;
 11   uint8_t towerType;
 12 } Tower;
 13 
 14 Tower towers[TOWER_MAX_COUNT];
 15 int towerCount = 0;
 16 
 17 void TowerInit()
 18 {
 19   for (int i = 0; i < TOWER_MAX_COUNT; i++)
 20   {
 21     towers[i] = (Tower){0};
 22   }
 23   towerCount = 0;
 24 }
 25 
 26 Tower *TowerGetAt(int16_t x, int16_t y)
 27 {
 28   for (int i = 0; i < towerCount; i++)
 29   {
 30     if (towers[i].x == x && towers[i].y == y)
 31     {
 32       return &towers[i];
 33     }
 34   }
 35   return 0;
 36 }
 37 
 38 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
 39 {
 40   if (towerCount >= TOWER_MAX_COUNT)
 41   {
 42     return 0;
 43   }
 44 
 45   Tower *tower = TowerGetAt(x, y);
 46   if (tower)
 47   {
 48     return 0;
 49   }
 50 
 51   tower = &towers[towerCount++];
 52   tower->x = x;
 53   tower->y = y;
 54   tower->towerType = towerType;
 55   return tower;
 56 }
 57 
 58 void TowerDraw()
 59 {
 60   for (int i = 0; i < towerCount; i++)
 61   {
 62     Tower tower = towers[i];
 63     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
 64     switch (tower.towerType)
 65     {
 66     case TOWER_TYPE_BASE:
 67       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
 68       break;
 69     case TOWER_TYPE_GUN:
 70       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
 71       break;
 72     }
 73   }
 74 }
 75 
 76 void InitGame()
 77 {
 78   TowerInit();
 79 
 80   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
 81   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
 82 }
 83 
 84 int main(void)
 85 {
 86   int screenWidth, screenHeight;
 87   GetPreferredSize(&screenWidth, &screenHeight);
 88   InitWindow(screenWidth, screenHeight, "Tower defense");
 89   SetTargetFPS(30);
 90 
 91   Camera3D camera = {0};
 92   camera.position = (Vector3){0.0f, 10.0f, 5.0f};
 93   camera.target = (Vector3){0.0f, 0.0f, 0.0f};
 94   camera.up = (Vector3){0.0f, 0.0f, -1.0f};
 95   camera.fovy = 45.0f;
 96   camera.projection = CAMERA_PERSPECTIVE;
 97 
 98   InitGame();
 99 
100   while (!WindowShouldClose())
101   {
102     if (IsPaused()) {
103       // canvas is not visible in browser - do nothing
104       continue;
105     }
106     BeginDrawing();
107     ClearBackground(DARKBLUE);
108 
109     BeginMode3D(camera);
110     DrawGrid(10, 1.0f);
111     TowerDraw();
112     EndMode3D();
113 
114     DrawText("Tower defense tutorial", 5, 5, 20, WHITE);
115     EndDrawing();
116   }
117 
118   CloseWindow();
119 
120   return 0;
121 }
  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 #endif
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif
For the enemies, we'll do something very similar; we'll have a separate array that contains the enemies and their position. To see where things are going, we'll just render a cube at the enemy's position. The enemy isn't moving yet:
  1 #include "td-tut-2-main.h"
  2 
  3 //# Enemies
  4 
  5 #define ENEMY_MAX_COUNT 400
  6 #define ENEMY_TYPE_NONE 0
  7 #define ENEMY_TYPE_MINION 1
  8 
  9 typedef struct Enemy
 10 {
 11   int16_t currentX, currentY;
 12   uint8_t enemyType;
 13 } Enemy;
 14 
 15 Enemy enemies[ENEMY_MAX_COUNT];
 16 int enemyCount = 0;
 17 
 18 void EnemyInit()
 19 {
 20   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 21   {
 22     enemies[i] = (Enemy){0};
 23   }
 24   enemyCount = 0;
 25 }
 26 
 27 void EnemyDraw()
 28 {
 29   for (int i = 0; i < enemyCount; i++)
 30   {
 31     Enemy enemy = enemies[i];
 32     switch (enemy.enemyType)
 33     {
 34     case ENEMY_TYPE_MINION:
 35       DrawCube((Vector3){enemy.currentX, 0.2f, enemy.currentY}, 0.4f, 0.4f, 0.4f, GREEN);
 36       break;
 37     }
 38   }
 39 }
 40 
 41 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
 42 {
 43   if (enemyCount >= ENEMY_MAX_COUNT)
 44   {
 45     return 0;
 46   }
 47 
 48   Enemy *enemy = &enemies[enemyCount++];
 49   enemy->currentX = currentX;
 50   enemy->currentY = currentY;
 51   enemy->enemyType = enemyType;
 52   return enemy;
 53 }
 54 
 55 //# Towers
 56 
 57 #define TOWER_MAX_COUNT 400
 58 #define TOWER_TYPE_NONE 0
 59 #define TOWER_TYPE_BASE 1
 60 #define TOWER_TYPE_GUN 2
 61 
 62 typedef struct Tower
 63 {
 64   int16_t x, y;
 65   uint8_t towerType;
 66 } Tower;
 67 
 68 Tower towers[TOWER_MAX_COUNT];
 69 int towerCount = 0;
 70 
 71 void TowerInit()
 72 {
 73   for (int i = 0; i < TOWER_MAX_COUNT; i++)
 74   {
 75     towers[i] = (Tower){0};
 76   }
 77   towerCount = 0;
 78 }
 79 
 80 Tower *TowerGetAt(int16_t x, int16_t y)
 81 {
 82   for (int i = 0; i < towerCount; i++)
 83   {
 84     if (towers[i].x == x && towers[i].y == y)
 85     {
 86       return &towers[i];
 87     }
 88   }
 89   return 0;
 90 }
 91 
 92 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
 93 {
 94   if (towerCount >= TOWER_MAX_COUNT)
 95   {
 96     return 0;
 97   }
 98 
 99   Tower *tower = TowerGetAt(x, y);
100   if (tower)
101   {
102     return 0;
103   }
104 
105   tower = &towers[towerCount++];
106   tower->x = x;
107   tower->y = y;
108   tower->towerType = towerType;
109   return tower;
110 }
111 
112 void TowerDraw()
113 {
114   for (int i = 0; i < towerCount; i++)
115   {
116     Tower tower = towers[i];
117     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
118     switch (tower.towerType)
119     {
120     case TOWER_TYPE_BASE:
121       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
122       break;
123     case TOWER_TYPE_GUN:
124       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
125       break;
126     }
127   }
128 }
129 
130 //# Game
131 
132 void InitGame()
133 {
134   TowerInit();
135   EnemyInit();
136 
137   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
138   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
139   EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4);
140 }
141 
142 int main(void)
143 {
144   int screenWidth, screenHeight;
145   GetPreferredSize(&screenWidth, &screenHeight);
146   InitWindow(screenWidth, screenHeight, "Tower defense");
147   SetTargetFPS(30);
148 
149   Camera3D camera = {0};
150   camera.position = (Vector3){0.0f, 10.0f, 5.0f};
151   camera.target = (Vector3){0.0f, 0.0f, 0.0f};
152   camera.up = (Vector3){0.0f, 0.0f, -1.0f};
153   camera.fovy = 45.0f;
154   camera.projection = CAMERA_PERSPECTIVE;
155 
156   InitGame();
157 
158   while (!WindowShouldClose())
159   {
160     if (IsPaused()) {
161       // canvas is not visible in browser - do nothing
162       continue;
163     }
164     BeginDrawing();
165     ClearBackground(DARKBLUE);
166 
167     BeginMode3D(camera);
168     DrawGrid(10, 1.0f);
169     TowerDraw();
170     EnemyDraw();
171     EndMode3D();
172 
173     DrawText("Tower defense tutorial", 5, 5, 20, WHITE);
174     EndDrawing();
175   }
176 
177   CloseWindow();
178 
179   return 0;
180 }
  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 #endif
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif
For the next step, we want to have the enemies move towards the castle, despawn them when they reach the castle and spawn new enemies at a random coordinate:
  1 #include "td-tut-2-main.h"
  2 #include <stdlib.h>
  3 #include <math.h>
  4 
  5 typedef struct GameTime
  6 {
  7   float time;
  8   float deltaTime;
  9 } GameTime;
 10 
 11 GameTime gameTime = {0};
 12 
 13 //# Enemies
 14 
 15 #define ENEMY_MAX_COUNT 400
 16 #define ENEMY_TYPE_NONE 0
 17 #define ENEMY_TYPE_MINION 1
 18 
 19 typedef struct Enemy
 20 {
 21   int16_t currentX, currentY;
 22   int16_t nextX, nextY;
 23   float startMovingTime;
 24   uint8_t enemyType;
 25 } Enemy;
 26 
 27 Enemy enemies[ENEMY_MAX_COUNT];
 28 int enemyCount = 0;
 29 
 30 void EnemyInit()
 31 {
 32   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 33   {
 34     enemies[i] = (Enemy){0};
 35   }
 36   enemyCount = 0;
 37 }
 38 
 39 void EnemyDraw()
 40 {
 41   for (int i = 0; i < enemyCount; i++)
 42   {
 43     Enemy enemy = enemies[i];
 44     switch (enemy.enemyType)
 45     {
 46     case ENEMY_TYPE_MINION:
 47       DrawCube((Vector3){enemy.currentX, 0.2f, enemy.currentY}, 0.4f, 0.4f, 0.4f, GREEN);
 48       break;
 49     }
 50   }
 51 }
 52 
 53 void EnemyUpdate()
 54 {
 55   const int16_t castleX = 0;
 56   const int16_t castleY = 0;
 57 
 58   for (int i = 0; i < enemyCount; i++)
 59   {
 60     Enemy *enemy = &enemies[i];
 61     if (enemy->enemyType == ENEMY_TYPE_NONE)
 62     {
 63       continue;
 64     }
 65     float speed = 1.0f;
 66     float transition = (gameTime.time - enemy->startMovingTime) * speed;
 67     if (transition >= 1.0f)
 68     {
 69       enemy->startMovingTime = gameTime.time;
 70       enemy->currentX = enemy->nextX;
 71       enemy->currentY = enemy->nextY;
 72       int16_t dx = castleX - enemy->currentX;
 73       int16_t dy = castleY - enemy->currentY;
 74       if (dx == 0 && dy == 0)
 75       {
 76         // enemy reached the castle; remove it
 77         enemy->enemyType = ENEMY_TYPE_NONE;
 78         continue;
 79       }
 80       if (abs(dx) > abs(dy))
 81       {
 82         enemy->nextX = enemy->currentX + (dx > 0 ? 1 : -1);
 83         enemy->nextY = enemy->currentY;
 84       }
 85       else
 86       {
 87         enemy->nextX = enemy->currentX;
 88         enemy->nextY = enemy->currentY + (dy > 0 ? 1 : -1);
 89       }
 90     }
 91   }
 92 }
 93 
 94 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
 95 {
 96   Enemy *spawn = 0;
 97   for (int i = 0; i < enemyCount; i++)
 98   {
 99     Enemy *enemy = &enemies[i];
100     if (enemy->enemyType == ENEMY_TYPE_NONE)
101     {
102       spawn = enemy;
103       break;
104     }
105   }
106 
107   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
108   {
109     spawn = &enemies[enemyCount++];
110   }
111 
112   if (spawn)
113   {
114     spawn->currentX = currentX;
115     spawn->currentY = currentY;
116     spawn->nextX = currentX;
117     spawn->nextY = currentY;
118     spawn->enemyType = enemyType;
119     spawn->startMovingTime = 0;
120   }
121 
122   return spawn;
123 }
124 
125 //# Towers
126 
127 #define TOWER_MAX_COUNT 400
128 #define TOWER_TYPE_NONE 0
129 #define TOWER_TYPE_BASE 1
130 #define TOWER_TYPE_GUN 2
131 
132 typedef struct Tower
133 {
134   int16_t x, y;
135   uint8_t towerType;
136 } Tower;
137 
138 Tower towers[TOWER_MAX_COUNT];
139 int towerCount = 0;
140 
141 void TowerInit()
142 {
143   for (int i = 0; i < TOWER_MAX_COUNT; i++)
144   {
145     towers[i] = (Tower){0};
146   }
147   towerCount = 0;
148 }
149 
150 Tower *TowerGetAt(int16_t x, int16_t y)
151 {
152   for (int i = 0; i < towerCount; i++)
153   {
154     if (towers[i].x == x && towers[i].y == y)
155     {
156       return &towers[i];
157     }
158   }
159   return 0;
160 }
161 
162 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
163 {
164   if (towerCount >= TOWER_MAX_COUNT)
165   {
166     return 0;
167   }
168 
169   Tower *tower = TowerGetAt(x, y);
170   if (tower)
171   {
172     return 0;
173   }
174 
175   tower = &towers[towerCount++];
176   tower->x = x;
177   tower->y = y;
178   tower->towerType = towerType;
179   return tower;
180 }
181 
182 void TowerDraw()
183 {
184   for (int i = 0; i < towerCount; i++)
185   {
186     Tower tower = towers[i];
187     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
188     switch (tower.towerType)
189     {
190     case TOWER_TYPE_BASE:
191       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
192       break;
193     case TOWER_TYPE_GUN:
194       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
195       break;
196     }
197   }
198 }
199 
200 //# Game
201 
202 float nextSpawnTime = 0.0f;
203 
204 void InitGame()
205 {
206   TowerInit();
207   EnemyInit();
208 
209   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
210   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
211   EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4);
212 }
213 
214 void GameUpdate()
215 {
216   float dt = GetFrameTime();
217   // cap maximum delta time to 0.1 seconds to prevent large time steps
218   if (dt > 0.1f) dt = 0.1f;
219   gameTime.time += dt;
220   gameTime.deltaTime = dt;
221   EnemyUpdate();
222 
223   // spawn a new enemy every second
224   if (gameTime.time >= nextSpawnTime)
225   {
226     nextSpawnTime = gameTime.time + 1.0f;
227     // add a new enemy at the boundary of the map
228     int randValue = GetRandomValue(-5, 5);
229     int randSide = GetRandomValue(0, 3);
230     int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue;
231     int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue;
232     EnemyTryAdd(ENEMY_TYPE_MINION, x, y);
233   }
234 }
235 
236 int main(void)
237 {
238   int screenWidth, screenHeight;
239   GetPreferredSize(&screenWidth, &screenHeight);
240   InitWindow(screenWidth, screenHeight, "Tower defense");
241   SetTargetFPS(30);
242 
243   Camera3D camera = {0};
244   camera.position = (Vector3){0.0f, 10.0f, 5.0f};
245   camera.target = (Vector3){0.0f, 0.0f, 0.0f};
246   camera.up = (Vector3){0.0f, 0.0f, -1.0f};
247   camera.fovy = 45.0f;
248   camera.projection = CAMERA_PERSPECTIVE;
249 
250   InitGame();
251 
252   while (!WindowShouldClose())
253   {
254     if (IsPaused()) {
255       // canvas is not visible in browser - do nothing
256       continue;
257     }
258     BeginDrawing();
259     ClearBackground(DARKBLUE);
260 
261     BeginMode3D(camera);
262     DrawGrid(10, 1.0f);
263     TowerDraw();
264     EnemyDraw();
265     GameUpdate();
266     EndMode3D();
267 
268     DrawText("Tower defense tutorial", 5, 5, 20, WHITE);
269     EndDrawing();
270   }
271 
272   CloseWindow();
273 
274   return 0;
275 }
  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 #endif
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif
The enemy movement is now very rough: They jump from field to field. This makes it hard to see where they are going or coming from. The way we've set it up, each enemy has the time when when they moved to the new field and the information, where they are going. We can use this information now to interpolate the enemy's position between the two fields. This way, the enemy moves smoothly from one field to the next:
  1 #include "td-tut-2-main.h"
  2 #include <stdlib.h>
  3 #include <math.h>
  4 
  5 typedef struct GameTime
  6 {
  7   float time;
  8   float deltaTime;
  9 } GameTime;
 10 
 11 GameTime gameTime = {0};
 12 
 13 //# Enemies
 14 
 15 #define ENEMY_MAX_COUNT 400
 16 #define ENEMY_TYPE_NONE 0
 17 #define ENEMY_TYPE_MINION 1
 18 
 19 typedef struct Enemy
 20 {
 21   int16_t currentX, currentY;
 22   int16_t nextX, nextY;
 23   float startMovingTime;
 24   uint8_t enemyType;
 25 } Enemy;
 26 
 27 Enemy enemies[ENEMY_MAX_COUNT];
 28 int enemyCount = 0;
 29 
 30 void EnemyInit()
 31 {
 32   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 33   {
 34     enemies[i] = (Enemy){0};
 35   }
 36   enemyCount = 0;
 37 }
 38 
 39 float EnemyGetCurrentSpeed(Enemy *enemy)
 40 {
 41   switch (enemy->enemyType)
 42   {
 43   case ENEMY_TYPE_MINION:
 44     return 1.0f;
 45   }
 46   return 1.0f;
 47 }
 48 
 49 void EnemyDraw()
 50 {
 51   for (int i = 0; i < enemyCount; i++)
 52   {
 53     Enemy enemy = enemies[i];
 54     float speed = EnemyGetCurrentSpeed(&enemy);
 55     float transition = (gameTime.time - enemy.startMovingTime) * speed;
 56 
 57     float x = enemy.currentX + (enemy.nextX - enemy.currentX) * transition;
 58     float y = enemy.currentY + (enemy.nextY - enemy.currentY) * transition;
 59     
 60     switch (enemy.enemyType)
 61     {
 62     case ENEMY_TYPE_MINION:
 63       DrawCube((Vector3){x, 0.2f, y}, 0.4f, 0.4f, 0.4f, GREEN);
 64       break;
 65     }
 66   }
 67 }
 68 
 69 void EnemyUpdate()
 70 {
 71   const int16_t castleX = 0;
 72   const int16_t castleY = 0;
 73 
 74   for (int i = 0; i < enemyCount; i++)
 75   {
 76     Enemy *enemy = &enemies[i];
 77     if (enemy->enemyType == ENEMY_TYPE_NONE)
 78     {
 79       continue;
 80     }
 81     float speed = EnemyGetCurrentSpeed(enemy);
 82     float transition = (gameTime.time - enemy->startMovingTime) * speed;
 83     if (transition >= 1.0f)
 84     {
 85       enemy->startMovingTime = gameTime.time;
 86       enemy->currentX = enemy->nextX;
 87       enemy->currentY = enemy->nextY;
 88       int16_t dx = castleX - enemy->currentX;
 89       int16_t dy = castleY - enemy->currentY;
 90       if (dx == 0 && dy == 0)
 91       {
 92         // enemy reached the castle; remove it
 93         enemy->enemyType = ENEMY_TYPE_NONE;
 94         continue;
 95       }
 96       if (abs(dx) > abs(dy))
 97       {
 98         enemy->nextX = enemy->currentX + (dx > 0 ? 1 : -1);
 99         enemy->nextY = enemy->currentY;
100       }
101       else
102       {
103         enemy->nextX = enemy->currentX;
104         enemy->nextY = enemy->currentY + (dy > 0 ? 1 : -1);
105       }
106     }
107   }
108 }
109 
110 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
111 {
112   Enemy *spawn = 0;
113   for (int i = 0; i < enemyCount; i++)
114   {
115     Enemy *enemy = &enemies[i];
116     if (enemy->enemyType == ENEMY_TYPE_NONE)
117     {
118       spawn = enemy;
119       break;
120     }
121   }
122 
123   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
124   {
125     spawn = &enemies[enemyCount++];
126   }
127 
128   if (spawn)
129   {
130     spawn->currentX = currentX;
131     spawn->currentY = currentY;
132     spawn->nextX = currentX;
133     spawn->nextY = currentY;
134     spawn->enemyType = enemyType;
135     spawn->startMovingTime = 0;
136   }
137 
138   return spawn;
139 }
140 
141 //# Towers
142 
143 #define TOWER_MAX_COUNT 400
144 #define TOWER_TYPE_NONE 0
145 #define TOWER_TYPE_BASE 1
146 #define TOWER_TYPE_GUN 2
147 
148 typedef struct Tower
149 {
150   int16_t x, y;
151   uint8_t towerType;
152 } Tower;
153 
154 Tower towers[TOWER_MAX_COUNT];
155 int towerCount = 0;
156 
157 void TowerInit()
158 {
159   for (int i = 0; i < TOWER_MAX_COUNT; i++)
160   {
161     towers[i] = (Tower){0};
162   }
163   towerCount = 0;
164 }
165 
166 Tower *TowerGetAt(int16_t x, int16_t y)
167 {
168   for (int i = 0; i < towerCount; i++)
169   {
170     if (towers[i].x == x && towers[i].y == y)
171     {
172       return &towers[i];
173     }
174   }
175   return 0;
176 }
177 
178 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
179 {
180   if (towerCount >= TOWER_MAX_COUNT)
181   {
182     return 0;
183   }
184 
185   Tower *tower = TowerGetAt(x, y);
186   if (tower)
187   {
188     return 0;
189   }
190 
191   tower = &towers[towerCount++];
192   tower->x = x;
193   tower->y = y;
194   tower->towerType = towerType;
195   return tower;
196 }
197 
198 void TowerDraw()
199 {
200   for (int i = 0; i < towerCount; i++)
201   {
202     Tower tower = towers[i];
203     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
204     switch (tower.towerType)
205     {
206     case TOWER_TYPE_BASE:
207       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
208       break;
209     case TOWER_TYPE_GUN:
210       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
211       break;
212     }
213   }
214 }
215 
216 //# Game
217 
218 float nextSpawnTime = 0.0f;
219 
220 void InitGame()
221 {
222   TowerInit();
223   EnemyInit();
224 
225   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
226   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
227   EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4);
228 }
229 
230 void GameUpdate()
231 {
232   float dt = GetFrameTime();
233   // cap maximum delta time to 0.1 seconds to prevent large time steps
234   if (dt > 0.1f) dt = 0.1f;
235   gameTime.time += dt;
236   gameTime.deltaTime = dt;
237   EnemyUpdate();
238 
239   // spawn a new enemy every second
240   if (gameTime.time >= nextSpawnTime)
241   {
242     nextSpawnTime = gameTime.time + 1.0f;
243     // add a new enemy at the boundary of the map
244     int randValue = GetRandomValue(-5, 5);
245     int randSide = GetRandomValue(0, 3);
246     int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue;
247     int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue;
248     EnemyTryAdd(ENEMY_TYPE_MINION, x, y);
249   }
250 }
251 
252 int main(void)
253 {
254   int screenWidth, screenHeight;
255   GetPreferredSize(&screenWidth, &screenHeight);
256   InitWindow(screenWidth, screenHeight, "Tower defense");
257   SetTargetFPS(30);
258 
259   Camera3D camera = {0};
260   camera.position = (Vector3){0.0f, 10.0f, 5.0f};
261   camera.target = (Vector3){0.0f, 0.0f, 0.0f};
262   camera.up = (Vector3){0.0f, 0.0f, -1.0f};
263   camera.fovy = 45.0f;
264   camera.projection = CAMERA_PERSPECTIVE;
265 
266   InitGame();
267 
268   while (!WindowShouldClose())
269   {
270     if (IsPaused()) {
271       // canvas is not visible in browser - do nothing
272       continue;
273     }
274     BeginDrawing();
275     ClearBackground(DARKBLUE);
276 
277     BeginMode3D(camera);
278     DrawGrid(10, 1.0f);
279     TowerDraw();
280     EnemyDraw();
281     GameUpdate();
282     EndMode3D();
283 
284     DrawText("Tower defense tutorial", 5, 5, 20, WHITE);
285     EndDrawing();
286   }
287 
288   CloseWindow();
289 
290   return 0;
291 }
  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 #endif
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif
The movement is now quite smooth, even though they still move in straight lines. For now, this is good enough, since we no longer have problems to recognize the enemy movement. The next step is to have the towers shoot at the enemies. There are several properties we have to consider:
- Each tower attacks the enemy that is closest to the castle and is in range
 - Each tower has a rate at which it can shoot
 - When an enemy reaches 0 health, it dies; we assume health = 1 for now, so 1 hit = kill
 
  1 #include "td-tut-2-main.h"
  2 #include <stdlib.h>
  3 #include <math.h>
  4 
  5 typedef struct GameTime
  6 {
  7   float time;
  8   float deltaTime;
  9 } GameTime;
 10 
 11 GameTime gameTime = {0};
 12 
 13 //# Enemies
 14 
 15 #define ENEMY_MAX_COUNT 400
 16 #define ENEMY_TYPE_NONE 0
 17 #define ENEMY_TYPE_MINION 1
 18 
 19 typedef struct Enemy
 20 {
 21   int16_t currentX, currentY;
 22   int16_t nextX, nextY;
 23   float startMovingTime;
 24   uint8_t enemyType;
 25 } Enemy;
 26 
 27 Enemy enemies[ENEMY_MAX_COUNT];
 28 int enemyCount = 0;
 29 
 30 void EnemyInit()
 31 {
 32   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 33   {
 34     enemies[i] = (Enemy){0};
 35   }
 36   enemyCount = 0;
 37 }
 38 
 39 float EnemyGetCurrentSpeed(Enemy *enemy)
 40 {
 41   switch (enemy->enemyType)
 42   {
 43   case ENEMY_TYPE_MINION:
 44     return 1.0f;
 45   }
 46   return 1.0f;
 47 }
 48 
 49 void EnemyDraw()
 50 {
 51   for (int i = 0; i < enemyCount; i++)
 52   {
 53     Enemy enemy = enemies[i];
 54     float speed = EnemyGetCurrentSpeed(&enemy);
 55     float transition = (gameTime.time - enemy.startMovingTime) * speed;
 56 
 57     float x = enemy.currentX + (enemy.nextX - enemy.currentX) * transition;
 58     float y = enemy.currentY + (enemy.nextY - enemy.currentY) * transition;
 59     
 60     switch (enemy.enemyType)
 61     {
 62     case ENEMY_TYPE_MINION:
 63       DrawCube((Vector3){x, 0.2f, y}, 0.4f, 0.4f, 0.4f, GREEN);
 64       break;
 65     }
 66   }
 67 }
 68 
 69 void EnemyUpdate()
 70 {
 71   const int16_t castleX = 0;
 72   const int16_t castleY = 0;
 73 
 74   for (int i = 0; i < enemyCount; i++)
 75   {
 76     Enemy *enemy = &enemies[i];
 77     if (enemy->enemyType == ENEMY_TYPE_NONE)
 78     {
 79       continue;
 80     }
 81     float speed = EnemyGetCurrentSpeed(enemy);
 82     float transition = (gameTime.time - enemy->startMovingTime) * speed;
 83     if (transition >= 1.0f)
 84     {
 85       enemy->startMovingTime = gameTime.time;
 86       enemy->currentX = enemy->nextX;
 87       enemy->currentY = enemy->nextY;
 88       int16_t dx = castleX - enemy->currentX;
 89       int16_t dy = castleY - enemy->currentY;
 90       if (dx == 0 && dy == 0)
 91       {
 92         // enemy reached the castle; remove it
 93         enemy->enemyType = ENEMY_TYPE_NONE;
 94         continue;
 95       }
 96       if (abs(dx) > abs(dy))
 97       {
 98         enemy->nextX = enemy->currentX + (dx > 0 ? 1 : -1);
 99         enemy->nextY = enemy->currentY;
100       }
101       else
102       {
103         enemy->nextX = enemy->currentX;
104         enemy->nextY = enemy->currentY + (dy > 0 ? 1 : -1);
105       }
106     }
107   }
108 }
109 
110 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
111 {
112   Enemy *spawn = 0;
113   for (int i = 0; i < enemyCount; i++)
114   {
115     Enemy *enemy = &enemies[i];
116     if (enemy->enemyType == ENEMY_TYPE_NONE)
117     {
118       spawn = enemy;
119       break;
120     }
121   }
122 
123   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
124   {
125     spawn = &enemies[enemyCount++];
126   }
127 
128   if (spawn)
129   {
130     spawn->currentX = currentX;
131     spawn->currentY = currentY;
132     spawn->nextX = currentX;
133     spawn->nextY = currentY;
134     spawn->enemyType = enemyType;
135     spawn->startMovingTime = 0;
136   }
137 
138   return spawn;
139 }
140 
141 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
142 {
143   int16_t castleX = 0;
144   int16_t castleY = 0;
145   Enemy* closest = 0;
146   int16_t closestDistance = 0;
147   float range2 = range * range;
148   for (int i = 0; i < enemyCount; i++)
149   {
150     Enemy* enemy = &enemies[i];
151     if (enemy->enemyType == ENEMY_TYPE_NONE)
152     {
153       continue;
154     }
155     int16_t dx = castleX - enemy->currentX;
156     int16_t dy = castleY - enemy->currentY;
157     int16_t distance = abs(dx) + abs(dy);
158     if (!closest || distance < closestDistance)
159     {
160       float tdx = towerX - enemy->currentX;
161       float tdy = towerY - enemy->currentY;
162       float tdistance2 = tdx * tdx + tdy * tdy;
163       if (tdistance2 <= range2)
164       {
165         closest = enemy;
166         closestDistance = distance;
167       }
168     }
169   }
170   return closest;
171 }
172 
173 //# Towers
174 
175 #define TOWER_MAX_COUNT 400
176 #define TOWER_TYPE_NONE 0
177 #define TOWER_TYPE_BASE 1
178 #define TOWER_TYPE_GUN 2
179 
180 typedef struct Tower
181 {
182   int16_t x, y;
183   uint8_t towerType;
184   float cooldown;
185 } Tower;
186 
187 Tower towers[TOWER_MAX_COUNT];
188 int towerCount = 0;
189 
190 void TowerInit()
191 {
192   for (int i = 0; i < TOWER_MAX_COUNT; i++)
193   {
194     towers[i] = (Tower){0};
195   }
196   towerCount = 0;
197 }
198 
199 Tower *TowerGetAt(int16_t x, int16_t y)
200 {
201   for (int i = 0; i < towerCount; i++)
202   {
203     if (towers[i].x == x && towers[i].y == y)
204     {
205       return &towers[i];
206     }
207   }
208   return 0;
209 }
210 
211 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
212 {
213   if (towerCount >= TOWER_MAX_COUNT)
214   {
215     return 0;
216   }
217 
218   Tower *tower = TowerGetAt(x, y);
219   if (tower)
220   {
221     return 0;
222   }
223 
224   tower = &towers[towerCount++];
225   tower->x = x;
226   tower->y = y;
227   tower->towerType = towerType;
228   return tower;
229 }
230 
231 void TowerDraw()
232 {
233   for (int i = 0; i < towerCount; i++)
234   {
235     Tower tower = towers[i];
236     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
237     switch (tower.towerType)
238     {
239     case TOWER_TYPE_BASE:
240       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
241       break;
242     case TOWER_TYPE_GUN:
243       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
244       break;
245     }
246   }
247 }
248 
249 void TowerGunUpdate(Tower *tower)
250 {
251   if (tower->cooldown <= 0)
252   {
253     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
254     if (enemy)
255     {
256       tower->cooldown = 1.0f;
257       // shoot the enemy
258       enemy->enemyType = ENEMY_TYPE_NONE;
259     }
260   }
261   else
262   {
263     tower->cooldown -= gameTime.deltaTime;
264   }
265 }
266 
267 void TowerUpdate()
268 {
269   for (int i = 0; i < towerCount; i++)
270   {
271     Tower *tower = &towers[i];
272     switch (tower->towerType)
273     {
274     case TOWER_TYPE_GUN:
275       TowerGunUpdate(tower);
276       break;
277     }
278   }
279 }
280 
281 //# Game
282 
283 float nextSpawnTime = 0.0f;
284 
285 void InitGame()
286 {
287   TowerInit();
288   EnemyInit();
289 
290   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
291   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
292   TowerTryAdd(TOWER_TYPE_GUN, -2, 0);
293   EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4);
294 }
295 
296 void GameUpdate()
297 {
298   float dt = GetFrameTime();
299   // cap maximum delta time to 0.1 seconds to prevent large time steps
300   if (dt > 0.1f) dt = 0.1f;
301   gameTime.time += dt;
302   gameTime.deltaTime = dt;
303   EnemyUpdate();
304   TowerUpdate();
305 
306   // spawn a new enemy every second
307   if (gameTime.time >= nextSpawnTime)
308   {
309     nextSpawnTime = gameTime.time + 1.0f;
310     // add a new enemy at the boundary of the map
311     int randValue = GetRandomValue(-5, 5);
312     int randSide = GetRandomValue(0, 3);
313     int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue;
314     int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue;
315     EnemyTryAdd(ENEMY_TYPE_MINION, x, y);
316   }
317 }
318 
319 int main(void)
320 {
321   int screenWidth, screenHeight;
322   GetPreferredSize(&screenWidth, &screenHeight);
323   InitWindow(screenWidth, screenHeight, "Tower defense");
324   SetTargetFPS(30);
325 
326   Camera3D camera = {0};
327   camera.position = (Vector3){0.0f, 10.0f, 5.0f};
328   camera.target = (Vector3){0.0f, 0.0f, 0.0f};
329   camera.up = (Vector3){0.0f, 0.0f, -1.0f};
330   camera.fovy = 45.0f;
331   camera.projection = CAMERA_PERSPECTIVE;
332 
333   InitGame();
334 
335   while (!WindowShouldClose())
336   {
337     if (IsPaused()) {
338       // canvas is not visible in browser - do nothing
339       continue;
340     }
341     BeginDrawing();
342     ClearBackground(DARKBLUE);
343 
344     BeginMode3D(camera);
345     DrawGrid(10, 1.0f);
346     TowerDraw();
347     EnemyDraw();
348     GameUpdate();
349     EndMode3D();
350 
351     DrawText("Tower defense tutorial", 5, 5, 20, WHITE);
352     EndDrawing();
353   }
354 
355   CloseWindow();
356 
357   return 0;
358 }
  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 #endif
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif
Once the enemies come into range of our tower, they currently just magically disappear.
This is highly unsatisfying. We want to see the enemies getting hit and dying.
There are several ways to implement this. In most games, the projectiles have a fairly slow movement speed. If that's the case, we'd need to predict where the enemy is going to be when the projectile hits the enemy - sometimes the projectiles can even miss the enemy.
Projectiles add more complexity, but they also take some off: If we don't handle the shooting and damage handling via projectiles, we have to set up alternative systems that does the same - like timers that trigger the damage over time, phases for handling the shooting and so on.
So let's take now the more thorny path and implement projectiles. A quick overview of what that means:
- A list of projectiles, compromising position, direction, speed, damage, and the target
 - When the projectile reaches the target, it deals damage, regardless of the distance to the target
 - We draw the projectiles as lines
 
  1 #include "td-tut-2-main.h"
  2 #include <raymath.h>
  3 #include <stdlib.h>
  4 #include <math.h>
  5 
  6 typedef struct GameTime
  7 {
  8   float time;
  9   float deltaTime;
 10 } GameTime;
 11 
 12 GameTime gameTime = {0};
 13 
 14 //# Enemies
 15 
 16 #define ENEMY_MAX_COUNT 400
 17 #define ENEMY_TYPE_NONE 0
 18 #define ENEMY_TYPE_MINION 1
 19 
 20 typedef struct EnemyId
 21 {
 22   uint16_t index;
 23   uint16_t generation;
 24 } EnemyId;
 25 
 26 typedef struct Enemy
 27 {
 28   int16_t currentX, currentY;
 29   int16_t nextX, nextY;
 30   uint16_t generation;
 31   float startMovingTime;
 32   uint8_t enemyType;
 33 } Enemy;
 34 
 35 Enemy enemies[ENEMY_MAX_COUNT];
 36 int enemyCount = 0;
 37 
 38 void EnemyInit()
 39 {
 40   for (int i = 0; i < ENEMY_MAX_COUNT; i++)
 41   {
 42     enemies[i] = (Enemy){0};
 43   }
 44   enemyCount = 0;
 45 }
 46 
 47 float EnemyGetCurrentSpeed(Enemy *enemy)
 48 {
 49   switch (enemy->enemyType)
 50   {
 51   case ENEMY_TYPE_MINION:
 52     return 1.0f;
 53   }
 54   return 1.0f;
 55 }
 56 
 57 void EnemyDraw()
 58 {
 59   for (int i = 0; i < enemyCount; i++)
 60   {
 61     Enemy enemy = enemies[i];
 62     float speed = EnemyGetCurrentSpeed(&enemy);
 63     float transition = (gameTime.time - enemy.startMovingTime) * speed;
 64 
 65     float x = enemy.currentX + (enemy.nextX - enemy.currentX) * transition;
 66     float y = enemy.currentY + (enemy.nextY - enemy.currentY) * transition;
 67     
 68     switch (enemy.enemyType)
 69     {
 70     case ENEMY_TYPE_MINION:
 71       DrawCube((Vector3){x, 0.2f, y}, 0.4f, 0.4f, 0.4f, GREEN);
 72       break;
 73     }
 74   }
 75 }
 76 
 77 void EnemyUpdate()
 78 {
 79   const int16_t castleX = 0;
 80   const int16_t castleY = 0;
 81 
 82   for (int i = 0; i < enemyCount; i++)
 83   {
 84     Enemy *enemy = &enemies[i];
 85     if (enemy->enemyType == ENEMY_TYPE_NONE)
 86     {
 87       continue;
 88     }
 89     float speed = EnemyGetCurrentSpeed(enemy);
 90     float transition = (gameTime.time - enemy->startMovingTime) * speed;
 91     if (transition >= 1.0f)
 92     {
 93       enemy->startMovingTime = gameTime.time;
 94       enemy->currentX = enemy->nextX;
 95       enemy->currentY = enemy->nextY;
 96       int16_t dx = castleX - enemy->currentX;
 97       int16_t dy = castleY - enemy->currentY;
 98       if (dx == 0 && dy == 0)
 99       {
100         // enemy reached the castle; remove it
101         enemy->enemyType = ENEMY_TYPE_NONE;
102         continue;
103       }
104       if (abs(dx) > abs(dy))
105       {
106         enemy->nextX = enemy->currentX + (dx > 0 ? 1 : -1);
107         enemy->nextY = enemy->currentY;
108       }
109       else
110       {
111         enemy->nextX = enemy->currentX;
112         enemy->nextY = enemy->currentY + (dy > 0 ? 1 : -1);
113       }
114     }
115   }
116 }
117 
118 EnemyId EnemyGetId(Enemy *enemy)
119 {
120   return (EnemyId){enemy - enemies, enemy->generation};
121 }
122 
123 Enemy *EnemyTryResolve(EnemyId enemyId)
124 {
125   if (enemyId.index >= ENEMY_MAX_COUNT)
126   {
127     return 0;
128   }
129   Enemy *enemy = &enemies[enemyId.index];
130   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
131   {
132     return 0;
133   }
134   return enemy;
135 }
136 
137 Enemy *EnemyTryAdd(uint8_t enemyType, int16_t currentX, int16_t currentY)
138 {
139   Enemy *spawn = 0;
140   for (int i = 0; i < enemyCount; i++)
141   {
142     Enemy *enemy = &enemies[i];
143     if (enemy->enemyType == ENEMY_TYPE_NONE)
144     {
145       spawn = enemy;
146       break;
147     }
148   }
149 
150   if (enemyCount < ENEMY_MAX_COUNT && !spawn)
151   {
152     spawn = &enemies[enemyCount++];
153   }
154 
155   if (spawn)
156   {
157     spawn->currentX = currentX;
158     spawn->currentY = currentY;
159     spawn->nextX = currentX;
160     spawn->nextY = currentY;
161     spawn->enemyType = enemyType;
162     spawn->startMovingTime = 0;
163     spawn->generation++;
164   }
165 
166   return spawn;
167 }
168 
169 Enemy* EnemyGetClosestToCastle(int16_t towerX, int16_t towerY, float range)
170 {
171   int16_t castleX = 0;
172   int16_t castleY = 0;
173   Enemy* closest = 0;
174   int16_t closestDistance = 0;
175   float range2 = range * range;
176   for (int i = 0; i < enemyCount; i++)
177   {
178     Enemy* enemy = &enemies[i];
179     if (enemy->enemyType == ENEMY_TYPE_NONE)
180     {
181       continue;
182     }
183     int16_t dx = castleX - enemy->currentX;
184     int16_t dy = castleY - enemy->currentY;
185     int16_t distance = abs(dx) + abs(dy);
186     if (!closest || distance < closestDistance)
187     {
188       float tdx = towerX - enemy->currentX;
189       float tdy = towerY - enemy->currentY;
190       float tdistance2 = tdx * tdx + tdy * tdy;
191       if (tdistance2 <= range2)
192       {
193         closest = enemy;
194         closestDistance = distance;
195       }
196     }
197   }
198   return closest;
199 }
200 
201 //# Projectiles
202 #define PROJECTILE_MAX_COUNT 1200
203 #define PROJECTILE_TYPE_NONE 0
204 #define PROJECTILE_TYPE_BULLET 1
205 
206 typedef struct Projectile
207 {
208   uint8_t projectileType;
209   float shootTime;
210   float arrivalTime;
211   float damage;
212   Vector2 position;
213   Vector2 target;
214   Vector2 directionNormal;
215   EnemyId targetEnemy;
216 } Projectile;
217 
218 Projectile projectiles[PROJECTILE_MAX_COUNT];
219 int projectileCount = 0;
220 
221 void ProjectileInit()
222 {
223   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
224   {
225     projectiles[i] = (Projectile){0};
226   }
227 }
228 
229 void ProjectileDraw()
230 {
231   for (int i = 0; i < projectileCount; i++)
232   {
233     Projectile projectile = projectiles[i];
234     if (projectile.projectileType == PROJECTILE_TYPE_NONE)
235     {
236       continue;
237     }
238     float transition = (gameTime.time - projectile.shootTime) / (projectile.arrivalTime - projectile.shootTime);
239     if (transition >= 1.0f)
240     {
241       continue;
242     }
243     Vector2 position = Vector2Lerp(projectile.position, projectile.target, transition);
244     float x = position.x;
245     float y = position.y;
246     float dx = projectile.directionNormal.x;
247     float dy = projectile.directionNormal.y;
248     for (float d = 1.0f; d > 0.0f; d -= 0.25f)
249     {
250       x -= dx * 0.1f;
251       y -= dy * 0.1f;
252       float size = 0.1f * d;
253       DrawCube((Vector3){x, 0.2f, y}, size, size, size, RED);
254     }
255   }
256 }
257 
258 void ProjectileUpdate()
259 {
260   for (int i = 0; i < projectileCount; i++)
261   {
262     Projectile *projectile = &projectiles[i];
263     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
264     {
265       continue;
266     }
267     float transition = (gameTime.time - projectile->shootTime) / (projectile->arrivalTime - projectile->shootTime);
268     if (transition >= 1.0f)
269     {
270       projectile->projectileType = PROJECTILE_TYPE_NONE;
271       Enemy *enemy = EnemyTryResolve(projectile->targetEnemy);
272       if (enemy)
273       {
274         enemy->enemyType = ENEMY_TYPE_NONE;
275       }
276       continue;
277     }
278   }
279 }
280 
281 Projectile *ProjectileTryAdd(uint8_t projectileType, Enemy *enemy, Vector2 position, Vector2 target, float speed, float damage)
282 {
283   for (int i = 0; i < PROJECTILE_MAX_COUNT; i++)
284   {
285     Projectile *projectile = &projectiles[i];
286     if (projectile->projectileType == PROJECTILE_TYPE_NONE)
287     {
288       projectile->projectileType = projectileType;
289       projectile->shootTime = gameTime.time;
290       projectile->arrivalTime = gameTime.time + Vector2Distance(position, target) / speed;
291       projectile->damage = damage;
292       projectile->position = position;
293       projectile->target = target;
294       projectile->directionNormal = Vector2Normalize(Vector2Subtract(target, position));
295       projectile->targetEnemy = EnemyGetId(enemy);
296       projectileCount = projectileCount <= i ? i + 1 : projectileCount;
297       return projectile;
298     }
299   }
300   return 0;
301 }
302 
303 //# Towers
304 
305 #define TOWER_MAX_COUNT 400
306 #define TOWER_TYPE_NONE 0
307 #define TOWER_TYPE_BASE 1
308 #define TOWER_TYPE_GUN 2
309 
310 typedef struct Tower
311 {
312   int16_t x, y;
313   uint8_t towerType;
314   float cooldown;
315 } Tower;
316 
317 Tower towers[TOWER_MAX_COUNT];
318 int towerCount = 0;
319 
320 void TowerInit()
321 {
322   for (int i = 0; i < TOWER_MAX_COUNT; i++)
323   {
324     towers[i] = (Tower){0};
325   }
326   towerCount = 0;
327 }
328 
329 Tower *TowerGetAt(int16_t x, int16_t y)
330 {
331   for (int i = 0; i < towerCount; i++)
332   {
333     if (towers[i].x == x && towers[i].y == y)
334     {
335       return &towers[i];
336     }
337   }
338   return 0;
339 }
340 
341 Tower *TowerTryAdd(uint8_t towerType, int16_t x, int16_t y)
342 {
343   if (towerCount >= TOWER_MAX_COUNT)
344   {
345     return 0;
346   }
347 
348   Tower *tower = TowerGetAt(x, y);
349   if (tower)
350   {
351     return 0;
352   }
353 
354   tower = &towers[towerCount++];
355   tower->x = x;
356   tower->y = y;
357   tower->towerType = towerType;
358   return tower;
359 }
360 
361 void TowerDraw()
362 {
363   for (int i = 0; i < towerCount; i++)
364   {
365     Tower tower = towers[i];
366     DrawCube((Vector3){tower.x, 0.125f, tower.y}, 1.0f, 0.25f, 1.0f, GRAY);
367     switch (tower.towerType)
368     {
369     case TOWER_TYPE_BASE:
370       DrawCube((Vector3){tower.x, 0.4f, tower.y}, 0.8f, 0.8f, 0.8f, MAROON);
371       break;
372     case TOWER_TYPE_GUN:
373       DrawCube((Vector3){tower.x, 0.2f, tower.y}, 0.8f, 0.4f, 0.8f, DARKPURPLE);
374       break;
375     }
376   }
377 }
378 
379 void TowerGunUpdate(Tower *tower)
380 {
381   if (tower->cooldown <= 0)
382   {
383     Enemy *enemy = EnemyGetClosestToCastle(tower->x, tower->y, 3.0f);
384     if (enemy)
385     {
386       tower->cooldown = 0.5f;
387       // shoot the enemy
388       ProjectileTryAdd(PROJECTILE_TYPE_BULLET, enemy, (Vector2){tower->x, tower->y}, (Vector2){enemy->currentX, enemy->currentY}, 5.0f, 1.0f);
389     }
390   }
391   else
392   {
393     tower->cooldown -= gameTime.deltaTime;
394   }
395 }
396 
397 void TowerUpdate()
398 {
399   for (int i = 0; i < towerCount; i++)
400   {
401     Tower *tower = &towers[i];
402     switch (tower->towerType)
403     {
404     case TOWER_TYPE_GUN:
405       TowerGunUpdate(tower);
406       break;
407     }
408   }
409 }
410 
411 //# Game
412 
413 float nextSpawnTime = 0.0f;
414 
415 void InitGame()
416 {
417   TowerInit();
418   EnemyInit();
419   ProjectileInit();
420 
421   TowerTryAdd(TOWER_TYPE_BASE, 0, 0);
422   TowerTryAdd(TOWER_TYPE_GUN, 2, 0);
423   TowerTryAdd(TOWER_TYPE_GUN, -2, 0);
424   EnemyTryAdd(ENEMY_TYPE_MINION, 5, 4);
425 }
426 
427 void GameUpdate()
428 {
429   float dt = GetFrameTime();
430   // cap maximum delta time to 0.1 seconds to prevent large time steps
431   if (dt > 0.1f) dt = 0.1f;
432   gameTime.time += dt;
433   gameTime.deltaTime = dt;
434   EnemyUpdate();
435   TowerUpdate();
436   ProjectileUpdate();
437 
438   // spawn a new enemy every second
439   if (gameTime.time >= nextSpawnTime)
440   {
441     nextSpawnTime = gameTime.time + 1.0f;
442     // add a new enemy at the boundary of the map
443     int randValue = GetRandomValue(-5, 5);
444     int randSide = GetRandomValue(0, 3);
445     int16_t x = randSide == 0 ? -5 : randSide == 1 ? 5 : randValue;
446     int16_t y = randSide == 2 ? -5 : randSide == 3 ? 5 : randValue;
447     EnemyTryAdd(ENEMY_TYPE_MINION, x, y);
448   }
449 }
450 
451 int main(void)
452 {
453   int screenWidth, screenHeight;
454   GetPreferredSize(&screenWidth, &screenHeight);
455   InitWindow(screenWidth, screenHeight, "Tower defense");
456   SetTargetFPS(30);
457 
458   Camera3D camera = {0};
459   camera.position = (Vector3){0.0f, 10.0f, 5.0f};
460   camera.target = (Vector3){0.0f, 0.0f, 0.0f};
461   camera.up = (Vector3){0.0f, 0.0f, -1.0f};
462   camera.fovy = 45.0f;
463   camera.projection = CAMERA_PERSPECTIVE;
464 
465   InitGame();
466 
467   while (!WindowShouldClose())
468   {
469     if (IsPaused()) {
470       // canvas is not visible in browser - do nothing
471       continue;
472     }
473     BeginDrawing();
474     ClearBackground(DARKBLUE);
475 
476     BeginMode3D(camera);
477     DrawGrid(10, 1.0f);
478     TowerDraw();
479     EnemyDraw();
480     ProjectileDraw();
481     GameUpdate();
482     EndMode3D();
483 
484     DrawText("Tower defense tutorial", 5, 5, 20, WHITE);
485     EndDrawing();
486   }
487 
488   CloseWindow();
489 
490   return 0;
491 }
  1 #ifndef TD_TUT_2_MAIN_H
  2 #define TD_TUT_2_MAIN_H
  3 
  4 #include <inttypes.h>
  5 
  6 #include "raylib.h"
  7 #include "preferred_size.h"
  8 
  9 #endif
  1 #include "raylib.h"
  2 #include "preferred_size.h"
  3 
  4 // Since the canvas size is not known at compile time, we need to query it at runtime;
  5 // the following platform specific code obtains the canvas size and we will use this
  6 // size as the preferred size for the window at init time. We're ignoring here the
  7 // possibility of the canvas size changing during runtime - this would require to
  8 // poll the canvas size in the game loop or establishing a callback to be notified
  9 
 10 #ifdef PLATFORM_WEB
 11 #include <emscripten.h>
 12 EMSCRIPTEN_RESULT emscripten_get_element_css_size(const char *target, double *width, double *height);
 13 
 14 void GetPreferredSize(int *screenWidth, int *screenHeight)
 15 {
 16   double canvasWidth, canvasHeight;
 17   emscripten_get_element_css_size("#" CANVAS_NAME, &canvasWidth, &canvasHeight);
 18   *screenWidth = (int)canvasWidth;
 19   *screenHeight = (int)canvasHeight;
 20   TraceLog(LOG_INFO, "preferred size for %s: %d %d", CANVAS_NAME, *screenWidth, *screenHeight);
 21 }
 22 
 23 int IsPaused()
 24 {
 25   const char *js = "(function(){\n"
 26   "  var canvas = document.getElementById(\"" CANVAS_NAME "\");\n"
 27   "  var rect = canvas.getBoundingClientRect();\n"
 28   "  var isVisible = (\n"
 29   "    rect.top >= 0 &&\n"
 30   "    rect.left >= 0 &&\n"
 31   "    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n"
 32   "    rect.right <= (window.innerWidth || document.documentElement.clientWidth)\n"
 33   "  );\n"
 34   "  return isVisible ? 0 : 1;\n"
 35   "})()";
 36   return emscripten_run_script_int(js);
 37 }
 38 
 39 #else
 40 void GetPreferredSize(int *screenWidth, int *screenHeight)
 41 {
 42   *screenWidth = 600;
 43   *screenHeight = 240;
 44 }
 45 int IsPaused()
 46 {
 47   return 0;
 48 }
 49 #endif
  1 #ifndef PREFERRED_SIZE_H
  2 #define PREFERRED_SIZE_H
  3 
  4 void GetPreferredSize(int *screenWidth, int *screenHeight);
  5 int IsPaused();
  6 
  7 #endif
While the newly added code is very similar to the existing code, I added one new concept:
  1 typedef struct EnemyId
  2 {
  3   uint16_t index;
  4   uint16_t generation;
  5 } EnemyId;
  6 
  7 EnemyId EnemyGetId(Enemy *enemy)
  8 {
  9   return (EnemyId){enemy - enemies, enemy->generation};
 10 }
 11 
 12 Enemy *EnemyTryResolve(EnemyId enemyId)
 13 {
 14   if (enemyId.index >= ENEMY_MAX_COUNT)
 15   {
 16     return 0;
 17   }
 18   Enemy *enemy = &enemies[enemyId.index];
 19   if (enemy->generation != enemyId.generation || enemy->enemyType == ENEMY_TYPE_NONE)
 20   {
 21     return 0;
 22   }
 23   return enemy;
 24 }
It is important to understand why this is needed here: The first solution to refer to an enemy is to use a simple pointer. Since we use only fixed size arrays, there's no chance to run into pointer invalidation issues. But we still do have "object" lifetimes: When an enemy dies, the slot is free to be used by another enemy. If we use a pointer, we do not know if it still is the very same enemy that existed when the projectile was created and the result would be that newly created enemies could take damage from projectiles that were meant for the enemy that died before and used the same index.
The generation counter solves this problem (in theory, it could still happen that the generation overflows, but the chances for that are very slim; for a 16bit generation and 400 enemies, we'd need to spawn 400 * 65536 enemies within the lifetime of a bullet to produce this kind of bug).
By increasing the generation integer each time we spawn an enemy, we can be sure that any existing reference will become invalid when the enemy dies and a new enemy is spawned. Moreover, this also handles the case to check if the enemy is still alive when the bullet hits, since the resolving function already does this check for us.
Generational indices are a very useful concept to deal with object lifetime invalidation and since I adopted this concept, I have reduced the amount of memory corruption bugs by a significant amount.
Wrapup
The basic structure is established: We have a grid, enemies that move towards the castle, towers that shoot at the enemies and projectiles that deal damage to the enemies.
The next part will cover the following topics:
- Handling health and damage
 - Adding graphics and animations
 - Adding a user interface
 
I hope you enjoyed the tutorial so far. If you have any questions or suggestions, please let me know. I will publish the next part next Friday, the 22nd of November.