5 min read

Many game engines have the concept of a content pipeline. Your project includes a collection of assets like images, sounds, and music. These may be stored in one format that you use for development but translated into another format that gets packaged along with the game. The content pipeline is responsible for this translation.

If you are developing a small game, the overhead of a large-scale game engine may be more than what you want to deal with. For those who prefer a minimalistic work environment, a relatively simple Make file can serve as your content pipeline.

It took a few game projects for me to set up a pipeline that I was happy with, and looking back, I really wish I had a post like this to get me started. I’m hoping this will be specific enough to get you started but generic enough to be adaptable to your needs!

The setup

Suppose you are making a game and you use Aseprite to create pixel art and MMPZ LMMS to compose music, your game’s file structure would look like this:

- src/
    - ... source code ...

- content/
    - song1.mmpz
    - song2.mmpz
    - ...
    - image1.ase
    - image2.ase
    - ...

- bin/
    - game
    - song1.ogg
    - song2.ogg
    - ...
    - image1.png
    - image2.png
    - ...

src contains your source code—the language is irrelevant for this discussion.

content contains the work-in-progress art and music for your game. They are saved in the source formats for Aseprite and LMMS (.ase and .mmpz, respectively).

The bin folder represents the actual game “package”—the thing you would distribute to those who want to play your game. bin/game represents the executable built from the source files. bin/ also contains playable .ogg files that are exported from the corresponding .mmpz files. Similarly, bin contains .png files that are built from the corresponding .ase files.

We want to automate the process of exporting the content files into their game-ready format.

The Makefile

I’ll start by showing the example Makefile and then explain how it works:

CONTENT_DIR = content
BIN_DIR = bin

IMAGE_FILES := $(wildcard $(CONTENT_DIR)/*.ase)
MUSIC_FILES := $(wildcard $(CONTENT_DIR)/*.mmpz)

all: code music art

code: bin_dir
    # build code here ...

bin_dir:
    @mkdir -p $(BIN_DIR)

art: bin_dir $(IMAGE_FILES:$(CONTENT_DIR)/%.ase=$(BIN_DIR)/%.png)

$(BIN_DIR)/%.png : $(CONTENT_DIR)/%.ase
    @echo building image $*
    @aseprite --batch --sheet $(BIN_DIR)/$*.png $(CONTENT_DIR)/$*.ase --data /dev/null

music: bin_dir $(MUSIC_FILES:$(CONTENT_DIR)/%.mmpz=$(BIN_DIR)/%.ogg)

$(BIN_DIR)/%.ogg : $(CONTENT_DIR)/%.mmpz
    @echo building song $*
    lmms -r $(CONTENT_DIR)/$*.mmpz -f ogg -b 64 -o $(BIN_DIR)/$*.ogg

clean:
    $(RM) -r $(BIN_DIR)

The first rule (all) will be run when you just type make. This depends on code, music, and art. I won’t get into the specifics of code, as that will differ depending on the language you use. Whatever the code is, it should build your source code into an executable that gets placed in the bin directory.

You can see that code, art, and music all depend on bin_dir, which ensures that the bin folder exists before we try to build anything.

Let’s take a look at how the art rule works. At the top of the file, we define IMAGE_FILES := $(wildcard $(CONTENT_DIR)/*.ase). This uses a wildcard search to collect the names of all the .ase files in our content directory. The expression $(IMAGE_FILES:$(CONTENT_DIR)/%.ase=$(BIN_DIR)/%.png) says that for every .ase file in the content directory, we want a corresponding .png file in bin. The rule below that provides a recipe for building a single .png from a single .ase:

$(BIN_DIR)/%.png : $(CONTENT_DIR)/%.ase
    @echo building image $*
    @aseprite --batch --sheet $(BIN_DIR)/$*.png $(CONTENT_DIR)/$*.ase --data /dev/null

That is, for every png file we want in bin, we need to find a matching ase file in content and invoke the given aseprite command on it.

The music rule works pretty much the same way, but for .mmpz and .ogg files instead.

Now you can run make music to build music files, make art to build art files, or just make to build everything. As all the resulting content ends up in bin, the clean rule just removes the bin directory.

Advantages

  • You don’t have to remember to export content every time you work on it. Without a system like this, you would typically have to save whatever you are working on to a source file (e.g. a .mmpz file for LMMS) and export it to the output format (e.g. .ogg). This is tedious and the second part is easy to forget.
  • If you are using a version control system (and you should be!), it doesn’t have to track the bin directory, as it can be generated just by running make. For git, this means you can put bin/ in.gitignore, which is a huge advantage as git doesn’t handle large binary files well.
  • It is relatively easy to create a distributable package for your game. Just run make and compress the bin directory.

Summary

I hope this illuminated how a process that is typically wrapped up in the complexity of a large-scale game engine can be made quite simple.

While I used LMMS and Aseprite as specific examples, this method can be easily adapted to any content-creation programs that have a command-line tool you can use to export files.

About the author

Ryan Roden-Corrent is a software developer by trade and hobby. He is an active contributor in the free/opensource software community and has a passion for simple but effective tools. He started gaming at a young age and dabbles in all aspects of game development, from coding to art and music. He’s also an aspiring musician and yoga teacher. You can find his open source work at here and Creative Commons art at here.

LEAVE A REPLY

Please enter your comment!
Please enter your name here