From c76697d51158ed5e52bd697c71fbdfed1ec25a3f Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Thu, 8 Feb 2024 19:53:09 -0800 Subject: Add Pong demo. --- game/src/plugins/CMakeLists.txt | 18 ++- game/src/plugins/pong.c | 237 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 252 insertions(+), 3 deletions(-) create mode 100644 game/src/plugins/pong.c diff --git a/game/src/plugins/CMakeLists.txt b/game/src/plugins/CMakeLists.txt index ecb2a45..e5abbb8 100644 --- a/game/src/plugins/CMakeLists.txt +++ b/game/src/plugins/CMakeLists.txt @@ -2,16 +2,28 @@ cmake_minimum_required(VERSION 3.0) project(plugins) -set(LINK_LIBRARIES cstring math gfx) +set(LINK_LIBRARIES cstring math gfx gfx-app) + +# GLTF viewer add_library(gltf_view SHARED gltf_view.c) +target_link_libraries(gltf_view PUBLIC + ${LINK_LIBRARIES}) + +# Texture viewer + add_library(texture_view SHARED texture_view.c) -target_link_libraries(gltf_view PUBLIC +target_link_libraries(texture_view PUBLIC ${LINK_LIBRARIES}) -target_link_libraries(texture_view PUBLIC +# Pong + +add_library(pong SHARED + pong.c) + +target_link_libraries(pong PUBLIC ${LINK_LIBRARIES}) diff --git a/game/src/plugins/pong.c b/game/src/plugins/pong.c new file mode 100644 index 0000000..3a0b825 --- /dev/null +++ b/game/src/plugins/pong.c @@ -0,0 +1,237 @@ +#include "plugin.h" + +#include +#include +#include + +#include +#include +#include + +#include + +static const vec2 PAD_SIZE = (vec2){120, 20}; +static const R PLAYER_Y_OFFSET = 50; +static const R PLAYER_SPEED = 800; + +static const R ENEMY_SPEED = 2; + +static const R BALL_SIZE = 18; +static const R BALL_SPEED = 360; // In each dimension. + +static const R EPS = (R)1e-3; + +typedef struct Player { + vec2 position; +} Player; + +typedef struct Ball { + vec2 position; + vec2 velocity; +} Ball; + +typedef struct State { + bool game_started; + Player human; + Player enemy; + Ball ball; + mat4 viewProjection; +} State; + +bool init(Game* game, State** pp_state) { + assert(game); + + State* state = calloc(1, sizeof(State)); + if (!state) { + return false; + } + + *pp_state = state; + return true; + +cleanup: + free(state); + return false; +} + +void shutdown(Game* game, State* state) { + assert(game); + assert(state); +} + +static void move_ball(Ball* ball, R dt, int width, int height) { + assert(ball); + + const R offset = BALL_SIZE / 2; + + ball->position = vec2_add(ball->position, vec2_scale(ball->velocity, dt)); + + // Right wall. + if (ball->position.x + offset > (R)width) { + ball->position.x = (R)width - offset - EPS; + ball->velocity.x = -ball->velocity.x; + } + // Left wall. + else if (ball->position.x - offset < 0) { + ball->position.x = offset + EPS; + ball->velocity.x = -ball->velocity.x; + } + // Top wall. + if (ball->position.y + offset > (R)height) { + ball->position.y = (R)height - offset - EPS; + ball->velocity.y = -ball->velocity.y; + } + // Bottom wall. + else if (ball->position.y - offset < 0) { + ball->position.y = offset + EPS; + ball->velocity.y = -ball->velocity.y; + } +} + +void move_enemy_player(int width, Player* player, R t) { + const R half_width = (R)width / 2; + const R amplitude = half_width - (PAD_SIZE.x / 2); + player->position.x = half_width + amplitude * sinf(t * ENEMY_SPEED); +} + +void move_human_player(Player* player, R dt) { + assert(player); + + R speed = 0; + if (gfx_is_key_pressed('a')) { + speed -= PLAYER_SPEED; + } + if (gfx_is_key_pressed('d')) { + speed += PLAYER_SPEED; + } + + player->position.x += speed * dt; +} + +void clamp_player(Player* player, int width) { + assert(player); + + const R offset = PAD_SIZE.x / 2; + + // Left wall. + if (player->position.x + offset > (R)width) { + player->position.x = (R)width - offset; + } + // Right wall. + else if (player->position.x - offset < 0) { + player->position.x = offset; + } +} + +void collide_ball(vec2 old_ball_position, const Player* player, Ball* ball) { + assert(player); + assert(ball); + + // Discrete but simple collision. Checks for intersection and moves the ball + // back by a small epsilon. + + // Player bounding box. + const vec2 player_pmin = vec2_make( + player->position.x - PAD_SIZE.x / 2, player->position.y - PAD_SIZE.y / 2); + const vec2 player_pmax = vec2_make( + player->position.x + PAD_SIZE.x / 2, player->position.y + PAD_SIZE.y / 2); + + // Ball bounding box. + const vec2 ball_pmin = vec2_make( + ball->position.x - BALL_SIZE / 2, ball->position.y - BALL_SIZE / 2); + const vec2 ball_pmax = vec2_make( + ball->position.x + BALL_SIZE / 2, ball->position.y + BALL_SIZE / 2); + + // Check for intersection and update ball. + if (!((ball_pmax.x < player_pmin.x) || (ball_pmin.x > player_pmax.x) || + (ball_pmax.y < player_pmin.y) || (ball_pmin.y > player_pmax.y))) { + ball->position = + vec2_add(old_ball_position, vec2_scale(ball->velocity, -EPS)); + ball->velocity.y = -ball->velocity.y; + } +} + +void update(Game* game, State* state, double t, double dt) { + assert(game); + assert(state); + + // TODO: Move game width/height to GfxApp query functions? + const vec2 old_ball_position = state->ball.position; + move_ball(&state->ball, (R)dt, game->width, game->height); + move_human_player(&state->human, (R)dt); + move_enemy_player(game->width, &state->enemy, (R)t); + clamp_player(&state->human, game->width); + collide_ball(old_ball_position, &state->human, &state->ball); + collide_ball(old_ball_position, &state->enemy, &state->ball); +} + +static void draw_player(ImmRenderer* imm, const Player* player) { + assert(imm); + assert(player); + + const vec2 half_box = vec2_div(PAD_SIZE, vec2_make(2, 2)); + + const vec2 pmin = vec2_sub(player->position, half_box); + const vec2 pmax = vec2_add(player->position, half_box); + const aabb2 box = aabb2_make(pmin, pmax); + + gfx_imm_draw_aabb2(imm, box); +} + +static void draw_ball(ImmRenderer* imm, const Ball* ball) { + assert(imm); + assert(ball); + + const vec2 half_box = vec2_make(BALL_SIZE / 2, BALL_SIZE / 2); + const vec2 pmin = vec2_sub(ball->position, half_box); + const vec2 pmax = vec2_add(ball->position, half_box); + const aabb2 box = aabb2_make(pmin, pmax); + + gfx_imm_draw_aabb2(imm, box); +} + +void render(const Game* game, const State* state) { + assert(game); + assert(state); + + ImmRenderer* imm = gfx_get_imm_renderer(game->gfx); + gfx_imm_start(imm); + gfx_imm_set_view_projection_matrix(imm, &state->viewProjection); + gfx_imm_load_identity(imm); + gfx_imm_set_colour(imm, vec4_make(1, 1, 1, 1)); + draw_player(imm, &state->human); + draw_player(imm, &state->enemy); + draw_ball(imm, &state->ball); + gfx_imm_end(imm); +} + +static R clamp_to_width(int width, R x, R extent) { + return min(x, (R)width - extent); +} + +void resize(Game* game, State* state, int width, int height) { + assert(game); + assert(state); + + state->viewProjection = mat4_ortho(0, (R)width, 0, (R)height, -1, 1); + + state->human.position.y = PLAYER_Y_OFFSET; + state->enemy.position.y = (R)height - PLAYER_Y_OFFSET; + + if (!state->game_started) { + state->human.position.x = (R)width / 2; + state->enemy.position.x = (R)width / 2; + + state->ball.position = + vec2_div(vec2_make((R)width, (R)height), vec2_make(2, 2)); + + state->ball.velocity = vec2_make(BALL_SPEED, BALL_SPEED); + + state->game_started = true; + } else { + state->human.position.x = + clamp_to_width(width, state->human.position.x, PAD_SIZE.x / 2); + state->enemy.position.x = + clamp_to_width(width, state->enemy.position.x, PAD_SIZE.x / 2); + } +} -- cgit v1.2.3