Sponsored By

Featured Blog | This community-written post highlights the best of what the game industry has to offer. Read more like it on the Game Developer Blogs.

Over 69% of the software industry is using Git, and recent improvements to handling of large assets (through Git LFS) have made it more practical to adopt for game dev. This article will teach you how to track and build Unity projects with Git & Git LFS.

Tim Pettersen, Blogger

December 6, 2016

10 Min Read

Git is a free and open source version control system that makes it easy to track changes to your Unity projects and collaborate with developers, artists, designers, and other contributors on the same code base. It is used by over 69% of the software industry, and recent improvements to Git's handling of large assets is causing a growing number of gaming studios to migrate away from Perforce and Subversion to Git. This article will teach you how to version Unity projects with Git, track large assets such as textures and audio efficiently with Git LFS, and host and build your code with a free Bitbucket and Unity Cloud Build account.

Unity .gitignore

Unity – and scripting IDEs like MonoDevelop, Consulo, Rider, and Visual Studio – create temporary files, build assets, log files, and other generated project files that are not suitable for checking into version control. Tracking these files with Git bloats the size of your repository, and may cause horrific conflicts when multiple developers are working on the same project. You can teach Git to ignore these files by checking a special .gitignore file into your project directory:


# .gitignore

# Unity
/[Ll]ibrary/
/[Tt]emp/
/[Oo]bj/
/[Bb]uild/
/[Bb]uilds/
/Assets/AssetStoreTools*
sysinfo.txt
*.pidb.meta
 
# VS/Rider/MD/Consulo
ExportedObj/
.consulo/
.idea/
*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
*.svd
 
# Builds
*.apk
*.unitypackage
*.app
*.exe

Based on Unity .gitignore rules generated from gitignore.io, with a few additions.

This .gitignore file should live in your Unity game directory (the immediate parent of your Assets and ProjectSettings subdirectories), even if the Unity game directory isn't the top level directory of your Git repository.

It's also worth adding .gitignore rules for special files maintained by your operating system, if you haven't already.

Unity version control settings for Git

Unity has a couple of settings, Version Control Mode and Asset Serialization Mode, that force scenes, prefabs, and meta files to be written to disk in a more Git-friendly format:

Unity version control settings for Git

Navigate to Edit > Project Settings > Editor and set:

  • Version Control > Mode to Visible Meta Files

This causes Unity to write asset .meta files as normal, non-hidden files in your Assets directory. 

  • Asset Serialization > Mode to Force Text

This causes Unity to serialize Unity-generated files as YAML (text) rather than a binary format. Text-based file formats give Git a fighting chance to automatically merge changes made by multiple developers, as multiple changes to a binary file can never be automatically merged and will always conflict. 

Once you've enabled these settings you should save your project, and commit the changes to your repository. Your settings will be saved in ProjectSettings/EditorBuildSettings.asset and distributed as part of your repository, so other developers on your team won't need to make the same changes.

Handling large Unity assets in Git

Git is a distributed version control system, which means the entire history of your repository (that is, every version of every asset) is copied from the server during the clone process. This is problematic if you want to version large game assets such as spritesheets, high-resolution textures, complex models, or audio and video files alongside your source code. One 10MB spritesheet may not seem like a big deal when you first commit it, but every time you modify it another 10MB is added to your repository, which means an additional 10MB for everyone to download.

Git LFS is an extension that breaks the "distributed" nature of Git by only downloading certain large files when you actually want to work with them. By default, Git LFS only downloads the latest version of each large asset from the server when you initially clone a repository. When you switch branches, pull new changes, or checkout an earlier commit, any required assets are fetched automatically from the server.

Once you've installed Git LFS, you typically use the git lfs track command to teach Git which file patterns to track with LFS. This adds new LFS filter bindings definitions to your .gitattributes file, which should be committed and distributed as part of your repository. You can add your own definitions - a good rule of thumb is that any binary file over ~500KB should be tracked with LFS - or you can create a file named .gitattributes in the root of your repository and copy this pre-made set of definitions into it:


