Last active
July 24, 2024 05:39
-
-
Save Falconerd/358609b9785dec686aa81fb66123e3a3 to your computer and use it in GitHub Desktop.
Some game code stuff
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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) \ | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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