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