Here is a sample game made with the engine (plays in browser): https://mattiasgustavsson.com/wasm/yarnspin
I have always been fond of game dev toolkits and construction sets. There is a beauty in the limitations they set, and something empowering about the simplicity they offer. I often enjoy figuring out how to make what I want to make within their given restrictions. When I first started using computers, first the Commodore 64 and later the Atari ST, I used a bunch of different gamedev tools.
Yarnspin as a concept, started out as the desire to make such a simple, oldschool engine. I imagined it as a choose-your-own-adventure game maker, and early on I intended to have features like money and skills, but later on I removed them to simplify things,
From the very start, I had the idea to make it in a retro style, but I also wanted to see if I could build in some tools to let you use any pictures and have them automatically fitted to a common, limited palette. I wanted it to be easy for anyone to make a game with it. When I first started out making games, I often stumbled on not being able to get something that looked decent. I wanted Yarnspin to try and bridge that gap.
The UI/layout was in part inspired by the games Maupiti Island and Mortville Manor on the Atari ST.
I decided to show the characters present in a side panel, just like in those games, and to have a corresponding items panel on the other side. At one point, I considered compass buttons for navigation, but dropped it. I might bring that idea back, to free up some options slots.
When it comes to dialogs, I decided to keep the layout simple and similar to the locations screen. As you can only talk to one character at a time, there is no character list, but there is the item list on the right (empty in this screenshot).
By changing the palette used, you can dramatically change the look. There’s a bunch of palettes included, and you can supply your own just by giving it an image file with the palette.
Using very limited palettes can give a highly stylized look. There is no emulation of graphics limitations beyond palette though. We are not trying to make things look like a specific old system here.
From the very start, I wanted games to be done by writing scripts, not by clicking a UI. Part of it was not having to build a visual tool, but also, I like working with text, I like the overview I feel I am getting with text files.
In fact, before even starting on the graphical parts, I had a very simple testbed for exploring the scripting language:
I wanted the scripts to be a lot more friendly than general purpose programming languages. You shouldn’t need to be an engineer to use this. My first version was in the form of ini files, which was very easy to implement, as I already had an ini loader. But it was awkward to use,
I soon moved to a custom parser, and that let me simplify many things. This is what the final scripting language looks like. This is a location named “welcome”, which shows an image and a text. When the user clicks to proceed, it goes to the new location “your_desk”
Adding options to a location is done like this. opt: declares the text to display for the option, and act: specifies where to go if that option is selected. Also, in the first line of the script, a flag (visited_another_room) is set to true. Flags can either true or false.
Flags can be tested like in this script, and text or images can be included or omitted based on any number of flags.
The scripting language has some more features as well, but they are all in this style. There are no arithmatic or variables beyound flags (though this might change in a future version).
As for the code, the whole thing is done entirely in C, using only stb-style single-file C libraries. It is built in just a single compile unit, so I don't need any build system - you just do:
cl source\yarnspin.c
to build everything.
I thought I’d talk a bit about the code components making up Yarnspin. Just a high-level overview, a look at what libraries I’m using and why, and how the different parts of the code is organized.
I guess most people on here are already well familiar with stb-style libs, but I'll explain them a bit anyway (at least my take on them).
The full source code is here: https://github.com/mattiasgustavsson/yarnspin/tree/30a54a3fc245986e7b23e7147ac2b808567e03fd
Let’s start off by looking at the Yarnspin code files, before looking into the libs. They are only just over 6k lines of code. There are two separate applications in yarnspin. One is the compiler and runtime, the other is the image editing tool. The latter is all contained in imgedit.h, and the former is the other files:
As you can see, there is just one .c file - everything else is a header file. Yarnspin is built from stb-style single-file libraries, and with the small projects I do, it makes a lot of sense to put everything in a single compile unit. This is a pattern I use in all my projects.
So yarnspin.c contains the program entry point, main
. And at the top of the file, I include all the libraries I am using - I’ll talk more about the different libs in a bit. The thing about stb-style libs, is that doing these includes only gets you declarations, not definitions.
This makes them different from C++ template header-only libs, which gets you both declarations and definitions at the same time. At the end of yarnspin.c, I include the same .h files again, but this time, each is preceded by an _IMPLEMENTATION
define to enable definitions.
These defines work similar to include guards, and toggle on the majority of the code in the lib, the actual definitions. If I was using a lot of huge libs, and they were to cause long compile times, I could put this second set of includes in a separate .c file. Compile times is never a problem for my projects though, so I keep things to a single C file for simplicity and convenience. Compiling yarnspin.c, which includes all the libs, is pretty much instant.
Now, I could have added the _IMPLEMENTATION
defines before the includes at the top of yarnspin.c, and then I wouldn’t need the second set at all. But then all the internal symbols and defines of the lib and files it includes would be visible in my app code and to intellisense. And that just makes things a bit noisy, so I prefer splitting things up like this. But having just a single set of includes for both declarations and definitions works just as well, and I sometimes do that.
The main
function in yarnspin.c will look at the commandline parameters, and decide to launch the image editor or to compile and load a yarn and launch it in the game runtime Yarnspin use my own app.h lib which create a window and gl context, and calls your own app_proc
func, where the app code is implemented.
app.h runs on Windows, Linux, Mac and in the browser, and compiles with gcc, clang, msvc and tcc. It can be built as either C or C++, and has a pretty streamlined API surface.
It would be nice to get around to making custom linux/mac backend implementations for app.h, but for the time being, I use SDL2 for linux and mac. For windows and wasm though, it has custom implementations that don't need SDL.
Now let’s have a brief look at all the libraries I’m using, and what I’m using them for.
I guess stb_image.h should be obvious - I use it to load images of any format, jpg, png, gifs etc. It’s an amazing file loader lib, highly recommended.
The next one, lzma.h, is the public domain LzmaLib conpression library by Igor Pavlov, repackaged into a stb-style single header lib by myself. I use it to compress the final data file for the game - more to make is content more obfuscated than because it really needs compressions.
stb_truetype.h reads ttf files, and renders them to pixels. I use this to convert ttf fonts to my own format for efficient rendering, as can be seen here: https://github.com/mattiasgustavsson/yarnspin/blob/f32201914cab601500b34945453d77bee0cbf8ca/source/gfxconv.h#L81. I’ll talk more about the in-game font renderer, pixelfont.h, in a bit.
The next lib (skipping app.h which we already looked at) is cstr.h, a fairly new and not entirely complete lib by myself for handling strings in C. It is mostly designed for ease of use and convenience, while still not wasteful in terms of performance. I’m still trying it out :)
stb_image_resize.h is used by both imgedit and yarnspin, to resize images from whatever input size to the fixed output size of Yarnspin. stb_image_write.h is not strictly necessary, but I use it to save a copy of each processed image as a PNG in the “cache” folder.
crtemu.h and crtemu_pc.h are two variants of my shader that emulates old CRT screens. What I really want to do at some point, is to implement the settings parameters in those libraries, and unify them into one lib with two default settings.
thread.h is my own library for doing multithreading - mutexes, threads, signals etc. It has implementations for Windows and Posix. It is used only by imgedit, to have a background thread which process images without blocking the main UI thread.
ini.h is a library I wrote to read and write classic old-school .ini files. I like the simplicity of those, and I use them whenever appropriate instead of XML or JSON. In Yarnspin, they are used to store the settings made with imgedit, so images gets processed according to those.
Yarnspin is limited to 256 colors, and the palette is loaded from an image file for simplicity. If the image contains 256 different colors or less, they are used as is. If there are more than 256 colors, it will use palettize.h to run a median-cut palettization algorithm on it.
paldither.h is used to take a 32-bit image and a palette, and convert it to an 8-bit palettized image using the specified palette and a choice of dither patterns. It is rather slow, as it loops through all combinations to find the best match. Really needs algorithmic optimization.
For drawing text in the game, I use my own pixelfont.h. It can draw with different alignment, wrapping, bold/italic/underline, even slow-print. I use it all the time, for all my text rendering needs. It has functions to create a font in the right format, from bitmaps of glyphs.
Once paldither.h have produced an 8-bit image, I use palrle.h to make a run-length-encoding of it. This is what’s saved to the output file, and the lib has functions to blit straight from the RLE data.
buffer.h is just a simple helper lib for reading/writing binary data to/from a dynamically growing buffer. It is quite handy when implementing custom binary formats. It’s used like this: https://github.com/mattiasgustavsson/yarnspin/blob/f32201914cab601500b34945453d77bee0cbf8ca/source/yarn.h#L150
By default, yarnspin does a bunch of adjustments to images before palettizing them, things like increasing contrast/saturation and applying a ”sharpen” filter. With imgedit you can set how they are applied. The img.h is a wip lib I use for doing that kind of image manipulation.
array.h is another new lib, for handling dynamic arrays in C. It works reasonably well, but is a bit lacking when it comes to type safety.
dir.h and file.h are simple utility libs for listing files and loading/saving them.
sysfont.h is a fixed font renderer with embedded font data, used only by imgedit to not have to rely on a specific font file being present.
So there we have it, all the libs used in yarnspin :)
I am currently working on version 2 of Yarnspin, and it has a lot of new stuff like sound/music and full hd RGB support. Most of it is ready, and release is not that far away. I might talk about that in a future post.