Back to Journal

Version Control for Game Projects

A game repository is half code and half heavy, unmergeable art, and keeping a Unity project safe under version control depends on a few precise settings that most beginners discover only after losing work.

A small team has its own particular silence, the one that falls when someone realizes the scene is broken and nobody knows why. An hour ago it worked. Now half the objects have lost their materials, a script reference shows Missing in the Inspector, and the person who made the last change swears they touched only a single prefab. Without a reliable history, the only recourse is guesswork and a backup folder of uncertain age, and guesswork is a poor foundation for anything that must ship.

Version control is the discipline that ends this kind of evening, and in ordinary software it is so universal that its absence would seem eccentric. Game projects, however, strain the usual tools in ways that a web application never does. They are full of large binary files that cannot be merged, generated folders that must never be committed, and engine specific files whose purpose is not obvious until one goes missing and takes a hundred references with it.

This article describes how to set up and maintain a Unity project under version control so that its history can be trusted: how Git and Git LFS divide the work between code and art, which folders to leave out, which editor settings make scenes legible to a merge tool, why the humble .meta file deserves respect, and when a studio outgrows Git in favor of tools built specifically for large binary assets.

Code and clay

A game repository holds two very different kinds of material. One is text: C# scripts, shaders, configuration, and, with the right settings, scenes and prefabs. Text can be compared line by line, and when two people change different parts of the same file, a tool can usually combine their work automatically. The other is binary: textures, audio, models, video, the layered source files from painting programs. These are opaque to diff tools and essentially impossible to merge.

The distinction shapes every decision that follows. For text, the ideal is many small commits, frequent merging and branches that live for days rather than months. For binary assets, the ideal is that only one person changes a given file at a time, because if two artists edit the same texture independently, one of them will lose their work entirely. A good workflow honors both needs at once, which is precisely what makes game version control harder than it first appears.

Size compounds the problem. Git stores every version of every file in the full history of the repository, and every clone downloads that entire history. A team that commits a two hundred megabyte texture source and revises it ten times has quietly added two gigabytes to every future clone, even if the final file is deleted. The repository grows heavier with every change to art, and soon cloning it becomes a lunch break rather than a moment.

None of this means Git is the wrong choice for a game. For solo developers and small teams it is usually the right one, being free, well documented and supported by every hosting service. It simply means Git must be configured with care, and supplemented with an extension that handles large binaries in a way Git itself was never designed to.

Git and its large companion

That extension is Git LFS, short for Large File Storage. With LFS installed, files matching certain patterns are not stored in the Git history at all. Instead, Git records a tiny text pointer containing a hash and a size, while the actual content is uploaded to a separate LFS store. When you check out a commit, LFS downloads only the versions of large files that commit needs, rather than every revision that ever existed.

Which files go to LFS is declared in a .gitattributes file at the repository root, with lines that track extensions such as psd, png, tga, fbx, wav, mp4 and so on. The command git lfs track writes these lines for you. The essential rule is to set this up before the first binary asset is committed, because files already in ordinary Git history stay there; moving them later requires rewriting history with git lfs migrate, which every collaborator must then accommodate.

Git LFS also supports file locking, which partly restores the exclusive editing that binary assets need. An artist can lock a file with git lfs lock before working on it, and the server will refuse pushes that modify a locked file from anyone else. Hosting services differ in how well they support this, and it depends on discipline, since nothing stops someone from editing an unlocked file locally. For a small team it is nonetheless a valuable safety net.

What to leave out

A Unity project folder contains a great deal that must never enter version control. The Library folder is the largest offender: it holds the imported, platform specific versions of every asset, caches and compiled data, all of which Unity can regenerate from the source assets. Committing it bloats the repository enormously and causes endless conflicts, because it changes whenever anyone opens the project. Temp, Obj, Logs and the per user UserSettings folder belong in the same category.

These exclusions live in a .gitignore file at the project root. The standard Unity template, maintained in GitHub's public collection of gitignore files, covers Library, Temp, Obj, Build and Builds, Logs and UserSettings, along with files generated by code editors such as solution and project files. Starting from that template rather than writing one by hand avoids most early mistakes. It is worth confirming that its patterns match your repository layout, especially if the Unity project lives in a subfolder.