# .gitattributes

# Images
*.jpg filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.psd filter=lfs diff=lfs merge=lfs -text
*.ai filter=lfs diff=lfs merge=lfs -text
*.tif filter=lfs diff=lfs merge=lfs -text
*.cubemap filter=lfs diff=lfs merge=lfs -text

# Audio
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text

# Video
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.mov filter=lfs diff=lfs merge=lfs -text

# 3D Objects
*.FBX filter=lfs diff=lfs merge=lfs -text
*.fbx filter=lfs diff=lfs merge=lfs -text
*.blend filter=lfs diff=lfs merge=lfs -text
*.obj filter=lfs diff=lfs merge=lfs -text

# Other
*.a filter=lfs diff=lfs merge=lfs -text
*.exr filter=lfs diff=lfs merge=lfs -text
*.tga filter=lfs diff=lfs merge=lfs -text
*.pdf filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.dll filter=lfs diff=lfs merge=lfs -text
*.aif filter=lfs diff=lfs merge=lfs -text
*.ttf filter=lfs diff=lfs merge=lfs -text
*.rns filter=lfs diff=lfs merge=lfs -text
*.reason filter=lfs diff=lfs merge=lfs -text
*.lxo filter=lfs diff=lfs merge=lfs -text

Based on Unity .gitattributes rules defined by nemotoo, with a few additions.

Note that you can also define patterns based on wildcards or directory names – e.g. git lfs track "Assets/Textures" – but this isn't a great idea as it will cause your text-based .meta files to be tracked with Git LFS as well. 

Merging game assets with Unity SmartMerge

Once you've set your Asset Serialization mode to Force Text, Git may be able to automatically merge some changes made by different developers to .scene and .prefab files. But don't despair if you're still seeing merge conflicts: Unity ships with a Git-compatible merge tool named SmartMerge

To enable SmartMerge, you'll need to add the following snippet to your Git config. For just one repository, add it to the .git/config file in the top-level directory of your repository. To enable SmartMerge for all repositories on your system, you'll need to add it to your global Git configuration. Locations vary, it's usually ~/.gitconfig on MacOS and Linux, but you can simply run git config --global -e to open it in your system editor.  


[mergetool "unity_yaml"]
cmd = '/Applications/Unity/Unity.app/Contents/Tools/UnityYAMLMerge' merge -p "$BASE" "$REMOTE" "$LOCAL" "$MERGED"
trustExitCode = false
keepTemporaries = true
keepBackup = false
 
[merge]
tool = unity_yaml

On Windows, replace '/Applications/Unity/Unity.app/Contents/Tools/UnityYAMLMerge' with:


'C:\Program Files\Unity\Editor\Data\Tools\UnityYAMLMerge.exe'

or wherever you've installed Unity.

A quick explanation of these properties:

  • cmd = ... is the path and arguments used to invoke UnityYAMLMerge (the binary that implements SmartMerge)

  • trustExitCode = false means Git doesn't use the exit code from UnityYAMLMerge to determine if a merge was successful

  • keepTemporaries = true means Git retains some temp files that are useful for debugging if UnityYAMLMerge fails with an error

  • keepBackup = false means Git discards the .scene.orig or .prefab.orig backups of the conflicted file after resolution. Since it's common to have multiple developers work on the same scene file, you're pretty likely to hit conflicts, leading to a lot of junky backups in your repository. It's better to discard them automatically. You can always recreate them manually if you need to.

Next time you encounter a conflict that Git can't automatically resolve, run:


$ git mergetool

And SmartMerge will (hopefully) resolve it for you. For example:


$ git merge feature1
Auto-merging Assets/Scenes/GameOver.unity
CONFLICT (content): Merge conflict in Assets/Scenes/GameOver.unity
Automatic merge failed; fix conflicts and then commit the result.
 
