├── .gitattributes ├── .gitignore ├── FlexRect ├── README.md ├── example │ ├── CMakeLists.txt │ ├── FlexExample.c │ └── FlexRectPreview.gif └── flexrect.h ├── HitboxCollision ├── Example │ ├── CMakeLists.txt │ └── HitboxExample.c ├── README.md └── hitbox_collision.h ├── LICENSE ├── README.md ├── SpriteSystem ├── README.md ├── examples │ ├── CMakeLists.txt │ ├── resources │ │ ├── Preview_Sprite_anim_scale.gif │ │ └── player_sheet.png │ ├── sprite_raylib.c │ └── sprite_rect_raylib.c ├── sprite.h └── sprite_rect.h ├── StateMachine ├── README.md ├── example │ ├── CMakeLists.txt │ └── StateExample.c └── statemachine.h ├── TileMapSystem ├── Example │ ├── .gitignore │ ├── CMakeLists.txt │ ├── README.md │ ├── Resource │ │ ├── Bitmask_example.png │ │ ├── Preview.gif │ │ ├── Preview_TileMap_resize.gif │ │ ├── Preview_TileMap_spread.gif │ │ ├── Preview_autotile.gif │ │ ├── RDE_8x8.png │ │ ├── TileSetBitmask.h │ │ ├── TileSetterBitmask.h │ │ ├── TileSetter_template.png │ │ ├── grass_and_dirt_tiles.ase │ │ └── grass_and_dirt_tiles.png │ ├── Test_AutoTile.c │ ├── Test_AutotTile.c │ ├── Test_TileMap.c │ ├── Test_TileMap_spread_parallax.c │ └── Test_TileSet_bitmask_editor.c ├── README.md ├── autotile.h ├── tilemap.h └── tileset.h └── ViewportScaling ├── Example ├── CMakeLists.txt └── ViewportExample.c ├── README.md └── viewport_scaling.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | */*.exe 2 | */*.o 3 | *.exe 4 | .vs/ -------------------------------------------------------------------------------- /FlexRect/README.md: -------------------------------------------------------------------------------- 1 | # FlexRect (in progress) 2 | ## A single header library for flexible rectangles that are usable for GUI positioning. 3 | 4 | To use function implementation `#define NEZ_FLEXRECT_IMPLEMENTATION` for library's .c part. 5 | Library allows to create rectangles that positions and sizes depending on anchor and margins from given or parent rectangle and includes minimal width & height. 6 | FlexRect are the resizing rectangles and FlexRect2 are plain {x, y, w, h} struct. FlexRect.r holds it's rectangle area of FlexRect2. 7 | 8 | ![](https://raw.githubusercontent.com/nezvers/GameSystemsInC/master/FlexRect/example/FlexRectPreview.gif) 9 | -------------------------------------------------------------------------------- /FlexRect/example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | include(FetchContent) 3 | 4 | 5 | 6 | # Raylib 7 | FetchContent_Declare( 8 | raylib 9 | DOWNLOAD_EXTRACT_TIMESTAMP OFF 10 | URL https://github.com/raysan5/raylib/archive/refs/tags/5.5.zip 11 | ) 12 | #FetchContent_GetProperties(raylib) 13 | set(FETCHCONTENT_QUIET NO) 14 | set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples 15 | set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games 16 | FetchContent_MakeAvailable(raylib) 17 | 18 | include_directories(${CMAKE_SOURCE_DIR}/..) 19 | 20 | 21 | set(ProjectName Flex_Example) 22 | project(${ProjectName}) 23 | set(CMAKE_CXX_STANDARD 17) 24 | file(GLOB Flex_SRC_FILES "FlexExample.c") 25 | add_executable(${ProjectName} ${Flex_SRC_FILES}) 26 | target_link_libraries(${ProjectName} raylib) 27 | target_compile_definitions(${ProjectName} PUBLIC SOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}") 28 | -------------------------------------------------------------------------------- /FlexRect/example/FlexExample.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************************* 2 | * 3 | * 4 | * 5 | ********************************************************************************************/ 6 | 7 | #include "raylib.h" 8 | 9 | #define NEZ_FLEXRECT_IMPLEMENTATION 10 | #include "flexrect.h" 11 | 12 | 13 | int screenWidth = 640; 14 | int screenHeight = 360; 15 | 16 | void InitGame(void); 17 | void GameLoop(void); 18 | 19 | NezRect_i rect; 20 | 21 | FlexRect* flexRect; 22 | FlexRect* flexChild1; 23 | 24 | void Resize(); 25 | 26 | int main() { 27 | InitGame(); 28 | 29 | SetTargetFPS(60); 30 | 31 | while (!WindowShouldClose()) { 32 | GameLoop(); 33 | } 34 | 35 | 36 | CloseWindow(); 37 | return 0; 38 | } 39 | 40 | 41 | void InitGame() { 42 | SetConfigFlags(FLAG_WINDOW_RESIZABLE); 43 | InitWindow(screenWidth, screenHeight, "FlexRect test"); 44 | 45 | rect = (NezRect_i){ 0, 0, screenWidth, screenHeight }; 46 | flexRect = FlexRectNew(&rect, 0.0f, 0.0f, 1.0f, 1.0f, 10, 10, 10, 10, 0, 0); 47 | flexChild1 = FlexRectCreateChild(flexRect, 0.0f, 0.0f, 1.0f, 0.5f, 10, 10, 10, 0, 300, 100); 48 | } 49 | 50 | void GameLoop(void) { 51 | if (IsWindowResized()) { 52 | screenWidth = GetScreenWidth(); 53 | screenHeight = GetScreenHeight(); 54 | Resize(); 55 | } 56 | 57 | 58 | BeginDrawing(); 59 | 60 | ClearBackground(RAYWHITE); 61 | 62 | if (flexRect) { 63 | DrawRectangleLines(flexRect->r.x, flexRect->r.y, flexRect->r.w, flexRect->r.h, RED); 64 | DrawRectangleLines(flexChild1->r.x, flexChild1->r.y, flexChild1->r.w, flexChild1->r.h, RED); 65 | } 66 | 67 | EndDrawing(); 68 | } 69 | 70 | 71 | void Resize() { 72 | rect = (NezRect_i){ 0, 0, screenWidth, screenHeight }; 73 | FlexRectResize(flexRect, &rect); 74 | } 75 | 76 | -------------------------------------------------------------------------------- /FlexRect/example/FlexRectPreview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nezvers/GameSystemsInC/b0118784f584446fc8a17c9369c5fdb30820d0bc/FlexRect/example/FlexRectPreview.gif -------------------------------------------------------------------------------- /FlexRect/flexrect.h: -------------------------------------------------------------------------------- 1 | #ifndef NEZ_FLEXRECT_H 2 | #define NEZ_FLEXRECT_H 3 | 4 | #include "stddef.h" 5 | 6 | #include "stdlib.h" 7 | #include "stdio.h" 8 | 9 | // Flags for expanding in case parent rect is smaller than minimal size 10 | enum { 11 | FR_RIGHT = 0, // grow rigth 12 | FR_LEFT = 1, // grow left 13 | FR_H_CENTER = 2, // grow centered 14 | FR_DOWN = 0, // grow down 15 | FR_UP = 1, // grow up 16 | FR_V_CENTER = 2, // grow centered 17 | }; 18 | 19 | #ifndef NEZRECT_I 20 | #define NEZRECT_I 21 | // Basic rectangle struct with x & y fosition and w & h size 22 | typedef struct{ 23 | int x; 24 | int y; 25 | int w; 26 | int h; 27 | }NezRect_i; 28 | #endif // NEZRECT_I 29 | 30 | // Flexible rectangle that positions self depending on it's parameters 31 | typedef struct FlexRect FlexRect; 32 | 33 | 34 | #ifndef NEZFRAPI 35 | #ifdef NEZ_FLEXRECT_STATIC 36 | #define NEZFRAPI static 37 | #else 38 | #define NEZFRAPI extern 39 | #endif 40 | #endif 41 | 42 | #ifdef __cplusplus 43 | extern "C" { 44 | #endif 45 | 46 | // Create new FlexRect providing NezRect_i rectangle, anchor percentage from sides and margin pixels from sides and minimal width & height. 47 | NEZFRAPI FlexRect* 48 | FlexRectNew(NezRect_i *rect, float al, float at, float ar, float ab, int ml, int mt, int mr, int mb, int minw, int minh); 49 | // Deallocate FlexRect and it's children 50 | NEZFRAPI void 51 | FlexRectDestroy(FlexRect *rect); 52 | // Create child FlexRect that automatically links with parent FlexRect 53 | NEZFRAPI FlexRect* 54 | FlexRectCreateChild(FlexRect *parent, float al, float at, float ar, float ab, int ml, int mt, int mr, int mb, int minw, int minh); 55 | // Remove parenting over child FlexRect 56 | NEZFRAPI void 57 | FlexRectRemoveChild(FlexRect *parent, FlexRect *child); 58 | // Destroys FlexRect and it's children 59 | NEZFRAPI void 60 | FlexRectDestroyChild(FlexRect *parent, FlexRect *child); 61 | // Give new rectangle are to resize it and it's children 62 | NEZFRAPI void 63 | FlexRectResize(FlexRect *fr, NezRect_i *rect); 64 | // FlexRectSetFlags(); 65 | // FlexRectWithPoint(FlexRect *rect); 66 | 67 | #ifdef __cplusplus 68 | } 69 | #endif 70 | #endif // NEZ_FLEXRECT_H 71 | 72 | // ------------------------------------- 73 | 74 | #ifdef NEZ_FLEXRECT_IMPLEMENTATION 75 | #undef NEZ_FLEXRECT_IMPLEMENTATION 76 | 77 | struct FlexRect{ 78 | float al; // anchor left 79 | float at; // anchor top 80 | float ar; // anchor right 81 | float ab; // anchor bottom 82 | int ml; // margin left 83 | int mt; // margin top 84 | int mr; // margin right 85 | int mb; // margin bottom 86 | int minw; // minimal width 87 | int minh; // minimal height 88 | NezRect_i r; // rectangle area 89 | int childCount; 90 | FlexRect **children; 91 | FlexRect *parent; 92 | }; 93 | 94 | NezRect_i 95 | FlexRectGetRect(NezRect_i *rect, float al, float at, float ar, float ab, int ml, int mt, int mr, int mb, int minw, int minh){ 96 | int x = rect->x + (rect->w * al) + ml; 97 | int y = rect->y + (rect->h * at) + mt; 98 | int w = (rect->w - (rect->w * al) - ml - mr) * ar; 99 | int h = (rect->h - (rect->h * at) - mt - mb) * ab; 100 | if (w < minw){w = minw;} 101 | if (h < minh){h = minh;} 102 | NezRect_i r = { 103 | x, 104 | y, 105 | w, 106 | h, 107 | }; 108 | return r; 109 | } 110 | 111 | 112 | FlexRect* 113 | FlexRectNew(NezRect_i *rect, float al, float at, float ar, float ab, int ml, int mt, int mr, int mb, int minw, int minh){ 114 | NezRect_i r = FlexRectGetRect(rect, al, at, ar, ab, ml, mt, mr, mb, minw, minh); 115 | 116 | FlexRect* fr = malloc(sizeof(FlexRect)); 117 | *fr = (FlexRect){ 118 | al, // anchor left 119 | at, // anchor top 120 | ar, // anchor right 121 | ab, // anchor bottom 122 | ml, // margin left 123 | mt, // margin top 124 | mr, // margin right 125 | mb, // margin bottom 126 | minw, // minimal width 127 | minh, // minimal height 128 | r, 129 | 0, 130 | NULL, 131 | NULL, 132 | }; 133 | return fr; 134 | } 135 | 136 | void 137 | FlexRectDestroy(FlexRect *rect){ 138 | for (int i = 0; i < rect->childCount; i++){ 139 | FlexRectDestroy(rect->children[i]); 140 | } 141 | free(rect); 142 | } 143 | 144 | FlexRect* 145 | FlexRectCreateChild(FlexRect *parent, float al, float at, float ar, float ab, int ml, int mt, int mr, int mb, int minw, int minh){ 146 | FlexRect ** old = parent->children; 147 | FlexRect *newChild = FlexRectNew(&parent->r, al, at, ar, ab, ml, mt, mr, mb, minw, minh); 148 | parent->children = malloc(sizeof(FlexRect**) * (parent->childCount + 1)); 149 | for (int i = 0; i < parent->childCount; i++){ 150 | parent->children[i] = old[i]; 151 | } 152 | parent->children[parent->childCount] = newChild; 153 | 154 | if (parent->childCount > 0){ 155 | free(old); 156 | } 157 | parent->childCount += 1; 158 | return newChild; 159 | } 160 | 161 | void 162 | FlexRectRemoveChild(FlexRect *parent, FlexRect *child){ 163 | if (parent->childCount < 1){ 164 | return; 165 | } 166 | else if (parent->childCount == 1){ 167 | free(parent->children); 168 | parent->childCount = 0; 169 | parent->children = 0; 170 | return; 171 | } 172 | FlexRect ** old = parent->children; 173 | parent->children = malloc(sizeof(FlexRect**) * (parent->childCount - 1)); 174 | for (int i = 0; i < parent->childCount; i++){ 175 | if (parent->children[i] != child){ 176 | parent->children[i] = old[i]; 177 | } 178 | } 179 | free(old); 180 | parent->childCount -= 1; 181 | } 182 | 183 | void 184 | FlexRectDestroyChild(FlexRect *parent, FlexRect *child){ 185 | FlexRectRemoveChild(parent, child); 186 | FlexRectDestroy(child); 187 | } 188 | 189 | void 190 | FlexRectResize(FlexRect *fr, NezRect_i *rect){ 191 | fr->r = FlexRectGetRect(rect, fr->al, fr->at, fr->ar, fr->ab, fr->ml, fr->mt, fr->mr, fr->mb, fr->minw, fr->minh); 192 | for(int i = 0 ; i < fr->childCount; i++){ 193 | FlexRectResize(fr->children[i], &fr->r); 194 | } 195 | } 196 | 197 | 198 | #endif // NEZ_FLEXRECT_IMPLEMENTATION 199 | 200 | -------------------------------------------------------------------------------- /HitboxCollision/Example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | include(FetchContent) 3 | 4 | 5 | 6 | # Raylib 7 | FetchContent_Declare( 8 | raylib 9 | DOWNLOAD_EXTRACT_TIMESTAMP OFF 10 | URL https://github.com/raysan5/raylib/archive/refs/tags/5.5.zip 11 | ) 12 | #FetchContent_GetProperties(raylib) 13 | set(FETCHCONTENT_QUIET NO) 14 | set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples 15 | set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games 16 | FetchContent_MakeAvailable(raylib) 17 | 18 | include_directories(${CMAKE_SOURCE_DIR}/..) 19 | 20 | 21 | set(ProjectName Hitbox_Example) 22 | project(${ProjectName}) 23 | set(CMAKE_CXX_STANDARD 17) 24 | file(GLOB Hitbox_SRC_FILES "HitboxExample.c") 25 | add_executable(${ProjectName} ${Hitbox_SRC_FILES}) 26 | target_link_libraries(${ProjectName} raylib) 27 | target_compile_definitions(${ProjectName} PUBLIC SOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}") 28 | -------------------------------------------------------------------------------- /HitboxCollision/Example/HitboxExample.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************************* 2 | 3 | 4 | ********************************************************************************************/ 5 | 6 | #include "raylib.h" 7 | 8 | #define HITBOX_COLLISIONS_IMPLEMENTATION 9 | #include "hitbox_collision.h" 10 | 11 | #define DEBUG 12 | 13 | 14 | #define MIN(a,b) (((a)<(b))?(a):(b)) 15 | #define MAX(a,b) (((a)>(b))?(a):(b)) 16 | 17 | bool PLATFORMER = true; // toggle to top-down movement with TAB 18 | 19 | 20 | void GameInit(); 21 | void GameUpdate(); 22 | void GameDraw(); 23 | void GameLoop(){GameUpdate(); GameDraw();} 24 | void Reset(); 25 | 26 | void DrawTileGrid(); 27 | void DrawTileMap(); 28 | void DrawCoins(); 29 | void DrawPlayer(); 30 | void DrawObstacles(); 31 | void DrawScoreText(); 32 | 33 | void UpdateScreen(); 34 | void UpdatePlayer(); 35 | void UpdateCoin(); 36 | 37 | 38 | #define MAP_W 20 39 | #define MAP_H 12 40 | int screenWidth = 32*MAP_W; 41 | int screenHeight = 32*MAP_H; 42 | const int gameWidth = 32*MAP_W; 43 | const int gameHeight = 32*MAP_H; 44 | RenderTexture viewport; 45 | int scale = 1; 46 | Vector2 vpOffset = {0.0f, 0.0f}; 47 | 48 | NezRect_f player = {32.0f * 2, 32.0f * 8, 32.0f, 32.0f}; 49 | NezRect_f obstacles[] = { 50 | {32.0f * 15, 32.0f * 5, 32.0f, 32.0f}, 51 | }; 52 | 53 | #define COIN_COUNT 10 54 | NezRect_f coins[COIN_COUNT] = {0}; 55 | bool visible[COIN_COUNT] = {0}; 56 | int points = 0; 57 | int time = 0; // For animation 58 | 59 | NezGridMap map; 60 | char tiles[] = { 61 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 62 | 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 63 | 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 64 | 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 65 | 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 66 | 1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,1, 67 | 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,1, 68 | 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 69 | 1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 70 | 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 71 | 1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, 72 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 73 | }; 74 | 75 | int main(void){ 76 | GameInit(); 77 | 78 | SetTargetFPS(60); 79 | while (!WindowShouldClose()){ 80 | GameLoop(); 81 | } 82 | UnloadRenderTexture(viewport); 83 | 84 | CloseWindow(); 85 | return 0; 86 | } 87 | 88 | void GameInit() { 89 | SetConfigFlags(FLAG_WINDOW_RESIZABLE); 90 | InitWindow(screenWidth, screenHeight, "classic game: platformer"); 91 | viewport = LoadRenderTexture(gameWidth, gameHeight); 92 | map.x = 0.0f; 93 | map.y = 0.0f; 94 | map.w = MAP_W; 95 | map.h = MAP_H; 96 | map.s = 32; 97 | map.cell = tiles; 98 | Reset(); 99 | } 100 | 101 | void Reset(){ 102 | const float s = 32.0f; 103 | player = (NezRect_f){s * 2, s * 8, s, s}; 104 | points = 0; 105 | time = 0; 106 | 107 | coins[0] = (NezRect_f){s * 1.5f, s * 8, 10.0f, 10.0f}; 108 | coins[1] = (NezRect_f){s * 3.5f, s * 6, 10.0f, 10.0f}; 109 | coins[2] = (NezRect_f){s * 4.5f, s * 6, 10.0f, 10.0f}; 110 | coins[3] = (NezRect_f){s * 5.5f, s * 6, 10.0f, 10.0f}; 111 | coins[4] = (NezRect_f){s * 8.5f, s * 3, 10.0f, 10.0f}; 112 | coins[5] = (NezRect_f){s * 9.5f, s * 3, 10.0f, 10.0f}; 113 | coins[6] = (NezRect_f){s * 10.5f, s * 3, 10.0f, 10.0f}; 114 | coins[7] = (NezRect_f){s * 14.5f, s * 4, 10.0f, 10.0f}; 115 | coins[8] = (NezRect_f){s * 15.5f, s * 4, 10.0f, 10.0f}; 116 | coins[9] = (NezRect_f){s * 17.5f, s * 2, 10.0f, 10.0f}; 117 | 118 | for (int i = 0; i < COIN_COUNT; i++){visible[i] = true;} 119 | } 120 | 121 | void GameUpdate(){ 122 | 123 | UpdateScreen();// Adapt to resolution 124 | UpdatePlayer(); 125 | UpdateCoin(); 126 | } 127 | 128 | void UpdateScreen(){ 129 | // Adapt to resolution 130 | if (IsWindowResized()){ 131 | screenWidth = GetScreenWidth(); 132 | screenHeight = GetScreenHeight(); 133 | scale = MAX(1, MIN((screenWidth/gameWidth), (screenHeight/gameHeight))); 134 | vpOffset.x = (screenWidth - (gameWidth * scale)) / 2; 135 | vpOffset.y = (screenHeight - (gameHeight * scale)) / 2; 136 | } 137 | } 138 | 139 | void UpdatePlayer(){ 140 | 141 | const float maxSpd = 6.0f; 142 | const float acc = 0.1f; 143 | const float grav = 0.5f; 144 | const float jmpImpulse = -10.0f; 145 | const int jmpBufferTime = 30; 146 | static bool isGrounded = false; 147 | static int jmpBuffer = 0; 148 | static int dirX = 0; 149 | static int dirY = 0; 150 | static NezVec2_f vel = {0}; 151 | 152 | // INPUT 153 | dirX = (float)(IsKeyDown(KEY_D) - IsKeyDown(KEY_A)); 154 | dirY = (float)(IsKeyDown(KEY_S) - IsKeyDown(KEY_W)); 155 | if(IsKeyPressed(KEY_TAB)){PLATFORMER = !PLATFORMER;} 156 | 157 | // HORIZONTAL SPEED 158 | vel.x += (dirX * maxSpd - vel.x) * acc; 159 | if (vel.x < -maxSpd){ 160 | vel.x = -maxSpd; 161 | } 162 | else if (vel.x > maxSpd){ 163 | vel.x = maxSpd; 164 | } 165 | 166 | // VERTICAL SPEED 167 | if (PLATFORMER){ 168 | if (isGrounded && jmpBuffer != jmpBufferTime){ 169 | jmpBuffer = jmpBufferTime; 170 | } 171 | if (isGrounded && IsKeyPressed(KEY_SPACE)){ 172 | vel.y = jmpImpulse; 173 | jmpBuffer = 0; 174 | } 175 | else if (jmpBuffer > 0 && IsKeyPressed(KEY_SPACE)){ 176 | vel.y = jmpImpulse; 177 | jmpBuffer = 0; 178 | } 179 | else{ 180 | if (!IsKeyDown(KEY_SPACE) && vel.y < jmpImpulse * 0.2){ 181 | vel.y = jmpImpulse * 0.2; 182 | } 183 | else{ 184 | vel.y += grav; 185 | if (vel.y > -jmpImpulse){ 186 | vel.y = -jmpImpulse; 187 | } 188 | if (jmpBuffer > 0){ 189 | jmpBuffer -= 1; 190 | } 191 | } 192 | } 193 | } 194 | else{ 195 | // TOP-DOWN 196 | vel.y += (dirY * maxSpd - vel.y) * acc; 197 | if (vel.y < -maxSpd){ 198 | vel.y = -maxSpd; 199 | } 200 | else if (vel.y > maxSpd){ 201 | vel.y = maxSpd; 202 | } 203 | } 204 | 205 | 206 | char flags = HitboxMoveAndCollide(&player, &vel, obstacles, 1, &map); 207 | //char flags = HitboxMoveAndCollideContinuous(&player, &vel, obstacles, 1, &map); 208 | 209 | 210 | isGrounded = flags & BOTTOM_COL; 211 | 212 | 213 | } 214 | 215 | void UpdateCoin(){ 216 | for (int i = 0; i < COIN_COUNT; i++){ 217 | if (visible[i]){ 218 | if (HitboxCheckHitbox(&coins[i], &player)){ 219 | visible[i] = false; 220 | points += 1; 221 | } 222 | } 223 | } 224 | 225 | if (IsKeyPressed(KEY_ENTER)){ 226 | Reset(); 227 | } 228 | } 229 | 230 | void GameDraw(){ 231 | // Viewport scaling 232 | const Vector2 origin = (Vector2){0.0f, 0.0f}; 233 | const Rectangle vp_r = (Rectangle){0.0f,gameHeight,gameWidth, -gameHeight}; // flip vertically: position = left-bottom 234 | Rectangle out_r = (Rectangle){vpOffset.x, vpOffset.y, gameWidth * scale, gameHeight * scale}; 235 | 236 | // Render game's viewport 237 | BeginTextureMode(viewport); 238 | DrawRectangle(0, 0, gameWidth, gameHeight, SKYBLUE); // Background 239 | DrawTileMap(); 240 | DrawTileGrid(); 241 | DrawScoreText(); 242 | DrawCoins(); 243 | DrawObstacles(); 244 | DrawPlayer(); 245 | EndTextureMode(); 246 | 247 | // Draw the viewport 248 | BeginDrawing(); 249 | ClearBackground(BLACK); 250 | DrawTexturePro(viewport.texture, vp_r, out_r, origin, 0.0f, WHITE); 251 | EndDrawing(); 252 | } 253 | 254 | void DrawTileMap(){ 255 | for (int y = 0; y < map.h; y++){ 256 | for (int x = 0; x < map.w; x++){ 257 | int i = x + y * map.w; 258 | int tile = map.cell[i]; 259 | if (tile){ 260 | float cellX = (map.x + map.s * x); 261 | float cellY = (map.y + map.s * y); 262 | DrawRectangle((int)cellX, (int)cellY, map.s, map.s, LIME); 263 | // check tile above 264 | if (i - map.w >= 0 && !map.cell[i - map.w]){ 265 | DrawLineEx((Vector2){cellX, cellY + 3}, (Vector2){cellX + map.s, cellY + 3}, 6.0f, GREEN); 266 | } 267 | } 268 | } 269 | } 270 | } 271 | 272 | void DrawTileGrid(){ 273 | Color c = (Color){255,255,255,25}; 274 | 275 | for (int y = 0; y < map.h + 1; y++){ 276 | int x1 = map.x; 277 | int x2 = map.x + map.w * map.s; 278 | int Y = map.y + map.s * y; 279 | DrawLine(x1, Y, x2, Y, c); 280 | } 281 | for (int x = 0; x < map.w + 1; x++){ 282 | int y1 = map.y; 283 | int y2 = map.y + map.h * map.s; 284 | int X = map.x + map.s * x; 285 | DrawLine(X, y1, X, y2, c); 286 | } 287 | } 288 | 289 | void DrawPlayer(){ 290 | Rectangle p = *(Rectangle*)&player; 291 | DrawRectangle((int)p.x, (int)p.y, (int)p.width, (int)p.height, WHITE); 292 | DrawRectangleLinesEx(p, 2, BLACK); 293 | 294 | // Artistic touch 295 | static int dirX = 0; 296 | dirX = (float)(IsKeyDown(KEY_D) - IsKeyDown(KEY_A)) * 4; 297 | Vector2 L1 = (Vector2){p.x + 12 + dirX, p.y + 4}; 298 | Vector2 R1 = (Vector2){p.x + 20 + dirX, p.y + 4}; 299 | Vector2 L2 = L1; 300 | L2.y += 8; 301 | Vector2 R2 = R1; 302 | R2.y += 8; 303 | DrawLineEx(L1, L2, 2.0f, BLACK); 304 | DrawLineEx(R1, R2, 2.0f, BLACK); 305 | } 306 | 307 | void DrawObstacles(){ 308 | int count = sizeof(obstacles) / sizeof(obstacles[0]); 309 | for (int i = 0; i < count; i++){ 310 | DrawRectangle((int)obstacles[i].x, (int)obstacles[i].y, (int)obstacles[i].w, (int)obstacles[i].h, WHITE); 311 | } 312 | } 313 | 314 | void DrawCoins(){ 315 | time += 1; 316 | 317 | for (int i = 0; i < COIN_COUNT; i++){ 318 | if (visible[i]){ 319 | Rectangle c = *(Rectangle*)&coins[i]; 320 | float y = (float)sin(2 * PI * (time / 60.0f * 0.5) + (c.x * 5)) * 4; // pseudo random offset floating 321 | float x = (float)sin(2 * PI * (time / 60.0f * 2)) * 4; 322 | DrawRectangle((int)(c.x + 4 + x * 0.5), (int)(c.y + y), (int)(c.width - 4 - x), (int)c.height, GOLD); 323 | } 324 | } 325 | } 326 | 327 | void DrawScoreText(){ 328 | const char *text; 329 | if (points == COIN_COUNT){ 330 | text = TextFormat("Pres 'ENTER' to restart!"); 331 | } 332 | else{ 333 | text = TextFormat("Score: %d", points); 334 | } 335 | 336 | const int size = 24; 337 | int x = gameWidth /2 - MeasureText(text, size) / 2; 338 | int y = 48; 339 | 340 | DrawText(text, x, y+1, size, BLACK); 341 | DrawText(text, x, y, size, WHITE); 342 | 343 | } 344 | 345 | -------------------------------------------------------------------------------- /HitboxCollision/README.md: -------------------------------------------------------------------------------- 1 | # Hitbox Collision (WIP) 2 | Single header library for AABB collision detection and resolution. 3 | Now includes continuous collision (thanks to Javidx9 a.k.a. [OneLoneCoder](https://github.com/OneLoneCoder/olcPixelGameEngine/blob/master/Videos/OneLoneCoder_PGE_Rectangles.cpp)) 4 | To include function implementations (*.c part) `#define HITBOX_COLLISIONS_IMPLEMENTATION` is needed before header inclusion. 5 | -------------------------------------------------------------------------------- /HitboxCollision/hitbox_collision.h: -------------------------------------------------------------------------------- 1 | /* 2 | Influenced by C++ project from Javidx9 / OneLoneCoder 3 | https://github.com/OneLoneCoder/olcPixelGameEngine/blob/master/Videos/OneLoneCoder_PGE_Rectangles.cpp 4 | */ 5 | 6 | #ifndef HITBOX_COLLISIONS_H 7 | #define HITBOX_COLLISIONS_H 8 | 9 | 10 | #include "stdlib.h" 11 | #include "stdbool.h" 12 | #include "math.h" 13 | 14 | #ifndef NEZRECT_F 15 | #define NEZRECT_F 16 | typedef struct { 17 | float x; // origin x 18 | float y; // origin y 19 | float w; // width 20 | float h; // height 21 | } NezRect_f; 22 | #endif // NEZRECT_F 23 | 24 | 25 | #ifndef NEZVEC2_F 26 | #define NEZVEC2_F 27 | typedef struct { 28 | float x; 29 | float y; 30 | } NezVec2_f; 31 | #endif // NEZVEC2_F 32 | 33 | 34 | //tile collision types 35 | #ifndef NEZGRIDMAP 36 | #define NEZGRIDMAP 37 | typedef struct{ 38 | float x; 39 | float y; 40 | int w; // width 41 | int h; // height 42 | int s; // cell size (square cells) 43 | char *cell; // array of tiles 44 | }NezGridMap; 45 | #endif // NEZGRIDMAP 46 | 47 | typedef struct { 48 | int size; 49 | NezRect_f *rect; 50 | }RectList; 51 | 52 | // Collision flags 53 | enum { 54 | RIGHT_COL = 1, 55 | LEFT_COL = 2, 56 | TOP_COL = 4, 57 | BOTTOM_COL = 8, 58 | X_COL = 16, 59 | Y_COL = 32, 60 | }; 61 | 62 | #ifndef NEZHBAPI 63 | #ifdef NEZ_HITBOX_STATIC 64 | #define NEZHBAPI static 65 | #else 66 | #define NEZHBAPI extern 67 | #endif 68 | #endif 69 | 70 | #ifdef __cplusplus 71 | extern "C" { 72 | #endif 73 | 74 | // Static collision resolution 75 | NEZHBAPI char 76 | HitboxMoveAndCollide(NezRect_f *rect, NezVec2_f *velocity, NezRect_f *bodies, int count, NezGridMap *grid); 77 | // Dynamic resolution using raycast 78 | NEZHBAPI char 79 | HitboxMoveAndCollideContinuous(NezRect_f *rect, NezVec2_f *velocity, NezRect_f *bodies, int count, NezGridMap *grid); 80 | // Check if two hitboxes collides 81 | NEZHBAPI bool 82 | HitboxCheckHitbox(NezRect_f *a, NezRect_f *b); 83 | NEZHBAPI char 84 | HitboxCollisionResolve(NezRect_f *rect, NezVec2_f *velocity, RectList *list); 85 | NEZHBAPI NezRect_f 86 | HitboxExpand(NezRect_f *rect, NezVec2_f *size); 87 | // Get grid cell area in grid coordinates. areaOut is int[4] 88 | NEZHBAPI void 89 | HitboxGridArea(NezRect_f *rect, NezGridMap *grid, int *areaOut); 90 | NEZHBAPI void 91 | HitboxListFromGrid(NezGridMap *grid, int *area, NezRect_f **outList, int *outCount); 92 | // Raycast against rectangle 93 | NEZHBAPI bool 94 | HitboxVsRaycast(NezVec2_f *rayOrig, NezVec2_f *rayDir, NezRect_f *target, NezVec2_f *colPoint, NezVec2_f *colNormal, float *timeStep); 95 | // Dynamic rectangle vs rectangle collision. Velocity gets multiplied by deltaTime. Pass deltaTime value of 1.0f if full velocity length should be performed. 96 | NEZHBAPI bool 97 | HitboxDynamicVsHitbox(NezRect_f *movingRect, NezVec2_f *velocity, NezRect_f *target, NezVec2_f *colPoint, NezVec2_f *colNormal, float *timeStep); 98 | // Resolve movement collision between rectangles 99 | NEZHBAPI bool 100 | HitboxResolveDynamicRectVsRect(NezRect_f *movingRect, NezVec2_f *velocity, NezRect_f *target); 101 | 102 | #ifdef __cplusplus 103 | } 104 | #endif 105 | #endif //HITBOX_COLLISIONS_H 106 | 107 | #ifdef HITBOX_COLLISIONS_IMPLEMENTATION 108 | #undef HITBOX_COLLISIONS_IMPLEMENTATION 109 | 110 | static inline void swapf(float *a, float *b){ 111 | float t; 112 | t = *b; 113 | *b = *a; 114 | *a = t; 115 | } 116 | 117 | bool HitboxCheckHitbox(NezRect_f *a, NezRect_f *b) { 118 | return a->x < b->x + b->w && a->x + a->w > b->x && a->y< b->y + b->h && a->y + a->h > b->y; 119 | } 120 | 121 | 122 | char HitboxMoveAndCollide(NezRect_f *rect, NezVec2_f *velocity, NezRect_f *bodies, int count, NezGridMap *grid){ 123 | NezRect_f colArea = HitboxExpand(rect, velocity); 124 | RectList colList = {0}; 125 | int tileCount = 0; 126 | if (grid){ 127 | int cellArea[4]; 128 | HitboxGridArea(&colArea, grid, cellArea); 129 | colList.rect = malloc(sizeof(NezRect_f) * (count + cellArea[3] * cellArea[3])); 130 | HitboxListFromGrid(grid, cellArea, &colList.rect, &tileCount); 131 | } 132 | else{ 133 | colList.rect = malloc(sizeof(NezRect_f) * count); 134 | } 135 | colList.size = count + tileCount; 136 | // insert bodies 137 | for (int i = 0; i < count; i++){ 138 | colList.rect[tileCount + i] = bodies[i]; 139 | } 140 | 141 | char flags = HitboxCollisionResolve(rect, velocity, &colList); 142 | 143 | if(colList.rect){ 144 | free(colList.rect); 145 | } 146 | return flags; 147 | } 148 | 149 | 150 | NezRect_f HitboxExpand(NezRect_f *rect, NezVec2_f *size){ 151 | return (NezRect_f){ 152 | size->x > 0 ? rect->x : rect->x + size->x, 153 | size->y > 0 ? rect->y : rect->y + size->y, 154 | size->x > 0 ? rect->w + size->x : rect->w - size->x, 155 | size->y > 0 ? rect->h + size->y : rect->h - size->y 156 | }; 157 | } 158 | 159 | void HitboxGridArea(NezRect_f *rect, NezGridMap *grid, int *areaOut){ 160 | float offX = rect->x - grid->x; 161 | float offY = rect->y - grid->y; 162 | float offXw = rect->x - grid->x + rect->w; 163 | float offYh = rect->y - grid->y + rect->h; 164 | // compensate flooring 165 | if (offX < 0.0f){offX -= grid->s;} 166 | if (offY < 0.0f){offY -= grid->s;} 167 | if (offXw < 0.0f){offXw -= grid->s;} 168 | if (offYh < 0.0f){offYh -= grid->s;} 169 | 170 | // grid coordinates 171 | int X = (int)(offX / grid->s); 172 | int Y = (int)(offY / grid->s); 173 | int sizeX = (int)(offXw / grid->s) - X + 1; 174 | int sizeY = (int)(offYh / grid->s) - Y + 1; 175 | areaOut[0] = X; 176 | areaOut[1] = Y; 177 | areaOut[2] = sizeX; 178 | areaOut[3] = sizeY; 179 | } 180 | 181 | /* 182 | *outList = malloc(sizeof(NezRect_f) * sizeX * sizeY); 183 | *outCount = 0; 184 | */ 185 | void HitboxListFromGrid(NezGridMap *grid, int *area, NezRect_f **outList, int *outCount){ 186 | int X = area[0]; 187 | int Y = area[1]; 188 | int sizeX = area[2]; 189 | int sizeY = area[3]; 190 | for (int y = Y; y < Y + sizeY; y++){ 191 | if (y >= 0 && y < grid->h){ 192 | for (int x = X; x < X + sizeX; x++){ 193 | if (x >= 0 && x < grid->w){ 194 | char tile = grid->cell[x + y * grid->w]; 195 | if (tile){ 196 | (*outList)[*outCount] = (NezRect_f){ 197 | grid->x + x * grid->s, 198 | grid->y + y * grid->s, 199 | grid->s, 200 | grid->s 201 | }; 202 | *outCount += 1; 203 | } 204 | } 205 | } 206 | } 207 | } 208 | } 209 | 210 | char HitboxCollisionResolve(NezRect_f *rect, NezVec2_f *velocity, RectList *list){ 211 | NezRect_f *a = rect; 212 | NezRect_f *b; 213 | NezRect_f c = (NezRect_f){a->x + velocity->x, a->y, a->w, a->h}; 214 | char flags = 0; 215 | 216 | // Solve X axis separately 217 | for (int i = 0; i < list->size; i++){ 218 | b = &list->rect[i]; // next collision Rectangle 219 | if (a != b && HitboxCheckHitbox(&c, b)) { 220 | // moving to the right 221 | if (velocity->x > 0.0f) { 222 | // adjust velocity 223 | velocity->x = (b->x - a->w) - a->x; 224 | flags |= RIGHT_COL; 225 | } 226 | // moving to the left 227 | else if (velocity->x < 0.0f) { 228 | velocity->x = (b->x + b->w) - a->x; 229 | flags |= LEFT_COL; 230 | } 231 | flags |= X_COL; 232 | } 233 | } 234 | // set C to resolved X position 235 | c.x = a->x + velocity->x; 236 | 237 | // move on Y axis 238 | // set C on test Y position 239 | c.y += velocity->y; 240 | for (int i = 0; i < list->size; i++){ 241 | b = &list->rect[i]; 242 | if (a != b && HitboxCheckHitbox(&c, b)) { 243 | // moving down 244 | if (velocity->y > 0.0f) { 245 | velocity->y = (b->y - a->h) - a->y; 246 | flags |= BOTTOM_COL; 247 | } 248 | // moving up 249 | else if (velocity->y < 0.0f) { 250 | velocity->y = (b->y + b->h) - a->y; 251 | flags |= TOP_COL; 252 | } 253 | flags |= Y_COL; 254 | } 255 | } 256 | 257 | rect->x += velocity->x; 258 | rect->y += velocity->y; 259 | return flags; 260 | } 261 | 262 | // Ported from Javidx9 rectangle collision video 263 | bool HitboxVsRaycast(NezVec2_f *rayOrig, NezVec2_f *rayDir, NezRect_f *target, NezVec2_f *colPoint, NezVec2_f *colNormal, float *timeStep){ 264 | *colNormal = (NezVec2_f){0.0f, 0.0f}; 265 | *colPoint = (NezVec2_f){0.0f, 0.0f}; 266 | 267 | // Cache division 268 | NezVec2_f invdir = (NezVec2_f){1.0f / rayDir->x, 1.0f / rayDir->y}; 269 | 270 | // Calculate intersections with rectangle bounding axes 271 | NezVec2_f t_near = (NezVec2_f){(target->x - rayOrig->x) * invdir.x, (target->y - rayOrig->y) * invdir.y}; 272 | NezVec2_f t_far = (NezVec2_f){(target->x + target->w - rayOrig->x) * invdir.x, (target->y + target->h - rayOrig->y) * invdir.y}; 273 | 274 | if (isnan(t_near.x) || isnan(t_near.y)){ 275 | return false;} 276 | if (isnan(t_far.x) || isnan(t_far.y)){ 277 | return false;} 278 | 279 | // Sort distance 280 | if (t_near.x > t_far.x) swapf(&t_near.x, &t_far.x); 281 | if (t_near.y > t_far.y) swapf(&t_near.y, &t_far.y); 282 | 283 | // Early rejection 284 | if (t_near.x > t_far.y || t_near.y > t_far.x) {return false;} 285 | 286 | // Closest 'time' will be the first contact 287 | *timeStep = fmaxf(t_near.x, t_near.y); 288 | 289 | // Furthest 'time' is contact on opposite side of target 290 | float t_hit_far = fminf(t_far.x, t_far.y); 291 | 292 | // Reject if ray direction is pointing away from object 293 | if (t_hit_far < 0){;return false;} 294 | 295 | // Contact point of collision from parametric line equation 296 | *colPoint = (NezVec2_f){rayOrig->x + *timeStep * rayDir->x, rayOrig->y + *timeStep * rayDir->y}; 297 | 298 | if (t_near.x > t_near.y){ 299 | if (invdir.x < 0){ 300 | *colNormal = (NezVec2_f){ 1.0f, 0.0f }; 301 | }else{ 302 | *colNormal = (NezVec2_f){ -1.0f, 0.0f }; 303 | } 304 | }else if (t_near.x < t_near.y){ 305 | if (invdir.y < 0){ 306 | *colNormal = (NezVec2_f){ 0.0f, 1.0f }; 307 | }else{ 308 | *colNormal = (NezVec2_f){ 0.0f, -1.0f }; 309 | } 310 | } 311 | 312 | // Note if t_near == t_far, collision is principly in a diagonal 313 | // so pointless to resolve. By returning a CN={0,0} even though its 314 | // considered a hit, the resolver wont change anything. 315 | 316 | return true; 317 | } 318 | 319 | // Ported from Javidx9 rectangle collision video 320 | bool HitboxDynamicVsHitbox(NezRect_f *movingRect, NezVec2_f *velocity, NezRect_f *target, NezVec2_f *colPoint, NezVec2_f *colNormal, float *timeStep){ 321 | // Check if dynamic rectangle is actually moving - we assume rectangles are NOT in collision to start 322 | if (velocity->x == 0.0f && velocity->y == 0.0f){return false;} 323 | 324 | // Expand target rectangle by source dimensions 325 | NezRect_f expanded_target = (NezRect_f){ 326 | target->x - movingRect->w * 0.5, 327 | target->y - movingRect->h * 0.5, 328 | target->w + movingRect->w, 329 | target->h + movingRect->h, 330 | }; 331 | NezVec2_f rayOrigin = {movingRect->x + movingRect->w * 0.5, movingRect->y + movingRect->h * 0.5}; 332 | NezVec2_f rayDir = {velocity->x, velocity->y}; 333 | 334 | 335 | if (HitboxVsRaycast(&rayOrigin, &rayDir, &expanded_target, colPoint, colNormal, timeStep)){ 336 | return (*timeStep >= 0.0f && *timeStep < 1.0f); 337 | } 338 | else{ 339 | return false; 340 | } 341 | } 342 | 343 | // Ported from Javidx9 rectangle collision video 344 | bool HitboxResolveDynamicRectVsRect(NezRect_f *movingRect, NezVec2_f *velocity, NezRect_f *target){ 345 | NezVec2_f colPoint; 346 | NezVec2_f colNormal; 347 | float timeStep = 0.0f; 348 | if (HitboxDynamicVsHitbox(movingRect, velocity, target, &colPoint, &colNormal, &timeStep)){ 349 | velocity->x += colNormal.x * (float)fabs(velocity->x) * (1 - timeStep); 350 | velocity->y += colNormal.y * (float)fabs(velocity->y) * (1 - timeStep); 351 | return true; 352 | } 353 | return false; 354 | } 355 | 356 | // ### --- *** SORTING *** --- ### 357 | // IntroSort(data, size) 358 | 359 | typedef struct{ 360 | NezRect_f rect; 361 | float toi; 362 | }RectToi; 363 | 364 | typedef struct{ 365 | int size; 366 | RectToi *rectToi; 367 | }RectListExt; 368 | 369 | bool sort_bigoreq(RectToi *a, RectToi *b){ 370 | return a->toi >= b->toi; 371 | } 372 | 373 | bool sort_biger(RectToi *a, RectToi *b){ 374 | return a->toi > b->toi; 375 | } 376 | 377 | void sort_set(RectToi *a, RectToi *b){ 378 | a->rect.x = b->rect.x; 379 | a->rect.y = b->rect.y; 380 | a->rect.w = b->rect.w; 381 | a->rect.h = b->rect.h; 382 | a->toi = b->toi; 383 | } 384 | 385 | void sort_swap(RectToi *a, RectToi *b){ 386 | RectToi temp = *a; 387 | *a = *b; 388 | *b = temp; 389 | } 390 | 391 | void InsertionSort(RectToi* data, int count) { 392 | for (int i = 1; i < count; ++i){ 393 | int j = i; 394 | while (j > 0){ 395 | if (sort_biger( &data[j - 1], &data[j])){ 396 | sort_swap(&data[j], &data[j - 1]); 397 | --j; 398 | } 399 | else{ 400 | break; 401 | } 402 | } 403 | } 404 | } 405 | 406 | void MaxHeapify(RectToi* data, int heapSize, int index) { 407 | int left = (index + 1) * 2 - 1; 408 | int right = (index + 1) * 2; 409 | int largest = 0; 410 | 411 | if (left < heapSize && sort_biger(&data[left], &data[index]) ){ 412 | largest = left; 413 | } 414 | else{ 415 | largest = index; 416 | } 417 | 418 | if (right < heapSize && sort_biger(&data[right], &data[largest])){ 419 | largest = right; 420 | } 421 | 422 | if (largest != index){ 423 | sort_swap(&data[index], &data[largest]); 424 | 425 | MaxHeapify(data, heapSize, largest); 426 | } 427 | } 428 | 429 | void HeapSort(RectToi* data, int count) { 430 | int heapSize = count; 431 | 432 | for (int p = (heapSize - 1) / 2; p >= 0; --p){ 433 | MaxHeapify(data, heapSize, p); 434 | } 435 | 436 | for (int i = count - 1; i > 0; --i) { 437 | sort_swap(&data[i], &data[0]); 438 | 439 | --heapSize; 440 | MaxHeapify(data, heapSize, 0); 441 | } 442 | } 443 | 444 | int Partition(RectToi* data, int left, int right) { 445 | void *pivot = &data[right]; 446 | int i = left; 447 | 448 | for (int j = left; j < right; ++j){ 449 | if ( sort_bigoreq(pivot, &data[j])){ 450 | sort_swap(&data[j], &data[i]); 451 | i++; 452 | } 453 | } 454 | sort_set(&data[right], &data[i]); 455 | sort_set(&data[i], pivot); 456 | return i; 457 | } 458 | 459 | void QuickSortRecursive(RectToi* data, int left, int right){ 460 | if (left < right){ 461 | int q = Partition(data, left, right); 462 | QuickSortRecursive(data, left, q - 1); 463 | QuickSortRecursive(data, q + 1, right); 464 | } 465 | } 466 | 467 | void IntroSort(RectToi* data, int count) { 468 | int partitionSize = Partition(data, 0, count - 1); 469 | 470 | if (partitionSize < 16) { 471 | InsertionSort(data, count); 472 | } 473 | else if (partitionSize >(2 * log(count))){ 474 | HeapSort(data, count); 475 | } 476 | else{ 477 | QuickSortRecursive(data, 0, count - 1); 478 | } 479 | } 480 | 481 | char HitboxMoveAndCollideContinuous(NezRect_f *rect, NezVec2_f *velocity, NezRect_f *bodies, int count, NezGridMap *grid){ 482 | NezRect_f colArea = HitboxExpand(rect, velocity); 483 | RectListExt colList = {0}; 484 | int tileCount = 0; 485 | NezVec2_f cp, cn; 486 | int colCount = 0; 487 | char flags = 0; 488 | if (grid){ 489 | int cellArea[4]; // x, y, w, h 490 | HitboxGridArea(&colArea, grid, cellArea); // position on grid and tiles taken 491 | int t = cellArea[3] * cellArea[3]; 492 | NezRect_f *tiles = malloc(sizeof(NezRect_f) * t); // allocate for tile rectangles 493 | HitboxListFromGrid(grid, cellArea, &tiles, &tileCount); // receive tile rects and count 494 | colList.rectToi = malloc(sizeof(RectToi) * (count + tileCount)); // allocate total rects 495 | 496 | for(int i = 0; i < tileCount; i++){ 497 | colList.rectToi[i].rect = tiles[i]; 498 | } 499 | if (tiles){ free(tiles); } 500 | } 501 | else{ 502 | colList.rectToi = malloc(sizeof(RectToi) * count); 503 | } 504 | colList.size = count + tileCount; 505 | // insert bodies 506 | for (int i = 0; i < count; i++){ 507 | colList.rectToi[tileCount + i].rect = bodies[i]; 508 | } 509 | // Get collision TOI for each collision 510 | for (int i = 0; i < count + tileCount; i++){ 511 | if(HitboxDynamicVsHitbox(rect, velocity, &colList.rectToi[i].rect, &cp, &cn, &colList.rectToi[i].toi)){ 512 | colCount += 1; 513 | if (cn.x < 0.0f){flags |= RIGHT_COL;} 514 | else if (cn.x > 0.0f){flags |= LEFT_COL;} 515 | if (cn.y < 0.0f){flags |= BOTTOM_COL;} 516 | else if (cn.y > 0.0f){flags |= TOP_COL;} 517 | } 518 | else{ 519 | colList.rectToi[i].toi = 2.0f; 520 | } 521 | } 522 | 523 | // Sort by TOI (time of impact) 524 | IntroSort(colList.rectToi, colList.size); 525 | 526 | 527 | for (int i = 0; i < colCount; i++){ 528 | HitboxResolveDynamicRectVsRect(rect, velocity, &colList.rectToi[i].rect); 529 | } 530 | 531 | if(colList.rectToi){ 532 | free(colList.rectToi); 533 | } 534 | rect->x += velocity->x; 535 | rect->y += velocity->y; 536 | 537 | return flags; 538 | } 539 | 540 | 541 | /* // NOT USED 542 | // bool HitboxAxisCollisionCheck(NezRect_f *a, NezRect_f *b, float *dirX, float *dirY) { 543 | // float d = (float)sqrt(*dirX * *dirX + *dirY * *dirY); 544 | // float projX = -*dirY / d; 545 | // float projY = *dirX / d; 546 | 547 | // float min_r1 = INFINITY; 548 | // float max_r1 = -INFINITY; 549 | // float a0 = ((a->x) * projX + a->y * (projY)); 550 | // float a1 = ((a->x + a->w) * projX + a->y * projY); 551 | // float a2 = ((a->x + a->w) * projX + (a->y + a->h) * projY); 552 | // float a3 = ((a->x) * projX + (a->y + a->h) * projY); 553 | // min_r1 = fminf(min_r1, a0); max_r1 = fmaxf(max_r1, a0); 554 | // min_r1 = fminf(min_r1, a1); max_r1 = fmaxf(max_r1, a1); 555 | // min_r1 = fminf(min_r1, a2); max_r1 = fmaxf(max_r1, a2); 556 | // min_r1 = fminf(min_r1, a3); max_r1 = fmaxf(max_r1, a3); 557 | 558 | // float min_r2 = INFINITY; 559 | // float max_r2 = -INFINITY; 560 | // float b0 = ((b->x) * projX + b->y * (projY)); 561 | // float b1 = ((b->x + b->w) * projX + b->y * projY); 562 | // float b2 = ((b->x + b->w) * projX + (b->y + b->h) * projY); 563 | // float b3 = ((b->x) * projX + (b->y + b->h) * projY); 564 | // min_r2 = fminf(min_r2, b0); max_r2 = fmaxf(max_r2, b0); 565 | // min_r2 = fminf(min_r2, b1); max_r2 = fmaxf(max_r2, b1); 566 | // min_r2 = fminf(min_r2, b2); max_r2 = fmaxf(max_r2, b2); 567 | // min_r2 = fminf(min_r2, b3); max_r2 = fmaxf(max_r2, b3); 568 | 569 | // if (!(max_r2 >= min_r1 && max_r1 >= min_r2)){ 570 | // return false; 571 | // }else{ 572 | // return true; 573 | // } 574 | // } 575 | */ 576 | 577 | #endif //HITBOX_COLLISIONS_IMPLEMENTATION 578 | 579 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 nezvers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gamedev systems in C 2 | 3 | ## Single header libraries that comes with example projects using Raylib 4 | 5 | 6 | -------------------------------------------------------------------------------- /SpriteSystem/README.md: -------------------------------------------------------------------------------- 1 | # Sprite system in C 2 | 3 | ![](https://github.com/nezvers/GameSystemsInC/raw/master/SpriteSystem/examples/resources/Preview_Sprite_anim_scale.gif) 4 | 5 | ## sprite_rect.h 6 | - Manages rectangle from texture to drawing rectangle 7 | - Framework independent 8 | - No allocations, user pre-allocated/ managed memory 9 | - Overridable Vector2 & Rectangle typedefs 10 | ```C 11 | #define NEZ_SPRITE_RECT_IMPLEMENTATION // use for *.c section implementation 12 | #define NEZ_VEC2_F Vector2 13 | #define NEZ_RECT_F Rectangle 14 | #include "sprite_rect.h" 15 | ``` 16 | 17 | ## sprite.h 18 | - Uses allocation 19 | - Tied to Raylib's Vector2, Rectangle, Texture 20 | 21 | ``` 22 | #define NEZ_SPRITE_IMPLEMENTATION // use for *.c section 23 | #include "sprite.h" 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /SpriteSystem/examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | include(FetchContent) 3 | 4 | 5 | 6 | # Raylib 7 | FetchContent_Declare( 8 | raylib 9 | DOWNLOAD_EXTRACT_TIMESTAMP OFF 10 | URL https://github.com/raysan5/raylib/archive/refs/tags/5.5.zip 11 | ) 12 | #FetchContent_GetProperties(raylib) 13 | set(FETCHCONTENT_QUIET NO) 14 | set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples 15 | set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games 16 | FetchContent_MakeAvailable(raylib) 17 | 18 | include_directories(${CMAKE_SOURCE_DIR}/..) 19 | 20 | 21 | set(ProjectName Test_Sprite) 22 | project(${ProjectName}) 23 | set(CMAKE_CXX_STANDARD 17) 24 | file(GLOB Sprite_SRC_FILES "sprite_raylib.c") 25 | add_executable(${ProjectName} ${Sprite_SRC_FILES}) 26 | target_link_libraries(${ProjectName} raylib) 27 | target_compile_definitions(${ProjectName} PUBLIC SOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}") 28 | 29 | set(ProjectName Test_SpriteRect) 30 | project(${ProjectName}) 31 | set(CMAKE_CXX_STANDARD 17) 32 | file(GLOB SpriteRect_SRC_FILES "sprite_rect_raylib.c") 33 | add_executable(${ProjectName} ${SpriteRect_SRC_FILES}) 34 | target_link_libraries(${ProjectName} raylib) 35 | target_compile_definitions(${ProjectName} PUBLIC SOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}") 36 | 37 | 38 | 39 | # Setting ASSETS_PATH 40 | target_compile_definitions(${PROJECT_NAME} PUBLIC ASSETS_PATH="${CMAKE_CURRENT_SOURCE_DIR}/assets/") # Set the asset path macro to the absolute path on the dev machine 41 | #target_compile_definitions(${PROJECT_NAME} PUBLIC ASSETS_PATH="./assets") # Set the asset path macro in release mode to a relative path that assumes the assets folder is in the same directory as the game executable -------------------------------------------------------------------------------- /SpriteSystem/examples/resources/Preview_Sprite_anim_scale.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nezvers/GameSystemsInC/b0118784f584446fc8a17c9369c5fdb30820d0bc/SpriteSystem/examples/resources/Preview_Sprite_anim_scale.gif -------------------------------------------------------------------------------- /SpriteSystem/examples/resources/player_sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nezvers/GameSystemsInC/b0118784f584446fc8a17c9369c5fdb30820d0bc/SpriteSystem/examples/resources/player_sheet.png -------------------------------------------------------------------------------- /SpriteSystem/examples/sprite_raylib.c: -------------------------------------------------------------------------------- 1 | #include "raylib.h" 2 | 3 | #define NEZ_SPRITE_IMPLEMENTATION 4 | #include "../sprite.h" 5 | 6 | //#define PLATFORM_WEB 7 | 8 | #if defined(PLATFORM_WEB) 9 | #include 10 | #endif 11 | 12 | 13 | int screenWidth = 640; 14 | int screenHeight = 360; 15 | 16 | void InitGame(void); 17 | void GameLoop(void); 18 | 19 | void UpdateButtons(void); 20 | void DrawInterface(void); 21 | 22 | Texture texture; 23 | Camera2D camera = {{0}, {0}, 0.0f, 4.0f}; 24 | float x; 25 | float y; 26 | Sprite *sprite; 27 | SpriteAnimation idleAnim; 28 | SpriteAnimation walkAnim; 29 | int idle; 30 | int walk; 31 | int currentAnimation; 32 | 33 | Rectangle xOrigPlus; 34 | Rectangle xOrigMinus; 35 | Rectangle yOrigPlus; 36 | Rectangle yOrigMinus; 37 | 38 | Rectangle xScalePlus; 39 | Rectangle xScaleMinus; 40 | Rectangle yScalePlus; 41 | Rectangle yScaleMinus; 42 | 43 | 44 | //---------------------------------------------------------------------------------- 45 | int main(){ 46 | InitGame(); 47 | 48 | #if defined(PLATFORM_WEB) 49 | emscripten_set_main_loop(GameLoop, 0, 1); 50 | #else 51 | SetTargetFPS(60); 52 | 53 | while (!WindowShouldClose()){ 54 | GameLoop(); 55 | } 56 | #endif 57 | 58 | UnloadTexture(texture); 59 | 60 | CloseWindow(); 61 | return 0; 62 | } 63 | 64 | 65 | void InitGame(){ 66 | SetConfigFlags(FLAG_WINDOW_RESIZABLE); 67 | InitWindow(screenWidth, screenHeight, "SpriteSystem test"); 68 | UpdateButtons(); 69 | 70 | texture = LoadTexture(SOURCE_PATH"/resources/player_sheet.png"); 71 | 72 | sprite = SpriteNewFromSheet(&texture, 16, 16, 8, 16); 73 | sprite->x = 30; 74 | sprite->y = 30; 75 | 76 | int idleList[] = {0}; 77 | idleAnim = SpriteAnimationNew(idleList, 1, 8); 78 | idle = SpriteAddAnimation(sprite, idleAnim); 79 | 80 | int walkList[] = {1,2,3,4,5,6}; 81 | walkAnim = SpriteAnimationNew(walkList, 6, 8); 82 | walk = SpriteAddAnimation(sprite, walkAnim); 83 | 84 | x = (float)sprite->x; 85 | y = (float)sprite->y; 86 | } 87 | 88 | void UpdateInput(); 89 | void GameLoop(void){ 90 | if (IsWindowResized()){ 91 | screenWidth = GetScreenWidth(); 92 | screenHeight = GetScreenHeight(); 93 | UpdateButtons(); 94 | } 95 | UpdateInput(); 96 | SpritePlay(sprite, currentAnimation, 1.0/60.0); 97 | 98 | BeginDrawing(); 99 | 100 | ClearBackground(RAYWHITE); 101 | BeginMode2D(camera); 102 | 103 | SpriteDraw(sprite); 104 | DrawLine(sprite->x -2, sprite->y, sprite->x +2, sprite->y,BLACK); 105 | DrawLine(sprite->x, sprite->y -2, sprite->x, sprite->y +2,BLACK); 106 | 107 | 108 | EndMode2D(); 109 | 110 | DrawInterface(); 111 | 112 | EndDrawing(); 113 | } 114 | 115 | void UpdateInput(){ 116 | if(IsKeyPressed(KEY_RIGHT)){ 117 | sprite->xScale = 1.0f; 118 | printf("xScale: %f\n", sprite->xScale); 119 | } 120 | if(IsKeyPressed(KEY_LEFT)){ 121 | sprite->xScale = -1.0f; 122 | printf("xScale: %f\n", sprite->xScale); 123 | } 124 | if(IsKeyPressed(KEY_UP)){ 125 | sprite->yScale = 1.0f; 126 | printf("yScale: %f\n", sprite->yScale); 127 | } 128 | if(IsKeyPressed(KEY_DOWN)){ 129 | sprite->yScale = -1.0f; 130 | printf("yScale: %f\n", sprite->yScale); 131 | } 132 | 133 | int xDir = (int)IsKeyDown(KEY_D) - (int)IsKeyDown(KEY_A); 134 | int yDir = (int)IsKeyDown(KEY_S) - (int)IsKeyDown(KEY_W); 135 | float spd = 0.4f; 136 | x += spd * xDir; 137 | y += spd *yDir; 138 | sprite->x = (int)x; 139 | sprite->y = (int)y; 140 | 141 | if(xDir != 0 || yDir != 0){ 142 | currentAnimation = walk; 143 | } 144 | else{ 145 | currentAnimation = idle; 146 | } 147 | 148 | if (xDir != 0){ 149 | sprite->xScale = xDir; 150 | } 151 | 152 | // MOUSE TO INTERFACE 153 | if(IsMouseButtonPressed(0)){ 154 | Vector2 mouse = GetMousePosition(); 155 | if(CheckCollisionPointRec(mouse, xOrigPlus)){ 156 | sprite->xOrigin++; 157 | }else if(CheckCollisionPointRec(mouse, xOrigMinus)){ 158 | sprite->xOrigin--; 159 | }else if(CheckCollisionPointRec(mouse, yOrigPlus)){ 160 | sprite->yOrigin++; 161 | }else if(CheckCollisionPointRec(mouse, yOrigMinus)){ 162 | sprite->yOrigin--; 163 | }else if(CheckCollisionPointRec(mouse, xScalePlus)){ 164 | sprite->xScale+= 0.1; 165 | }else if(CheckCollisionPointRec(mouse, xScaleMinus)){ 166 | sprite->xScale-= 0.1; 167 | }else if(CheckCollisionPointRec(mouse, yScalePlus)){ 168 | sprite->yScale+= 0.1; 169 | }else if(CheckCollisionPointRec(mouse, yScaleMinus)){ 170 | sprite->yScale-= 0.1; 171 | } 172 | } 173 | } 174 | 175 | void UpdateButtons(){ 176 | float w = 16.0f; 177 | float h = 16.0f; 178 | xOrigPlus = (Rectangle){screenWidth-(w*2), 0.0f, w, h}; 179 | xOrigMinus = (Rectangle){screenWidth-(w*3) -2, 0.0f, w, h}; 180 | yOrigPlus = (Rectangle){screenWidth-(w*2), h +2, w, h}; 181 | yOrigMinus = (Rectangle){screenWidth-(w*3) -2, h +2, w, h}; 182 | 183 | xScalePlus = (Rectangle){screenWidth-(w*2), h*3 +6, w, h}; 184 | xScaleMinus = (Rectangle){screenWidth-(w*3) -2, h*3 +6, w, h}; 185 | yScalePlus = (Rectangle){screenWidth-(w*2), h*4 +8, w, h}; 186 | yScaleMinus = (Rectangle){screenWidth-(w*3) -2, h*4 +8, w, h}; 187 | } 188 | 189 | void DrawInterface(void){ 190 | //Origin 191 | DrawRectangleLinesEx(xOrigPlus, 1, LIGHTGRAY); 192 | DrawRectangleLinesEx(xOrigMinus, 1, LIGHTGRAY); 193 | DrawRectangleLinesEx(yOrigPlus, 1, LIGHTGRAY); 194 | DrawRectangleLinesEx(yOrigMinus, 1, LIGHTGRAY); 195 | DrawText("x+", (int)xOrigPlus.x+4, (int)xOrigPlus.y+4, 8, BLACK); 196 | DrawText("x-", (int)xOrigMinus.x+4, (int)xOrigMinus.y+4, 8, BLACK); 197 | DrawText("y+", (int)yOrigPlus.x+4, (int)yOrigPlus.y+4, 8, BLACK); 198 | DrawText("y-", (int)yOrigMinus.x+4, (int)yOrigMinus.y+4, 8, BLACK); 199 | 200 | const char * text = TextFormat("origin: (%d, %d)", sprite->xOrigin, sprite->yOrigin); 201 | int textWidth = MeasureText(text, 8); 202 | DrawText(text, screenWidth-textWidth - (int)yOrigMinus.width, (int)yOrigMinus.y+(int)yOrigMinus.height +2, 8, BLACK); 203 | 204 | // Scale 205 | DrawRectangleLinesEx(xScalePlus, 1, LIGHTGRAY); 206 | DrawRectangleLinesEx(xScaleMinus, 1, LIGHTGRAY); 207 | DrawRectangleLinesEx(yScalePlus, 1, LIGHTGRAY); 208 | DrawRectangleLinesEx(yScaleMinus, 1, LIGHTGRAY); 209 | DrawText("x+", (int)xScalePlus.x+4, (int)xScalePlus.y+4, 8, BLACK); 210 | DrawText("x-", (int)xScaleMinus.x+4, (int)xScaleMinus.y+4, 8, BLACK); 211 | DrawText("y+", (int)yScalePlus.x+4, (int)yScalePlus.y+4, 8, BLACK); 212 | DrawText("y-", (int)yScaleMinus.x+4, (int)yScaleMinus.y+4, 8, BLACK); 213 | 214 | const char * text2 = TextFormat("scale: (%.2f, %.2f)", sprite->xScale, sprite->yScale); 215 | textWidth = MeasureText(text2, 8); 216 | DrawText(text2, screenWidth-textWidth - (int)yScaleMinus.width, (int)yScaleMinus.y+(int)yScaleMinus.height +2, 8, BLACK); 217 | } 218 | 219 | 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /SpriteSystem/examples/sprite_rect_raylib.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "raylib.h" 4 | 5 | #define NEZ_SPRITE_RECT_IMPLEMENTATION 6 | #define NEZ_VEC2_F Vector2 7 | #define NEZ_RECT_F Rectangle 8 | #include "../sprite_rect.h" 9 | 10 | Texture2D plumber_texture = { 0 }; 11 | Sprite plumber_sprite_1; 12 | Sprite plumber_sprite_2; 13 | SpriteAnimation plumber_animations[2]; 14 | 15 | // Array of texture offsets used for Sprite 16 | Vector2 plumber_images[] = { {0.0f,0.0f}, { 16.0f,0.0f }, { 32.0f,0.0f }, { 48.0f,0.0f }, { 64.0f,0.0f }, { 80.0f,0.0f }, { 96.0f,0.0f } }; 17 | 18 | // Array of texture offset IDs for each animation 19 | int plumber_idle_IDs[] = { 0 }; 20 | int plumber_walk_IDs[] = { 1,2,3,4,5,6 }; 21 | 22 | 23 | int main(void) 24 | { 25 | const int screenWidth = 800; 26 | const int screenHeight = 450; 27 | InitWindow(screenWidth, screenHeight, "raylib [core] example - basic window"); 28 | SetTargetFPS(60); 29 | 30 | // Initialization 31 | plumber_texture = LoadTexture(SOURCE_PATH"/resources/player_sheet.png"); 32 | plumber_animations[0] = SpriteAnimationCreate(1, 12, plumber_idle_IDs); 33 | plumber_animations[1] = SpriteAnimationCreate(6, 12, plumber_walk_IDs); 34 | plumber_sprite_1 = SpriteCreate(16, 16, -8, -16, 7, plumber_images); // Sets configurations 35 | plumber_sprite_2 = plumber_sprite_1; // Copy configuration 36 | 37 | // Main game loop 38 | while (!WindowShouldClose()) 39 | { 40 | BeginDrawing(); 41 | 42 | ClearBackground(RAYWHITE); 43 | 44 | // visualize origin position 45 | DrawLine(200, 170, 200, 230, PINK); 46 | DrawLine(170, 200, 230, 200, PINK); 47 | 48 | // reusable default values 49 | Vector2 origin = (Vector2){ 0.0f, 0.0f }; 50 | float rotation = 0.0f; 51 | 52 | // for demonstration set values 53 | plumber_sprite_1.y = 200; 54 | plumber_sprite_2.y = 200; 55 | 56 | // Used for animation end detection 57 | bool is_finished = false; 58 | 59 | plumber_sprite_1.x = 200; 60 | plumber_sprite_1.x_scale = 2.f; 61 | plumber_sprite_1.y_scale = 2.f; 62 | // plays walking animation 63 | SpritePlay(&plumber_sprite_1, &plumber_animations[1], GetFrameTime(), true, &is_finished); 64 | DrawRectangleLinesEx(plumber_sprite_1.spr_rect, 1.f, BLACK); 65 | DrawTexturePro(plumber_texture, plumber_sprite_1.img_rect, plumber_sprite_1.spr_rect, origin, rotation, WHITE); 66 | 67 | //plays idle animation 68 | plumber_sprite_2.x = 200; 69 | plumber_sprite_2.x_scale = -4.f; 70 | plumber_sprite_2.y_scale = -4.f; 71 | SpritePlay(&plumber_sprite_2, &plumber_animations[0], GetFrameTime(), true, &is_finished); 72 | DrawRectangleLinesEx(plumber_sprite_2.spr_rect, 1.f, BLACK); 73 | DrawTexturePro(plumber_texture, plumber_sprite_2.img_rect, plumber_sprite_2.spr_rect, origin, rotation, WHITE); 74 | 75 | 76 | EndDrawing(); 77 | } 78 | 79 | // De-Initialization 80 | UnloadTexture(plumber_texture); 81 | 82 | CloseWindow(); // Close window and OpenGL context 83 | 84 | return 0; 85 | } -------------------------------------------------------------------------------- /SpriteSystem/sprite.h: -------------------------------------------------------------------------------- 1 | #ifndef NEZ_SPRITE_H 2 | #define NEZ_SPRITE_H 3 | 4 | #include "raylib.h" 5 | #include "stdlib.h" 6 | #include 7 | 8 | typedef struct{ 9 | float image; // used as timer (moduled to imageCount) and on drawing floored(cast as int) 10 | int imageCount; // number of images in the animation 11 | int fps; // time in seconds for each frame 12 | int *imageID; // array of sprite IDs 13 | }SpriteAnimation; 14 | 15 | typedef struct{ 16 | int x; 17 | int y; 18 | int w; // individual sprite image width 19 | int h; // individual sprite image height 20 | int xOrigin; // origin x point against source rectangle 21 | int yOrigin; // origin y point against source rectangle 22 | float xScale; // scale output rectangle width depending on origin x position 23 | float yScale; // scale output rectangle height depending on origin y position 24 | int imageCount; // number of all frames 25 | int animation; // ID for current animation 26 | int animationCount; // animationList size 27 | Texture2D *texture; // texture holding pointer to sprite sheet texture 28 | Vector2 *imagePos; // list of source x&y on the texture 29 | SpriteAnimation *animationList; 30 | }Sprite; 31 | 32 | #ifndef NEZSPAPI 33 | #ifdef NEZ_SPRITE_STATIC 34 | #define NEZSSAPI static 35 | #else 36 | #define NEZSSAPI extern 37 | #endif 38 | #endif 39 | 40 | #ifdef __cplusplus 41 | extern "C" { 42 | #endif 43 | 44 | // Automatically create sprite image collection 45 | NEZSSAPI Sprite* 46 | SpriteNewFromSheet(Texture2D *tex, int w, int h, int xO, int yO); 47 | // Version for predetermined frame position list array 48 | NEZSSAPI Sprite* 49 | SpriteNewFromAtlas(Texture2D *tex, int w, int h, int xO, int yO, Vector2 *posList, int posCount); 50 | // Free allocated memory 51 | NEZSSAPI void 52 | SpriteDestroy(Sprite *sprite, bool destroyAnimations); 53 | // Adds SpriteAnimation to the Sprite and returns animation ID for it 54 | NEZSSAPI int 55 | SpriteAddAnimation(Sprite *sprite, SpriteAnimation anim); 56 | // Adds SpriteAnimation list and returns ID for first anamation in the new list 57 | NEZSSAPI int 58 | SpriteAddAnimationList(Sprite *sprite, SpriteAnimation *anim, int animationCount); 59 | // Create animation from list of sprite image collection 60 | NEZSSAPI SpriteAnimation 61 | SpriteAnimationNew(int *imageIDList, int imageCount, int fps); 62 | // Free memory for animation 63 | NEZSSAPI void 64 | SpriteAnimationDestroy(SpriteAnimation *anim); 65 | // Animation update, requires delta time (seconds) since last frame. Or (1.0/60.0) for constant time of 60FPS 66 | NEZSSAPI void 67 | SpritePlay(Sprite *sprite, int animID, float delta); 68 | // Animation update that can trigger callback for AnimationFinished with animation ID 69 | NEZSSAPI void 70 | SpritePlayWithCallback(Sprite *sprite, int animID, float delta, void (*AnimationFinished)(int)); 71 | // Just draw the sprite 72 | NEZSSAPI void 73 | SpriteDraw(Sprite *sprite); 74 | 75 | #ifdef __cplusplus 76 | } 77 | #endif 78 | #endif // NEZ_SPRITE_H 79 | 80 | //---------------------------------------------------------------------------- 81 | 82 | #ifdef NEZ_SPRITE_IMPLEMENTATION 83 | #undef NEZ_SPRITE_IMPLEMENTATION 84 | float SpriteAbs(float x){return x>0.0 ? x : -x;} 85 | 86 | Sprite* 87 | SpriteNewFromSheet(Texture2D *tex, int w, int h, int xO, int yO){ 88 | Sprite *s = (Sprite*)malloc(sizeof(Sprite)); 89 | *s = (Sprite){0}; 90 | s->w = w; 91 | s->h = h; 92 | s->xOrigin = xO; 93 | s->yOrigin = yO; 94 | s->xScale = 1.0f; 95 | s->yScale = 1.0f; 96 | s->texture = tex; 97 | s->animation = -1; 98 | 99 | int xFrames = tex->width / w; 100 | int yFrames = tex->height / h; 101 | s->imageCount = xFrames *yFrames; 102 | s->imagePos = (Vector2*)malloc(sizeof(Vector2)*xFrames *yFrames); 103 | 104 | for(int y=0; yimagePos[y *xFrames +x] = (Vector2){(float)x*w, (float)y*h}; 107 | } 108 | } 109 | 110 | return s; 111 | } 112 | 113 | Sprite* 114 | SpriteNewFromAtlas(Texture2D *tex, int w, int h, int xO, int yO, Vector2 *posList, int posCount){ 115 | Sprite *s = (Sprite*)malloc(sizeof(Sprite)); 116 | *s = (Sprite){0}; 117 | s->w = w; 118 | s->h = h; 119 | s->xOrigin = xO; 120 | s->yOrigin = yO; 121 | s->xScale = 1.0f; 122 | s->yScale = 1.0f; 123 | s->texture = tex; 124 | s->animation = -1; 125 | 126 | s->imageCount = posCount; 127 | s->imagePos = (Vector2*)malloc(sizeof(Vector2) *posCount); 128 | 129 | for(int i=0; iimagePos[i] = posList[i]; 131 | } 132 | 133 | return s; 134 | } 135 | 136 | void 137 | SpriteDestroy(Sprite *sprite, bool destroyAnimations){ 138 | if(destroyAnimations){ 139 | for(int i=0; ianimationCount; i++){ 140 | SpriteAnimationDestroy(&sprite->animationList[i]); 141 | } 142 | } 143 | free(sprite->animationList); 144 | free(sprite->imagePos); 145 | free(sprite); 146 | } 147 | 148 | int 149 | SpriteAddAnimation(Sprite *sprite, SpriteAnimation anim){ 150 | if(!anim.imageID){ 151 | printf("Invalid animation\n"); 152 | return -1; 153 | } 154 | 155 | SpriteAnimation *newList = (SpriteAnimation*)malloc(sizeof(SpriteAnimation) *(sprite->animationCount +1)); 156 | for(int i=0; i< sprite->animationCount; i++){ 157 | newList[i] = sprite->animationList[i]; 158 | } 159 | free(sprite->animationList); 160 | sprite->animationList = newList; 161 | sprite->animationList[sprite->animationCount] = anim; 162 | sprite->animationCount++; 163 | return sprite->animationCount -1; 164 | } 165 | 166 | int 167 | SpriteAddAnimationList(Sprite *sprite, SpriteAnimation *anim, int animationCount){ 168 | SpriteAnimation *newList = (SpriteAnimation*)malloc(sizeof(SpriteAnimation) *(sprite->animationCount +animationCount)); 169 | //old animations 170 | for(int i=0; i< sprite->animationCount; i++){ 171 | newList[i] = sprite->animationList[i]; 172 | } 173 | //new animations 174 | for(int i=0; i< animationCount; i++){ 175 | newList[sprite->animationCount +i] = anim[i]; 176 | } 177 | free(sprite->animationList); 178 | sprite->animationList = newList; 179 | sprite->animationCount += animationCount; 180 | return sprite->animationCount -animationCount; 181 | } 182 | 183 | 184 | 185 | SpriteAnimation 186 | SpriteAnimationNew(int *imageIDList, int imageCount, int fps){ 187 | SpriteAnimation sa = {0}; 188 | sa.fps = fps; 189 | sa.imageCount = imageCount; 190 | sa.imageID = (int*)malloc(sizeof(int) *imageCount); 191 | for (int i=0; iimageID); 200 | } 201 | 202 | 203 | void 204 | SpritePlay(Sprite *sprite, int animID, float delta){ 205 | if(sprite->animation != animID){ 206 | sprite->animation = animID; 207 | sprite->animationList[animID].image = 0.0f; 208 | } 209 | else{ 210 | sprite->animationList[animID].image += delta *sprite->animationList[animID].fps; 211 | if(sprite->animationList[animID].image > sprite->animationList[animID].imageCount){ 212 | sprite->animationList[animID].image -= (float)(int)sprite->animationList[animID].image; 213 | } 214 | } 215 | } 216 | 217 | void 218 | SpritePlayWithCallback(Sprite *sprite, int animID, float delta, void (*AnimationFinished)(int)){ 219 | if(sprite->animation != animID){ 220 | sprite->animation = animID; 221 | sprite->animationList[animID].image = 0.0f; 222 | } 223 | else{ 224 | sprite->animationList[animID].image += delta *sprite->animationList[animID].fps; 225 | if(sprite->animationList[animID].image > sprite->animationList[animID].imageCount){ 226 | sprite->animationList[animID].image -= (float)(int)sprite->animationList[animID].image; 227 | AnimationFinished(animID); 228 | } 229 | } 230 | } 231 | 232 | 233 | void 234 | SpriteDraw(Sprite *sprite){ 235 | SpriteAnimation *anim = &sprite->animationList[sprite->animation]; 236 | int id = anim->imageID[(int)anim->image]; 237 | Vector2 sourcePos = sprite->imagePos[id]; 238 | 239 | Vector2 offset = (Vector2){sprite->xOrigin *sprite->xScale, sprite->yOrigin *sprite->yScale}; 240 | 241 | 242 | float x = sprite->x -offset.x; 243 | float y = sprite->y -offset.y; 244 | float w = sprite->w *SpriteAbs(sprite->xScale); 245 | float h = sprite->h *SpriteAbs(sprite->yScale); 246 | 247 | 248 | Rectangle source = {sourcePos.x, sourcePos.y, (float)sprite->w, (float)sprite->h}; 249 | if (sprite->xScale < 0.0){ 250 | source.width = -(float)sprite->w; 251 | x -= w; 252 | } 253 | if (sprite->yScale < 0.0){ 254 | source.y += sprite->h; 255 | source.height *= -1; 256 | y -= h; 257 | } 258 | 259 | Rectangle dest = {x, y, w, h}; 260 | Vector2 origin = (Vector2){ 0.0f, 0.0f}; 261 | float rotation = 0.0f; 262 | 263 | DrawTexturePro(*sprite->texture, source, dest, origin, rotation, WHITE); 264 | } 265 | 266 | #endif // NEZ_SPRITE_IMPLEMENTATION 267 | 268 | 269 | -------------------------------------------------------------------------------- /SpriteSystem/sprite_rect.h: -------------------------------------------------------------------------------- 1 | #ifndef NEZ_SPRITE_RECT_H 2 | #define NEZ_SPRITE_RECT_H 3 | 4 | /* 5 | #ifndef MEM_ALLOC 6 | #include "stdlib.h" 7 | #define MEM_ALLOC(x) maloc(x) 8 | 9 | #ifndef MEM_FREE 10 | #define MEM_FREE(x) free(x) 11 | #endif // !MEM_FREE 12 | 13 | #endif // !MEM_ALLOC 14 | */ 15 | 16 | 17 | #ifndef NEZ_VEC2_F 18 | #define NEZ_VEC2_F NezVec2_f 19 | // Vector2, 2 components 20 | typedef struct NezVec2_f { 21 | float x; // Vector x component 22 | float y; // Vector y component 23 | } NezVec2_f; 24 | #endif // !NEZ_VEC2_F 25 | 26 | #ifndef NEZ_RECT_F 27 | #define NEZ_RECT_F NezRect_f 28 | // Rectangle, 4 components 29 | typedef struct NezRect_f { 30 | float x; // Rectangle top-left corner position x 31 | float y; // Rectangle top-left corner position y 32 | float width; // Rectangle width 33 | float height; // Rectangle height 34 | } NezRect_f; 35 | #endif // !NEZ_RECT_F 36 | 37 | 38 | // Struct for each animation. User needs to provide an array of IDs for image rect positions used in an animation. 39 | typedef struct{ 40 | int image_count; // number of images in the animation 41 | int fps; // time in seconds for each frame 42 | int *id_list; // array of sprite image rect position IDs 43 | }SpriteAnimation; 44 | 45 | 46 | // TODO: Include rectangles for image and position 47 | typedef struct{ 48 | int x; // origin x 49 | int y; // origin y 50 | int w; // sprite image width 51 | int h; // sprite image height 52 | int x_offset; // rectangle x offset from origin 53 | int y_offset; // rectangle y offset from origin 54 | float x_scale; // scale output rectangle width depending on origin x position 55 | float y_scale; // scale output rectangle height depending on origin y position 56 | int image_count; // number of all frames 57 | NEZ_RECT_F img_rect; // rectangle for image location on texture 58 | NEZ_RECT_F spr_rect; // rectangle for drawing on screen 59 | float time; // used as timer (moduled to imageCount) 60 | NEZ_VEC2_F *image_pos_list; // list of source x&y on the texture 61 | }Sprite; 62 | 63 | #ifndef NEZ_SPRITE_API 64 | #ifdef NEZ_SPRITE_STATIC 65 | #define NEZ_SPRITE_API static 66 | #else 67 | #define NEZ_SPRITE_API extern 68 | #endif 69 | #endif 70 | 71 | #ifdef __cplusplus 72 | extern "C" { 73 | #endif 74 | 75 | // Initialize a sprite 76 | // User needs to provide arrays for image rect positions and array of SpriteAnimations 77 | NEZ_SPRITE_API Sprite 78 | SpriteCreate(int _w, int _h, int x_off, int y_off, int total_img_count, NEZ_VEC2_F img_pos[]); 79 | 80 | // Create sprite animation 81 | // User needs to provide an array of IDs for image rect positions used in an animation 82 | NEZ_SPRITE_API SpriteAnimation 83 | SpriteAnimationCreate(int image_count, int fps, int id_list[]); 84 | 85 | // Switches animation and resets timer 86 | NEZ_SPRITE_API void 87 | SpriteResetAnimation(Sprite* sprite); 88 | 89 | // Handles logic of providing rectangle from texture and it's location in game with scaling 90 | NEZ_SPRITE_API void 91 | SpritePlay(Sprite *sprite, SpriteAnimation* sprite_anim, float delta, bool loop, bool* is_finished); 92 | 93 | #ifdef __cplusplus 94 | } 95 | #endif 96 | #endif // NEZ_SPRITE_H 97 | 98 | //---------------------------------------------------------------------------- 99 | 100 | #ifdef NEZ_SPRITE_RECT_IMPLEMENTATION 101 | #undef NEZ_SPRITE_RECT_IMPLEMENTATION 102 | float SpriteAbs(float x){return x>0.0 ? x : -x;} 103 | int SpriteSign(float x) { return x < 0.0 ? -1 : 1; } 104 | 105 | Sprite 106 | SpriteCreate(int _w, int _h, int x_off, int y_off, int total_img_count, NEZ_VEC2_F img_pos[]) { 107 | Sprite sprite = { 0 }; 108 | sprite.w = _w; 109 | sprite.h = _h; 110 | sprite.x_offset = x_off; 111 | sprite.y_offset = y_off; 112 | sprite.image_count = total_img_count; 113 | sprite.image_pos_list = img_pos; 114 | return sprite; 115 | } 116 | 117 | SpriteAnimation 118 | SpriteAnimationCreate(int image_count, int fps, int id_list[]) { 119 | SpriteAnimation anim = { 0 }; 120 | anim.image_count = image_count; 121 | anim.fps = fps; 122 | anim.id_list = id_list; 123 | return anim; 124 | } 125 | 126 | void 127 | SpriteResetAnimation(Sprite* sprite) { 128 | sprite->time = 0.f; 129 | } 130 | 131 | void 132 | SpritePlay(Sprite* sprite, SpriteAnimation* sprite_anim, float delta, bool loop, bool *is_finished) { 133 | 134 | // SpriteAnimation *sprite_anim = &sprite->animation_list[sprite->current_animation]; 135 | // advance timer 136 | sprite->time += delta * sprite_anim->fps; 137 | if (sprite->time >= sprite_anim->image_count) { 138 | *is_finished = true; // mark as finished 139 | if (loop) { 140 | sprite->time -= sprite_anim->image_count; 141 | } 142 | else { 143 | sprite->time = (float)(sprite_anim->image_count -1); 144 | } 145 | } 146 | 147 | //printf("%f \n", sprite->time); 148 | 149 | int image_index = (int)(sprite->time) % sprite_anim->image_count; 150 | int img_ID = sprite_anim->id_list[image_index]; 151 | NEZ_VEC2_F image_pos = sprite->image_pos_list[img_ID]; 152 | //printf("%d \n", image_index); 153 | 154 | sprite->img_rect.x = image_pos.x; 155 | sprite->img_rect.y = image_pos.y; 156 | sprite->img_rect.width = sprite->w; 157 | sprite->img_rect.height = sprite->h; 158 | 159 | // flip texture rectangle if negative 160 | if (sprite->x_scale < 0.0) { 161 | sprite->img_rect.width *= -1; 162 | //sprite->img_rect->x += sprite->w; // If required to have X offset for negative scale 163 | } 164 | 165 | if (sprite->y_scale < 0.0) { 166 | sprite->img_rect.height *= -1; 167 | //sprite->img_rect->y += sprite->h; // If required to have y offset for negative scale 168 | } 169 | 170 | 171 | float abs_x_scale = SpriteAbs(sprite->x_scale); 172 | float abs_y_scale = SpriteAbs(sprite->y_scale); 173 | 174 | float x = sprite->x + sprite->x_offset * abs_x_scale; 175 | float y = sprite->y + sprite->y_offset * abs_y_scale; 176 | 177 | if (sprite->y_scale < 0.f) { 178 | y += sprite->y_offset * sprite->y_scale; 179 | } 180 | 181 | float w = sprite->w * abs_x_scale; 182 | float h = sprite->h * abs_y_scale; 183 | // TODO: use scaling 184 | sprite->spr_rect.x = x; 185 | sprite->spr_rect.y = y; 186 | sprite->spr_rect.width = w; 187 | sprite->spr_rect.height = h; 188 | } 189 | 190 | 191 | #endif // NEZ_SPRITE_RECT_IMPLEMENTATION 192 | 193 | 194 | -------------------------------------------------------------------------------- /StateMachine/README.md: -------------------------------------------------------------------------------- 1 | # State machine system in C 2 | 3 | Super simple state machine system in C. States hold pointers to functions but they are triggered by StateMachine. You can pass data to each state function via casting it as a void pointer and cast it back to according data type. 4 | 5 | To implement .c parts need to `#define` IMPLEMENTATION guard flags before `#include` of the header file: 6 | ``` 7 | #define NEZ_STATEMACHINE_IMPLEMENTATION 8 | #include "statemachine.h" 9 | ``` 10 | 11 | -------------------------------------------------------------------------------- /StateMachine/example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | include(FetchContent) 3 | 4 | 5 | 6 | # Raylib 7 | FetchContent_Declare( 8 | raylib 9 | DOWNLOAD_EXTRACT_TIMESTAMP OFF 10 | URL https://github.com/raysan5/raylib/archive/refs/tags/5.5.zip 11 | ) 12 | #FetchContent_GetProperties(raylib) 13 | set(FETCHCONTENT_QUIET NO) 14 | set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples 15 | set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games 16 | FetchContent_MakeAvailable(raylib) 17 | 18 | include_directories(${CMAKE_SOURCE_DIR}/..) 19 | 20 | 21 | set(ProjectName State_Example) 22 | project(${ProjectName} LANGUAGES C) 23 | #set(CMAKE_CXX_STANDARD 17) 24 | file(GLOB State_SRC_FILES "StateExample.c") 25 | add_executable(${ProjectName} ${State_SRC_FILES}) 26 | target_link_libraries(${ProjectName} raylib) 27 | target_compile_definitions(${ProjectName} PUBLIC SOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}") 28 | -------------------------------------------------------------------------------- /StateMachine/example/StateExample.c: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | 3 | #define NEZ_STATEMACHINE_IMPLEMENTATION 4 | #include "statemachine.h" 5 | 6 | int testVar = 0; 7 | 8 | void IdleEnter(void *data){ 9 | testVar = 1; 10 | } 11 | void IdleExit(void *data){ 12 | printf("Idle state exits\n"); 13 | } 14 | void IdleUpdate(void *data){ 15 | float delta = *((float*)data); 16 | printf("Idle state update: %f\n", delta); 17 | } 18 | void MoveEnter(void *data){ 19 | testVar = 99; 20 | } 21 | void MoveExit(void *data){ 22 | printf("Move state exits\n"); 23 | } 24 | void MoveUpdate(void *data){ 25 | float delta = *((float*)data); 26 | printf("Move state update: %f\n", delta); 27 | } 28 | 29 | int main(){ 30 | printf("testVar Begining: %d\n", testVar); 31 | 32 | State idle = (State){IdleEnter, IdleExit, IdleUpdate}; 33 | State move = (State){MoveEnter, MoveExit, MoveUpdate}; 34 | printf("States created\n"); 35 | 36 | StateMachine *sm = StateMachineNew(); 37 | int idleId = StateMachineAddState(sm, idle); 38 | int moveId = StateMachineAddState(sm, move); 39 | printf("States Added\n"); 40 | 41 | StateMachineStart(sm, idleId, NULL); 42 | printf("testVar post idle enter: %d\n", testVar); 43 | 44 | float dt = 1.0/60; 45 | StateMachineUpdate(sm, (void*)&dt); 46 | // Idle update printed dt value 47 | 48 | StateMachineTransition(sm, moveId, NULL); 49 | printf("testVar post transition to Move: %d\n", testVar); 50 | 51 | dt = 1.0/30; 52 | StateMachineUpdate(sm, (void*)&dt); 53 | // Move update printed dt value 54 | 55 | StateMachineDestroy(sm); 56 | return 0; 57 | } -------------------------------------------------------------------------------- /StateMachine/statemachine.h: -------------------------------------------------------------------------------- 1 | #ifndef NEZ_STATEMACHINE_H 2 | #define NEZ_STATEMACHINE_H 3 | 4 | #include "stdlib.h" 5 | 6 | typedef struct{ 7 | void (*StateEnter)(void *data); 8 | void (*StateExit)(void *data); 9 | void (*StateUpdate)(void *data); 10 | }State; 11 | 12 | #ifndef STATEMACHINE_HISTORY_SIZE 13 | #define STATEMACHINE_HISTORY_SIZE 5 14 | #endif 15 | typedef struct{ 16 | // List of states. 17 | State *stateList; 18 | // Current state pointer 19 | State *state; 20 | // ID of current state 21 | int current; 22 | // List of previous states (#define STATEMACHINE_HISTORY_SIZE 5) 23 | // From last to oldest 24 | int previous[STATEMACHINE_HISTORY_SIZE]; 25 | // Number of states added 26 | int stateCount; 27 | }StateMachine; 28 | 29 | #ifndef NEZSMAPI 30 | #ifdef NEZ_STATEMACHINE_STATIC 31 | #define NEZSMAPI static 32 | #else 33 | #define NEZSMAPI extern 34 | #endif 35 | #endif 36 | 37 | #ifdef __cplusplus 38 | extern "C" { 39 | #endif 40 | 41 | // Create a new StateMachine instance with memory allocated for it. 42 | // Use StateMachineDestroy to free memory. 43 | NEZSMAPI StateMachine* 44 | StateMachineNew(); 45 | 46 | // Free allocated memory for StateMachine 47 | NEZSMAPI void 48 | StateMachineDestroy(StateMachine *sm); 49 | 50 | // Add a state to StateMachine's list. 51 | // Returns states ID in the list for use in StateMachineTransition. 52 | NEZSMAPI int 53 | StateMachineAddState(StateMachine *sm, State state); 54 | 55 | // Use it to start with a first state 56 | NEZSMAPI void 57 | StateMachineStart(StateMachine *sm, int id, void *data); 58 | 59 | // Transitions out of current state into provided state. 60 | // If state is current transition won't happen. 61 | NEZSMAPI void 62 | StateMachineTransition(StateMachine *sm, int id, void *data); 63 | 64 | // Trigger update function for current state. 65 | NEZSMAPI void 66 | StateMachineUpdate(StateMachine *sm, void *data); 67 | 68 | #ifdef __cplusplus 69 | } 70 | #endif 71 | #endif // NEZ_STATEMACHINE_H 72 | 73 | #ifdef NEZ_STATEMACHINE_IMPLEMENTATION 74 | #undef NEZ_STATEMACHINE_IMPLEMENTATION 75 | 76 | void StateMachineUpdateHistory(StateMachine *sm, int id){ 77 | for(int i=1; iprevious[i] = sm->previous[i-1]; 79 | } 80 | sm->previous[0] = id; 81 | } 82 | 83 | StateMachine* 84 | StateMachineNew(){ 85 | StateMachine *sm = (StateMachine*)malloc(sizeof(StateMachine)); 86 | *sm = (StateMachine){0}; 87 | sm->current = -1; 88 | 89 | for(int i=0; iprevious[i] = -1; 91 | } 92 | 93 | return sm; 94 | } 95 | 96 | void 97 | StateMachineDestroy(StateMachine *sm){ 98 | if (sm->stateList){free(sm->stateList);} 99 | if (sm->state){free(sm->state);} 100 | free(sm); 101 | } 102 | 103 | int 104 | StateMachineAddState(StateMachine *sm, State state){ 105 | State *old = sm->stateList; 106 | sm->stateList = (State*)malloc(sizeof(State) *(sm->stateCount +1)); 107 | for(int i=0; i< sm->stateCount; i++){ 108 | sm->stateList[i] = old[i]; 109 | } 110 | sm->stateList[sm->stateCount] = state; 111 | sm->stateCount++; 112 | return sm->stateCount -1; 113 | } 114 | 115 | void 116 | StateMachineStart(StateMachine *sm, int id, void *data){ 117 | // state doesn't exists 118 | if (id >= sm->stateCount){return;} 119 | 120 | StateMachineUpdateHistory(sm, sm->current); 121 | sm->current = id; 122 | sm->state = &(sm->stateList[id]); 123 | sm->state->StateEnter(data); 124 | } 125 | 126 | void 127 | StateMachineTransition(StateMachine *sm, int id, void *data){ 128 | // state doesn't exists 129 | if (id> sm->stateCount){return;} 130 | 131 | sm->state->StateExit(data); 132 | 133 | StateMachineUpdateHistory(sm, sm->current); 134 | sm->current = id; 135 | sm->state = &(sm->stateList[id]); 136 | sm->state->StateEnter(data); 137 | } 138 | 139 | void 140 | StateMachineUpdate(StateMachine *sm, void *data){ 141 | sm->state->StateUpdate(data); 142 | } 143 | 144 | #endif // NEZ_STATEMACHINE_IMPLEMENTATION 145 | 146 | 147 | -------------------------------------------------------------------------------- /TileMapSystem/Example/.gitignore: -------------------------------------------------------------------------------- 1 | *.exe -------------------------------------------------------------------------------- /TileMapSystem/Example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | include(FetchContent) 3 | 4 | 5 | 6 | # Raylib 7 | FetchContent_Declare( 8 | raylib 9 | DOWNLOAD_EXTRACT_TIMESTAMP OFF 10 | URL https://github.com/raysan5/raylib/archive/refs/tags/5.5.zip 11 | ) 12 | #FetchContent_GetProperties(raylib) 13 | set(FETCHCONTENT_QUIET NO) 14 | set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples 15 | set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games 16 | FetchContent_MakeAvailable(raylib) 17 | 18 | include_directories(${CMAKE_SOURCE_DIR}/..) 19 | 20 | 21 | set(ProjectName Test_AutoTile) 22 | project(${ProjectName}) 23 | set(CMAKE_CXX_STANDARD 17) 24 | file(GLOB AutoTile_SRC_FILES "Test_AutoTile.c") 25 | add_executable(${ProjectName} ${AutoTile_SRC_FILES}) 26 | target_link_libraries(${ProjectName} raylib) 27 | target_compile_definitions(${ProjectName} PUBLIC SOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}") 28 | 29 | set(ProjectName Test_TileMap) 30 | project(${ProjectName}) 31 | set(CMAKE_CXX_STANDARD 17) 32 | file(GLOB TileMap_SRC_FILES "Test_TileMap.c") 33 | add_executable(${ProjectName} ${TileMap_SRC_FILES}) 34 | target_link_libraries(${ProjectName} raylib) 35 | target_compile_definitions(${ProjectName} PUBLIC SOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}") 36 | 37 | set(ProjectName Test_Spread) 38 | project(${ProjectName}) 39 | set(CMAKE_CXX_STANDARD 17) 40 | file(GLOB Spread_SRC_FILES "Test_TileMap_spread_parallax.c") 41 | add_executable(${ProjectName} ${Spread_SRC_FILES}) 42 | target_link_libraries(${ProjectName} raylib) 43 | target_compile_definitions(${ProjectName} PUBLIC SOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}") 44 | 45 | set(ProjectName Test_bitmask) 46 | project(${ProjectName}) 47 | set(CMAKE_CXX_STANDARD 17) 48 | file(GLOB Bitmask_SRC_FILES "Test_TileSet_bitmask_editor.c") 49 | add_executable(${ProjectName} ${Bitmask_SRC_FILES}) 50 | target_link_libraries(${ProjectName} raylib) 51 | target_compile_definitions(${ProjectName} PUBLIC SOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}") 52 | 53 | 54 | 55 | # Setting ASSETS_PATH 56 | target_compile_definitions(${PROJECT_NAME} PUBLIC ASSETS_PATH="${CMAKE_CURRENT_SOURCE_DIR}/assets/") # Set the asset path macro to the absolute path on the dev machine 57 | #target_compile_definitions(${PROJECT_NAME} PUBLIC ASSETS_PATH="./assets") # Set the asset path macro in release mode to a relative path that assumes the assets folder is in the same directory as the game executable -------------------------------------------------------------------------------- /TileMapSystem/Example/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Examples using Raylib 4 | 5 | ## Test_TileSet_bitmask_editor 6 | 7 | - Tool to create bitmasks for autotile. 8 | - Mouse wheel to zoom 9 | - Middle mouse to move tile set around 10 | - Press `Enter` to print bitmask data 11 | - `Resource/TileSetBitmask.h` is example of bitmask data 12 | - `Test_Autotile.c` is an example how to use bitmask data 13 | -------------------------------------------------------------------------------- /TileMapSystem/Example/Resource/Bitmask_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nezvers/GameSystemsInC/b0118784f584446fc8a17c9369c5fdb30820d0bc/TileMapSystem/Example/Resource/Bitmask_example.png -------------------------------------------------------------------------------- /TileMapSystem/Example/Resource/Preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nezvers/GameSystemsInC/b0118784f584446fc8a17c9369c5fdb30820d0bc/TileMapSystem/Example/Resource/Preview.gif -------------------------------------------------------------------------------- /TileMapSystem/Example/Resource/Preview_TileMap_resize.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nezvers/GameSystemsInC/b0118784f584446fc8a17c9369c5fdb30820d0bc/TileMapSystem/Example/Resource/Preview_TileMap_resize.gif -------------------------------------------------------------------------------- /TileMapSystem/Example/Resource/Preview_TileMap_spread.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nezvers/GameSystemsInC/b0118784f584446fc8a17c9369c5fdb30820d0bc/TileMapSystem/Example/Resource/Preview_TileMap_spread.gif -------------------------------------------------------------------------------- /TileMapSystem/Example/Resource/Preview_autotile.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nezvers/GameSystemsInC/b0118784f584446fc8a17c9369c5fdb30820d0bc/TileMapSystem/Example/Resource/Preview_autotile.gif -------------------------------------------------------------------------------- /TileMapSystem/Example/Resource/RDE_8x8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nezvers/GameSystemsInC/b0118784f584446fc8a17c9369c5fdb30820d0bc/TileMapSystem/Example/Resource/RDE_8x8.png -------------------------------------------------------------------------------- /TileMapSystem/Example/Resource/TileSetBitmask.h: -------------------------------------------------------------------------------- 1 | int bitmaskData[] = { 2 | 208, 3 | 248, 4 | 104, 5 | 64, 6 | 80, 7 | 120, 8 | 216, 9 | 72, 10 | 88, 11 | 219, 12 | 214, 13 | 255, 14 | 107, 15 | 66, 16 | 86, 17 | 127, 18 | 223, 19 | 75, 20 | 95, 21 | 126, 22 | 22, 23 | 31, 24 | 11, 25 | 2, 26 | 210, 27 | 251, 28 | 254, 29 | 106, 30 | 250, 31 | 218, 32 | 16, 33 | 24, 34 | 8, 35 | 0, 36 | 18, 37 | 27, 38 | 30, 39 | 10, 40 | 26, 41 | 94, 42 | 82, 43 | 123, 44 | 222, 45 | 74, 46 | 90, 47 | 122, 48 | 91, 49 | -1, 50 | -1, 51 | -1, 52 | }; -------------------------------------------------------------------------------- /TileMapSystem/Example/Resource/TileSetterBitmask.h: -------------------------------------------------------------------------------- 1 | int bitmaskData[] = { 2 | 208, 3 | 248, 4 | 104, 5 | 64, 6 | 80, 7 | 120, 8 | 216, 9 | 72, 10 | 88, 11 | 219, 12 | 214, 13 | 255, 14 | 107, 15 | 66, 16 | 86, 17 | 127, 18 | 223, 19 | 75, 20 | 95, 21 | 126, 22 | 22, 23 | 31, 24 | 11, 25 | 2, 26 | 210, 27 | 251, 28 | 254, 29 | 106, 30 | 250, 31 | 218, 32 | 16, 33 | 24, 34 | 8, 35 | 0, 36 | 18, 37 | 27, 38 | 30, 39 | 10, 40 | 26, 41 | 94, 42 | 82, 43 | 123, 44 | 222, 45 | 74, 46 | 90, 47 | 122, 48 | 91, 49 | -1, 50 | -1, 51 | -1, 52 | }; -------------------------------------------------------------------------------- /TileMapSystem/Example/Resource/TileSetter_template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nezvers/GameSystemsInC/b0118784f584446fc8a17c9369c5fdb30820d0bc/TileMapSystem/Example/Resource/TileSetter_template.png -------------------------------------------------------------------------------- /TileMapSystem/Example/Resource/grass_and_dirt_tiles.ase: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nezvers/GameSystemsInC/b0118784f584446fc8a17c9369c5fdb30820d0bc/TileMapSystem/Example/Resource/grass_and_dirt_tiles.ase -------------------------------------------------------------------------------- /TileMapSystem/Example/Resource/grass_and_dirt_tiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nezvers/GameSystemsInC/b0118784f584446fc8a17c9369c5fdb30820d0bc/TileMapSystem/Example/Resource/grass_and_dirt_tiles.png -------------------------------------------------------------------------------- /TileMapSystem/Example/Test_AutoTile.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************************* 2 | * 3 | * raylib [audio] example - Module playing (streaming) 4 | * 5 | * This example has been created using raylib 1.5 (www.raylib.com) 6 | * raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) 7 | * 8 | * Copyright (c) 2016 Ramon Santamaria (@raysan5) 9 | * 10 | ********************************************************************************************/ 11 | 12 | 13 | #include "raylib.h" 14 | #include "stdio.h" 15 | 16 | #define NEZ_AUTOTILE_IMPLEMENTATION 17 | #include "../autotile.h" 18 | #define NEZ_TILEMAP_IMPLEMENTATION 19 | #include "../tilemap.h" 20 | #define NEZ_TILESET_IMPLEMENTATION 21 | #include "../tileset.h" 22 | #include "Resource/TileSetBitmask.h" 23 | 24 | int screenWidth = 480; 25 | int screenHeight = 240; 26 | 27 | void InitGame(void); 28 | void GameLoop(void); // Update and Draw one frame 29 | void (*Screen)(void); 30 | void DefaultScreen(void); 31 | void Inputs(void); 32 | 33 | TileSet *tileSet; 34 | TileMap *tileMap1; 35 | TileMap *tileMap2; 36 | AutoTile *autoTile; 37 | AutoTile *autoTile1; 38 | AutoTile *autoTile2; 39 | 40 | int main(){ 41 | // Initialization 42 | //-------------------------------------------------------------------------------------- 43 | InitWindow(screenWidth, screenHeight, "AutoTile system test"); 44 | 45 | 46 | 47 | #if defined(PLATFORM_WEB) 48 | emscripten_set_main_loop(GameLoop, 0, 1); 49 | #else 50 | SetTargetFPS(60); // Set our game to run at 60 frames-per-second 51 | //-------------------------------------------------------------------------------------- 52 | InitGame(); 53 | // Main game loop 54 | while (!WindowShouldClose()){ // Detect window close button or ESC key 55 | GameLoop(); 56 | } 57 | #endif 58 | 59 | // De-Initialization 60 | //-------------------------------------------------------------------------------------- 61 | TileSetDestroy(tileSet); 62 | TileMapDestroy(tileMap1); 63 | TileMapDestroy(tileMap2); 64 | AutoTileDestroy(autoTile1); 65 | AutoTileDestroy(autoTile2); 66 | CloseWindow(); // Close window and OpenGL context 67 | //-------------------------------------------------------------------------------------- 68 | return 0; 69 | } 70 | 71 | void InitGame(void){ 72 | //tileSet = TileSetNewInitFromFile("Resource/TileSetter_template.png", 16, 16); 73 | tileSet = TileSetNewInitFromFile(SOURCE_PATH"/Resource/grass_and_dirt_tiles.png", 16, 16, NULL, 0); 74 | 75 | // tilemap for each autoTile 76 | tileMap1 = TileMapNew(); 77 | tileMap1->tileSet = tileSet; 78 | tileMap2 = TileMapNew(); 79 | tileMap2->tileSet = tileSet; 80 | 81 | autoTile1 = AutoTileNewInitTileRegion(tileMap1, 0, 0, 10, 5); 82 | AutoTileSetBitmaskData(autoTile1, (int*)&bitmaskData, sizeof(bitmaskData)/ sizeof(bitmaskData[0])); 83 | 84 | // create AutoTile providing list of TileSet IDs 85 | int tileList[] = {50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99}; 86 | autoTile2 = AutoTileNewInitTileList(tileMap2, (int*)&tileList, sizeof(tileList)/ sizeof(tileList[0])); 87 | AutoTileSetBitmaskData(autoTile2, (int*)&bitmaskData, sizeof(bitmaskData)/ sizeof(bitmaskData[0])); 88 | 89 | autoTile = autoTile1; 90 | 91 | Screen = DefaultScreen; 92 | } 93 | 94 | 95 | 96 | void GameLoop(void){ 97 | Screen(); 98 | } 99 | 100 | 101 | void DefaultScreen(){ 102 | Inputs(); 103 | //---------------------------------------------------------------------------------- 104 | BeginDrawing(); 105 | ClearBackground(RAYWHITE); 106 | TileMapDrawGrid(tileMap1, BLACK); 107 | TileMapDrawGrid(tileMap2, BLACK); 108 | TileMapDraw(tileMap2); //dirt 109 | TileMapDraw(tileMap1); //grass 110 | //TileMapDrawExWorld(tileMap, 0, 0, 20, 10); 111 | DrawText("Press 1 or 2 to switch autotiles", 100, 0, 8, (Color){18, 18, 18, 150}); 112 | EndDrawing(); 113 | } 114 | 115 | 116 | void Inputs(){ 117 | if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)){ 118 | Vector2 mouse = GetMousePosition(); 119 | AutoTileSetCellResizeWorld(autoTile, (int)mouse.x, (int)mouse.y); 120 | } 121 | else if (IsMouseButtonDown(MOUSE_RIGHT_BUTTON)){ 122 | Vector2 mouse = GetMousePosition(); 123 | AutoTileRemoveCellResizeWorld(autoTile, (int)mouse.x, (int)mouse.y); 124 | } 125 | // change active AutoTile 126 | if(IsKeyPressed(KEY_ONE)){ 127 | autoTile = autoTile1; 128 | } 129 | else if(IsKeyPressed(KEY_TWO)){ 130 | autoTile = autoTile2; 131 | } 132 | } 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /TileMapSystem/Example/Test_AutotTile.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************************* 2 | * 3 | * raylib [audio] example - Module playing (streaming) 4 | * 5 | * This example has been created using raylib 1.5 (www.raylib.com) 6 | * raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) 7 | * 8 | * Copyright (c) 2016 Ramon Santamaria (@raysan5) 9 | * 10 | ********************************************************************************************/ 11 | 12 | 13 | #include "raylib.h" 14 | #include "stdio.h" 15 | 16 | #define NEZ_AUTOTILE_IMPLEMENTATION 17 | #include "../autotile.h" 18 | #define NEZ_TILEMAP_IMPLEMENTATION 19 | #include "../tilemap.h" 20 | #define NEZ_TILESET_IMPLEMENTATION 21 | #include "../tileset.h" 22 | #include "Resource/TileSetBitmask.h" 23 | 24 | int screenWidth = 480; 25 | int screenHeight = 240; 26 | 27 | void InitGame(void); 28 | void GameLoop(void); // Update and Draw one frame 29 | void (*Screen)(void); 30 | void DefaultScreen(void); 31 | void Inputs(void); 32 | 33 | TileSet *tileSet; 34 | TileMap *tileMap1; 35 | TileMap *tileMap2; 36 | AutoTile *autoTile; 37 | AutoTile *autoTile1; 38 | AutoTile *autoTile2; 39 | 40 | int main(){ 41 | // Initialization 42 | //-------------------------------------------------------------------------------------- 43 | InitWindow(screenWidth, screenHeight, "AutoTile system test"); 44 | 45 | 46 | 47 | #if defined(PLATFORM_WEB) 48 | emscripten_set_main_loop(GameLoop, 0, 1); 49 | #else 50 | SetTargetFPS(60); // Set our game to run at 60 frames-per-second 51 | //-------------------------------------------------------------------------------------- 52 | InitGame(); 53 | // Main game loop 54 | while (!WindowShouldClose()){ // Detect window close button or ESC key 55 | GameLoop(); 56 | } 57 | #endif 58 | 59 | // De-Initialization 60 | //-------------------------------------------------------------------------------------- 61 | TileSetDestroy(tileSet); 62 | TileMapDestroy(tileMap1); 63 | TileMapDestroy(tileMap2); 64 | AutoTileDestroy(autoTile1); 65 | AutoTileDestroy(autoTile2); 66 | CloseWindow(); // Close window and OpenGL context 67 | //-------------------------------------------------------------------------------------- 68 | return 0; 69 | } 70 | 71 | void InitGame(void){ 72 | //tileSet = TileSetNewInitFromFile("Resource/TileSetter_template.png", 16, 16); 73 | tileSet = TileSetNewInitFromFile(SOURCE_PATH"/Resource/grass_and_dirt_tiles.png", 16, 16, NULL, 0); 74 | 75 | // tilemap for each autoTile 76 | tileMap1 = TileMapNew(); 77 | tileMap1->tileSet = tileSet; 78 | tileMap2 = TileMapNew(); 79 | tileMap2->tileSet = tileSet; 80 | 81 | autoTile1 = AutoTileNewInitTileRegion(tileMap1, 0, 0, 10, 5); 82 | AutoTileSetBitmaskData(autoTile1, (int*)&bitmaskData, sizeof(bitmaskData)/ sizeof(bitmaskData[0])); 83 | 84 | // create AutoTile providing list of TileSet IDs 85 | int tileList[] = {50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99}; 86 | autoTile2 = AutoTileNewInitTileList(tileMap2, (int*)&tileList, sizeof(tileList)/ sizeof(tileList[0])); 87 | AutoTileSetBitmaskData(autoTile2, (int*)&bitmaskData, sizeof(bitmaskData)/ sizeof(bitmaskData[0])); 88 | 89 | autoTile = autoTile1; 90 | 91 | Screen = DefaultScreen; 92 | } 93 | 94 | 95 | 96 | void GameLoop(void){ 97 | Screen(); 98 | } 99 | 100 | 101 | void DefaultScreen(){ 102 | Inputs(); 103 | //---------------------------------------------------------------------------------- 104 | BeginDrawing(); 105 | ClearBackground(RAYWHITE); 106 | TileMapDrawGrid(tileMap1, BLACK); 107 | TileMapDrawGrid(tileMap2, BLACK); 108 | TileMapDraw(tileMap2); //dirt 109 | TileMapDraw(tileMap1); //grass 110 | //TileMapDrawExWorld(tileMap, 0, 0, 20, 10); 111 | EndDrawing(); 112 | } 113 | 114 | 115 | void Inputs(){ 116 | if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)){ 117 | Vector2 mouse = GetMousePosition(); 118 | AutoTileSetCellResizeWorld(autoTile, (int)mouse.x, (int)mouse.y); 119 | } 120 | else if (IsMouseButtonDown(MOUSE_RIGHT_BUTTON)){ 121 | Vector2 mouse = GetMousePosition(); 122 | AutoTileRemoveCellResizeWorld(autoTile, (int)mouse.x, (int)mouse.y); 123 | } 124 | // change active AutoTile 125 | if(IsKeyPressed(KEY_ONE)){ 126 | autoTile = autoTile1; 127 | } 128 | else if(IsKeyPressed(KEY_TWO)){ 129 | autoTile = autoTile2; 130 | } 131 | } 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /TileMapSystem/Example/Test_TileMap.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************************* 2 | * 3 | * raylib [audio] example - Module playing (streaming) 4 | * 5 | * This example has been created using raylib 1.5 (www.raylib.com) 6 | * raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) 7 | * 8 | * Copyright (c) 2016 Ramon Santamaria (@raysan5) 9 | * 10 | ********************************************************************************************/ 11 | 12 | 13 | #include "raylib.h" 14 | #define NEZ_TILEMAP_IMPLEMENTATION 15 | #include "../tilemap.h" 16 | #define NEZ_TILESET_IMPLEMENTATION 17 | #include "../tileset.h" 18 | 19 | int screenWidth = 480; 20 | int screenHeight = 240; 21 | 22 | // fake camera 23 | typedef struct{ 24 | float x; 25 | float y; 26 | int w; 27 | int h; 28 | }View; 29 | 30 | void InitGame(void); 31 | void GameLoop(void); // Update and Draw one frame 32 | void (*Screen)(void); 33 | void DefaultScreen(void); 34 | void Inputs(void); 35 | 36 | TileSet *tileSet; 37 | TileMap *tileMap; 38 | View cam = {0, 0, 16*5, 16*3}; 39 | 40 | int main(){ 41 | // Initialization 42 | //-------------------------------------------------------------------------------------- 43 | InitWindow(screenWidth, screenHeight, "TileMap system test"); 44 | 45 | 46 | 47 | #if defined(PLATFORM_WEB) 48 | emscripten_set_main_loop(GameLoop, 0, 1); 49 | #else 50 | SetTargetFPS(60); // Set our game to run at 60 frames-per-second 51 | //-------------------------------------------------------------------------------------- 52 | InitGame(); 53 | // Main game loop 54 | while (!WindowShouldClose()){ // Detect window close button or ESC key 55 | GameLoop(); 56 | } 57 | #endif 58 | 59 | // De-Initialization 60 | //-------------------------------------------------------------------------------------- 61 | TileSetDestroy(tileSet); 62 | TileMapDestroy(tileMap); 63 | CloseWindow(); // Close window and OpenGL context 64 | //-------------------------------------------------------------------------------------- 65 | return 0; 66 | } 67 | 68 | void InitGame(void){ 69 | tileSet = TileSetNewInitFromFile(SOURCE_PATH"/Resource/TileSetter_template.png", 16, 16, NULL, 0); 70 | tileMap = TileMapNew(); 71 | tileMap->tileSet = tileSet; 72 | tileMap->x = 100; 73 | tileMap->y = 50; 74 | TileMapInitSize(tileMap, 10, 5); 75 | for (int i=0; i<50; i++){tileMap->grid[i] = i;} 76 | 77 | Screen = DefaultScreen; 78 | } 79 | 80 | 81 | 82 | void GameLoop(void){ 83 | Screen(); 84 | } 85 | 86 | 87 | void DefaultScreen(){ 88 | Inputs(); 89 | //---------------------------------------------------------------------------------- 90 | BeginDrawing(); 91 | ClearBackground(RAYWHITE); 92 | DrawText("Use WASD to move fake camera", 20, 20, 16, BLACK); 93 | TileMapDrawGrid(tileMap, BLACK); 94 | // only draw tiles that camera can see 95 | TileMapDrawExWorld(tileMap, (int)cam.x, (int)cam.y, cam.w, cam.h); 96 | DrawRectangleLines(cam.x, cam.y, cam.w, cam.h, LIGHTGRAY); 97 | EndDrawing(); 98 | } 99 | 100 | 101 | // move fake camera 102 | void Inputs(){ 103 | cam.x += ((int)(IsKeyDown(KEY_D)||IsKeyDown(KEY_RIGHT)) - (int)(IsKeyDown(KEY_A)||IsKeyDown(KEY_LEFT))) *0.5; 104 | cam.y += ((int)(IsKeyDown(KEY_S)||IsKeyDown(KEY_DOWN)) - (int)(IsKeyDown(KEY_W)||IsKeyDown(KEY_UP))) *0.5; 105 | } 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /TileMapSystem/Example/Test_TileMap_spread_parallax.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************************* 2 | * 3 | * raylib [audio] example - Module playing (streaming) 4 | * 5 | * This example has been created using raylib 1.5 (www.raylib.com) 6 | * raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) 7 | * 8 | * Copyright (c) 2016 Ramon Santamaria (@raysan5) 9 | * 10 | ********************************************************************************************/ 11 | 12 | 13 | #include "raylib.h" 14 | #define NEZ_TILEMAP_IMPLEMENTATION 15 | #include "../tilemap.h" 16 | #define NEZ_TILESET_IMPLEMENTATION 17 | #include "../tileset.h" 18 | 19 | int screenWidth = 320; 20 | int screenHeight = 180; 21 | 22 | // fake camera 23 | typedef struct{ 24 | float x; 25 | float y; 26 | int w; 27 | int h; 28 | }View; 29 | 30 | void InitGame(void); 31 | void GameLoop(void); // Update and Draw one frame 32 | void (*Screen)(void); 33 | void DefaultScreen(void); 34 | void Inputs(void); 35 | 36 | TileSet *tileSet; 37 | TileMap *tileMap1; 38 | TileMap *tileMap2; 39 | TileMap *tileMap3; 40 | TileMap *tileMap4; 41 | View cam = {0, 0, 8*9, 8*5}; 42 | 43 | int main(){ 44 | // Initialization 45 | //-------------------------------------------------------------------------------------- 46 | InitWindow(screenWidth, screenHeight, "TileMap system test"); 47 | 48 | 49 | 50 | #if defined(PLATFORM_WEB) 51 | emscripten_set_main_loop(GameLoop, 0, 1); 52 | #else 53 | SetTargetFPS(60); // Set our game to run at 60 frames-per-second 54 | //-------------------------------------------------------------------------------------- 55 | InitGame(); 56 | // Main game loop 57 | while (!WindowShouldClose()){ // Detect window close button or ESC key 58 | GameLoop(); 59 | } 60 | #endif 61 | 62 | // De-Initialization 63 | //-------------------------------------------------------------------------------------- 64 | TileSetDestroy(tileSet); 65 | CloseWindow(); // Close window and OpenGL context 66 | //-------------------------------------------------------------------------------------- 67 | return 0; 68 | } 69 | 70 | int mapWidth = 20; 71 | int mapHeight = 20; 72 | int mapData1[20*20]; 73 | int mapData2[20*20]; 74 | int mapData3[20*20]; 75 | int mapData4[20*20]; 76 | 77 | Camera2D camera = {0}; 78 | void SetMapData(void); 79 | void InitGame(){ 80 | Screen = DefaultScreen; 81 | camera.zoom = 2.0f; 82 | SetMapData(); 83 | 84 | tileSet = TileSetNewInitFromFile(SOURCE_PATH"/Resource/RDE_8x8.png", 8, 8, NULL, 0); 85 | 86 | tileMap1 = TileMapNew(); 87 | tileMap1->tileSet = tileSet; 88 | tileMap1->width = 20; 89 | tileMap1->height = 20; 90 | tileMap1->grid = (int*)&mapData1; 91 | 92 | tileMap2 = TileMapNew(); 93 | tileMap2->tileSet = tileSet; 94 | tileMap2->width = 20; 95 | tileMap2->height = 20; 96 | tileMap2->grid = (int*)&mapData2; 97 | 98 | tileMap3 = TileMapNew(); 99 | tileMap3->tileSet = tileSet; 100 | tileMap3->width = 20; 101 | tileMap3->height = 20; 102 | tileMap3->grid = (int*)&mapData3; 103 | 104 | tileMap4 = TileMapNew(); 105 | tileMap4->tileSet = tileSet; 106 | tileMap4->width = 20; 107 | tileMap4->height = 20; 108 | tileMap4->grid = (int*)&mapData4; 109 | 110 | } 111 | 112 | 113 | 114 | void GameLoop(void){ 115 | Screen(); 116 | } 117 | 118 | 119 | void DefaultScreen(){ 120 | Inputs(); 121 | //---------------------------------------------------------------------------------- 122 | BeginDrawing(); 123 | ClearBackground(BLACK); 124 | BeginMode2D(camera); 125 | TileMapDrawGrid(tileMap1, GRAY); 126 | // only draw tiles that camera can see 127 | // Since height color gradient should be game specific, in example it's done with transparent black rectangle between layers 128 | TileMapDrawExWorld(tileMap1, (int)cam.x, (int)cam.y, cam.w, cam.h); 129 | DrawRectangle(0, 0, screenWidth, screenHeight, (Color){0,0,0,100}); 130 | TileMapDrawExSpreadWorld(tileMap2, (int)cam.x, (int)cam.y, cam.w, cam.h, 1.0, 1.0); 131 | DrawRectangle(0, 0, screenWidth, screenHeight, (Color){0,0,0,100}); 132 | TileMapDrawExSpreadWorld(tileMap3, (int)cam.x, (int)cam.y, cam.w, cam.h, 2.0, 2.0); 133 | DrawRectangle(0, 0, screenWidth, screenHeight, (Color){0,0,0,100}); 134 | TileMapDrawExSpreadWorld(tileMap4, (int)cam.x, (int)cam.y, cam.w, cam.h, 3.0, 3.0); 135 | DrawRectangleLines(cam.x, cam.y, cam.w, cam.h, LIGHTGRAY); 136 | EndMode2D(); 137 | EndDrawing(); 138 | } 139 | 140 | 141 | // move fake camera 142 | void Inputs(){ 143 | cam.x += ((int)(IsKeyDown(KEY_D)||IsKeyDown(KEY_RIGHT)) - (int)(IsKeyDown(KEY_A)||IsKeyDown(KEY_LEFT))) *0.5; 144 | cam.y += ((int)(IsKeyDown(KEY_S)||IsKeyDown(KEY_DOWN)) - (int)(IsKeyDown(KEY_W)||IsKeyDown(KEY_UP))) *0.5; 145 | 146 | float visibleWidth = screenWidth / camera.zoom; 147 | float visibleHeight = screenHeight / camera.zoom; 148 | 149 | camera.target.x = cam.x -(visibleWidth -cam.w) *0.5; 150 | camera.target.y = cam.y -(visibleHeight -cam.h) *0.5; 151 | } 152 | 153 | void SetMapData(){ 154 | int d1[] = { 155 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 156 | -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, 157 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 158 | -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, 159 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 160 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 161 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 162 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 163 | -1, 45, -1, 45, -1, 45, -1, 45, -1, 45, -1, 45, -1, 45, -1, 45, -1, 45, -1, 45, 164 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 165 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 166 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 167 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 168 | -1, -1, -1, 197, 196, 196, 197, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 169 | -1, -1, -1, 179, -1, -1, 179, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 170 | -1, -1, -1, 179, -1, -1, 179, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 171 | -1, -1, -1, 197, 196, 196, 197, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 172 | -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, 173 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 174 | -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, -1, 249, 175 | }; 176 | int d2[] = { 177 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 178 | -1, 249, -1, 249, -1, 42, -1, 249, -1, 249, -1, 42, -1, 249, -1, 249, -1, 249, -1, 249, 179 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 180 | -1, 249, -1, 249, -1, 249, -1, 42, -1, 249, -1, 249, -1, 249, -1, 249, -1, 42, -1, 42, 181 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 182 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 183 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 184 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 185 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 186 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 187 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 188 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 189 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 190 | -1, -1, -1, 197, 196, 196, 197, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 191 | -1, -1, -1, 179, -1, -1, 179, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 192 | -1, -1, -1, 179, -1, -1, 179, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 193 | -1, -1, -1, 197, 196, 196, 197, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 194 | -1, 249, -1, 249, -1, 42, -1, 249, -1, 249, -1, 42, -1, 249, -1, 249, -1, 249, -1, 249, 195 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 196 | -1, 249, -1, 249, -1, 249, -1, 42, -1, 249, -1, 249, -1, 249, -1, 249, -1, 42, -1, 42, 197 | }; 198 | int d3[] = { 199 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 200 | -1, 42, -1, 249, -1, -1, -1, 42, -1, 249, -1, -1, -1, 249, -1, 42, -1, 42, -1, 249, 201 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 202 | -1, 249, -1, 42, -1, 249, -1, -1, -1, 249, -1, 42, -1, 42, -1, 249, -1, -1, -1, -1, 203 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 204 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 205 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 206 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 207 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 208 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 209 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 210 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 211 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 212 | -1, -1, -1, 197, 196, 196, 197, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 213 | -1, -1, -1, 179, -1, -1, 179, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 214 | -1, -1, -1, 179, -1, -1, 179, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 215 | -1, -1, -1, 197, 196, 196, 197, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 216 | -1, 42, -1, 249, -1, -1, -1, 42, -1, 249, -1, -1, -1, 249, -1, 42, -1, 42, -1, 249, 217 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 218 | -1, 249, -1, 42, -1, 249, -1, -1, -1, 249, -1, 42, -1, 42, -1, 249, -1, -1, -1, -1, 219 | }; 220 | int d4[] = { 221 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 222 | -1, -1, -1, 42, -1, -1, -1, -1, -1, 42, -1, -1, -1, 42, -1, -1, -1, -1, -1, 42, 223 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 224 | -1, 42, -1, -1, -1, 42, -1, -1, -1, 42, -1, -1, -1, -1, -1, 42, -1, -1, -1, -1, 225 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 226 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 227 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 228 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 229 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 230 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 231 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 232 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 233 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 234 | -1, -1, -1, 197, 196, 196, 197, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 235 | -1, -1, -1, 179, -1, -1, 179, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 236 | -1, -1, -1, 179, -1, -1, 179, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 237 | -1, -1, -1, 197, 196, 196, 197, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 238 | -1, -1, -1, 42, -1, -1, -1, -1, -1, 42, -1, -1, -1, 42, -1, -1, -1, -1, -1, 42, 239 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 240 | -1, 42, -1, -1, -1, 42, -1, -1, -1, 42, -1, -1, -1, -1, -1, 42, -1, -1, -1, -1, 241 | }; 242 | 243 | for (int i=0; i<20*20; i++){ 244 | mapData1[i] = d1[i]; 245 | mapData2[i] = d2[i]; 246 | mapData3[i] = d3[i]; 247 | mapData4[i] = d4[i]; 248 | } 249 | 250 | } 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /TileMapSystem/Example/Test_TileSet_bitmask_editor.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************************* 2 | Simplified editor to create data for AutoTile bitmask list. 3 | Press enter to print a ready-to-use line in console. 4 | ********************************************************************************************/ 5 | 6 | #include 7 | #include "raylib.h" 8 | #define NEZ_TILESET_IMPLEMENTATION 9 | #include "../tileset.h" 10 | 11 | int screenWidth = 480; 12 | int screenHeight = 240; 13 | 14 | 15 | void InitGame(void); 16 | void GameLoop(void); // Update and Draw one frame 17 | void (*Screen)(void); // Pointer for callback functions 18 | void DefaultScreen(void); 19 | 20 | TileSet *tileSet; 21 | 22 | int main(){ 23 | // Initialization 24 | //-------------------------------------------------------------------------------------- 25 | SetConfigFlags(FLAG_WINDOW_RESIZABLE); 26 | InitWindow(screenWidth, screenHeight, "TileMap system test"); 27 | 28 | 29 | 30 | #if defined(PLATFORM_WEB) 31 | emscripten_set_main_loop(GameLoop, 0, 1); 32 | #else 33 | SetTargetFPS(60); // Set our game to run at 60 frames-per-second 34 | //-------------------------------------------------------------------------------------- 35 | InitGame(); 36 | // Main game loop 37 | while (!WindowShouldClose()){ // Detect window close button or ESC key 38 | GameLoop(); 39 | } 40 | #endif 41 | 42 | // De-Initialization 43 | //-------------------------------------------------------------------------------------- 44 | TileSetDestroy(tileSet); 45 | CloseWindow(); // Close window and OpenGL context 46 | //-------------------------------------------------------------------------------------- 47 | return 0; 48 | } 49 | 50 | 51 | RenderTexture2D texture; 52 | void BakeTileSet(void); 53 | int scale = 2; 54 | int col; 55 | int row; 56 | int width; 57 | int height; 58 | Rectangle area = { 0 }; 59 | Camera2D camera = { 0 }; 60 | int *bitmask; 61 | 62 | void InitGame(void){ 63 | 64 | tileSet = TileSetNewInitFromFile(SOURCE_PATH"/Resource/TileSetter_template.png", 16, 16, NULL, 0); 65 | texture = LoadRenderTexture((tileSet->collumns*tileSet->tileX), (tileSet->rows*tileSet->tileY)); 66 | col = tileSet->collumns; 67 | row = tileSet->rows; 68 | area.width = tileSet->tileX * col; 69 | area.height = tileSet->tileY * row; 70 | bitmask = MemAlloc(sizeof(int) * col * row); 71 | // set all to -1 72 | for (int i=0; i< col*row; i++){ bitmask[i] = -1;} 73 | 74 | camera.target = (Vector2){ 0.0f,0.0f } ; 75 | camera.zoom = scale; 76 | 77 | BakeTileSet(); 78 | 79 | Screen = DefaultScreen; 80 | } 81 | 82 | 83 | 84 | void GameLoop(void){ 85 | Screen(); 86 | } 87 | 88 | void Input(void); 89 | void DrawTileHighlight(void); 90 | Vector2 mouseScreen; 91 | Vector2 mouseWorld; 92 | 93 | void DefaultScreen(){ 94 | Input(); 95 | 96 | //---------------------------------------------------------------------------------- 97 | BeginDrawing(); 98 | ClearBackground(RAYWHITE); 99 | BeginMode2D(camera); 100 | DrawTextureRec(texture.texture, (Rectangle){ 0.0f, 0.0f, 101 | (float)texture.texture.width, (float)-texture.texture.height }, 102 | (Vector2){0.0f,0.0f}, WHITE); 103 | EndMode2D(); 104 | 105 | DrawTileHighlight(); 106 | 107 | const char *text = TextFormat("(%f, %f)", mouseWorld.x, mouseWorld.y); 108 | DrawText(text, 20, screenHeight - 20, 20, LIGHTGRAY); 109 | 110 | EndDrawing(); 111 | } 112 | 113 | void BakeTileSet(){ 114 | BeginTextureMode(texture); 115 | for(int y = 0; y < row; y++){ 116 | for(int x = 0; x < col; x++){ 117 | TileSetDrawTile(tileSet, x+y*col, x*tileSet->tileX, y*tileSet->tileY); 118 | } 119 | } 120 | EndTextureMode(); 121 | } 122 | 123 | 124 | bool inArea = false; 125 | int bit[] = {1, 2, 4, 8, 0, 16, 32, 64, 128}; // center is 0 and is considered as active 126 | 127 | void UpdateZoom(void); 128 | void PrintBitmaskData(void); 129 | void Input(void){ 130 | static Vector2 mouseStart; 131 | static Vector2 camStartPos; // needed to move by integer values; 132 | static bool drag = false; 133 | mouseScreen = GetMousePosition(); 134 | 135 | if (IsKeyPressed(KEY_ENTER)){PrintBitmaskData();} 136 | if (IsWindowResized()){ 137 | screenWidth = GetScreenWidth(); 138 | screenHeight = GetScreenHeight(); 139 | UpdateZoom(); 140 | mouseStart = mouseScreen; 141 | camStartPos = camera.target; 142 | } 143 | int mWheel = GetMouseWheelMove(); 144 | if (mWheel != 0){ 145 | scale += mWheel; 146 | scale = scale > 0 ? scale : 1; 147 | UpdateZoom(); 148 | mouseStart = mouseScreen; 149 | camStartPos = camera.target; 150 | } 151 | if (IsMouseButtonPressed(MOUSE_MIDDLE_BUTTON)){ 152 | drag = true; 153 | mouseStart = mouseScreen; 154 | camStartPos = camera.target; 155 | } 156 | if (IsMouseButtonReleased(MOUSE_MIDDLE_BUTTON)){ 157 | drag = false; 158 | } 159 | if (drag){ 160 | // need to move by int without float rounding loss, hence camStartPos 161 | camera.target.x = camStartPos.x +(int)(( mouseStart.x -mouseScreen.x) /scale); 162 | camera.target.y = camStartPos.y +(int)(( mouseStart.y -mouseScreen.y) /scale); 163 | } 164 | 165 | // Offset after moving camera 166 | mouseWorld.x = (mouseScreen.x + camera.target.x *scale) /scale; 167 | mouseWorld.y = (mouseScreen.y + camera.target.y *scale) /scale; 168 | inArea = !(mouseWorld.x < 0|| mouseWorld.y < 0 || mouseWorld.x > area.width -1 || mouseWorld.y > area.height -1); 169 | if (!inArea){return;} 170 | 171 | //no button pressed 172 | if (!IsMouseButtonDown(MOUSE_LEFT_BUTTON) && !IsMouseButtonDown(MOUSE_RIGHT_BUTTON)){ 173 | return; 174 | } 175 | 176 | int w = tileSet->tileX; 177 | int h = tileSet->tileY; 178 | int x = ((int)mouseWorld.x / w); 179 | int y = ((int)mouseWorld.y / h); 180 | int posX = x * w; 181 | int posY = y * h; 182 | int id = x+y*col; 183 | 184 | int mx = ((int)mouseWorld.x - posX) / (w/3); 185 | int my = ((int)mouseWorld.y - posY) / (h/3); 186 | // Floating point imprecision on positive direction edges 187 | mx = mx > 2 ? 2 : mx; 188 | my = my > 2 ? 2 : my; 189 | int b = bit[mx +my *3]; 190 | if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)){ 191 | if (bitmask[id] < 0){bitmask[id] = 0;} 192 | bitmask[id] |= b; 193 | } 194 | if (IsMouseButtonDown(MOUSE_RIGHT_BUTTON)){ 195 | if (bitmask[id] == 0 && b == 0){bitmask[id] = -1;} 196 | else{bitmask[id] &= ~b;} 197 | } 198 | } 199 | 200 | void UpdateZoom(void){ 201 | if ((int)camera.zoom == scale){return;} 202 | camera.zoom = (float)scale; 203 | camera.target.x = mouseWorld.x - ((float)screenWidth /2 /scale) -(mouseScreen.x - (float)screenWidth /2) /scale; 204 | camera.target.y = mouseWorld.y - ((float)screenHeight /2 /scale) -(mouseScreen.y - (float)screenHeight /2) /scale; 205 | } 206 | 207 | 208 | Color colorBit = { 100, 175, 241, 100 }; 209 | Color colorEmpty = { 241, 175, 50, 100 }; 210 | Color colorHover = { 200, 200, 200, 100 }; 211 | 212 | void DrawTileHighlight(void){ 213 | 214 | int w = tileSet->tileX *scale; 215 | int h = tileSet->tileY *scale; 216 | 217 | float ws = (float)w/3; 218 | float hs = (float)h/3; 219 | int offX = -camera.target.x *scale; 220 | int offY = -camera.target.y *scale; 221 | for (int y = 0; y < row; y++){ 222 | for (int x = 0; x < col; x++){ 223 | int id = x+y*col; 224 | int mask = bitmask[id]; 225 | if(mask > -1){ 226 | for (int i=0; i<9; i++){ 227 | int ys = i/3; 228 | int xs = i%3; 229 | int b = bit[i]; 230 | if (mask & b){ 231 | DrawRectangle(x*w +ws*xs +offX, y*h +hs*ys +offY, ws, hs, colorBit); 232 | } 233 | } 234 | DrawRectangle(x*w +ws*1 +offX, y*h +hs*1 +offY, ws, hs, colorBit); 235 | } 236 | else{ 237 | DrawRectangle(x*w +offX, y*h +offY, w, h, colorEmpty); 238 | } 239 | } 240 | } 241 | 242 | 243 | 244 | //Hovered tile 245 | if (!inArea){return;} 246 | int x = (int)(mouseWorld.x *scale / w); 247 | int y = (int)(mouseWorld.y *scale/ h); 248 | int posX = x * w; 249 | int posY = y * h; 250 | DrawRectangleLines( posX -1 +offX, posY -1 +offY, w +2, h +2, colorHover); 251 | 252 | 253 | } 254 | 255 | // tiles without bitmask has value of -1 and shouldn't be processed when importing. 256 | void PrintBitmaskData(void){ 257 | printf("int bitmaskData[] = {"); 258 | for (int i = 0; i < tileSet->tileCount; i++){ 259 | printf("%d, ", bitmask[i]); 260 | } 261 | printf("};\n"); 262 | } 263 | 264 | 265 | -------------------------------------------------------------------------------- /TileMapSystem/README.md: -------------------------------------------------------------------------------- 1 | # TileMap system in C [Raylib rendering] 2 | 3 | `void TileMapDrawExWorld(TileMap *tileMap, int x, int y, int width, int height)` 4 | ![Preview GIF](https://github.com/nezvers/GameSystemsInC/raw/master/TileMapSystem/Example/Resource/Preview.gif) 5 | `void AutoTileSetCell(AutoTile *autoTile, int x, int y)` 6 | ![autotile](https://github.com/nezvers/GameSystemsInC/raw/master/TileMapSystem/Example/Resource/Preview_autotile.gif) 7 | `void TileMapSetTileResize(TileMap *tileMap, int x, int y, int id)` 8 | ![resize](https://github.com/nezvers/GameSystemsInC/raw/master/TileMapSystem/Example/Resource/Preview_TileMap_resize.gif) 9 | `void TileMapDrawExSpreadWorld(TileMap *tileMap, int x, int y, int width, int height, float spreadX, float spreadY)` 10 | - influenced by "Door in the Woods". Requires game specific alterations for fog of war and height gradient 11 | ![spread](https://github.com/nezvers/GameSystemsInC/raw/master/TileMapSystem/Example/Resource/Preview_TileMap_spread.gif) 12 | 13 | Header only libraries with separate elements - tileset.h, tilemap.h, autotile.h. 14 | To implement .c parts need to `#define` IMPLEMENTATION guard flags before `#include` of the header file: 15 | ``` 16 | #define NEZ_TILESET_IMPLEMENTATION 17 | #include "tileset.h" 18 | ``` 19 | 20 | 21 | -------------------------------------------------------------------------------- /TileMapSystem/autotile.h: -------------------------------------------------------------------------------- 1 | /* 2 | #define NEZ_AUTOTILE_IMPLEMENTATION to include .c implementation 3 | 4 | */ 5 | 6 | #ifndef NEZ_AUTOTILE_H 7 | #define NEZ_AUTOTILE_H 8 | 9 | #include "raylib.h" 10 | #include 11 | #include "tilemap.h" 12 | 13 | #define BMASK uint8_t 14 | 15 | typedef struct { 16 | TileMap *tileMap; 17 | TileSet *tileSet; 18 | int *id; // hold bitmask rules for each tile ID - 3x3 bitmask 19 | BMASK lookup[256]; // lookup table for tiles by bitmask - 9 bits 20 | }AutoTile; 21 | 22 | #ifdef __cplusplus 23 | extern "C" { 24 | #endif 25 | 26 | #ifndef NEZATAPI 27 | #ifdef NEZ_AUTOTILE_STATIC 28 | #define NEZATAPI static 29 | #else 30 | #define NEZATAPI extern 31 | #endif 32 | #endif 33 | 34 | NEZATAPI AutoTile* 35 | AutoTileNew(void); // Allocate memory 36 | NEZATAPI void 37 | AutoTileDestroy(AutoTile *autoTile); // Free memory 38 | // TileMap has to have assigned TileSet 39 | NEZATAPI AutoTile* 40 | AutoTileNewInitTileRegion(TileMap *tileMap, int tx, int ty, int tw, int th); // create new tilemap, requires region of TileSet 41 | NEZATAPI AutoTile* 42 | AutoTileNewInitTileList(TileMap *tileMap, int *tileList, int tileCount); // create new tilemap, requires array of tile IDs 43 | NEZATAPI void 44 | AutoTileInit(AutoTile *autoTile, TileMap *tileMap, int *tileList, int tileCount); // Init existing autotile, requires region of TileSet 45 | NEZATAPI void 46 | AutoTileSetBitmaskData(AutoTile *autoTile, int *data, int dataSize); // Creates lookup array for tile IDs 47 | NEZATAPI int 48 | AutoTileGetBitmask(AutoTile *autoTile, int x, int y); // scan bitmask value for tile position 49 | NEZATAPI void 50 | AutoTileSetCell(AutoTile *autoTile, int x, int y); // Set TileMap cell ID and update surrounding 51 | NEZATAPI void 52 | AutoTileSetCellWorld(AutoTile *autoTile, int x, int y); // World coordinates 53 | NEZATAPI void 54 | AutoTileSetCellResize(AutoTile *autoTile, int x, int y); // Set TileMap cell ID, allows to expand existing TileMap grid 55 | NEZATAPI void 56 | AutoTileSetCellResizeWorld(AutoTile *autoTile, int x, int y); // World coordinates 57 | NEZATAPI void 58 | AutoTileRemoveCell(AutoTile *autoTile, int x, int y); // Clears TileMap cell and update surrounding cells 59 | NEZATAPI void 60 | AutoTileRemoveCellWorld(AutoTile *autoTile, int x, int y); // World coordinates 61 | NEZATAPI void 62 | AutoTileRemoveCellResize(AutoTile *autoTile, int x, int y); // Allows to trim the TileMap grid 63 | NEZATAPI void 64 | AutoTileRemoveCellResizeWorld(AutoTile *autoTile, int x, int y); // World coordinates 65 | NEZATAPI void 66 | AutoTileUpdateCell(AutoTile *autoTile, int x, int y); // Update Tilemap cell ID with new bitmask matched ID 67 | NEZATAPI void 68 | AutoTileUpdateCellsAround(AutoTile *autoTile, int x, int y); // Update surrounding TileMap cells 69 | 70 | #ifdef __cplusplus 71 | } 72 | #endif 73 | #endif //NEZ_AUTOTILE_H 74 | 75 | 76 | 77 | // .c implementation / definition 78 | #ifdef NEZ_AUTOTILE_IMPLEMENTATION 79 | #undef NEZ_AUTOTILE_IMPLEMENTATION 80 | 81 | static int 82 | GetSetBitCount(int n); // Necessary function to count how many bits ar enabled 83 | 84 | 85 | AutoTile* AutoTileNew(void){ 86 | AutoTile *autoTile = malloc(sizeof(AutoTile)); 87 | *autoTile = (AutoTile){0}; // set all to 0 88 | return autoTile; 89 | } 90 | 91 | void AutoTileDestroy(AutoTile *autoTile){ 92 | if (autoTile->id){free(autoTile->id);} 93 | free(autoTile); 94 | } 95 | 96 | 97 | AutoTile* AutoTileNewInitTileRegion(TileMap *tileMap, int tx, int ty, int tw, int th){ 98 | AutoTile *autoTile = AutoTileNew(); 99 | 100 | // create tileList 101 | int tileCount = tw*th; 102 | int *tileList = malloc(sizeof(int) *tileCount); 103 | int col = tileMap->tileSet->collumns; 104 | for (int y=0; ytileMap = tileMap; 124 | autoTile->tileSet = tileMap->tileSet; 125 | 126 | if (autoTile->id){free(autoTile->id);} 127 | autoTile->id = malloc(sizeof(int) *tileCount); 128 | 129 | // assign tileSet IDs to autoTile->id 130 | for (int i=0; iid[i] = tileList[i]; 132 | } 133 | } 134 | 135 | // dataSize must not exceed tileList 136 | void AutoTileSetBitmaskData(AutoTile *autoTile, int *data, int dataSize){ 137 | // find tile ID with most bits in each position 138 | for(int i = 0; i < 256; i++){ 139 | int maxBits = -1; 140 | int id = 0; 141 | // flip all bits in i 142 | int negativeMask = i ^ 0xFF; 143 | for(int j = 0; j < dataSize; j++){ 144 | int bitmask = data[j]; 145 | 146 | // accept only if doesn't have bits that "i" doesn't have 147 | if (bitmask > -1 && !(negativeMask & bitmask)){ 148 | int bits = GetSetBitCount(bitmask & i); 149 | if (bits > maxBits){ 150 | maxBits = bits; 151 | id = j; 152 | } 153 | } 154 | } 155 | autoTile->lookup[ i ] = id; 156 | } 157 | } 158 | 159 | // -1 if empty 160 | int AutoTileGetBitmask(AutoTile *autoTile, int x, int y){ 161 | int bitmask = 0; 162 | 163 | // Neighbour values 164 | // ------------- 165 | // | 1 2 4 | 166 | // | 8 0 16 | 167 | // | 32 64 128 | 168 | // ------------- 169 | // scan tiles around and set coresponding bits 170 | bitmask += (int)(TileMapGetTile(autoTile->tileMap, x-1, y-1) > -1); 171 | bitmask += (int)(TileMapGetTile(autoTile->tileMap, x, y-1) > -1) << 1; 172 | bitmask += (int)(TileMapGetTile(autoTile->tileMap, x+1, y-1) > -1) << 2; 173 | bitmask += (int)(TileMapGetTile(autoTile->tileMap, x-1, y) > -1) << 3; 174 | bitmask += (int)(TileMapGetTile(autoTile->tileMap, x+1, y) > -1) << 4; 175 | bitmask += (int)(TileMapGetTile(autoTile->tileMap, x-1, y+1) > -1) << 5; 176 | bitmask += (int)(TileMapGetTile(autoTile->tileMap, x, y+1) > -1) << 6; 177 | bitmask += (int)(TileMapGetTile(autoTile->tileMap, x+1, y+1) > -1) << 7; 178 | 179 | if (bitmask == 0){ 180 | int id = TileMapGetTile(autoTile->tileMap, x, y); 181 | if (id == -1){ return -1; } 182 | } 183 | return bitmask; 184 | } 185 | 186 | void AutoTileSetCell(AutoTile *autoTile, int x, int y){ 187 | int bitmask = AutoTileGetBitmask(autoTile, x, y); 188 | bitmask = bitmask > -1 ? bitmask : 0; 189 | int id = autoTile->id[ autoTile->lookup[bitmask] ]; 190 | TileMapSetTile(autoTile->tileMap, x, y, id); 191 | 192 | AutoTileUpdateCellsAround(autoTile, x, y); 193 | } 194 | 195 | void AutoTileSetCellWorld(AutoTile *autoTile, int x, int y){ 196 | NezVec2_i tp = TileMapWorld2Tile(autoTile->tileMap, x, y); 197 | AutoTileSetCell(autoTile, tp.x, tp.y); 198 | } 199 | 200 | 201 | void AutoTileSetCellResize(AutoTile *autoTile, int x, int y){ 202 | int bitmask = AutoTileGetBitmask(autoTile, x, y); 203 | // if empty cell, then new tile has bitmask of 0 204 | bitmask = bitmask > -1 ? bitmask : 0; 205 | int id = autoTile->id[ autoTile->lookup[bitmask] ]; 206 | NezVec2_i offset = TileMapSetTileResize(autoTile->tileMap, x, y, id); 207 | 208 | 209 | AutoTileUpdateCellsAround(autoTile, x -offset.x, y -offset.y); 210 | } 211 | 212 | 213 | void AutoTileSetCellResizeWorld(AutoTile *autoTile, int x, int y){ 214 | NezVec2_i tp = TileMapWorld2Tile(autoTile->tileMap, x, y); 215 | AutoTileSetCellResize(autoTile, tp.x, tp.y); 216 | } 217 | 218 | void AutoTileRemoveCell(AutoTile *autoTile, int x, int y){ 219 | TileMapSetTile(autoTile->tileMap, x, y, -1); 220 | 221 | AutoTileUpdateCellsAround(autoTile, x, y); 222 | } 223 | 224 | void AutoTileRemoveCellWorld(AutoTile *autoTile, int x, int y){ 225 | NezVec2_i tp = TileMapWorld2Tile(autoTile->tileMap, x, y); 226 | AutoTileRemoveCell(autoTile, tp.x, tp.y); 227 | } 228 | 229 | void AutoTileRemoveCellResize(AutoTile *autoTile, int x, int y){ 230 | NezVec2_i offset = TileMapSetTileResize(autoTile->tileMap, x, y, -1); 231 | 232 | AutoTileUpdateCellsAround(autoTile, x -offset.x, y -offset.y); 233 | } 234 | 235 | void AutoTileRemoveCellResizeWorld(AutoTile *autoTile, int x, int y){ 236 | NezVec2_i tp = TileMapWorld2Tile(autoTile->tileMap, x, y); 237 | AutoTileRemoveCellResize(autoTile, tp.x, tp.y); 238 | } 239 | 240 | void AutoTileUpdateCell(AutoTile *autoTile, int x, int y){ 241 | int id = TileMapGetTile(autoTile->tileMap, x, y); 242 | if (id < 0){return;} 243 | 244 | int bitmask = AutoTileGetBitmask(autoTile, x, y); 245 | 246 | //reusing same variable 247 | id = autoTile->id[ autoTile->lookup[bitmask] ]; 248 | TileMapSetTile(autoTile->tileMap, x, y, id); 249 | } 250 | 251 | void AutoTileUpdateCellsAround(AutoTile *autoTile, int x, int y){ 252 | AutoTileUpdateCell(autoTile, x-1, y-1); 253 | AutoTileUpdateCell(autoTile, x, y-1); 254 | AutoTileUpdateCell(autoTile, x+1, y-1); 255 | AutoTileUpdateCell(autoTile, x-1, y); 256 | AutoTileUpdateCell(autoTile, x+1, y); 257 | AutoTileUpdateCell(autoTile, x-1, y+1); 258 | AutoTileUpdateCell(autoTile, x, y+1); 259 | AutoTileUpdateCell(autoTile, x+1, y+1); 260 | } 261 | 262 | int GetSetBitCount(int n){ 263 | int count = 0; 264 | while (n) { 265 | n &= (n - 1); 266 | count++; 267 | } 268 | return count; 269 | } 270 | 271 | #endif //NEZ_AUTOTILE_IMPLEMENTATION 272 | 273 | -------------------------------------------------------------------------------- /TileMapSystem/tilemap.h: -------------------------------------------------------------------------------- 1 | /* 2 | #define NEZ_TILEMAP_IMPLEMENTATION to include .c implementation 3 | */ 4 | 5 | #ifndef NEZ_TILEMAP_H 6 | #define NEZ_TILEMAP_H 7 | 8 | #include "raylib.h" 9 | #include "tileset.h" 10 | #include "stdio.h" 11 | 12 | typedef struct 13 | { 14 | int *grid; 15 | TileSet *tileSet; 16 | //RenderTexture2D texture; 17 | int x; 18 | int y; 19 | int width; 20 | int height; 21 | } TileMap; 22 | 23 | #ifdef __cplusplus 24 | extern "C" 25 | { 26 | #endif 27 | 28 | #ifndef NEZTMAPI 29 | #ifdef NEZ_TILEMAP_STATIC 30 | #define NEZTMAPI static 31 | #else 32 | #define NEZTMAPI extern 33 | #endif 34 | #endif 35 | 36 | NEZTMAPI TileMap* 37 | TileMapNew(); // Allocates memory and give pointer to it 38 | NEZTMAPI void 39 | TileMapDestroy(TileMap *tileMap); // Free allocated memory (TileMap and grid) 40 | NEZTMAPI void 41 | TileMapInitSize(TileMap *tileMap, int width, int height); // Sets grid size and allocates memory for new size (for conserving grid data use TileMapResize) 42 | NEZTMAPI void 43 | TileMapSetGridData(TileMap *tileMap, int *data, int dataSize); // Fill TileMap grid with array of data (if exceeds size the remain is not used) 44 | NEZTMAPI int 45 | TileMapGetTile(TileMap *tileMap, int x, int y); // Gets tile ID using tile coordinates 46 | NEZTMAPI int 47 | TileMapGetTileWorld(TileMap *tileMap, int x, int y); // Gets tile ID using world coordinates 48 | NEZTMAPI void 49 | TileMapSetTile(TileMap *tileMap, int x, int y, int id); // Sets ID of given grid slot 50 | NEZTMAPI NezVec2_i 51 | TileMapSetTileResize(TileMap *tileMap, int x, int y, int id); // Sets ID of given grid slot 52 | NEZTMAPI void 53 | TileMapResize(TileMap *tileMap, int left, int top, int right, int bottom); // Moves TileMap edges by given amount 54 | NEZTMAPI NezVec2_i 55 | TileMapTrim(TileMap *tileMap); // Resizes TileMap if there is unused outer collumns or rows 56 | NEZTMAPI NezVec2_i 57 | TileMapWorld2Tile(TileMap *tileMap, int x, int y); // Convert world coordinates to tile coordinates 58 | NEZTMAPI NezVec2_i 59 | TileMapTile2World(TileMap *tileMap, int x, int y); // Convert tile coordinates to world coordinates 60 | NEZTMAPI void 61 | TileMapClearGrid(TileMap *tileMap); // Remove all tile IDs (sets to -1) 62 | NEZTMAPI void 63 | TileMapClearGridEx(TileMap *tileMap, int x, int y, int w, int h); 64 | NEZTMAPI void 65 | TileMapDraw(TileMap *tileMap); // Draw entire TileMap 66 | NEZTMAPI void 67 | TileMapDrawEx(TileMap *tileMap, int x, int y, int width, int height); // Draw part of TileMap giving tile coordinates 68 | NEZTMAPI void 69 | TileMapDrawExWorld(TileMap *tileMap, int x, int y, int width, int height); // Draw part of TileMap giving world coordinates (example: camera's rectangle) 70 | NEZTMAPI void 71 | TileMapDrawExSpreadWorld(TileMap *tileMap, int x, int y, int width, int height, float spreadX, float spreadY); // Top-down height parallax (influenced by "Door in the Woods") 72 | NEZTMAPI void 73 | TileMapDrawPart(TileMap *tileMap, int posX, int posY, int x, int y, int width, int height); // Draw part of TileMap ignoring TIleMap position 74 | NEZTMAPI void 75 | TileMapDrawGrid(TileMap *tileMap, Color color); 76 | NEZTMAPI void 77 | TileMapDrawPartSpread(TileMap *tileMap, int posX, int posY, int x, int y, int width, int height, float spreadX, float spreadY); 78 | 79 | #ifdef __cplusplus 80 | } 81 | #endif 82 | #endif //NEZ_TILEMAP_H 83 | 84 | #ifdef NEZ_TILEMAP_IMPLEMENTATION 85 | #undef NEZ_TILEMAP_IMPLEMENTATION 86 | TileMap *TileMapNew() 87 | { 88 | TileMap *tileMap = malloc(sizeof(TileMap)); 89 | *tileMap = (TileMap){0}; 90 | TileMapInitSize(tileMap, 1, 1); 91 | return tileMap; 92 | } 93 | 94 | void TileMapDestroy(TileMap *tileMap) 95 | { 96 | if(tileMap){ 97 | if (tileMap->grid) 98 | { 99 | free(tileMap->grid); 100 | } 101 | free(tileMap); 102 | } 103 | } 104 | 105 | void TileMapInitSize(TileMap *tileMap, int width, int height) 106 | { 107 | tileMap->width = width; 108 | tileMap->height = height; 109 | if (tileMap->grid) 110 | { 111 | free(tileMap->grid); 112 | } 113 | tileMap->grid = malloc(sizeof(int) * width * height); 114 | TileMapClearGrid(tileMap); 115 | } 116 | 117 | void TileMapSetGridData(TileMap *tileMap, int *data, int dataSize) 118 | { 119 | int count = tileMap->width * tileMap->height > dataSize ? dataSize : tileMap->width * tileMap->height; 120 | for (int i = 0; i < count; i++) 121 | { 122 | tileMap->grid[i] = data[i]; 123 | } 124 | } 125 | 126 | // TileMap coordinates 127 | int TileMapGetTile(TileMap *tileMap, int x, int y) 128 | { 129 | bool lessX = x < 0; 130 | bool lessY = y < 0; 131 | bool biggerX = x > tileMap->width - 1; 132 | bool biggerY = y > tileMap->height - 1; 133 | if (lessX || lessY || biggerX || biggerY) 134 | { 135 | return -1; 136 | } 137 | return tileMap->grid[tileMap->width * y + x]; 138 | } 139 | 140 | // World coordinates 141 | int TileMapGetTileWorld(TileMap *tileMap, int x, int y) 142 | { 143 | bool lessX = x < tileMap->x; 144 | bool lessY = y < tileMap->x; 145 | bool biggerX = x > tileMap->x + (tileMap->width * tileMap->tileSet->tileX); 146 | bool biggerY = y > tileMap->y + (tileMap->height * tileMap->tileSet->tileX); 147 | if (lessX || lessY || biggerX || biggerY) 148 | { 149 | return -1; 150 | } 151 | x = (x - tileMap->x) / tileMap->tileSet->tileX; 152 | y = (y - tileMap->y) / tileMap->tileSet->tileY; 153 | return tileMap->grid[tileMap->width * y + x]; 154 | } 155 | 156 | void TileMapSetTile(TileMap *tileMap, int x, int y, int id) 157 | { 158 | if (id < -1 || id > tileMap->tileSet->tileCount - 1) 159 | { 160 | return; 161 | } 162 | bool xIn = x > -1 && x < tileMap->width; 163 | bool yIn = y > -1 && y < tileMap->height; 164 | // sets tile within existing size 165 | if (xIn && yIn) 166 | { 167 | int pos = x + y * tileMap->width; 168 | tileMap->grid[pos] = id; 169 | } 170 | } 171 | 172 | NezVec2_i TileMapSetTileResize(TileMap *tileMap, int x, int y, int id) 173 | { 174 | NezVec2_i offset = {0}; 175 | //not existing tile 176 | if (id < -1 || id > tileMap->tileSet->tileCount - 1) 177 | { 178 | return offset; 179 | } 180 | 181 | // inside tilemap rectangle per axis 182 | bool xIn = x > -1 && x < tileMap->width; 183 | bool yIn = y > -1 && y < tileMap->height; 184 | 185 | // sets tile within existing size 186 | if (xIn && yIn) 187 | { 188 | int pos = x + y * tileMap->width; 189 | tileMap->grid[pos] = id; 190 | if (id > -1) 191 | { 192 | return offset; 193 | } 194 | } 195 | //RESIZE 196 | else if (id > -1) 197 | { 198 | int left, top, right, bottom; 199 | left = x < 0 ? x : 0; 200 | top = y < 0 ? y : 0; 201 | right = x > tileMap->width - 1 ? x - (tileMap->width - 1) : 0; 202 | bottom = y > tileMap->height - 1 ? y - (tileMap->height - 1) : 0; 203 | TileMapResize(tileMap, left, top, right, bottom); // border adjustments 204 | x -= left; 205 | y -= top; 206 | offset = (NezVec2_i){left, top}; 207 | tileMap->grid[x + y * tileMap->width] = id; 208 | } 209 | NezVec2_i trimOffset = TileMapTrim(tileMap); 210 | offset.x += trimOffset.x; 211 | offset.y += trimOffset.y; 212 | return offset; 213 | } 214 | 215 | void TileMapResize(TileMap *tileMap, int left, int top, int right, int bottom) 216 | { 217 | // TO DO: negative resize bug 218 | // printf("%d, %d\n", left, tileMap->width +right); 219 | // if (left >= tileMap->width +right){ 220 | // left = tileMap->width +right -left; 221 | // right = left - tileMap->width +right; 222 | // } 223 | // if (top > tileMap->height +bottom){ 224 | // top = tileMap->height +bottom -top; 225 | // bottom = top - tileMap->height +bottom; 226 | // } 227 | 228 | tileMap->x += left * tileMap->tileSet->tileX; //reposition horizontally 229 | tileMap->y += top * tileMap->tileSet->tileY; //reposition vertically 230 | int w = tileMap->width - left + right; //new width 231 | int h = tileMap->height - top + bottom; //new height 232 | int *tmp = tileMap->grid; //preparing for deleting old pointer 233 | 234 | tileMap->grid = malloc(sizeof(int) * w * h); // Yeet 235 | for (int i = 0; i < w * h; i++) 236 | { 237 | tileMap->grid[i] = -1; 238 | } 239 | 240 | for (int y = 0; y < tileMap->height; y++) 241 | { 242 | if (y - top >= 0 && y - top < h) 243 | { 244 | for (int x = 0; x < tileMap->width; x++) 245 | { 246 | if (x - left >= 0 && x - left < w) 247 | { 248 | tileMap->grid[w * (y - top) + x - left] = tmp[tileMap->width * y + x]; 249 | } 250 | } 251 | } 252 | } 253 | free(tmp); 254 | tileMap->width = w; 255 | tileMap->height = h; 256 | } 257 | 258 | NezVec2_i TileMapTrim(TileMap *tileMap) 259 | { 260 | // init to furthest values 261 | 262 | int left = tileMap->width - 1; 263 | int top = tileMap->height - 1; 264 | int right = 0; 265 | int bottom = 0; 266 | // iterate through grid 267 | for (int y = 0; y < tileMap->height; y++) 268 | { 269 | for (int x = 0; x < tileMap->width; x++) 270 | { 271 | // detect used grid sides 272 | if (tileMap->grid[tileMap->width * y + x] > -1) 273 | { 274 | if (x > right) 275 | { 276 | right = x; 277 | } 278 | if (y > bottom) 279 | { 280 | bottom = y; 281 | } 282 | if (x < left) 283 | { 284 | left = x; 285 | } 286 | if (y < top) 287 | { 288 | top = y; 289 | } 290 | } 291 | } 292 | } 293 | right -= (tileMap->width - 1); 294 | bottom -= (tileMap->height - 1); 295 | NezVec2_i offset = {0}; 296 | if (left == tileMap->width - 1 && right == -(tileMap->width - 1) && top == tileMap->height - 1 && bottom == -(tileMap->height - 1)) 297 | { 298 | return offset; 299 | } 300 | if (left != 0 || top != 0 || right != 0 || bottom != 0) 301 | { 302 | TileMapResize(tileMap, left, top, right, bottom); 303 | } 304 | return (NezVec2_i){left, top}; 305 | } 306 | 307 | NezVec2_i TileMapWorld2Tile(TileMap *tileMap, int x, int y) 308 | { 309 | x = (x - tileMap->x); 310 | y = (y - tileMap->y); 311 | if (x < 0) 312 | { 313 | x -= tileMap->tileSet->tileX; 314 | } 315 | if (y < 0) 316 | { 317 | y -= tileMap->tileSet->tileY; 318 | } 319 | x = x / tileMap->tileSet->tileX; 320 | y = y / tileMap->tileSet->tileY; 321 | return (NezVec2_i){x, y}; 322 | } 323 | 324 | NezVec2_i TileMapTile2World(TileMap *tileMap, int x, int y) 325 | { 326 | int _x = x * tileMap->tileSet->tileX + tileMap->x; 327 | int _y = y * tileMap->tileSet->tileY + tileMap->y; 328 | return (NezVec2_i){_x, _y}; 329 | } 330 | 331 | void TileMapClearGrid(TileMap *tileMap) 332 | { 333 | for (int i = 0; i < tileMap->width * tileMap->height; i++) 334 | { 335 | tileMap->grid[i] = -1; 336 | } 337 | } 338 | 339 | void TileMapClearGridEx(TileMap *tileMap, int x, int y, int w, int h) 340 | { 341 | } 342 | 343 | void TileMapDraw(TileMap *tileMap) 344 | { 345 | int w = tileMap->width; 346 | int h = tileMap->height; 347 | int px = tileMap->x; 348 | int py = tileMap->y; 349 | int sx = tileMap->tileSet->tileX; 350 | int sy = tileMap->tileSet->tileY; 351 | for (int y = 0; y < h; y++) 352 | { 353 | for (int x = 0; x < w; x++) 354 | { 355 | int id = tileMap->grid[x + y * w]; 356 | if (id > -1) 357 | { 358 | TileSetDrawTile(tileMap->tileSet, id, px + x * sx, py + y * sy); 359 | } 360 | } 361 | } 362 | } 363 | 364 | void TileMapDrawGrid(TileMap *tileMap, Color color) 365 | { 366 | int x = tileMap->x; 367 | int y = tileMap->y; 368 | int w = tileMap->width; 369 | int h = tileMap->height; 370 | int sx = tileMap->tileSet->tileX; 371 | int sy = tileMap->tileSet->tileY; 372 | int lenX = w * sx; 373 | int lenY = h * sy; 374 | // horizontal lines 375 | for (int i = 0; i < h + 1; i++) 376 | { 377 | DrawLine(x, y + sy * i, x + lenX, y + sy * i, color); 378 | } 379 | // vertical lines 380 | for (int i = 0; i < w + 1; i++) 381 | { 382 | DrawLine(x + sx * i, y, x + sx * i, y + lenY, color); 383 | } 384 | } 385 | 386 | // Draw only part of TileMap in their original position. 387 | // Tile coordinates. x & y must be left top corner. 388 | void TileMapDrawEx(TileMap *tileMap, int x, int y, int width, int height) 389 | { 390 | TileMapDrawPart(tileMap, tileMap->x, tileMap->y, x, y, width, height); 391 | } 392 | 393 | // World coordinates. x & y must be left top corner. 394 | void TileMapDrawExWorld(TileMap *tileMap, int x, int y, int width, int height) 395 | { 396 | // find tile coordinates 397 | int localX = x - tileMap->x; 398 | int localY = y - tileMap->y; 399 | int signX = localX >= 0 ? 1 : -1; 400 | int signY = localY >= 0 ? 1 : -1; 401 | int tileSizeX = tileMap->tileSet->tileX; 402 | int tileSizeY = tileMap->tileSet->tileY; 403 | int X = localX / tileSizeX; 404 | int Y = localY / tileSizeY; 405 | // compensate flooring if negative cell positions 406 | if (signX < 0 && X * tileSizeX != localX) 407 | { 408 | X += signX; 409 | } 410 | if (signY < 0 && Y * tileSizeY != localY) 411 | { 412 | Y += signY; 413 | } 414 | 415 | int X2 = (localX + width) / tileSizeX; 416 | int Y2 = (localY + height) / tileSizeY; 417 | 418 | int W = (X2 - X) + 1; 419 | int H = (Y2 - Y) + 1; 420 | TileMapDrawPart(tileMap, tileMap->x, tileMap->y, X, Y, W, H); 421 | } 422 | 423 | void TileMapDrawExSpreadWorld(TileMap *tileMap, int x, int y, int width, int height, float spreadX, float spreadY) 424 | { 425 | // find tile coordinates 426 | int localX = x - tileMap->x; 427 | int localY = y - tileMap->y; 428 | int signX = localX >= 0 ? 1 : -1; 429 | int signY = localY >= 0 ? 1 : -1; 430 | int tileSizeX = tileMap->tileSet->tileX; 431 | int tileSizeY = tileMap->tileSet->tileY; 432 | int X = localX / tileSizeX; 433 | int Y = localY / tileSizeY; 434 | // compensate flooring if negative cell positions 435 | if (signX < 0 && X * tileSizeX != localX) 436 | { 437 | X += signX; 438 | } 439 | if (signY < 0 && Y * tileSizeY != localY) 440 | { 441 | Y += signY; 442 | } 443 | 444 | int X2 = (localX + width) / tileSizeX; 445 | int Y2 = (localY + height) / tileSizeY; 446 | int W = (X2 - X) + 1; 447 | int H = (Y2 - Y) + 1; 448 | 449 | float cellOffX = (float)(localX % tileSizeX)/ tileSizeX * spreadX; 450 | float cellOffY = (float)(localY % tileSizeY)/ tileSizeY * spreadY; 451 | 452 | int offX = -((spreadX *W) *0.5 +cellOffX -spreadX); 453 | int offY = -((spreadY *H) *0.5 +cellOffY -spreadY); 454 | TileMapDrawPartSpread(tileMap, tileMap->x +offX, tileMap->y +offY, X, Y, W, H, spreadX, spreadY); 455 | } 456 | 457 | // Can be useful to render into a RenderTexture for a single texture draw if view doesn't changes. 458 | void TileMapDrawPart(TileMap *tileMap, int posX, int posY, int x, int y, int width, int height) 459 | { 460 | if (x > tileMap->width || x + width < 0 || y > tileMap->height || y + height < 0) 461 | { 462 | return; 463 | } 464 | // find used cells 465 | int w = x + width < tileMap->width ? x + width : tileMap->width; 466 | int h = y + height < tileMap->height ? y + height : tileMap->height; 467 | x = x > 0 ? x : 0; 468 | y = y > 0 ? y : 0; 469 | int px = posX; 470 | int py = posY; 471 | int sx = tileMap->tileSet->tileX; 472 | int sy = tileMap->tileSet->tileY; 473 | int gridW = tileMap->width; 474 | for (int _y = y; _y < h; _y++) 475 | { 476 | for (int _x = x; _x < w; _x++) 477 | { 478 | int id = tileMap->grid[_x +_y *gridW]; 479 | if (id > -1){ 480 | TileSetDrawTile(tileMap->tileSet, tileMap->grid[_x + _y * gridW], px + _x * sx, py + _y * sy); 481 | } 482 | } 483 | } 484 | } 485 | 486 | // Useful for fake top-down height parallax. Influenced from "Door in the Woods" 487 | void TileMapDrawPartSpread(TileMap *tileMap, int posX, int posY, int x, int y, int width, int height, float spreadX, float spreadY) 488 | { 489 | if (x > tileMap->width || x + width < 0 || y > tileMap->height || y + height < 0) 490 | { 491 | return; 492 | } 493 | // find used cells 494 | int w = x + width < tileMap->width ? x + width : tileMap->width; 495 | int h = y + height < tileMap->height ? y + height : tileMap->height; 496 | 497 | x = x > 0 ? x : 0; 498 | y = y > 0 ? y : 0; 499 | int px = posX; 500 | int py = posY; 501 | int sx = tileMap->tileSet->tileX; 502 | int sy = tileMap->tileSet->tileY; 503 | int gridW = tileMap->width; 504 | 505 | for (int _y = y; _y < h; _y++) 506 | { 507 | for (int _x = x; _x < w; _x++) 508 | { 509 | int id = tileMap->grid[_x +_y *gridW]; 510 | if (id > -1){ 511 | TileSetDrawTile(tileMap->tileSet, id, px +_x *sx +spreadX *(_x-x), py +_y *sy +spreadY *(_y-y)); 512 | } 513 | } 514 | } 515 | } 516 | 517 | #endif //NEZ_TILEMAP_IMPLEMENTATION 518 | -------------------------------------------------------------------------------- /TileMapSystem/tileset.h: -------------------------------------------------------------------------------- 1 | /* 2 | #define NEZ_TILESET_IMPLEMENTATION to include .c implementation 3 | */ 4 | 5 | #ifndef NEZ_TILESET_H 6 | #define NEZ_TILESET_H 7 | 8 | #include "raylib.h" 9 | #include "stdlib.h" 10 | 11 | #ifndef NEZVEC2_I 12 | #define NEZVEC2_I 13 | //Used for custom positions and TileMap positions 14 | typedef struct{ 15 | int x; 16 | int y; 17 | }NezVec2_i; 18 | #endif // NEZVEC2_I 19 | 20 | typedef struct{ 21 | Texture2D texture; 22 | int tileX; 23 | int tileY; 24 | int collumns; 25 | int rows; 26 | int tileCount; 27 | bool customPositions; // used in TileSetDrawTile 28 | NezVec2_i *positionList; 29 | }TileSet; 30 | 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | #ifndef NEZTSAPI 37 | #ifdef NEZ_TILESET_STATIC 38 | #define NEZTSAPI static 39 | #else 40 | #define NEZTSAPI extern 41 | #endif 42 | #endif 43 | 44 | NEZTSAPI TileSet* 45 | TileSetNew(); // Allocates memory and give pointer to it 46 | 47 | // if have custom positions on texture, give array of TilePositions otherwise NULL and positionCount = 0 or less 48 | NEZTSAPI TileSet* 49 | TileSetNewInitFromFile(const char * fileName, int tileWidth, int tileHeight, NezVec2_i *positionList, int positionCount);// Allocates memory, gives pointer to it and initializes sizes 50 | NEZTSAPI TileSet* 51 | TileSetNewInitFromMemory(Texture texture, int tileWidth, int tileHeight, NezVec2_i *positionList, int positionCount); // Allocates memory, gives pointer to it and initializes sizes 52 | NEZTSAPI void 53 | TileSetDestroy(TileSet* tileSet); // Free allocated memory 54 | NEZTSAPI void 55 | TileSetDestroyWithTexture(TileSet* tileSet); // Free allocated memory and unloads texture 56 | NEZTSAPI void 57 | TileSetSetPositionList(TileSet *tileSet, NezVec2_i *positionList, int positionCount); // Sets list of custom positions that are used to draw tiles by ID 58 | NEZTSAPI void 59 | TileSetSetSize(TileSet *tileSet, int tileWidth, int tileHeight); // Set tile size and tile IDs (Texture must be assigned) 60 | NEZTSAPI void 61 | TileSetDrawTile(TileSet *tileSet, int id, int x, int y); // Draws tile at given position 62 | NEZTSAPI void 63 | TileSetDrawTileStandalone(Texture texture, int id, int x, int y, int tileCollumns, int tileWidth, int tileHeight); // Draws tile without creating TileSet 64 | NEZTSAPI void 65 | TileSetDrawTileFromTexture(Texture texture, int x, int y, NezVec2_i texturePosition, int tileWidth, int tileHeight); // Draws tile without creating TileSet, provide position on texture and tile size 66 | 67 | 68 | #ifdef __cplusplus 69 | } 70 | #endif 71 | #endif //NEZ_TILESET_H 72 | 73 | #ifdef NEZ_TILESET_IMPLEMENTATION 74 | #undef NEZ_TILESET_IMPLEMENTATION 75 | 76 | TileSet* TileSetNew(){ 77 | TileSet *tileSet = malloc(sizeof(TileSet)); 78 | *tileSet = (TileSet){0}; 79 | return tileSet; 80 | } 81 | 82 | TileSet* TileSetNewInitFromFile(const char * fileName, int tileWidth, int tileHeight, NezVec2_i *positionList, int positionCount){ 83 | TileSet *tileSet = TileSetNew(); 84 | tileSet->texture = LoadTexture(fileName); 85 | if (positionCount < 1){ 86 | tileSet->customPositions = false; 87 | TileSetSetSize(tileSet, tileWidth, tileHeight); 88 | } 89 | else{ 90 | tileSet->tileX = tileWidth; 91 | tileSet->tileY = tileHeight; 92 | TileSetSetPositionList(tileSet, positionList, positionCount); 93 | } 94 | return tileSet; 95 | } 96 | 97 | TileSet* TileSetNewInitFromMemory(Texture texture, int tileWidth, int tileHeight, NezVec2_i *positionList, int positionCount){ 98 | TileSet *tileSet = TileSetNew(); 99 | tileSet->texture = texture; 100 | if (positionCount < 1){ 101 | tileSet->customPositions = false; 102 | TileSetSetSize(tileSet, tileWidth, tileHeight); 103 | } 104 | else{ 105 | tileSet->tileX = tileWidth; 106 | tileSet->tileY = tileHeight; 107 | TileSetSetPositionList(tileSet, positionList, positionCount); 108 | } 109 | return tileSet; 110 | } 111 | 112 | void TileSetDestroy(TileSet* tileSet){ 113 | if(tileSet->positionList){free(tileSet->positionList);} 114 | free(tileSet); 115 | } 116 | 117 | void TileSetDestroyWithTexture(TileSet* tileSet){ 118 | UnloadTexture(tileSet->texture); 119 | TileSetDestroy(tileSet); 120 | } 121 | 122 | void TileSetSetPositionList(TileSet *tileSet, NezVec2_i *positionList, int positionCount){ 123 | tileSet->customPositions = true; 124 | tileSet->positionList = (NezVec2_i*)malloc(sizeof(NezVec2_i) * positionCount); 125 | for(int i=0; ipositionList[i] = positionList[i]; 127 | } 128 | } 129 | 130 | // texture must be assigned 131 | void TileSetSetSize(TileSet *tileSet, int tileWidth, int tileHeight){ 132 | tileSet->tileX = tileWidth; 133 | tileSet->tileY = tileHeight; 134 | tileSet->collumns = tileSet->texture.width/tileWidth; 135 | tileSet->rows = tileSet->texture.height/tileHeight; 136 | tileSet->tileCount = tileSet->collumns * tileSet->rows; 137 | } 138 | 139 | void TileSetDrawTile(TileSet *tileSet, int id, int x, int y){ 140 | if(!tileSet->customPositions && tileSet->collumns > 0){ 141 | int col = id % tileSet->collumns; 142 | int row = id / tileSet->collumns; 143 | Rectangle tileRect = {(float)col * tileSet->tileX, (float)row * tileSet->tileY, (float)tileSet->tileX, (float)tileSet->tileY}; 144 | DrawTextureRec(tileSet->texture, tileRect, (Vector2){(float)x, (float)y}, WHITE); 145 | } 146 | else if (tileSet->collumns > 0){ 147 | Rectangle tileRect = {(float)tileSet->positionList[id].x, (float)tileSet->positionList[id].y, (float)tileSet->tileX, (float)tileSet->tileY}; 148 | DrawTextureRec(tileSet->texture, tileRect, (Vector2){(float)x, (float)y}, WHITE); 149 | } 150 | } 151 | 152 | void TileSetDrawTileStandalone(Texture texture, int id, int x, int y, int tileCollumns, int tileWidth, int tileHeight){ 153 | int col = id % tileCollumns; 154 | int row = id / tileCollumns; 155 | Rectangle tileRect = {(float)col * tileWidth, (float)row * tileHeight, (float)tileWidth, (float)tileHeight}; 156 | DrawTextureRec(texture, tileRect, (Vector2){(float)x, (float)y}, WHITE); 157 | } 158 | 159 | void TileSetDrawTileFromTexture(Texture texture, int x, int y, NezVec2_i texturePosition, int tileWidth, int tileHeight){ 160 | Rectangle tileRect = {(float)texturePosition.x, (float)texturePosition.y, (float)tileWidth, (float)tileHeight}; 161 | DrawTextureRec(texture, tileRect, (Vector2){(float)x, (float)y}, WHITE); 162 | } 163 | 164 | #endif //NEZ_TILESET_IMPLEMENTATION 165 | 166 | -------------------------------------------------------------------------------- /ViewportScaling/Example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | include(FetchContent) 3 | 4 | 5 | 6 | # Raylib 7 | FetchContent_Declare( 8 | raylib 9 | DOWNLOAD_EXTRACT_TIMESTAMP OFF 10 | URL https://github.com/raysan5/raylib/archive/refs/tags/5.5.zip 11 | ) 12 | #FetchContent_GetProperties(raylib) 13 | set(FETCHCONTENT_QUIET NO) 14 | set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples 15 | set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games 16 | FetchContent_MakeAvailable(raylib) 17 | 18 | include_directories(${CMAKE_SOURCE_DIR}/..) 19 | 20 | 21 | set(ProjectName Viewport_Example) 22 | project(${ProjectName} LANGUAGES C) 23 | #set(CMAKE_CXX_STANDARD 17) 24 | file(GLOB Viewport_SRC_FILES "ViewportExample.c") 25 | add_executable(${ProjectName} ${Viewport_SRC_FILES}) 26 | target_link_libraries(${ProjectName} raylib) 27 | target_compile_definitions(${ProjectName} PUBLIC SOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}") 28 | -------------------------------------------------------------------------------- /ViewportScaling/Example/ViewportExample.c: -------------------------------------------------------------------------------- 1 | #include "raylib.h" 2 | #include "math.h" 3 | 4 | #define VIEWPORT_SCALING_IMPLEMENTATION 5 | #include "viewport_scaling.h" 6 | 7 | void GameInit(); 8 | void GameUpdate(); 9 | void GameDraw(); 10 | void UpdateResolution(); 11 | void DrawScene(); 12 | 13 | int gameWidth = 320; 14 | int gameHeight = 180; 15 | int screenWidth = 320; 16 | int screenHeight = 180; 17 | RenderTexture viewport; 18 | NezRect_f viewRect; 19 | NezRect_f screenRect; 20 | 21 | void (*scaler[6])(Rectangle*, Rectangle*, int*, int*, int*, int*); 22 | int funcIndex = 0; 23 | 24 | int main(void){ 25 | GameInit(); 26 | SetTargetFPS(60); 27 | while (!WindowShouldClose()){ 28 | GameUpdate(); 29 | GameDraw(); 30 | } 31 | UnloadRenderTexture(viewport); 32 | CloseWindow(); 33 | return 0; 34 | } 35 | 36 | void GameInit() { 37 | SetConfigFlags(FLAG_WINDOW_RESIZABLE); 38 | InitWindow(screenWidth, screenHeight, "2D viewport scaling"); 39 | scaler[0] = ViewportKeepAspectPixel; 40 | scaler[1] = ViewportKeepHeightPixel; 41 | scaler[2] = ViewportKeepWidthPixel; 42 | scaler[3] = ViewportKeepAspect; 43 | scaler[4] = ViewportKeepHeight; 44 | scaler[5] = ViewportKeepWidth; 45 | UpdateResolution(); 46 | } 47 | 48 | void GameUpdate(){ 49 | // change scaling function 50 | if (IsKeyPressed(KEY_TAB)){ 51 | funcIndex = (funcIndex + 1) % 6; 52 | UpdateResolution(); 53 | } 54 | if (IsWindowResized()){ 55 | screenWidth = GetScreenWidth(); 56 | screenHeight = GetScreenHeight(); 57 | UpdateResolution(); 58 | } 59 | } 60 | 61 | void UpdateResolution(){ 62 | scaler[funcIndex](&viewRect, &screenRect, &gameWidth, &gameHeight, &screenWidth, &screenHeight); 63 | 64 | // Change resolution for RenderTexture 65 | UnloadRenderTexture(viewport); 66 | viewport = LoadRenderTexture((int)viewRect.w, (int)viewRect.h); 67 | 68 | // RenderTexture needs to be flipped 69 | viewRect.h *= -1; 70 | } 71 | 72 | void GameDraw(){ 73 | const Vector2 orig = (Vector2){ 0.0f, 0.0f }; 74 | const char* modes[] = { 75 | "Keep aspect (pixel) \nTAB to change", 76 | "Keep height (pixel) \nTAB to change", 77 | "Keep width (pixel) \nTAB to change", 78 | "Keep aspect \nTAB to change", 79 | "Keep height \nTAB to change", 80 | "Keep width \nTAB to change", 81 | }; 82 | BeginTextureMode(viewport); 83 | DrawScene(); 84 | EndTextureMode(); 85 | BeginDrawing(); 86 | ClearBackground(RAYWHITE); 87 | DrawTexturePro(viewport.texture, *(Rectangle*)&viewRect, *(Rectangle*)&screenRect, orig, 0.0f, WHITE); 88 | DrawRectangleLinesEx(*(Rectangle*)&screenRect, 1, BLACK); 89 | 90 | DrawText(modes[funcIndex], 7, 8, 16, WHITE); 91 | DrawText(modes[funcIndex], 9, 8, 16, WHITE); 92 | DrawText(modes[funcIndex], 8, 7, 16, WHITE); 93 | DrawText(modes[funcIndex], 8, 9, 16, WHITE); 94 | DrawText(modes[funcIndex], 8, 8, 16, BLACK); 95 | EndDrawing(); 96 | } 97 | 98 | void DrawScene(){ 99 | const Vector2 orig = (Vector2){ 0.0f, 0.0f }; 100 | DrawRectangleLines(0, 0, gameWidth, gameHeight, GOLD); 101 | DrawLine(0,0,gameHeight, gameHeight, LIGHTGRAY); 102 | DrawLine(gameWidth,0, gameWidth - gameHeight, gameHeight, LIGHTGRAY); 103 | for (int i = 0; i < 20; i++){ 104 | int m = 10; 105 | DrawTexturePro(viewport.texture, *(Rectangle*)&viewRect, *(Rectangle*)&screenRect, orig, 0.0f, WHITE); 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /ViewportScaling/README.md: -------------------------------------------------------------------------------- 1 | # Viewport scaling 2 | Single header utility for getting rectangles to use for scaling viewport texture (Raylib RenderTexture as an example) to the window. 3 | 4 | To get the function implementation part use `#define VIEWPORT_SCALING_IMPLEMENTATION` before including the header. 5 | There is an example for Raylib. 6 | -------------------------------------------------------------------------------- /ViewportScaling/viewport_scaling.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | */ 4 | 5 | #ifndef VIEWPOR_SCALING_H 6 | #define VIEWPOR_SCALING_H 7 | 8 | 9 | #include "stdio.h" 10 | #include "stdlib.h" 11 | #include "stdbool.h" 12 | #include "math.h" 13 | 14 | 15 | #ifndef NEZRECT_F 16 | #define NEZRECT_F 17 | typedef struct { 18 | float x; // origin x 19 | float y; // origin y 20 | float w; // width 21 | float h; // height 22 | } NezRect_f; 23 | #endif // NEZRECT_F 24 | 25 | 26 | #ifndef NEZVEC2_F 27 | #define NEZVEC2_F 28 | typedef struct { 29 | float x; 30 | float y; 31 | } NezVec2_f; 32 | #endif // NEZVEC2_F 33 | 34 | #ifndef NEZVPAPI 35 | #ifdef NEZ_VIEWPORT_SCALING_STATIC 36 | #define NEZVPAPI static 37 | #else 38 | #define NEZVPAPI extern 39 | #endif 40 | #endif 41 | 42 | #ifdef __cplusplus 43 | extern "C" { 44 | #endif 45 | // Helper function to get available scale 46 | NEZVPAPI int 47 | ViewportGetAspectScalePixel(int *viewWidth, int *viewHeight, int *windowWidth, int *windowHeight); 48 | NEZVPAPI float 49 | ViewportGetAspectScale(int *viewWidth, int *viewHeight, int *windowWidth, int *windowHeight); 50 | // Get rectangles for scaling viewport to the window 51 | NEZVPAPI void 52 | ViewportKeepAspectPixel(NezRect_f *view, NezRect_f *screen, int *viewWidth, int *viewHeight, int *windowWidth, int *windowHeight); 53 | NEZVPAPI void 54 | ViewportKeepHeightPixel(NezRect_f *view, NezRect_f *screen, int *viewWidth, int *viewHeight, int *windowWidth, int *windowHeight); 55 | NEZVPAPI void 56 | ViewportKeepWidthPixel(NezRect_f *view, NezRect_f *screen, int *viewWidth, int *viewHeight, int *windowWidth, int *windowHeight); 57 | NEZVPAPI void 58 | ViewportKeepAspect(NezRect_f *view, NezRect_f *screen, int *viewWidth, int *viewHeight, int *windowWidth, int *windowHeight); 59 | NEZVPAPI void 60 | ViewportKeepHeight(NezRect_f *view, NezRect_f *screen, int *viewWidth, int *viewHeight, int *windowWidth, int *windowHeight); 61 | NEZVPAPI void 62 | ViewportKeepWidth(NezRect_f *view, NezRect_f *screen, int *viewWidth, int *viewHeight, int *windowWidth, int *windowHeight); 63 | 64 | #ifdef __cplusplus 65 | } 66 | #endif 67 | #endif //VIEWPOR_SCALING_H 68 | 69 | #ifdef VIEWPORT_SCALING_IMPLEMENTATION 70 | #undef VIEWPORT_SCALING_IMPLEMENTATION 71 | 72 | #define MIN(a,b) (((a)<(b))?(a):(b)) 73 | #define MAX(a,b) (((a)>(b))?(a):(b)) 74 | 75 | int ViewportGetAspectScalePixel(int *viewWidth, int *viewHeight, int *windowWidth, int *windowHeight){ 76 | return MAX(1, MIN((*windowWidth / *viewWidth), (*windowHeight / *viewHeight))); 77 | } 78 | 79 | float ViewportGetAspectScale(int *viewWidth, int *viewHeight, int *windowWidth, int *windowHeight){ 80 | return MAX(1, MIN(((float)*windowWidth / *viewWidth), ((float)*windowHeight / *viewHeight))); 81 | } 82 | 83 | void ViewportKeepAspectPixel(NezRect_f *view, NezRect_f *screen, int *viewWidth, int *viewHeight, int *windowWidth, int *windowHeight){ 84 | int scale = MAX(1, MIN((*windowWidth / *viewWidth), (*windowHeight / *viewHeight))); 85 | view->w = *viewWidth; 86 | view->h = *viewHeight; 87 | view->x = 0.0f; 88 | view->y = 0.0f; 89 | 90 | screen->w = *viewWidth * scale; 91 | screen->h = *viewHeight * scale; 92 | screen->x = (int)((*windowWidth - screen->w) * 0.5); 93 | screen->y = (int)((*windowHeight - screen->h) * 0.5); 94 | } 95 | 96 | void ViewportKeepHeightPixel(NezRect_f *view, NezRect_f *screen, int *viewWidth, int *viewHeight, int *windowWidth, int *windowHeight){ 97 | int scale = MAX(1, (*windowHeight / *viewHeight)); 98 | view->w = *windowWidth / scale; 99 | view->h = *viewHeight; 100 | view->x = 0.0f; 101 | view->y = 0.0f; 102 | 103 | screen->w = view->w * scale; 104 | screen->h = view->h * scale; 105 | screen->x = (int)((*windowWidth - screen->w) * 0.5); 106 | screen->y = (int)((*windowHeight - screen->h) * 0.5); 107 | } 108 | 109 | void ViewportKeepWidthPixel(NezRect_f *view, NezRect_f *screen, int *viewWidth, int *viewHeight, int *windowWidth, int *windowHeight){ 110 | int scale = MAX(1, (*windowWidth / *viewWidth)); 111 | view->w = *viewWidth; 112 | view->h = *windowHeight / scale; 113 | view->x = 0.0f; 114 | view->y = 0.0f; 115 | 116 | screen->w = (int)view->w * scale; 117 | screen->h = (int)view->h * scale; 118 | screen->x = (int)((*windowWidth - screen->w) * 0.5); 119 | screen->y = (int)((*windowHeight - screen->h) * 0.5); 120 | } 121 | 122 | void ViewportKeepAspect(NezRect_f *view, NezRect_f *screen, int *viewWidth, int *viewHeight, int *windowWidth, int *windowHeight){ 123 | float scale = MAX(1.0f, MIN(((float)*windowWidth / *viewWidth), ((float)*windowHeight / *viewHeight))); 124 | view->w = *viewWidth; 125 | view->h = *viewHeight; 126 | view->x = 0.0f; 127 | view->y = 0.0f; 128 | 129 | screen->w = view->w * scale; 130 | screen->h = view->h * scale; 131 | screen->x = (int)((*windowWidth - screen->w) * 0.5); 132 | screen->y = (int)((*windowHeight - screen->h) * 0.5); 133 | } 134 | 135 | void ViewportKeepHeight(NezRect_f *view, NezRect_f *screen, int *viewWidth, int *viewHeight, int *windowWidth, int *windowHeight){ 136 | float scale = MAX(1.0f, ((float)*windowHeight / *viewHeight)); 137 | view->w = (float)*windowWidth / scale; 138 | view->h = *viewHeight; 139 | view->x = 0.0f; 140 | view->y = 0.0f; 141 | 142 | screen->w = view->w * scale; 143 | screen->h = view->h * scale; 144 | screen->x = (int)((*windowWidth - screen->w) * 0.5); 145 | screen->y = (int)((*windowHeight - screen->h) * 0.5); 146 | } 147 | 148 | void ViewportKeepWidth(NezRect_f *view, NezRect_f *screen, int *viewWidth, int *viewHeight, int *windowWidth, int *windowHeight){ 149 | float scale = MAX(1.0f, ((float)*windowWidth / *viewWidth)); 150 | view->w = *viewWidth; 151 | view->h = (float)*windowHeight / scale; 152 | view->x = 0.0f; 153 | view->y = 0.0f; 154 | 155 | screen->w = view->w * scale; 156 | screen->h = view->h * scale; 157 | screen->x = (int)((*windowWidth - screen->w) * 0.5); 158 | screen->y = (int)((*windowHeight - screen->h) * 0.5); 159 | } 160 | 161 | #endif //VIEWPORT_SCALING_IMPLEMENTATION 162 | 163 | --------------------------------------------------------------------------------