fangflecked

90s style ARPG
git clone git://moonbender.net/fangflecked
Log | Files | Refs | README

commit d909dacc5a9a8610829f6dd8fe3ffdb1d4dfc54d
parent 09d674bd9907c19729d8ef0e07e5a0bb60c869f3
Author: calliope <me@calliope.sh>
Date:   Thu,  2 Oct 2025 07:55:24 -0500

added comments

Diffstat:
M.gitignore | 1+
Mfangflecked.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);