aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJordan Halase <jordan@halase.me>2019-09-27 13:10:17 -0500
committerJordan Halase <jordan@halase.me>2019-09-27 13:10:17 -0500
commitf754a990d27369e1c21f5d3314770ebd5a2bf1aa (patch)
tree65864db0a41c2d7e2f91e6bd89239d0ff48916d5
Initial commit
-rw-r--r--license.txt13
-rw-r--r--main.c347
-rw-r--r--readme.txt5
3 files changed, 365 insertions, 0 deletions
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 <jordan@halase.me>
+
+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 <jordan@halase.me>
+ *
+ * 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 <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <math.h>
+
+#include <SDL2/SDL.h>
+
+#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.