a-game

2D platformer written from scratch.
git clone git://git.amin.space/a-game.git
Log | Files | Refs | README | LICENSE

commit c79354bf54276bc99002fc404dda161925b127ee
parent 41c2e6ed9f56bf284f097164cf05d708746a9c02
Author: amin <dev@aminmesbah.com>
Date:   Thu, 25 Apr 2019 04:54:14 +0000

Ignore internal walls when colliding

Finally! Smooth (and seemingly robust) movement along solid surfaces!!!

FossilOrigin-Name: 9b5c39a203aa578e4c0a21214d21e106e9f9a42b034bcc3d1c31536f3ac20ae0
Diffstat:
Msrc/game.c | 175+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/glmth.h | 37+++++++++++++++++++++++++++++++++++++
2 files changed, 151 insertions(+), 61 deletions(-)

diff --git a/src/game.c b/src/game.c @@ -2,6 +2,9 @@ #include "glad.c" #include "shader.c" +// One tile is one square meter +#define TILE_SIZE 1.0f + #ifdef PLATFORM_HOTLOAD_GAME_CODE void game_load_opengl_symbols(void) { @@ -15,7 +18,6 @@ internal void game_init(struct GameMemory *game_memory, v2u framebuffer) // init player game_state->player.pos = (v2) {12.5f, 5.0f}; - game_state->player.pos = (v2) {12.5f, 8.5f}; // In Knytt, the player is 9 by 14 texels and a tile is 24 by 24 texels. // These dimensions are relative to a square 'meter', one tile @@ -134,6 +136,55 @@ internal struct WallCollision get_wall_collision(v2 entity_p_initial, v2 entity_ return result; } +internal bool tile_is_solid(u32 tiles[][ROOM_TILE_DIM_X], v2 tile_pos) +{ + assert(tiles); + + bool is_solid = false; + + v2u tile_index = { + tile_pos.x, + ROOM_TILE_DIM_Y - 1.0f - tile_pos.y, + }; + + assert(tile_index.x >= 0); + assert(tile_index.x < ROOM_TILE_DIM_X); + assert(tile_index.y >= 0); + assert(tile_index.y < ROOM_TILE_DIM_X); + + u32 tile_value = tiles[tile_index.y][tile_index.x]; + is_solid = tile_value > 0; + + return is_solid; +} + +internal bool wall_is_internal(u32 tiles[][ROOM_TILE_DIM_X], segment wall) +{ + bool is_internal = false; + bool wall_is_horizontal = wall.min.y == wall.max.y; + bool wall_is_vertical = wall.min.x == wall.max.x; + + assert(wall_is_vertical || wall_is_horizontal); + + v2 lesser_neighbor = {0}; + v2 greater_neighbor = {0}; + + if (wall_is_vertical) + { + lesser_neighbor = (v2) {wall.min.x - TILE_SIZE, wall.min.y}; + greater_neighbor = wall.min; + is_internal = tile_is_solid(tiles, lesser_neighbor) && tile_is_solid(tiles, greater_neighbor); + } + else + { + lesser_neighbor = (v2) {wall.min.x, wall.min.y - TILE_SIZE}; + greater_neighbor = wall.min; + is_internal = tile_is_solid(tiles, lesser_neighbor) && tile_is_solid(tiles, greater_neighbor); + } + + return is_internal; +} + // NOTE(amin): For now updating and rendering are interleaved. We simulate the // world at the same rate we render it. Our timestep is not fixed. We may want // to change this in the future. @@ -218,8 +269,6 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, }; - // One tile is one square meter - f32 tile_size = 1.0f; // render tiles { @@ -240,9 +289,9 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga // our square verts are anchored around the center point of the // square, so we want to offset by 0.5 to instead have our // anchor in the min corner - model = glmth_translate(model, (v3) {tile_size * 0.5f, tile_size * 0.5f, 0.0f}); + model = glmth_translate(model, (v3) {TILE_SIZE * 0.5f, TILE_SIZE * 0.5f, 0.0f}); model = glmth_translate(model, (v3) {tile_pos.x, tile_pos.y, 0.0f}); - model = glmth_scale(model, (v3) {tile_size, tile_size, 1.0f}); + model = glmth_scale(model, (v3) {TILE_SIZE, TILE_SIZE, 1.0f}); v3 color; @@ -265,7 +314,7 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga }; rect tile_aabb = { .min = tile_pos, - .max = {tile_pos.x + tile_size, tile_pos.y + tile_size}, + .max = {tile_pos.x + TILE_SIZE, tile_pos.y + TILE_SIZE}, }; bool player_is_in_tile = glmth_intersect_aabb_aabb(player_aabb, tile_aabb); @@ -276,7 +325,7 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga if (player_is_in_tile && tile_id > 0) { color = (v3) {0.8f, 0.4f, 0.4f}; - //assert(false); + assert(false); } } @@ -368,7 +417,7 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga rect tile_aabb = { .min = {x, y}, - .max = {x + tile_size, y + tile_size}, + .max = {x + TILE_SIZE, y + TILE_SIZE}, }; rect player_aabb = { .min = {next_player_pos.x - half_w, next_player_pos.y - half_h}, @@ -434,85 +483,89 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga { rect tile_aabb = { .min = {tile_x, tile_y}, - .max = {tile_x + tile_size, tile_y + tile_size}, + .max = {tile_x + TILE_SIZE, tile_y + TILE_SIZE}, }; rect tile_player_sum = glmth_minkowski_sum_rect_rect(tile_aabb, player->dimensions); RENDER_COLLISION_DEBUG_QUAD(tile_player_sum, ((v3) {0.8f, 0.4f, 0.4f})); - v2 tile_nw = {tile_player_sum.min.x, tile_player_sum.max.y}; - v2 tile_ne = tile_player_sum.max; - v2 tile_sw = tile_player_sum.min; - v2 tile_se = {tile_player_sum.max.x, tile_player_sum.min.y}; - - segment tile_b = {tile_sw, tile_se}; - segment tile_t = {tile_nw, tile_ne}; - segment tile_l = {tile_sw, tile_nw}; - segment tile_r = {tile_se, tile_ne}; + segment tile_sum_b = glmth_rect_get_edge(tile_player_sum, RECT_EDGE_BOTTOM); + segment tile_sum_t = glmth_rect_get_edge(tile_player_sum, RECT_EDGE_TOP); + segment tile_sum_l = glmth_rect_get_edge(tile_player_sum, RECT_EDGE_LEFT); + segment tile_sum_r = glmth_rect_get_edge(tile_player_sum, RECT_EDGE_RIGHT); v2 norm_b = {0.0f, -1.0f}; v2 norm_t = {0.0f, 1.0f}; v2 norm_l = {-1.0f, 0.0f}; v2 norm_r = {1.0f, 0.0f}; - struct WallCollision bottom = get_wall_collision(player->pos, new_p, tile_b, norm_b); - if (bottom.collision_occurred) + if (!wall_is_internal(tiles, glmth_rect_get_edge(tile_aabb, RECT_EDGE_BOTTOM))) { - if (smallest_distance_scale_factor > bottom.distance_scale_factor) + struct WallCollision bottom = get_wall_collision(player->pos, new_p, tile_sum_b, norm_b); + if (bottom.collision_occurred) { - smallest_distance_scale_factor = bottom.distance_scale_factor; + if (smallest_distance_scale_factor > bottom.distance_scale_factor) + { + smallest_distance_scale_factor = bottom.distance_scale_factor; + } + wall_normal = norm_b; + rect collision = { + .min = tile_aabb.min, + .max = {tile_aabb.max.x, tile_aabb.min.y + 0.2f}, + }; + RENDER_COLLISION_DEBUG_QUAD(collision, ((v3) {1.0f, 0.0f, 0.0f})); } - wall_normal = norm_b; - rect collision = { - .min = tile_aabb.min, - .max = {tile_aabb.max.x, tile_aabb.min.y + 0.2f}, - }; - RENDER_COLLISION_DEBUG_QUAD(collision, ((v3) {1.0f, 0.0f, 0.0f})); } - - struct WallCollision top = get_wall_collision(player->pos, new_p, tile_t, norm_t); - if (top.collision_occurred) + if (!wall_is_internal(tiles, glmth_rect_get_edge(tile_aabb, RECT_EDGE_TOP))) { - if (smallest_distance_scale_factor > top.distance_scale_factor) + struct WallCollision top = get_wall_collision(player->pos, new_p, tile_sum_t, norm_t); + if (top.collision_occurred) { - smallest_distance_scale_factor = top.distance_scale_factor; + if (smallest_distance_scale_factor > top.distance_scale_factor) + { + smallest_distance_scale_factor = top.distance_scale_factor; + } + wall_normal = norm_t; + rect collision = { + .min = {tile_aabb.min.x, tile_aabb.max.y - 0.2f}, + .max = tile_aabb.max, + }; + RENDER_COLLISION_DEBUG_QUAD(collision, ((v3) {0.0f, 1.0f, 0.0f})); } - wall_normal = norm_t; - rect collision = { - .min = {tile_aabb.min.x, tile_aabb.max.y - 0.2f}, - .max = tile_aabb.max, - }; - RENDER_COLLISION_DEBUG_QUAD(collision, ((v3) {0.0f, 1.0f, 0.0f})); } - - struct WallCollision left = get_wall_collision(player->pos, new_p, tile_l, norm_l); - if (left.collision_occurred) + if (!wall_is_internal(tiles, glmth_rect_get_edge(tile_aabb, RECT_EDGE_LEFT))) { - if (smallest_distance_scale_factor > left.distance_scale_factor) + struct WallCollision left = get_wall_collision(player->pos, new_p, tile_sum_l, norm_l); + if (left.collision_occurred) { - smallest_distance_scale_factor = left.distance_scale_factor; + if (smallest_distance_scale_factor > left.distance_scale_factor) + { + smallest_distance_scale_factor = left.distance_scale_factor; + } + wall_normal = norm_l; + rect collision = { + .min = tile_aabb.min, + .max = {tile_aabb.min.x + 0.2f, tile_aabb.max.y}, + }; + RENDER_COLLISION_DEBUG_QUAD(collision, ((v3) {0.0f, 0.0f, 1.0f})); } - wall_normal = norm_l; - rect collision = { - .min = tile_aabb.min, - .max = {tile_aabb.min.x + 0.2f, tile_aabb.max.y}, - }; - RENDER_COLLISION_DEBUG_QUAD(collision, ((v3) {0.0f, 0.0f, 1.0f})); } - - struct WallCollision right = get_wall_collision(player->pos, new_p, tile_r, norm_r); - if (right.collision_occurred) + if (!wall_is_internal(tiles, glmth_rect_get_edge(tile_aabb, RECT_EDGE_RIGHT))) { - if (smallest_distance_scale_factor > right.distance_scale_factor) + struct WallCollision right = get_wall_collision(player->pos, new_p, tile_sum_r, norm_r); + if (right.collision_occurred) { - smallest_distance_scale_factor = right.distance_scale_factor; + if (smallest_distance_scale_factor > right.distance_scale_factor) + { + smallest_distance_scale_factor = right.distance_scale_factor; + } + wall_normal = norm_r; + rect collision = { + .min = {tile_aabb.max.x - 0.2f, tile_aabb.min.y}, + .max = tile_aabb.max, + }; + RENDER_COLLISION_DEBUG_QUAD(collision, ((v3) {1.0f, 0.0f, 1.0f})); } - wall_normal = norm_r; - rect collision = { - .min = {tile_aabb.max.x - 0.2f, tile_aabb.min.y}, - .max = tile_aabb.max, - }; - RENDER_COLLISION_DEBUG_QUAD(collision, ((v3) {1.0f, 0.0f, 1.0f})); } } } diff --git a/src/glmth.h b/src/glmth.h @@ -498,6 +498,43 @@ internal inline rect glmth_minkowski_sum_rect_rect(rect r1, v2 r2_dimensions) return sum; } +enum GlmthRectEdge +{ + RECT_EDGE_BOTTOM, + RECT_EDGE_TOP, + RECT_EDGE_LEFT, + RECT_EDGE_RIGHT, +}; + +internal inline segment glmth_rect_get_edge(rect r, enum GlmthRectEdge e) +{ + v2 nw = {r.min.x, r.max.y}; + v2 ne = r.max; + v2 sw = r.min; + v2 se = {r.max.x, r.min.y}; + + segment edge = {0}; + switch(e) + { + case RECT_EDGE_BOTTOM: + edge = (segment) {sw, se}; + break; + case RECT_EDGE_TOP: + edge = (segment) {nw, ne}; + break; + case RECT_EDGE_LEFT: + edge = (segment) {sw, nw}; + break; + case RECT_EDGE_RIGHT: + edge = (segment) {se, ne}; + break; + default: + assert(false); + break; + } + return edge; +} + internal inline void glmth_v2_print(v2 v) { printf("( %f, %f )\n", v.x, v.y);