diff --git a/include/AssetManager.h b/include/AssetManager.h index a21bfaa..cfe7ceb 100644 --- a/include/AssetManager.h +++ b/include/AssetManager.h @@ -22,14 +22,38 @@ typedef enum { } AssetType; typedef struct asset_manager AssetManager; +/** + * Attempts to allocate an AssetManager object on the heap. + * + * Returns the pointer to the object when it succeeds, or NULL if it fails. + */ AssetManager *create_asset_manager(); + +/** + * Attempts to load an asset to the manager from a file path. + * + * Returns the AssetID on success or an INVALID_ID in the case of failure. + */ AssetID load_asset_to_manager(AssetManager *am, const char *path, AssetType type, Renderer *renderer); + +/** + * Removes an asset from the manager using its ID. + * + * Does nothing if the ID is not valid. + */ void remove_asset_from_manager(AssetManager *am, AssetID *id_ptr); + SDL_Texture *get_texture(AssetManager *am, AssetID id); TTF_Font *get_font(AssetManager *am, AssetID id); Mix_Chunk *get_audio_chunk(AssetManager *am, AssetID id); Mix_Music *get_music(AssetManager *am, AssetID id); + +/** + * Frees the memory allocated to the AssetManager object and performs the + * necessary cleanup to free all memory that were allocated to the loaded + * assets. + */ void destroy_asset_manager(AssetManager **am); #ifdef __cplusplus diff --git a/include/GameEngine.h b/include/GameEngine.h index 70e791d..32f738d 100644 --- a/include/GameEngine.h +++ b/include/GameEngine.h @@ -4,6 +4,7 @@ #include "AssetManager.h" #include "ObjectStore.h" #include "SDL_pixels.h" +#include "SDL_rect.h" #include "Window.h" #include "reusable_ids.h" #include @@ -13,20 +14,68 @@ extern "C" { #endif // !__cplusplus +// GENERAL ENGINE API + +/** + * Initialises the game engine. Returns true on success and false on failure. + * + * The audio_tracks parameter determines how many audio tracks to be allocated. + * Pass 0 if you want to allocate the default number. + */ bool init_engine(const char *game_name, WindowSize window_size, SDL_Color window_background_color, bool full_screen, uint8_t audio_tracks); + bool game_running(); -WindowSize get_window_defined_size(); -WindowSize get_window_drawable_size(); +void close_engine(); +// END GENERAL ENGINE API + +// ASSET MANAGEMENT API ObjectID add_object(void *object, EventHandlerPtr eh, UpdateFuncPtr uf, RenderFuncPtr rf); void remove_object(ObjectID *id); +// END ASSET MANAGEMENT API + +// ASSET MANAGEMENT API AssetID load_asset(const char *path, AssetType type); +void close_asset(AssetID *id); +// END ASSET MANAGEMENT API + +// RENDERING API + +/** + * Returns the user-defined window size or the screen size when in full screen. + */ +WindowSize get_window_defined_size(); + +/** + * Returns the actual window size which can be different from the user-defined + * size or the screen size if the window is on a display that supports high DPI. + */ +WindowSize get_window_real_size(); + +/** + * Returns the valid drawing area which can be a portion of the screen when + * running in full screen mode and the width to height ratio of the user-defined + * window size is different from the width to height ratio of the screen. + */ +WindowDrawingArea get_window_drawing_area(); + +void fill_rectangle(float x, float y, float w, float h, SDL_Color color); + +/** + * Ensures that a rendered rectangle is clipped to the drawing area when it is + * smaller than the actual size of the window. + */ +void clip_render_rect_to_drawing_area(SDL_FRect *rect); + void render_texture_asset(AssetID id, float x, float y, float w, float h); void render_text_with_font_asset(AssetID id, const char *text, float x, float y); void set_font_asset_size(AssetID id, uint32_t size); +// END RENDERING API + +// AUDIO API int8_t play_audio_asset(AssetID id, int16_t loops, bool fade_in, uint32_t fade_length_in_ms); void pause_audio_asset_on_track(AssetID id, uint8_t track); @@ -56,11 +105,13 @@ void stop_playing_music(); void fade_out_playing_music(uint32_t fade_length_in_ms); void set_playing_music_volume(int8_t volume); bool is_music_playing(); -void close_asset(AssetID *id); +// END AUDIO API + +// GAME LOOP API void handle_events(); void update(); void render(); -void close_engine(); +// END GAME LOOP API #ifdef __cplusplus } diff --git a/include/ObjectStore.h b/include/ObjectStore.h index b566bcc..25466bd 100644 --- a/include/ObjectStore.h +++ b/include/ObjectStore.h @@ -17,13 +17,36 @@ typedef void (*RenderFuncPtr)(void *object); typedef struct object_store ObjectStore; +/** + * Attempts to allocate an ObjectStore object on the heap. + * + * Returns the pointer to the object when it succeeds, or NULL if it fails. + */ ObjectStore *create_object_store(); + +/** + * Attempts to add an object to the store. + * + * Returns the ObjectID on success or an INVALID_ID in the case of failure. + */ ObjectID add_object_to_store(ObjectStore *os, void *object, EventHandlerPtr eh, UpdateFuncPtr uf, RenderFuncPtr rf); + +/** + * Removes an object from the store using its ID. + * + * Does nothing if the ID is not valid. + */ void remove_object_from_store(ObjectStore *os, ObjectID *id_ptr); + void handle_all_objects_events(ObjectStore *os, const SDL_Event *event); void update_all_objects(ObjectStore *os, float delta); void render_all_objects(ObjectStore *os); + +/** + * Frees the memory allocated to the ObjectStore object and performs the + * necessary cleanup. + */ void destroy_object_store(ObjectStore **os); #ifdef DEBUG_ENGINE diff --git a/include/Renderer.h b/include/Renderer.h index 9c7e041..d858a00 100644 --- a/include/Renderer.h +++ b/include/Renderer.h @@ -14,16 +14,41 @@ extern "C" { typedef struct renderer Renderer; -Renderer *create_renderer(); -bool init_renderer(Renderer *renderer, SDL_Window *window); -void get_drawable_size(Renderer *renderer, int32_t *w, int32_t *h); +/** + * Attempts to allocate a Renderer object on the heap. + * + * Returns the pointer to the object when it succeeds, or NULL if it fails. + */ +Renderer *create_renderer(SDL_Window *window); + +/** + * Returns the valid drawable area that the renderer can use. + * + * Useful when rendering on screens that support high DPI since the drawable + * area is usually bigger than the user-defined size. + */ +void get_render_output_size(Renderer *renderer, int32_t *w, int32_t *h); + +/** + * Attempts to load an SDL_Texture from a file path. + * + * Returns a pointer to the object if it succeeds or NULL when it fails. + */ SDL_Texture *load_texture(const Renderer *renderer, const char *path); + void render_clear(const Renderer *renderer, SDL_Color color); +void fill_rect(const Renderer *renderer, SDL_Color color, float x, float y, + float w, float h); void render_surface(const Renderer *renderer, SDL_Surface *surface, float x, float y, float w, float h); void render_texture(const Renderer *renderer, SDL_Texture *texture, float x, float y, float w, float h); void swap_buffers(const Renderer *renderer); + +/** + * Frees the memory allocated to the Renderer object and performs the + * necessary cleanup. + */ void destroy_renderer(Renderer **renderer); #ifdef __cplusplus diff --git a/include/Window.h b/include/Window.h index ddc1319..7817155 100644 --- a/include/Window.h +++ b/include/Window.h @@ -16,12 +16,27 @@ typedef struct window_size { int32_t height; } WindowSize; +typedef struct drawing_area { + float x; + float y; + float width; + float height; +} WindowDrawingArea; + typedef struct window { + // We need to store the defined size because the coordinates of the mouse + // cursor are calculated according to it. In the case of displays that support + // high DPI, this creates a problem, because the mouse coordinates retrieved + // from an SDL_Event won't work with the actual window size. By storing the + // defined size of the window, this allows us to convert the mouse coordinates + // to a ratio of the defined size, then multiply that ratio by the real size. WindowSize defined_size; - WindowSize drawable_size; + WindowSize real_size; + WindowDrawingArea drawing_area; Renderer *renderer; SDL_Window *SDL_base_window; SDL_Color background_color; + bool full_screen; } Window; bool init_window(Window *wnd, const char *title, WindowSize size, diff --git a/src/AssetManager.c b/src/AssetManager.c index 0a9290a..cff6b47 100644 --- a/src/AssetManager.c +++ b/src/AssetManager.c @@ -46,6 +46,10 @@ struct asset_manager { }; size_t hash_path(const char *path) { + /** + * Converts a string into a numerical index using a simple hashing algorithm + */ + size_t i = 0; for (size_t j = 0; path[j]; ++j) { @@ -125,6 +129,11 @@ void remove_id_node(IDNodeList *node_list, AssetID id) { AssetID get_asset_id_from_id_node(AssetManager *am, IDNodeList *node_list, const char *path) { + /** + * Checks to see if an asset with the same path exists and returns the ID of + * that asset if it exists, or an INVALID_ID otherwise + */ + if (node_list->node_count == 0) { return INVALID_ID; } @@ -184,9 +193,14 @@ AssetID load_asset_to_manager(AssetManager *am, const char *path, } ++(am->user_counter[existing_id]); + return existing_id; } + if (am->count + 1 == MAX_ASSET_COUNT && am->deleted_count == 0) { + return INVALID_ID; + } + Asset asset = {}; bool loaded = true; @@ -363,7 +377,14 @@ void destroy_asset_manager(AssetManager **am) { return; } - for (size_t i = 0; i < (*am)->count; ++i) { + // TODO (Abdelrahman): Figure out if there is a better way to remove all of + // the existing assets without having to loop for the maximum number of + // assets allowed. + for (size_t i = 0; i < MAX_ASSET_COUNT; ++i) { + if (!(*am)->assets[i].path) { + continue; + } + (*am)->user_counter[i] = 0; size_t hash_index = hash_path((*am)->assets[i].path); diff --git a/src/GameEngine.c b/src/GameEngine.c index 225196c..c503ebc 100644 --- a/src/GameEngine.c +++ b/src/GameEngine.c @@ -18,6 +18,7 @@ #include "constants.h" #include "engine_build_config.h" #include "reusable_ids.h" +#include #include #include #include @@ -130,10 +131,32 @@ bool init_engine(const char *game_name, WindowSize window_size, bool game_running() { return GameEngine.running; } -WindowSize get_window_defined_size() { return GameEngine.window.defined_size; } +void close_engine() { + destroy_asset_manager(&(GameEngine.asset_manager)); -WindowSize get_window_drawable_size() { - return GameEngine.window.drawable_size; + destroy_object_store(&(GameEngine.object_store)); + + destroy_window(&(GameEngine.window)); + + GameEngine.last_time = 0.0f; + + GameEngine.delta = 0.0f; + + GameEngine.running = false; + + Mix_CloseAudio(); + + Mix_Quit(); + + TTF_Quit(); + + IMG_Quit(); + + SDL_Quit(); + +#ifdef DEBUG_ENGINE + printf("Game closed successfully\n"); +#endif /* DEBUG_ENGINE */ } ObjectID add_object(void *object, EventHandlerPtr event_handler, @@ -162,6 +185,62 @@ AssetID load_asset(const char *path, AssetType type) { NULL); } +void close_asset(AssetID *id) { + if (*id < 0) { + return; + } + + remove_asset_from_manager(GameEngine.asset_manager, id); +} + +WindowSize get_window_defined_size() { return GameEngine.window.defined_size; } + +WindowSize get_window_real_size() { return GameEngine.window.real_size; } + +WindowDrawingArea get_window_drawing_area() { + return GameEngine.window.drawing_area; +} + +void fill_rectangle(float x, float y, float w, float h, SDL_Color color) { + fill_rect(GameEngine.window.renderer, color, x, y, w, h); +} + +void clip_render_rect_to_drawing_area(SDL_FRect *rect) { + WindowDrawingArea area = get_window_drawing_area(); + + // If the rectangle is completely out of bounds, set its width and height to 0 + if (rect->x > area.x + area.width || rect->x + rect->w < area.x || + rect->y > area.y + area.height || rect->y + rect->h < area.y) { + rect->w = 0.0f; + rect->h = 0.0f; + return; + } + + float x_diff = 0.0f; + float y_diff = 0.0f; + + // If the rectangle is partially out of the horizontal bounds of the drawing + // area, calculate how much of its width will be clipped + if (rect->x < area.x) { + x_diff = area.x - rect->x; + rect->x += x_diff; + } else if (rect->x + rect->w > area.x + area.width) { + x_diff = (rect->x + rect->w) - (area.x + area.width); + } + + // If the rectangle is partially out of the vertical bounds of the drawing + // area, calculate how much of its height will be clipped + if (rect->y < area.y) { + y_diff = area.y - rect->y; + rect->y += y_diff; + } else if (rect->y + rect->h > area.y + area.height) { + y_diff = (rect->y + rect->h) - (area.y + area.height); + } + + rect->w -= x_diff; + rect->h -= y_diff; +} + void render_texture_asset(AssetID id, float x, float y, float w, float h) { if (id < 0) { return; @@ -529,14 +608,6 @@ void set_playing_music_volume(int8_t volume) { Mix_VolumeMusic(volume); } bool is_music_playing() { return Mix_PlayingMusic(); } -void close_asset(AssetID *id) { - if (*id < 0) { - return; - } - - remove_asset_from_manager(GameEngine.asset_manager, id); -} - void handle_events() { if (!(GameEngine.running)) { return; @@ -586,31 +657,3 @@ void render() { swap_render_buffers(&(GameEngine.window)); } - -void close_engine() { - destroy_asset_manager(&(GameEngine.asset_manager)); - - destroy_object_store(&(GameEngine.object_store)); - - destroy_window(&(GameEngine.window)); - - GameEngine.last_time = 0.0f; - - GameEngine.delta = 0.0f; - - GameEngine.running = false; - - Mix_CloseAudio(); - - Mix_Quit(); - - TTF_Quit(); - - IMG_Quit(); - - SDL_Quit(); - -#ifdef DEBUG_ENGINE - printf("Game closed successfully\n"); -#endif /* DEBUG_ENGINE */ -} diff --git a/src/ObjectStore.c b/src/ObjectStore.c index c78d9f2..d207972 100644 --- a/src/ObjectStore.c +++ b/src/ObjectStore.c @@ -41,9 +41,22 @@ ObjectStore *create_object_store() { return os; } +bool object_exists(ObjectStore *os, void *object) { + // This is a crude implementation for checking that an object has already + // been added. + // TODO (Abdelrahman): Figure out if there is a better way of doing this + for (size_t i = 0; i < MAX_OBJ_COUNT; ++i) { + if (object == os->object_ptrs[i]) { + return true; + } + } + + return false; +} + ObjectID add_object_to_store(ObjectStore *os, void *object, EventHandlerPtr eh, UpdateFuncPtr uf, RenderFuncPtr rf) { - if (!os || !object || + if (!os || !object || object_exists(os, object) || (os->count + 1 == MAX_OBJ_COUNT && os->deleted_count == 0)) { #ifdef DEBUG_ENGINE printf("Failed to add object\n"); @@ -52,14 +65,6 @@ ObjectID add_object_to_store(ObjectStore *os, void *object, EventHandlerPtr eh, return INVALID_ID; } - // TODO (Abdelrahman): Need to figure out a way to check that the object - // hasn't already been added to the store. If it has been added before, the - // function should return -1 indicating an error to ensure that there aren't - // two references to the same object without the user being aware that they - // refer to the same thing. If the user intentionally copies the ID of one - // object, then it would be their responsibility to make sure they don't - // delete the object using one of the IDs, then try to use it with the other. - ObjectID id; if (os->deleted_count > 0) { @@ -141,7 +146,10 @@ void destroy_object_store(ObjectStore **os) { return; } - for (size_t i = 0; i < (*os)->count; ++i) { + // TODO (Abdelrahman): Figure out if there is a better way to remove all of + // the existing objects without having to loop for the maximum number of + // objects allowed. + for (size_t i = 0; i < MAX_OBJ_COUNT; ++i) { (*os)->object_ptrs[i] = NULL; (*os)->event_handlers[i] = NULL; (*os)->update_funcs[i] = NULL; diff --git a/src/Renderer.c b/src/Renderer.c index 19b64c8..37e7574 100644 --- a/src/Renderer.c +++ b/src/Renderer.c @@ -15,24 +15,24 @@ struct renderer { SDL_Renderer *SDL_base_renderer; }; -Renderer *create_renderer() { return (Renderer *)malloc(sizeof(Renderer)); } - -bool init_renderer(Renderer *renderer, SDL_Window *window) { - if (!renderer || !window) { - return false; +Renderer *create_renderer(SDL_Window *window) { + Renderer *renderer = (Renderer *)malloc(sizeof(Renderer)); + if (!renderer) { + return NULL; } renderer->SDL_base_renderer = SDL_CreateRenderer( window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!(renderer->SDL_base_renderer)) { + destroy_renderer(&renderer); - // Technically speaking, I believe it should be possible to just return - // the pointer. However, to make sure that everything is clear, I am - // performing a test that returns true or false based on whether the - // pointer was initialised or not. - return renderer->SDL_base_renderer ? true : false; + return NULL; + } + + return renderer; } -void get_drawable_size(Renderer *renderer, int32_t *w, int32_t *h) { +void get_render_output_size(Renderer *renderer, int32_t *w, int32_t *h) { SDL_GetRendererOutputSize(renderer->SDL_base_renderer, w, h); } @@ -47,6 +47,15 @@ void render_clear(const Renderer *renderer, SDL_Color color) { SDL_RenderClear(renderer->SDL_base_renderer); } +void fill_rect(const Renderer *renderer, SDL_Color color, float x, float y, + float w, float h) { + SDL_FRect dest = {x, y, w, h}; + + SDL_SetRenderDrawColor(renderer->SDL_base_renderer, color.r, color.g, color.b, + color.a); + SDL_RenderFillRectF(renderer->SDL_base_renderer, &dest); +} + void render_surface(const Renderer *renderer, SDL_Surface *surface, float x, float y, float w, float h) { SDL_Texture *texture = diff --git a/src/Window.c b/src/Window.c index 7669d6c..167a82b 100644 --- a/src/Window.c +++ b/src/Window.c @@ -4,8 +4,10 @@ #include "engine_build_config.h" #include -#include +#include + #ifdef DEBUG_ENGINE +#include #endif /* DEBUG_ENGINE */ bool init_window(Window *wnd, const char *title, WindowSize size, @@ -32,22 +34,62 @@ bool init_window(Window *wnd, const char *title, WindowSize size, wnd->defined_size.height = size.height; wnd->background_color = color; + wnd->full_screen = full_screen; - wnd->renderer = create_renderer(); + wnd->renderer = create_renderer(wnd->SDL_base_window); - if (!wnd->renderer || !init_renderer(wnd->renderer, wnd->SDL_base_window)) { + if (!wnd->renderer) { destroy_window(wnd); return false; } - get_drawable_size(wnd->renderer, &(wnd->drawable_size.width), - &(wnd->drawable_size.height)); + get_render_output_size(wnd->renderer, &(wnd->real_size.width), + &(wnd->real_size.height)); + + wnd->drawing_area.x = 0; + wnd->drawing_area.y = 0; + wnd->drawing_area.width = wnd->real_size.width; + wnd->drawing_area.height = wnd->real_size.height; + + if (full_screen) { + // Fit the window into the screen when running in full screen mode on + // a non-mobile platform. Mobile platforms will be handled differently + + float window_size_ratio = + (float)wnd->defined_size.width / (float)wnd->defined_size.height; + + // Fit the drawing area size to the smallest dimension of the screen and + // make sure the drawing area is centered + if (wnd->real_size.height <= wnd->real_size.width) { + wnd->drawing_area.width = + (int32_t)(window_size_ratio * wnd->real_size.height); + wnd->drawing_area.x = + 0.0f + ((wnd->real_size.width - wnd->drawing_area.width) / 2.0f); + } else { + wnd->drawing_area.height = + (int32_t)((float)wnd->real_size.width / window_size_ratio); + wnd->drawing_area.y = + 0.0f + ((wnd->real_size.height - wnd->drawing_area.height) / 2.0f); + } + + // Update the defined size, because in full screen mode, it would be equal + // to the screen dimensions + SDL_DisplayMode mode; + if (SDL_GetWindowDisplayMode(wnd->SDL_base_window, &mode) != 0) { + destroy_window(wnd); + + return false; + } + + wnd->defined_size.width = mode.w; + wnd->defined_size.height = mode.h; + } #ifdef DEBUG_ENGINE printf("Renderer initialised successfully\n"); printf("Window initialised successfully with width %d and height %d\n", - wnd->drawable_size.width, wnd->drawable_size.height); + wnd->real_size.width, wnd->real_size.height); #endif /* DEBUG_ENGINE */ return true; @@ -58,7 +100,18 @@ void clear_window(const Window *wnd) { return; } - render_clear(wnd->renderer, wnd->background_color); + if (wnd->full_screen) { + // When rendering in full screen on a non-mobile platform, we fill the + // entire screen with black, then fill an area that fits the window + // dimensions with the user-defined backgroud color of the window + + render_clear(wnd->renderer, (SDL_Color){0, 0, 0, 255}); + fill_rect(wnd->renderer, wnd->background_color, wnd->drawing_area.x, + wnd->drawing_area.y, wnd->drawing_area.width, + wnd->drawing_area.height); + } else { + render_clear(wnd->renderer, wnd->background_color); + } } void swap_render_buffers(const Window *wnd) { @@ -82,8 +135,10 @@ void destroy_window(Window *wnd) { SDL_DestroyWindow(wnd->SDL_base_window); wnd->SDL_base_window = NULL; + wnd->full_screen = false; wnd->defined_size = (WindowSize){0, 0}; - wnd->drawable_size = (WindowSize){0, 0}; + wnd->real_size = (WindowSize){0, 0}; + wnd->drawing_area = (WindowDrawingArea){0.0f, 0.0f, 0.0f, 0.0f}; wnd->background_color = (SDL_Color){0, 0, 0, 255}; #ifdef DEBUG_ENGINE