Skip to content

Instantly share code, notes, and snippets.

@Falconerd
Last active February 14, 2024 10:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Falconerd/358609b9785dec686aa81fb66123e3a3 to your computer and use it in GitHub Desktop.
Save Falconerd/358609b9785dec686aa81fb66123e3a3 to your computer and use it in GitHub Desktop.
Some game code stuff
#include "common.h"
uintptr_t align_forward(uintptr_t ptr, size_t alignment) {
uintptr_t p, a, modulo;
if (!is_power_of_two(alignment)) {
return 0;
}
p = ptr;
a = (uintptr_t)alignment;
modulo = p & (a - 1);
if (modulo) {
p += a - modulo;
}
return p;
}
void *arena_alloc_aligned(Arena *a, size_t size, size_t alignment) {
uintptr_t curr_ptr = (uintptr_t)a->base + (uintptr_t)a->offset;
uintptr_t offset = align_forward(curr_ptr, alignment);
offset -= (uintptr_t)a->base;
if (offset + size > a->size) {
return 0;
}
a->committed += size;
void *ptr = (uint8_t *)a->base + offset;
a->offset = offset + size;
return ptr;
}
void *arena_alloc(size_t size, void *context) {
if (!size) {
return 0;
}
return arena_alloc_aligned((Arena *)context, size, DEFAULT_ALIGNMENT);
}
// Does nothing.
void arena_free(size_t size, void *ptr, void *context) {
(void)ptr; (void)size; (void)context;
}
void arena_free_all(void *context) {
Arena *a = context;
a->offset = 0;
a->committed = 0;
}
Arena arena_init(void *buffer, size_t size) {
return (Arena){.base = buffer, .size = size};
}
#pragma once
#define DEFAULT_ALIGNMENT (2 * sizeof(void *))
typedef struct {
void *(*alloc)(size_t size, void *context);
void (*free)(size_t size, void *ptr, void *context);
void *context;
} Allocator;
typedef struct {
void *base;
size_t size;
size_t offset;
size_t committed;
} Arena;
#define arena_alloc_init(a) (Allocator){arena_alloc, arena_free, a}
#define is_power_of_two(x) ((x != 0) && ((x & (x - 1)) == 0))
#define KB(x) (x * 1024ULL)
#define MB(x) (KB(x) * 1024ULL)
#define GB(x) (MB(x) * 1024ULL)
#define make(T, n, a) ((T *)((a).alloc(sizeof(T) * n, (a).context)))
#define release(s, p, a) ((a).free(s, p, (a).context))
typedef struct {
size_t length;
size_t capacity;
} FixedArrayHeader;
#define fixed_array_header(a) ((FixedArrayHeader *)(a) - 1)
#define fixed_array_length(a) (fixed_array_header(a)->length)
#define fixed_array_capacity(a) (fixed_array_header(a)->capacity)
#define fixed_array_append(a, v) ( \
(fixed_array_capacity(a) > fixed_array_length(a) ? ( \
((a)[fixed_array_length(a)] = (v), \
&(a)[fixed_array_header(a)->length++]) \
) : 0) \
)
#include <stdio.h>
#include <stdint.h>
#include <math.h>
#include "SDL.h"
#include "common.h"
#include "arena.c"
#define assert(x) do { if (!(x)) __debugbreak(); } while (0)
#define flag_is_set(bf, mask) ((bf & mask) != 0)
typedef uint8_t u8;
typedef int8_t b8;
typedef int32_t b32;
typedef int32_t s32;
typedef uint32_t u32;
typedef uint32_t m32;
typedef float f32;
typedef struct {
b8 pressed;
b8 pressed_last_frame;
} KeyState;
typedef struct {
s32 x, y;
s32 width, height;
} Camera;
typedef struct {
f32 x, y;
} V2;
#define V2(x, y) (V2){x, y}
typedef struct {
u8 r, g, b, a;
} RGBA8;
#define RGBA8(r, g, b, a) (RGBA8){r, g, b, a}
typedef struct {
f32 x, y, w, h;
} Rect;
#define Rect(x, y, w, h) (Rect){x, y, w, h}
typedef enum {
ENTITY_BEHAVIOR_AUTOWALK = 1 << 0,
ENTITY_BEHAVIOR_FLIP_AT_EDGE = 1 << 1,
ENTITY_BEHAVIOR_FLIP_AT_WALL = 1 << 2,
} EntityBehavior;
#define ENTITY_BEHAVIOR_GROUP_PATROL ( \
ENTITY_BEHAVIOR_AUTOWALK | ENTITY_BEHAVIOR_FLIP_AT_EDGE | ENTITY_BEHAVIOR_FLIP_AT_WALL \
)
typedef struct {
m32 behaviors;
Rect collider;
V2 velocity;
f32 move_speed;
b8 is_active;
b8 is_grounded;
b8 is_facing_left;
} Entity;
typedef struct {
SDL_Window *window;
SDL_Renderer *renderer;
f32 player_move_speed;
f32 player_jump_force;
f32 player_coyote_time;
f32 player_coyote_timer;
s32 window_width;
s32 window_height;
s32 render_width;
s32 render_height;
f32 gravity;
f32 terminal_velocity;
u32 last_time;
u32 current_time;
f32 delta_time;
KeyState left;
KeyState right;
KeyState jump;
KeyState pan_left;
KeyState pan_right;
KeyState pan_up;
KeyState pan_down;
Camera camera;
size_t solid_geometry_len;
size_t solid_geometry_cap;
Rect solid_geometry[64];
size_t entities_len;
size_t entities_cap;
Entity entities[64];
size_t normals_len;
size_t normals_cap;
V2 normals[16];
size_t hit_colliders_len;
size_t hit_colliders_cap;
Rect hit_colliders[16];
} GameState;
Allocator perm_allocator;
Allocator temp_allocator;
Arena perm_arena;
Arena temp_arena;
GameState *gs;
void update_keydown(KeyState *ks) {
ks->pressed_last_frame = ks->pressed;
ks->pressed = 1;
}
void update_keyup(KeyState *ks) {
ks->pressed_last_frame = ks->pressed;
ks->pressed = 0;
}
void render_fill_rect(SDL_Renderer *r, s32 x, s32 y, s32 w, s32 h, RGBA8 c) {
SDL_Rect rect = {x - gs->camera.x, y - gs->camera.y, w, h};
SDL_SetRenderDrawColor(r, c.r, c.g, c.b, c.a);
SDL_RenderFillRect(r, &rect);
}
void render_draw_rect(SDL_Renderer *r, s32 x, s32 y, s32 w, s32 h, RGBA8 c) {
SDL_Rect rect = {x - gs->camera.x, y - gs->camera.y, w, h};
SDL_SetRenderDrawColor(r, c.r, c.g, c.b, c.a);
SDL_RenderDrawRect(r, &rect);
}
void render_draw_line(SDL_Renderer *r, s32 x1, s32 y1, s32 x2, s32 y2, RGBA8 c) {
SDL_SetRenderDrawColor(r, c.r, c.g, c.b, c.a);
SDL_RenderDrawLine(r, x1 - gs->camera.x, y1 - gs->camera.y, x2 - gs->camera.x, y2 - gs->camera.y);
}
void render_draw_point(SDL_Renderer *r, s32 x, s32 y, RGBA8 c) {
SDL_SetRenderDrawColor(r, c.r, c.g, c.b, c.a);
SDL_RenderDrawPoint(r, x - gs->camera.x, y - gs->camera.y);
}
Rect rect_intersect(Rect a, Rect b) {
Rect intersection = {0};
f32 x1 = a.x > b.x ? a.x : b.x;
f32 y1 = a.y > b.y ? a.y : b.y;
f32 x2 = (a.x + a.w) < (b.x + b.w) ? (a.x + a.w) : (b.x + b.w);
f32 y2 = (a.y + a.h) < (b.y + b.h) ? (a.y + a.h) : (b.y + b.h);
if (x1 < x2 && y1 < y2) {
intersection.x = x1;
intersection.y = y1;
intersection.w = x2 - x1;
intersection.h = y2 - y1;
}
return intersection;
}
V2 calculate_collision_normal(Rect a, Rect b) {
V2 normal = {0, 0};
f32 ax = a.x + (a.w / 2.0f);
f32 ay = a.y + (a.h / 2.0f);
f32 bx = b.x + (b.w / 2.0f);
f32 by = b.y + (b.h / 2.0f);
f32 dx = ax - bx;
f32 dy = ay - by;
f32 px = (a.w / 2.0f + b.w / 2.0f) - fabsf(dx);
f32 py = (a.h / 2.0f + b.h / 2.0f) - fabsf(dy);
if (px < py) {
normal.x = dx < 0 ? -1.0f : 1.0f;
} else {
normal.y = dy < 0 ? -1.0f : 1.0f;
}
return normal;
}
int main(int argc, char *argv[]) {
// Init memory.
{
size_t perm_size = MB(64);
size_t temp_size = MB(64);
void *perm_buffer = malloc(perm_size);
void *temp_buffer = malloc(temp_size);
perm_arena = arena_init(perm_buffer, perm_size);
temp_arena = arena_init(temp_buffer, temp_size);
perm_allocator = arena_alloc_init(&perm_arena);
temp_allocator = arena_alloc_init(&temp_arena);
}
// TODO: Get these from config file.
gs = make(GameState, 1, perm_allocator);
gs->window_width = 1920;
gs->window_height = 1080;
gs->render_width = 1920;
gs->render_height = 1080;
gs->gravity = 10000.f;
gs->player_move_speed = 1000.f;
gs->player_coyote_time = 0.075f;
gs->terminal_velocity = 3500.f;
gs->player_jump_force = 3000.f;
gs->solid_geometry_cap = 64;
gs->entities_cap = 64;
gs->normals_cap = 16;
gs->hit_colliders_cap = 16;
fixed_array_append(gs->solid_geometry, Rect(0, gs->window_height - 10, gs->window_width, 10));
fixed_array_append(gs->solid_geometry, Rect(0, gs->window_height - 100, 100, 100));
fixed_array_append(gs->solid_geometry, Rect(200, gs->window_height - 100, 100, 100));
fixed_array_append(gs->solid_geometry, Rect(500, gs->window_height - 300, 50, 300));
fixed_array_append(gs->solid_geometry, Rect(900, gs->window_height - 100, 100, 100));
fixed_array_append(gs->solid_geometry, Rect(800, gs->window_height - 700, 300, 50));
// Player is always Entity 0.
Entity *player = fixed_array_append(gs->entities, (Entity){0});
player->is_active = 1;
player->collider.x = (f32)gs->window_width / 2.f - 50.f;
player->collider.w = 70.f;
player->collider.h = 135.f;
// Spawn an enemy with the patrol behavior.
{
Entity e = {
.is_active = 1,
.collider = {
.x = (f32)gs->window_width / 2.f - 50.f,
.w = 70.f,
.h = 70.f
},
.move_speed = 500.f,
.behaviors = ENTITY_BEHAVIOR_GROUP_PATROL
};
fixed_array_append(gs->entities, e);
e.collider.x = 500.f;
e.collider.h = 140.f;
e.move_speed = 300.f;
fixed_array_append(gs->entities, e);
}
gs->camera.width = gs->render_width;
gs->camera.height = gs->render_height;
// Init rendering.
{
assert(!SDL_Init(SDL_INIT_VIDEO));
gs->window = SDL_CreateWindow(
"Magepunk",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
gs->render_width,
gs->render_height,
SDL_WINDOW_SHOWN
);
assert(gs->window);
gs->renderer = SDL_CreateRenderer(gs->window, -1, SDL_RENDERER_ACCELERATED);
}
gs->last_time = SDL_GetTicks();
b32 running = 1;
while (running) {
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT) {
running = 0;
} else if (e.type == SDL_KEYDOWN) {
switch (e.key.keysym.sym) {
case SDLK_ESCAPE:
running = 0;
break;
case SDLK_r: update_keydown(&gs->left); break;
case SDLK_t: update_keydown(&gs->right); break;
case SDLK_SPACE: update_keydown(&gs->jump); break;
case SDLK_n: update_keydown(&gs->pan_left); break;
case SDLK_i: update_keydown(&gs->pan_right); break;
case SDLK_u: update_keydown(&gs->pan_up); break;
case SDLK_e: update_keydown(&gs->pan_down); break;
}
} else if (e.type == SDL_KEYUP) {
switch (e.key.keysym.sym) {
case SDLK_r: update_keyup(&gs->left); break;
case SDLK_t: update_keyup(&gs->right); break;
case SDLK_SPACE: update_keyup(&gs->jump); break;
case SDLK_n: update_keyup(&gs->pan_left); break;
case SDLK_i: update_keyup(&gs->pan_right); break;
case SDLK_u: update_keyup(&gs->pan_up); break;
case SDLK_e: update_keyup(&gs->pan_down); break;
}
}
}
// Update time.
{
gs->current_time = SDL_GetTicks();
gs->delta_time = (gs->current_time - gs->last_time) / 1000.f;
gs->last_time = gs->current_time;
}
// Handle player input.
{
if (gs->left.pressed) {
player->velocity.x = -gs->player_move_speed;
}
if (gs->right.pressed) {
player->velocity.x = gs->player_move_speed;
}
if (!gs->left.pressed && !gs->right.pressed) {
player->velocity.x = 0;
}
if (gs->jump.pressed && !gs->jump.pressed_last_frame && player->is_grounded) {
player->velocity.y = -gs->player_jump_force;
player->is_grounded = 0;
}
}
// Update camera.
{
if (gs->pan_left.pressed) {
gs->camera.x -= 1000.f * gs->delta_time;
}
if (gs->pan_right.pressed) {
gs->camera.x += 1000.f * gs->delta_time;
}
if (gs->pan_up.pressed) {
gs->camera.y -= 1000.f * gs->delta_time;
}
if (gs->pan_down.pressed) {
gs->camera.y += 1000.f * gs->delta_time;
}
}
// Update entities.
for (int i = 0; i < gs->entities_len; i += 1) {
Entity *e = gs->entities + i;
if (!e->is_active) {
continue;
}
e->velocity.y += gs->gravity * gs->delta_time;
if (e->velocity.y > gs->terminal_velocity) {
e->velocity.y = gs->terminal_velocity;
}
Rect next_collider = e->collider;
next_collider.x += e->velocity.x * gs->delta_time;
next_collider.y += e->velocity.y * gs->delta_time;
b32 on_ground = 0;
// Reset normals array.
gs->normals_len = 0;
gs->hit_colliders_len = 0;
for (int j = 0; j < gs->solid_geometry_len; j += 1) {
Rect *r = gs->solid_geometry + j;
Rect intersection = rect_intersect(next_collider, *r);
if (!intersection.x && !intersection.y) {
continue;
}
fixed_array_append(gs->hit_colliders, *r);
V2 normal = calculate_collision_normal(e->collider, *r);
if (normal.y != 0 || normal.x != 0) {
fixed_array_append(gs->normals, normal);
}
if (normal.y != 0) {
e->velocity.y = 0;
if (normal.y < 0) {
on_ground = 1;
next_collider.y = r->y - e->collider.h;
} else {
next_collider.y = r->y + r->h;
}
}
if (normal.x != 0) {
e->velocity.x = 0;
if (normal.x < 0) {
next_collider.x = r->x - e->collider.w;
} else {
next_collider.x = r->x + r->w;
}
}
}
// Player gets coyote time.
if (i == 0) {
if (on_ground) {
gs->player_coyote_timer = gs->player_coyote_time;
} else {
gs->player_coyote_timer -= gs->delta_time;
}
if (gs->player_coyote_timer <= 0.f) {
e->is_grounded = 0;
} else {
e->is_grounded = 1;
}
}
if (flag_is_set(e->behaviors, ENTITY_BEHAVIOR_AUTOWALK)) {
if (e->is_facing_left) {
e->velocity.x = e->move_speed;
} else {
e->velocity.x = -e->move_speed;
}
}
if (flag_is_set(e->behaviors, ENTITY_BEHAVIOR_FLIP_AT_EDGE)) {
for (int j = 0; j < gs->normals_len; j += 1) {
V2 *n = gs->normals + j;
Rect *r = gs->hit_colliders + j;
if (n->y < 0.f) {
if (e->collider.x < r->x) {
e->is_facing_left = 1;
}
if (e->collider.x + e->collider.w > r->x + r->w) {
e->is_facing_left = 0;
}
}
}
}
if (flag_is_set(e->behaviors, ENTITY_BEHAVIOR_FLIP_AT_WALL)) {
for (int j = 0; j < gs->normals_len; j += 1) {
V2 *n = gs->normals + j;
Rect *r = gs->hit_colliders + j;
if (n->x != 0.f) {
if (e->collider.x < r->x + r->w) {
e->is_facing_left = 0;
}
if (e->collider.x + e->collider.w > r->x) {
e->is_facing_left = 1;
}
}
}
}
e->is_grounded = on_ground;
e->collider = next_collider;
}
SDL_SetRenderDrawColor(gs->renderer, 0, 0, 0, 255);
SDL_RenderClear(gs->renderer);
for (int i = 0; i < gs->entities_len; i += 1) {
Entity *e = gs->entities + i;
Rect *r = &e->collider;
RGBA8 color = {255, 0, 0, 255};
if (i == 0) {
color = RGBA8(0, 255, 255, 255);
}
render_fill_rect(gs->renderer, r->x, r->y, r->w, r->h, color);
}
// Draw Debug Geometry.
for (int i = 0; i < gs->solid_geometry_len; i += 1) {
Rect *r = gs->solid_geometry + i;
render_draw_rect(gs->renderer, r->x, r->y, r->w, r->h, RGBA8(255, 255, 255, 255));
}
SDL_RenderPresent(gs->renderer);
}
printf("Yay...\n");
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment