diff options
Diffstat (limited to 'in_practice/breakout/game.c')
-rw-r--r-- | in_practice/breakout/game.c | 397 |
1 files changed, 371 insertions, 26 deletions
diff --git a/in_practice/breakout/game.c b/in_practice/breakout/game.c index cf3c0a2..3a84f01 100644 --- a/in_practice/breakout/game.c +++ b/in_practice/breakout/game.c @@ -9,8 +9,11 @@ #include <stdio.h> #include <math.h> +#include <stdlib.h> -static void init_gl(game_t game) +static v2 initial_ball_velocity = {100.0f, -350.0f}; + +static void init_gl(struct game game) { i32 flags; glGetIntegerv(GL_CONTEXT_FLAGS, &flags); @@ -27,56 +30,360 @@ static void init_gl(game_t game) glClearColor(0.16f, 0.16f, 0.16f, 1.0f); } -game_t init_game(i32 width, i32 height) +static void reset_powerups(struct game *game) +{ + for (i32 i = 0; i < max_powerups; i++) { + game->powerups[i].type = powerup_none; + game->powerups[i].destroyed = 1; + } +} + +struct game init_game(i32 width, i32 height) { - game_t game = {0}; - game.state = game_active; + struct game game = {0}; + game.state = game_menu; game.running = 1; game.width = width; game.height = height; game.input = init_input(); init_gl(game); game.bindir = get_bin_dir(); + char dir[512] = {0}; snprintf(dir, 512, "%s/%s", game.bindir, "shaders"); - u32 shader = load_shader(dir, "shader.vert", "shader.frag", 0, "shader"); - glUseProgram(shader); + load_shader(dir, "shader.vert", "shader.frag", 0, "shader"); + load_shader(dir, "particle.vert", "particle.frag", 0, "particle"); + load_shader(dir, "post.vert", "post.frag", 0, "post"); + load_shader(dir, "text.vert", "text.frag", 0, "text"); + mat projection = ortho(0.0f, game.width, game.height, 0.0f, -1.0f, 1.0f); + u32 shader = get_shader("shader"); + glUseProgram(shader); + uniform_mat(shader, "projection", projection); + glUseProgram(0); + shader = get_shader("particle"); + glUseProgram(shader); uniform_mat(shader, "projection", projection); glUseProgram(0); + snprintf(dir, 512, "%s/%s", game.bindir, "textures"); load_texture(dir, "gentleman.jpg", "gentleman", 0); load_texture(dir, "block.png", "block", 0); load_texture(dir, "solid.png", "solid", 0); load_texture(dir, "paddle.png", "paddle", 1); load_texture(dir, "ball.png", "ball", 1); - game.renderer = init_renderer(shader); + load_texture(dir, "particle.png", "particle", 1); + load_texture(dir, "powerup_chaos.png", "powerup_chaos", 1); + load_texture(dir, "powerup_confuse.png", "powerup_confuse", 1); + load_texture(dir, "powerup_increase.png", "powerup_increase", 1); + load_texture(dir, "powerup_passthrough.png", "powerup_passthrough", 1); + load_texture(dir, "powerup_speed.png", "powerup_speed", 1); + load_texture(dir, "powerup_sticky.png", "powerup_sticky", 1); + + game.renderer = init_renderer(get_shader("shader")); + snprintf(dir, 512, "%s/%s", game.bindir, "levels"); - game.level = load_level(0, "first.txt", game.width, game.height / 2.0f); - game.player = (object_t)default_object; + game.levels[0] = load_level(dir, "first.txt", game.width, game.height / 2.0f); + game.levels[1] = load_level(dir, "second.txt", game.width, game.height / 2.0f); + game.levels[2] = load_level(dir, "third.txt", game.width, game.height / 2.0f); + + snprintf(dir, 512, "%s/%s", game.bindir, "audio"); + load_wav(dir, "background.wav", "back"); + load_wav(dir, "solid.wav", "solid"); + load_wav(dir, "tile.wav", "tile"); + load_wav(dir, "powerup.wav", "powerup"); + play_audio("back", 1); + + game.text_renderer = init_text_renderer(game.width, game.height); + snprintf(dir, 512, "%s/%s", game.bindir, "fonts"); + load_font(&game.text_renderer, dir, "ibm.ttf", 32); + + game.player = (struct object)default_object; v2 size = get_texture_size("paddle"); - game.player.size = scale_v2(size, 0.4f); - game.player.pos = (v2){width / 2.0f - game.player.size.x / 2.0f, height - game.player.size.y}; - game.player.vel = v2a(500.0f); + game.player.size = scale_v2(size, 0.2f); + game.player.pos = (v2){ + game.width / 2.0f - game.player.size.x / 2.0f, + game.height - game.player.size.y + }; + game.player.vel = (v2){200.0f, 0.0f}; game.player.texture = get_texture_id("paddle"); - game.ball.o = (object_t)default_object; + game.lives = 3; + game.ball.o = (struct object)default_object; game.ball.radius = 12.5f; game.ball.o.size = v2a(2.0f * game.ball.radius); - game.ball.o.pos = (v2){game.player.pos.x + game.player.size.x / 2.0f - game.ball.o.size.x / 2.0f, game.player.pos.y - game.ball.o.size.y}; - game.ball.o.vel = (v2){100.0f, -350.0f}; + game.ball.o.pos = (v2){ + game.player.pos.x + game.player.size.x / 2.0f - game.ball.o.size.x / 2.0f, + game.player.pos.y - game.ball.o.size.y + }; + game.ball.o.vel = initial_ball_velocity; game.ball.o.texture = get_texture_id("ball"); game.ball.stuck = 1; + game.generator = init_generator(2, get_shader("particle"), get_texture_id("particle")); + game.post_processor = init_post_processor(game.width, game.height, get_shader("post")); + reset_powerups(&game); return game; } -void process_input(game_t *game) +static void reset_level(struct game *game) +{ + for (i32 i = 0; i < game->levels[game->level].count; i++) + game->levels[game->level].tiles[i].destroyed = 0; + game->lives = 3; +} + +static void reset_player(struct game *game) +{ + game->player.pos = (v2){ + game->width / 2.0f - game->player.size.x / 2.0f, + game->height - game->player.size.y + }; + game->ball.o.pos = (v2){ + game->player.pos.x + game->player.size.x / 2.0f - game->ball.o.size.x / 2.0f, + game->player.pos.y - game->ball.o.size.y + }; + game->ball.o.vel = initial_ball_velocity; + game->ball.stuck = 1; +} + +void process_input(struct game *game) { if (key_first_press(game->input.escape)) game->running = 0; + if (key_first_press(game->input.start)) { + if (game->state == game_menu) + game->state = game_active; + else if (game->state == game_win) { + game->post_processor.chaos = 0; + game->state = game_menu; + } + else if (game->state == game_active) { + game->state = game_menu; + } + } + if (game->state != game_menu) + return; + if (key_first_press(game->input.up)) { + game->level = (game->level + 1) % max_levels; + if (!game->levels[game->level].tiles) + game->level--; + reset_level(game); + reset_player(game); + } + if (key_first_press(game->input.down)) { + i32 new = game->level - 1; + if (new == -1) + new = max_levels - 1; + if (game->levels[new].tiles) { + game->level = new; + reset_level(game); + reset_player(game); + } + } +} + +static i32 should_spawn(u32 chance) +{ + u32 random = rand() % chance; + return random == 0; +} + +static i32 can_spawn_powerup(struct game *game) +{ + for (i32 i = 0; i < max_powerups; i++) + if (game->powerups[i].type == powerup_none) + return 1; + return 0; +} + +static void spawn_powerups(struct game *game, v2 pos) +{ + if (!can_spawn_powerup(game)) + return; + struct powerup *powerup; + for (i32 i = 0; i < max_powerups; i++) { + powerup = game->powerups + i; + if (powerup->type == powerup_none) { + break; + } + } + if (should_spawn(75)) { + *powerup = spawn_powerup(pos, (v3){0.5f, 0.5f, 1.0f}, + powerup_speed, 0.0f, get_texture_id("powerup_speed")); + return; + } + if (should_spawn(75)) { + *powerup = spawn_powerup(pos, (v3){1.0f, 0.5f, 1.0f}, + powerup_sticky, 20.0f, get_texture_id("powerup_sticky")); + return; + } + if (should_spawn(75)) { + *powerup = spawn_powerup(pos, (v3){0.5f, 1.0f, 0.5f}, + powerup_pass, 10.0f, get_texture_id("powerup_pass")); + return; + } + if (should_spawn(75)) { + *powerup = spawn_powerup(pos, (v3){1.0f, 0.6f, 0.4f}, + powerup_increase, 0.0f, get_texture_id("powerup_increase")); + return; + } + if (should_spawn(15)) { + *powerup = spawn_powerup(pos, (v3){1.0f, 0.3f, 0.3f}, + powerup_confuse, 15.0f, get_texture_id("powerup_confuse")); + return; + } + if (should_spawn(15)) { + *powerup = spawn_powerup(pos, (v3){0.9f, 0.25f, 0.25f}, + powerup_chaos, 15.0f, get_texture_id("powerup_chaos")); + return; + } +} + +static void activate_powerup(struct game *game, struct powerup powerup) +{ + switch (powerup.type) { + case powerup_speed: + game->ball.o.vel = scale_v2(game->ball.o.vel, 1.2f); + break; + case powerup_sticky: + game->ball.sticky = 1; + game->player.color = (v3){1.0f, 0.5f, 1.0f}; + break; + case powerup_pass: + game->ball.pass = 1; + game->ball.o.color = (v3){1.0f, 0.5f, 0.5f}; + break; + case powerup_increase: + game->player.size.x += 50.0f; + break; + case powerup_confuse: + if (!game->post_processor.confuse) + game->post_processor.confuse = 1; + break; + case powerup_chaos: + if (!game->post_processor.chaos) + game->post_processor.chaos = 1; + break; + default: + break; + } +} + +static void process_tile_collision(struct object *tile, struct game *game) +{ + if (tile->destroyed) + return; + struct collision collision = check_ball_collision(game->ball, *tile); + if (!collision.collide) + return; + if (!tile->solid) { + tile->destroyed = 1; + v2 pos = sub_v2(add_v2(tile->pos, scale_v2(tile->size, 0.5f)), + scale_v2(default_powerup_size, 0.5f)); + spawn_powerups(game, pos); + } else { + game->shake_time = 0.05f; + game->post_processor.shake = 1; + } + direction_enum dir = collision.dir; + v2 diff = collision.diff; + if (game->ball.pass && !tile->solid) + return; + if (dir == direction_left || dir == direction_right) { + game->ball.o.vel.x = -game->ball.o.vel.x; + f32 offset = game->ball.radius - abs(diff.x); + game->ball.o.pos.x += (dir == direction_left) ? offset : -offset; + } else { + game->ball.o.vel.y = -game->ball.o.vel.y; + f32 offset = game->ball.radius - abs(diff.y); + game->ball.o.pos.y += (dir == direction_up) ? -offset : offset; + } + if (tile->solid) + play_audio("solid", 0); + else + play_audio("tile", 0); +} + +static void process_powerup_collisions(struct game *game, struct powerup *powerup) +{ + if (powerup->destroyed) + return; + if (powerup->pos.y >= game->height) { + powerup->destroyed = 1; + powerup->active = 0; + powerup->type = powerup_none; + } + if (check_powerup_collision(*powerup, game->player)) { + activate_powerup(game, *powerup); + powerup->destroyed = 1; + powerup->active = 1; + play_audio("powerup", 0); + } +} + +static void check_collisions(struct game *game) +{ + for (i32 i = 0; i < game->levels[game->level].count; i++) { + struct object *tile = game->levels[game->level].tiles + i; + process_tile_collision(tile, game); + } + struct collision collision = check_ball_collision(game->ball, game->player); + if (!game->ball.stuck && collision.collide) { + f32 center = game->player.pos.x + game->player.size.x / 2.0f; + f32 distance = game->ball.o.pos.x + game->ball.radius - center; + f32 percentage = distance / game->player.size.x / 2.0f; + f32 strength = 10.0f; + v2 old_vel = game->ball.o.vel; + game->ball.o.vel.x = initial_ball_velocity.x * percentage * strength; + game->ball.o.vel.y = -1.0f * abs(game->ball.o.vel.y); + game->ball.o.vel = scale_v2(norm_v2(game->ball.o.vel), length_v2(old_vel)); + game->ball.stuck = game->ball.sticky; + play_audio("tile", 0); + } + for (i32 i = 0; i < max_powerups; i++) { + struct powerup *powerup = game->powerups + i; + process_powerup_collisions(game, powerup); + } +} + +static void update_powerups(struct game *game, f32 dt) +{ + for (i32 i = 0; i < max_powerups; i++) { + struct powerup *powerup = game->powerups + i; + if (!powerup->destroyed) + powerup->pos = add_v2(powerup->pos, scale_v2(powerup->vel, dt)); + if (powerup->active) { + powerup->duration -= dt; + if (powerup->duration <= 0.0f) { + powerup->duration = 0.0f; + powerup->active = 0; + switch (powerup->type) { + case powerup_sticky: + game->ball.sticky = 0; + game->player.color = v3a(1.0f); + break; + case powerup_pass: + game->ball.pass = 0; + game->ball.o.color = v3a(1.0f); + break; + case powerup_confuse: + game->post_processor.confuse = 0; + break; + case powerup_chaos: + game->post_processor.chaos = 0; + break; + default: + break; + } + powerup->type = powerup_none; + } + } + } } -void update_game(game_t *game, f32 dt) +void update_game(struct game *game, f32 dt) { + if ((game->state != game_active) && (game->state != game_win)) + return; f32 vel = game->player.vel.x * dt; if (game->state == game_active) { if (key_is_pressed(game->input.right)) { @@ -97,21 +404,59 @@ void update_game(game_t *game, f32 dt) game->ball.stuck = 0; } move_ball(&game->ball, dt, game->width); - /* collisions */ - for (i32 i = 0; i < game->level.count; i++) { - object_t *tile = game->level.tiles + i; - if (!tile->destroyed && check_collision(game->ball.o, *tile) && (!tile->solid)) { - tile->destroyed = 1; + check_collisions(game); + if (game->ball.o.pos.y >= game->height) { + game->lives--; + if (game->lives == 0) { + reset_level(game); + game->state = game_menu; } + reset_player(game); + } + update_particles(&game->generator, game->ball.o, dt, v2a(game->ball.radius / 2.0f)); + game->time += dt; + if (game->shake_time > 0.0f) { + game->shake_time -= dt; + if (game->shake_time < 0.0f) + game->post_processor.shake = 0; + } + update_powerups(game, dt); + if (game->state == game_active && level_is_complete(game->levels[game->level])) { + reset_level(game); + reset_player(game); + game->post_processor.chaos = 1; + game->state = game_win; } } -void render_game(game_t game) +static void render_powerups(struct game game) { - glClear(GL_COLOR_BUFFER_BIT); - if (game.state == game_active) { - render_level(game.renderer, game.level); + for (i32 i = 0; i < max_powerups; i++) + if (!game.powerups[i].destroyed) + render_powerup(game.renderer, game.powerups[i]); +} + +void render_game(struct game game) +{ + if ((game.state == game_active) || (game.state == game_menu) || (game.state == game_win)) { + begin_render(game.post_processor); + render_level(game.renderer, game.levels[game.level]); + render_powerups(game); render_object(game.renderer, game.player); + render_particles(game.generator); render_object(game.renderer, game.ball.o); + end_render(game.post_processor); + render_post(game.post_processor, game.time); + char lives_str[512] = {0}; + snprintf(lives_str, 512, "lives: %d", game.lives); + render_text(game.text_renderer, lives_str, v2a(5.0f), 1.0f, v3a(1.0f)); + } + if (game.state == game_menu) { + render_text_centered(game.text_renderer, "Press Enter to start", (v2){game.width * 0.5f, game.height * 0.5f}, 1.0f, v3a(1.0f)); + render_text_centered(game.text_renderer, "Press UP and DOWN to select the level", (v2){game.width * 0.5f, game.height * 0.55f}, 0.75f, v3a(1.0f)); + } + if (game.state == game_win) { + render_text_centered(game.text_renderer, "You WON!!!", (v2){game.width * 0.5f, game.height * 0.5f}, 1.0f, v3a(1.0f)); + render_text_centered(game.text_renderer, "Press Enter to retry or ESC to quit", (v2){game.width * 0.5f, game.height * 0.55f}, 0.75f, v3a(1.0f)); } } |