diff options
-rw-r--r-- | levels/level0.txt | 27 | ||||
-rwxr-xr-x | source/main.cpp | 338 | ||||
-rw-r--r-- | source/todo.txt | 5 |
3 files changed, 270 insertions, 100 deletions
diff --git a/levels/level0.txt b/levels/level0.txt index 822c8d4..27dd2fc 100644 --- a/levels/level0.txt +++ b/levels/level0.txt @@ -1,10 +1,19 @@ -# level format version number +# level format 0x1 -# entity_id posx poxy sizex sizey -0 150 70 1 1 -1 0 0 50 1 -1 0 0 1 50 -1 200 150 1 1 -1 400 250 1 1 -1 840 400 10 1 -2 900 800 1 1 +# type posx posy sizex sizey + 0 150 70 1 1 + 1 0 0 50 1 + 1 0 0 1 50 + 1 200 150 1 1 + 1 400 250 1 1 + 1 840 400 10 1 + 2 900 800 1 1 +# == gameplay elements == +# gravity inverter +# type posx posy sizex sizey + 3 650 60 1 1 +# teleporter +# type posx posy sizex sizey id link_id + 4 800 90 1 2 50 51 + 4 800 490 1 2 51 50 + diff --git a/source/main.cpp b/source/main.cpp index 5ebfcc2..b66c37e 100755 --- a/source/main.cpp +++ b/source/main.cpp @@ -117,10 +117,6 @@ struct Rect { Vec3 position; }; -#define PLAYER_COLORS Vec3{0.45f, 0.8f, 0.2f} -#define OBSTACLE_COLORS Vec3{0.0f, 0.0f, 0.0f} -#define GOAL_COLORS Vec3{1.0f, 0.0f, 0.0f} - #define PLAYER_Z -1.0f #define OBSTACLE_Z -2.0f #define GOAL_Z -3.0f @@ -128,12 +124,17 @@ struct Rect { enum ENTITY_TYPE { PLAYER = 0, OBSTACLE = 1, - GOAL = 2 + GOAL = 2, + INVERT_GRAVITY = 3, + TELEPORT = 4, }; +static r32 entity_z[10]; +static Vec3 entity_colors[10]; + struct Entity { // @todo: set a base resolution and design the game elements around it - u32 id; + s32 id; ENTITY_TYPE type; // raw property values in pixels Vec3 raw_position; @@ -142,6 +143,8 @@ struct Entity { Vec3 position; Vec2 size; Rect bounds; + // teleporter + u32 link_id; // which portal this is linked to }; struct EntityInfo { @@ -261,9 +264,13 @@ struct GameState { u32 level_index; Str256 level_name; Level game_level; - Entity player; + EntityInfo player; EntityInfo goal; EntityInfoArr obstacles; + // gameplay + b8 flip_gravity; + b8 inside_teleporter; + b8 teleporting; }; Rect rect(Vec3 position, Vec2 size) { @@ -278,7 +285,25 @@ Rect rect(Vec3 position, Vec2 size) { return r; } -void level_load(GameState *state, Arena *level_arena, Str256 level_path) { +Entity get_entity_by_id(GameState state, u32 id) { + Entity res; + res.id = -1; + + for (int i = 0; i < state.game_level.entity_count; i++) { + Entity e = state.game_level.entities[i]; + if (e.id == id) { + res = e; + break; + } + } + + // We should always have the entity we are trying to look for + SDL_assert(res.id != -1); + + return res; +} + +void load_level(GameState *state, Arena *level_arena, Str256 level_path) { // @step: initialise arena_clear(level_arena); memset(&state->game_level, 0, sizeof(Level)); @@ -329,20 +354,17 @@ void level_load(GameState *state, Arena *level_arena, Str256 level_path) { continue; } break; case 1: { - level_entity.id = entity_id_counter; switch (sub_prop_flag) { case 0: { + // auto-generated id + // @note: will be overwritten when id is explicitly defined + level_entity.id = entity_id_counter; + // type level_entity.type = (ENTITY_TYPE)strtol(level_property.buffer, NULL, 10); // set z index based off of entity type - if (level_entity.type == PLAYER) { - level_entity.raw_position.z = PLAYER_Z; - } else if (level_entity.type == OBSTACLE) { - level_entity.raw_position.z = OBSTACLE_Z; - } else if (level_entity.type == GOAL) { - level_entity.raw_position.z = GOAL_Z; - } + level_entity.raw_position.z = entity_z[level_entity.type]; } break; case 1: { // posx @@ -360,6 +382,14 @@ void level_load(GameState *state, Arena *level_arena, Str256 level_path) { // sizey level_entity.raw_size.y = strtol(level_property.buffer, NULL, 10); } break; + case 5: { + // pre-defined id + level_entity.id = strtol(level_property.buffer, NULL, 10); + } break; + case 6: { + // linked id + level_entity.link_id = strtol(level_property.buffer, NULL, 10); + } default: { } break; } @@ -393,22 +423,20 @@ void level_load(GameState *state, Arena *level_arena, Str256 level_path) { e.size = e.raw_size * state->atom_size * state->render_scale.x; e.bounds = rect(e.position, e.size); + EntityInfo o; + o.id = e.id; + o.index = i; + switch (e.type) { case PLAYER: { - state->player = e; + state->player = o; } break; - case OBSTACLE: { - EntityInfo o; - o.id = e.id; - o.index = i; + case OBSTACLE: + case INVERT_GRAVITY: { state->obstacles.buffer[state->obstacles.size] = o; state->obstacles.size++; } break; case GOAL: { - EntityInfo o; - o.id = e.id; - o.index = i; - state->goal = o; } break; default: { @@ -932,6 +960,23 @@ int main(int argc, char* argv[]) u32 scr_width = 1280; u32 scr_height = 960; + { + // entity configs setup + entity_colors[PLAYER] = Vec3{0.45f, 0.8f, 0.2f}; + entity_colors[OBSTACLE] = Vec3{1.0f, 1.0f, 1.0f}; + entity_colors[GOAL] = Vec3{ 0.93f, 0.7f, 0.27f }; + entity_colors[INVERT_GRAVITY] = Vec3{1.0f, 0.0f, 0.0f}; + entity_colors[TELEPORT] = Vec3{0.0f, 0.0f, 0.0f}; + + entity_z[OBSTACLE] = -3.0f; + entity_z[GOAL] = -4.0f; + { + entity_z[TELEPORT] = -5.0f; + entity_z[INVERT_GRAVITY] = -5.0f; + } + entity_z[PLAYER] = -6.0f; + } + if (SDL_Init(SDL_INIT_VIDEO) != 0) { printf("Error initialising SDL2: %s\n", SDL_GetError()); @@ -1059,6 +1104,7 @@ int main(int argc, char* argv[]) ); // @section: gameplay variables + r32 gravity_diry = 1.0f; r32 motion_scale = 2.0f; r32 fall_accelx = 3.0f*motion_scale; r32 move_accelx = 4.0f*motion_scale; @@ -1099,7 +1145,7 @@ int main(int argc, char* argv[]) Str256 _level_name = str256(level_names[state.level_index]); Str256 level_path = base; str_push256(&level_path, _level_name); - level_load(&state, &level_arena, level_path); + load_level(&state, &level_arena, level_path); // gameplay camera movement stuff Vec2 cam_lt_limit = {0}; @@ -1116,6 +1162,7 @@ int main(int argc, char* argv[]) b8 is_key_down_x = false; // gravity calculations + b8 was_colliding = 0; b8 collidex = 0; b8 collidey = 0; b8 is_gravity = 0; @@ -1124,6 +1171,10 @@ int main(int argc, char* argv[]) FrameTimer timer = frametimer(); +#if AUDIO + // @resume: audio has clicky sound, I need to figure generate audio clips that are better. + // Why is this difficult + // @section: audio_setup ma_result result; ma_engine engine; @@ -1134,16 +1185,30 @@ int main(int argc, char* argv[]) return -1; } - // @resume: playing around with audio - ma_sound sound; - const char *sound_path = "assets/audio/Click_Soft_01.mp3"; - result = ma_sound_init_from_file(&engine, sound_path, 0, NULL, NULL, &sound); + ma_sound jump_sound; + ma_sound landing_sound; + ma_sound level_complete_sound; + const char *sound_path = "assets/audio/jump.wav"; + result = ma_sound_init_from_file(&engine, sound_path, 0, NULL, NULL, &jump_sound); if (result != MA_SUCCESS) { SDL_Log("Failed to load sound: %s\n", sound_path); return -1; } - //ma_sound_set_pitch(&sound, 1.5f); - //ma_sound_set_looping(&sound, 1); + + sound_path = "assets/audio/landing.wav"; + result = ma_sound_init_from_file(&engine, sound_path, 0, NULL, NULL, &landing_sound); + if (result != MA_SUCCESS) { + SDL_Log("Failed to load sound: %s\n", sound_path); + return -1; + } + + sound_path = "assets/audio/level_complete.wav"; + result = ma_sound_init_from_file(&engine, sound_path, 0, NULL, NULL, &level_complete_sound); + if (result != MA_SUCCESS) { + SDL_Log("Failed to load sound: %s\n", sound_path); + return -1; + } +#endif while (game_running) { @@ -1190,27 +1255,27 @@ int main(int argc, char* argv[]) // @todo: fix this janky manual camera movement if (ev.key.keysym.sym == SDLK_F5) { - level_load(&state, &level_arena, level_path); + load_level(&state, &level_arena, level_path); renderer.cam_update = true; } if (ev.key.keysym.sym == SDLK_LEFT) { - renderer.cam_pos.x -= 20.0f * state.render_scale.x; + renderer.cam_pos.x -= 40.0f * state.render_scale.x; renderer.cam_update = true; } if (ev.key.keysym.sym == SDLK_RIGHT) { - renderer.cam_pos.x += 20.0f * state.render_scale.x; + renderer.cam_pos.x += 40.0f * state.render_scale.x; renderer.cam_update = true; } if (ev.key.keysym.sym == SDLK_UP) { - renderer.cam_pos.y += 20.0f * state.render_scale.x; + renderer.cam_pos.y += 40.0f * state.render_scale.x; renderer.cam_update = true; } if (ev.key.keysym.sym == SDLK_DOWN) { - renderer.cam_pos.y -= 20.0f * state.render_scale.x; + renderer.cam_pos.y -= 40.0f * state.render_scale.x; renderer.cam_update = true; } if (renderer.cam_update) { @@ -1242,6 +1307,10 @@ int main(int argc, char* argv[]) controller.move_right = 0; key_down_time[PK_D] = 0.0f; } + if (ev.key.keysym.sym == SDLK_i) + { + state.flip_gravity = 1; + } } break; default: { @@ -1256,7 +1325,7 @@ int main(int argc, char* argv[]) Str256 _level_name = str256(level_names[state.level_index]); Str256 level_path = base; str_push256(&level_path, _level_name); - level_load(&state, &level_arena, level_path); + load_level(&state, &level_arena, level_path); } // @section: input processing @@ -1268,6 +1337,14 @@ int main(int argc, char* argv[]) effective_force = 0.0f; p_motion_dir = {0}; } + if (state.flip_gravity) + { + // @resume: I need to add a buffer zone, something like some iframes, so that once I touch a gravity block + // I don't reflip gravity if I am in contact with the block for 1-2 seconds right after first colliding + gravity_diry = gravity_diry > 0.0f ? -0.8f : 1.0f; + //gravity_diry *= -1.0f; + state.flip_gravity = 0; + } if (controller.move_up) { p_move_dir.y = 1.0f; @@ -1408,19 +1485,19 @@ int main(int argc, char* argv[]) { // vertical motion when falling - r32 dy1 = player_velocity.y; - dy1 = dy1 + freefall_accel*timer.tDelta; + r32 dy1 = player_velocity.y; + dy1 = dy1 + gravity_diry * freefall_accel * timer.tDelta; if (controller.jump) { - dy1 = jump_force; + dy1 = gravity_diry*jump_force; if (!collidey) { // if we are in the air, the jump force is 75% of normal - dy1 = jump_force * 0.75f; + dy1 = gravity_diry * jump_force * 0.75f; } } - if (dy1 < 0.0f) { - p_motion_dir.y = -1.0f; - } else if (dy1 > 0.0f) { - p_motion_dir.y = 1.0f; + if (dy1 < gravity_diry * -0.01f) { + p_motion_dir.y = -gravity_diry; + } else if (dy1 > gravity_diry * 0.01f) { + p_motion_dir.y = gravity_diry; } player_velocity.y = dy1; pd_1.y = dy1; @@ -1445,11 +1522,12 @@ int main(int argc, char* argv[]) // @section: collision + Entity player = state.game_level.entities[state.player.index]; Vec3 next_player_position; - next_player_position.x = state.player.position.x + pd_1.x; - next_player_position.y = state.player.position.y + pd_1.y; + next_player_position.x = player.position.x + pd_1.x; + next_player_position.y = player.position.y + pd_1.y; - Rect player_next = rect(next_player_position, state.player.size); + Rect player_next = rect(next_player_position, player.size); b8 is_collide_x = 0; b8 is_collide_y = 0; @@ -1468,10 +1546,10 @@ int main(int argc, char* argv[]) b8 t_collide_bottom = 0; b8 t_collide_top = 0; - r32 prev_top = state.player.bounds.tl.y; - r32 prev_left = state.player.bounds.tl.x; - r32 prev_bottom = state.player.bounds.br.y; - r32 prev_right = state.player.bounds.br.x; + r32 prev_top = player.bounds.tl.y; + r32 prev_left = player.bounds.tl.x; + r32 prev_bottom = player.bounds.br.y; + r32 prev_right = player.bounds.br.x; r32 p_top = player_next.tl.y; r32 p_left = player_next.tl.x; @@ -1501,9 +1579,12 @@ int main(int argc, char* argv[]) // @func: update_player_positions_if_sides_colliding if (t_collide_top) { - state.player.position.y -= (prev_bottom - t_top - 0.1f); + player.position.y -= (prev_bottom - t_top - 0.1f); } else if (t_collide_bottom) { - state.player.position.y += (t_bottom - prev_top - 0.1f); + player.position.y += (t_bottom - prev_top - 0.1f); + } + if (e.type == INVERT_GRAVITY && (t_collide_x || t_collide_top || t_collide_bottom)) { + state.flip_gravity = 1; } is_collide_x = is_collide_x || t_collide_x; @@ -1514,13 +1595,13 @@ int main(int argc, char* argv[]) if (p_motion_dir.x != 0.0f) { renderer.cam_update = true; } - state.player.position.x = next_player_position.x; + player.position.x = next_player_position.x; } if (!is_collide_y) { if (p_motion_dir.y != 0.0f) { renderer.cam_update = true; } - state.player.position.y = next_player_position.y; + player.position.y = next_player_position.y; } // check collision with goal @@ -1528,17 +1609,6 @@ int main(int argc, char* argv[]) Entity goal = state.game_level.entities[state.goal.index]; Rect target = goal.bounds; - b8 t_collide_x = 0; - // need to adjust player position in case of vertical collisions - // so need to check which player side collides - b8 t_collide_bottom = 0; - b8 t_collide_top = 0; - - r32 prev_top = state.player.bounds.tl.y; - r32 prev_left = state.player.bounds.tl.x; - r32 prev_bottom = state.player.bounds.br.y; - r32 prev_right = state.player.bounds.br.x; - r32 p_top = player_next.tl.y; r32 p_left = player_next.tl.x; r32 p_bottom = player_next.br.y; @@ -1549,32 +1619,95 @@ int main(int argc, char* argv[]) r32 t_right = target.br.x; r32 t_bottom = target.br.y; - b8 prev_collide_x = !(prev_left > t_right || prev_right < t_left); - b8 new_collide_yb = (p_bottom < t_top && p_top > t_top); - b8 new_collide_yt = (p_top > t_bottom && p_bottom < t_bottom); - if (prev_collide_x && new_collide_yb) { - t_collide_top = 1; + b8 t_collide = !((p_left > t_right) || (p_right < t_left) || (p_top < t_bottom) || (p_bottom > t_top)); + + state.level_state = t_collide; + } + + // @section: teleport + b8 inside_teleporter_now = 0; + b8 teleporting_now = state.teleporting; + Vec2 teleported_position = Vec2{player.position.x, player.position.y}; + for (u32 i = 0; i < state.game_level.entity_count; i++) { + /* + * @note; + * TELEPORT START ... + * 1. go inside a teleport block, player marked as in block + * 2. hit teleport block center, player marked as teleporting + * 3. player teleported to new block + * 4. once player exits the new block, player marked as in block false + * 5. then player marked as teleporting false + * ... TELEPORT COMPLETE + */ + Entity e = state.game_level.entities[i]; + if (e.type != TELEPORT) { + continue; } - if (prev_collide_x && new_collide_yt) { - t_collide_bottom = 1; + + Rect target = e.bounds; + + r32 p_top = player.bounds.tl.y; + r32 p_left = player.bounds.tl.x; + r32 p_bottom = player.bounds.br.y; + r32 p_right = player.bounds.br.x; + + r32 t_left = target.tl.x; + r32 t_top = target.tl.y; + r32 t_right = target.br.x; + r32 t_bottom = target.br.y; + + if (teleporting_now) { + // check if player is outside of this teleport block or not + b8 t_collide = !(p_left > t_right || p_right < t_left || p_top < t_bottom || p_bottom > t_top); + inside_teleporter_now |= t_collide; + + continue; + } + // check if player is completely inside teleport block + b8 t_inside = !(p_left > t_right || p_right < t_left || p_top < t_bottom || p_bottom > t_top); + //b8 t_inside = p_left >= t_left && p_top <= t_top && p_right <= t_right && p_bottom >= t_bottom; + if (!t_inside) { + continue; } - b8 prev_collide_y = !(prev_top < t_bottom || prev_bottom > t_top); - b8 new_collide_x = !(p_right < t_left || p_left > t_right); - if (prev_collide_y && new_collide_x) { - t_collide_x = 1; + inside_teleporter_now |= t_inside; + + // check if player x-axis is within teleport x-axis + Vec2 displacement; + displacement.x = player.position.x - e.position.x; + displacement.y = player.position.y - e.position.y; + + if (ABS(displacement.x) <= 5.0f*render_scale) { + teleporting_now = 1; + { + // @step: teleport_player + Entity teleport_to = get_entity_by_id(state, e.link_id); + // set next position + teleported_position.x = teleport_to.position.x + displacement.x; + teleported_position.y = teleport_to.position.y + displacement.y; + // add displacement so it gives a smooth effect of just moving over to someplace + } + } + } + { + // update teleport variable + state.inside_teleporter = inside_teleporter_now; + state.teleporting = teleporting_now && state.inside_teleporter; + if (state.teleporting) { + player.position.x = teleported_position.x; + player.position.y = teleported_position.y; } - state.level_state = t_collide_x || t_collide_top || t_collide_bottom; } - state.player.bounds = rect(state.player.position, state.player.size); + player.bounds = rect(player.position, player.size); + was_colliding = collidex || collidey; collidex = is_collide_x; collidey = is_collide_y; // @func: update_camera if (renderer.cam_update == true) { renderer.cam_update = false; - Vec2 player_screen = state.player.position.v2() - renderer.cam_pos.v2(); + Vec2 player_screen = player.position.v2() - renderer.cam_pos.v2(); if (player_screen.x <= cam_lt_limit.x && p_motion_dir.x == -1) { renderer.cam_pos.x += pd_1.x; @@ -1601,19 +1734,35 @@ int main(int argc, char* argv[]) renderer.cam_update = false; } } + + { + // update player entity + state.game_level.entities[state.player.index] = player; + } // output +#if AUDIO // @section: audio if (controller.jump) { - ma_sound_start(&sound); + ma_sound_start(&jump_sound); } + if ((collidex || collidey) && !was_colliding) { + //ma_sound_set_pitch(&landing_sound, 0.5f); + //ma_sound_set_volume(&landing_sound, 0.2); + ma_sound_start(&landing_sound); + } + if (state.level_state == 1) { + ma_sound_start(&level_complete_sound); + } +#endif glClearColor(0.8f, 0.5f, 0.7f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // @section: rendering // render_player +#if old_drawing gl_draw_colored_quad_optimized( &renderer, state.player.position, @@ -1629,15 +1778,17 @@ int main(int argc, char* argv[]) goal.size, Vec3{ 0.93f, 0.7f, 0.27f }); } - // render_obstacles - for (int i = 0; i < state.obstacles.size; i++) { - u32 index = state.obstacles.buffer[i].index; - Entity entity = state.game_level.entities[index]; +#endif + + // render_entities + for (int i = 0; i < state.game_level.entity_count; i++) { + Entity entity = state.game_level.entities[i]; + Vec3 color = entity_colors[entity.type]; gl_draw_colored_quad_optimized( &renderer, - Vec3{entity.position.x, entity.position.y, -2.0f}, + entity.position, entity.size, - Vec3{1.0f, 1.0f, 1.0f} + color ); } @@ -1693,13 +1844,20 @@ int main(int argc, char* argv[]) 28.0f*render_scale, // size Vec3{0.0f, 0.0f, 0.0f}); // color + sprintf(fmt_buffer, "inside_teleporter: %d\nteleporting: %d", state.inside_teleporter, state.teleporting); + gl_render_text(&renderer, + fmt_buffer, + Vec2{900.0f, 190.0f}, // position + 28.0f*render_scale, // size + Vec3{0.0f, 0.0f, 0.0f}); // color + SDL_GL_SwapWindow(window); update_frame_timer(&timer); enforce_frame_rate(&timer, 60); } - ma_engine_uninit(&engine); + //ma_engine_uninit(&engine); free(level_mem); free(batch_memory); free(renderer.ui_text.transforms); diff --git a/source/todo.txt b/source/todo.txt index 6219117..90d4117 100644 --- a/source/todo.txt +++ b/source/todo.txt @@ -26,12 +26,15 @@ DONE: - Level completion Object - Better level format, not having to specify the level element count in the level file. +- Added invert gravity mechanic DOING: +- teleport block mechanic + +BLOCKED: - Audio: figure out what the correct audio sound for jumping will be. TODO: -- Gameplay programming with levels - Update camera follower for centering player in view (with limits) after a few seconds (maybe like 2 seconds) - make movement grid based? let's see but probably not |