Update: This article has been updated in June 2022 to reflect GameCI’s plans to introduce a CLI tool, and in July 2022 to introduce some changes to the codemagic.yaml file in the sample project.
This article is written by Mina Pêcheux
TL;DR: GameCI is a toolbox for building Unity games, which contains a collection of Dockerized Unity Editors for different target platforms, and documentation to set up pipelines using GitLab CI and GitHub Actions. You can use GameCI with any CI platform that supports Docker, even with Codemagic. Alternatively, you can use Codemagic’s preinstalled Unity Editor, or install another version. Unlike GameCI itself, Codemagic also provides native integrations with Google Play and App Store to streamline automated deployment of your game.
Let’s compare two automation tools for Unity game projects: GameCI and Codemagic!
I’ve already had the chance to talk quite a lot about CI/CD and why it’s interesting for all kinds of software projects, including video games. As I also mentioned earlier this year, these tools are sadly not that widely adopted among game developers…
But this trend is beginning to catch on, and we are now starting to see some video-game-dedicated CI/CD tools!
In particular, for developers like me who want to automate their Unity projects, there’s a special solution called GameCI. Ever heard of it before? I decided to explore it and wanted to share my findings.
So, let’s learn more! In this article, I’ll:
- Talk about what GameCI is
- Detail the shared features and differences between GameCI and Codemagic
- Run through GameCI’s GitLab example project and compare this process with a comparable Codemagic setup
- Try to run GameCI on top of Codemagic and see if the two solutions can (and should) be combined
Learn why you should use a CI/CD for your Unity projects from this article. It also covers other popular tools such as Unity Cloud Build and Unity Build Server.
What is GameCI?
An overview of GameCI
GameCI is an open-source collaborative project that started back in 2020. It aims to provide a toolbox to ease the building and testing of your game projects. For now, it only supports Unity, but it could eventually grow into something more.
It’s important to note that it is fully community based, meaning that the original authors of the project, Gabriel Le Breton and Dick Webbink, aren’t the only ones to code and contribute features to GameCI. Rather, anyone can enter the GameCI Discord, join the “team,” and offer improvements, fixes, or new functionalities for the tool. Since it’s open source and developed by the community, the tool is completely free but also highly subject to changes in the future…
This is what you can find out on GameCI’s website. However, it’s still a bit unclear what exactly GameCI is. It sounds like it’s a continuous integration platform, but in fact, it’s not.
To put it short, GameCI is a collection of Dockerized versions of Unity. You can spin up a GameCI Docker container on pretty much any CI/CD platform that supports Docker and build your Unity project with it. Obviously, GameCI has documentation on how to do that. So far, it documents the process for the two most popular CI/CD tools — GitLab CI and GitHub Actions.
How do GameCI and Codemagic compare?
Given that GameCI isn’t actually a continuous integration and delivery platform, it’s quite hard to compare it with Codemagic. But we’re going to try anyway. First things first, it’s important to understand that GameCI doesn’t really cover the same steps of an automation pipeline as Codemagic. As stated in their docs, GameCI is a CI (continuous integration) tool, but it doesn’t handle the deployment part!
In other words, you can use GameCI to build your game and, optionally, run some tests on it, but it is by no means meant for (or capable of, at the moment) deploying your game to Google Play, the App Store, a Firebase App, etc.
In short: The sole goal of GameCI is to enable building and testing — it doesn’t involve CD (continuous delivery).
However, GameCI provides some documentation on using fastlane, a well-known publishing tool, to compensate for its lack of continuous development functionality.
Codemagic, on the other hand, can handle both the building and testing phases as well as deployment to various well-known and commonly used marketplaces or distribution services: It is a complete CI/CD automation tool.
Note: Codemagic can also deliver updates, builds, or changelogs to standard communication channels, such as email or Slack, which I find very cool because it’s often a pain to set up yourself. ;)
OK, so what about the build phase? Because GameCI is solely focused on Unity game projects, it can “afford” to dedicate more resources to them — in particular, the tool offers all current (modern) versions of Unity via Docker images. So you can simply get one of their Docker images, and you’ll instantly have a computer with a given OS and a specific Unity version on it.
In comparison, Codemagic is more diverse and can also be used for non-Unity projects. So, it doesn’t have that many Unity versions readily available. (At the time of writing, Codemagic build machines have either the 2020 LTS or the 2021 LTS version.) However, you can install a different version of Unity using Unity Hub CLI, and Codemagic has fully documented this process.
From this point of view, we could say that GameCI has a slight advantage since it offers more versions out of the box.
How do the two tools compare when it comes to testing? To be honest, they are very similar: It’s up to you to prepare your unit tests beforehand in the Unity project and run them from the shell using the right command. GameCI’s example GitLab project shows us how to run a test.sh
script during CI, which is about the same as the technique I used to run unit tests in my Unity project with Codemagic (just with a few more options).
What about ease of use? To me, this is the real tiebreaker. Automation is supposed to make your life easier — I’m OK with spending a bit of time learning the basics, but I prefer the docs and overall usage to be clear and centralized… which I feel is something that hasn’t yet been perfected by GameCI!
When you work with Codemagic, all you need to provide is your code versioned somewhere on a Git provider (GitHub, GitLab, Bitbucket) with a script to build the project from the command line and the codemagic.yaml
config file. Yes, you have to link this repo to your Codemagic app, but then everything happens in the Codemagic dashboard — there, you can even manage your team permissions and global environment variables.
GameCI, on the other hand, is more of a DIY solution. That said, their Docker images are great, and it’s very cool to know that you can easily download a machine config that’s all set to run your Unity version. :)
But once you have this image, it’s still up to you to set up your GitLab or GitHub CI/CD (using the basic .gitlab-ci.yml
or GitHub Actions features from your Git provider). You have to add the same types of build/config files as with Codemagic, and if you want to include the deployment part, you’ll need to install the necessary tools in your instance of the Docker in your pipeline.
Though it’s interesting to improve your DevOps skills and learn more about the various CI/CD tools that exist nowadays, this still means that GameCI is essentially a Docker image + sample projects provider. Unlike Codemagic, GameCI doesn’t have its own runners, and it requires some hardware to spin those Docker images on.
That’s a very valuable thing, but it’s clearly not as “all-inclusive” as Codemagic, and it doesn’t work “out of the box”!
Note: Incidentally, this is also why GameCI is free — apart from building Docker images and hosting them on the public Docker Hub (which is free for small projects), it doesn’t need to manage any actual hardware or API connections, and it leverages ready-built features like GitHub Actions or GitLab CI runners.
Additional notes
To wrap up this quick comparison, I’d like to point out a few things:
-
By default, because GameCI relies on the GitLab CI/GitHub Actions free runners, it has the same limitations as they do — meaning that if everyone is using them at the same time as you, you might need to wait for a while before you get your build!
-
On the other hand, since GameCI is based on the Git providers, it also shares some of their advantages… such as the on-premise/personal instances, which allow you to keep your code inside your enterprise! This can be helpful for avoiding security issues, and some companies might prefer this “customizable” solution instead of a cloud-based one like Codemagic.
-
If you look at Codemagic’s docs on building/deploying a Unity app, you’ll see that this requires a Pro or Plus license, whereas GameCI also allows you to use a Personal Unity license. Again, this is because GameCI is more of a “CI kit,” so it simply lets you handle the registration step with Unity and then expects a preactivated Personal license in its pipeline — this is not actually “included” in the CI features.
Still, if you want to automatically request a Personal license activation, you can get a nice script from GameCI to do it over here! ;)
A quick example with GameCI
To begin with, let’s test GameCI by seeing how to set up a basic CI pipeline with this tool. In this section and the rest of the tutorial, I’ll work with a basic sample Unity project I prepared and published publicly on GitLab. 🚀
As we can see in GameCI’s GitLab demo project, to use this CI, we need to add three things:
- The
.gitlab-ci.yml
that describes the CI stages, environment vars, and other config data (placed in the root of the repository) - A C# build script so that the project can be built via the command line (in a
BuildCommand.cs
file) - Various shell scripts to get the Unity license activation file, prepare a CI stage environment, or build the project (in a
ci/
folder)
Check out the GitLab project to see what these files contain — they’re mostly stuff I copy-pasted from the GameCI demo project, except for the .gitlab-ci.yml
that I re-adapted and simplified to suit my sample. :)
With this CI setup, each commit will trigger a new GitLab CI pipeline in my repo. However, since I’m using my personal Unity license, I first need to activate my license and add it as an environment variable in my GitLab project, as explained in the GameCI docs.
This can be done by running a first pipeline that will stop after producing a license request file (this file can be downloaded in the artifacts using the three-dot menu on the right):
Then, once the UNITY_LICENSE
var is defined (follow the GameCI instructions to get this value), the rest of the pipeline will run to build the project and produce a Mac app.
We see that the first pipeline (to activate the license) takes about five minutes, while the second one (to actually build the project) takes six minutes.
If you take a close look at the pipeline steps, you’ll notice that the Docker initialization is quicker after the first time. That’s because, thankfully, GitLab has a caching system that cleverly “stores” the Docker image for later reuse.
This means that as long as I use the same Docker image (i.e., in my case, the same OS and Unity version), I’ll be able to “skip” some of the setup time and proceed directly to the build step, which is fairly quick for this small project.
Of course, the same thing happens for the build-target-specific Docker during the build phase, so there might be some initial overhead if you change the build target between two pipeline executions (because GitLab will need to pull another Docker that is specifically for this build target and is not yet cached).
This small experiment shows us that:
- Setting up a CI with GitLab and GameCI is fairly straightforward…
- … as long as you’re already familiar with the GitLab CI tools and have some ready-made shell scripts to run Unity from the command line!
Mixing GameCI and Codemagic?
OK — we just saw that GameCI is interesting because it can instantly provide you with the right Unity version in a given OS using Docker. On the other hand, at the time of this writing, Codemagic only has two available Unity versions: Unity 2020.3.31f1 and 2021.3.0f1.
So, what if we tried to take the best from both worlds and use these Docker images on a Codemagic machine? Can we gain a performance boost by mixing the two or maybe just use Codemagic instead of GitLab CI/GitHub Actions?
Foreword: Installing a specific Unity version on a Codemagic instance (without GameCI)
Let’s first prepare a little “comparison sample” in which we stick to the Codemagic tool on its own and simply install a specific version of Unity on it. This way, we’ll be able to compare the build time with a direct Unity installation and a GameCI-prepared Docker image.
The Unity project I’ll use for these Codemagic tests is still the same sample demo from before.
My Codemagic pipeline is very simple: I’ll build my project for a Mac, but before I run any Unity-related commands, I’ll first install my custom Unity version 2020.3.18f1 on the machine.
All I have to do is:
- Go to Unity’s download archive page
- Select my major Unity version in the top tab:
- Then, I’ll find my Unity version (in this case, 2020.3.18f1) and open the “Release Notes”:
- And finally, scroll down to the bottom of the page to find the changeset of this specific version:
And once I have this changeset, I’ll just run the following workflow (in my codemagic.yaml
config file):
workflows:
unity-mac-workflow:
name: Unity Mac Workflow
environment:
groups:
# Add the group environment variables in Codemagic UI (either in Application/Team variables) - https://docs.codemagic.io/variables/environment-variable-groups/
- unity # <-- (Includes UNITY_SERIAL, UNITY_USERNAME, UNITY_PASSWORD)
vars:
UNITY_VERSION: 2020.3.18f1
UNITY_VERSION_CHANGESET: a7d1c678663c
UNITY_BIN: /Applications/Unity/Hub/Editor/${UNITY_VERSION}/Unity.app/Contents/MacOS/Unity
scripts:
- name: Install Unity
script: |
/Applications/Unity\ Hub.app/Contents/MacOS/Unity\ Hub -- --headless install --version ${UNITY_VERSION} --changeset ${UNITY_VERSION_CHANGESET}
- name: Activate License
script: $UNITY_BIN -batchmode -quit -logFile -serial ${UNITY_SERIAL?} -username ${UNITY_USERNAME?} -password ${UNITY_PASSWORD?}
- name: Build
script: $UNITY_BIN -batchmode -quit -logFile -projectPath . -executeMethod BuildScript.BuildMac -nographics
artifacts:
- "mac/sample-project.app"
publishing:
scripts:
- name: Deactivate License
script: $UNITY_BIN -batchmode -quit -returnlicense -nographics
Note: As I explained in my other Codemagic articles, I also need to set up three environment variables in my Codemagic app (in the “unity” group): UNITY_SERIAL
, UNITY_USERNAME
, and UNITY_PASSWORD
.
If I run this workflow, I eventually get my Mac app as an artifact… after 16 minutes, 12 of which are just for the Unity installation!
This clearly shows one of Codemagic’s drawbacks compared with GameCI for using a specific Unity version — downloading and installing a custom editor can take a while, and because we are “borrowing” a different machine each time, we have to rerun these setups every time and don’t have the same caching tricks as before with the GitLab CI.
Conversely, if you’re using the Unity version that is preinstalled on the Codemagic machine, then there won’t really be any setup time before activating the license and building the project (whereas the GitLab-based CI always requires some docker pulls and execution, even with caching). :)
Running GameCI on top of Codemagic
Now that we have a rough idea of how long it takes to set up the environment to build the Unity project with version 2020.18.3f1 on a Codemagic instance, let’s see if GameCI can help.
Mixing GameCI and Codemagic the wrong way
We’re going to start with a naive approach… and see why it’s clearly suboptimal.
Suppose we keep the same overall configuration, and in particular, we keep our Mac Codemagic instance for the build. This sounds pretty logical since we want to compare two scenarios, right?
Then, rather than downloading a Unity editor version from Unity’s archive downloads, I instead want to use the docker
CLI tool that is preinstalled on Codemagic machines to pull the matching GameCI Docker image (that contains the right Unity version).
The build phase is more complex than in our initial GitLab-CI-based experiments because this time, we can’t run the GameCI Docker images as intended: Since we’re on remote Codemagic machines, we can’t use the interactive bash
but instead have to cheat by “keeping the Docker alive” and then sending it commands with [docker exec](https://docs.docker.com/engine/reference/commandline/exec/)
.
Finally, we get the following codemagic.yaml
configuration file (with both the previous “pure” Codemagic workflow and our new “Codemagic/GameCI mix” workflow):
workflows:
unity-mac-workflow:
name: Unity Mac Workflow
max_build_duration: 60
environment:
groups:
# Add the group environment variables in Codemagic UI (either in Application/Team variables) - https://docs.codemagic.io/variables/environment-variable-groups/
- unity # <-- (Includes UNITY_VERSION, UNITY_SERIAL, UNITY_USERNAME and UNITY_PASSWORD)
vars:
UNITY_VERSION: 2020.3.18f1
UNITY_VERSION_CHANGESET: a7d1c678663c
UNITY_BIN: /Applications/Unity/Hub/Editor/${UNITY_VERSION}/Unity.app/Contents/MacOS/Unity
BUILD_SCRIPT: BuildScript.BuildMac
BUILD_OPTIONS: -batchmode -quit -logFile -projectPath . -nographics
scripts:
- name: Install Unity
script: |
/Applications/Unity\ Hub.app/Contents/MacOS/Unity\ Hub -- --headless install --version ${UNITY_VERSION} --changeset ${UNITY_VERSION_CHANGESET}
- &activate_unity_license
name: Activate License
script: ${UNITY_BIN} -batchmode -quit -logFile -serial ${UNITY_SERIAL?} -username ${UNITY_USERNAME?} -password ${UNITY_PASSWORD?}
- &build_app
name: Build
script: ${UNITY_BIN} ${BUILD_OPTIONS} -executeMethod ${BUILD_SCRIPT}
artifacts:
- &app build/*.app
publishing:
scripts:
- &deactivate_unity_license
name: Deactivate License
script: ${UNITY_BIN} -batchmode -quit -returnlicense -nographics
unity-gameci-mac-workflow:
name: Unity Game.ci Mac Workflow
max_build_duration: 60
environment:
groups:
- unity # <-- (Includes UNITY_VERSION, UNITY_SERIAL, UNITY_USERNAME and UNITY_PASSWORD)
vars:
UNITY_VERSION: 2020.3.18f1
DOCKER_IMAGE: unityci/editor:${UNITY_VERSION}-mac-mono-0
DOCKER_CONTAINER_NAME: unityci-editor
BUILD_TARGET: StandaloneOSX
BUILD_NAME: SampleProject
PROJECT_PATH: ${CM_BUILD_DIR}
BUILD_PATH: ${CM_BUILD_DIR}/build/
DOCKER_PROJECT_PATH: /usr/games
DOCKER_BUILD_PATH: ${DOCKER_PROJECT_PATH}/build/
UNITY_BIN: docker exec ${DOCKER_CONTAINER_NAME} /usr/bin/unity-editor
BUILD_SCRIPT: BuildCommand.PerformBuild
BUILD_OPTIONS: |
-batchmode -quit -logFile -projectPath ${DOCKER_PROJECT_PATH} -nographics -buildTarget ${BUILD_TARGET} -customBuildTarget ${BUILD_TARGET} -customBuildName ${BUILD_NAME} -customBuildPath ${DOCKER_BUILD_PATH}
scripts:
- name: Startup Docker
script: until docker system info > /dev/null 2>&1; do sleep 1; done
- name: Get Docker
script: docker pull ${DOCKER_IMAGE}
- name: Setup Build Dir
script: mkdir ${BUILD_PATH}
- name: Run Docker
script: |
docker run -tid --rm --name ${DOCKER_CONTAINER_NAME} -v ${PROJECT_PATH}:${DOCKER_PROJECT_PATH} ${DOCKER_IMAGE} bash
- *activate_unity_license
- *build_app
artifacts:
- *app
publishing:
scripts:
- *deactivate_unity_license
- name: Cleanup Docker
script: exit
Note: Here, I’m using some anchors and references to avoid redefining my workflow steps and thereby avoid inconsistencies. For more info, check out Codemagic’s docs. ;)
But at this point, we’re going to run into two issues:
- Pulling the Docker still takes several minutes, and there is no caching (we do gain about five minutes, which is interesting — but overall, it depends on the bandwidth that’s available at the moment)
- Building the Unity project inside the Docker is actually not working that well!
If you try to run this workflow on a Mac instance, you’ll see that it dramatically slows down the Unity command-line build (either locally or on the Codemagic servers)… and brings our total CI time to roughly one hour!
Now, why does this happen? Well, if you take a look at the build log file (you can download it by clicking the small download icon on the right of the “Build” step), you’ll immediately see something strange but crucial:
Unity Editor version: 2020.3.18f1 (a7d1c678663c) Branch: 2020.3/staging Build type: Release Batch mode: YES System name: Linux Distro version: #1 SMP Tue Dec 1 17:50:32 UTC 2020 Kernel version: 4.19.121-linuxkit Architecture: x86_64 Available memory: 1989 MB
Take a look at the last line: Despite the fact that Codemagic’s Mac Pro machines have a total of 32 GB of available memory, the Unity Editor is only using 2 GB. This, in turn, means that the Unity Editor is severely capped and can’t function at its real speed… hence, the hour-long build!
This is a well-known problem with Dockers running on Macs: Because of architectural limitations, the Docker container has a limited amount of memory available. This amount is set machine-wide, i.e., it’s the same for all Dockers (sadly, the --memory
and --memory-swap
options from the official Docker’s docs don’t work on Mac!), and it’s 2 GB by default.
In other words, the only way to bypass this limitation is to set a higher default memory usage for all Docker containers on this machine… which is not the way to go. Codemagic is a platform that lends you a machine: In addition to it being potentially dangerous, our workflow should not require us to make admin-level modifications to the computer’s configuration!
Mixing GameCI and Codemagic the right way!
The solution is actually pretty simple: We need to use something other than a Mac as a host machine. Remember that since we’re building in a GameCI Unity Mac Docker image, we’ll be able to produce a Mac app anyway — no matter which type of system the Docker container is running on.
Whenever you work with Docker, the truth is that Linux is your friend. This OS works great with the tool because they share a sort of parenting. In particular, when you run a Docker on a Linux machine, the computer doesn’t distinguish between the host and the container memory; therefore, we’ll be able to take advantage of the full 32 GB of memory!
So let’s update our codemagic.yaml
file to specify we want a Linux instance:
workflows:
unity-mac-workflow:
...
unity-gameci-mac-workflow:
name: Unity Game.ci Mac Workflow
max_build_duration: 60
instance_type: linux_x2
...
Just by switching our Codemagic instance type (and not changing anything else in the workflow — since Mac is Unix based, all shell commands are still valid), we immediately get these results:
Pretty great, right? The CI now completes in just under five minutes, and we can see that the Docker pull and Unity project build phases are way quicker than before. We still get a Mac app because the Docker itself doesn’t care if it’s run on a Mac or a Linux, but we now use way more memory, which allows Unity to build faster:
Unity Editor version: 2020.3.18f1 (a7d1c678663c) Branch: 2020.3/staging Build type: Release Batch mode: YES System name: Linux Distro version: #33~20.04.3-Ubuntu SMP Tue Jan 18 12:03:29 UTC 2022 Kernel version: 5.11.0-1029-gcp Architecture: x86_64 Available memory: 32104 MB
With this alternative technique, we even beat the original GitLab-based GameCI pipeline (which took roughly 10 minutes to complete, considering the license activation time)!
Quick final notes
Is this workflow always the best?
Despite being a bit cumbersome in terms of setup (the codemagic.yaml
file is honestly a bit more intricate than usual), this quick experiment shows that mixing Codemagic with GameCI can be pretty interesting in terms of performance! We can benefit from the power of a Linux computer while using any Unity version we want and actually producing a Mac app.
However, keep in mind that the download time of the initial Docker image actually depends not only on the current state of the network but also on the platform you want to build for: If you take a look at all the Docker images GameCI offers, you see that their size is different for each build platform. And images for Mac builds are generally the smallest ones, so chances are that others would take longer to download.
You should obviously try the various alternatives shown here in your project to see which one is the best in your case!
Security and important legal gotchas
Did you know that Apple currently forbids you from running macOS (software) on non-Apple hardware? Doing so results in a breach of their EULA (which you a priori agreed to when you first turned on an Apple device…)!
That’s why Mac GameCI Docker images don’t actually use macOS but instead run on Linux. ;)
However, this is only possible because we are building a Standalone OS X version of our project, i.e., an app meant to be used by Mac computers (laptops or iMacs). If your goal is to build for iOS smartphones, then you’ll need to run your pipeline on Apple hardware (in other words, a Mac machine) with XCode installed, so you won’t be able to follow the steps shown here.
You’ll need to either:
- Stick with GameCI on its own and, in your GitHub or GitLab CI workflow, make sure that the signing part is done on a Mac, as shown in their docs
- Or stick with a “pure” Codemagic workflow done on a Mac instance, in which case, as we’ve seen before, you should probably directly download the proper Unity version if you’re not using the one installed by default on the machine
Conclusion
In this post, we’ve discussed GameCI. We’ve seen that it is actually not exactly a CI tool (even though they are working on that, and will soon introduce a CLI tool) but rather a collection of Dockerized Unity versions with some documentation and a community around it. These docs provide you with quick getting-started tips and samples to run GameCI using the GitLab CI or the GitHub Actions, but in truth, we can use the Dockers on any CI we want, including Codemagic.
GameCI is interesting because it allows you to quickly get the specific version of Unity you want, along with the OS you want to build for. This can be particularly relevant if you want to take advantage of Codemagic’s computational power or its direct features to publish to the App Store or the Google Play Store (which GameCI doesn’t have)… but you also want/need to build your Unity project with a given Unity version that is not available by default on the Codemagic machines. (Remember, Codemagic already has the latest Unity version installed, and it takes some time to install another one.)
But there is no silver bullet, so it’s best to try it out for yourself and see whether it’s better to use Codemagic on its own for your project or mix it with GameCI. ;)
I hope you enjoyed this tutorial — and, of course, don’t hesitate to share your ideas for other DevOps topics you’d like me to make Unity tutorials on! You can find me on Codemagic’s Slack, on Medium, or on Twitter.
Don’t forget that you can check out the sample project on GitLab over here. 🚀
Mina Pêcheux is a freelance full-stack web & game developer. She’s also passionate about computer graphics, music, data science, and other topics! She runs her own blog. Alternatively, you can find her on Twitter.