diff options
| author | 3gg <3gg@shellblade.net> | 2024-02-08 19:53:09 -0800 | 
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2024-02-08 19:53:09 -0800 | 
| commit | c76697d51158ed5e52bd697c71fbdfed1ec25a3f (patch) | |
| tree | f7cded192f7ca4233b8734ecbb58051498d3e28d /game | |
| parent | b116ac146cd1c802b4c87c980971dc20cb65a881 (diff) | |
Add Pong demo.
Diffstat (limited to 'game')
| -rw-r--r-- | game/src/plugins/CMakeLists.txt | 18 | ||||
| -rw-r--r-- | game/src/plugins/pong.c | 237 | 
2 files changed, 252 insertions, 3 deletions
| 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) | |||
| 2 | 2 | ||
| 3 | project(plugins) | 3 | project(plugins) | 
| 4 | 4 | ||
| 5 | set(LINK_LIBRARIES cstring math gfx) | 5 | set(LINK_LIBRARIES cstring math gfx gfx-app) | 
| 6 | |||
| 7 | # GLTF viewer | ||
| 6 | 8 | ||
| 7 | add_library(gltf_view SHARED | 9 | add_library(gltf_view SHARED | 
| 8 | gltf_view.c) | 10 | gltf_view.c) | 
| 9 | 11 | ||
| 12 | target_link_libraries(gltf_view PUBLIC | ||
| 13 | ${LINK_LIBRARIES}) | ||
| 14 | |||
| 15 | # Texture viewer | ||
| 16 | |||
| 10 | add_library(texture_view SHARED | 17 | add_library(texture_view SHARED | 
| 11 | texture_view.c) | 18 | texture_view.c) | 
| 12 | 19 | ||
| 13 | target_link_libraries(gltf_view PUBLIC | 20 | target_link_libraries(texture_view PUBLIC | 
| 14 | ${LINK_LIBRARIES}) | 21 | ${LINK_LIBRARIES}) | 
| 15 | 22 | ||
| 16 | target_link_libraries(texture_view PUBLIC | 23 | # Pong | 
| 24 | |||
| 25 | add_library(pong SHARED | ||
| 26 | pong.c) | ||
| 27 | |||
| 28 | target_link_libraries(pong PUBLIC | ||
| 17 | ${LINK_LIBRARIES}) | 29 | ${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 @@ | |||
| 1 | #include "plugin.h" | ||
| 2 | |||
| 3 | #include <gfx/gfx.h> | ||
| 4 | #include <gfx/gfx_app.h> | ||
| 5 | #include <gfx/renderer.h> | ||
| 6 | |||
| 7 | #include <math/mat4.h> | ||
| 8 | #include <math/vec2.h> | ||
| 9 | #include <math/vec4.h> | ||
| 10 | |||
| 11 | #include <stdlib.h> | ||
| 12 | |||
| 13 | static const vec2 PAD_SIZE = (vec2){120, 20}; | ||
| 14 | static const R PLAYER_Y_OFFSET = 50; | ||
| 15 | static const R PLAYER_SPEED = 800; | ||
| 16 | |||
| 17 | static const R ENEMY_SPEED = 2; | ||
| 18 | |||
| 19 | static const R BALL_SIZE = 18; | ||
| 20 | static const R BALL_SPEED = 360; // In each dimension. | ||
| 21 | |||
| 22 | static const R EPS = (R)1e-3; | ||
| 23 | |||
| 24 | typedef struct Player { | ||
| 25 | vec2 position; | ||
| 26 | } Player; | ||
| 27 | |||
| 28 | typedef struct Ball { | ||
| 29 | vec2 position; | ||
| 30 | vec2 velocity; | ||
| 31 | } Ball; | ||
| 32 | |||
| 33 | typedef struct State { | ||
| 34 | bool game_started; | ||
| 35 | Player human; | ||
| 36 | Player enemy; | ||
| 37 | Ball ball; | ||
| 38 | mat4 viewProjection; | ||
| 39 | } State; | ||
| 40 | |||
| 41 | bool init(Game* game, State** pp_state) { | ||
| 42 | assert(game); | ||
| 43 | |||
| 44 | State* state = calloc(1, sizeof(State)); | ||
| 45 | if (!state) { | ||
| 46 | return false; | ||
| 47 | } | ||
| 48 | |||
| 49 | *pp_state = state; | ||
| 50 | return true; | ||
| 51 | |||
| 52 | cleanup: | ||
| 53 | free(state); | ||
| 54 | return false; | ||
| 55 | } | ||
| 56 | |||
| 57 | void shutdown(Game* game, State* state) { | ||
| 58 | assert(game); | ||
| 59 | assert(state); | ||
| 60 | } | ||
| 61 | |||
| 62 | static void move_ball(Ball* ball, R dt, int width, int height) { | ||
| 63 | assert(ball); | ||
| 64 | |||
| 65 | const R offset = BALL_SIZE / 2; | ||
| 66 | |||
| 67 | ball->position = vec2_add(ball->position, vec2_scale(ball->velocity, dt)); | ||
| 68 | |||
| 69 | // Right wall. | ||
| 70 | if (ball->position.x + offset > (R)width) { | ||
| 71 | ball->position.x = (R)width - offset - EPS; | ||
| 72 | ball->velocity.x = -ball->velocity.x; | ||
| 73 | } | ||
| 74 | // Left wall. | ||
| 75 | else if (ball->position.x - offset < 0) { | ||
| 76 | ball->position.x = offset + EPS; | ||
| 77 | ball->velocity.x = -ball->velocity.x; | ||
| 78 | } | ||
| 79 | // Top wall. | ||
| 80 | if (ball->position.y + offset > (R)height) { | ||
| 81 | ball->position.y = (R)height - offset - EPS; | ||
| 82 | ball->velocity.y = -ball->velocity.y; | ||
| 83 | } | ||
| 84 | // Bottom wall. | ||
| 85 | else if (ball->position.y - offset < 0) { | ||
| 86 | ball->position.y = offset + EPS; | ||
| 87 | ball->velocity.y = -ball->velocity.y; | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | void move_enemy_player(int width, Player* player, R t) { | ||
| 92 | const R half_width = (R)width / 2; | ||
| 93 | const R amplitude = half_width - (PAD_SIZE.x / 2); | ||
| 94 | player->position.x = half_width + amplitude * sinf(t * ENEMY_SPEED); | ||
| 95 | } | ||
| 96 | |||
| 97 | void move_human_player(Player* player, R dt) { | ||
| 98 | assert(player); | ||
| 99 | |||
| 100 | R speed = 0; | ||
| 101 | if (gfx_is_key_pressed('a')) { | ||
| 102 | speed -= PLAYER_SPEED; | ||
| 103 | } | ||
| 104 | if (gfx_is_key_pressed('d')) { | ||
| 105 | speed += PLAYER_SPEED; | ||
| 106 | } | ||
| 107 | |||
| 108 | player->position.x += speed * dt; | ||
| 109 | } | ||
| 110 | |||
| 111 | void clamp_player(Player* player, int width) { | ||
| 112 | assert(player); | ||
| 113 | |||
| 114 | const R offset = PAD_SIZE.x / 2; | ||
| 115 | |||
| 116 | // Left wall. | ||
| 117 | if (player->position.x + offset > (R)width) { | ||
| 118 | player->position.x = (R)width - offset; | ||
| 119 | } | ||
| 120 | // Right wall. | ||
| 121 | else if (player->position.x - offset < 0) { | ||
| 122 | player->position.x = offset; | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | void collide_ball(vec2 old_ball_position, const Player* player, Ball* ball) { | ||
| 127 | assert(player); | ||
| 128 | assert(ball); | ||
| 129 | |||
| 130 | // Discrete but simple collision. Checks for intersection and moves the ball | ||
| 131 | // back by a small epsilon. | ||
| 132 | |||
| 133 | // Player bounding box. | ||
| 134 | const vec2 player_pmin = vec2_make( | ||
| 135 | player->position.x - PAD_SIZE.x / 2, player->position.y - PAD_SIZE.y / 2); | ||
| 136 | const vec2 player_pmax = vec2_make( | ||
| 137 | player->position.x + PAD_SIZE.x / 2, player->position.y + PAD_SIZE.y / 2); | ||
| 138 | |||
| 139 | // Ball bounding box. | ||
| 140 | const vec2 ball_pmin = vec2_make( | ||
| 141 | ball->position.x - BALL_SIZE / 2, ball->position.y - BALL_SIZE / 2); | ||
| 142 | const vec2 ball_pmax = vec2_make( | ||
| 143 | ball->position.x + BALL_SIZE / 2, ball->position.y + BALL_SIZE / 2); | ||
| 144 | |||
| 145 | // Check for intersection and update ball. | ||
| 146 | if (!((ball_pmax.x < player_pmin.x) || (ball_pmin.x > player_pmax.x) || | ||
| 147 | (ball_pmax.y < player_pmin.y) || (ball_pmin.y > player_pmax.y))) { | ||
| 148 | ball->position = | ||
| 149 | vec2_add(old_ball_position, vec2_scale(ball->velocity, -EPS)); | ||
| 150 | ball->velocity.y = -ball->velocity.y; | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | void update(Game* game, State* state, double t, double dt) { | ||
| 155 | assert(game); | ||
| 156 | assert(state); | ||
| 157 | |||
| 158 | // TODO: Move game width/height to GfxApp query functions? | ||
| 159 | const vec2 old_ball_position = state->ball.position; | ||
| 160 | move_ball(&state->ball, (R)dt, game->width, game->height); | ||
| 161 | move_human_player(&state->human, (R)dt); | ||
| 162 | move_enemy_player(game->width, &state->enemy, (R)t); | ||
| 163 | clamp_player(&state->human, game->width); | ||
| 164 | collide_ball(old_ball_position, &state->human, &state->ball); | ||
| 165 | collide_ball(old_ball_position, &state->enemy, &state->ball); | ||
| 166 | } | ||
| 167 | |||
| 168 | static void draw_player(ImmRenderer* imm, const Player* player) { | ||
| 169 | assert(imm); | ||
| 170 | assert(player); | ||
| 171 | |||
| 172 | const vec2 half_box = vec2_div(PAD_SIZE, vec2_make(2, 2)); | ||
| 173 | |||
| 174 | const vec2 pmin = vec2_sub(player->position, half_box); | ||
| 175 | const vec2 pmax = vec2_add(player->position, half_box); | ||
| 176 | const aabb2 box = aabb2_make(pmin, pmax); | ||
| 177 | |||
| 178 | gfx_imm_draw_aabb2(imm, box); | ||
| 179 | } | ||
| 180 | |||
| 181 | static void draw_ball(ImmRenderer* imm, const Ball* ball) { | ||
| 182 | assert(imm); | ||
| 183 | assert(ball); | ||
| 184 | |||
| 185 | const vec2 half_box = vec2_make(BALL_SIZE / 2, BALL_SIZE / 2); | ||
| 186 | const vec2 pmin = vec2_sub(ball->position, half_box); | ||
| 187 | const vec2 pmax = vec2_add(ball->position, half_box); | ||
| 188 | const aabb2 box = aabb2_make(pmin, pmax); | ||
| 189 | |||
| 190 | gfx_imm_draw_aabb2(imm, box); | ||
| 191 | } | ||
| 192 | |||
| 193 | void render(const Game* game, const State* state) { | ||
| 194 | assert(game); | ||
| 195 | assert(state); | ||
| 196 | |||
| 197 | ImmRenderer* imm = gfx_get_imm_renderer(game->gfx); | ||
| 198 | gfx_imm_start(imm); | ||
| 199 | gfx_imm_set_view_projection_matrix(imm, &state->viewProjection); | ||
| 200 | gfx_imm_load_identity(imm); | ||
| 201 | gfx_imm_set_colour(imm, vec4_make(1, 1, 1, 1)); | ||
| 202 | draw_player(imm, &state->human); | ||
| 203 | draw_player(imm, &state->enemy); | ||
| 204 | draw_ball(imm, &state->ball); | ||
| 205 | gfx_imm_end(imm); | ||
| 206 | } | ||
| 207 | |||
| 208 | static R clamp_to_width(int width, R x, R extent) { | ||
| 209 | return min(x, (R)width - extent); | ||
| 210 | } | ||
| 211 | |||
| 212 | void resize(Game* game, State* state, int width, int height) { | ||
| 213 | assert(game); | ||
| 214 | assert(state); | ||
| 215 | |||
| 216 | state->viewProjection = mat4_ortho(0, (R)width, 0, (R)height, -1, 1); | ||
| 217 | |||
| 218 | state->human.position.y = PLAYER_Y_OFFSET; | ||
| 219 | state->enemy.position.y = (R)height - PLAYER_Y_OFFSET; | ||
| 220 | |||
| 221 | if (!state->game_started) { | ||
| 222 | state->human.position.x = (R)width / 2; | ||
| 223 | state->enemy.position.x = (R)width / 2; | ||
| 224 | |||
| 225 | state->ball.position = | ||
| 226 | vec2_div(vec2_make((R)width, (R)height), vec2_make(2, 2)); | ||
| 227 | |||
| 228 | state->ball.velocity = vec2_make(BALL_SPEED, BALL_SPEED); | ||
| 229 | |||
| 230 | state->game_started = true; | ||
| 231 | } else { | ||
| 232 | state->human.position.x = | ||
| 233 | clamp_to_width(width, state->human.position.x, PAD_SIZE.x / 2); | ||
| 234 | state->enemy.position.x = | ||
| 235 | clamp_to_width(width, state->enemy.position.x, PAD_SIZE.x / 2); | ||
| 236 | } | ||
| 237 | } | ||
