Project & engine architecture --------------------------- This document is a collection of vaguely related ideas surrounding how some of the core functionality of the engine is designed. ## State A state should define an initialization, update, and cleanup function. Optionally, it should define a `RenderStack` which allows customizing how your game is rendered. Use this to add shadow mapping, secondary cameras and UI. ## State Management [state.c](../src/state.c) ![gamestate illustration](gamestate.svg) ![gameloop illustration](gameloop.svg) WIP ``` while((newstate = state_update(current_state)) != STATE_Exit) { if (newstate != STATE_NULL) { state = newstate; state_free(current_state); current_state = state_load(newstate); } else { // Input stuff // Update stuff // Render stuff // Time stuff } } ``` ## Input Handling [keypress.h](../src/keypress.h) The input handling can be thought of as a stack of contexts. The method used in the engine is further described in ["designing a robust input handling system for games"](https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/designing-a-robust-input-handling-system-for-games-r2975/) We use a null-terminated statically allocated list of structs containing the following: * `struct Key`: Takes a `uint16 modifier` and a `uint32 key` ([SDL Keycode](https://wiki.libsdl.org/SDLKeycodeLookup)) * `uint32 time_pressed`: Time in ms since it was pressed. * `void (*down_function)(void*)`: The function to be run upon key activation. * `struct Funarg down_arg`: The argument for the function to be called. The argument is automatically added a pointer to the current (game) context for each call. * `void (*up_function)(void*)`: The function to be run upon key deactivation. * `struct Funarg up_arg`: The argument for the function to be called. Upon keypress, ```c for (int i = 0; i < len(keys) && keys[i] != NULL; i++) { if ( keys[i].function_down != NULL && keys[i].key.key == pressed_key && keys[i].key.modifier & current_mods == current_mods) { time_pressed = now(); keys[i].function_down(funarg_up); } } ``` And basically the same goes for keyup, except, we dont check any modifier keys, instead we check if `time_pressed > 0` By using `time_pressed`, we can "charge" moves if needed be, and once released we can measure how long the player held down the key. It can be visualized by giving a pointer to a UI-part and setting it to visible, and toggling said visibility again once the key is released. ## Rendering ### Custom pipelines DAW initializes GLFW, which uses a "default framebuffer". When you do not setup a custom renderpipeline (RenderStack), the default behaviour when you issue a drawcall is for DAW to render to an internal framebuffer drawn to a texture, which is then displayed on a fullscreen quad on the default framebuffer. You can then define your own "default shader" to add custom post-processing. If you define a custom pipeline, you setup one or more framebuffers, the first one _is_ then used by DAW in place of the "internal" one, displayed to the "default framebuffer" for GLFW. This allows you to add stuff like UI on a separate layer of your pipeline. ### Window management The plan is to spawn a new thread for each window. ### Tile rendering When rendering tiles, we go by drawing walls on the floor tile, such that for each floor tile we calculate the number of adjacent walls and fetch the corresponding texture that match that combination of neighbouring walls.