a-game

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

commit 27d37834dd6757196353842ae94ace93866ee1ac
parent 37f3791a33110ddee4973098008ec3765f9c9389
Author: amin <dev@aminmesbah.com>
Date:   Tue, 23 Apr 2019 20:00:17 +0000

Try positioning player properly after collision

This still has bugs. You can get inside a tile in some frames.

FossilOrigin-Name: 73f17ca1d4375ddf1ccd6e6534c4a90327034c19cd0b94cebdf6f710abfa1a81
Diffstat:
Msrc/game.c | 221+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/glmth.h | 8++++++++
2 files changed, 122 insertions(+), 107 deletions(-)

diff --git a/src/game.c b/src/game.c @@ -15,8 +15,6 @@ internal void game_init(struct GameMemory *game_memory, v2u framebuffer) // init player game_state->player.pos = (v2) {12.5f, 5.0f}; - // TODO: remove test velocity - game_state->player.velocity = (v2) {290.0f, 80.0f}; // 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 @@ -79,8 +77,7 @@ internal void game_init(struct GameMemory *game_memory, v2u framebuffer) struct WallCollision { bool collision_occurred; - f32 game_time_offset; - v2 collision_point; + f32 distance_scale_factor; }; internal struct WallCollision get_wall_collision(v2 entity_p_initial, v2 entity_p_final, segment wall) @@ -108,18 +105,18 @@ internal struct WallCollision get_wall_collision(v2 entity_p_initial, v2 entity_ if (player_delta.E[wall_normal_axis] != 0.0f) { f32 wall_position = wall.min.E[wall_normal_axis]; - f32 initial_d = wall.min.E[wall_normal_axis] - entity_p_initial.E[wall_normal_axis]; + f32 initial_d = wall_position - entity_p_initial.E[wall_normal_axis]; - f32 scale_factor = initial_d / player_delta.E[wall_normal_axis]; - v2 collision_point = glmth_v2_a(entity_p_initial, glmth_v2f_m(player_delta, scale_factor)); + f32 segment_scale_factor = initial_d / player_delta.E[wall_normal_axis]; + v2 collision_point = glmth_v2_a(entity_p_initial, glmth_v2f_m(player_delta, segment_scale_factor)); - if (scale_factor > 0.0f && scale_factor < 1.0f) + if (segment_scale_factor >= 0.0f && segment_scale_factor <= 1.0f) { if (collision_point.E[wall_axis] >= wall.min.E[wall_axis] && collision_point.E[wall_axis] <= wall.max.E[wall_axis]) { result.collision_occurred = true; - result.collision_point = collision_point; + result.distance_scale_factor = segment_scale_factor - 0.0001f; } } } @@ -287,11 +284,9 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga { struct Entity *player = &game_state->player; f32 dt = game_input->dt; - //f32 max_meters_per_second = 5.0f; + f32 max_meters_per_second = 5.0f; f32 acceleration_rate = 50.0f; - // TODO: refriction - //f32 friction = 0.7f; - f32 friction = 1.0f; + f32 friction = 0.7f; if (game_input->key_up) { @@ -323,9 +318,8 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga // Semi implicit Euler integration: https://gafferongames.com/post/integration_basics/ player->velocity = glmth_v2_a(player->velocity, glmth_v2f_m(player->acceleration, dt)); - // TODO: reclamp - //glmth_clamp(&player->velocity.x, -max_meters_per_second, max_meters_per_second); - //glmth_clamp(&player->velocity.y, -max_meters_per_second, max_meters_per_second); + glmth_clamp(&player->velocity.x, -max_meters_per_second, max_meters_per_second); + glmth_clamp(&player->velocity.y, -max_meters_per_second, max_meters_per_second); #if 0 { @@ -376,6 +370,7 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga } #else { +#define RENDER_COLLISION_DEBUG_QUAD(r, c) render_debug_quad(game_state, (r), (c), &view, &projection); v2 old_p = player->pos; v2 new_p = glmth_v2_a(old_p, glmth_v2f_m(player->velocity, dt)); @@ -383,10 +378,10 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga .min = {glmth_min(old_p.x, new_p.x), glmth_min(old_p.y, new_p.y)}, .max = {glmth_max(old_p.x, new_p.x), glmth_max(old_p.y, new_p.y)}, }; - //render_debug_quad(game_state, player_traversal_bb, (v3) {0.4f, 0.4f, 0.4f}, &view, &projection); + //RENDER_COLLISION_DEBUG_QUAD(player_traversal_bb, ((v3) {0.4f, 0.4f, 0.4f})); rect player_traversal_occupancy_bb = glmth_minkowski_sum_rect_rect(player_traversal_bb, player->dimensions); - //render_debug_quad(game_state, player_traversal_occupancy_bb, (v3) {0.8f, 0.4f, 0.8f}, &view, &projection); + //RENDER_COLLISION_DEBUG_QUAD(player_traversal_occupancy_bb, ((v3) {0.8f, 0.4f, 0.8f})); rect tile_search_range = { .min = { @@ -398,109 +393,121 @@ void game_update_and_render(struct GameMemory *game_memory, struct GameInput *ga (i32)glmth_min(ROOM_TILE_DIM_Y, player_traversal_occupancy_bb.max.y + 1), }, }; - render_debug_quad(game_state, tile_search_range, (v3) {0.8f, 0.8f, 0.8f}, &view, &projection); + RENDER_COLLISION_DEBUG_QUAD(tile_search_range, ((v3) {0.8f, 0.8f, 0.8f})); - for (i32 tile_y = tile_search_range.min.y; tile_y < tile_search_range.max.y; tile_y++) + v2 player_delta = glmth_v2_s(new_p, old_p); + f32 remaining_time_factor = 1.0f; + for (u32 i = 0; i < 4 && remaining_time_factor > 0.0f; i++) { - for (i32 tile_x = tile_search_range.min.x; tile_x < tile_search_range.max.x; tile_x++) + v2 wall_normal = {0}; + f32 smallest_distance_scale_factor = 1.0f; + for (i32 tile_y = tile_search_range.min.y; tile_y < tile_search_range.max.y; tile_y++) { - assert(tile_x < ROOM_TILE_DIM_X); - assert(tile_y < ROOM_TILE_DIM_Y); - - v2u tile_index = {tile_x, ROOM_TILE_DIM_Y - tile_y - 1}; - u32 tile_id = tiles[tile_index.y][tile_index.x]; - - if (tile_id == 1) + for (i32 tile_x = tile_search_range.min.x; tile_x < tile_search_range.max.x; tile_x++) { - rect tile_aabb = { - .min = {tile_x, tile_y}, - .max = {tile_x + tile_size, tile_y + tile_size}, - }; - - rect tile_player_sum = glmth_minkowski_sum_rect_rect(tile_aabb, player->dimensions); - render_debug_quad(game_state, tile_player_sum, (v3) {0.8f, 0.4f, 0.4f}, &view, &projection); + assert(tile_x < ROOM_TILE_DIM_X); + assert(tile_y < ROOM_TILE_DIM_Y); - 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}; + v2u tile_index = {tile_x, ROOM_TILE_DIM_Y - tile_y - 1}; + u32 tile_id = tiles[tile_index.y][tile_index.x]; - 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}; - - v2 wall_normal = {0}; - - struct WallCollision bottom = get_wall_collision(old_p, new_p, tile_b); - if (bottom.collision_occurred) + if (tile_id == 1) { - wall_normal = (v2) {0.0f, -1.0f}; - rect collision = { - .min = tile_aabb.min, - .max = {tile_aabb.max.x, tile_aabb.min.y + 0.2f}, + rect tile_aabb = { + .min = {tile_x, tile_y}, + .max = {tile_x + tile_size, tile_y + tile_size}, }; - render_debug_quad(game_state, collision, (v3) {1.0f, 0.0f, 0.0f}, &view, &projection); - } - - struct WallCollision top = get_wall_collision(old_p, new_p, tile_t); - if (top.collision_occurred) - { - wall_normal = (v2) {0.0f, 1.0f}; - rect collision = { - .min = {tile_aabb.min.x, tile_aabb.max.y - 0.2f}, - .max = tile_aabb.max, - }; - render_debug_quad(game_state, collision, (v3) {0.0f, 1.0f, 0.0f}, &view, &projection); - } - - struct WallCollision left = get_wall_collision(old_p, new_p, tile_l); - if (left.collision_occurred) - { - wall_normal = (v2) {-1.0f, 0.0f}; - rect collision = { - .min = tile_aabb.min, - .max = {tile_aabb.min.x + 0.2f, tile_aabb.max.y}, - }; - render_debug_quad(game_state, collision, (v3) {0.0f, 0.0f, 1.0f}, &view, &projection); - } - struct WallCollision right = get_wall_collision(old_p, new_p, tile_r); - if (right.collision_occurred) - { - wall_normal = (v2) {1.0f, 0.0f}; - rect collision = { - .min = {tile_aabb.max.x - 0.2f, tile_aabb.min.y}, - .max = tile_aabb.max, - }; - render_debug_quad(game_state, collision, (v3) {1.0f, 0.0f, 1.0f}, &view, &projection); + 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}; + + + struct WallCollision bottom = get_wall_collision(old_p, new_p, tile_b); + if (bottom.collision_occurred) + { + if (smallest_distance_scale_factor > bottom.distance_scale_factor) + { + smallest_distance_scale_factor = bottom.distance_scale_factor; + } + wall_normal = (v2) {0.0f, -1.0f}; + 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(old_p, new_p, tile_t); + if (top.collision_occurred) + { + if (smallest_distance_scale_factor > top.distance_scale_factor) + { + smallest_distance_scale_factor = top.distance_scale_factor; + } + wall_normal = (v2) {0.0f, 1.0f}; + 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(old_p, new_p, tile_l); + if (left.collision_occurred) + { + if (smallest_distance_scale_factor > left.distance_scale_factor) + { + smallest_distance_scale_factor = left.distance_scale_factor; + } + wall_normal = (v2) {-1.0f, 0.0f}; + 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(old_p, new_p, tile_r); + if (right.collision_occurred) + { + if (smallest_distance_scale_factor > right.distance_scale_factor) + { + smallest_distance_scale_factor = right.distance_scale_factor; + } + wall_normal = (v2) {1.0f, 0.0f}; + 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})); + } } } } - } - - //player->pos = new_p; - // render new player - { - shader_use(&game_state->player.shader); - glBindVertexArray(game_state->tiles.vao); - struct Entity player = game_state->player; - - m4 model = glmth_m4_init_id(); - model = glmth_translate(model, (v3) {new_p.x, new_p.y, 0.0f}); - model = glmth_scale(model, (v3) {player.dimensions.x, player.dimensions.y, 1.0f}); - - v3 color = (v3) { 1.0f, 0.0f, 1.0f }; - - shader_setv3(&game_state->tiles.shader, "color", &color); - shader_setm4(&game_state->tiles.shader, "model", &model); - shader_setm4(&game_state->tiles.shader, "view", &view); - shader_setm4(&game_state->tiles.shader, "projection", &projection); - - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); - glBindVertexArray(0); + v2 nearest_collision_p = glmth_v2_a(old_p, glmth_v2f_m(player_delta, smallest_distance_scale_factor)); + player->pos = nearest_collision_p; + player->velocity = glmth_v2_s( + player->velocity, + glmth_v2f_m(wall_normal, glmth_v2_dot(player->velocity, wall_normal)) + ); + player_delta = glmth_v2_s( + player_delta, + glmth_v2f_m(player_delta, glmth_v2_dot(player->velocity, wall_normal)) + ); + remaining_time_factor -= smallest_distance_scale_factor * remaining_time_factor; } } +#undef RENDER_COLLISION_DEBUG_QUAD #endif glmth_clamp(&player->pos.x, 0.0f, ROOM_TILE_DIM_X); glmth_clamp(&player->pos.y, 0.0f, ROOM_TILE_DIM_Y); diff --git a/src/glmth.h b/src/glmth.h @@ -175,8 +175,16 @@ internal inline v2 glmth_v2_normalize(v2 v) return r; } +internal inline f32 glmth_v2_dot(v2 vec1, v2 vec2) +{ + // dot product, a.k.a. inner product or scalar product + f32 dot_product = (vec1.x * vec2.x) + (vec1.y * vec2.y); + return dot_product; +} + internal inline v3 glmth_v3_cross(v3 vec1, v3 vec2) { + // cross product, a.k.a. outer product or vector product v3 r = { .x = (vec1.y * vec2.z) - (vec1.z * vec2.y), .y = (vec1.z * vec2.x) - (vec1.x * vec2.z),