Equally important is what must be kept. The Assets folder, the Packages folder with its manifest.json and packages-lock.json, and the ProjectSettings folder together define the project. Losing ProjectSettings means losing tags, layers, input configuration, quality levels and physics settings; losing the package manifest means a fresh clone resolves different package versions than the ones the team developed against. These three folders, and nothing generated from them, form the true source of the project.

The same caution applies to large outputs that are not engine generated. Built players, recorded gameplay video and exported trailers do not belong in the repository at all, even under LFS, since they are products of the work rather than its source. A shared drive or a release page on the hosting service is a better home, keeping the repository focused on what is needed to rebuild everything else.

Text, metadata and identity

Two editor settings, found under Project Settings, Editor, decide whether Unity's own files can be meaningfully versioned. The first is Asset Serialization Mode, which should be set to Force Text. This makes Unity write scenes, prefabs, materials and ScriptableObject assets as human readable YAML rather than a compact binary format, so that diffs show which properties changed and merge tools have a chance of combining edits. Force Text is the default in modern versions, but older projects should be checked.

The second is Version Control Mode, which should be set to Visible Meta Files. Unity creates a .meta file beside every asset and folder in the Assets directory, and those files must be visible on disk so that version control can track them. Again, modern versions default to this. In very old versions of Unity the meta files were hidden, and teams who unknowingly failed to commit them suffered mysteriously broken projects for reasons no one could see.

The reason the .meta file matters so much is the GUID it contains. Unity does not reference assets by path; a material refers to its texture, and a scene refers to a prefab, by that identifier. If a teammate's clone lacks a .meta file, Unity generates a new one with a different GUID, and every reference to that asset silently breaks. The rule is therefore absolute: always commit each .meta file together with its asset, and move or rename assets inside the editor, never in the file explorer.

When scenes collide

Even with text serialization, merging scenes and prefabs remains treacherous. Unity's YAML files are long, full of numeric file identifiers, and ordered in ways that do not correspond to how a person thinks about a scene. When two developers modify the same scene, a line based merge may produce a file that is syntactically valid YAML but semantically broken: duplicated objects, references to components that no longer exist, or a hierarchy that loads with orphaned children.

Unity ships a dedicated tool for this called UnityYAMLMerge, also known as Smart Merge, located in the Tools folder of the editor installation. Unlike a generic text merge, it parses the scene structure, understands objects and their properties, and resolves many conflicts that would defeat an ordinary tool. Configuring Git to use it involves adding a merge tool entry in your Git configuration pointing at the executable, and optionally a fallback tool for conflicts it cannot settle alone.

The better defense, though, is structural. Large scenes edited by many people are conflict generators, so teams split them: environment, lighting and gameplay objects in separate scenes loaded additively, and almost everything reusable turned into prefabs, each a separate file. Two people then rarely touch the same file. Combined with simple communication about who is working on which scene, this avoids most merges altogether, which is a far happier outcome than resolving them skillfully.

When a conflict does arrive and resists the tools, the humblest strategy is often the safest: keep one version whole, discard the other, and redo the smaller change by hand. Choosing ours or theirs for a scene file loses a little work but guarantees a coherent result. Hand editing YAML to combine two versions is possible for those who understand the format, yet every hour spent doing so is an argument for organizing the project to prevent it.

Beyond Git

Larger studios frequently leave Git behind for tools designed around big binary repositories and artist workflows. Perforce, whose server was sold for many years under the name Helix Core, has long been the standard in much of the industry. It keeps the authoritative history on a central server, lets each person sync only the portions of the depot they need, and makes exclusive checkout a natural part of editing binary files, so two artists cannot unknowingly work on the same asset.

Unity's own offering grew out of Plastic SCM, which Unity acquired with its developer Codice Software in 2020 and later rebranded as Unity Version Control. It supports both centralized and distributed workflows, handles large binaries without an extension, offers file locking, and provides a simplified interface intended for artists and designers who would rather not learn Git. It integrates with the editor and with Unity's cloud services, which makes it attractive to teams already invested there.

For a solo developer or a small team, Git with LFS, a careful .gitignore and the right editor settings is usually sufficient, and the habits matter more than the tool. Commit often, write messages that explain why a change was made, never leave a .meta file behind, and push regularly to a remote so that a failed drive is an inconvenience rather than a catastrophe. The broken scene at midnight then becomes a matter of reverting one commit, rather than mourning a week.