From f754a990d27369e1c21f5d3314770ebd5a2bf1aa Mon Sep 17 00:00:00 2001 From: Jordan Halase Date: Fri, 27 Sep 2019 13:10:17 -0500 Subject: Initial commit --- license.txt | 13 +++ main.c | 347 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ readme.txt | 5 + 3 files changed, 365 insertions(+) create mode 100644 license.txt create mode 100644 main.c create mode 100644 readme.txt diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..2b081bd --- /dev/null +++ b/license.txt @@ -0,0 +1,13 @@ +Copyright (C) 2019 Jordan Halase + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/main.c b/main.c new file mode 100644 index 0000000..cb3ba73 --- /dev/null +++ b/main.c @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2019 Jordan Halase + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include + +#define SCREEN_WIDTH 800 +#define SCREEN_HEIGHT 600 +#define RENDER_SCALE 16.0f +#define SCREEN_WIDTH_F (SCREEN_WIDTH/RENDER_SCALE) +#define SCREEN_HEIGHT_F (SCREEN_HEIGHT/RENDER_SCALE) +#define SCREEN_CENTER_X (SCREEN_WIDTH_F/2) +#define SCREEN_CENTER_Y (SCREEN_HEIGHT_F/2) +#define TWOPI 6.283185307f +#define PIOVER2 1.570796327f +#define INVSQRT2 0.707106781f + +void fatal_sdl() +{ + fprintf(stderr, "%s\n", SDL_GetError()); + SDL_ShowSimpleMessageBox( + SDL_MESSAGEBOX_ERROR, + "Fatal Error", + SDL_GetError(), + NULL + ); + exit(1); +} + +#define CHECK_SDL(f) do { if ((f) < 0) { fatal_sdl(); } } while (0) + +typedef struct vec2 { + float x, y; +} vec2; + +#define vec2(x, y) ((vec2){(x), (y)}) +#define vadd(a, b) vec2((a).x+(b).x, (a).y+(b).y) +#define vscale(v, s) vec2((v).x*(s), (v).y*(s)) +#define vcomplex(a, b) vec2((a).x*(b).x-(a).y*(b).y, (a).x*(b).y+(a).y*(b).x) +#define vdot(a, b) ((a).x*(b).x + (a).y*(b).y) +#define vdotself(v) vdot(v, v) +#define viszero(v) ((v).x == 0.0f && (v).y == 0.0f) + +enum CollisionType { + COLLISION_TYPE_NONE = 0x0, + COLLISION_TYPE_PLAYER = 0x1, + COLLISION_TYPE_ASTEROID = 0x2, + COLLISION_TYPE_UFO = 0x4, + COLLISION_TYPE_PLAYER_BULLET = 0x8, + COLLISION_TYPE_UFO_BULLET = 0x10 +}; + +struct Bullet { + vec2 position; + vec2 velocity; // Zero velocity bullets are vacant +}; + +#define MAX_BULLETS 8 + +/* Zero is initialization */ +struct BulletPool { + uint32_t used; // Number of bullets currently in use + uint32_t index; // Index of possibly vacant bullet + struct Bullet bullets[MAX_BULLETS]; +}; + +struct BulletPool player_bullets; + +bool bullets_shoot(struct BulletPool *pool, const vec2 position, const vec2 velocity) +{ + if (pool->used >= MAX_BULLETS) { + return false; + } + uint32_t i; + for (i = pool->index; i < MAX_BULLETS; ++i) { + if (viszero(pool->bullets[i].velocity)) { + goto found; + } + } + for (i = 0; i < pool->index; ++i) { + if (viszero(pool->bullets[i].velocity)) { + goto found; + } + } + return false; +found: + pool->bullets[i].position = position; + pool->bullets[i].velocity = velocity; + pool->index = (pool->index + 1) % MAX_BULLETS; + ++pool->used; + return true; +} + +static bool is_point_in_screen(const vec2 position) +{ + if (position.x > SCREEN_WIDTH_F || position.y > SCREEN_HEIGHT_F || + position.x < 0.0f || position.y < 0.0f) { + return false; + } + return true; +} + +void bullets_integrate(struct BulletPool *pool) +{ + for (uint32_t i = 0; i < MAX_BULLETS; ++i) { + if (!viszero(pool->bullets[i].velocity)) { + pool->bullets[i].position = vadd(pool->bullets[i].position, pool->bullets[i].velocity); + if (!is_point_in_screen(pool->bullets[i].position)) { + pool->bullets[i].velocity = vec2(0.0f, 0.0f); + --pool->used; + pool->index = i; + } + } + } +} + +void bullets_draw(const struct BulletPool *pool, SDL_Renderer *ren) +{ + uint32_t num_bullets = 0; + SDL_Point points[MAX_BULLETS]; + for (uint32_t i = 0; i < MAX_BULLETS; ++i) { + if (!viszero(pool->bullets[i].velocity)) { + const vec2 v = vscale(pool->bullets[i].position, RENDER_SCALE); + points[num_bullets].x = lroundf(v.x); + points[num_bullets].y = lroundf(v.y); + ++num_bullets; + } + } + SDL_RenderDrawPoints(ren, points, num_bullets); +} + +struct KinematicBody { + vec2 position; + vec2 velocity; + vec2 acceleration; + float angle; + float angular_velocity; + float k_max_speed; + float collision_radius; + uint16_t collision_listen_mask; + uint16_t collision_create_mask; +}; + +struct Player { + struct KinematicBody body; + vec2 direction; + float k_acceleration; + float k_angular_speed; + bool can_shoot; + bool is_thrusting; + bool show_thrust; + uint8_t thrust_counter; +}; + +#define PLAYER_RENDER_RADIUS INVSQRT2 + +void player_create(struct Player *player, const vec2 position, const float update_rate) +{ + *player = (struct Player){ + .body = { + .position = position, + .angle = -PIOVER2, + .collision_radius = PLAYER_RENDER_RADIUS, + .k_max_speed = 0.375f + }, + .direction = vec2(0.0f, -1.0f), + .k_acceleration = 0.375f/update_rate, + .k_angular_speed = TWOPI/update_rate, // 1 revolution per second + .can_shoot = true + }; +} + +void player_input(struct Player *player, const Uint8 *keys) +{ + struct KinematicBody *body = &player->body; + bool update_direction = false; + if (keys[SDL_SCANCODE_LEFT] && !keys[SDL_SCANCODE_RIGHT]) { + body->angular_velocity = -player->k_angular_speed; + update_direction = true; + } else if (keys[SDL_SCANCODE_RIGHT] && !keys[SDL_SCANCODE_LEFT]) { + body->angular_velocity = player->k_angular_speed; + update_direction = true; + } else { + body->angular_velocity = 0.0f; + } + if (keys[SDL_SCANCODE_UP]) { + body->acceleration = vscale(player->direction, player->k_acceleration); + player->is_thrusting = true; + } else { + body->acceleration = vec2(0.0f, 0.0f); + player->is_thrusting = false; + } + if (keys[SDL_SCANCODE_Z]) { + if (player->can_shoot) { + // TODO + const vec2 nose = vec2(INVSQRT2, 0.0f); // Fire out the nose of the ship + const vec2 pos = vadd(vcomplex(nose, player->direction), body->position); + if (bullets_shoot(&player_bullets, pos, vadd(vscale(player->direction, 0.375f), body->velocity))) { + // Successful fire + } + player->can_shoot = false; + } + } else { + player->can_shoot = true; + } + if (update_direction) { + player->direction.x = cosf(body->angle); + player->direction.y = sinf(body->angle); + } +} + +void kinematic_integrate(struct KinematicBody *body) +{ + body->angle += body->angular_velocity; + while (body->angle < 0.0f) body->angle += TWOPI; + while (body->angle >= TWOPI) body->angle -= TWOPI; + + body->velocity = vadd(body->velocity, body->acceleration); + if (body->velocity.x > body->k_max_speed) { + body->velocity.x = body->k_max_speed; + } else if (-body->velocity.x > body->k_max_speed) { + body->velocity.x = -body->k_max_speed; + } + if (body->velocity.y > body->k_max_speed) { + body->velocity.y = body->k_max_speed; + } else if (-body->velocity.y > body->k_max_speed) { + body->velocity.y = -body->k_max_speed; + } + body->position = vadd(body->position, body->velocity); +} + +void kinematic_wrap(struct KinematicBody *body) +{ + while (body->position.x > SCREEN_WIDTH_F + body->collision_radius) { + body->position.x -= SCREEN_WIDTH_F; + } + while (body->position.x < -body->collision_radius) { + body->position.x += SCREEN_WIDTH_F; + } + while (body->position.y > SCREEN_HEIGHT_F + body->collision_radius) { + body->position.y -= SCREEN_HEIGHT_F; + } + while (body->position.y < -body->collision_radius) { + body->position.y += SCREEN_HEIGHT_F; + } +} + +/* Exact center of mass to set the origin of the player 1 - 1/sqrt(2) */ +#define PLAYER_ORIGIN 0.292893219f + +static const vec2 player_lines[] = { + vec2(1.0f - PLAYER_ORIGIN, 0.0f), vec2(-0.25f - PLAYER_ORIGIN, -0.5f), + vec2(1.0f - PLAYER_ORIGIN, 0.0f), vec2(-0.25f - PLAYER_ORIGIN, 0.5f), + vec2(0.0f - PLAYER_ORIGIN, -0.4f), vec2( 0.0f - PLAYER_ORIGIN, 0.4f), + vec2(0.0f - PLAYER_ORIGIN, -0.2f), vec2(-0.75f, 0.0f), + vec2(0.0f - PLAYER_ORIGIN, 0.2f), vec2(-0.75f, 0.0f) +}; + +static void player_draw(struct Player *player, SDL_Renderer *const ren) +{ + struct KinematicBody *body = &player->body; + const int num_points = player->is_thrusting && player->show_thrust ? 10 : 6; + for (int i = 0; i < num_points; i += 2) { + const vec2 v1 = vscale(vadd(vcomplex(player_lines[i], player->direction), body->position), RENDER_SCALE); + const vec2 v2 = vscale(vadd(vcomplex(player_lines[i+1], player->direction), body->position), RENDER_SCALE); + SDL_RenderDrawLine( + ren, + lroundf(v1.x), + lroundf(v1.y), + lroundf(v2.x), + lroundf(v2.y) + ); + } + if (++player->thrust_counter > 1) { + player->thrust_counter = 0; + player->show_thrust = !player->show_thrust; + } +} + +int main(int argc, char **argv) +{ + (void)argc; + (void)argv; + CHECK_SDL(SDL_Init(SDL_INIT_EVERYTHING)); + SDL_Window *win = SDL_CreateWindow( + "Asteroids Clone", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + SCREEN_WIDTH, + SCREEN_HEIGHT, + 0 + ); + if (!win) fatal_sdl(); + SDL_Renderer *ren = SDL_CreateRenderer( + win, + -1, + SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC + ); + if (!ren) fatal_sdl(); + + struct Player player; + player_create(&player, vec2(SCREEN_CENTER_X, SCREEN_CENTER_Y), 60.0f); + + SDL_Event e; + for (;;) { + while(SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) goto quit; + if (e.type == SDL_KEYDOWN && + e.key.keysym.sym == SDLK_ESCAPE) { + goto quit; + } + } + const Uint8 *keys = SDL_GetKeyboardState(NULL); + player_input(&player, keys); + kinematic_integrate(&player.body); + bullets_integrate(&player_bullets); + kinematic_wrap(&player.body); + SDL_SetRenderDrawColor(ren, 0, 0, 0, 255); + SDL_RenderClear(ren); + SDL_SetRenderDrawColor(ren, 255, 255, 255, 255); + player_draw(&player, ren); + bullets_draw(&player_bullets, ren); + SDL_RenderPresent(ren); + } +quit: + SDL_DestroyRenderer(ren); + SDL_DestroyWindow(win); + SDL_Quit(); + return 0; +} diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..4efc9e5 --- /dev/null +++ b/readme.txt @@ -0,0 +1,5 @@ +Simple Asteroids clone in C as an exercise in game development. + +Requires SDL2 for input and drawing. Details for installation at libsdl.org. + +Compiles with gcc or clang. Can compile on Windows with clang or mingw. -- cgit v1.2.1