diff options
| author | Mitja Felicijan <mitja.felicijan@gmail.com> | 2025-04-11 21:17:07 +0200 |
|---|---|---|
| committer | Mitja Felicijan <mitja.felicijan@gmail.com> | 2025-04-11 21:17:07 +0200 |
| commit | a6bc7a7697ac4745d9f724811cecfafb98b9f209 (patch) | |
| tree | 7feef2a3b4a537591a6690965ef702ff3e9cc82f /content/notes | |
| parent | 62f954cf1afc8bfc17ef3622154f3ec025a1b69e (diff) | |
| download | mitjafelicijan.com-a6bc7a7697ac4745d9f724811cecfafb98b9f209.tar.gz | |
Update
Diffstat (limited to 'content/notes')
| -rw-r--r-- | content/notes/2025-04-08-embedding-game-assets-within-your-binary.md | 246 |
1 files changed, 0 insertions, 246 deletions
diff --git a/content/notes/2025-04-08-embedding-game-assets-within-your-binary.md b/content/notes/2025-04-08-embedding-game-assets-within-your-binary.md deleted file mode 100644 index 70f5d0f..0000000 --- a/content/notes/2025-04-08-embedding-game-assets-within-your-binary.md +++ /dev/null | |||
| @@ -1,246 +0,0 @@ | |||
| 1 | --- | ||
| 2 | title: Embedding game assets within your executable binary | ||
| 3 | url: embedding-game-assets-within-your-binary.html | ||
| 4 | date: 2025-04-08T16:13:13+02:00 | ||
| 5 | type: note | ||
| 6 | draft: false | ||
| 7 | tags: [gamedev] | ||
| 8 | --- | ||
| 9 | |||
| 10 | ## Why? | ||
| 11 | |||
| 12 | Normally, assets live outside the binary, but there is a valid reason to embed | ||
| 13 | them. Specially if you do not want to deal with reading and parsing external | ||
| 14 | files. This makes development and distribution much easier. | ||
| 15 | |||
| 16 | In this example, I'm using [raylib](https://github.com/raysan5/raylib) and C, | ||
| 17 | but this can also be done with [SDL](https://github.com/libsdl-org/SDL) or | ||
| 18 | other libraries. | ||
| 19 | |||
| 20 | Code for these notes is available as an | ||
| 21 | [embedding-binary-data.tar.xz](/assets/notes/embedding-binary-data.tar.xz) | ||
| 22 | tarball, but beware that this only includes the Linux build of raylib so please | ||
| 23 | change to appropriate operating system. | ||
| 24 | |||
| 25 | You can also check code on GitHub | ||
| 26 | [@mitjafelicijan/probe/c-embedding-data](https://github.com/mitjafelicijan/probe/tree/master/c-embedding-data). | ||
| 27 | |||
| 28 | ## Project structure | ||
| 29 | |||
| 30 | We are going to keep it clean and simple here. I am using pre-build version of | ||
| 31 | raylib just to simplify compilation. All the code is located in the `main.c` | ||
| 32 | file, so refer to that. | ||
| 33 | |||
| 34 | ``` | ||
| 35 | ├── data Contains assets | ||
| 36 | │ ├── armor.png Image example | ||
| 37 | │ └── dejavusans-mono.ttf Font example | ||
| 38 | ├── libs | ||
| 39 | │ └── raylib-5.5_linux_amd64 Includes prebuild raylib | ||
| 40 | ├── main.c Main file | ||
| 41 | └── Makefile Build instruction | ||
| 42 | ``` | ||
| 43 | |||
| 44 | ## Main game loop | ||
| 45 | |||
| 46 | We first need to setup main game loop and get the game running. | ||
| 47 | |||
| 48 | ```c | ||
| 49 | #include <stdio.h> | ||
| 50 | #include "raylib.h" | ||
| 51 | |||
| 52 | int main(void) { | ||
| 53 | SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT | FLAG_WINDOW_HIGHDPI); | ||
| 54 | InitWindow(900, 400, "Embedding assets"); | ||
| 55 | SetTargetFPS(60); | ||
| 56 | |||
| 57 | while (!WindowShouldClose()) { | ||
| 58 | BeginDrawing(); | ||
| 59 | ClearBackground(BLACK); | ||
| 60 | EndDrawing(); | ||
| 61 | } | ||
| 62 | |||
| 63 | CloseWindow(); | ||
| 64 | return 0; | ||
| 65 | } | ||
| 66 | ``` | ||
| 67 | |||
| 68 | And then prepare basic `Makefile` to compile this. | ||
| 69 | |||
| 70 | ```make | ||
| 71 | RAYLIB_VER := 5.5_linux_amd64 | ||
| 72 | CC ?= cc | ||
| 73 | CFLAGS := -Wall -Wextra -Wunused -Wno-unused-parameter -Wswitch-enum -Wpedantic -Wno-bitwise-instead-of-logical | ||
| 74 | LDFLAGS := -I./libs -I./libs/raylib-$(RAYLIB_VER)/include ./libs/raylib-$(RAYLIB_VER)/lib/libraylib.a -lm | ||
| 75 | |||
| 76 | game: | ||
| 77 | $(CC) $(CFLAGS) -o game main.c $(LDFLAGS) | ||
| 78 | ``` | ||
| 79 | |||
| 80 | Now we can compile with `make game` and get the following. | ||
| 81 | |||
| 82 |  | ||
| 83 | |||
| 84 | ## Converting assets to C header files | ||
| 85 | |||
| 86 | Here we use [`xxd`](https://linux.die.net/man/1/xxd) which is used to make a | ||
| 87 | hexdump or do the reverse. This tool contains an interesting option `-i` which | ||
| 88 | allows us to output the file in C include file style. And this is exactly what | ||
| 89 | we need. | ||
| 90 | |||
| 91 | Modified `Makefile` now looks like. | ||
| 92 | |||
| 93 | ```make | ||
| 94 | RAYLIB_VER := 5.5_linux_amd64 | ||
| 95 | CC ?= cc | ||
| 96 | CFLAGS := -Wall -Wextra -Wunused -Wno-unused-parameter -Wswitch-enum -Wpedantic -Wno-bitwise-instead-of-logical | ||
| 97 | LDFLAGS := -I./libs -I./libs/raylib-$(RAYLIB_VER)/include ./libs/raylib-$(RAYLIB_VER)/lib/libraylib.a -lm | ||
| 98 | |||
| 99 | game: embed | ||
| 100 | $(CC) $(CFLAGS) -o game main.c $(LDFLAGS) | ||
| 101 | |||
| 102 | embed: | ||
| 103 | xxd -i data/armor.png > data/armor.h | ||
| 104 | xxd -i data/dejavusans-mono.ttf > data/dejavusans-mono.h | ||
| 105 | ``` | ||
| 106 | |||
| 107 | This converted binary data files into C header style files which contain the | ||
| 108 | array of bytes and the size of the array of bytes. This will be useful later | ||
| 109 | with raylib code. | ||
| 110 | |||
| 111 | If we execute `make embed` we will create C header files. But running `make | ||
| 112 | game` will also call embed as well, so no need to call it separately. | ||
| 113 | |||
| 114 | An example of such a file (in our case armor.png) looks like this. | ||
| 115 | |||
| 116 | ```c | ||
| 117 | // data/armor.h | ||
| 118 | unsigned char data_armor_png[] = { | ||
| 119 | 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, | ||
| 120 | 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x01, 0x40, | ||
| 121 | 0x08, 0x06, 0x00, 0x00, 0x00, 0xcd, 0x90, 0xa5, 0xaa, 0x00, 0x00, 0x00, | ||
| 122 | 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00, | ||
| 123 | 0x00, 0x06, 0x62, 0x4b, 0x47, 0x44, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, | ||
| 124 | 0xa0, 0xbd, 0xa7, 0x93, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, | ||
| 125 | 0x00, 0x00, 0x17, 0x12, 0x00, 0x00, 0x17, 0x12, 0x01, 0x67, 0x9f, 0xd2, | ||
| 126 | 0x52, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xdc, 0x0c, | ||
| 127 | 0x1b, 0x07, 0x30, 0x17, 0xb2, 0x1a, 0xee, 0xda, 0x00, 0x00, 0x20, 0x00, | ||
| 128 | ... | ||
| 129 | 0x81, 0x98, 0xa6, 0xb9, 0x2d, 0x37, 0xac, 0x6d, 0x57, 0xb0, 0xed, 0xea, | ||
| 130 | 0x86, 0xac, 0xa1, 0xa6, 0x6b, 0xf8, 0x54, 0x1f, 0x8e, 0xe3, 0x90, 0xcb, | ||
| 131 | 0xe6, 0x36, 0x7d, 0x4e, 0x6b, 0xab, 0xcb, 0x78, 0xbd, 0x5e, 0x6c, 0xc7, | ||
| 132 | 0x01, 0x8f, 0xa7, 0x5e, 0x46, 0x22, 0x37, 0x17, 0x76, 0x13, 0x4d, 0x6c, | ||
| 133 | 0xab, 0x0c, 0xb0, 0x89, 0x26, 0x9a, 0x68, 0xe2, 0x45, 0xa3, 0x99, 0x2b, | ||
| 134 | 0x34, 0xd1, 0x44, 0x13, 0xff, 0xb7, 0xf8, 0x27, 0x9b, 0x96, 0x5c, 0x0d, | ||
| 135 | 0x8b, 0xbb, 0x21, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, | ||
| 136 | 0xae, 0x42, 0x60, 0x82 | ||
| 137 | }; | ||
| 138 | unsigned int data_armor_png_len = 118324; | ||
| 139 | ``` | ||
| 140 | |||
| 141 | ## Embedding and compiling | ||
| 142 | |||
| 143 | Now it's time to include this files in our `main.c` code and use them. raylib's | ||
| 144 | API fits perfectly with this style of converting binary files. | ||
| 145 | |||
| 146 | ```c | ||
| 147 | #include <stdio.h> | ||
| 148 | #include "raylib.h" | ||
| 149 | |||
| 150 | #include "data/armor.h" | ||
| 151 | #include "data/dejavusans-mono.h" | ||
| 152 | |||
| 153 | #define FONT_SIZE 24 | ||
| 154 | |||
| 155 | int main(void) { | ||
| 156 | SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_VSYNC_HINT | FLAG_WINDOW_HIGHDPI); | ||
| 157 | InitWindow(900, 400, "Embedding assets"); | ||
| 158 | SetTargetFPS(60); | ||
| 159 | |||
| 160 | // Load font from memory. | ||
| 161 | Font font = LoadFontFromMemory(".ttf", data_dejavusans_mono_ttf, data_dejavusans_mono_ttf_len, FONT_SIZE, NULL, 0); | ||
| 162 | SetTextureFilter(font.texture, TEXTURE_FILTER_TRILINEAR); // Font antialising. | ||
| 163 | |||
| 164 | // Load image from memory and create texture from it. | ||
| 165 | Image armor = LoadImageFromMemory(".png", data_armor_png, data_armor_png_len); | ||
| 166 | Texture2D armor_texture = LoadTextureFromImage(armor); | ||
| 167 | UnloadImage(armor); | ||
| 168 | |||
| 169 | while (!WindowShouldClose()) { | ||
| 170 | BeginDrawing(); | ||
| 171 | ClearBackground(BLACK); | ||
| 172 | |||
| 173 | // Draw the armor texture. | ||
| 174 | DrawTexture(armor_texture, 20, 20, WHITE); | ||
| 175 | |||
| 176 | // Draw some text on the screen. | ||
| 177 | DrawTextEx(font, "Hello embedded assets.", (Vector2){ 400, 20 }, FONT_SIZE, 0, WHITE); | ||
| 178 | DrawTextEx(font, "This is example how we can use embedded fonts.", (Vector2){ 400, 50 }, FONT_SIZE - 4, 0, WHITE); | ||
| 179 | |||
| 180 | EndDrawing(); | ||
| 181 | } | ||
| 182 | |||
| 183 | UnloadTexture(armor_texture); | ||
| 184 | CloseWindow(); | ||
| 185 | return 0; | ||
| 186 | } | ||
| 187 | ``` | ||
| 188 | |||
| 189 | This makes embedding assets quite straightforward. All we need to do is include | ||
| 190 | header files and then provide byte arrays to the appropriate functions. Like I | ||
| 191 | said raylib has a bunch of `FromMemory` functions that make this quite easy. | ||
| 192 | |||
| 193 | This produces a single binary `game` that includes both assets, so there is no | ||
| 194 | need to also copy over these assets. Mind you, these binaries can potentially | ||
| 195 | get quite big and if that is the problem, you could always compile this into a | ||
| 196 | `so` library and include that. This way you could create data packs for audio, | ||
| 197 | graphics, etc. and ship that alongside your game binary. | ||
| 198 | |||
| 199 | Run `make -B game` and run the game. | ||
| 200 | |||
| 201 |  | ||
| 202 | |||
| 203 | ## Honorable mention: C23-embed-directive | ||
| 204 | |||
| 205 | `#embed` is a preprocessor directive to include (binary) resources in the | ||
| 206 | build, where a resource is defined as a source of data accessible from the | ||
| 207 | translation environment. This has been introduces with the C23 standard so it's | ||
| 208 | quite new. | ||
| 209 | |||
| 210 | Speaking plainly, `#embed` allows inclusion of binary data in a program | ||
| 211 | executable image, as arrays of unsigned char or other types, without the need | ||
| 212 | for an external script run from a Makefile. | ||
| 213 | |||
| 214 | - I do like this approach, but some compilers might not support this feature, | ||
| 215 | and this is why I will be sticking with manual approach for now. | ||
| 216 | - One additional drawback is that every time you compile your game, all the | ||
| 217 | assets need to be re-read and converted. I have not tested this heavily, but | ||
| 218 | this could potentially significantly increase build times when you have a lot | ||
| 219 | of assets. | ||
| 220 | - Not using Make could be detrimental to incremental builds. But this would | ||
| 221 | also need to be tested. I do not claim that this is a real problem. Test this | ||
| 222 | yourself. | ||
| 223 | |||
| 224 | Read more about [Binary resource | ||
| 225 | inclusion](https://en.cppreference.com/w/c/preprocessor/embed). | ||
| 226 | |||
| 227 | Below is a quick example how to use this new directive. | ||
| 228 | |||
| 229 | ```c | ||
| 230 | #include <stdint.h> | ||
| 231 | #include <stdio.h> | ||
| 232 | |||
| 233 | const uint8_t image_data[] = { | ||
| 234 | #embed "image.png" | ||
| 235 | }; | ||
| 236 | |||
| 237 | int main(void) { | ||
| 238 | printf("Image size: %d bytes\n", sizeof(image_data)); | ||
| 239 | return 0; | ||
| 240 | } | ||
| 241 | ``` | ||
| 242 | |||
| 243 | ## Credits | ||
| 244 | |||
| 245 | - https://opengameart.org/content/armor-icons-by-equipment-slot-with-transparency | ||
| 246 | - https://dejavu-fonts.github.io/ | ||