$ git mergetool
Merging:
Assets/Scenes/GameOver.unity
 
Normal merge conflict for 'Assets/Scenes/GameOver.unity':
  {local}: modified file
  {remote}: modified file
Conflicts:
Conflict handling:
Assets/Scenes/GameOver.unity seems unchanged.
Was the merge successful [y/n]? y

Note, Git sometimes thinks that a file is unchanged when in fact the conflicts have been resolved successfully. To test, try opening the conflicted file in Unity. If it really is unchanged and there are still conflict markers, it will fail to open. If it opens successfully, you can check the result of the merge in the Unity Inspector (it should have the new values from both sides of the merge) and then commit it as normal.

Migrating Unity projects from other version control systems

Regardless of your toolchain, you are surely not the first person to switch from your current version control system to Git: dedicated importers exist for almost every version control system imaginable. The advantage of using an importer is that you can retain your existing commit and branch history when switching to Git. Atlassian maintains guides on migrating from SVN to Git and migrating from Perforce to Git, and there are many other guides and tools for other version control systems that are just a quick Google away.

If you have large assets in your version control history, they will be converted to Git alongside your other project files and stored directly in your repository. As discussed in the section on handling large assets above, this may result in a huge repository that will take quite a bit of time to clone, push, and pull. To keep your repository lean, you should rewrite your history to use Git LFS to track these large assets. If you're migrating from Perforce, Git LFS support is actually built into the git p4 command. If you're migrating from SVN (or any other version control system), you'll typically want to migrate your history to Git LFS after the initial migration to Git using a tool like git-lfs-migrate

Hosting and building your Unity project

Git is distributed so you'll always have the complete history of your project locally, but you'll still need a centralized host to backup your repository and collaborate with others. Bitbucket offers unlimited private repositories, support for Git LFS (including a free storage tier), and is free for up to five users, making it ideal for many indie game studios. Though AAAs like Ubisoft and SEGA use Bitbucket as game development software too. 

You can sign up for Bitbucket for free, or for a complete walkthrough on setting up your first Unity project on Bitbucket, you can follow the official Unity3D tutorial on getting started with Bitbucket and SourceTree.

Another advantage of hosting your game on Bitbucket is that you can easily integrate Unity Cloud Build with your project. Unity Cloud Build is a free service that monitors your Bitbucket repository for changes, automatically creates game builds for your target platform(s) when new changes are pushed, and emails you a link to download your packaged game.

Unity Cloud Build platform selectionUnity Cloud Build supports all popular desktop and mobile gaming platforms.

This makes sharing new versions of games with non-technical users really easy, as they no longer have to pull and build the changes themselves. Also, if you're working on games for mobile devices, you can simply install them directly from Unity Cloud Build, rather than uploading APKs to your own cloud storage or messing around with Xcode. 

To get going with Unity Cloud Build and Bitbucket, follow the official Unity3d.com tutorial: Your first Cloud Build Project.

Or if you're feeling adventurous, dive straight in and enable Unity Cloud Build for your project – it's pretty straight forward. Just enter your Bitbucket repository URL (e.g. https://bitbucket.org/tpettersen/spaceshooter) as the Server URL when setting up your first build target, and copy and paste the generated Unity Cloud Build SSH key to your Bitbucket account when prompted. If you're planning on using Git LFS you'll need to add the generated key as a general purpose SSH key, as deployment keys do not yet support Git LFS. Otherwise, for vanilla Git, you can add it as a deployment key specific to your game repository. 

I hope you've found this guide useful for getting started with Git and Unity! If you have any questions or tips on Git and Unity, drop me a line on Twitter - I'm @kannonboy.

Thanks very much to Git LFS contributor Steve Streeting and Unity Evangelist John Sietsma for their help preparing this article!

Read more about:

Featured Blogs
Daily news, dev blogs, and stories from Game Developer straight to your inbox

You May Also Like