From 449795d4f76b5289edb4e1a887bc31d0d732220d Mon Sep 17 00:00:00 2001 From: Sara Date: Fri, 26 Jan 2024 11:32:53 +0100 Subject: [PATCH] i somehow managed to forget the fucking source code '~' --- src/engine/adb.c | 333 ++++++++++++++++++++++++++ src/engine/adb.h | 35 +++ src/engine/border_interaction_c.c | 58 +++++ src/engine/border_interaction_c.h | 23 ++ src/engine/camera.c | 62 +++++ src/engine/camera.h | 18 ++ src/engine/collision_shape_c.c | 334 ++++++++++++++++++++++++++ src/engine/collision_shape_c.h | 39 +++ src/engine/components.c | 3 + src/engine/components.h | 11 + src/engine/engine.c | 300 +++++++++++++++++++++++ src/engine/engine.h | 48 ++++ src/engine/fmath.h | 16 ++ src/engine/input.c | 27 +++ src/engine/input.h | 11 + src/engine/sprite_c.c | 65 +++++ src/engine/sprite_c.h | 18 ++ src/engine/transform_c.c | 28 +++ src/engine/transform_c.h | 34 +++ src/engine/ui.c | 386 ++++++++++++++++++++++++++++++ src/engine/ui.h | 44 ++++ src/engine/vec.h | 203 ++++++++++++++++ src/engine/velocity_c.c | 86 +++++++ src/engine/velocity_c.h | 16 ++ src/engine/view_c.c | 32 +++ src/engine/view_c.h | 19 ++ src/game/assets.c | 7 + src/game/assets.h | 12 + src/game/bullet.h | 53 ++++ src/game/bullet_c.c | 3 + src/game/bullet_c.h | 13 + src/game/debug_ui.c | 36 +++ src/game/debug_ui.h | 8 + src/game/enemies.h | 14 ++ src/game/enemy_c.c | 4 + src/game/enemy_c.h | 12 + src/game/game.c | 68 ++++++ src/game/input_c.c | 25 ++ src/game/input_c.h | 17 ++ src/game/player.c | 55 +++++ src/game/player.h | 17 ++ src/game/player_c.c | 67 ++++++ src/game/player_c.h | 18 ++ 43 files changed, 2678 insertions(+) create mode 100644 src/engine/adb.c create mode 100644 src/engine/adb.h create mode 100644 src/engine/border_interaction_c.c create mode 100644 src/engine/border_interaction_c.h create mode 100644 src/engine/camera.c create mode 100644 src/engine/camera.h create mode 100644 src/engine/collision_shape_c.c create mode 100644 src/engine/collision_shape_c.h create mode 100644 src/engine/components.c create mode 100644 src/engine/components.h create mode 100644 src/engine/engine.c create mode 100644 src/engine/engine.h create mode 100644 src/engine/fmath.h create mode 100644 src/engine/input.c create mode 100644 src/engine/input.h create mode 100644 src/engine/sprite_c.c create mode 100644 src/engine/sprite_c.h create mode 100644 src/engine/transform_c.c create mode 100644 src/engine/transform_c.h create mode 100644 src/engine/ui.c create mode 100644 src/engine/ui.h create mode 100644 src/engine/vec.h create mode 100644 src/engine/velocity_c.c create mode 100644 src/engine/velocity_c.h create mode 100644 src/engine/view_c.c create mode 100644 src/engine/view_c.h create mode 100644 src/game/assets.c create mode 100644 src/game/assets.h create mode 100644 src/game/bullet.h create mode 100644 src/game/bullet_c.c create mode 100644 src/game/bullet_c.h create mode 100644 src/game/debug_ui.c create mode 100644 src/game/debug_ui.h create mode 100644 src/game/enemies.h create mode 100644 src/game/enemy_c.c create mode 100644 src/game/enemy_c.h create mode 100644 src/game/game.c create mode 100644 src/game/input_c.c create mode 100644 src/game/input_c.h create mode 100644 src/game/player.c create mode 100644 src/game/player.h create mode 100644 src/game/player_c.c create mode 100644 src/game/player_c.h diff --git a/src/engine/adb.c b/src/engine/adb.c new file mode 100644 index 0000000..234185b --- /dev/null +++ b/src/engine/adb.c @@ -0,0 +1,333 @@ +// +// adb.c +// engine +// +// Created by Scott on 14/10/2022. +// + +#include "adb.h" +#include +#include +#include +#include +#include +#include + +typedef struct asset_t { + char* filename; + size_t extention_offset; + void* instance; + asset_handle_t handle; +} asset_t; + +typedef struct file_handler_t { + uint64_t ext_hash; + char* extention; + asset_load_fn load; + asset_free_fn free; +} file_handler_t; + +int adb_is_init = 0; + +asset_t* adb_assets_first; +size_t adb_assets_last; +size_t adb_assets_size; + +file_handler_t* adb_handlers_first; +size_t adb_handlers_last; +size_t adb_handlers_size; + +static inline uint64_t str_hash(const char* str); +static inline void unload_asset(asset_t* asset); +static inline size_t file_extention_offset(const char* filename); +static inline void free_asset_t(asset_t*); + + +// +// MODULE LIFETIME +// + +int adb_init(size_t max_asset_bytes) +{ + assert(adb_is_init == 0); + adb_assets_size = max_asset_bytes; + adb_handlers_size = 5; + + adb_assets_last = 0; + adb_assets_first = malloc(adb_assets_size * sizeof(asset_t)); + + adb_handlers_last = 0; + adb_handlers_first = malloc(adb_handlers_size * sizeof(file_handler_t)); + + adb_is_init = 1; + + return 0; +} + +int adb_close() +{ + for(size_t i = 0; i < adb_assets_last; ++i) + { + free_asset_t(&adb_assets_first[i]); + } + for(size_t i = 0; i < adb_handlers_last; ++i) + { + free(adb_handlers_first[i].extention); + } + free(adb_assets_first); + free(adb_handlers_first); + + return 0; +} + + +// +// FILE HANDLERS +// + +void sort_file_handlers() +{ + size_t swaps = 0; + file_handler_t tmp; + do + { + swaps = 0; + for(size_t i = 1; i < adb_handlers_last; ++i) + { + if(adb_handlers_first[i-1].ext_hash > adb_handlers_first[i].ext_hash) + { + memcpy(&tmp, &adb_handlers_first[i-1], sizeof(file_handler_t)); + memcpy(&adb_handlers_first[i-1], &adb_handlers_first[i], sizeof(file_handler_t)); + memcpy(&adb_handlers_first[i], &tmp, sizeof(file_handler_t)); + ++swaps; + } + } + } while(swaps > 0); +} + +void destroy_file_handler(file_handler_t* handler) +{ + free(handler->extention); + free(handler); +} + +file_handler_t* get_file_handler(const char* extention) +{ + if(adb_handlers_last == 0) + return NULL; + + uint64_t hash = str_hash(extention); + long int l = 0, r = adb_handlers_last-1, m; + file_handler_t* current; + while(l <= r) + { + m = floorf((float)(l+r)/2.f); + current = &adb_handlers_first[m]; + if(current->ext_hash < hash) + l = m + 1; + else if(current->ext_hash > hash) + r = m - 1; + else if(current->ext_hash == hash) + return current; + } + return NULL; +} + +int adb_register_file_handler(const char* file_extention, asset_load_fn loadfn, asset_free_fn freefn) +{ + file_handler_t* existing = get_file_handler(file_extention); + if(existing == NULL) + { + if(adb_handlers_last + 1 >= adb_handlers_size) + { + adb_handlers_first = realloc(adb_handlers_first, adb_handlers_size * 2); + } + file_handler_t* new = (adb_handlers_first+adb_handlers_last); + new->load = loadfn; + new->free = freefn; + new->extention = malloc(strlen(file_extention)); + strcpy(new->extention, file_extention); + new->ext_hash = str_hash(file_extention); + ++adb_handlers_last; + } + else + { + if(loadfn != NULL) + existing->load = loadfn; + if(freefn != NULL) + existing->free = freefn; + } + + sort_file_handlers(); + + return 0; +} + + +// +// ASSETS +// + +void sort_assets() +{ + size_t swaps = 0; + asset_t tmp; + do + { + swaps = 0; + for(size_t i = 0; i < adb_assets_last; ++i) + { + if(adb_assets_first[i-1].handle > adb_assets_first[i].handle) + { + memcpy(&tmp, &adb_assets_first[i-1], sizeof(asset_t)); + memcpy(&adb_assets_first[i-1], &adb_assets_first[i], sizeof(asset_t)); + memcpy(&adb_assets_first[i], &tmp, sizeof(asset_t)); + ++swaps; + } + } + } while(swaps > 0); +} + +asset_t* get_asset_t(asset_handle_t handle) +{ + if(adb_assets_last == 0) + return NULL; + + asset_t* current; + long int l = 0, r = adb_assets_last-1, m; + while(l <= r) + { + m = floorf((float)(l+r)/2.f); + current = &adb_assets_first[m]; + + if(current->handle < handle) + l = m + 1; + else if(current->handle > handle) + r = m - 1; + else if(current->handle == handle) + return current; + } + return NULL; +} + +void* adb_get_asset(asset_handle_t handle) +{ + asset_t* asset = get_asset_t(handle); + if(asset == NULL) + return NULL; + else + return asset->instance; +} + +int adb_try_get_asset(asset_handle_t handle, void** o_ptr) +{ + void* asset = adb_get_asset(handle); + if(o_ptr != NULL) + memcpy(o_ptr, &asset, sizeof(void*)); + return asset != NULL; +} + +asset_handle_t adb_load_asset(const char* file) +{ + // get the full path of the file + char* filename = realpath(file, NULL); + if(filename == NULL) + return 0; + + // create asset handle + asset_handle_t handle = str_hash(filename); + + // just return handle if asset is loaded + if(adb_try_get_asset(handle, NULL)) + return handle; + + // get the index of the file extention + size_t ext_offset = file_extention_offset(filename); + // find the handler which is supposed to handle files of this type + file_handler_t* handler = get_file_handler(filename + ext_offset); + + // adb has no idea how to handle this file type + if(handler == NULL) + return 0x0; + + // add the asset + adb_assets_first[adb_assets_last] = (asset_t){ + .filename = filename, + .extention_offset = ext_offset, + .instance = handler->load(filename), + .handle = handle + }; + + // increment asset offset + ++adb_assets_last; + + // sort assets by handle + /// TODO: properly insert new assets rather than sorting every time an asset is added + sort_assets(); + return handle; +} + +void free_asset_t(asset_t* asset) +{ + file_handler_t* handler = get_file_handler(asset->filename + asset->extention_offset); + assert(handler != NULL); + + handler->free(asset->instance); + free(asset->filename); +} + +void adb_free_asset(asset_handle_t handle) +{ + asset_t* asset = get_asset_t(handle); + + if(asset == NULL) + return; + + free_asset_t(asset); +} + + +// +// FILES +// + +asset_handle_t get_file_handle(const char* filename) +{ + char* real_path = realpath(filename, NULL); + asset_handle_t handle = str_hash(real_path); + free(real_path); + return adb_try_get_asset(handle, NULL) ? handle : 0; +} + +size_t file_extention_offset(const char* filename) +{ + const char* cptr = filename + strlen(filename); + while(cptr != filename) + { + if(*cptr == '.') + return cptr - filename; + --cptr; + } + + return 0; +} + +uint64_t str_hash(const char* str) +{ + static const int shift = (8 * sizeof(uint64_t) - 4); + static const uint64_t upper4mask = (uint64_t)0xF << shift; + uint64_t hash = 0; + uint64_t upperfour; + size_t len = 0; + + for(const char* c = str; *c != '\0'; ++c) + { + upperfour = hash & upper4mask; + hash = ((hash << 4) ^ (*c)); + if(upperfour != 0) + hash ^= (upperfour >> shift); + ++len; + } + + return hash ^ len; +} diff --git a/src/engine/adb.h b/src/engine/adb.h new file mode 100644 index 0000000..0cf460c --- /dev/null +++ b/src/engine/adb.h @@ -0,0 +1,35 @@ +// +// adb.h +// engine +// +// Created by Scott on 14/10/2022. +// + +#ifndef adb_h +#define adb_h + +#include +#include + +typedef uint64_t asset_handle_t; + +typedef void* (*asset_load_fn)(const char*); +typedef void (*asset_free_fn)(void*); + +typedef struct asset_t asset_t; +typedef struct file_handler_t file_handler_t; + +extern int adb_init(size_t max_asset); +extern asset_handle_t adb_get_file_handle(const char* filename); +extern int adb_asset_is_loaded(asset_handle_t handle); +extern void* adb_get_asset(asset_handle_t handle); +#define adb_get_asset_as(__TYPE, handle) ((__TYPE*)get_asset(handle)) +extern int adb_try_get_asset(asset_handle_t handle, void** o_ptr); +extern asset_handle_t adb_load_asset(const char* file); +extern void adb_free_asset(asset_handle_t handle); +extern size_t adb_set_asset_memory(size_t max_bytes); +extern int adb_close(); +extern int adb_register_file_handler(const char* file_extention, asset_load_fn loadfn, asset_free_fn freefn); + + +#endif /* adb_h */ diff --git a/src/engine/border_interaction_c.c b/src/engine/border_interaction_c.c new file mode 100644 index 0000000..b6e7573 --- /dev/null +++ b/src/engine/border_interaction_c.c @@ -0,0 +1,58 @@ +#include "border_interaction_c.h" + +#include +#include +#include +#include +#include "components.h" + +ecsComponentMask border_interaction_component = nocomponent; + +void _wrap_position(fvec* position, fvec margin, SDL_Rect border) +{ + // wrap horizontal + if(position->x < border.x-margin.x) + position->x += border.w + margin.x*2; + else if(position->x > border.x + border.w + margin.x) + position->x -= border.w + margin.x*2; + + // wrap vertical + if(position->y < border.y-margin.y) + position->y += border.h + margin.y*2; + else if(position->y > border.y + border.h + margin.x) + position->y -= border.h + margin.y*2; +} + +void border_interaction_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time) +{ + fvec* position; + border_interaction_t* interaction; + view_t* view = ecsGetComponentPtr(get_main_camera(), view_component); + SDL_Rect border = {-(view->width/2), -(view->height/2), view->width, view->height}; + + if(query_update_group(&physics_update, &delta_time)) + { + for(size_t i = 0; i < count; ++i) + { + position = ecsGetComponentPtr(entities[i], position_component); + interaction = ecsGetComponentPtr(entities[i], border_interaction_component); + + switch(interaction->type) + { + default: assert(0); break; + case BORDER_WRAP: + _wrap_position(position, interaction->margin, border); + break; + case BORDER_DESTROY: + { + if(position->x < border.x || position->x > border.x + border.w || position->y < border.y || position->y > border.y + border.h ) + { + ecsDestroyEntity(entities[i]); + } + } + break; + } + } + } +} + diff --git a/src/engine/border_interaction_c.h b/src/engine/border_interaction_c.h new file mode 100644 index 0000000..91e3d5e --- /dev/null +++ b/src/engine/border_interaction_c.h @@ -0,0 +1,23 @@ +#ifndef border_interaction_c_h +#define border_interaction_c_h + +#include +#include +#include + +typedef enum border_interaction_e { + BORDER_WRAP, + BORDER_DESTROY +} border_interaction_e; + +typedef struct border_interaction_t { + border_interaction_e type; + fvec margin; +} border_interaction_t; + + +extern ecsComponentMask border_interaction_component; + +extern void border_interaction_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time); + +#endif /* border_interaction_c_h */ diff --git a/src/engine/camera.c b/src/engine/camera.c new file mode 100644 index 0000000..8376a6d --- /dev/null +++ b/src/engine/camera.c @@ -0,0 +1,62 @@ +#include "camera.h" + +ecsEntityId camera_main = noentity; + +void set_main_camera(ecsEntityId camera) +{ + if(get_main_camera() == noentity) + { + camera_main = camera; + } +} + +ecsEntityId get_main_camera() +{ + if((ecsGetComponentMask(camera_main) & CAMERA_A) == 0) + { + return (camera_main = noentity); + } + else return camera_main; +} + +void get_camera_rect(ecsEntityId camera, SDL_Rect* o_rect) +{ + view_t* view = ecsGetComponentPtr(camera, view_component); + fvec* position = ecsGetComponentPtr(camera, position_component); + + if(view != NULL && position != NULL) + { + get_view_rect(view, o_rect); + return; + + o_rect->x += (int)floorf(position->x); + o_rect->y += (int)floorf(position->y); + } + else + { + *o_rect = (SDL_Rect){0,0,0,0}; + } +} + +ecsEntityId create_default_camera() +{ + ecsEntityId camera_id = ecsCreateEntity(CAMERA_A); + + if(camera_id != noentity) + { + view_t* view = ecsGetComponentPtr(camera_id, view_component); + int width, height; + SDL_GetRendererOutputSize(renderer, &width, &height); + double asp_ratio = (double)width/height; + view->width = 50; + view->height = view->width * asp_ratio; + + fvec* position = ecsGetComponentPtr(camera_id, position_component); + position->x = 0.f; + position->y = 0.f; + + if(get_main_camera() == noentity) + set_main_camera(camera_id); + } + return camera_id; +} diff --git a/src/engine/camera.h b/src/engine/camera.h new file mode 100644 index 0000000..848d8e3 --- /dev/null +++ b/src/engine/camera.h @@ -0,0 +1,18 @@ +#ifndef camera_h +#define camera_h + +#include "view_c.h" +#include "transform_c.h" + +extern ecsEntityId get_main_camera(); + +extern void set_main_camera(ecsEntityId entity); + +extern ecsEntityId create_default_camera(); + +extern void get_camera_rect(ecsEntityId camera, SDL_Rect* o_rect); + +#define CAMERA_A position_component | view_component + +#endif /* camera_h */ + diff --git a/src/engine/collision_shape_c.c b/src/engine/collision_shape_c.c new file mode 100644 index 0000000..0eb67a9 --- /dev/null +++ b/src/engine/collision_shape_c.c @@ -0,0 +1,334 @@ +#include "collision_shape_c.h" + +#include "transform_c.h" +#include "engine.h" + +#include +#include +#include +#include + +ecsComponentMask shape_component = 0x0; + +pthread_mutex_t col_reg_mutex = PTHREAD_MUTEX_INITIALIZER; +size_t col_num_registered = 0; +ecsEntityId* col_registered_shapes = NULL; + +typedef struct simplex_t { + fvec points[4]; + int count; +} simplex_t; + +void _simplex_push(simplex_t* simpl, fvec point); +int _next_simplex(simplex_t* simpl, fvec* direction); +fvec _support_point(shape_t* a, shape_t* b, fvec direction); +fvec _shape_furthest_point(shape_t* shape, fvec direction); +int _entities_overlap(ecsEntityId a, ecsEntityId b); +void _remove_from_collision_world(size_t index); + +ecsEntityId shape_overlap_test(shape_t* shape, fvec position) +{ + shape_t* other; + + for(size_t i = 0; i < col_num_registered; ++i) + { + do + { + other = ecsGetComponentPtr(col_registered_shapes[i], shape_component); + + if(other == NULL) + _remove_from_collision_world(i); + + if(col_num_registered == 0) + return noentity; + } while(other == NULL); + + if(shapes_overlap(shape, other)) + { + return col_registered_shapes[i]; + } + } + + return noentity; +} + +void _transform_shape(ecsEntityId entity, shape_t* shape) +{ + shape_t* a = ecsGetComponentPtr(entity, shape_component); + memcpy(shape, a, sizeof(shape_t)); + + fvec* position = ecsGetComponentPtr(entity, position_component); + fvec* scale = ecsGetComponentPtr(entity, scale_component); + float* rotation = ecsGetComponentPtr(entity, rotation_component); + + transform_point(&shape->position_offset, position, scale, rotation); + + shape->scale = *scale; + shape->rotation_offset += *rotation; +} + +int _entities_overlap(ecsEntityId entity_a, ecsEntityId entity_b) +{ + // make copies of the two shapes so that we can transform them safely + shape_t real_a; + shape_t real_b; + _transform_shape(entity_a, &real_a); + _transform_shape(entity_b, &real_b); + return shapes_overlap(&real_a, &real_b); +} + +int shapes_overlap(shape_t* a, shape_t* b) +{ + if(a == NULL || b == NULL) + { + return 0; + } + + fvec direction = VRIGHT; + fvec support = _support_point(a, b, direction); + + simplex_t simplex; + _simplex_push(&simplex, support); + vmulf(&direction, &direction, -1.f); + + while(1) + { + support = _support_point(a, b, direction); + + if(vdot(&support, &direction) <= 0) + { + return 0; + } + + _simplex_push(&simplex, support); + + if(_next_simplex(&simplex, &direction)) + { + return 1; + } + } +} + +void add_to_collision_world(ecsEntityId entity) +{ + pthread_mutex_lock(&col_reg_mutex); + size_t new_registered = col_num_registered + 1; + col_registered_shapes = realloc(col_registered_shapes, sizeof(ecsEntityId) * new_registered); + + col_registered_shapes[col_num_registered] = entity; + col_num_registered = new_registered; + pthread_mutex_unlock(&col_reg_mutex); +} + +void _remove_from_collision_world(size_t index) +{ + pthread_mutex_lock(&col_reg_mutex); + if(col_num_registered > 0) + { + size_t length_after = (col_num_registered - index); + + ecsEntityId* dest = col_registered_shapes + index; + ecsEntityId* src = dest + 1; + + memmove(dest, src, length_after); + + col_num_registered -= 1; + + if(col_num_registered == 0) + { + free(col_registered_shapes); + col_registered_shapes = NULL; + } + else + { + ecsEntityId* nreg = NULL; + do + { + nreg = realloc(col_registered_shapes, sizeof(ecsEntityId) * col_num_registered); + } while(nreg == NULL); + col_registered_shapes = nreg; + } + } + pthread_mutex_unlock(&col_reg_mutex); +} + +ecsEntityId shape_sweep(ecsEntityId entity, fvec move, short trigger_overlap) +{ + ecsEntityId hit = noentity; + ecsEntityId newhit; + fvec* position = ecsGetComponentPtr(entity, position_component); + fvec target_position; + vadd(&target_position, position, &move); + shape_t* shape = ecsGetComponentPtr(entity, shape_component); + shape_t* other = NULL; + + while(!veq(position, &target_position)) + { + vmovetowards(position, position, &target_position, 0.1f); + + newhit = shape_overlap_test(shape, *position); + + if(newhit) + { + if(trigger_overlap) + { + other = ecsGetComponentPtr(newhit, shape_component); + other->on_overlap(newhit, entity); + shape->on_overlap(entity, newhit); + } + + if(hit == noentity) + { + hit = newhit; + return hit; + } + } + } + + return hit; +} + +// support functions +fvec _circle_furthest_point(shape_t* shape, fvec direction) +{ + vnor(&direction, &direction); + vmulf(&direction, &direction, shape->circle.radius); + return direction; +} + +fvec _array_furthest_point(fvec* array, size_t count, fvec direction) +{ + vnor(&direction, &direction); + fvec point = array[0]; + float dot_to_beat = vdot(array, &direction); + for(int i = 0; i < count; ++i) + { + float dot = vdot(array + i, &direction); + if(dot > dot_to_beat) + { + dot_to_beat = dot; + point = array[i]; + } + } + + return point; +} + +fvec _rect_furthest_point(shape_t* shape, fvec direction) +{ + fvec points[4] = { + {shape->rect.x, shape->rect.y}, + {shape->rect.x + shape->rect.w, shape->rect.y}, + {shape->rect.x + shape->rect.w, shape->rect.y + shape->rect.h}, + {shape->rect.x, shape->rect.y + shape->rect.h} + }; + + fvec point = _array_furthest_point(points, 4, direction); + return point; +} + +fvec _shape_furthest_point(shape_t* shape, fvec direction) +{ + switch(shape->type) + { + default: + return VZERO; + case SHAPE_CIRCLE: + return _circle_furthest_point(shape, direction); + case SHAPE_RECT: + return _circle_furthest_point(shape, direction); + } +} + +fvec _support_point(shape_t* a, shape_t* b, fvec direction) +{ + fvec inv_dir; + vmulf(&inv_dir, &direction, -1.f); + + fvec fpa = _shape_furthest_point(a, direction); + fvec fpb = _shape_furthest_point(b, inv_dir); + transform_point(&fpa, &a->position_offset, &a->scale, &a->rotation_offset); + transform_point(&fpb, &b->position_offset, &b->scale, &b->rotation_offset); + + vsub(&direction, &fpa, &fpb); + + return direction; +} + +void _simplex_push(simplex_t* simpl, fvec point) +{ + memcpy(&simpl->points[1], simpl->points, sizeof(fvec) * 3); + simpl->points[0] = point; +} + +int _same_direction(fvec direction, fvec ao) +{ + return vdot(&direction, &ao) > 0; +} + +int _simplex_line(simplex_t* simplex, fvec* direction) +{ + fvec a = simplex->points[0]; + fvec b = simplex->points[1]; + + fvec ab, ao; + vmulf(&ao, &a, -1.f); + vsub(&ab, &b, &a); + + if(_same_direction(ab, ao)) + { + vtriple(direction, &ab, &ao); + } + else + { + simplex->count = 1; + *direction = ao; + } + + return 0; +} + +int _simplex_triangle(simplex_t* simplex, fvec* direction) +{ + fvec a = simplex->points[0]; + fvec b = simplex->points[1]; + fvec c = simplex->points[2]; + + fvec ab, ac, ao; + vsub(&ab, &b, &a); + vsub(&ac, &c, &a); + vmulf(&ao, &a, -1.f); + + fvec abp, acp; + vtriple(&abp, &ac, &ab); + vtriple(&acp, &ab, &ac); + + if(vdot(&abp, &ao) > 0) + { + // remove c + *direction = abp; + simplex->count = 2; + return 0; + } + else if(vdot(&acp, &ao) > 0) + { + // remove b + simplex->points[1] = simplex->points[2]; + simplex->count = 2; + return 0; + } + + return 1; +} + +int _next_simplex(simplex_t* simpl, fvec* direction) +{ + switch(simpl->count) + { + case 2: + return _simplex_line(simpl, direction); + case 3: + return _simplex_triangle(simpl, direction); + } +} + diff --git a/src/engine/collision_shape_c.h b/src/engine/collision_shape_c.h new file mode 100644 index 0000000..b68b6bf --- /dev/null +++ b/src/engine/collision_shape_c.h @@ -0,0 +1,39 @@ +#ifndef collision_shape_c_h +#define collision_shape_c_h + +#include +#include +#include + +typedef void (*on_overlap_fn)(ecsEntityId self, ecsEntityId other); + +extern ecsComponentMask shape_component; + +typedef enum SHAPE_TYPE { + SHAPE_CIRCLE, + SHAPE_RECT +} SHAPE_TYPE; + +typedef struct shape_t { + SHAPE_TYPE type; + on_overlap_fn on_overlap; + + float rotation_offset; + fvec position_offset; + fvec scale; + + union { + SDL_Rect rect; + struct { + float radius; + } circle; + }; +} shape_t; + +extern ecsEntityId shape_sweep(ecsEntityId entity, fvec move, short trigger_overlap); +extern int shapes_overlap(shape_t* a, shape_t* b); +extern ecsEntityId shape_overlap_test(shape_t* shape, fvec position); + +extern void add_to_collision_world(ecsEntityId entity); + +#endif /* collision_shape_c_h */ diff --git a/src/engine/components.c b/src/engine/components.c new file mode 100644 index 0000000..a9a2e78 --- /dev/null +++ b/src/engine/components.c @@ -0,0 +1,3 @@ +#include "components.h" + +ecsComponentMask collision_shape_component = 0x0; diff --git a/src/engine/components.h b/src/engine/components.h new file mode 100644 index 0000000..cec7bb5 --- /dev/null +++ b/src/engine/components.h @@ -0,0 +1,11 @@ +#ifndef components_h +#define components_h + +#include "transform_c.h" +#include "velocity_c.h" +#include "sprite_c.h" +#include "collision_shape_c.h" +#include "border_interaction_c.h" +#include "view_c.h" + +#endif /* components_h */ diff --git a/src/engine/engine.c b/src/engine/engine.c new file mode 100644 index 0000000..1756350 --- /dev/null +++ b/src/engine/engine.c @@ -0,0 +1,300 @@ +#include "engine.h" + +#include +#include +#include +#include +#include +#include +#include "components.h" +#include "adb.h" +#include "input.h" +#include "ui.h" + +typedef struct update_group_t { + double last_time; + double time; + double delta_time; + double target_delta; + short is_frame; +} update_group_t; + +SDL_Window* window; +SDL_Renderer* renderer; +int engine_wants_to_quit; + +update_group_t frame_update; +update_group_t physics_update; + +void system_window_clear(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time); +void system_window_display(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time); +void system_update_groups(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time); +void system_poll_events(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time); + +void engine_init(); +void engine_run(); +void engine_handle_event(SDL_Event* event); +void engine_clean(); + +void engine_update_group(update_group_t* group); + +void* asset_load_sdl_image(const char* filename) +{ + SDL_Surface* surf = IMG_Load(filename); + SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf); + SDL_FreeSurface(surf); + return tex; +} + +void asset_free_sdl_image(void* instance) +{ + SDL_Texture* tex = instance; + if(tex != NULL) + { + SDL_DestroyTexture(tex); + } +} + +void* asset_load_ttf_font(const char* filename) +{ + TTF_Font* font = TTF_OpenFont(filename, 50); + return font; +} + +void asset_free_ttf_font(void* instance) +{ + TTF_Font* font = instance; + if(instance != NULL) + { + TTF_CloseFont(font); + } +} + +int main(int argc, char* argv[]) +{ + engine_init(); + engine_run(); + engine_clean(); +} + +void engine_init() +{ + engine_wants_to_quit = 0; + + // init asset database for 100 assets + adb_init(100); + + // register default image file handlers + adb_register_file_handler(".png", &asset_load_sdl_image, &asset_free_sdl_image); + adb_register_file_handler(".jpg", &asset_load_sdl_image, &asset_free_sdl_image); + // default font file handlers + adb_register_file_handler(".ttf", &asset_load_ttf_font, &asset_free_ttf_font); + adb_register_file_handler(".otf", &asset_load_ttf_font, &asset_free_ttf_font); + + // load default init settings + engine_init_t init_settings; + default_engine_init_settings(&init_settings); + // set default target framerates + set_update_group_target_framerate(&frame_update, 60); + physics_update.target_delta = 0.f; + engine_update_group(&frame_update); + engine_update_group(&physics_update); + + // allow game to adjust init settings as needed + game_config(&init_settings); + + // init sdl, create window and create renderer from window + SDL_Init(init_settings.sdl_init_flags); + TTF_Init(); + IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG); + window = SDL_CreateWindow(init_settings.window_name, + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + init_settings.window_width, + init_settings.window_height, + init_settings.window_init_flags); + + if(window == NULL) + { + exit(1); + } + + renderer = SDL_CreateRenderer(window, init_settings.renderer_index, + init_settings.renderer_init_flags); + + if(renderer == NULL) + { + exit(2); + } + + // init input system + input_init(); + + // initialize runtime object database + ecsInit(); + + // initialize imgui renderer + uiInit(renderer); + + position_component = ecsRegisterComponent(fvec); + rotation_component = ecsRegisterComponent(float); + scale_component = ecsRegisterComponent(fvec); + + linear_velocity_component = ecsRegisterComponent(fvec); + angular_velocity_component = ecsRegisterComponent(float); + linear_drag_component = ecsRegisterComponent(float); + angular_drag_component = ecsRegisterComponent(float); + + sprite_component = ecsRegisterComponent(sprite_t); + border_interaction_component = ecsRegisterComponent(border_interaction_t); + view_component = ecsRegisterComponent(view_t); + shape_component = ecsRegisterComponent(shape_t); + + // enable core engine systems + ecsEnableSystem(&system_poll_events, nocomponent, ECS_NOQUERY, 0, -300); + ecsEnableSystem(&system_update_groups, nocomponent, ECS_NOQUERY, 0, -200); + ecsEnableSystem(&system_update_views, view_component, ECS_QUERY_ALL, MAX_THREADS, 499); + + // 500-599 is for rendering + ecsEnableSystem(&system_window_clear, nocomponent, ECS_NOQUERY, 0, 500); + ecsEnableSystem(&render_textures_s, sprite_component | position_component, ECS_QUERY_ALL, 0, 550); + ecsEnableSystem(&system_window_display, nocomponent, ECS_NOQUERY, 0, 599); + + + // 600-999 is for post-render updates + ecsEnableSystem(&linear_velocity_update_s, position_component | linear_velocity_component, ECS_QUERY_ALL, MAX_THREADS, 750); + ecsEnableSystem(&angular_velocity_update_s, rotation_component | angular_velocity_component, ECS_QUERY_ALL, MAX_THREADS, 755); + ecsEnableSystem(&border_interaction_s, border_interaction_component | position_component, ECS_QUERY_ALL, MAX_THREADS, 810); + ecsEnableSystem(&wrap_rotation_s, rotation_component, ECS_QUERY_ALL, 8, 815); + + + // initialize game + game_init(); + + // run created tasks + ecsRunTasks(); +} + +void engine_run() +{ + float actual_time; + + while(!engine_wants_to_quit) + { + float physics_update_delta; + query_update_group(&physics_update, &physics_update_delta); + ecsRunSystems(physics_update_delta); + } +} + +void engine_handle_event(SDL_Event* event) +{ + switch(event->type) + { + default: break; + case SDL_QUIT: + engine_wants_to_quit = 1; + break; + case SDL_WINDOWEVENT: + { + switch(event->window.event) + { + default: break; + case SDL_WINDOWEVENT_SIZE_CHANGED: + ecsEnableSystem(&system_update_views, view_component, ECS_QUERY_ALL, 8, -500); + break; + } + break; + } + } +} + +void engine_clean() +{ + // quit game, ui asset database, ecs + game_quit(); + uiTerminate(); + adb_close(); + ecsTerminate(); + input_terminate(); + + // delete renderer and window, quit sdl + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); +} + +void engine_update_group(update_group_t* group) +{ + group->time = (double)clock() / (double)CLOCKS_PER_SEC; + double delta_time = group->time - group->last_time; + group->is_frame = delta_time >= group->target_delta; + + if(group->is_frame) + { + group->last_time = group->time; + group->delta_time = delta_time; + } +} + +void system_update_groups(ecsEntityId* entities, ecsComponentMask* components, size_t size, float delta_time) +{ + engine_update_group(&frame_update); + engine_update_group(&physics_update); +} + +void system_window_clear(ecsEntityId* entities, ecsComponentMask* components, size_t size, float delta_time) +{ + if(query_update_group(&frame_update, &delta_time)) + { + // clear screen all black + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + } +} +void system_window_display(ecsEntityId* entities, ecsComponentMask* components, size_t size, float delta_time) +{ + if(query_update_group(&frame_update, &delta_time)) + { + // swap buffer + SDL_RenderPresent(renderer); + } +} + +void system_poll_events(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time) +{ + SDL_Event evt; + if(query_update_group(&frame_update, &delta_time)) + { + while(SDL_PollEvent(&evt)) + { + engine_handle_event(&evt); + } + } +} + +short query_update_group(update_group_t* update_group, float* out_delta_time) +{ + if(out_delta_time != NULL) + (*out_delta_time) = (float)update_group->delta_time; + + return update_group->is_frame; +} + +void set_update_group_target_framerate(update_group_t* group, int fps) +{ + group->target_delta = 1.f / fps; +} + +void default_engine_init_settings(engine_init_t* init_settings) +{ + (*init_settings) = (engine_init_t){ + .window_width = 640, .window_height = 420, + .window_init_flags = SDL_WINDOW_SHOWN, + .sdl_init_flags = SDL_INIT_VIDEO, + .renderer_init_flags = SDL_RENDERER_ACCELERATED, + .renderer_index = -1, + .window_name = "Game" + }; +} + diff --git a/src/engine/engine.h b/src/engine/engine.h new file mode 100644 index 0000000..c3341d1 --- /dev/null +++ b/src/engine/engine.h @@ -0,0 +1,48 @@ +#ifndef _engine_h +#define _engine_h + +#include + +#ifndef MAX_THREADS +#define MAX_THREADS 8 +#endif /* MAX_THREADS */ + +typedef struct engine_init_t { + int window_width, window_height; + uint32_t window_init_flags; + uint32_t sdl_init_flags; + uint32_t renderer_init_flags; + int renderer_index; + const char* window_name; +} engine_init_t; + +typedef struct update_group_t update_group_t; + +extern update_group_t frame_update; +extern update_group_t physics_update; + +extern void default_engine_init_settings(engine_init_t*); + +extern void game_config(engine_init_t*); +extern void game_init(); +extern void game_quit(); + +extern short query_update_group(update_group_t* group, float* delta_time); +extern void set_update_group_target_framerate(update_group_t* group, int fps); + +extern struct SDL_Renderer* renderer; + +#if defined(DEBUG) +#include + +#define E_LOG(...)\ + fprintf(stdout, "%s:%d: ", __FILE__, __LINE__);\ + fprintf(stdout, __VA_ARGS__);\ + fprintf(stdout, "\n") +#else +#define E_LOG(...) +#endif + +#include "components.h" + +#endif /* !_engine_h */ diff --git a/src/engine/fmath.h b/src/engine/fmath.h new file mode 100644 index 0000000..5181404 --- /dev/null +++ b/src/engine/fmath.h @@ -0,0 +1,16 @@ +#ifndef fmath_h +#define fmath_h + +static inline +float fmovetowards(float start, float target, float delta) +{ + if(start - delta > target) + start -= delta; + else if(start + delta < target) + start += delta; + else + start = target; + return start; +} + +#endif /* fmath_h */ diff --git a/src/engine/input.c b/src/engine/input.c new file mode 100644 index 0000000..69210a4 --- /dev/null +++ b/src/engine/input.c @@ -0,0 +1,27 @@ +#include "input.h" +#include + +const uint8_t* input_key_array; +int input_num_keys; + +void input_init() +{ + input_key_array = SDL_GetKeyboardState(&input_num_keys); +} + +void input_terminate() {} + +int8_t input_axis(unsigned key_negative, unsigned key_positive) +{ + return input_button(key_positive) - input_button(key_negative); +} + +int8_t input_button(unsigned key) +{ + if(key < input_num_keys) + { + return input_key_array[key]; + } + + return 0; +} diff --git a/src/engine/input.h b/src/engine/input.h new file mode 100644 index 0000000..0d0da38 --- /dev/null +++ b/src/engine/input.h @@ -0,0 +1,11 @@ +#ifndef input_h +#define input_h + +#include + +extern void input_init(); +extern void input_terminate(); +extern int8_t input_axis(unsigned key_negative, unsigned key_positive); +extern int8_t input_button(unsigned key); + +#endif /* input_h */ diff --git a/src/engine/sprite_c.c b/src/engine/sprite_c.c new file mode 100644 index 0000000..f197a7f --- /dev/null +++ b/src/engine/sprite_c.c @@ -0,0 +1,65 @@ +#include "sprite_c.h" + +#include +#include +#include + +#include "transform_c.h" +#include "camera.h" + +ecsComponentMask sprite_component = 0x0; + +void render_textures_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time) +{ + SDL_Rect camera_rect; + ecsEntityId id; + + sprite_t* sprite; + fvec* position; + float* rotation; + fvec* scale; + float scale_mul; + + float angle; + SDL_FRect dst; + int window_width, window_height; + + SDL_GetRendererOutputSize(renderer, &window_width, &window_height); + get_camera_rect(get_main_camera(), &camera_rect); + scale_mul = (float)window_width / camera_rect.w; + + SDL_RenderDrawRect(renderer, &camera_rect); + + if(query_update_group(&frame_update, &delta_time)) + { + for(size_t i = 0; i < count; ++i) + { + id = entities[i]; + sprite = ecsGetComponentPtr(id, sprite_component); + position = ecsGetComponentPtr(id, position_component); + rotation = ecsGetComponentPtr(id, rotation_component); + scale = ecsGetComponentPtr(id, scale_component); + + if(sprite->texture != NULL) + { + angle = rotation ? (*rotation) : 0; + dst = (SDL_FRect){ + .x=(position->x - camera_rect.x) * scale_mul, + .y=(position->y - camera_rect.y) * scale_mul, + .w=(scale ? scale->x : 1.f) * scale_mul, + .h=(scale ? scale->y : 1.f) * scale_mul, + }; + + dst.x -= dst.w * 0.5f; + dst.y -= dst.h * 0.5f; + + SDL_RenderCopyExF( + renderer, sprite->texture, + &sprite->rect, &dst, (double)angle, + &(SDL_FPoint){.x=dst.w*0.5f, .y=dst.h*0.5f}, + 0x0 + ); + } + } + } +} diff --git a/src/engine/sprite_c.h b/src/engine/sprite_c.h new file mode 100644 index 0000000..f7b9bf1 --- /dev/null +++ b/src/engine/sprite_c.h @@ -0,0 +1,18 @@ +#ifndef sprite_c_h +#define sprite_c_h + +#include +#include + +extern ecsComponentMask sprite_component; + +#define SPRITE_A ((ecsComponentMask)(sprite_component | position_component)) + +typedef struct sprite_t { + SDL_Texture* texture; + SDL_Rect rect; +} sprite_t; + +extern void render_textures_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time); + +#endif /* sprite_c_h */ diff --git a/src/engine/transform_c.c b/src/engine/transform_c.c new file mode 100644 index 0000000..7cceda8 --- /dev/null +++ b/src/engine/transform_c.c @@ -0,0 +1,28 @@ +#include "transform_c.h" + +#include +#include +#include + +ecsComponentMask position_component = 0x0; +ecsComponentMask rotation_component = 0x0; +ecsComponentMask scale_component = 0x0; +ecsComponentMask wrap_screen_component = 0x0; + +void wrap_rotation_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time) +{ + float* rotation; + if(query_update_group(&physics_update, &delta_time)) + { + for(size_t i = 0; i < count; ++i) + { + rotation = ecsGetComponentPtr(entities[i], rotation_component); + + if(*rotation < -360.f) + *rotation += 360.f; + else if(*rotation > 360.f) + *rotation -= 360.f; + } + } +} + diff --git a/src/engine/transform_c.h b/src/engine/transform_c.h new file mode 100644 index 0000000..358bb9d --- /dev/null +++ b/src/engine/transform_c.h @@ -0,0 +1,34 @@ +#ifndef transform_c_h +#define transform_c_h + +#include +#include + +extern ecsComponentMask position_component; +extern ecsComponentMask rotation_component; +extern ecsComponentMask scale_component; + +#define TRANSFORM_A ((ecsComponentMask)(position_component | rotation_component | scale_component)) + +extern void wrap_screen_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time); +extern void wrap_rotation_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time); + +static inline +void transform_point(fvec* r, fvec* position, fvec* scale, float* rotation) +{ + r->x *= scale->x; + r->y *= scale->y; + vrot(r, r, *rotation); + vadd(r, r, position); +} + +static inline +void inverse_transform_point(fvec* r, fvec* position, fvec* scale, float* rotation) +{ + r->x /= scale->x; + r->y /= scale->y; + vrot(r, r, -(*rotation)); + vsub(r, r, position); +} + +#endif /* transform_c_h */ diff --git a/src/engine/ui.c b/src/engine/ui.c new file mode 100644 index 0000000..02e2b00 --- /dev/null +++ b/src/engine/ui.c @@ -0,0 +1,386 @@ +// +// ui.c +// engine +// +// Created by Scott on 21/10/2022. +// + +#include "ui.h" +#include +#include + +SDL_Renderer* uiTarget; +typedef struct ui_selection_t { + uintptr_t item; + short keepSelected; + enum { + UI_SELECT_DEFAULT, + UI_SELECT_TEXT + } selectKind; + union { + struct { + long start, end; + } text_select; + }; +} ui_selection_t; + +ui_selection_t uiSelection; + +uint32_t uiCurrentMouseState; +uint32_t uiLastMouseState; +int uiMouseX, uiMouseY; + +SDL_Rect uiCurrentWindow; +int uiIndentLevel; +int uiSingleLineHeight = 30; +int uiLineSpacing = 5; +int uiWindowPadding = 10; +int uiTotalHeight; +int uiRenderSameLine; + +SDL_Rect uiNextLineRect; + +struct ui_style_t { + TTF_Font* font; + int indentPixels; +} uiStyle; + +void uiAddPixels(int n); + +void uiUpdateMouseState() +{ + uiLastMouseState = uiCurrentMouseState; + uiCurrentMouseState = SDL_GetMouseState(&uiMouseX, &uiMouseY); +} + +int uiCanSelect(void* ptr) +{ + intptr_t item = (intptr_t)ptr; + + return uiSelection.item == item || uiSelection.item == 0; +} + +void uiSelect(void* ptr, short keepActive) +{ + uiSelection.item = (intptr_t)ptr; + uiSelection.keepSelected = keepActive; +} + +short uiIsSelected(void* ptr) +{ + return uiSelection.item == (intptr_t)ptr; +} + +void uiInit(SDL_Renderer* target) +{ + uiTarget = target; + uiSelection.item = 0; + memset(&uiStyle, 0, sizeof(struct ui_style_t)); + uiUpdateMouseState(); + uiStyle.indentPixels = 10; +} + +void uiTerminate() +{ + +} + +void uiSetFont(TTF_Font* font) +{ + uiStyle.font = font; +} + +// check if a mouse button was pressed on this ui frame +static inline +short uiMouseButtonPressed(uint32_t buttonMask) +{ + return + (buttonMask & uiCurrentMouseState) != 0 && // button is down + (buttonMask & uiLastMouseState) == 0; // button was down +} + +// check if a mouse button was released on this ui frame +static inline +short uiMouseButtonReleased(uint32_t buttonMask) +{ + return + (buttonMask & uiLastMouseState) != 0 && // button was down + (buttonMask & uiCurrentMouseState) == 0; // button is up +} + +// check if a mouse button is currently pressed +static inline +short uiMouseButtonDown(uint32_t buttonMask) +{ + return uiCurrentMouseState & buttonMask; +} + +// change the SDL render target +void uiSetTarget(SDL_Renderer* renderer) +{ + uiTarget = renderer; +} + +// begin a new frame +void uiBeginFrame() +{ + if(uiSelection.keepSelected == 0) + uiSelection.item = 0; + uiUpdateMouseState(); +} + +// push any number of lines +void uiSkipLines(int n) +{ + while(n-- >= 0) + { + uiNextLine(); + } +} + +// push a new line +void uiNextLine() +{ + if(uiRenderSameLine > 0) + { + uiRenderSameLine--; + uiNextLineRect.x += uiNextLineRect.w; + } + else + { + uiAddPixels(uiSingleLineHeight + uiLineSpacing); + } +} + +// push the next line down by a number of pixels +void uiAddPixels(int px) +{ + uiTotalHeight += px; + uiNextLineRect = uiCurrentWindow; + uiNextLineRect.h = uiSingleLineHeight; + uiNextLineRect.y += uiTotalHeight; + uiNextLineRect.w -= uiIndentLevel * uiStyle.indentPixels * 2; + uiNextLineRect.x += uiIndentLevel * uiStyle.indentPixels; +} + +// draw the next n elements on the same line +void uiSameLine(int n) +{ + if(n > 1) + { + uiRenderSameLine += n-1; + + uiNextLineRect.w /= n; + } +} + +// start a window fitting within the given rectangle +int uiBeginWindow(SDL_Rect* rect, const char* label, uiWindowState* state) +{ + short isActive = ((*state) & UI_WINDOWSTATE_MINIMIZED) == 0; + uiTotalHeight = 0; + uiIndentLevel = 0; + memcpy(&uiCurrentWindow, rect, sizeof(SDL_Rect)); + + if(isActive) + { + SDL_SetRenderDrawColor(uiTarget, 20, 20, 20, 255); + SDL_RenderFillRect(uiTarget, rect); + } + + uiNextLineRect = uiCurrentWindow; + uiNextLineRect.h = uiSingleLineHeight; + int toggled = uiButton(label); + + uiCurrentWindow.x += uiWindowPadding; + uiCurrentWindow.y += uiWindowPadding; + uiCurrentWindow.w -= uiWindowPadding*2; + uiCurrentWindow.h -= uiWindowPadding*2; + + uiNextLineRect.x = uiCurrentWindow.x; + uiNextLineRect.w = uiCurrentWindow.w; + uiNextLineRect.y = uiCurrentWindow.y + uiTotalHeight; + uiNextLineRect.h = uiSingleLineHeight; + + if(toggled) + { + (*state) ^= UI_WINDOWSTATE_MINIMIZED; + } + + return isActive; +} + +// check if point (x,y) is in r +int uiInArea(SDL_Rect* r, int x, int y) +{ + int x_min = r->x, x_max = r->x + r->w; + int y_min = r->y, y_max = r->y + r->h; + return (x >= x_min && x < x_max && y >= y_min && y < y_max); +} + +void uiDrawText(const char* text, SDL_Rect dstrect, SDL_Color colour) +{ + int height = TTF_FontHeight(uiStyle.font); + double fontToLineHeight = (double)height/(double)uiSingleLineHeight; + + SDL_Rect srcrect = { 0, 0, dstrect.w * fontToLineHeight, dstrect.h * fontToLineHeight }; + SDL_Surface* surface = TTF_RenderText_Solid_Wrapped(uiStyle.font, text, colour, srcrect.w); + + dstrect.w = surface->w / fontToLineHeight; + + if(surface == NULL) + { + SDL_Log("Failed to render text to surface:\n%s", SDL_GetError()); + return; + } + + SDL_Texture* tex = SDL_CreateTextureFromSurface(uiTarget, surface); + SDL_FreeSurface(surface); + + if(tex == NULL) + return; + + SDL_RenderCopy(uiTarget, tex, &srcrect, &dstrect); + + SDL_DestroyTexture(tex); +} + +// draw an interactive slider with a min, max and step +int uiSlider(float* value, float min, float max, float step) +{ + const int SLIDER_WIDTH = 10; + const int LINE_THICKNESS = 5; + + float startValue = *value; + int valueChanged = 0; + float valuePercentage = ((*value)-min) / (max - min); + + SDL_Rect position = uiNextLineRect; + position.x += SLIDER_WIDTH/2; + position.w -= SLIDER_WIDTH; + + SDL_Rect lineRect = { + position.x, position.y + uiSingleLineHeight/2, + position.w, LINE_THICKNESS + }; + + SDL_Color sliderColor = {230, 230, 230, 255}; + + if(uiMouseButtonPressed(SDL_BUTTON_LEFT)) + { + if(uiCanSelect(value) && uiInArea(&position, uiMouseX, uiMouseY)) + { + uiSelect(value, 1); + } + } + else if(uiIsSelected(value) && !uiMouseButtonDown(SDL_BUTTON_LEFT)) + { + uiSelection.keepSelected = 0; + } + + if(uiIsSelected(value)) + { + valuePercentage = (float)(uiMouseX - position.x) / (float)(position.w); + + if(valuePercentage < 0) + valuePercentage = 0; + else if(valuePercentage > 1) + valuePercentage = 1; + + (*value) = valuePercentage * (max - min); + (*value) = roundf((*value) * (1.f/step)) * step + min; + valuePercentage = ((*value) - min) / (max-min); + + + if(*value != startValue) + { + valueChanged = 1; + } + } + + SDL_Rect sliderRect = { + position.x + valuePercentage * position.w - SLIDER_WIDTH/2, position.y, + SLIDER_WIDTH, uiSingleLineHeight + }; + + SDL_SetRenderDrawColor(uiTarget, 100, 100, 100, 255); + SDL_RenderFillRect(uiTarget, &lineRect); + + if(valuePercentage >= 0 && valuePercentage <= 1) + { + SDL_SetRenderDrawColor(uiTarget, sliderColor.r, sliderColor.g, sliderColor.b, sliderColor.a); + SDL_RenderFillRect(uiTarget, &sliderRect); + } + + uiNextLine(); + + return valueChanged; +} + +// draw a clickable button +int uiButton(const char* label) +{ + SDL_Rect position = uiNextLineRect; + short clicked = 0; + + if(uiMouseButtonPressed(SDL_BUTTON_LEFT)) + { + if(uiCanSelect(0) && uiInArea(&position, uiMouseX, uiMouseY)) + { + uiSelect(0, 0); + clicked = 1; + } + } + + SDL_SetRenderDrawColor(uiTarget, 100, 100, 100, 255); + SDL_RenderFillRect(uiTarget, &position); + + uiLabel(label); + + return clicked; +} + +void uiLabel(const char* label) +{ + SDL_Colour white = {255, 255, 255, 255}; + uiDrawText(label, uiNextLineRect, white); + uiNextLine(); +} + +void uiLabelNext(const char* label, float ratio) +{ + int y = uiNextLineRect.y; + uiNextLineRect.w = uiCurrentWindow.w * ratio; + uiLabel(label); + uiTotalHeight -= uiSingleLineHeight + uiLineSpacing; + uiNextLineRect.y = y; + uiNextLineRect.x += uiCurrentWindow.w * ratio; + uiNextLineRect.w = uiCurrentWindow.w * (1-ratio); + uiNextLineRect.h = uiSingleLineHeight; +} + +void uiHeader(const char* label) +{ + uiSubIndent(); + uiAddPixels(uiSingleLineHeight/2); + uiLabel(label); + uiAddIndent(); +} + +void uiSetIndent(int n) +{ + uiIndentLevel = n; + uiIndentLevel = uiIndentLevel >= 0 ? uiIndentLevel : 0; + // force update of next line rect + uiAddPixels(0); +} + +void uiAddIndent() +{ + uiSetIndent(uiIndentLevel + 1); +} + +void uiSubIndent() +{ + uiSetIndent(uiIndentLevel - 1); +} diff --git a/src/engine/ui.h b/src/engine/ui.h new file mode 100644 index 0000000..09fe246 --- /dev/null +++ b/src/engine/ui.h @@ -0,0 +1,44 @@ +// +// ui.h +// engine +// +// Created by Scott on 21/10/2022. +// + +#ifndef ui_h +#define ui_h + +#include +#include +#include + +typedef uint32_t uiWindowState; +#define UI_WINDOWSTATE_ALLOW_MINIMIZE ((uiWindowState)0x1) +#define UI_WINDOWSTATE_MINIMIZED ((uiWindowState)0x2) +#define UI_WINDOWSTATE_DEFAULT (UI_WINDOWSTATE_ALLOW_MINIMIZE) + +extern void uiInit(SDL_Renderer* target); +extern void uiTerminate(); + +extern void uiSetTarget(SDL_Renderer* renderer); + +extern void uiSetFont(TTF_Font* font); + +extern void uiBeginFrame(); + +extern int uiBeginWindow(SDL_Rect* rect, const char* label, uiWindowState* state); + +extern void uiSkipLines(int n); +extern void uiNextLine(); +extern void uiSameLine(int n); + +extern int uiSlider(float* value, float min, float max, float step); +extern int uiButton(const char* label); +extern void uiLabel(const char* label); +extern void uiLabelNext(const char* label, float ratio); +extern void uiHeader(const char* label); +extern void uiAddIndent(); +extern void uiSubIndent(); +extern void uiSetIndent(int n); + +#endif /* ui_h */ diff --git a/src/engine/vec.h b/src/engine/vec.h new file mode 100644 index 0000000..e3a270a --- /dev/null +++ b/src/engine/vec.h @@ -0,0 +1,203 @@ +// +// vec.h +// sim +// +// Created by Scott on 11/10/2022. +// + +#ifndef vec_h +#define vec_h + +#include +#include + +typedef struct fvec { + float x, y; +} fvec; + +static inline void vadd(fvec* r, fvec* a, fvec* b) +{ r->x = a->x + b->x; r->y = a->y + b->y; } + +static inline void vsub(fvec* r, fvec* a, fvec* b) +{ r->x = a->x - b->x; r->y = a->y - b->y; } + +static inline void vmul(fvec* r, fvec* a, fvec* b) +{ r->x = a->x * b->x; r->y = a->y * b->y; } + +static inline void vmulf(fvec* r, fvec* a, float b) +{ r->x = a->x * b; r->y = a->y * b; } + +static inline void vdiv(fvec* r, fvec* a, fvec* b) +{ r->x = a->x / b->x; r->y = a->y / b->y; } + +static inline float vmag(fvec* a) +{ return sqrt(fabsf(a->x*a->x) + fabsf(a->y*a->y)); } + +static inline int veq(fvec* a, fvec* b) +{ return(a->x == b->x && a->y == b->y); } + +static inline void vnor(fvec* r, fvec* a) +{ + float m = vmag(a); + if(m == 0) + { + *r = (fvec){0.f, 0.f}; + } + else + { + r->x = a->x / m; + r->y = a->y / m; + } +} + +static inline void vabs(fvec* r, fvec* a) +{ + r->x = fabsf(a->x); + r->y = fabsf(a->y); +} + +static inline float vdist(fvec* a, fvec* b) +{ + fvec diff; + vsub(&diff, a, b); + vabs(&diff, &diff); + float m = vmag(&diff); + return isnan(m) ? 0 : m; +} + +static inline void vmovetowards(fvec* r, fvec* from, fvec* to, float max_delta) +{ + fvec diff, dir; + + if(max_delta == 0 || isnan(max_delta)) + { + memmove(r, from, sizeof(fvec)); + return; + } + + vsub(&diff, to, from); + + // get the distance between from and to + float diffm = vmag(&diff); + + // if the distance is zero, set return to target vector + if(diffm == 0 || isnan(diffm)) + { + memmove(r, to, sizeof(fvec)); + return; + } + + vmulf(&dir, &diff, 1.f/diffm * max_delta); + + float dirm = vmag(&dir); + + if(dirm >= diffm) + { + memmove(r, to, sizeof(fvec)); + } + else + { + vadd(r, from, &dir); + } +} + +static inline void vlerp(fvec* r, fvec* from, fvec* to, float t) +{ + fvec dir; + vsub(&dir, to, from); + vnor(&dir, &dir); + vmulf(&dir, &dir, t); + vadd(r, from, &dir); +} + +static inline void vmin(fvec* r, fvec* a, float min) +{ + float m = vmag(a); + if(m < min) + { + vmulf(r, a, (1.f/m)*min); + } + else + { + *r = *a; + } +} + +static inline void vmax(fvec* r, fvec* a, float max) +{ + float m = vmag(a); + if(m > max) + vmulf(r, a, (1.f/m)*max); + else + *r = *a; +} + +static inline void vclamp(fvec* r, fvec* a, float min, float max) +{ + float m = vmag(a); + if(m > max) + vmulf(r, a, (1.f/m)*max); + else if(m < min) + vmulf(r, a, (1.f/m)*min); + else + *r = *a; +} + +static inline float vdot(fvec* a, fvec* b) +{ + return(a->x * b->x + a->y * b->y); +} + +static inline float vang(fvec* a, fvec* b) +{ + return atan2f(a->y, a->x) - atan2f(b->y, b->x); +} + +static inline void vrot(fvec* r, fvec* a, float ang) +{ + fvec tmp = *a; + r->x = cosf(ang)*tmp.x - sinf(ang)*tmp.y; + r->y = sinf(ang)*tmp.x + cosf(ang)*tmp.y; +} + +static inline void vperpend(fvec* r, fvec* a) +{ + r->y = a->x; + r->x = -a->y; +} + +static inline void v3cross(float* r, float* a, float* b) +{ + float store[3]; + memcpy(store, r, sizeof(float) * 3); + store[0] = a[1]*b[2] - a[2]*b[1]; + store[1] = a[2]*b[0] - a[0]*b[2]; + store[2] = a[0]*b[1] - a[3]*b[0]; + memcpy(r, store, sizeof(float) * 3); +} + +static inline void vtriple(fvec* r, fvec* a, fvec* b) +{ + float va[3]; + float vb[3]; + float vr[3]; + memcpy(va, a, sizeof(float) * 2); + memcpy(vb, b, sizeof(float) * 2); + memset(vr, 0.f, sizeof(float) * 3); + + va[2] = vb[2] = 0; + + v3cross(vr, va, vb); + v3cross(vr, vr, va); + + r->x = vr[0]; + r->y = vr[1]; +} + +#define VZERO ((fvec){0.f,0.f}) +#define VUP ((fvec){0.f, 1.f}) +#define VDOWN ((fvec){0.f, -1.f}) +#define VLEFT ((fvec){-1.f, 0.f}) +#define VRIGHT ((fvec){1.f, 0.f}) + +#endif /* vec_h */ diff --git a/src/engine/velocity_c.c b/src/engine/velocity_c.c new file mode 100644 index 0000000..d69bfa6 --- /dev/null +++ b/src/engine/velocity_c.c @@ -0,0 +1,86 @@ +#include "velocity_c.h" +#include +#include +#include "components.h" + +ecsComponentMask linear_velocity_component = 0x0; +ecsComponentMask angular_velocity_component = 0x0; + +ecsComponentMask linear_drag_component = 0x0; +ecsComponentMask angular_drag_component = 0x0; + + +void linear_velocity_update_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time) +{ + fvec* position; + fvec* velocity; + float* drag; + shape_t* collision_shape; + fvec actual_velocity; + + if(query_update_group(&physics_update, &delta_time)) + { + for(size_t i = 0; i < count; ++i) + { + velocity = ecsGetComponentPtr(entities[i], linear_velocity_component); + drag = ecsGetComponentPtr(entities[i], linear_drag_component); + collision_shape = ecsGetComponentPtr(entities[i], shape_component); + + // apply velocity + memcpy(&actual_velocity, velocity, sizeof(fvec)); + vmulf(&actual_velocity, &actual_velocity, delta_time); + if(collision_shape == NULL) + { + position = ecsGetComponentPtr(entities[i], position_component); + vadd(position, position, &actual_velocity); + } + else + { + shape_sweep(entities[i], actual_velocity, 1); + } + + // apply drag + if(drag) + { + vmovetowards(velocity, velocity, &VZERO, (*drag) * delta_time); + } + } + } +} + +void angular_velocity_update_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time) +{ + float* rotation; + float* velocity; + float* drag; + float max_delta; + + if(query_update_group(&physics_update, &delta_time)) + { + for(size_t i = 0; i < count; ++i) + { + rotation = ecsGetComponentPtr(entities[i], rotation_component); + velocity = ecsGetComponentPtr(entities[i], angular_velocity_component); + drag = ecsGetComponentPtr(entities[i], angular_drag_component); + + // apply current velocity + *rotation += (*velocity) * delta_time; + + // apply drag + if(drag) + { + max_delta = *drag * delta_time; + if(*velocity != 0.f) + { + if(*velocity > max_delta) + *velocity -= max_delta; + else if(*velocity < -max_delta) + *velocity += max_delta; + else + *velocity = 0.f; + } + } + } + } +} + diff --git a/src/engine/velocity_c.h b/src/engine/velocity_c.h new file mode 100644 index 0000000..a801b76 --- /dev/null +++ b/src/engine/velocity_c.h @@ -0,0 +1,16 @@ +#ifndef velocity_c_h +#define velocity_c_h + +#include + +extern ecsComponentMask linear_velocity_component; +extern ecsComponentMask angular_velocity_component; +extern ecsComponentMask linear_drag_component; +extern ecsComponentMask angular_drag_component; + +#define PHYSICS_A ((ecsComponentMask)(linear_velocity_component | linear_drag_component | angular_velocity_component | angular_drag_component| TRANSFORM_A)) + +extern void linear_velocity_update_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time); +extern void angular_velocity_update_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time); + +#endif /* velocity_c_h */ diff --git a/src/engine/view_c.c b/src/engine/view_c.c new file mode 100644 index 0000000..de831bf --- /dev/null +++ b/src/engine/view_c.c @@ -0,0 +1,32 @@ +#include "view_c.h" +#include "engine.h" + +double last_aspect_ratio; + +ecsComponentMask view_component = 0x0; + +void get_view_rect(view_t const* view, SDL_Rect* rect) +{ + rect->x = -(view->width / 2); + rect->y = -(view->height / 2); + rect->w = view->width; + rect->h = view->height; +} + +void system_update_views(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time) +{ + view_t* view; + + int width, height; double aspect_ratio; + + SDL_GetRendererOutputSize(renderer, &width, &height); + aspect_ratio = (double)height/width; + + for(size_t i = 0; i < count; ++i) + { + view = ecsGetComponentPtr(entities[i], view_component); + view->height = view->width * aspect_ratio; + } + + ecsDisableSystem(&system_update_views); +} diff --git a/src/engine/view_c.h b/src/engine/view_c.h new file mode 100644 index 0000000..1d0fbd4 --- /dev/null +++ b/src/engine/view_c.h @@ -0,0 +1,19 @@ +#ifndef view_c_h +#define view_c_h + +#include +#include +#include "engine.h" + +extern ecsComponentMask view_component; + +typedef struct view_t { + unsigned width; + unsigned height; +} view_t; + +void get_view_rect(view_t const* view, SDL_Rect* rect); + +extern void system_update_views(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time); + +#endif /* view_c_h */ diff --git a/src/game/assets.c b/src/game/assets.c new file mode 100644 index 0000000..a4961f1 --- /dev/null +++ b/src/game/assets.c @@ -0,0 +1,7 @@ +#include "assets.h" + +asset_handle_t fnt_inter = 0x0; +asset_handle_t fnt_ibmplex = 0x0; +asset_handle_t spr_player = 0x0; +asset_handle_t spr_bullet = 0x0; +asset_handle_t spr_ufo = 0x0; diff --git a/src/game/assets.h b/src/game/assets.h new file mode 100644 index 0000000..3bc8390 --- /dev/null +++ b/src/game/assets.h @@ -0,0 +1,12 @@ +#ifndef assets_h +#define assets_h + +#include + +extern asset_handle_t fnt_inter; +extern asset_handle_t fnt_ibmplex; +extern asset_handle_t spr_player; +extern asset_handle_t spr_bullet; +extern asset_handle_t spr_ufo; + +#endif /* !assets_h */ diff --git a/src/game/bullet.h b/src/game/bullet.h new file mode 100644 index 0000000..5988704 --- /dev/null +++ b/src/game/bullet.h @@ -0,0 +1,53 @@ +#ifndef bullet_h +#define bullet_h + +#include "components.h" +#include "assets.h" +#include +#include +#include "player.h" +#include "bullet_c.h" + +#define BULLET_A (PHYSICS_A | SPRITE_A | border_interaction_component | bullet_component | shape_component) + +static inline +ecsEntityId spawn_bullet(fvec start_position, fvec start_velocity, ecsEntityId source, int power) +{ + ecsEntityId bullet_id = ecsCreateEntity(BULLET_A); + assert(bullet_id != noentity); + { + fvec* position = ecsGetComponentPtr(bullet_id, position_component); + memcpy(position, &start_position, sizeof(fvec)); + + fvec* velocity = ecsGetComponentPtr(bullet_id, linear_velocity_component); + memcpy(velocity, &start_velocity, sizeof(fvec)); + + fvec* scale = ecsGetComponentPtr(bullet_id, scale_component); + *scale = (fvec){0.5f,0.5f}; + + bullet_t* bullet = ecsGetComponentPtr(bullet_id, bullet_component); + bullet->source = source; + bullet->power = power; + + float* rotation = ecsGetComponentPtr(bullet_id, rotation_component); + *rotation = vang(&VUP, &start_velocity); + + sprite_t* sprite = ecsGetComponentPtr(bullet_id, sprite_component); + SDL_Texture* spr_bullet_ptr = adb_get_asset(spr_bullet); + sprite->texture = spr_bullet_ptr; + SDL_QueryTexture(spr_bullet_ptr, NULL, NULL, &sprite->rect.w, &sprite->rect.h); + + shape_t* shape = ecsGetComponentPtr(bullet_id, shape_component); + shape->type = SHAPE_CIRCLE; + shape->circle.radius = 10.f; + + border_interaction_t* border_interaction = ecsGetComponentPtr(bullet_id, border_interaction_component); + border_interaction->type = BORDER_DESTROY; + border_interaction->margin = (fvec){0,0};//(fvec){scale->x * sprite->rect.w, scale->y * sprite->rect.h}; + + add_to_collision_world(bullet_id); + } + return bullet_id; +} + +#endif /* bullet_h */ diff --git a/src/game/bullet_c.c b/src/game/bullet_c.c new file mode 100644 index 0000000..c7adab9 --- /dev/null +++ b/src/game/bullet_c.c @@ -0,0 +1,3 @@ +#include "bullet_c.h" + +ecsComponentMask bullet_component = 0x0; diff --git a/src/game/bullet_c.h b/src/game/bullet_c.h new file mode 100644 index 0000000..0fd20b2 --- /dev/null +++ b/src/game/bullet_c.h @@ -0,0 +1,13 @@ +#ifndef bullet_c_h +#define bullet_c_h + +#include + +extern ecsComponentMask bullet_component; + +typedef struct bullet_t { + ecsEntityId source; + int power; +} bullet_t; + +#endif /* bullet_c_h */ diff --git a/src/game/debug_ui.c b/src/game/debug_ui.c new file mode 100644 index 0000000..2aafcf3 --- /dev/null +++ b/src/game/debug_ui.c @@ -0,0 +1,36 @@ +#include "debug_ui.h" + +#include +#include +#include + +void show_framerate_ui_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time) +{ + static uiWindowState window_state = UI_WINDOWSTATE_MINIMIZED; + + if(query_update_group(&frame_update, &delta_time)) + { + char formatted[100]; + + uiBeginFrame(); + SDL_Rect rect = {0, 0, 300, 300}; + + if(uiBeginWindow(&rect, "DEBUG INFO", &window_state)) + { + if(sprintf(formatted, "delta_time: %f", delta_time)) + uiLabel(formatted); + + if(sprintf(formatted, "fps: %f", 1.f/delta_time)) + uiLabel(formatted); + + if(sprintf(formatted, "ax_hor: %d", input_axis(SDL_SCANCODE_A, SDL_SCANCODE_D))) + uiLabel(formatted); + + if(sprintf(formatted, "ax_ver: %d", input_axis(SDL_SCANCODE_S, SDL_SCANCODE_W))) + uiLabel(formatted); + + if(sprintf(formatted, "count: %zu", count)) + uiLabel(formatted); + } + } +} diff --git a/src/game/debug_ui.h b/src/game/debug_ui.h new file mode 100644 index 0000000..39f9e25 --- /dev/null +++ b/src/game/debug_ui.h @@ -0,0 +1,8 @@ +#ifndef debug_ui_h +#define debug_ui_h + +#include + +extern void show_framerate_ui_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time); + +#endif /* debug_ui_h */ diff --git a/src/game/enemies.h b/src/game/enemies.h new file mode 100644 index 0000000..bdc1783 --- /dev/null +++ b/src/game/enemies.h @@ -0,0 +1,14 @@ +#ifndef enemies_h +#define enemies_h + +#include +#include +#include "enemy_c.h" +#include "player_c.h" +#include "vec.h" + +#define NMI_A PHYSICS_A | player_component | enemy_component; + +ecsEntityId spawn_enemy(fvec position, float rotation, ecsEntityId target); + +#endif /* enemies_h */ diff --git a/src/game/enemy_c.c b/src/game/enemy_c.c new file mode 100644 index 0000000..10cb936 --- /dev/null +++ b/src/game/enemy_c.c @@ -0,0 +1,4 @@ +#include "enemy_c.h" +#include "ecs.h" + +ecsComponentMask enemy_component = 0x0; diff --git a/src/game/enemy_c.h b/src/game/enemy_c.h new file mode 100644 index 0000000..9185caa --- /dev/null +++ b/src/game/enemy_c.h @@ -0,0 +1,12 @@ +#ifndef enemy_c_h +#define enemy_c_h + +#include "ecs.h" + +typedef struct enemy_t { + ecsEntityId target; +} enemy_t; + +extern ecsComponentMask enemy_component; + +#endif /* enemy_c_h */ diff --git a/src/game/game.c b/src/game/game.c new file mode 100644 index 0000000..99d5f9a --- /dev/null +++ b/src/game/game.c @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bullet.h" +#include "assets.h" +#include "player.h" +#include "debug_ui.h" + +void game_config(engine_init_t* config) +{ + config->window_name = "Asteroids"; + config->window_init_flags = SDL_WINDOW_FULLSCREEN_DESKTOP; + config->window_width = 1200; + config->window_height = 800; + set_update_group_target_framerate(&frame_update, 60); + set_update_group_target_framerate(&frame_update, 120); +} + +void game_load_assets() +{ + fnt_inter = adb_load_asset("Inter-Regular.otf"); + fnt_ibmplex = adb_load_asset("IBMPlexMono-Regular.otf"); + spr_player = adb_load_asset("ship-player.png"); + spr_bullet = adb_load_asset("orb-pink-red.png"); + spr_ufo = adb_load_asset("ufo.png"); + + uiSetFont(adb_get_asset(fnt_ibmplex)); +} + +void game_load_main_scene() +{ + int w,h; + SDL_GetRendererOutputSize(renderer, &w, &h); + + // create player entity + ecsEntityId cam = create_default_camera(); + spawn_player((fvec){0,0}); + fvec* campos = ecsGetComponentPtr(cam, position_component); +} + +void game_init() +{ + player_component = ecsRegisterComponent(player_t); + input_component = ecsRegisterComponent(input_t); + bullet_component = ecsRegisterComponent(bullet_t); + + // the 0-499 block is for pre-render updates + ecsEnableSystem(&input_update_s, input_component, ECS_QUERY_ALL, MAX_THREADS, 250); + ecsEnableSystem(&player_update_s, PLAYER_A, ECS_QUERY_ALL, MAX_THREADS, 255); + +#if defined(DEBUG) + ecsEnableSystem(&show_framerate_ui_s, ~noentity, ECS_QUERY_ANY, 0, 550); +#endif + + game_load_assets(); + game_load_main_scene(); +} + +void game_quit() +{} diff --git a/src/game/input_c.c b/src/game/input_c.c new file mode 100644 index 0000000..3c2a216 --- /dev/null +++ b/src/game/input_c.c @@ -0,0 +1,25 @@ +#include "input_c.h" + +#include +#include +#include + +ecsComponentMask input_component = 0x0; + +void input_update_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time) +{ + input_t* input; + if(query_update_group(&frame_update, &delta_time)) + { + for(size_t i = 0; i < count; ++i) + { + input = ecsGetComponentPtr(entities[i], input_component); + input->axis_horizontal = input_axis(SDL_SCANCODE_A, SDL_SCANCODE_D); + input->axis_vertical = input_axis(SDL_SCANCODE_S, SDL_SCANCODE_W); + if(input_button(SDL_SCANCODE_J)) + input->button_fire += 1; + else + input->button_fire = 0; + } + } +} diff --git a/src/game/input_c.h b/src/game/input_c.h new file mode 100644 index 0000000..1563ab6 --- /dev/null +++ b/src/game/input_c.h @@ -0,0 +1,17 @@ +#ifndef input_c_h +#define input_c_h + +#include +#include + +extern ecsComponentMask input_component; + +typedef struct input_t { + int8_t axis_horizontal; + int8_t axis_vertical; + uint8_t button_fire; +} input_t; + +extern void input_update_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time); + +#endif /* input_c_h */ diff --git a/src/game/player.c b/src/game/player.c new file mode 100644 index 0000000..7ec9d1d --- /dev/null +++ b/src/game/player.c @@ -0,0 +1,55 @@ +#include "player.h" + +#include +#include +#include +#include "assets.h" + +void player_on_overlap(ecsEntityId self, ecsEntityId other) +{ + E_LOG("%zu, %zu", self, other); +} + +ecsEntityId spawn_player(fvec start_position) +{ + ecsEntityId player_id = ecsCreateEntity(PLAYER_A); + + if(player_id) + { + fvec* position = ecsGetComponentPtr(player_id, position_component); + memcpy(position, &start_position, sizeof(fvec)); + + sprite_t* sprite = ecsGetComponentPtr(player_id, sprite_component); + SDL_Texture* spr_player_ptr = adb_get_asset(spr_player); + sprite->texture = spr_player_ptr; + SDL_QueryTexture(spr_player_ptr, NULL, NULL, &sprite->rect.w, &sprite->rect.h); + + fvec* scale = ecsGetComponentPtr(player_id, scale_component); + scale->x = 1.f; + scale->y = 1.f; + + player_t* player = ecsGetComponentPtr(player_id, player_component); + player->acceleration = 10.f; + player->speed = 10.f; + player->rotation_speed = 100.f; + player->rotation_acceleration = 120.f; + + float* linear_drag = ecsGetComponentPtr(player_id, linear_drag_component); + *linear_drag = 0.5f; + + float* angular_drag = ecsGetComponentPtr(player_id, angular_drag_component); + *angular_drag = 1.f; + + shape_t* collider = ecsGetComponentPtr(player_id, shape_component); + collider->type = SHAPE_CIRCLE; + collider->circle.radius = 100; + collider->on_overlap = &player_on_overlap; + + border_interaction_t* border_interaction = ecsGetComponentPtr(player_id, border_interaction_component); + border_interaction->type = BORDER_WRAP; + border_interaction->margin = (fvec){scale->x, scale->y}; + + add_to_collision_world(player_id); + } + return player_id; +} diff --git a/src/game/player.h b/src/game/player.h new file mode 100644 index 0000000..b8482b6 --- /dev/null +++ b/src/game/player.h @@ -0,0 +1,17 @@ +#ifndef player_h +#define player_h + +#include +#include +#include + +#include "player_c.h" +#include "input_c.h" + +#define PLAYER_A (ecsComponentMask)(PHYSICS_A | SPRITE_A | border_interaction_component | player_component | input_component | shape_component) + +extern void player_on_overlap(ecsEntityId self, ecsEntityId other); + +extern ecsEntityId spawn_player(fvec start_position); + +#endif /* player_h */ diff --git a/src/game/player_c.c b/src/game/player_c.c new file mode 100644 index 0000000..00d3211 --- /dev/null +++ b/src/game/player_c.c @@ -0,0 +1,67 @@ +#include "player_c.h" + +#include +#include +#include +#include + +#include "input_c.h" +#include "transform_c.h" +#include "velocity_c.h" +#include "bullet.h" + +ecsComponentMask player_component = 0x0; + +void player_update_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time) +{ + ecsEntityId id; + + player_t* player; + input_t* input; + fvec* lin_velocity; + float* ang_velocity; + float* rotation; + + if(query_update_group(&frame_update, &delta_time)) + { + for(size_t i = 0; i < count; ++i) + { + id = entities[i]; + player = ecsGetComponentPtr(id, player_component); + input = ecsGetComponentPtr(id, input_component); + lin_velocity = ecsGetComponentPtr(id, linear_velocity_component); + ang_velocity = ecsGetComponentPtr(id, angular_velocity_component); + rotation = ecsGetComponentPtr(id, rotation_component); + + *ang_velocity = fmovetowards(*ang_velocity, player->rotation_speed * input->axis_horizontal, player->rotation_acceleration * delta_time); + + if(input->axis_vertical > 0) + { + fvec input_dir = { + .x = 0, + .y = -(float)(input->axis_vertical) + }; + + vnor(&input_dir, &input_dir); + vrot(&input_dir, &input_dir, *rotation * 0.017); + vmulf(&input_dir, &input_dir, player->speed); + vmovetowards(lin_velocity, lin_velocity, &input_dir, player->acceleration * delta_time); + } + else if(input->axis_vertical > 0) + { + vmovetowards(lin_velocity, lin_velocity, &VZERO, player->acceleration * delta_time * abs(input->axis_vertical)); + } + + if(input->button_fire == 1) + { + fvec* position = ecsGetComponentPtr(id, position_component); + + fvec velocity; + vrot(&velocity, &VDOWN, *rotation * 0.017); + vmulf(&velocity, &velocity, 50.f); + + spawn_bullet(*position, velocity, id, 1); + } + } + } +} diff --git a/src/game/player_c.h b/src/game/player_c.h new file mode 100644 index 0000000..c473dca --- /dev/null +++ b/src/game/player_c.h @@ -0,0 +1,18 @@ +#ifndef player_c_h +#define player_c_h + +#include + +extern ecsComponentMask player_component; + +typedef struct player_t { + float speed; + float rotation_speed; + float fire_timer; + float acceleration; + float rotation_acceleration; +} player_t; + +extern void player_update_s(ecsEntityId* entities, ecsComponentMask* components, size_t count, float delta_time); + +#endif /* player_c_h */