Automate Building and Publishing a Game on itch.io

Automated builds are a great way to produce consistent results, and to make a developer's life easier. In this post, I show how to use GitHub Actions to first create a release for a game, and then push that release to itch.io.

Image showing an arrow that points from the GitHub logo to the itch.io logo

I really like automation in software development (and elsewhere). And since my personal projects are hosted on GitHub, I have fully adopted GitHub Actions as my continuous integration platform. When I push code, a handful of actions is started that lint the code, check its style, and run automated tests. When they all pass, it gives me confidence in the quality of my work.

Features • GitHub Actions
Easily build, package, release, update, and deploy your project in any language—on GitHub or any external system—without having to run code yourself.

For my upcoming game Auto Traffic Control, I wanted to use GitHub Actions again to create reproducible builds of the game with a predictable and consistent quality. The goal was to avoid human error by fully automating the process, from build to release on itch.io.

Download the latest indie games
itch.io is a simple way to find, download and distribute indie games online. Whether you’re a developer looking to upload your game or just someone looking for something new to play itch.io has you covered.

Create a New Action

I recommend creating a new action for this workflow. Especially if you want to build your game for multiple operating systems, the file will get pretty long as is. Go ahead and create a new YAML file in .github/workflows/itch.yml, and add the following content:

---
name: itch.io

env:
  itch_project: jdno/auto-traffic-control

Change the itch_project string to match your username on itch.io and the name of your project. If you like, you can also change the name of the action.

Trigger a Release

The setup that I have developed over the years for my GitHub Actions has one important feature: It has different actions for different workflows.

When I push code to GitHub, actions are started that check the quality of the code. These checks have to pass before I can merge the code into the main branch. Their purpose is to ensure a consistent code quality, not to create a release.

When I want to create a new release, I actually use the release feature on GitHub. I like writing a short summary of what's changed, and include a changelog in the release notes. It makes the release feel more "official", and like an actual achievement. When I create a release, another action is kicked off that builds the game in release mode, and then pushes the game to itch.io.

The following snippet is what I use to trigger my release workflow. It runs the action whenever a pre-release or release is published on GitHub:

"on":
  release:
    types:
      - prereleased
      - released

You can also build and publish the game every time code is merged into the default branch with a snippet like below. The choice is ultimately yours, and depends on what makes sense for your development workflow.

"on":
  push:

Build the Game

The build step depends on your game, and I cannot give you advice on it. The steps will look different based on the programming language or game engine that you use. On a high level, though, you want to do the following:

  1. Check out the code
  2. Set up a build environment
  3. Compile the game

The following examples assume that you also copy the binary and any resources/assets that the game needs into a dist folder. This is the folder that will be pushed to itch.io. What needs to go into this folder again depends on the game engine that you are using.

I create builds of my game for Linux, macOS, and Windows. Each operating system requires slightly different dependencies, so I decided to split the build into three different jobs: one for each operating system. Below is the Linux build as an example. The job installs the dependencies, checks out the code, sets up the Rust toolchain, and then builds the game.

jobs:
  linux:
    name: Publish Linux build
    runs-on: ubuntu-latest

    steps:
      - name: Install system dependencies
        run: |
          sudo apt-get update && sudo apt-get install -y \
          libx11-dev \
          libasound2-dev \
          libudev-dev

      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up Rust toolchain
        uses: actions-rs/toolchain@v1

      - name: Build release artifact
        uses: actions-rs/cargo@v1
        with:
          command: build
          args: --release

After these steps, the release artifact will be copied into the dist folder along with the assets that my game use. Theoretically, I can now run my game from this folder and it would work.

Publish to itch.io

The final step is publishing the game to itch.io. We will be using itch's butler, a tool that makes it easy to upload games to itch. This is the command to publish a new version of a game:

butler push directory user/game:channel --userversion version

The command takes a directory as its argument, and uploads the contents of this directory to itch.io. This will be our dist directory. The next argument is the project, which we've set at the beginning in the itch_project variable, followed by the channel.

Channels are an important concept on itch.io, and the channel name can be used to automatically tag the game on the platform. For example, pushing to the linux channel will tag the game as a Linux executable.

Lastly, we pass the optional userversion argument. The goal is to use the same version number on GitHub and on itch.io.

I am getting these variables from the release that I created on GitHub. When a release is published and the action is started, GitHub will add the release to the action's context. This makes it possible, for example, to access the title of the release with github.event.release.name (which is 0.1.0 for this release). If you want to run the action on every commit (i.e. on: [push] above), you need to either hard-code them or find another way to provide them dynamically.

      - name: Set version
        run: echo "version=${{ github.event.release.name }}" >> $GITHUB_ENV

      - name: Set beta channel
        if: github.event.action == 'prereleased'
        run: echo "itch_channel=linux-beta" >> $GITHUB_ENV

      - name: Set release channel
        if: github.event.action == 'released'
        run: echo "itch_channel=linux-stable" >> $GITHUB_ENV

I am particularly proud of finding a way to support pre-releases. When a release is tagged as a pre-release on GitHub, the game will be pushed to the beta channel. This makes it easy for me to test a new version of the game, before making it generally available.

Now that we have set the variables that are required to run butler, we can first add the tool to our workflow and then run it. Pushing a build to itch.io requires an API key, which you can find here.

      - name: Set up butler
        uses: jdno/setup-butler@v1

      - name: Publish game to itch.io
        run: |
          butler push dist ${{ env.itch_project }}:${{ env.itch_channel }} --userversion ${{ env.version }}
        env:
          BUTLER_API_KEY: ${{ secrets.ITCHIO_API_KEY }}

A new version of your game is now available on itch.io.

Profit

This is admittedly an advanced action with some pretty complex steps. Getting it to work correctly for your project might require a few iterations. It took me many builds to get this right. But the results are great, and totally worth the effort!

Most importantly, you now have a reproducible build. Getting your game ready for release is not a dark art anymore, nor does it require finding the note with the instructions somewhere on your desk. You can just look at the action to see which steps are required to build and then push the game.

Because the build is automated, it is also guaranteed to produce consistent results. Your local development environment cannot break the build or the game in mysterious ways, because the build environment is always clean.

And you know that everything that is required to build the game is backed up on GitHub. All code and all assets are safely stored in a second place, with a version history.

Follow me

If you're interesting in my game Auto Traffic Control or programming games in general, make sure to follow along. I am not sure where the road will take us, but I am very excited for the journey!

Subscribe on my blog to receive weekly updates about the progress on the project.

Auto Traffic Control – A Video Game for Programmers
Auto Traffic Control, fittingly abbreviated ATC, will be a video game for programmers about safely routing planes to an airport. It takes inspiration from the old school hit Flight Control, and adapts its game mechanics for programming.

I am also planning to stream some if not all of the development of this game on Twitch, so follow me there as well.

Twitch
Twitch is the world’s leading video platform and community for gamers.