skip to content
clait.sh

Version Control Options in Unreal Engine

/ 7 min read

Last Updated:

Version Control is vital to any project, and for those developing with Unreal Engine, a basic GitHub repository may not cut it, especially when the project exceeds the 1GB limit of the free plan.
Moreover, Unreal Engine projects often contain several binary files that pose challenges for Git, leading to daunting merge conflicts.

So, what’s the solution?
Here’s a look at two main contenders:

Perforce: A Solid Choice

Perforce is a commercial version control system. It provides a free version for teams of up to 5, accommodating 20 workspaces1.

Its strengths comprise efficient binary file handling, seamless Unreal Engine integration (since Epic Games employs it), and its status as an industry benchmark.
On the flip side, its limitations include the constraints of the free tier (although generous), a learning curve for those used to Git, and a user experience that can sometimes feel unintuitive.

Having used Perforce extensively, I can vouch for its efficacy.

For each project, you’ll establish a depot (akin to a repository), a stream (similar to a branch), and a workspace (your local project copy). Setting this up the first time is not as swift as a simple git init.

Onboarding novices, especially for game jams or brief projects, can be slightly challenging given the limited familiarity with Perforce outside the industry.

Setting Up Your Perforce Server with Docker

Although Perforce doesn’t offer a pre-made Docker image, we can follow the official documentation to create one.

Initialize by creating a directory for Docker files and configurations. For storing the actual Perforce data, you can either map specific paths (like an external Volume in my setup) or use Docker volumes.

cd /path/to/your/storage # (e.g. /mnt/Storage/docker/)
mkdir perforce && cd perforce
touch docker-compose.yml
touch Dockerfile

We’ll also need 3 directories to store:

  1. Perforce configuration
  2. Perforce data (e.g. depots)
  3. Perforce databases
mkdir p4dctl.conf.d
mkdir perforce-data
mkdir dbs

Once we have defined the directories in which we’ll save our data, we can edit our docker-compose and map the locations.
Since we first need to run the configuration script, we’ll keep the p4dctl.conf.d mapping commented until we’re ready to fully launch the instance.

version: '3'
services:
perforce:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
volumes:
#- /mnt/Storage/docker/p4dctl.conf.d:/etc/perforce/p4dctl.conf.d
- /mnt/Storage/docker/perforce-data:/perforce-data
- /mnt/Storage/docker/dbs:/dbs
environment:
- P4PORT=1666
- P4ROOT=/perforce-data
ports:
- 1666:1666

We’re now ready to edit our Dockerfile like so:

FROM ubuntu:focal

# Update the image
RUN apt-get update
RUN apt-get dist-upgrade -y

# Install dependencies and add the Perforce APT repository (ref. Perforce Docs)
RUN apt-get install -y wget gnupg
RUN wget -qO - https://package.perforce.com/perforce.pubkey | apt-key add -
RUN echo 'deb http://package.perforce.com/apt/ubuntu focal release' > /etc/apt/sources.list.d/perforce.list
RUN apt-get update

# Install Perforce
RUN apt-get install -y helix-p4d

# Start Perforce and tail logs
CMD chown -R perforce:perforce /perforce-data && cd /dbs && p4dctl start master && tail -F /perforce-data/logs/log

Save the file, we’re ready to run our instance for the first time and copy the setup configuration to our mapped directory.
Run the following command, and don’t forget to change the output directory to match your actual mapped directory (e.g., /mnt/Storage/docker/p4dctl.conf.d).

$ docker compose run -T --rm perforce \
tar czf - -C /etc/perforce/p4dctl.conf.d  . | \
tar xvzf - -C p4dctl.conf.d/ # change this

Next, as we have now initialised the Perforce server and let it create the initial configuration files, we can uncomment the last mapping in our compose:

version: '3'
services:
perforce:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
volumes:
- /mnt/Storage/docker/p4dctl.conf.d:/etc/perforce/p4dctl.conf.d
- /mnt/Storage/docker/perforce-data:/perforce-data
- /mnt/Storage/docker/dbs:/dbs
environment:
- P4PORT=1666
- P4ROOT=/perforce-data
ports:
- 1666:1666

Finally, we can run Perforce configuration script to finish setting up the instance before taking it for a ride. Run the following command:

docker compose run --rm perforce /opt/perforce/sbin/configure-helix-p4d.sh

Follow the instructions, and remember to set the following configuration when asked:

Server Name: master
Server Root: /perforce-data
Server Port: 1666

You made it! You can now bring up your very own Perforce instance with:

docker compose up --build -d

Congratulations!

.p4ignore

Just as Git, Perforce also uses ignore files - namely .p4ignore
Here’s mine:

Show my .p4ignore
# Ignore built binaries and temporary build files
Intermediate/
Binaries/
Saved/
DerivedDataCache/
Platforms/
Build/

# Plugins
# Uncomment the line below to ignore the plugin directory (binaries) - alternate the comments below
Plugins/ 
#Plugins/*/Binaries/*  
#Plugins/*/Intermediate/* 

# Ignore root Visual Studio solution files. 
# We do check in some sln files in subdirectories, so only ignore the ones found in the root.
*.sln

