commit d909dacc5a9a8610829f6dd8fe3ffdb1d4dfc54d
parent 09d674bd9907c19729d8ef0e07e5a0bb60c869f3
Author: calliope <me@calliope.sh>
Date: Thu, 2 Oct 2025 07:55:24 -0500
added comments
Diffstat:
| M | .gitignore | | | 1 | + |
| M | fangflecked.c | | | 175 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ |
2 files changed, 164 insertions(+), 12 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -4,3 +4,4 @@ vendored
*core*
Makefile
~*
+fangflecked
diff --git a/fangflecked.c b/fangflecked.c
@@ -29,6 +29,7 @@ static SDL_Renderer *renderer = NULL;
SDL_Texture *texture_tile, *texture_tile2, *texture_tile3;
SDL_Texture *texture_warrior_stand, *texture_warrior_walk;
+/* pathing data */
typedef struct {
u32 size;
u32 cur;
@@ -47,7 +48,6 @@ typedef struct {
u32 frame_next;
u32 state_next;
- /* pathing */
NavPath path;
} PC;
@@ -91,6 +91,7 @@ vecf vecf_lerp(vecf p1, vecf p2, float t) {
return result;
}
+/* Take a tile index and return level coordinates */
vecf pos_world(u32 tile) {
vecf result;
@@ -99,25 +100,50 @@ vecf pos_world(u32 tile) {
return result;
}
-/* Return the screen position of the passed tile */
+/*
+ * Return the screen position of the passed tile
+ * so we know where to draw it. Essentially this
+ * function does the work of converting an axis-
+ * aligned grid into an isometric one, as well
+ * as performing translations based on the camera
+ * position
+*/
vecf iso_coords(u32 tile) {
vecf world;
vecf result;
u32 offs_x, offs_y;
+ /*
+ * We offset every graphic by half its width, to
+ * make positioning easier
+ */
offs_x = TILE_WIDTH / 2;
offs_y = TILE_HEIGHT / 2;
+ /* get the level coordinates of the passed tile index */
world = pos_world(tile);
+
+ /*
+ * Do some math to make the tiles line up along a 45
+ * degree line, rather than horizontal
+ */
result.x = world.x * offs_x + world.y * offs_x;
result.y = world.y * offs_y - world.x * offs_y;
+ /*
+ * Add a constant offset which serves to position
+ * our drawing of the level correctly on screen
+ */
result.x += MAP_RENDER_OFFSET_X;
result.y += MAP_RENDER_OFFSET_Y;
return result;
}
+/*
+ * Calculates a NavPath between two points (essentially
+ * just an array of tile indices) using linear interpolation
+ */
u8 path_line(u32 t1, u32 t2, NavPath *line) {
u32 dx, dy;
u32 i;
@@ -125,24 +151,50 @@ u8 path_line(u32 t1, u32 t2, NavPath *line) {
vecf p1, p2;
vecf lerp_point;
+ /* Error out if the start and end tiles are the same */
if (t1 == t2) { return 1; }
line->cur = 0;
+
+ /* get level coordinates of the start and end tiles */
p1 = pos_world(t1);
p2 = pos_world(t2);
+ /*
+ * Calculate how many tiles are in the NavPath. This is
+ * just the diagonal distance between the start and end
+ * tiles
+ */
dx = SDL_abs(p2.x - p1.x);
dy = SDL_abs(p2.y - p1.y);
line->size = dx > dy ? dx : dy;
+
+ /*
+ * We start at 1 so as not to include the start tile
+ * in the NavPath array
+ */
for (i = 1; i <= line->size; i++) {
+ /*
+ * lerp along the line between the two points. Each iteration
+ * we step up by 1 / (diagonal distance) to account for each
+ * tile in the NavPath
+ */
lerp_point = vecf_lerp(p1, p2, (float) i / (float) line->size);
+
+ /* Round to nearest integer to get the level grid coordinates */
tx = SDL_round(lerp_point.x);
ty = SDL_round(lerp_point.y);
+
+ /*
+ * Now convert from xy to a tile index and push
+ * to the NavPath array
+ */
line->tiles[i-1] = ty * LEVEL_MAX_WIDTH + tx;
}
return 0;
}
+/* For iterating through the array of tiles in a NavPath */
u8 path_next(NavPath *path, u32 *tile) {
if (path->cur >= path->size) { return 1; }
*tile = path->tiles[path->cur];
@@ -150,35 +202,55 @@ u8 path_next(NavPath *path, u32 *tile) {
return 0;
}
+/* Initialize or clear out the values in a NavPath */
u8 path_reset(NavPath *path) {
path->cur = 0;
path->size = 0;
return 0;
}
-/* Calculate the tile that the cursor is currently hovering over */
+/*
+ * Find the tile that the cursor is currently hovering over.
+ * Essentially does the opposite of the iso_coords() function.
+*/
u8 cursor_tile(float screen_x, float screen_y, u32 *tile) {
u32 result;
float world_x, world_y, offs_x, offs_y;
*tile = 0;
+
+ /*
+ * Sprites are always offset by their width to make positioning easier.
+ * They're also offset by a constant amount which determines where the
+ * entire level is drawn in relation to the screen. Here we account for
+ * this as a first step to determining where the screen position of the
+ * cursor falls in relation to the level coordinates.
+ */
+ offs_x = TILE_WIDTH / 2 + MAP_RENDER_OFFSET_X;
+ offs_y = TILE_HEIGHT / 2 + MAP_RENDER_OFFSET_Y;
- offs_x = TILE_WIDTH / 2;
- offs_y = TILE_HEIGHT / 2;
-
- screen_x -= offs_x + MAP_RENDER_OFFSET_X;
- screen_y -= offs_y + MAP_RENDER_OFFSET_Y;
+ screen_x -= offs_x;
+ screen_y -= offs_y;
+ /*
+ * Taking isometric coordinates and converting back into level xy.
+ */
world_x = SDL_roundf(screen_x / TILE_WIDTH - screen_y / TILE_HEIGHT);
world_y = SDL_roundf(screen_x / TILE_WIDTH + screen_y / TILE_HEIGHT);
+ /* Sanity check the results */
if (world_x < 0 || world_y < 0)
return 1;
-
if (world_y > LEVEL_MAX_HEIGHT || world_x >= LEVEL_MAX_WIDTH)
return 1;
+ /* Convert from level xy to a tile index */
result = world_y * LEVEL_MAX_WIDTH + world_x;
+
+ /*
+ * Another sanity check and assign the passed tile
+ * to our result
+ */
if (result < (LEVEL_MAX_WIDTH * LEVEL_MAX_HEIGHT)) {
*tile = result;
return 0;
@@ -187,6 +259,10 @@ u8 cursor_tile(float screen_x, float screen_y, u32 *tile) {
}
}
+/*
+ * Loops through all the tiles in the level_tiles array
+ * and draws the associated graphics to the screen
+ */
void draw_level(void) {
u32 i;
vecf pos;
@@ -200,11 +276,23 @@ void draw_level(void) {
src.x = 0;
src.y = 0;
+ /* Iterate through each tile in the level */
for(i = 0; i < LEVEL_MAX_WIDTH * LEVEL_MAX_HEIGHT; i++) {
+ /*
+ * Figure out the screen coordinates where the
+ * tile should be drawn
+ */
pos = iso_coords(i);
dest.x = pos.x;
dest.y = pos.y;
+ /*
+ * Simple if/then block to determine which tile graphic
+ * to draw based on the integer value in the array. This
+ * will quickly get out of hand as we add more tile graphics,
+ * so a better implementation will be necessary for the
+ * next refactor.
+ */
if (level_tiles[i] == 1) {
SDL_RenderTexture(renderer, texture_tile, &src, &dest);
} else if (level_tiles[i] == 2) {
@@ -227,14 +315,27 @@ void draw_level(void) {
u8 player_move(u32 tile) {
u32 target_x, target_y, current_x, current_y;
- if (tile == player.pos) { return 1; }
- if (player.pos != player.pos_next) { return 1; } /* Don't interrupt if player is already between tiles */
+ /* No need to move to the tile we're already standing on */
+ if (tile == player.pos) { return 1; }
+
+ /* Don't interrupt if player is already between tiles */
+ if (player.pos != player.pos_next) { return 1; }
+ /*
+ * Convert tile index to x and y coordinates
+ * target is the tile the player wants to move to
+ * current is the tile the player is currently standing on
+ */
target_x = tile % LEVEL_MAX_WIDTH;
target_y = tile / LEVEL_MAX_WIDTH;
current_x = player.pos % LEVEL_MAX_WIDTH;
current_y = player.pos / LEVEL_MAX_WIDTH;
+ /*
+ * Make sure the target tile is adjacent to the one the player's standing on.
+ * if so, update the player's angle and continue.
+ */
+
/* axis-aligned movement (relative to the screen) is kitty corner according to the world coordinates */
if (target_x - current_x == 1 && target_y - current_y == 1) {
player.angle = ANGLE_RIGHT;
@@ -258,6 +359,10 @@ u8 player_move(u32 tile) {
return 1;
}
+ /*
+ * Set the player to walking state and reset animation variables
+ * in preparation for playing the player's walk animation.
+ */
player.pos_next = tile;
player.state = PLAYER_STATE_WALKING;
player.frame = 0;
@@ -266,6 +371,10 @@ u8 player_move(u32 tile) {
return 0;
}
+/*
+ * Use lerp to calculate a NavPath between player's current position
+ * and destination tile
+*/
u8 player_path(u32 tile) {
/* adjacent tile, no pathing needed */
if (player_move(tile) == 0) { return 0; }
@@ -276,6 +385,12 @@ u8 player_path(u32 tile) {
return 1;
}
+/*
+ * Draws the player sprite, as well as handling animations
+ * and state changes. This function is a mess, and should
+ * probably be divided up. I'll try to comment it on the
+ * next refactor.
+ */
u8 draw_player() {
SDL_FRect dest;
SDL_FRect src;
@@ -352,6 +467,7 @@ u8 draw_player() {
* ========================================================
*/
+/* Called at launch */
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) {
SDL_Surface *buffer;
u8 i;
@@ -368,7 +484,10 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) {
return SDL_APP_FAILURE;
}
- /* load graphics from file */
+ /*
+ * Load graphics from file, set color FF00FF (bright pink)
+ * to transparent, convert/assign to an SDL_Texture object
+ */
buffer = SDL_LoadBMP("assets/ph_tile.bmp");
SDL_SetSurfaceColorKey(buffer, true, SDL_MapRGB(SDL_GetPixelFormatDetails(buffer->format), NULL, 0xFF, 0x00, 0xFF));
texture_tile = SDL_CreateTextureFromSurface(renderer, buffer);
@@ -386,6 +505,13 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) {
texture_warrior_walk = SDL_CreateTextureFromSurface(renderer, buffer);
SDL_DestroySurface(buffer);
+ /*
+ * Initialize player position and angle
+ * to arbitrary values and player state
+ * to standing/ready (as opposed to moving
+ * between tiles or performing some other
+ * action)
+ */
player.pos = 17;
player.pos_next = 17;
player.angle = ANGLE_DOWN;
@@ -396,6 +522,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) {
return SDL_APP_CONTINUE;
}
+/* Called when an input event is received */
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
if (event->type == SDL_EVENT_QUIT) {
return SDL_APP_SUCCESS;
@@ -403,6 +530,7 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
return SDL_APP_CONTINUE;
}
+/* Called every frame */
SDL_AppResult SDL_AppIterate(void *appstate) {
SDL_MouseButtonFlags mouse_state;
float mouse_x, mouse_y;
@@ -410,12 +538,22 @@ SDL_AppResult SDL_AppIterate(void *appstate) {
u32 i;
/* key_state = SDL_GetKeyboardState(NULL); */
+
+ /*
+ * Initialize the level data array into a flat room made
+ * up of pathable tiles
+ */
for (i = 0; i < LEVEL_MAX_WIDTH * LEVEL_MAX_HEIGHT; i++) {
level_tiles[i] = 1;
}
+ /* Get mourse coordinates relative to the game screen */
mouse_state = SDL_GetMouseState(&mouse_x, &mouse_y);
+ /*
+ * Set draw color to black and clear the screen so we have
+ * a blank canvas on which we can draw our next frame
+ */
SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
SDL_RenderClear(renderer);
@@ -426,25 +564,38 @@ SDL_AppResult SDL_AppIterate(void *appstate) {
if (key_state[SDL_SCANCODE_RIGHT]) { posx++; }
*/
+ /*
+ * On left click, find the tile the player has clicked on
+ * and try to find a NavPath from the player's current
+ * position to the clicked tile
+ */
if (mouse_state & SDL_BUTTON_LEFT) {
if (cursor_tile(mouse_x, mouse_y, &hovertile) == 0) {
player_path(hovertile);
}
}
+ /*
+ * Quick and dirty debug code to change the data
+ * for the tiles in the player's current NavPath.
+ * so we can draw them in distinct colors.
+ */
for (i = 0; i < player.path.size; i++) {
level_tiles[player.path.tiles[i]] = 2;
}
level_tiles[player.pos_next] = 3;
+ /* Draw current frame */
draw_level();
draw_player();
+ /* Tell SDL to present the frame we've just drawn */
SDL_RenderPresent(renderer);
return SDL_APP_CONTINUE;
}
+/* Cleanup on game quit */
void SDL_AppQuit(void *appstate, SDL_AppResult result) {
SDL_DestroyTexture(texture_tile);
SDL_DestroyTexture(texture_tile2);