Table of Contents:
Introduction
Great architecture in Unity is entirely about drawing the right boundaries. Whether you are building a view-first UI framework or completely decoupling your core logic from your visual representation, the success of those systems is dictated by your initial project setup and that poor scaffolding will fight you evry step of the way. In this article we are going to look at how to properly setup and structure a brand new Unity project.
Scaffolding
In this article, we are going to setup some basic scaffolding that most Unity projects will need (one way or another). Scaffolding is about setting up the foundational environment of a project. The goal is to handle all the repetitive, baseline setup so you can immediately start writing actual feature code.
After creating a new Unity project, there's a few things that Unity devs do but we are going to focus on the shared setup process (we're skipping things that are only local like editor layouts, IDE setup, etc).
Git
Next we'll create our .gitignore and a .gitattributes files.
For the .gitignore we can use the following file which should be placed at the root of your project folder:
##################################################
# Unity
##################################################
# Unity-generated folders
.utmp/
/[Ll]ibrary/
/[Ll]ogs/
/[Tt]emp/
/[Oo]bj/
/[Bb]uild/
/[Bb]uilds/
/[Uu]ser[Ss]ettings/
# Unity 6 : ignore recovery folder created upon restarting after a crash.
/[Aa]Assets/_Recovery/
/[Aa]Assets/_Recovery.meta
# ignore the asset store tools plugin
/[Aa]ssets/AssetStoreTools*
# Explicitly track Unity's package manifest and packages-lock json files
!**/[Pp]ackages/manifest.json
!**/[Pp]ackages/packages-lock.json
# Autogenerated VS/MD/Consulo solution and project files
ExportedObj/
.consulo/
*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
*.svd
*.pdb
*.mdb
*.opendb
*.VC.db
# Unity3D generated meta files
*.pidb.meta
*.pdb.meta
# MemoryCaptures can get excessive in size.
# They also could contain extremely sensitive data
/[Mm]emoryCaptures/
# Recordings can get excessive in size
/[Rr]ecordings/
# Gradle cache directory
.gradle/
# Unity3D generated file on crash reports
sysinfo.txt
# Builds artifacts
*.apk
*.aab
*.unitypackage
*.app
# Crashlytics generated file
crashlytics-build.properties
# Packed Addressables
/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
# Temporary auto-generated Android Assets
/[Aa]ssets/[Ss]treamingAssets/aa.meta
/[Aa]ssets/[Ss]treamingAssets/aa/*
##################################################
# IDEs
##################################################
# Rider
##################################################
/[Aa]ssets/Plugins/Editor/JetBrains*
.idea/
# VS Code
##################################################
.vscode/
# Visual Studio
##################################################
.vs/
##################################################
# Custom
##################################################
# ignore any folder named 'gitignore' and its contents
gitignore/
gitignore.meta
# End of Custom ##################################
Note the Custom section at the end. Here is where you can add custom ignore patterns. I added a pattern to ignore any folder called gitignore in the project as a nice to have. This is so that devs can experiment with things locally without worrying about needing to discard/stash those changes so that they don't show up in another PR.
Next we'll create the .gitattributes file which is used to define how Git should track, format, and merge specific file types. But what's more important is its relation to Git LFS. Unity projects inherently contain massive binary assets like textures (.png, .psd), 3D models (.fbx, .obj), and audio clips (.wav, .mp3). Normal Git cannot handle these well and they will bloat your repository quickly. The .gitattributes file acts as the explicit instruction manual for Git LFS. It intercepts designated file extensions and flags those files to be uploaded to an external storage server (take note of all the lines ending in lfs), replacing them in your standard repo with a tiny text pointer instead. Make sure that you enable Git LFS for your project. Anyways, here's the .gitattributes file which should be placed at the root of your project folder:
# Define macros (only works in top-level gitattributes files)
[attr]lfs filter=lfs diff=lfs merge=lfs -text
[attr]unity-json eol=lf linguist-language=json
[attr]unity-yaml merge=unityyamlmerge eol=lf linguist-language=yaml
# Ensure that text files that any contributor introduces to the repository have their line endings normalized
* text=auto
# Unity source files
*.cginc text
*.compute text linguist-language=hlsl
*.cs text diff=csharp
*.hlsl text linguist-language=hlsl
*.raytrace text linguist-language=hlsl
*.shader text
# Unity JSON files
*.asmdef unity-json
*.asmref unity-json
*.index unity-json
*.inputactions unity-json
*.shadergraph unity-json
*.shadersubgraph unity-json
# Unity UI Toolkit files
*.tss text diff=css linguist-language=css
*.uss text diff=css linguist-language=css
*.uxml text linguist-language=xml linguist-detectable
# Unity YAML files
*.anim unity-yaml
*.asset unity-yaml
*.brush unity-yaml
*.controller unity-yaml
*.flare unity-yaml
*.fontsettings unity-yaml
*.giparams unity-yaml
*.guiskin unity-yaml
*.lighting unity-yaml
*.mask unity-yaml
*.mat unity-yaml
*.meta unity-yaml
*.mixer unity-yaml
*.overrideController unity-yaml
*.playable unity-yaml
*.prefab unity-yaml
*.preset unity-yaml
*.renderTexture unity-yaml
*.scenetemplate unity-yaml
*.shadervariants unity-yaml
*.signal unity-yaml
*.spriteatlas unity-yaml
*.spriteatlasv2 unity-yaml
*.terrainlayer unity-yaml
*.unity unity-yaml
*.physicMaterial unity-yaml
*.physicsMaterial2D unity-yaml
# Unity LFS
*.cubemap lfs
*.unitypackage lfs
## 3D models
*.3dm lfs
*.3ds lfs
*.blend lfs
*.blend1 lfs
*.c4d lfs
*.collada lfs
*.dae lfs
*.dxf lfs
*.FBX lfs
*.fbx lfs
*.jas lfs
*.lws lfs
*.lxo lfs
*.ma lfs
*.max lfs
*.mb lfs
*.obj lfs
*.ply lfs
*.skp lfs
*.stl lfs
*.ztl lfs
## Audio
*.aif lfs
*.aiff lfs
*.it lfs
*.mod lfs
*.mp3 lfs
*.ogg lfs
*.s3m lfs
*.wav lfs
*.xm lfs
## Video
*.asf lfs
*.avi lfs
*.flv lfs
*.mov lfs
*.mp4 lfs
*.mpeg lfs
*.mpg lfs
*.ogv lfs
*.wmv lfs
## Images
*.bmp lfs
*.exr lfs
*.gif lfs
*.hdr lfs
*.iff lfs
*.jpeg lfs
*.jpg lfs
*.pict lfs
*.png lfs
*.psd lfs
*.tga lfs
*.tif lfs
*.tiff lfs
*.webp lfs
## Compressed Archive
*.7z lfs
*.bz2 lfs
*.gz lfs
*.rar lfs
*.tar lfs
*.zip lfs
## Compiled Dynamic Library
*.dll lfs
*.pdb lfs
*.so lfs
## Fonts
*.otf lfs
*.ttf lfs
## Executable/Installer
*.apk lfs
*.exe lfs
## Documents
*.pdf lfs
## .a files (Static Libraries): These are archive files often used for compiled C++ static libraries, frequently used in Unity projects for iOS native plugins or specialized plug-in libraries.
*.a lfs
## Spine export file for Unity
*.skel.bytes lfs
Project Settings
You should configure any initial project settings in the Project Settings window which you can open by selecting Edit > Project Settings... in the top toolbar. While many of the settings here are project specific there are a few that you should adjust right away.
-
Playersettings:- You should update the
Company Name,Product Name, andVersionfields so that they are not set to the default. - You should also configure the
Splash Image. I usually just set it to this (see image below) and simply append to the list of logos when a logo for the game is ready:
- You should update the
-
Editorsettings:- You should set
Asset Serialization > ModetoForce Textto force YAML instead of binary for asset files. This makes it easier to work with Git and Unity YAML-Merge. - You should also set
Enter Play Mode Settings > When entering Play ModetoReload Scene Onlyto enable fast enter play mode. This will be the default setting in Unity 6.5+ and moving forward as they are deprecating and removing the option to Reload Domain when entering Play Mode. This makes it so that Unity does not destroy the entire C# code domain state and does not reload any assemblies which means that static data, variables, events and memory are not reset when entering play mode in editor. The plus side is that this makes you write cleaner code and also makes entering play mode super-super quick!
- You should set
Project Folder Structure
Let's start with folder structure. This is the folder hierarchy that I use for my projects:
[PROJECT_ROOT]/
├── Assets/
│ ├── _Project/
│ │ ├── _Modules/
│ │ │ └── Default/
│ │ │ ├── _Runtime/
│ │ │ └── Editor/
│ │ ├── _Scenes/
│ │ ├── Art/
│ │ ├── Audio/
│ │ │ ├── Music/
│ │ │ └── Sounds/
│ │ ├── Resources/
│ │ ├── Sandbox/
│ │ ├── Settings/
│ │ └── StreamingAssets/
│ │ └── Videos/
│ ├── TextMesh Pro/
│ └── ...
├── Packages/
├── .gitignore
├── .gitattributes
├── README.md
└── ...
The key take-aways from this folder structure is the following:
- The Special Folders that Unity reserves that we have setup prematurely (Editor, Resources, StreamingAssets). Note that you can have multiple of some folders in your project and Unity will consider/group them all together when processing them.
- The
_prefix for some of the folder names is just so that Unity orders it first inside its parent folder in the project explorer which uses alphabetical sorting for folders. - The
_Projectfolder where we have already setup a neat standard moving forward for how we separate and organize our project's code modules (more on that later) from other assets like_Scenes(level design),Art,Settings, etc. - The
_Scenesfolder, where we keep the scene files as well as any files related to a given scene. Usually I create a folder with the same name as the scene and place stuff like baked navmesh and lighting files there (unity will do this automatically for you as well when you bake navmesh or lighting). - The
Artfolder. This folder is for artists only (no code). Many artists want to just focus on art and be able to have their own project structure for organization and this folder allows them to do that. Textures, images, 3D models, all go in here. - The
Audiofolder. Again, this is a folder where audio engineers can have their own file organization. Although I usually impose aMusicandSoundssubfolder so that my code can easily scan a single folder when generating assets that connect the audio to in-game systems (so I'll leave this up to you). - The
Sandboxfolder. This is where devs can roughly test things and place files that might not ever be used in production if a feature they are prototyping is rejected. Essentially, this is where someone can prototype a feature. I usually create subfolders with the names of each dev in this folder so each dev has there own working directory. - The
Settingsfolder. This folder is to group all the settings/configuration files for the project. Things likeInputSystem(.inputactions file),Localization(locales, tables, and settings),URP Settings(URP Render Pipeline assets, etc), - The
TextMesh ProandAssets/...folders. These folders sit outside the_Projectfolder and are mainly for external asset packages like the ones you'd download off the asset store. It may be tempting to group them under athird partyfolder but I've seen some assets that assume they are placed at the root of the assets folder and if not placed there don't function properly as a result. While this is a very small minority of asset plugins, it's best to side with caution here. Besides, since we prefixed the project folder with_these external assets will always end up sorted at the end of the project explorer window.
TextMesh Pro
In the editor toolbar menu at the top, select Window > TextMeshPro > Import TMP Essential Resources. This will import the TextMesh Pro essential resources. This will also trigger when you add your first TMP Text component in a UI. You might want to configure a default font at some point but I'll leave that up to you.
Scenes
There are at least 2-3 scenes that I create at the start of every project.
- The
Bootstrapscene: This scene is the entry point of the application in a build and should be set as index 0 in the build settings scene list. For more information about bootstrapping see my other article. If you prefer to use your own bootstrapping logic than you can skip creating this scene however I strongly advise reading the other article and setting up your bootstrapping logic using a scene. - The
Splashscene (optional): This scene is only needed if you want to have something more flashy as a splash screen other than the built in logo fades with a plain background (think animations or 3D scene or particle effects). If you implement this scene don't forget to add it as index 1 (after the bootstrap scene) in the build settings scene list. - The
Welcomescene: Every game has this, it's the first scene where the player has input agency and is usually reffered to as "the main menu". Here I usually just setup a UI view with the Game's title at the top and a basic vertical menu with some basic buttons which have some simple functionality. The 2 buttons I implement arePlay(brings you to another scene) andQuitwhich callsApplication.Quit(). This scene should be set as index 2 (after the splash scene) in the build settings scene list.
When creating scenes I also like to impose a structure to the scene hierarchy. To make it easier to keep the same initial hierarchy when creating scenes, I create a scene template via RightClick in Project Window > Create > Scene > Scene Template. This will create a scenetemplate asset that you can point to a scene to use as the template. So create a regular scene and place it adjacent to the scenetemplate asset and then simply drag the scene into the Template Scene field of the scenetemplate asset's inspector. Also make sure to check Pin in New Scene Dialog so that our template stands out.
My basic scene template is as follows:
Everything in [] is at position (0,0,0) and has zero rotation and a scale of (1,1,1). The [Context] object and everything under it are mostly for contextual things like navmesh, players spawned at runtime, level specific managers, etc. The [Environment] object is for the level designer and is meant to contain all the 3D models and gameobjects that make up the 3D world as well as any lights, post processing volumes, etc.
To use this Scene Template that we created, we just have to press Ctrl+N in the Editor and we should see our scene template in the New Scene Dialog list.
Code Modules
Every project can, at some point, have code that can be grouped into its own module and even if you don't have any initially it's good to set this structure up now. The _Modules folder is where all your codebase will live. Each code module should be separated within its own folder and have assembly definitions to enforce separation of concerns and to ensure a clean dependency between modules and assemblies. I will not go over assembly definitions in this article but I will explain the one or two that every Unity dev uses whether they know it or not which is the "Default" hidden Assembly-CSharp and Assembly-CSharp-Editor assemblies.
The default Assembly-CSharp assembly (which you may have already seen in your IDE) is the default assembly that any code under the Assets/ lives in. Unity sets this assembly up by default and automatically makes it depend on every single other assembly in the project. This assembly is also a "Runtime" assembly which is a term I use to mean that the code in this assembly is compiled and packaged in our built game. Basically any code we add under _Modules/Default/Runtime/ will be part of this default Assembly-CSharp assembly, which is why the folders are structured this way.
You may have noticed that there is also an Editor folder under the _Modules/Default/ folder. This is because if we write Editor code we do not want this code to be compiled and packaged with our built game, in fact Unity will throw errors when building if this is the case. By adding Editor specific scripts under this folder, Unity will automatically add them into a default Assembly-CSharp-Editor assembly (which you also may have seen in your IDE before). This entire assembly is automatically excluded from all builds except for editor which means that the code here is not packaged with your game builds and Unity will not throw any errors. So you should use this folder for any Editor specific code.
You can make other modules like a "Core" module which contains interfaces and common logic if you want but you'll have to create your own assembly definitions for that and that is a whole other topic. If you do create another module, you should follow the same folder structure where you create the module folder "Core" and under it create the "_Runtime" and "Editor" folders which will house their respective assembly definition files.
README
Finally, you should create a README.md with instructions on how to clone the project and install it on your machine.
Here's a good starting point:
# PROJECT_NAME
- [PROJECT_NAME](#project_name)
- [Installation](#installation)
- [Clone Project](#clone-project-using-git)
- [Configuration](#configuration)
- [Setup Custom Mergetool (optional)](#setup-custom-mergetool-optional)
- [Getting Started](#getting-started)
## Installation
### Clone Project Using Git
```
cd [CHOOSE_A_DIRECTORY_TO_STORE_THE_REPO]
git clone [INSERT_URL_TO_GIT_REPO_HERE]
```
Next, Continue to the `Git Configuration` section to configure git or just go straight to `Getting Started` section below.
## Configuration
### Setup Custom Mergetool (optional)
UnityYAMLMerge.exe is a merge tool that aids with the merging of scene, prefab and asset files (and any other YAML files). It comes packaged with each installation of the Unity Editor, therefore if you upgrade the project version you should also update your custom merge tool to use the newer UnityYAMLMerge.exe for your version of Unity.
You must tell Git to use UnityYAMLMerge.exe as a custom merge tool and also as a custom diff tool. It is located in the [UnityEditor installation folder](https://docs.unity3d.com/2021.3/Documentation/Manual/SmartMerge.html) under `./Editor/Data/Tools/UnityYAMLMerge.exe`.
On Windows this is located at `C:/Program Files/Unity/Hub/Editor/{EDITOR_VERSION}/Editor/Data/Tools/UnityYAMLMerge.exe`.
Once you have setup Git to use UnityYAMLMerge, you must then tell UnityYAMLMerge what diff tool to use as a fallback. To do this, you simply need to edit `mergespecfile.txt` located adjacent (within the same folder) to the UnityYAMLMerge.exe file.
Follow the instructions in this txt file to setup a fallback merge/diff tool.
For more info on UnityYAML merge (Smart Merge) and setup with git or git GUIs see : https://docs.unity3d.com/2021.3/Documentation/Manual/SmartMerge.html
## Getting Started
Notice how I included a paragraph in the README about Unity YAML-Merge? That's because this is not something that you can do for the whole project. Each developer should be setting this up locally for every project that they are collaborating on. If you are working on a project and are arguing 90% of the time about who is currently modifying a scene or prefab file then you are using Unity wrong in my opinion. Unity YAML-Merge allows you to merge scene, prefab, and asset files without breaking or corrupting anything. There are some edge cases but from my experience they are far and in between and are generally not an issue in practical use when work is separated cleanly.
Push the Initial Commit
With all of the above done, you can stage all your changes and push the initial commit to your main branch.
Top comments (0)