A tiny game engine with zero dependencies in C that I made for myself to make tiny games. It's great for game jams. It is also my kōhai course in simplicity and a tribute to my senpai and friend rxi.
- Two files:
punity.c
,punity.h
all ready for you to start working. (maybe also grabbuild.bat
when you're at it) - Approx. 1500 lines of C code.
- No dependencies.
- Produces a single executable with all resources baked to it.
- Images in PNG, JPG, PSD, TGA, GIF, BMP, HDR, PIC, PNM when used with
stb_image
. (see Integration below) - Sounds in OGG when used with
stb_vorbis
. (see Integration below) - Drawing bitmaps, texts and standard primitives all in software, this is feature, shut up!
- Sounds, fonts and bitmap API available.
On an average machine, a game 33 grams utilizing previous version of Punity runs in 240x240 at ~20μs per frame (equivalent of ~50000 FPS). On GCW Zero, the same game in 320x240 runs between 0.7ms to 2ms per frame (equivalent of ~500-1500 FPS).
- Perfect for use with 320x240 resolution. No one will ever need more, anyway.
- Perfect for simple pixel art with only 255 colors (0 reserved for transparency)!
- No need to worry about 8 bits of transparency, because you just get one!
- Limited to 30 frames per second that gives you them retro feels!
- Fuck other platforms, Windows is where it's at! (see Planned features for more platforms)
There is another project I'm working on called Blander which is also quite small game engine that allows a lot of things that Punity does not. It's more bland, though. It's much bigger than Punity, multiple files aaaaand it renders in hardware,... shut up!
- Ready-to-go instant package. No tedious external dependencies or fussing around with linkers: download, code and build from a single package.
- Simplicity and minimalism. Rather go for limitations than complexities.
- Portability Only get absolute minimum from the platform (window, input and drawing/audio buffers).
- Optional additional features. Plug & play addons for additional asset file formats, platforms or even scripting.
- No arbitrary limitations. All the limitations in the system have their reasons (performance, API simplicity). You might be writing a retro game, but the devices today have a lot of memory and computational power to use.
- Two files. Even though it's supposed to be a ready-to-go package, the essence is in two files that you can grab and use.
Note that the project is still in development. More documentation, features, examples and fixes is underway.
- More drawing functions very soon (
draw_frame()
). - Even more drawing functions a bit later (
draw_line()
,draw_circle()
, etc.). - Tile maps (with support of loading simple Tiled format and with customizable Tile elements).
- GIF recorder to record the screen to animated gifs. Optional, because it brings a dependency on rxi's modified version of
jo_gif.c
. - Draw lists to be able to push drawing operations in arbitrary order, but to get them sorted by z coordinate before rendering.
- Live asset reloading in debug mode while keeping the same possibility of baking assets to executable as it has now in release mode.
- SDL platform layer in an optional separate file
punity_sdl.c
with additional pre-made build scripts (Windows, Linux, Mac OS X, iOS, Android, Raspberry Pi, Dingux, etc.). This would introduce a dependency on SDL2, though. - Optional integration with Mason (my other project) that provides single-file build system for C and C++ to replace tedious
.bat
/.sh
maintenance with awesomely beautiful Lisp dialect from rxi.
Build is working with MSVC or MinGW.
- Download.
- Edit
main.c
. - Run
build
.
Build script is setup to compile main.c
as single-translation unit, so if you need another C files, include them in main.c
directly (same as I've include punity.c
there). If you need something else, modify build.bat
to you likings.
You can customize some aspects of Punity by changing macros in config.h
, which is included automatically from punity.h
. If you don't want this, use #define NO_CONFIG
and specify the macros in any way you feel is better for you.
To use your own building script and project structure, just grab punity.h
and punity.c
and use them as you see fit.
Punity defines the main()
function for you, but it gives you two other functions for you to implement as compensation:
init()
is called to initialize your own data, load assets, etc.step()
that is called every frame; you update and draw here.
Punity also gives you a pointer to CORE
(see Core
struct in punity.h
) to access canvas, inputs, timers, audio and memory.
The CORE
is used also within the Punity's functions to make it a bit easier on passing arguments. For example, text_draw()
expects a font to be set in CORE->font
.
Punity provides two fixed-size basic memory banks:
CORE->stack
is used to store larger amounts of temporary memory. Typically it's used store dynamic in-function buffers that are thrown away when the function ends.CORE->storage
is used for long term memory (like bitmaps, tilemaps, audio, etc.)
This way, no complex memory management is needed at all. Taking memory from the banks is just a matter of one pointer change (and a few optional assertions for you convenience). Should you need more memory, just change the STACK_CAPACITY
or STORAGE_CAPACITY
macros in config.h
to use more memory.
Remember, you always know in advance how many entities, assets and buffers, so generally there's no need to use dynamic memory management.
Get BankState
with bank_begin()
to store current state of the bank. This is useful if you want to do more bank_push()
calls and free the whole memory with just one call to bank_end()
.
BankState bank_state = bank_begin(CORE->stack);
f32 *channel0 = bank_push(CORE->stack, samples_count * sizeof(f32));
f32 *channel1 = bank_push(CORE->stack, samples_count * sizeof(f32));
// TODO: Do nasty things to `channel0` and `channel1`.
bank_end(&bank_state);
For simpler allocations, you can just use bank_push()
and bank_pop()
:
u8 *canvas = bank_push(CORE->stack, CANVAS_WIDTH * CANVAS_HEIGHT);
// TODO: Draw naked women on `canvas`.
// TODO: Blend naked women in `canvas` to the `CORE->canvas`.
bank_pop(CORE->stack, canvas);
Asset loading functions like bitmap_load()
store the bitmap data to CORE->storage
memory bank automatically. You are free to change pointer to CORE->storage
to any other bank you have. For example it might be handy to keep asset and state memory separate, so you can write and load them to file system separately.
For tilemaps, images, audio, or other additional files, you can either choose to access the files directly, or to pack them with the executable. To do that, take a peek at main.rc
. It looks like this:
Format:
<ResourceIdentifier> RESOURCE "<ResourcePath>"
Example:
font.png RESOURCE "res\\font.png"
Please, make sure you keep the first line icon.ico ICON "res\\icon.ico"
in there, so your application and the main window will have a nice icon (that you can customize too!).
In the code, I usually define a Game
struct that holds all the state and asset data like this:
typedef struct
{
Font font;
Bitmap background;
// ---
}
Game;
static Game game;
void init() {
// To load from a file.
bitmap_load(&game.font.bitmap, "res/font.png");
// To load from resource.
bitmap_load_resource(&game.font.bitmap, "font.png");
// To use the font.
CORE->font = &game.font;
}
// See the main.c for more examples.
To customize size and scale of the canvas, change CANVAS_WIDTH
, CANVAS_HEIGHT
and CANVAS_SCALE
macros in config.h
.
CORE->canvas
is Bitmap
struct that allows you to access the frame buffer at any time. You can either change the buffer manually, or use Punity's functions. See punity.h
for detailed information.
rect_draw()
- to draw a filled rectangle.frame_draw()
- to draw just an frame of a rectangle.bitmap_draw()
- to draw whole or a piece of bitmap.text_draw()
- to draw text usingCORE->font
.
Currently the drawing uses painter's algorithm (last drawn is on top). I plan to add DrawList
feature so Punity will sort the draw calls by z key for you.
Punity is prepared for use with stb_image or stb_vorbis to load images and audio files. The versions that I'm using are available in lib/
directory.
- In
config.h
enableUSE_STB_IMAGE
macro. It'll allow you to usebitmap_load
andbitmap_load_resource
to load PNG, GIS, PSD and more. - In
config.h
enableUSE_STB_VORBIS
macro. It'll allow you to usesound_load
andsound_load_resource
to load or stream OGG files.
You can run devenv bin\main.exe
in case you're running from Visual Studio Command Prompt (or with vcvarsall.bat
environment) to debug with Visual Studio. Note that you'll need to build with build debug
to be able to debug.
build
- Runs build with defaults (debug version of MSVC).build debug|release
- Runs build with MSVC, so you'll need to runvcvarsall.bat
or Visual Studio Command Prompt.build debug|release gcc
- Runs build with GCC (note that you need MinGW-W64 to compile successfully).
mingw/_mingw_unicode.h
&mingw/dsound.h
- Provided for your convenience to be able to build with default MinGW installation. This file is taken from MinGW-W64 project.lib/stb_image.h
- Optional library to load images.lib/stb_vorbis.c
- Optional library to load ogg audio files.build.bat
- MSVC and MinGW build batch file.config.h
- Example configuration file that customizes Punity. Can be switched off with using#define NO_CONFIG
main.c
- Example application using Punity.punity.h
- Punity's header file.punity.c
- Punity's source file.
A list of tasks I keep with important changes planned to appear in upcoming releases.
- Tilemaps.
draw_frame()
draw_circle()
draw_line()
- Limit the number of audio voices.
- Palette rotations, shifting, etc.
- Experiment with a reasonable replacement of
StretchDIBits
to gain more performance. - Animated GIF recording. Will need to solve the previous issues with it being too slow. Probably first record the frames to memory, then process the gif in a separate thread.
- @d7samurai - I use a lot of his ideas and pieces of code reworked from C++ to C.
- @x_rxi for immense support and inspiration.
- @d7samurai for the name Punity and a lot of wisdom donations.
- @j_vanrijn for his tall support 24/7.
- @cmuratori for handmadehero.org
- @nothings for stb.