# Ignore Rider settings
.idea/

# Ignore VS files 
.vs/
.vsconfig

# Ignore Xcode files
*.xcworkspace/

# Ignore P4config file
.p4config

# Git
.git/

Place this in your future projects root folder.

Git LFS: A Git-Centric Approach

Git usually tops the list when considering version control. Although it’s not perfectly suited for Unreal projects, with a few tweaks, it can be a viable alternative to Perforce.

Bypassing GitHub’s free tier limitations is a breeze with self-hosted Gitea. It natively supports Git LFS and file locking.

Setting Up Your Gitea Server with Docker Compose

If you aren’t keen on setting up your Gitea server, you can skip to the Unreal Engine 5 configuration.

Gitea deployment is straightforward, with multiple available methods.
For my setup, I used Docker Compose on a VPS, guided by the official documentation, and selected MYSQL for database management over the default SQLite.

Show my Gitea docker-compose.yml

The /mnt/Storage/ folder is a mounted volume attached to my VPS, which is where I store all my Gitea data.

version: "3"

networks:
  gitea:
    external: false

services:
  server:
    image: gitea/gitea:latest
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=mysql
      - GITEA__database__HOST=db:3306
      - GITEA__database__NAME=change-me
      - GITEA__database__USER=change-me
      - GITEA__database__PASSWD=change-me
    restart: always
    networks:
      - gitea
    volumes:
      - /mnt/Storage/docker/gitea/data:/data
      - /mnt/Storage/docker/gitea/https:/https
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "222:22"
    depends_on:
      - db

  db:
    image: mysql:8
    container_name: gitea-db
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=change-me
      - MYSQL_USER=change-me
      - MYSQL_PASSWORD=change-me
      - MYSQL_DATABASE=change-me
    networks:
      - gitea
    volumes:
      - /mnt/Storage/docker/gitea/mysql:/var/lib/mysql

The final step involves configuring a reverse proxy for SSL handling. I prefer Caddy, but other options work as well.

Unreal Engine 5 Setup for Git LFS

UE’s default Git plugin lacks compatibility with Git LFS or file locking, necessitating a custom plugin installation and we have two options: SRombauts GitPlugin v2 and ProjectBorealis GitPlugin v3.

Review the READMEs for both plugins and select the one that suits you. While ProjectBorealis’ plugin offers more features, I found v2 to be more stable. Whichever you choose, download the latest release for your Engine version, then extract it into your project’s Plugins folder (create it if it doesn’t exist). After restarting the editor, enable the plugin via the Plugins menu.

With that done, simply click “Connect to Source Control” in the lower-right corner of the editor and select “Git LFS 2”.

.gitignore and .gitattributes

The last step involves setting up your .gitignore and .gitattributes files. The real magic happens in the .gitattributes file, where you specify which files to track with LFS and which ones to lock.
I have a template I use for all my projects. When I start a new project, I copy it to the project’s folder and customize it.

Show my .gitattributes
[attr]lock filter=lfs diff=lfs merge=binary -text lockable
[attr]lockonly lockable
[attr]lfs filter=lfs diff=lfs merge=binary -text
[attr]lfstext filter=lfs diff=lfstext merge=lfstext -text
# Unreal Engine file types.
*.uasset lock
*.umap lock
*.locres lfs
*.locmeta lfs
# Steam Audio files
*.phononscene lfs
*.probebox lfs
*.probebatch lfs
*.bakedsources lfs
# Binaries
*.exe lfs
*.dll lfs
*.rcc lfs
# FMOD
*.bank lfs
*.wav lfs
*.mp3 lfs
*.ogg lfs
*.flac lfs
# Icons
*.png lfs
*.ico lfs
*.icns lfs
# Movies
*.bk2 lfs

Show my .gitignore
# Ignore built binaries and temporary build files
Intermediate/
Binaries/
Saved/
DerivedDataCache/
Platforms/
Build/

# Plugins
# Uncomment the line below to ignore the plugin directory (binaries) - alternate the comments below
Plugins/ 
#Plugins/*/Binaries/*  
#Plugins/*/Intermediate/* 

# Ignore root Visual Studio solution files. 
# We do check in some sln files in subdirectories, so only ignore the ones found in the root.
*.sln

# Ignore Rider settings
.idea/

# Ignore VS files 
.vs/
.vsconfig

# Ignore Xcode files
*.xcworkspace/

# Ignore P4config file
.p4config


# Mac files
__MACOSX/
*.DS_Store

# Perforce
.p4ignore
.p4config
.p4config.example

Conclusion

From my experience with both Perforce and Git LFS, unless I really want to share the code externally in a public repo I tend to stick with Perforce for Unreal.

If you’re thinking of hosting either Perforce or Gitea, I’d recommend a VPS equipped with at least 2GB of RAM. Factor in storage and backup expenses too.
My Hetzner configuration, which serves multiple purposes and has around 200GB of storage, sets me back by approximately £9/month.
Both Perforce and GitHub hosted plans generally have a higher cost per gigabyte.

Footnotes

  1. In Perforce, a workspace is a local copy of a project.