<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alon Farchy</title>
    <description>The latest articles on DEV Community by Alon Farchy (@virtualmaker).</description>
    <link>https://dev.to/virtualmaker</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2583174%2Fcc44555d-ab79-4524-9051-200c44011760.png</url>
      <title>DEV Community: Alon Farchy</title>
      <link>https://dev.to/virtualmaker</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/virtualmaker"/>
    <language>en</language>
    <item>
      <title>Git and Unity: A Comprehensive Guide to Version Control for Game Devs</title>
      <dc:creator>Alon Farchy</dc:creator>
      <pubDate>Fri, 15 May 2026 22:52:05 +0000</pubDate>
      <link>https://dev.to/virtualmaker/git-and-unity-a-comprehensive-guide-to-version-control-for-game-devs-2n2a</link>
      <guid>https://dev.to/virtualmaker/git-and-unity-a-comprehensive-guide-to-version-control-for-game-devs-2n2a</guid>
      <description>&lt;p&gt;I have been using Git for version control for over 15 years. It's been my constant companion on tiny personal projects, 20-person Unity projects, and even the massive Windows operating system. Git has become the de facto standard for the software industry, and it's growing in popularity for Unity game development.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6b1xxagdu9ocz1y5fu39.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6b1xxagdu9ocz1y5fu39.png" alt="Git For Unity Developers" width="489" height="750"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article was originally posted to &lt;a href="https://www.virtualmaker.dev&amp;amp;utm_source=devto" rel="noopener noreferrer"&gt;virtualmaker.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Git is actually easy to learn if you start from the fundamentals. In this guide, we'll begin with the basics of setting up &lt;strong&gt;Git for Unity&lt;/strong&gt; to track versions of your project. Then, we'll move on to more advanced topics like backing up your project to GitHub, collaborating with a team, and sharing code and assets between multiple projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Summary
&lt;/h2&gt;

&lt;p&gt;If you're short on time, here’s the high-level flow for Unity version control:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Initialize a Git repository in your Unity project.&lt;/li&gt;
&lt;li&gt;Add a Unity-specific &lt;code&gt;.gitignore&lt;/code&gt; so you don’t commit generated files.&lt;/li&gt;
&lt;li&gt;Use Git LFS for large assets (textures, audio, video, models).&lt;/li&gt;
&lt;li&gt;Commit early and push to GitHub for backup and collaboration.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you start, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Unity project that builds locally.&lt;/li&gt;
&lt;li&gt;Git installed (CLI tools or a Git client).&lt;/li&gt;
&lt;li&gt;A GitHub account if you want remote backups or collaboration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Quick Summary&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Why You Need Version Control for Unity&lt;/li&gt;
&lt;li&gt;What is Git?&lt;/li&gt;
&lt;li&gt;What is a .gitignore?&lt;/li&gt;
&lt;li&gt;How to Initialize a Git Repository for Your Unity Project&lt;/li&gt;
&lt;li&gt;Using Git LFS with Unity To Track Large Assets&lt;/li&gt;
&lt;li&gt;Making Your First Commit&lt;/li&gt;
&lt;li&gt;Useful Git Commands for Unity Developers&lt;/li&gt;
&lt;li&gt;Commit Early, Commit Often&lt;/li&gt;
&lt;li&gt;Branching and Merging&lt;/li&gt;
&lt;li&gt;Asset Conflicts and Merging&lt;/li&gt;
&lt;li&gt;Git Clients: UI for Git!&lt;/li&gt;
&lt;li&gt;Git on the Web: GitHub for Unity Developers&lt;/li&gt;
&lt;li&gt;Pushing your Unity Project to GitHub&lt;/li&gt;
&lt;li&gt;Working with a Team: GitHub Organizations&lt;/li&gt;
&lt;li&gt;Creating Your First Pull Request on GitHub&lt;/li&gt;
&lt;li&gt;Sharing Code and Assets Between Projects: Submodules vs Unity Packages&lt;/li&gt;
&lt;li&gt;Git on a Team: Branching Strategies for Unity&lt;/li&gt;
&lt;li&gt;Automated Unity Builds and CI/CD with GitHub Actions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why You Need Version Control for Unity
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Timmy the Clever Game Developer
&lt;/h3&gt;

&lt;p&gt;Meet Timmy, a talented and passionate game developer. Like many before him, Timmy started his career by working on small projects, handling everything from code to art. Timmy had a clever way of tracking changes to his project. Whenever he made significant changes, he would create a new version of the file, appending a systematic version number to end of the filename. It worked great – GameScene_v0.0.1.unity, GameScene_v0.0.2.unity, and so on.&lt;/p&gt;

&lt;p&gt;But as his projects grew in complexity, so did his file naming conventions. Soon, his project folder was littered with files GameScene_v3.6.7_FINAL.unity, GameScene_v3.6.7_FINAL_b.unity, and GameScene_v3.6.7_REALLYFINAL.unity. Finding the right file became a game in itself. Timmy's team was not too happy either, unable to understand which version of Timmy's files they should use.&lt;/p&gt;

&lt;p&gt;There must be a better way, Timmy thought.&lt;/p&gt;

&lt;h3&gt;
  
  
  Timmy Discovers Source Control
&lt;/h3&gt;

&lt;p&gt;One day, Timmy decided enough was enough. He read an interesting blog post which introduced him to the world of version control systems (VCS). Intrigued, Timmy started learning about Git, a popular and powerful VCS.&lt;/p&gt;

&lt;p&gt;The more Timmy learned about Git, the more he realized how it could revolutionize his workflow. Instead of manually renaming files and risking data loss, Git could track every change automatically. He could create branches for new features, merge them seamlessly, and revert to previous versions effortlessly. Collaboration would become straightforward, with each team member working on their own branch without conflict.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Git?
&lt;/h2&gt;

&lt;p&gt;Git is a distributed version control system (VCS) that allows developers to track changes in their code, collaborate with others, and manage their projects efficiently.&lt;/p&gt;

&lt;p&gt;Git was initially created by Linus Torvalds, the creator of the Linux operating system, in 2005. He developed Git to manage the Linux kernel development, and it has since become one of the most widely used version control systems in the world.&lt;/p&gt;

&lt;p&gt;Git has been used for solo Unity projects and for AAA games. At Virtual Maker, we use Git for all of our projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Concepts
&lt;/h3&gt;

&lt;p&gt;Learning all the concepts in Git can take a bit of time. We'll start with the core concepts that are most important to understanding what you're doing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbfmdxkwnxk22o1f0jcxz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbfmdxkwnxk22o1f0jcxz.png" alt="Git Repo Basics" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository / Repo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All of your project files and the entire history of their changes are kept in a repo. A repo can be stored locally on your computer or remotely on a service like GitHub or GitLab.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Commits are the units of your Git history. Each time you make changes, you bundle them into a commit. Git stores commits in a directed graph where each commit points to the previous commit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Branch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A branch is just a named location in your Git history. You can create branches to work on different bugs and features independently, then "merge" those branches back together when you're done. The default branch is often called &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Initialize a Git Repository for Your Unity Project
&lt;/h2&gt;

&lt;p&gt;To get started, you need to install Git from one of these links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/download/win" rel="noopener noreferrer"&gt;Install git for Windows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/download/mac" rel="noopener noreferrer"&gt;Install git for Mac OS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/download/linux" rel="noopener noreferrer"&gt;Install git for Linux&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this guide, we’ll illustrate how to use Git with the command line. This is the best way to understand how Git works. Once you've mastered the basics, you can use Git Clients like Fork and GitHub Desktop that offer a graphical interface (if you prefer).&lt;/p&gt;

&lt;p&gt;Let's get started.&lt;/p&gt;

&lt;p&gt;Choose a Unity project or create a new one. Then, open your terminal or command prompt and navigate to your Unity project directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;path/to/my/unity/project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize the repository by using the &lt;code&gt;git init&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git init
&lt;span class="go"&gt;Initialized empty Git repository in /Users/vm/repos/GitTestProject/.git/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command creates a new subdirectory named &lt;code&gt;.git&lt;/code&gt; that contains all of your necessary repository files.&lt;/p&gt;

&lt;p&gt;Now, use &lt;code&gt;git status&lt;/code&gt; to see what Git sees.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git status
&lt;span class="go"&gt;On branch main

No commits yet

Untracked files:
&lt;/span&gt;&lt;span class="gp"&gt;  (use "git add &amp;lt;file&amp;gt;&lt;/span&gt;...&lt;span class="s2"&gt;" to include in what will be committed)
&lt;/span&gt;&lt;span class="go"&gt;        Assets/
        GitTestProject.sln
        Library/
        Logs/
        Packages/
        ProjectSettings/
        Unity.Services.Core.Analytics.csproj
        Unity.Services.Core.Configuration.Editor.csproj
        Unity.Services.Core.Configuration.csproj
        Unity.Services.Core.Device.csproj
        Unity.Services.Core.Editor.csproj
        Unity.Services.Core.Environments.Internal.csproj
        Unity.Services.Core.Environments.csproj
        Unity.Services.Core.Internal.csproj
        Unity.Services.Core.Networking.csproj
        Unity.Services.Core.Registration.csproj
        Unity.Services.Core.Scheduler.csproj
        Unity.Services.Core.Telemetry.csproj
        Unity.Services.Core.Threading.csproj
        Unity.Services.Core.csproj
        UnityEditor.UI.csproj
        UnityEngine.UI.csproj
        UserSettings/

nothing added to commit but untracked files present (use "git add" to track)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git says there are no commits, so nothing is being tracked in version control. Git lists files that could be tracked but aren't as untracked files.&lt;/p&gt;

&lt;p&gt;Before we start tracking files, we need to ensure Git doesn't track anything we don't need.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a .gitignore?
&lt;/h2&gt;

&lt;p&gt;A .gitignore file specifies which files and directories git should ignore in a project. For Unity projects, this file is crucial because it helps avoid committing all the stuff that Unity will regenerate by itself, like the &lt;code&gt;Library&lt;/code&gt;, &lt;code&gt;Temp&lt;/code&gt;, and &lt;code&gt;Logs&lt;/code&gt; folders.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/github/gitignore/refs/heads/main/Unity.gitignore" rel="noopener noreferrer"&gt;Here is a template &lt;code&gt;.gitignore&lt;/code&gt; for Unity projects&lt;/a&gt;. Drop it in the root folder of your repository.&lt;/p&gt;

&lt;p&gt;Now let's check what &lt;code&gt;git status&lt;/code&gt; says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git status
&lt;span class="go"&gt;On branch main

No commits yet

Untracked files:
&lt;/span&gt;&lt;span class="gp"&gt;  (use "git add &amp;lt;file&amp;gt;&lt;/span&gt;...&lt;span class="s2"&gt;" to include in what will be committed)
&lt;/span&gt;&lt;span class="go"&gt;        .gitignore
        .vscode/
        Assets/
        Packages/
        ProjectSettings/

nothing added to commit but untracked files present (use "git add" to track)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that Unity generates &lt;code&gt;.meta&lt;/code&gt; files for every asset. &lt;strong&gt;Always commit your &lt;code&gt;.meta&lt;/code&gt; files&lt;/strong&gt; along with the assets themselves. If you lose them, Unity will regenerate new GUIDs and break references across scenes, prefabs, and materials.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Git LFS with Unity To Track Large Assets
&lt;/h2&gt;

&lt;p&gt;If your project doesn't have any large files, then skip this section and come back to it later. As a rule of thumb, we consider &lt;code&gt;10 MB&lt;/code&gt; a "large" file, but this is up to you.&lt;/p&gt;

&lt;p&gt;The problem with large files stems from how Git was designed. Each commit in repo contains all of the changes made since the previous commit. So, if you commit a &lt;code&gt;10 MB&lt;/code&gt; image, you add &lt;code&gt;10 MB&lt;/code&gt; to the size of your repository — forever. Even if you delete the file in the next commit, the file is still tracked in the history.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpl93ndp9mxslsgiho2k6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpl93ndp9mxslsgiho2k6.png" alt="Git with Large Files" width="800" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To make matters worse, each time you edit and commit a large binary file like an image or audio file, the entire file is added to your Git history. This will cause the repo size to balloon, taking much more disk space on each developer's machine than they would need without Git.&lt;/p&gt;

&lt;p&gt;Git LFS (Large File Storage) solves this problem by only storing a &lt;em&gt;pointer&lt;/em&gt; to the large file into the Git history. The large file itself is stored in a separate database so it doesn't take space in your repository, and only the necessary version is downloaded onto each developer's machine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9zrv2o9b33jg09kfqsca.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9zrv2o9b33jg09kfqsca.png" alt="Git with Large Files with LFS" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Git web hosts like GitHub will limit how much LFS data you can have and will charge you to increase this limit.&lt;/p&gt;

&lt;p&gt;To use Git LFS, each member of your team needs to install it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://git-lfs.com/" rel="noopener noreferrer"&gt;Install Git LFS&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once installed, Git LFS needs to be set up for each user account using &lt;code&gt;git lfs install&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git lfs &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="go"&gt;Updated Git hooks.
Git LFS initialized.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two ways to tell LFS which files to track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Track specific files.&lt;/li&gt;
&lt;li&gt;Pattern match with the wildcard &lt;code&gt;*&lt;/code&gt; syntax.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: You need to set up LFS tracking rules &lt;strong&gt;before&lt;/strong&gt; adding the large files to git. Otherwise, they won't be tracked by LFS correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tracking specific files with LFS
&lt;/h3&gt;

&lt;p&gt;If you're using a web host like GitHub, it's usually best to track specific large files to avoid unnecessary LFS limits and fees.&lt;br&gt;
Track a specific file with LFS using the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git lfs track path/to/MyLargeFile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will create or modify the &lt;code&gt;.gitattributes&lt;/code&gt; file. You can inspect and edit this file manually.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;path/to/MyLargeFile filter=lfs diff=lfs merge=lfs -text
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-text&lt;/code&gt; at the end tells Git to treat the file as binary and skip line-ending normalization. The file checked in to Git will be a simple text pointer to the real file tracked by LFS.&lt;br&gt;
The "filter", "diff", and "merge" options are how LFS hooks into Git's operations to download the real file onto your machine.&lt;/p&gt;
&lt;h3&gt;
  
  
  Pattern matching files with LFS
&lt;/h3&gt;

&lt;p&gt;Usually, large files are of a certain type. For example, maybe you have many videos of &lt;code&gt;.mov&lt;/code&gt; format. Track these files with LFS using the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git lfs track &lt;span class="s2"&gt;"*.mov"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are some other file types you may want to track. However, keep in mind that web hosts like GitHub will charge you based on how much LFS data you use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Image
&lt;span class="go"&gt;*.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

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Audio
&lt;span class="go"&gt;*.mp3 filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Video
&lt;span class="go"&gt;*.mp4 filter=lfs diff=lfs merge=lfs -text
*.mov filter=lfs diff=lfs merge=lfs -text

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;3D Object
&lt;span class="go"&gt;*.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

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ETC
&lt;span class="go"&gt;*.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
*.unitypackage 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
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that the large files are taken care of, let's get back to making your first commit!&lt;/p&gt;

&lt;h2&gt;
  
  
  Making your First Commit
&lt;/h2&gt;

&lt;p&gt;To make a commit takes two steps. First, we "stage" the changes using the &lt;code&gt;git add&lt;/code&gt; command. This command can be used over and over to select which files you want to add to the commit.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;git add .&lt;/code&gt; command is a shorthand to stage everything in the current directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's do a quick check to see that Git is tracking with the &lt;code&gt;git status&lt;/code&gt; command. Your list of files may be different depending on your project setup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git status
&lt;span class="go"&gt;On branch main

No commits yet

Changes to be committed:
&lt;/span&gt;&lt;span class="gp"&gt;  (use "git rm --cached &amp;lt;file&amp;gt;&lt;/span&gt;...&lt;span class="s2"&gt;" to unstage)
&lt;/span&gt;&lt;span class="go"&gt;        new file:   .gitignore
        new file:   Assets/Scenes.meta
        new file:   Assets/Scenes/SampleScene.unity
        new file:   Assets/Scenes/SampleScene.unity.meta
        new file:   Packages/manifest.json
        new file:   Packages/packages-lock.json
        new file:   ProjectSettings/AudioManager.asset
        new file:   ProjectSettings/ClusterInputManager.asset
        new file:   ProjectSettings/DynamicsManager.asset
        new file:   ProjectSettings/EditorBuildSettings.asset
        new file:   ProjectSettings/EditorSettings.asset
        new file:   ProjectSettings/GraphicsSettings.asset
        new file:   ProjectSettings/InputManager.asset
        new file:   ProjectSettings/MemorySettings.asset
        new file:   ProjectSettings/NavMeshAreas.asset
        new file:   ProjectSettings/PackageManagerSettings.asset
        new file:   ProjectSettings/Packages/com.unity.testtools.codecoverage/Settings.json
        new file:   ProjectSettings/Physics2DSettings.asset
        new file:   ProjectSettings/PresetManager.asset
        new file:   ProjectSettings/ProjectSettings.asset
        new file:   ProjectSettings/ProjectVersion.txt
        new file:   ProjectSettings/QualitySettings.asset
        new file:   ProjectSettings/TagManager.asset
        new file:   ProjectSettings/TimeManager.asset
        new file:   ProjectSettings/UnityConnectSettings.asset
        new file:   ProjectSettings/VFXManager.asset
        new file:   ProjectSettings/VersionControlSettings.asset
        new file:   ProjectSettings/XRSettings.asset
        new file:   ProjectSettings/boot.config
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can use the &lt;code&gt;git commit&lt;/code&gt; command to create a new commit with a meaningful message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Initial commit of my Unity project"&lt;/span&gt;
&lt;span class="go"&gt;
[main (root-commit) e1c03d9] Initial commit of my Unity project
 29 files changed, 2717 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 Assets/Scenes.meta
 create mode 100644 Assets/Scenes/SampleScene.unity
 create mode 100644 Assets/Scenes/SampleScene.unity.meta
 create mode 100644 Packages/manifest.json
 create mode 100644 Packages/packages-lock.json
 create mode 100644 ProjectSettings/AudioManager.asset
 create mode 100644 ProjectSettings/ClusterInputManager.asset
 create mode 100644 ProjectSettings/DynamicsManager.asset
 create mode 100644 ProjectSettings/EditorBuildSettings.asset
 create mode 100644 ProjectSettings/EditorSettings.asset
 create mode 100644 ProjectSettings/GraphicsSettings.asset
 create mode 100644 ProjectSettings/InputManager.asset
 create mode 100644 ProjectSettings/MemorySettings.asset
 create mode 100644 ProjectSettings/NavMeshAreas.asset
 create mode 100644 ProjectSettings/PackageManagerSettings.asset
 create mode 100644 ProjectSettings/Packages/com.unity.testtools.codecoverage/Settings.json
 create mode 100644 ProjectSettings/Physics2DSettings.asset
 create mode 100644 ProjectSettings/PresetManager.asset
 create mode 100644 ProjectSettings/ProjectSettings.asset
 create mode 100644 ProjectSettings/ProjectVersion.txt
 create mode 100644 ProjectSettings/QualitySettings.asset
 create mode 100644 ProjectSettings/TagManager.asset
 create mode 100644 ProjectSettings/TimeManager.asset
 create mode 100644 ProjectSettings/UnityConnectSettings.asset
 create mode 100644 ProjectSettings/VFXManager.asset
 create mode 100644 ProjectSettings/VersionControlSettings.asset
 create mode 100644 ProjectSettings/XRSettings.asset
 create mode 100644 ProjectSettings/boot.config
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pay attention to the first line that Git printed out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;[main (root-commit) e1c03d9] Initial commit of my Unity project
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;main&lt;/code&gt; is the name of the branch that Git added the commit onto. &lt;code&gt;e1c03d9&lt;/code&gt; are the last 7 characters of the ID of this commit. If we use the &lt;code&gt;git log&lt;/code&gt; command, we can see the full ID and your message.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git log
&lt;span class="gp"&gt;commit e1c03d962e7f8e4d1e08f69b6aebbc3e528b5b84 (HEAD -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;Author: Alon Farchy &amp;lt;some@email.net&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Date:   Tue Jul 16 15:34:39 2024 -0700

    Initial commit of my Unity project
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Useful Git Commands for Unity Developers
&lt;/h2&gt;

&lt;p&gt;Here are some useful Git commands that you might need frequently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git status&lt;/code&gt;: Check the status of your working directory.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git add&lt;/code&gt;: Stage a file to be added to the commit.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git reset&lt;/code&gt;: Unstage a file so it won't be added to the commit.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git commit&lt;/code&gt;: Create a commit.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git revert&lt;/code&gt;: Create a new commit that undoes everything in a previous commit.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git log&lt;/code&gt;: View the commit history.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git reflog&lt;/code&gt;: View the history of what you did (commit, checkout, reset, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git branch&lt;/code&gt;: List, create, or delete branches.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git switch&lt;/code&gt;: Switch branches.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git restore&lt;/code&gt;: Update a file to specific version.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git checkout&lt;/code&gt;: Older command to switch branches or update a file to specific version&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git merge&lt;/code&gt;: Merge branches.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git fetch&lt;/code&gt;: Download changes from your remote repository.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git pull&lt;/code&gt;: Do &lt;code&gt;git fetch&lt;/code&gt; and then merge your branch with any remote changes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git push&lt;/code&gt;: Upload changes made to your branch to your remote repository.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a comprehensive list of commands and their usage, refer to the &lt;a href="https://git-scm.com/docs" rel="noopener noreferrer"&gt;Git documentation&lt;/a&gt;, or consider learning with an interactive tutorial like &lt;a href="https://learngitbranching.js.org" rel="noopener noreferrer"&gt;learngitbranching&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commit Early, Commit Often
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqlluce7f1t1icst3tnbb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqlluce7f1t1icst3tnbb.png" alt="Git Commit Often" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Git, commits are considered "cheap" because they only store file changes. Because of this, you can use Git as your personal undo system by committing frequently. Each time you make some small changes, you can make a commit and return to it later if you don't like the changes you made.&lt;/p&gt;

&lt;p&gt;In Unity, committing often is even more relevant because it's easy to accidentally lose your undo history. For example, when you make some changes to a prefab in Unity, and then close that prefab, you'll lose the undo history for that prefab. Or, what if you're trying out some changes to a scene and Unity crashes? Goodbye undo history.&lt;/p&gt;

&lt;p&gt;Your typical workflow should look something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do some work.&lt;/li&gt;
&lt;li&gt;Hit the Save button in Unity!&lt;/li&gt;
&lt;li&gt;Make a commit.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git add &amp;lt;files&amp;gt;
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Description of what has changed"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Backup your commit by pushing it to your remote repo. We'll discuss remote repos in the section: Git on the Web: GitHub for Unity Developers.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Repeat.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Help, I made a boo boo!
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhrusyebdjlfxc3tvqn5m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhrusyebdjlfxc3tvqn5m.png" alt="Git revert to undo commits" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you make a mistake, you can use the commands in this cheatsheet to roll back to an earlier version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Unstage a file so it won&lt;span class="s1"&gt;'t be committed
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;git reset &amp;lt;path&amp;gt;
&lt;/span&gt;&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Checkout the version of the file at the latest commit.
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git checkout &lt;span class="nt"&gt;--&lt;/span&gt; &amp;lt;path&amp;gt;
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Restore the version of the file at the commit before the latest.
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git restore &lt;span class="nt"&gt;--source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;HEAD~1 &amp;lt;path&amp;gt;
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Restore the version of a file at a particular commit
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git restore &lt;span class="nt"&gt;--source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;commit-id&amp;gt; &amp;lt;path&amp;gt;
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Create a NEW commit that undoes everything &lt;span class="k"&gt;in &lt;/span&gt;the specified commit
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git revert &amp;lt;commit-id&amp;gt;
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Delete files which are not tracked by Git. Useful when Unity gets &lt;span class="k"&gt;in &lt;/span&gt;a bad state.
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git clean &lt;span class="nt"&gt;-xfd&lt;/span&gt; &amp;lt;path&amp;gt;
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Move your branch to the target commit without changing your files.
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git reset &amp;lt;commit-id&amp;gt;
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Move your branch to the target commit and discard all your changes.
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; &amp;lt;commit-id&amp;gt;
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Shows you the &lt;span class="nb"&gt;history &lt;/span&gt;of what you did &lt;span class="o"&gt;(&lt;/span&gt;commit, checkout, reset, etc.&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git reflog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Branching and Merging
&lt;/h2&gt;

&lt;p&gt;Often, you and your team will be working on multiple things at once. This is where branching comes handy. In Git, you are always making commits into a branch. By default, this is the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;p&gt;From the &lt;code&gt;main&lt;/code&gt; branch, you can create a new branch using the &lt;code&gt;git switch -c&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git switch &lt;span class="nt"&gt;-c&lt;/span&gt; branch1
&lt;span class="go"&gt;Switched to a new branch 'branch1'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbz5kmk0h4k8gjadof8un.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbz5kmk0h4k8gjadof8un.png" alt="Git Branch" width="793" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Think of your repository like a tree. The trunk of the tree is the &lt;code&gt;main&lt;/code&gt; branch, and you've just created the first branch on that tree called &lt;code&gt;branch1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Many Git commands operate on the current branch, also called &lt;code&gt;HEAD&lt;/code&gt;. As you make new commits, you'll add them to &lt;code&gt;branch1&lt;/code&gt; instead of &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's create a new file and commit it to &lt;code&gt;branch1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1t6nz7vpxjliql4gpjlt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1t6nz7vpxjliql4gpjlt.png" alt="Git Branch and Commit" width="800" height="212"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;1234 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; file.txt
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git add file.txt
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Creating my first commit on branch1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's compare the histories of &lt;code&gt;main&lt;/code&gt; and &lt;code&gt;branch1&lt;/code&gt; using the &lt;code&gt;git log&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git log main
&lt;span class="go"&gt;commit e1c03d962e7f8e4d1e08f69b6aebbc3e528b5b84 (main)
&lt;/span&gt;&lt;span class="gp"&gt;Author: Alon Farchy &amp;lt;some@email.com&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Date:   Tue Jul 16 15:34:39 2024 -0700

    Initial commit of my Unity project
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git log branch1
&lt;span class="gp"&gt;commit 736c35ada5a09ef54737f12ded8bb83d92cdc9d3 (HEAD -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;branch1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;Author: Alon Farchy &amp;lt;some@email.com&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Date:   Sun Jul 21 19:24:54 2024 -0700

    Creating my first commit on branch1

commit e1c03d962e7f8e4d1e08f69b6aebbc3e528b5b84 (main)
&lt;/span&gt;&lt;span class="gp"&gt;Author: Alon Farchy &amp;lt;some@email.com&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Date:   Tue Jul 16 15:34:39 2024 -0700

    Initial commit of my Unity project
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that the history of &lt;code&gt;branch1&lt;/code&gt; also contains the history of &lt;code&gt;main&lt;/code&gt;, but the &lt;code&gt;main&lt;/code&gt; branch hasn't been changed.&lt;/p&gt;

&lt;p&gt;We can always go back to the &lt;code&gt;main&lt;/code&gt; branch and make a commit there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8bn278vzsa6wma7o2vbq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8bn278vzsa6wma7o2vbq.png" alt="Git Branch and Commit to main" width="800" height="223"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git switch main
&lt;span class="go"&gt;Switched to branch 'main'

&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;4567 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; file.txt
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git add file.txt
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Added file.txt to main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's look at the history of each branch&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git log main
&lt;span class="gp"&gt;commit 855678981c8179c05baaf17c29090726976f6860 (HEAD -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;Author: Alon Farchy &amp;lt;some@email.com&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Date:   Sun Jul 21 19:29:17 2024 -0700

    Added file.txt to main

commit e1c03d962e7f8e4d1e08f69b6aebbc3e528b5b84
&lt;/span&gt;&lt;span class="gp"&gt;Author: Alon Farchy &amp;lt;some@email.com&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Date:   Tue Jul 16 15:34:39 2024 -0700

    Initial commit of my Unity project
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git log branch1
&lt;span class="go"&gt;commit 736c35ada5a09ef54737f12ded8bb83d92cdc9d3 (branch1)
&lt;/span&gt;&lt;span class="gp"&gt;Author: Alon Farchy &amp;lt;some@email.com&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Date:   Sun Jul 21 19:24:54 2024 -0700

    Creating my first commit on branch1

commit e1c03d962e7f8e4d1e08f69b6aebbc3e528b5b84
&lt;/span&gt;&lt;span class="gp"&gt;Author: Alon Farchy &amp;lt;some@email.com&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Date:   Tue Jul 16 15:34:39 2024 -0700

    Initial commit of my Unity project
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These branches have now diverged, which means that each branch has at least one commit that the other does not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Merging Branches
&lt;/h3&gt;

&lt;p&gt;To bring these branches back together, we can merge them with the &lt;code&gt;git merge&lt;/code&gt; command. This will take the commits in a specified branch and try to combine them with the commits in the current branch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwya513l0xafg1jur6ctx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwya513l0xafg1jur6ctx.png" alt="Git Merge" width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's try merging &lt;code&gt;branch1&lt;/code&gt; into &lt;code&gt;main&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git switch main
&lt;span class="go"&gt;Switched to branch 'main'.

&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git merge branch1
&lt;span class="go"&gt;Auto-merging file.txt
CONFLICT (add/add): Merge conflict in file.txt
&lt;/span&gt;&lt;span class="gp"&gt;Automatic merge failed;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fix conflicts and &lt;span class="k"&gt;then &lt;/span&gt;commit the result.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uh oh. If you've been following these examples, you'll notice that we added file.txt in both branches and gave them different contents. Git doesn't know what you want to do, so it sounds the alarm by calling a &lt;strong&gt;merge conflict&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Merge Conflicts
&lt;/h3&gt;

&lt;p&gt;Merge conflicts happen when Git can't automatically reconcile the differences between two commits. This usually occurs when the same line in a file has been changed in both branches. In our case, &lt;code&gt;file.txt&lt;/code&gt; was modified in both &lt;code&gt;main&lt;/code&gt; and &lt;code&gt;branch1&lt;/code&gt;, resulting in a conflict.&lt;/p&gt;

&lt;p&gt;When a merge conflict occurs, Git leaves both changes in the file, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD
4567
=======
1234
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; branch1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This notation shows the conflicting sections. Remember that &lt;code&gt;HEAD&lt;/code&gt; refers to our current place in history, which is the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The content between &lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD&lt;/code&gt; and &lt;code&gt;=======&lt;/code&gt; is from the &lt;code&gt;main&lt;/code&gt; branch.&lt;/li&gt;
&lt;li&gt;The content between &lt;code&gt;=======&lt;/code&gt; and &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; branch1&lt;/code&gt; is from &lt;code&gt;branch1&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To resolve the conflict, you need to edit the file to contain the correct final content. You might decide to keep one change over the other, or merge the two changes together.&lt;/p&gt;

&lt;p&gt;Once you're done merging the file, use &lt;code&gt;git add&lt;/code&gt; to stage it for the merge commit. When all files are staged, we can use &lt;code&gt;git merge --continue&lt;/code&gt; to complete the merge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git add file.txt
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git merge &lt;span class="nt"&gt;--continue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Asset Conflicts and Merging
&lt;/h2&gt;

&lt;p&gt;Merging your code and text files is straightforward, but what about assets? Two people on your team might edit the same image files, audio files, Unity scenes, prefabs, or many other files in your project. This can cause a big problem because these assets are not easy to merge. In the worst case, one teammate will have to redo their work.&lt;/p&gt;

&lt;p&gt;Let's address this problem with two strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to avoid asset conflicts.&lt;/li&gt;
&lt;li&gt;How to use Unity's YAML merge tool to merge scenes and prefabs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before you go further, make sure your Unity project is set to &lt;strong&gt;Visible Meta Files&lt;/strong&gt; and &lt;strong&gt;Force Text&lt;/strong&gt; (Project Settings &amp;gt; Editor). This makes scenes and prefabs readable in Git diffs and greatly improves merge success.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Avoid Asset Conflicts
&lt;/h3&gt;

&lt;p&gt;If there's no conflict, there's nothing to merge. Problem solved! But how can you actually avoid conflicts?&lt;/p&gt;

&lt;p&gt;Let's take an example scenario. One developer is adjusting the jump parameters of a character by editing fields on the components attached to the character. Another developer is updating the colors and fonts of the main menu. If the UI and the character are both in the same scene, then there will be a conflict on the scene asset.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm06xigf9ceyvehu24spe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm06xigf9ceyvehu24spe.png" alt="Git Conflict in the Unity Scene" width="449" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Break up your Scenes
&lt;/h4&gt;

&lt;p&gt;We can avoid this conflict if the character and the main menu were each separate prefabs. Each developer could update the prefab instead of updating the scene.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fop6omnzjs3zqn8cdd67t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fop6omnzjs3zqn8cdd67t.png" alt="Breaking the scene into prefabs" width="800" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Taking this strategy to its logical conclusion, you can avoid many asset conflicts by moving &lt;strong&gt;every GameObject in the scene into its own prefab&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hint&lt;/em&gt;: Sometimes you'll accidentally edit a prefab in the scene. Oops! Fortunately, Unity has a solution for this. Find the &lt;strong&gt;Overrides&lt;/strong&gt; dropdown on the root GameObject of your prefab. Not only can you see which fields have been edited, but you can also apply those edits to the prefab. Nifty!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5q0fxhskup46630s0dpg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5q0fxhskup46630s0dpg.png" alt="Find and Fix Prefab Overrides" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes, you'll end up with a very large prefab. For example, maybe the whole UI for the game is in one big prefab. Now you might get asset conflicts on this large prefab.&lt;/p&gt;

&lt;p&gt;We can fix this with the same strategy: break up the large prefab into smaller prefabs. It's up to you to balance the complexity of more prefabs and the risk of asset conflicts.&lt;/p&gt;

&lt;p&gt;If done correctly, the only time the scene or large prefab needs to be edited is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To add or remove a prefab, or&lt;/li&gt;
&lt;li&gt;To drag a reference from one prefab into the component property of another prefab.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's possible to avoid the first case by instantiating all of your prefabs at runtime. You will have to decide for yourself if this tradeoff is worth the effort.&lt;/p&gt;

&lt;p&gt;But how do we deal with the second case: references &lt;em&gt;across&lt;/em&gt; prefabs?&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoiding Asset References: Dependency Injection and Service Locator
&lt;/h3&gt;

&lt;p&gt;In any beginner's guide to Unity, you learn how to create public properties on your components which appear as editable fields in the editor. If these properties are a component type or GameObject type, then you can drag other GameObjects from the scene into these fields and Unity will inject them into your component. Magic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjsgqubuw2jzbyrimrl2q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjsgqubuw2jzbyrimrl2q.png" alt="Prefab reference in the scene" width="800" height="591"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While convenient, these references can cause frustrating asset conflicts in two ways. First, they simply create more reasons for you to edit your assets. Second, if these references are between separate prefabs, then you end up having to edit the scene to set these references. This reduces the benefits of breaking the scene up into prefabs.&lt;/p&gt;

&lt;p&gt;This problem isn't just an asset problem. It's actually a well known problem in software architecture: how do you get separate parts of your code to access each other? Martin Fowler, a well known software engineer, wrote a &lt;a href="https://www.martinfowler.com/articles/injection.html" rel="noopener noreferrer"&gt;great article&lt;/a&gt; on this topic. Cutting to the chase, you have two options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dependency Injection: The entity instantiating your component hands you the references you need.&lt;/li&gt;
&lt;li&gt;Service Locator: You look up the references you need in a well known table (the service locator).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you drag objects into fields in the scene, Unity is effectively performing dependency injection for you by 'injecting' references to your component at runtime. Unfortunately, this forces you to edit the scene, which creates merge conflicts.&lt;/p&gt;

&lt;p&gt;Instead, you can use a dependency injection library or a service locator library help you solve this problem. At Virtual Maker, we use a custom service locator library, but here are some open source options you can choose from.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://vcontainer.hadashikick.jp/" rel="noopener noreferrer"&gt;VContainer&lt;/a&gt;: A dependency injection library that is "light and fast".&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Mathijs-Bakker/Extenject" rel="noopener noreferrer"&gt;Extenject&lt;/a&gt;: A dependency injection library forked from &lt;a href="https://github.com/modesttree/Zenject" rel="noopener noreferrer"&gt;Zenject&lt;/a&gt; with lots of features.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to use Unity's YAML merge tool to merge scenes and prefabs.
&lt;/h3&gt;

&lt;p&gt;Ok, so you broke up your scene into prefabs, but now two developers have added new prefabs to the scene. What do you do?&lt;/p&gt;

&lt;p&gt;Meet your new best friend: &lt;a href="https://docs.unity3d.com/Manual/SmartMerge.html" rel="noopener noreferrer"&gt;UnityYAMLMerge&lt;/a&gt;. This is a "mergetool" which will attempt to automatically merge scenes and prefabs. In simple cases, like two developers adding or removing different prefabs to the scene, it can resolve the conflict correctly by adding or removing the correct prefabs.&lt;/p&gt;

&lt;p&gt;Unfortunately, this is not a get out of jail free card. You might still hit unresolvable conflicts, like two developers changing the same transform's position. In this case, you'll have to manually merge the files.&lt;/p&gt;

&lt;p&gt;Unity stores its scenes, prefabs, and many other asset files in a &lt;a href="https://yaml.org/" rel="noopener noreferrer"&gt;YAML&lt;/a&gt; text format. For simple conflicts like a transform position change, you can edit the YAML files directly to merge the changes.&lt;/p&gt;

&lt;p&gt;It's also possible for UnityYAMLMerge to do the wrong thing in complicated situations. Suppose one developer unpacks a prefab in the scene, and another developer overrides a value in the same prefab. UnityYAMLMerge will end up keeping both versions of the prefab in the scene.&lt;/p&gt;

&lt;h2&gt;
  
  
  Git Clients: UI for Git!
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe1brexq94nr2hq7efcpe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe1brexq94nr2hq7efcpe.png" alt="GitHub Desktop" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While the command line is a powerful tool for working with Git, many developers prefer to use Git clients for their graphical interfaces and additional features. Git clients can make managing repositories more intuitive, especially for those who are new to version control or prefer a visual approach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: Even if you use one of these Git clients, it is important to install the Git command line tools. Not only are these tools useful in a pinch, but they are &lt;strong&gt;required for Unity&lt;/strong&gt; to install git dependency packages!&lt;/p&gt;

&lt;p&gt;Below are some of the most popular Git clients available:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/apps/desktop" rel="noopener noreferrer"&gt;GitHub Desktop&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;GitHub Desktop is a free, open-source Git client developed by GitHub. It is designed to simplify the Git workflow, making it accessible for both beginners and experienced developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.sourcetreeapp.com/" rel="noopener noreferrer"&gt;SourceTree&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;SourceTree is a free Git client developed by Atlassian. It supports both Git and Mercurial repositories and offers a rich set of features for managing your version control workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.gitkraken.com/" rel="noopener noreferrer"&gt;GitKraken&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;GitKraken is a cross-platform Git client known for its elegant interface and robust features. It is designed to streamline Git workflows and improve productivity.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.git-tower.com/" rel="noopener noreferrer"&gt;Tower&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Tower is a powerful Git client available for macOS and Windows. It offers a comprehensive set of features designed for professional developers and teams.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://git-fork.com/" rel="noopener noreferrer"&gt;Fork&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Fork is a fast and friendly Git client for macOS and Windows. It focuses on providing a simple yet powerful interface for managing Git repositories.&lt;/p&gt;

&lt;h2&gt;
  
  
  Git on the Web: GitHub for Unity Developers
&lt;/h2&gt;

&lt;p&gt;So now we know how to use Git for version control. Now we'll learn how to backup your project and start collaborating with a team.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; is a web-based platform that hosts Git repositories. It provides tools for collaborative development, code review, and project management. By pushing your local Git repository to GitHub, you can collaborate with others, track issues, and more.&lt;/p&gt;

&lt;p&gt;We use GitHub here as an example, but there are also other hosts you could explore like &lt;a href="https://about.gitlab.com/" rel="noopener noreferrer"&gt;GitLab&lt;/a&gt; and &lt;a href="https://bitbucket.org/product" rel="noopener noreferrer"&gt;BitBucket&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pushing your Unity Project to GitHub
&lt;/h2&gt;

&lt;p&gt;To push your local repository to GitHub, follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a free account on &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/new" rel="noopener noreferrer"&gt;Create a new repository on GitHub&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkihsnvtaq4w43x2zg3gp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkihsnvtaq4w43x2zg3gp.png" alt="GitHub new Repo" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set these options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No template&lt;/li&gt;
&lt;li&gt;Choose yourself as the owner and give your repo a name.&lt;/li&gt;
&lt;li&gt;Choose &lt;code&gt;Private&lt;/code&gt; unless you want your project to be available to anyone on the internet.&lt;/li&gt;
&lt;li&gt;Leave &lt;code&gt;Add a README file&lt;/code&gt; unchecked, since we'll be pushing our own content.&lt;/li&gt;
&lt;li&gt;Don't add a &lt;code&gt;.gitignore&lt;/code&gt;, since you already have one.&lt;/li&gt;
&lt;li&gt;Leave License as &lt;code&gt;None&lt;/code&gt; if you chose &lt;code&gt;Private&lt;/code&gt;, or choose an appropriate license.&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Create repository&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Push your changes using these commands, replacing &lt;code&gt;{org}&lt;/code&gt; and &lt;code&gt;{repo}&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git remote add origin https://github.com/&lt;span class="o"&gt;{&lt;/span&gt;org&lt;span class="o"&gt;}&lt;/span&gt;/&lt;span class="o"&gt;{&lt;/span&gt;repo&lt;span class="o"&gt;}&lt;/span&gt;.git
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations, your project is now backed up to GitHub! You can test this by "cloning" your repository to another device or folder using the &lt;code&gt;git clone&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git clone https://github.com/&lt;span class="o"&gt;{&lt;/span&gt;org&lt;span class="o"&gt;}&lt;/span&gt;/&lt;span class="o"&gt;{&lt;/span&gt;repo&lt;span class="o"&gt;}&lt;/span&gt;.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each time you make a commit, you can &lt;code&gt;git push&lt;/code&gt; your branch to GitHub and then &lt;code&gt;git pull&lt;/code&gt; those changes from GitHub to one of your other clones.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb356v7zvcuwjtsg5m64y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb356v7zvcuwjtsg5m64y.png" alt="Git Push and Pull" width="800" height="349"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Machine 1
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git push
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Machine 2
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git pull
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Working with a Team: GitHub Organizations
&lt;/h2&gt;

&lt;p&gt;A GitHub organization is a shared account for collaborative projects. It allows teams to manage permissions, streamline access, and consolidate billing. Key features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Team Management: Group members into teams with different access levels.&lt;/li&gt;
&lt;li&gt;Repository Permissions: Control who can push, review, and administer repositories.&lt;/li&gt;
&lt;li&gt;Project Management: Use tools like GitHub Projects for task tracking and progress management.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As of May 2026, creating an organization on GitHub is free with limited features.&lt;/p&gt;

&lt;p&gt;To create an organization, click your profile picture &amp;gt; &lt;code&gt;Your organizations&lt;/code&gt; &amp;gt; &lt;code&gt;New organization&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once you've created an organization, you can either create a new repo or transfer an existing repo to the organization in the &lt;code&gt;Settings&lt;/code&gt; page for that repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Your First Pull Request
&lt;/h2&gt;

&lt;p&gt;When working on a team, it's often important for team members to review each other's changes before merging them into the &lt;code&gt;main&lt;/code&gt; branch or release branches. In Git nomenclature, this review process is called a "pull request" because you're making a request to your team to pull your changes into another branch.&lt;/p&gt;

&lt;p&gt;There are several critical advantages to using pull requests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Team members can catch each other's mistakes.&lt;/li&gt;
&lt;li&gt;Team members can suggest alternative ways to fix a bug or implement a feature.&lt;/li&gt;
&lt;li&gt;Automation can run with each pull request to build the Unity project for playtesting or perform other checks.&lt;/li&gt;
&lt;li&gt;By their nature, pull requests create a clear history of changes going into the main branch, allowing individual bug fixes or features to be reverted if necessary.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Creating a new branch for your pull request
&lt;/h3&gt;

&lt;p&gt;Let's start simple by making a trivial pull request. First, we need to make a new branch to do our feature development.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Go to the main branch and get the latest changes.
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git switch main
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git pull
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Create a new branch off main
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git switch &lt;span class="nt"&gt;-c&lt;/span&gt; dev/test-feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command creates a new branch from &lt;code&gt;main&lt;/code&gt; called &lt;code&gt;dev/test-feature&lt;/code&gt;. The &lt;code&gt;dev/&lt;/code&gt; part is a naming convention we like to use to separate developer branches from the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;p&gt;As a test, let's add a new text file to your repository and make a new commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git add path/to/my_file.txt
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Adding my_file"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, push this branch and all of its commits to GitHub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin dev/test-feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the first time you push a branch, you can just use the shorthand &lt;code&gt;git push&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a new pull request
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj9n3hmx95vg1uvn0uirz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj9n3hmx95vg1uvn0uirz.png" alt="GitHub new pull request" width="800" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's go to GitHub and create a pull request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to your repo: &lt;code&gt;https://github.com/{org}/{repo}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Pull requests&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;New pull request&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Set the &lt;code&gt;base&lt;/code&gt; to &lt;code&gt;main&lt;/code&gt; and &lt;code&gt;compare&lt;/code&gt; to &lt;code&gt;dev/test-feature&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add a title and description&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;Create pull request&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This will take you to your pull request page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc09prctq1moz1vjt886c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc09prctq1moz1vjt886c.png" alt="GitHub Pull Request Page" width="800" height="865"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Review the pull request
&lt;/h3&gt;

&lt;p&gt;From the pull request page, there are many options. Most importantly, your team can click the &lt;code&gt;Files changed&lt;/code&gt; tab to start reviewing the pull request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ybrjhb2m1awhd9uk145.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ybrjhb2m1awhd9uk145.png" alt="GitHub Review" width="800" height="865"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you start a review, you can add comments to individual files. &lt;strong&gt;Important:&lt;/strong&gt; your team won't see your comments until you finish your review.&lt;/p&gt;

&lt;p&gt;Once you're done making comments, click the &lt;code&gt;Review&lt;/code&gt; dropdown and finish your review.&lt;/p&gt;

&lt;h3&gt;
  
  
  Update your Pull Request
&lt;/h3&gt;

&lt;p&gt;At any point during the review process, you can create a new commit, and use &lt;code&gt;git push&lt;/code&gt; to update your branch on GitHub. The pull request will be automatically updated with your changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Complete the pull request
&lt;/h3&gt;

&lt;p&gt;Once you've answered all your team's comments and are ready to complete your pull requests, navigate back to the &lt;code&gt;Conversation&lt;/code&gt; tab and locate the &lt;code&gt;Merge pull request&lt;/code&gt; button. If you click the dropdown, you'll see some options:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgnudyeq8pczjxqpkh8pi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgnudyeq8pczjxqpkh8pi.png" alt="GitHub Pull Request Complete Options" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a merge commit&lt;/li&gt;
&lt;li&gt;Squash and merge&lt;/li&gt;
&lt;li&gt;Rebase and merge&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Creating a merge commit is the most straightforward option. It creates a new &lt;code&gt;merge commit&lt;/code&gt; in your target branch (&lt;code&gt;main&lt;/code&gt;) that combines your branch and the target branch together. All of the commits you made will appear in the &lt;code&gt;main&lt;/code&gt; branch history.&lt;/p&gt;

&lt;p&gt;Squash and merge is another popular option, where all your commits are "squashed" into one commit which is added to the &lt;code&gt;main&lt;/code&gt; branch. This keeps the main history "clean" by not including all the little commits you made while developing your bug or feature, but in exchange you lose those details in your Git history.&lt;/p&gt;

&lt;p&gt;Don't use Rebase and merge unless you &lt;em&gt;really&lt;/em&gt; know what you're doing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary of the Pull Request flow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbxesa44e7wus5mc6m5t8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbxesa44e7wus5mc6m5t8.png" alt="Git Dev Cycle" width="661" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a team that embraces pull requests, each member uses the following workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new branch off &lt;code&gt;main&lt;/code&gt; in which to do feature or bug work.&lt;/li&gt;
&lt;li&gt;Commit often to this branch and push those changes to back them up.&lt;/li&gt;
&lt;li&gt;When ready, create a pull request back to the &lt;code&gt;main&lt;/code&gt; branch.&lt;/li&gt;
&lt;li&gt;The team reviews the pull request. Automation also runs to build the project or run tests.&lt;/li&gt;
&lt;li&gt;Commit and push additional changes as needed.&lt;/li&gt;
&lt;li&gt;When all review comments are resolved, and all automation passes, complete the pull request.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Sharing Code and Assets Between Projects: Submodules vs Unity Packages
&lt;/h2&gt;

&lt;p&gt;Once you've gotten the hang of storing your Unity projects in Git repositories, you may run into a situation where you have the same code and assets in multiple projects. You might then find yourself making changes in one project, and then having to repeat those changes in the second project. And the third project.&lt;/p&gt;

&lt;p&gt;So, you may be thinking -- is there a good way to share assets between repositories?&lt;/p&gt;

&lt;h3&gt;
  
  
  Here be Dragons!
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvc6j4xx235n2dx3d84p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvc6j4xx235n2dx3d84p.png" alt="Share Code Dragons" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Beware! Sharing assets between repos comes at a considerable cost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The project organization becomes more complicated, which makes it harder for your team to understand.&lt;/li&gt;
&lt;li&gt;Making changes to the shared assets becomes more difficult because multiple projects depend on them.&lt;/li&gt;
&lt;li&gt;The Git history is now split between the &lt;code&gt;main&lt;/code&gt; repository and the shared assets. It becomes an extra chore to keep them in sync.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To avoid these issues, some teams opt to use a &lt;strong&gt;monorepo&lt;/strong&gt;, which is a big repository with multiple projects. To give you an extreme example, Microsoft opted to &lt;a href="https://devblogs.microsoft.com/bharry/scaling-git-and-some-back-story/" rel="noopener noreferrer"&gt;host all of Windows in a single repository&lt;/a&gt;. This required making changes to Git to cope with the millions of files.&lt;/p&gt;

&lt;p&gt;Ok, you've been warned. If you're still convinced your projects will benefit from sharing assets between repos, then you have two main options:&lt;/p&gt;

&lt;h3&gt;
  
  
  Git Submodules
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6oddiz3syg4m3n9ty0q0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6oddiz3syg4m3n9ty0q0.png" alt="Git Submodules" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Git submodules are the Git-based solution to the sharing problem. A submodule is just a pointer from one repository to a commit in another repository. So, all you have to do is create a separate repository for your shared assets, then add a submodule to each of your projects. Sounds simple right?&lt;/p&gt;

&lt;p&gt;Unfortunately, it's not so simple in practice. Developers on the project need to be aware that submodules exist and to keep their local repo updated with the latest submodules. This makes the learning curve for Git steeper for new developers.&lt;/p&gt;

&lt;p&gt;When should you use Submodules? If the code or assets you're trying to share are in Unity, we recommend creating a Unity Package (see below) and using the Unity Package Manager (UPM) to pull it into different projects. Package dependencies are more automatic and can be properly versioned.&lt;/p&gt;

&lt;p&gt;But if your shared assets are outside of Unity, then use submodules.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to add a submodule to your repo
&lt;/h3&gt;

&lt;p&gt;First, create a new repo with your shared assets. We'll pull this repo into your main project repo as a submodule.&lt;/p&gt;

&lt;p&gt;Navigate to the directory where you want to add your submodule and use the &lt;code&gt;git submodule add&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;main_repo/path/to/somewhere
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git submodule add https://github.com/&lt;span class="o"&gt;{&lt;/span&gt;org&lt;span class="o"&gt;}&lt;/span&gt;/&lt;span class="o"&gt;{&lt;/span&gt;submodule_repo&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git status
&lt;span class="go"&gt;On branch main
Changes to be committed:
&lt;/span&gt;&lt;span class="gp"&gt;  (use "git restore --staged &amp;lt;file&amp;gt;&lt;/span&gt;...&lt;span class="s2"&gt;" to unstage)
&lt;/span&gt;&lt;span class="go"&gt;        new file:   ../.gitmodules
        new file:   {submodule_repo}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll notice git added or modified the &lt;code&gt;.gitmodules&lt;/code&gt; file. You'll also notice your repo appears like a "new file". It's a bit misleading - Git is actually just telling you that the submodule is staged to be committed.&lt;/p&gt;

&lt;p&gt;Commit these changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add submodule"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you need to know how to work with the submodule. If you &lt;code&gt;cd&lt;/code&gt; into the submodule folder and start using git commands like &lt;code&gt;git log&lt;/code&gt;, it will look just like you cloned this repository in a normal way. You can switch branches here, make changes, commit, push, etc.&lt;/p&gt;

&lt;p&gt;However, any time you make changes, the parent repository will detect those changes. For example, if I add an untracked file to the submodule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git status
&lt;span class="go"&gt;On branch main
Changes not staged for commit:
&lt;/span&gt;&lt;span class="gp"&gt;  (use "git add &amp;lt;file&amp;gt;&lt;/span&gt;...&lt;span class="s2"&gt;" to update what will be committed)
&lt;/span&gt;&lt;span class="gp"&gt;  (use "git restore &amp;lt;file&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt; to discard changes &lt;span class="k"&gt;in &lt;/span&gt;working directory&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;  (commit or discard the untracked or modified content in submodules)
        modified:   {submodule_repo} (untracked content)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It doesn't tell me which files have changed, just that there are changes. If I go back to the submodule and commit this file, then I get a different kind of message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git status
&lt;span class="go"&gt;On branch main
Changes not staged for commit:
&lt;/span&gt;&lt;span class="gp"&gt;  (use "git add &amp;lt;file&amp;gt;&lt;/span&gt;...&lt;span class="s2"&gt;" to update what will be committed)
&lt;/span&gt;&lt;span class="gp"&gt;  (use "git restore &amp;lt;file&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt; to discard changes &lt;span class="k"&gt;in &lt;/span&gt;working directory&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;        modified:   {submodule_repo} (new commits)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now Git recognizes that the submodule has a new commit, but we haven't updated the pointer in the main repo to point to that new commit. We need to make another commit in the project repo to update that pointer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;git add {submodule_repo}
git commit -m "Update Submodule"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great - now the main repo and the submodule are in sync! But how do we push this update to our team? This is where things get tricky.&lt;/p&gt;

&lt;p&gt;By default, &lt;code&gt;git push&lt;/code&gt; will only push the current repo to its remote origin, but it will not push the submodule. You need to remember to do &lt;code&gt;git push&lt;/code&gt; in the submodule directory too.&lt;/p&gt;

&lt;p&gt;To review this change with your team, you need to create two pull requests - one in the submodule repo, and another in your main repo. If you need to make more changes in the submodule, you need to go through the whole process again:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl6vyb6n5tfqppgb0yqu8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl6vyb6n5tfqppgb0yqu8.png" alt="Git Updating a Submodule" width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make a commit in the submodule&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git push&lt;/code&gt; that commit.&lt;/li&gt;
&lt;li&gt;Make a commit in your main repo&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git push&lt;/code&gt; that commit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you're finished with your changes and you complete your two pull requests, it's time for your team to pull your changes. First, they check out and pull the branch where you added the submodule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git switch main
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git pull
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If they go looking for your newly added submodule, what they'll notice is that the submodule directory is empty. To tell Git to recognize new submodules, use the &lt;code&gt;git submodule init&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;git submodule init
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now Git recognizes the submodule exists, but it hasn't checked out any files. Each time you update the submodule with new commits, your team needs to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;git submodule update
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the &lt;a href="https://git-scm.com/docs/git-submodule" rel="noopener noreferrer"&gt;Git Submodule documentation&lt;/a&gt; for additional commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unity Packages
&lt;/h3&gt;

&lt;p&gt;If the code or assets you want to share are in Unity, we highly recommend using Unity Packages over submodules. While they are a bit of work to set up, they are easier to version and will automatically keep your team in sync.&lt;/p&gt;

&lt;p&gt;For reference, Unity has some good documentation for &lt;a href="https://docs.unity3d.com/Manual/CustomPackages.html" rel="noopener noreferrer"&gt;custom packages&lt;/a&gt;. In this example, we'll create an "embedded" package, and then move it to a separate repository to be shared.&lt;/p&gt;

&lt;p&gt;To create an embedded package, create a folder inside the &lt;code&gt;Packages&lt;/code&gt; folder called &lt;code&gt;com.{org}.{package-name}&lt;/code&gt;. Then, add this &lt;code&gt;package.json&lt;/code&gt; file to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"displayName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Package Name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"This package does XYZ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.{org}.{package-name}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"unity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2020.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"utilities"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can hop over to Unity and start adding assets to our package. Your package should appear as a folder in the &lt;code&gt;Project&lt;/code&gt; window under the &lt;code&gt;Packages&lt;/code&gt; section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl2fj72c9weq2s0qros3q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl2fj72c9weq2s0qros3q.png" alt="Unity Project Window Packages" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to add code to your package, then you'll also need to create an assembly definition file: &lt;code&gt;Assets &amp;gt; Create &amp;gt; Assembly Definition&lt;/code&gt;. This ensures your package code is compiled separately from the main project. Learn more about &lt;a href="https://docs.unity3d.com/Manual/cus-asmdef.html" rel="noopener noreferrer"&gt;Assembly Definition and packages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you're happy with your package, you can move it to its own repository, so it can be shared with other projects. Create a new repository and move the whole package into it.&lt;/p&gt;

&lt;p&gt;If you want your package to be open source, you can use OpenUPM to distribute your package with the Unity open source community. &lt;a href="https://openupm.com/docs/" rel="noopener noreferrer"&gt;Learn how to distribute your package with OpenUPM&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to keep your package private, then you can use a Git reference to pull it into your Unity projects.&lt;br&gt;
Open the &lt;code&gt;Package Manager&lt;/code&gt; and go to &lt;code&gt;+ &amp;gt; Add package from git URL...&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Favhant6rainudtlpb4kq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Favhant6rainudtlpb4kq.png" alt="Unity Package Manager Add Git Package" width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy and paste the URL to your package's Git repository, and add &lt;code&gt;.git&lt;/code&gt; on the end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;https://github.com/{org}/{repo}.git
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all goes well, you'll see your package in the &lt;code&gt;Project&lt;/code&gt; window as before. Check which changes Unity made with &lt;code&gt;git status&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;git status
On branch main
Changes not staged for commit:
&lt;/span&gt;&lt;span class="gp"&gt;  (use "git add &amp;lt;file&amp;gt;&lt;/span&gt;...&lt;span class="s2"&gt;" to update what will be committed)
&lt;/span&gt;&lt;span class="gp"&gt;  (use "git restore &amp;lt;file&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt; to discard changes &lt;span class="k"&gt;in &lt;/span&gt;working directory&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;        modified:   Packages/manifest.json
        modified:   Packages/packages-lock.json
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unity stores the list of packages you use in &lt;code&gt;Packages/manifest.json&lt;/code&gt;. Here, you'll find all of the built-in Unity packages and your newly added package. You can also see the same list of packages in the &lt;code&gt;Package Manager&lt;/code&gt; window in Unity.&lt;/p&gt;

&lt;p&gt;Also pay attention to the &lt;code&gt;Packages/packages-lock.json&lt;/code&gt; file. This is where Unity stores the specific &lt;strong&gt;version&lt;/strong&gt; of each package that your project uses. By tracking this file in Git, you can ensure your whole team is using the same version of every package and its dependencies.&lt;/p&gt;

&lt;p&gt;Since you added your package from a Git URL, the &lt;code&gt;packages-lock.json&lt;/code&gt; file will store the latest commit ID that Unity found when you added the package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"com.virtualmaker.servicelocator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/virtual-maker-net/com.virtualmaker.sharedcode.git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"depth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ba0619eac92235362b45e791b70ac2637ce73900"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The "hash" field is the commit ID. You can edit the &lt;code&gt;packages-lock.json&lt;/code&gt; file directly to update the commit ID, but it's usually better practice to specify the commit ID in the &lt;code&gt;manifest.json&lt;/code&gt; file. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"com.{org}.{package-name}"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/{org}/{repo}.git#0fde24173316721063c9b46ec35fa8cf261e6531"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A better way is to use &lt;a href="https://git-scm.com/book/en/v2/Git-Basics-Tagging" rel="noopener noreferrer"&gt;git tags&lt;/a&gt; to version your package and pass in the specific tag name.&lt;/p&gt;

&lt;p&gt;First, create and push a tag in your package's repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git tag 1.2.0
&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git push &lt;span class="nt"&gt;--tags&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, use it in &lt;code&gt;manifest.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"com.{org}.{package-name}"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/{org}/{repo}.git#1.2.0"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Git on a Team: Branching Strategies for Unity
&lt;/h2&gt;

&lt;p&gt;The way we've discussed branching so far is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create &lt;code&gt;dev/&lt;/code&gt; branches for features and bug fixes.&lt;/li&gt;
&lt;li&gt;Create pull requests from &lt;code&gt;dev/&lt;/code&gt; branches to the &lt;code&gt;main&lt;/code&gt; branch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flueeuvc31j1oep8hncxh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flueeuvc31j1oep8hncxh.png" alt="Git Branching Strategy" width="800" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;During development, this is usually an excellent strategy because it is simple to follow and keeps things organized. Before releasing your game, we recommend using this strategy exclusively.&lt;/p&gt;

&lt;p&gt;You may be tempted to create branches for separate teams to work in. Avoid it if you can. Each team will be working on and testing a different version of the product, doubling your QA efforts. Merging those team branches also becomes a big chore and often results in regressions. You can end up with developers whose entire job is to merge branches together.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2eq2logndsfmw3ijnaus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2eq2logndsfmw3ijnaus.png" alt="Git Bad Branching Strategy" width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You may be also tempted to create a new branch for a demo. Resist the temptation -- your demo branch will quickly drift from your main branch, and it will become impossible to merge back together. If you absolutely must create a demo branch, be prepared to throw away all work targeting that branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Release Tags
&lt;/h3&gt;

&lt;p&gt;At some point, you'll be ready to release your game. Congratulations!&lt;/p&gt;

&lt;p&gt;As soon as you release, there become two versions of your game:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The released version of your game&lt;/li&gt;
&lt;li&gt;The development version of your game, where you're making bug fixes and feature updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will want to keep track of the released version of your game for multiple reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You may want to debug an issue found only in the release version of your game.&lt;/li&gt;
&lt;li&gt;You may need to compare the release and development versions to diagnose regressions.&lt;/li&gt;
&lt;li&gt;In rare cases, you'll need to apply an immediate hotfix to your game. The issue is so critical that you won't be able to wait for the development version to stabilize for release.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our strategy at Virtual Maker is to follow &lt;a href="https://trunkbaseddevelopment.com/" rel="noopener noreferrer"&gt;trunk-based development&lt;/a&gt;. Essentially, always keep your developers creating pull requests to the same branch (&lt;code&gt;main&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9fu17g40rajh2xjdo84.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9fu17g40rajh2xjdo84.png" alt="Git Release Taggings" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each time you make a release, create a new &lt;code&gt;version&lt;/code&gt; tag. This tag largely exists as a reference by your team. Developers can use the version tag to test bug reports and compare changes with the &lt;code&gt;main&lt;/code&gt; branch. The ONLY time you should make a new branch from a release tag is for an emergency hotfix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated Unity Builds and CI/CD with GitHub Actions
&lt;/h2&gt;

&lt;p&gt;One of the best things about GitHub is the robust CI/CD infrastructure called &lt;strong&gt;GitHub Actions&lt;/strong&gt;. Using actions, you can automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build your Unity project for multiple platforms.&lt;/li&gt;
&lt;li&gt;Run unit tests&lt;/li&gt;
&lt;li&gt;Perform asset validation&lt;/li&gt;
&lt;li&gt;Perform style checking&lt;/li&gt;
&lt;li&gt;Deploy your game to one ore more app stores.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Better yet, you can configure your actions to run on &lt;em&gt;each pull request&lt;/em&gt;, forcing developers to fix issues before they even make it into the main build.&lt;/p&gt;

&lt;p&gt;Unfortunately, setting up GitHub actions for Unity is complicated, to say the least. You need to deal with installing Unity, activating a Unity license, adding build extensions to the Unity editor, and more. Build Unity also takes a lot of compute time. Not only does that cost you money, but it makes your team's pull requests drag on as developers wait for their changes to build.&lt;/p&gt;

&lt;p&gt;But hope is not lost! Here at Virtual Maker, we've developed a solution...&lt;/p&gt;

&lt;h2&gt;
  
  
  Buildalon: The one-stop solution for Unity automation.
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fidcp6gffbx5691sk8f8i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fidcp6gffbx5691sk8f8i.png" alt="Buildalon Logo" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;Learn more about Unity automation at our website at &lt;a href="https://www.buildalon.com&amp;amp;utm_source=devto" rel="noopener noreferrer"&gt;buildalon.com&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More by Virtual Maker
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.virtualmaker.dev/blog/git-and-unity-a-comprehensive-guide-to-version-control-for-game-devs&amp;amp;utm_source=devto" rel="noopener noreferrer"&gt;Original Article&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.virtualmaker.dev&amp;amp;utm_source=devto" rel="noopener noreferrer"&gt;Virtual Maker Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>beginners</category>
      <category>gamedev</category>
      <category>git</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Deploying Unity Builds to Google Play Store with Buildalon</title>
      <dc:creator>Alon Farchy</dc:creator>
      <pubDate>Mon, 16 Feb 2026 16:03:21 +0000</pubDate>
      <link>https://dev.to/virtualmaker/deploying-unity-builds-to-google-play-store-with-buildalon-4d6</link>
      <guid>https://dev.to/virtualmaker/deploying-unity-builds-to-google-play-store-with-buildalon-4d6</guid>
      <description>&lt;p&gt;Getting your game onto the Google Play Store involves a complex dance of signing keys, version codes, and console configurations. Manually building an Android App Bundle (AAB), signing it, and uploading it to the Play Console every time you want to test a change is a recipe for burnout.&lt;/p&gt;

&lt;p&gt;In this guide, we'll walk through how to automate your Unity builds for &lt;strong&gt;Android&lt;/strong&gt; and deploy them directly to the &lt;strong&gt;Google Play Store&lt;/strong&gt; internal testing track using GitHub Actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Setup Overview&lt;/li&gt;
&lt;li&gt;Setting up Google Play Console&lt;/li&gt;
&lt;li&gt;Configuring Google Cloud Access&lt;/li&gt;
&lt;li&gt;Handling the Keystore&lt;/li&gt;
&lt;li&gt;Updating the Build Pipeline&lt;/li&gt;
&lt;li&gt;Deploying to Internal Testing&lt;/li&gt;
&lt;li&gt;Installing the Internal App and Inviting Testers&lt;/li&gt;
&lt;li&gt;About Buildalon&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we start, make sure you have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Google Play Developer Account&lt;/strong&gt;: You need to be able to access the &lt;a href="https://play.google.com/console/signup" rel="noopener noreferrer"&gt;Google Play Console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Working Unity Build&lt;/strong&gt;: You should have a basic automated build pipeline set up. Check out our &lt;a href="https://dev.to/blog/automating-unity-builds-with-github-actions"&gt;GitHub Actions Unity&lt;/a&gt; guide if you're starting from scratch.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setup Overview
&lt;/h2&gt;

&lt;p&gt;Automating Android deployments involves two key security components: &lt;strong&gt;Android Keystores&lt;/strong&gt; (for &lt;strong&gt;Android Signing&lt;/strong&gt;) and &lt;strong&gt;Service Accounts&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;App Registration&lt;/strong&gt;: Create the app placeholder in Google Play Console.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Authentication&lt;/strong&gt;: We'll use &lt;strong&gt;Workload Identity Federation&lt;/strong&gt; to securely authenticate &lt;strong&gt;GitHub Actions Android&lt;/strong&gt; workflows with Google Cloud.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Keystore&lt;/strong&gt;: We need to securely provide our signing keystore to the build server.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Build &amp;amp; Upload&lt;/strong&gt;: We'll update the workflow to build an &lt;strong&gt;Android App Bundle&lt;/strong&gt; (&lt;code&gt;.aab&lt;/code&gt;) and upload it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setting up Google Play Console
&lt;/h2&gt;

&lt;p&gt;First, let's make sure Google is expecting our app.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Log in to &lt;a href="https://play.google.com/console" rel="noopener noreferrer"&gt;Google Play Console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt; Click &lt;strong&gt;Create app&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; Fill in the app details (Name, Default language, Game/App type).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Important:&lt;/strong&gt; Manually build your APK/AAB in Unity and upload it to the &lt;strong&gt;Internal Testing&lt;/strong&gt; track via the web interface. This initializes the track, sets the &lt;strong&gt;Package Name&lt;/strong&gt; permanently, and allows you to answer the initial regulatory questions (Data Safety, etc.).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Configuring Google Cloud Access
&lt;/h2&gt;

&lt;p&gt;To allow GitHub Actions to upload via the API, we need to link our Google Play account to a Google Cloud project. We will use a modern, secure method called &lt;strong&gt;Workload Identity Federation&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Enable the Google Play Android Developer API
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Go to the &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;Google Cloud Console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt; Select or create a project for your game.&lt;/li&gt;
&lt;li&gt; Search for &lt;strong&gt;Google Play Android Developer API&lt;/strong&gt; and enable it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frf65rhxmwdfsr96xvvs5.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frf65rhxmwdfsr96xvvs5.webp" alt="Google Cloud Play API" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Create a Service Account
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; In Google Cloud Console, go to &lt;strong&gt;IAM &amp;amp; Admin&lt;/strong&gt; &amp;gt; &lt;strong&gt;Service Accounts&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; Click &lt;strong&gt;Create Service Account&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fblglgnajhptww213zv1r.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fblglgnajhptww213zv1r.webp" alt="Google Cloud Service Accounts" width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Name it something like &lt;code&gt;unity-cicd-uploader&lt;/code&gt; and click &lt;strong&gt;Create and Continue&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Skip&lt;/strong&gt; the role assignment steps (click &lt;strong&gt;Continue&lt;/strong&gt; then &lt;strong&gt;Done&lt;/strong&gt;). This service account does not need specific Google Cloud permissions, only Google Play permissions.&lt;/li&gt;
&lt;li&gt; Copy the email address (e.g., &lt;code&gt;unity-cicd-uploader@my-project.iam.gserviceaccount.com&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt; Go back to &lt;strong&gt;Google Play Console&lt;/strong&gt; &amp;gt; &lt;strong&gt;Users and permissions&lt;/strong&gt; and invite this email.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqwzorgeptpfpabl10398.webp" alt="Google Play Console Users and Permissions" width="800" height="371"&gt;
&lt;/li&gt;
&lt;li&gt; In the permissions tab, grant it &lt;strong&gt;App Access&lt;/strong&gt; to your game, and under &lt;strong&gt;Account permissions&lt;/strong&gt;, ensure it has &lt;strong&gt;Releases to play store&lt;/strong&gt; &amp;gt; &lt;strong&gt;Release to testing tracks&lt;/strong&gt; (or Admin if you want full control).
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ip3k5fc3luhlejh68bg.webp" alt="Google Play Console Release Options" width="800" height="347"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  3. Setup Workload Identity Federation
&lt;/h3&gt;

&lt;p&gt;Next we need to give have the action runner authenticate with the service account we created. A secure way to do this is to use Workload Identity Federation, which procides an secure authentication scheme between GitHub Actions and Google Cloud.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;In Google Cloud Console, go to &lt;strong&gt;IAM &amp;amp; Admin&lt;/strong&gt; &amp;gt; &lt;strong&gt;Workload Identity Federation&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgyb79hu06xnigrjuq96s.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgyb79hu06xnigrjuq96s.webp" alt="Google Cloud Workload Identity Federation" width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a &lt;strong&gt;Pool&lt;/strong&gt; (e.g., &lt;code&gt;unity-cicd-pool&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a &lt;strong&gt;Provider&lt;/strong&gt; inside that pool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Type&lt;/strong&gt;: OpenID Connect (OIDC)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Provider name&lt;/strong&gt;: &lt;code&gt;unity-cicd-provider&lt;/code&gt; (or any descriptive name - you will reference it in the workflow)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Issuer (URL)&lt;/strong&gt;: &lt;code&gt;https://token.actions.githubusercontent.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Audience&lt;/strong&gt;: &lt;code&gt;sts.amazonaws.com&lt;/code&gt; (Standard default for GitHub Actions).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Provider attributes&lt;/strong&gt; (attribute mapping):&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;google.subject&lt;/code&gt; = &lt;code&gt;assertion.sub&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;attribute.repository&lt;/code&gt; = &lt;code&gt;assertion.repository&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;attribute.ref&lt;/code&gt; = &lt;code&gt;assertion.ref&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;attribute.actor&lt;/code&gt; = &lt;code&gt;assertion.actor&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Condition&lt;/strong&gt;: Use a minimal repo lock like &lt;code&gt;attribute.repository == "owner/repo"&lt;/code&gt;, for example &lt;code&gt;attribute.repository == "virtualmaker-net/proxima"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After creating the provider, go back to the page for your pool and at the top click &lt;strong&gt;Grant Access&lt;/strong&gt;. Select &lt;strong&gt;Grant access using service account impersonation&lt;/strong&gt; and choose your service account created in the previous section. Set the principals to &lt;code&gt;repository&lt;/code&gt; and the value to your &lt;code&gt;owner/repo&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5xi9osgudiku34mgbah.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe5xi9osgudiku34mgbah.webp" alt="Connect Workflow Identity Federation to Service Account" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Handling the Keystore
&lt;/h2&gt;

&lt;p&gt;Your Android Keystore is the identity of your app. If you lose it, you can't update your app. If it's stolen, someone else can update your app.&lt;/p&gt;

&lt;p&gt;For CI/CD, we have two common approaches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Base64 Encode&lt;/strong&gt;: Encode the &lt;code&gt;.keystore&lt;/code&gt; file to a base64 string and store it as a GitHub Secret.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Repo Storage&lt;/strong&gt;: Commit the keystore file to your repository (if private) or encrypted.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Method 1: Base64 Encoding (Recommended)
&lt;/h3&gt;

&lt;p&gt;This method keeps your keystore out of your repository entirely.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Encode&lt;/strong&gt;: Run one of the following commands to turn your file into a text string.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;macOS / Linux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-in&lt;/span&gt; user.keystore &lt;span class="nt"&gt;-out&lt;/span&gt; user.keystore.base64.txt
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;strong&gt;Windows (PowerShell):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;ToBase64String&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;IO.File&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;ReadAllBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user.keystore"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Set-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user.keystore.base64.txt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Store&lt;/strong&gt;: Copy the contents of the text file and add it as a secret named &lt;code&gt;ANDROID_KEYSTORE_BASE64&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Method 2: Repo Storage
&lt;/h3&gt;

&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; commit the keystore file to your repository (e.g. in &lt;code&gt;Assets/Keys/user.keystore&lt;/code&gt;), but this is risky. If your repo is ever compromised or made public, your signing key is exposed. If you choose this route, ensure your &lt;code&gt;.gitignore&lt;/code&gt; does not ignore &lt;code&gt;.keystore&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;Regardless of the method, go to your GitHub Repository &amp;gt; &lt;strong&gt;Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;Secrets and variables&lt;/strong&gt; &amp;gt; &lt;strong&gt;Actions&lt;/strong&gt; and add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ANDROID_KEYSTORE_PASSWORD&lt;/code&gt;: The password for your keystore.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ANDROID_KEYALIAS_PASSWORD&lt;/code&gt;: The password for your key alias (often the same as the keystore, but not always).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Updating the Build Pipeline
&lt;/h2&gt;

&lt;p&gt;Now let's update our workflow to include the Android build, Google Cloud authentication, and Play Store upload steps. We'll be using the following actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/google-github-actions/auth" rel="noopener noreferrer"&gt;google-github-actions/auth&lt;/a&gt; - Authenticates with Google Cloud via Workload Identity Federation.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/RageAgainstThePixel/upload-google-play-console" rel="noopener noreferrer"&gt;RageAgainstThePixel/upload-google-play-console&lt;/a&gt; - Uploads an AAB to the Google Play Console.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create or update your &lt;code&gt;.github/workflows/unity-build.yml&lt;/code&gt; file with the following configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Google Play&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="c1"&gt;# Required for Workload Identity Federation&lt;/span&gt;
&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build &amp;amp; Deploy to Google Play&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon-windows&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Checkout the repository&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v6&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;lfs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

      &lt;span class="c1"&gt;# Setup Unity with Android build support&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/unity-setup@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;build-targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Android&lt;/span&gt;

      &lt;span class="c1"&gt;# Activate Unity License&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/activate-unity-license@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;license&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Personal'&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.UNITY_EMAIL }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.UNITY_PASSWORD }}&lt;/span&gt;

      &lt;span class="c1"&gt;# Add the Buildalon command line package to your Unity project&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Add Build Pipeline Package&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.UNITY_PROJECT_PATH }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm install -g openupm-cli&lt;/span&gt;
          &lt;span class="s"&gt;openupm add com.virtualmaker.buildalon&lt;/span&gt;

      &lt;span class="c1"&gt;# Decode base64 keystore from GitHub Secrets&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Decode Keystore&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | openssl base64 -d -out ${{ github.workspace }}/user.keystore&lt;/span&gt;

      &lt;span class="c1"&gt;# Build the Android App Bundle (.aab)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/unity-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Android AAB&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;build-target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Android&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
            &lt;span class="s"&gt;-quit -batchmode&lt;/span&gt;
            &lt;span class="s"&gt;-executeMethod Buildalon.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild&lt;/span&gt;
            &lt;span class="s"&gt;-keystorePath ${{ github.workspace }}/user.keystore&lt;/span&gt;
            &lt;span class="s"&gt;-keystorePass "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}"&lt;/span&gt;
            &lt;span class="s"&gt;-keyaliasName "my-key-alias"&lt;/span&gt;
            &lt;span class="s"&gt;-keyaliasPass "${{ secrets.ANDROID_KEYALIAS_PASSWORD }}"&lt;/span&gt;
            &lt;span class="s"&gt;-appBundle&lt;/span&gt;
          &lt;span class="na"&gt;log-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Android-Build&lt;/span&gt;

      &lt;span class="c1"&gt;# Authenticate with Google Cloud using Workload Identity Federation&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;google-github-actions/auth@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# The service account email from Google Cloud&lt;/span&gt;
          &lt;span class="na"&gt;service_account&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unity-cicd-uploader@my-project.iam.gserviceaccount.com&lt;/span&gt;
          &lt;span class="c1"&gt;# The full resource name of the Workload Identity Provider&lt;/span&gt;
          &lt;span class="na"&gt;workload_identity_provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;projects/123456789/locations/global/workloadIdentityPools/unity-cicd-pool/providers/unity-cicd-provider&lt;/span&gt;

      &lt;span class="c1"&gt;# Java is required by the Play Console upload tool&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-java@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;distribution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;temurin&lt;/span&gt;
          &lt;span class="na"&gt;java-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;21&lt;/span&gt;

      &lt;span class="c1"&gt;# Upload the AAB to the Google Play Internal Testing track&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RageAgainstThePixel/upload-google-play-console@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;

          &lt;span class="c1"&gt;# Directory containing the .aab file&lt;/span&gt;
          &lt;span class="na"&gt;release-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.UNITY_PROJECT_PATH }}/Builds/Android&lt;/span&gt;

          &lt;span class="c1"&gt;# The track to upload to (internal, alpha, beta, production)&lt;/span&gt;
          &lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;internal&lt;/span&gt;

          &lt;span class="c1"&gt;# Draft status lets you review before rolling out&lt;/span&gt;
          &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;draft&lt;/span&gt;

          &lt;span class="c1"&gt;# Optional release notes&lt;/span&gt;
          &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;{&lt;/span&gt;
              &lt;span class="s"&gt;"releaseNotes": {&lt;/span&gt;
                &lt;span class="s"&gt;"language": "en-US",&lt;/span&gt;
                &lt;span class="s"&gt;"text": "${{ env.RELEASE_NOTES }}"&lt;/span&gt;
              &lt;span class="s"&gt;}&lt;/span&gt;
            &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Steps Explained
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decode Keystore&lt;/strong&gt;: Converts the base64-encoded keystore secret back into a file for signing. If you checked in your keystore, you can skip this step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Android AAB&lt;/strong&gt;: Runs the Unity build with keystore credentials and &lt;code&gt;-appBundle&lt;/code&gt; to produce an &lt;code&gt;.aab&lt;/code&gt; instead of an &lt;code&gt;.apk&lt;/code&gt;. Replace &lt;code&gt;my-key-alias&lt;/code&gt; with the alias you chose when creating your keystore in Unity (&lt;strong&gt;Project Settings &amp;gt; Player &amp;gt; Publishing Settings&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authenticate with Google Cloud&lt;/strong&gt;: Uses Workload Identity Federation to get a short-lived token. You need to replace two values:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;service_account&lt;/code&gt;: The email address from &lt;strong&gt;step 2.5&lt;/strong&gt; above (visible on the Service Account details page in Google Cloud Console under &lt;strong&gt;IAM &amp;amp; Admin &amp;gt; Service Accounts&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;workload_identity_provider&lt;/code&gt;: The full resource name of the provider. You can find this in &lt;strong&gt;IAM &amp;amp; Admin &amp;gt; Workload Identity Federation&lt;/strong&gt;, click your pool, then click your provider, and copy the &lt;strong&gt;Default audience&lt;/strong&gt; or &lt;strong&gt;Resource name&lt;/strong&gt; value. It follows the format &lt;code&gt;projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID&lt;/code&gt;. Your &lt;strong&gt;Project Number&lt;/strong&gt; (not Project ID) is on the Google Cloud Console &lt;strong&gt;Dashboard&lt;/strong&gt; or &lt;strong&gt;Project Settings&lt;/strong&gt; page.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Upload to Play Console&lt;/strong&gt;: Pushes the &lt;code&gt;.aab&lt;/code&gt; to the &lt;strong&gt;internal&lt;/strong&gt; testing track as a draft, so your team can test immediately.&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: We are deploying to the &lt;code&gt;internal&lt;/code&gt; track. This is usually the best place for automated builds so your team can test them immediately.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Deploying to Internal Testing
&lt;/h2&gt;

&lt;p&gt;Once you push these changes, your workflow should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Build the Android App Bundle (&lt;code&gt;.aab&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt; Authenticate with Google Cloud.&lt;/li&gt;
&lt;li&gt; Upload the &lt;code&gt;.aab&lt;/code&gt; to the Internal Testing track on the Play Console.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When the upload finishes, your testers in the Internal Testing email list will receive an update (or see it in the Play Store if they have it installed).&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Gotchas
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Android Version Code&lt;/strong&gt;: Google Play requires a unique integer &lt;code&gt;versionCode&lt;/code&gt; for every single upload. Ensure your build script increments this on every build (e.g., using &lt;code&gt;${{ github.run_number }}&lt;/code&gt;) in your Unity Android Build settings or build script.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Android Build Number&lt;/strong&gt;: Similar to &lt;code&gt;versionCode&lt;/code&gt;, the user-visible &lt;code&gt;bundleVersion&lt;/code&gt; (or &lt;strong&gt;Unity App Version&lt;/strong&gt;) should be updated to help testers identify which build they are on.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Play App Signing&lt;/strong&gt;: When you create your app in the console, you likely opted into &lt;strong&gt;Play App Signing&lt;/strong&gt;. This means Google manages the key used to sign APKs delivered to users. The keystore we configured above is your &lt;strong&gt;Upload Key&lt;/strong&gt;. If you lose it, you can contact Google support to reset it, whereas the final signing key is safely stored by Google.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;API Delays&lt;/strong&gt;: Sometimes the Google Play API takes a few minutes to process a new build.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Permissions&lt;/strong&gt;: If you get a 403 error, double-check that the Service Account email is added to the Google Play Console &lt;strong&gt;Users&lt;/strong&gt; with the correct permissions &lt;em&gt;and&lt;/em&gt; linked to the Google Cloud project.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installing the Internal App and Inviting Testers
&lt;/h2&gt;

&lt;p&gt;After your first successful upload to the &lt;code&gt;internal&lt;/code&gt; track, you need to explicitly invite testers and share the opt-in link.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invite Internal Testers
&lt;/h3&gt;

&lt;p&gt;In the Google Play Console:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your app and go to &lt;strong&gt;Testing &amp;gt; Internal testing&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Open the &lt;strong&gt;Testers&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Create an email list (or select an existing one) and add tester emails.&lt;/li&gt;
&lt;li&gt;Save the list and make sure it is attached to your internal test track.&lt;/li&gt;
&lt;li&gt;Copy the &lt;strong&gt;Opt-in URL&lt;/strong&gt; from the internal testing page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Share that opt-in URL with your testers. They must accept the invite using the same Google account you added to the tester list.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Testers Install the Internal Version
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Tester opens the opt-in URL and taps &lt;strong&gt;Become a tester&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Tester taps the Play Store link shown on that page.&lt;/li&gt;
&lt;li&gt;Tester installs the app (or updates it if already installed).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the app is already installed from another source (for example, a local APK), the tester may need to uninstall it first if the signing key or package signature does not match the Play build.&lt;/p&gt;

&lt;p&gt;For later releases, once users are opted in, they usually receive updates through the Play Store automatically (or via manual update if auto-update is disabled).&lt;/p&gt;

&lt;h2&gt;
  
  
  About Buildalon
&lt;/h2&gt;

&lt;p&gt;Buildalon provides verified GitHub Actions and dedicated build infrastructure to streamline Unity development. &lt;a href="https://www.buildalon.com?utm_source=devto" rel="noopener noreferrer"&gt;Register for Buildalon&lt;/a&gt; to get support with your Unity build automation needs.&lt;/p&gt;

</description>
      <category>unity3d</category>
      <category>playstore</category>
      <category>gamedev</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Automating Unity Builds for Meta Quest</title>
      <dc:creator>Alon Farchy</dc:creator>
      <pubDate>Mon, 26 Jan 2026 16:33:22 +0000</pubDate>
      <link>https://dev.to/virtualmaker/automating-unity-builds-for-meta-quest-2lpf</link>
      <guid>https://dev.to/virtualmaker/automating-unity-builds-for-meta-quest-2lpf</guid>
      <description>&lt;p&gt;Developing for VR on the Meta Quest involves a lot of iteration. Since many device capabilities only work on the headset, you'll find yourself making frequent builds to sideload. Distributing those builds to play-testers manually can consume much of your time. As your app and team grow, you start wishing this process was automated.&lt;/p&gt;

&lt;p&gt;In this guide, we'll set up a completely automated pipeline that takes your code from GitHub, builds it in the cloud, and delivers it directly to a release channel on the Meta Quest Store.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: This guide assumes you are already familiar with setting up basic Unity builds with GitHub Actions. If you're just starting out, check out my guide on &lt;a href="https://dev.to/virtualmaker/automating-unity-builds-with-github-actions-1inf"&gt;Automating Unity Builds with GitHub Actions&lt;/a&gt; first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By the end of this tutorial, you will have a GitHub Actions workflow that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Triggers whenever you push to your &lt;code&gt;main&lt;/code&gt; branch.&lt;/li&gt;
&lt;li&gt;Builds your Unity project for Android (Meta Quest).&lt;/li&gt;
&lt;li&gt;Uploads the resulting APK directly to a specific Release Channel (e.g., Alpha) on the Meta Quest Store.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Setting up the Meta Developer Portal&lt;/li&gt;
&lt;li&gt;Configure GitHub Secrets for Meta&lt;/li&gt;
&lt;li&gt;Configure GitHub Secrets for Keystores and Signing&lt;/li&gt;
&lt;li&gt;Update Your Build Workflow&lt;/li&gt;
&lt;li&gt;Installing the Build on Your Headset&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setting up the Meta Developer Portal
&lt;/h2&gt;

&lt;p&gt;Before we touch any YAML configuration, we need to prepare our application on the &lt;a href="https://developer.oculus.com/manage/" rel="noopener noreferrer"&gt;Meta Developer Dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create Your App
&lt;/h3&gt;

&lt;p&gt;If you haven't created an app yet:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log in to the Developer Dashboard.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2cqi0rruyxxd2nq6r3x6.png" alt="Meta Developer Dashboard - Create New App" width="800" height="400"&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create New App&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Give it a name and click &lt;strong&gt;Create&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Get Your Credentials
&lt;/h3&gt;

&lt;p&gt;To allow GitHub Actions to upload builds on your behalf, you need two pieces of information: the &lt;strong&gt;App ID&lt;/strong&gt; and the &lt;strong&gt;App Secret&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to the &lt;strong&gt;Development&lt;/strong&gt; &amp;gt; &lt;strong&gt;API&lt;/strong&gt; tab in your app's dashboard.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9b5nk3rp5taklpbuxx6.png" alt="Meta Developer Dashboard - API" width="800" height="496"&gt;
&lt;/li&gt;
&lt;li&gt;Copy the &lt;strong&gt;App ID&lt;/strong&gt;. You'll need this for your workflow file.&lt;/li&gt;
&lt;li&gt;Copy the &lt;strong&gt;App Secret&lt;/strong&gt;. &lt;strong&gt;Do not commit this to your repository!&lt;/strong&gt; We will save this as a secret in GitHub.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;TIP: On some accounts, you may need to verify your account or complete the Data Use Checkup before you can access full API functionality. If your upload fails later with permission errors, double-check that your developer account status is in good standing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxxeps8jr5eq90xfzvkcc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxxeps8jr5eq90xfzvkcc.png" alt="Meta Developer Dashboard - Data Use Checkup Notice" width="800" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Release Channel
&lt;/h3&gt;

&lt;p&gt;Release channels let you to distribute different versions of your app to different users. For example, you might have a "Live" channel for the public and an "Alpha" channel for internal testing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Distribution&lt;/strong&gt; &amp;gt; &lt;strong&gt;Release Channels&lt;/strong&gt;.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjphnhjt8corcpp9xoeys.png" alt="Meta Developer Dashboard - Release Channels" width="800" height="446"&gt;
&lt;/li&gt;
&lt;li&gt;You likely already have &lt;code&gt;LIVE&lt;/code&gt;, &lt;code&gt;ALPHA&lt;/code&gt;, &lt;code&gt;BETA&lt;/code&gt;, and &lt;code&gt;RC&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You can use one of these (e.g., &lt;code&gt;ALPHA&lt;/code&gt;) or create a new one specifically for automated builds, like &lt;code&gt;NIGHTLY&lt;/code&gt; or &lt;code&gt;ACTIONS&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Note the exact name of the channel you want to target.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Configure GitHub Secrets for Meta
&lt;/h2&gt;

&lt;p&gt;We never want to hardcode our App Secret into our workflow files where anyone could see them. Instead, we use GitHub Secrets.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to your GitHub repository.&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;Secrets and variables&lt;/strong&gt; &amp;gt; &lt;strong&gt;Actions&lt;/strong&gt;.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F83vu3ptldh7bxz4qgwcr.png" alt="GitHub - Action Secrets" width="800" height="629"&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;New repository secret&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Create a secret named &lt;code&gt;META_APP_SECRET&lt;/code&gt; and paste the value you copied from the Meta Developer Dashboard.&lt;/li&gt;
&lt;li&gt;(Optional) You can also store your App ID as &lt;code&gt;META_APP_ID&lt;/code&gt;, though it is generally considered public information.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Configure GitHub Secrets for Keystores and Signing
&lt;/h2&gt;

&lt;p&gt;One critical detail for Android builds is code signing. The Meta Quest Store requires your APKs to be signed with a production keystore, even for the Alpha channel. If you try to upload a development build signed with a default debug keystore, the action will successfully build, but the Meta server will reject the upload during validation.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a Keystore?
&lt;/h3&gt;

&lt;p&gt;A keystore is a binary file that acts as a digital certificate for your app. It proves that the update comes from the same developer as the original app. If you lose your keystore, you lose the ability to update your app on the store—forever. So keep it safe!&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Keystore
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open your Unity project.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Project Settings &amp;gt; Player &amp;gt; Publishing Settings&lt;/strong&gt;.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0v7vsascru9r2jbn757r.png" alt="Unity - Keystore Management" width="800" height="446"&gt;
&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Keystore Manager&lt;/strong&gt;, create a new keystore.&lt;/li&gt;
&lt;li&gt;Choose a location inside your project folder.&lt;/li&gt;
&lt;li&gt;Set a &lt;strong&gt;Keystore Password&lt;/strong&gt;, create a new Key Alias, and set a &lt;strong&gt;Key Password&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Go to your GitHub repository &lt;strong&gt;Settings &amp;gt; Secrets and variables &amp;gt; Actions&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Create two new secrets for your passwords: &lt;code&gt;ANDROID_KEYSTORE_PASS&lt;/code&gt; and &lt;code&gt;ANDROID_KEYALIAS_PASS&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;Buildalon&lt;/code&gt; build script we are using is designed to pick up these secrets from the command line arguments and apply them to the keystore found in your project settings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keystore Security
&lt;/h3&gt;

&lt;p&gt;For better security, you can encode your keystore in base64 and store it as a GitHub Secret instead of committing the file.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Encode the keystore in base64.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On macOS/Linux:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-in&lt;/span&gt; user.keystore &lt;span class="nt"&gt;-out&lt;/span&gt; user.keystore.base64.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows (PowerShell):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;ToBase64String&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;IO.File&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;ReadAllBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user.keystore"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Set-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;user.keystore.base64.txt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Store:&lt;/strong&gt; Add it as a secret named &lt;code&gt;ANDROID_KEYSTORE_BASE64&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decode:&lt;/strong&gt; Add a step in your workflow (below) to decode it back to a file using &lt;code&gt;openssl base64 -d&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build:&lt;/strong&gt; Pass &lt;code&gt;-keystorePath user.keystore&lt;/code&gt; in your &lt;code&gt;unity-action&lt;/code&gt; build arguments.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Update Your Build Workflow
&lt;/h2&gt;

&lt;p&gt;Now, let's look at the workflow file. We'll build upon the standard Buildalon Android workflow but add a crucial final step: the upload.&lt;/p&gt;

&lt;p&gt;We'll be using the following actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/buildalon/setup-ovr-platform-util" rel="noopener noreferrer"&gt;buildalon/setup-ovr-platform-util&lt;/a&gt; - Installs OVR Platform Tool.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/buildalon/upload-meta-quest-build" rel="noopener noreferrer"&gt;buildalon/upload-meta-quest-build&lt;/a&gt; - Invokes OVR Platform Tool to upload your build.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create or update your &lt;code&gt;.github/workflows/quest-build.yml&lt;/code&gt; file with the following configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Quest&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build &amp;amp; Deploy to Quest&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon-windows&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Checkout the repository&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;lfs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

      &lt;span class="c1"&gt;# Setup Unity&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/unity-setup@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;build-targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Android&lt;/span&gt;

      &lt;span class="c1"&gt;# Activate Unity License&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/activate-unity-license@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;license&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Personal'&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.UNITY_EMAIL }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.UNITY_PASSWORD }}&lt;/span&gt;

      &lt;span class="c1"&gt;# Temporarily add the Buildalon command line package to your Unity project&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Add Build Pipeline Package&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.UNITY_PROJECT_PATH }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm install -g openupm-cli&lt;/span&gt;
          &lt;span class="s"&gt;openupm add com.virtualmaker.buildalon&lt;/span&gt;

      &lt;span class="c1"&gt;# Optional: Decode base64 keystore from GitHub Secrets&lt;/span&gt;
      &lt;span class="c1"&gt;# - name: Decode Keystore&lt;/span&gt;
      &lt;span class="c1"&gt;#   run: |&lt;/span&gt;
      &lt;span class="c1"&gt;#     echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | openssl base64 -d -out ${{ env.UNITY_PROJECT_PATH }}/user.keystore&lt;/span&gt;

      &lt;span class="c1"&gt;# Build the Project&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/unity-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Android APK&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;build-target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Android&lt;/span&gt;
          &lt;span class="c1"&gt;# Optional: add a previous step to decode a base64 keystore, and add: -keystorePath ${{ env.UNITY_PROJECT_PATH }}/user.keystore&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
            &lt;span class="s"&gt;-quit -batchmode&lt;/span&gt;
            &lt;span class="s"&gt;-executeMethod Buildalon.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild&lt;/span&gt;
            &lt;span class="s"&gt;-keyaliasPass "${{ secrets.ANDROID_KEYALIAS_PASS }}"&lt;/span&gt;
            &lt;span class="s"&gt;-keystorePass "${{ secrets.ANDROID_KEYSTORE_PASS }}"&lt;/span&gt;
          &lt;span class="na"&gt;log-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Quest-Build&lt;/span&gt;

      &lt;span class="c1"&gt;# Setup OVR Platform Util&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/setup-ovr-platform-util@v1&lt;/span&gt;

      &lt;span class="c1"&gt;# Upload to Meta Quest Store&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/upload-meta-quest-build@v1&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload to Meta Quest&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# Your App ID (can be hardcoded or a secret)&lt;/span&gt;
          &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;123456789012345'&lt;/span&gt;

          &lt;span class="c1"&gt;# Your App Secret&lt;/span&gt;
          &lt;span class="na"&gt;appSecret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.META_APP_SECRET }}&lt;/span&gt;

          &lt;span class="c1"&gt;# The path to your built APK.&lt;/span&gt;
          &lt;span class="na"&gt;buildDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.UNITY_PROJECT_PATH }}/Builds/Android&lt;/span&gt;

          &lt;span class="c1"&gt;# The channel to upload to (ALPHA, BETA, LIVE, etc.)&lt;/span&gt;
          &lt;span class="na"&gt;releaseChannel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ALPHA'&lt;/span&gt;

          &lt;span class="c1"&gt;# Optional, uploads the build even if there are validation errors.&lt;/span&gt;
          &lt;span class="na"&gt;force&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Key Changes:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Set the build platform to Android.&lt;/li&gt;
&lt;li&gt;Pass the keystore credentials to the build step.&lt;/li&gt;
&lt;li&gt;Install the OVR Platform Tools&lt;/li&gt;
&lt;li&gt;Upload the built APK.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The magic happens in the &lt;code&gt;buildalon/upload-meta-quest-build&lt;/code&gt; step. This action wraps the Oculus Platform Command Line Utility, handling the authentication and upload process for you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;appId&lt;/code&gt;: Identifies which application specifically you are updating.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;appSecret&lt;/code&gt;: Authenticates the upload request.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;buildDir&lt;/code&gt;: Tells the uploader where to find your build. Note that &lt;code&gt;StartCommandLineBuild&lt;/code&gt; by default outputs builds to the &lt;code&gt;Builds/{Platform}&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;releaseChannel&lt;/code&gt;: Ensures the build lands in the right place so your testers get the update without affecting live users.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installing the Build on Your Headset
&lt;/h2&gt;

&lt;p&gt;Once your build succeeds, it will be uploaded to the &lt;strong&gt;ALPHA&lt;/strong&gt; channel.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Invite Users:&lt;/strong&gt; In the Meta Developer Dashboard, go to &lt;strong&gt;Distribution &amp;gt; Users&lt;/strong&gt;. Add yourself and your teammates to the ALPHA channel using their Meta account emails.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Accept the Invite:&lt;/strong&gt; Each person will receive an email invitation to test the app. They must accept this invite.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Find the App:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  Put on your Quest headset.&lt;/li&gt;
&lt;li&gt;  Navigate to your &lt;strong&gt;App Library&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  Look for the app in the &lt;strong&gt;All&lt;/strong&gt; or &lt;strong&gt;Not Installed&lt;/strong&gt; category. It may also appear in the &lt;strong&gt;My Preview Apps&lt;/strong&gt; section of the Store.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Install&lt;/strong&gt; the application.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Confirm the Channel:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  Navigate to the app details in your Library or on the Store page.&lt;/li&gt;
&lt;li&gt;  Click the version number to verify or switch the release channel to "ALPHA" (or your chosen channel).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once installed, future updates from your GitHub Actions pipeline will appear as regular app updates on the headset!&lt;/p&gt;

&lt;p&gt;Mission complete!&lt;/p&gt;

&lt;h2&gt;
  
  
  About Buildalon
&lt;/h2&gt;

&lt;p&gt;Buildalon provides verified GitHub Actions and dedicated build infrastructure to streamline Unity development. &lt;a href="https://www.buildalon.com?utm_source=devto" rel="noopener noreferrer"&gt;Register for Buildalon&lt;/a&gt; to get support with your Unity build automation needs.&lt;/p&gt;

</description>
      <category>metaquest</category>
      <category>unity3d</category>
      <category>githubactions</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Automating Unity Builds to iOS, macOS, and visionOS</title>
      <dc:creator>Alon Farchy</dc:creator>
      <pubDate>Fri, 16 Jan 2026 12:35:30 +0000</pubDate>
      <link>https://dev.to/virtualmaker/automating-unity-builds-to-ios-macos-and-visionos-14km</link>
      <guid>https://dev.to/virtualmaker/automating-unity-builds-to-ios-macos-and-visionos-14km</guid>
      <description>&lt;h1&gt;
  
  
  Automating Unity Builds to iOS, macOS, and visionOS
&lt;/h1&gt;

&lt;p&gt;When deploying a Unity game to Apple platforms, you can easily get bogged down with provisioning profiles, signing certificates, and Xcode configuration. It's a time complex consuming process, and manually repeating it for every build is going to eat up your time and slow your team down.&lt;/p&gt;

&lt;p&gt;In this guide, we'll walk through how to use &lt;strong&gt;GitHub Actions&lt;/strong&gt; and &lt;strong&gt;Buildalon&lt;/strong&gt; to automate your builds for &lt;strong&gt;iOS&lt;/strong&gt;, &lt;strong&gt;macOS&lt;/strong&gt;, and &lt;strong&gt;visionOS&lt;/strong&gt;, covering everything from setting up your Apple Developer account to creating a workflow that deploys straight to your testers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Setup Overview&lt;/li&gt;
&lt;li&gt;Setting up App Store Connect&lt;/li&gt;
&lt;li&gt;Generating a Build for Sideloading&lt;/li&gt;
&lt;li&gt;Deploying to TestFlight&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we dive into the automation, there are a few things you'll need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Apple Developer Account&lt;/strong&gt;: You need an active membership to deploy to Apple devices or the App Store. &lt;a href="https://developer.apple.com/programs/enroll/" rel="noopener noreferrer"&gt;Enroll in the Apple Developer Program&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Working Unity Build&lt;/strong&gt;: You should already have a basic automated build pipeline. If not, follow our &lt;a href="https://dev.to/virtualmaker/automating-unity-builds-with-github-actions-1inf"&gt;Automating Unity Builds with GitHub Actions&lt;/a&gt; guide first.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setup Overview
&lt;/h2&gt;

&lt;p&gt;Automating Xcode builds involves connecting several services with your GitHub workflow. At a high level we need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Registration&lt;/strong&gt;: We need to register the App ID and create an app record in App Store Connect so Apple knows what we are building.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Authentication&lt;/strong&gt;: We will create an &lt;strong&gt;App Store Connect API Key&lt;/strong&gt; to allow GitHub Actions to act on our behalf without 2FA issues.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Update the Build Pipeline&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Unity Build&lt;/strong&gt;: Unity runs in batch mode to export an Xcode project. It's important to set the &lt;code&gt;bundleVersion&lt;/code&gt; and &lt;code&gt;buildNumber&lt;/code&gt; here to track our releases.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Xcode Build&lt;/strong&gt;: We use &lt;code&gt;xcodebuild&lt;/code&gt; (wrapped in a GitHub Action) to compile, sign, and archive the app into an &lt;code&gt;.ipa&lt;/code&gt; (or &lt;code&gt;.app&lt;/code&gt;/&lt;code&gt;.pkg&lt;/code&gt; for macOS) file.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Distribution&lt;/strong&gt;: Finally, we either upload the build to TestFlight for beta testing or keep it as an artifact for manual sideloading.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key to making this easy is the &lt;a href="https://github.com/buildalon/unity-xcode-builder" rel="noopener noreferrer"&gt;buildalon/unity-xcode-builder&lt;/a&gt; action, which handles the complex Xcode build and upload steps for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the App on Apple's Portal
&lt;/h2&gt;

&lt;p&gt;Before automation can take over, we need to do a little manual setup to tell Apple about our app. This is a one-time process for each new project.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Log in to &lt;a href="https://appstoreconnect.apple.com/" rel="noopener noreferrer"&gt;App Store Connect&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt; Go to &lt;strong&gt;My Apps&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; Click the blue &lt;strong&gt;(+)&lt;/strong&gt; button &amp;gt; &lt;strong&gt;New App&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; Select your platform (&lt;strong&gt;iOS&lt;/strong&gt;, &lt;strong&gt;macOS&lt;/strong&gt;, or &lt;strong&gt;visionOS&lt;/strong&gt;) and give it a &lt;strong&gt;Name&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; In the &lt;strong&gt;Bundle ID&lt;/strong&gt; section, you can choose an existing identifier or create a new one. Ensure this matches the Bundle Identifier in your Unity project (e.g., &lt;code&gt;com.mycompany.mygame&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt; Enter a &lt;strong&gt;SKU&lt;/strong&gt; (a unique tracking ID for your own use) and click &lt;strong&gt;Create&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setting up App Store Connect Authentication
&lt;/h2&gt;

&lt;p&gt;For GitHub Actions to upload builds on your behalf, it needs a secure way to authenticate with Apple. The best way to do this is with an &lt;strong&gt;App Store Connect API Key&lt;/strong&gt;, which avoids the headaches of 2FA in CI environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Generate an API Key
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; Head over to &lt;a href="https://appstoreconnect.apple.com/" rel="noopener noreferrer"&gt;App Store Connect&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt; Go to &lt;strong&gt;Users and Access&lt;/strong&gt; &amp;gt; &lt;strong&gt;Integrations&lt;/strong&gt; &amp;gt; &lt;strong&gt;Team Keys&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; Click the &lt;strong&gt;+&lt;/strong&gt; button to generate a new key.&lt;/li&gt;
&lt;li&gt; Give it a name like "GitHub Actions" and assign the &lt;strong&gt;App Manager&lt;/strong&gt; role.&lt;/li&gt;
&lt;li&gt; Download the &lt;code&gt;.p8&lt;/code&gt; file. &lt;strong&gt;Keep this safe!&lt;/strong&gt; You can only download it once.&lt;/li&gt;
&lt;li&gt; Take note of the &lt;strong&gt;Key ID&lt;/strong&gt; and your &lt;strong&gt;Issuer ID&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. Base64 Encode the Key
&lt;/h3&gt;

&lt;p&gt;GitHub Secrets can sometimes struggle with the newlines in the &lt;code&gt;.p8&lt;/code&gt; file. A robust way to handle this is to Base64 encode the file content.&lt;/p&gt;

&lt;p&gt;Run this command in your terminal (macOS/Linux):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-in&lt;/span&gt; AuthKey_XXXXXXXXXX.p8 &lt;span class="nt"&gt;-out&lt;/span&gt; AuthKey_Base64.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows (PowerShell):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;ToBase64String&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;IO.File&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;ReadAllBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"AuthKey_XXXXXXXXXX.p8"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Set-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AuthKey_Base64.txt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the contents of the generated text file.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Add Secrets to GitHub
&lt;/h3&gt;

&lt;p&gt;Go to your repository on GitHub, navigate to &lt;strong&gt;Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;Secrets and variables&lt;/strong&gt; &amp;gt; &lt;strong&gt;Actions&lt;/strong&gt;, and add the following secrets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;APP_STORE_CONNECT_KEY&lt;/code&gt;: The Base64 encoded string from the previous step.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;APP_STORE_CONNECT_KEY_ID&lt;/code&gt;: The Key ID (e.g., &lt;code&gt;D383SF739&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;APP_STORE_CONNECT_ISSUER_ID&lt;/code&gt;: The Issuer UUID (e.g., &lt;code&gt;6053b7fe...&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;APPLE_TEAM_ID&lt;/code&gt;: Your Team ID (found in &lt;a href="https://developer.apple.com/account" rel="noopener noreferrer"&gt;Apple Developer Portal&lt;/a&gt; under Membership).&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If you notice your build failing with authentication errors, double-check that you copied the stored secret correctly and that it doesn't contain any extra whitespace.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Generating a Build for Sideloading
&lt;/h2&gt;

&lt;p&gt;Before messing with TestFlight, it's often easier to just get an installable build (an &lt;code&gt;.ipa&lt;/code&gt; for iOS/visionOS, or &lt;code&gt;.app&lt;/code&gt;/&lt;code&gt;.pkg&lt;/code&gt; for macOS) that you can install directly. This is great for rapid iteration or testing on a few specific devices.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Workflow
&lt;/h3&gt;

&lt;p&gt;Now let's update your build workflow to add steps for building and signing your app. If you don't have a workflow yet, see our &lt;a href="https://dev.to/virtualmaker/automating-unity-builds-with-github-actions-1inf"&gt;Automating Unity Builds&lt;/a&gt; article to get started.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choosing a Runner:&lt;/strong&gt;&lt;br&gt;
To build for Apple platforms, you &lt;strong&gt;must&lt;/strong&gt; use a macOS runner. You have two main options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;runs-on: macos-latest&lt;/code&gt;: GitHub's hosted runners. They are convenient for one-off builds, but can be slow and expensive if you're building frequently.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;runs-on: buildalon-macos&lt;/code&gt;: &lt;a href="https://www.buildalon.com" rel="noopener noreferrer"&gt;Buildalon's runners&lt;/a&gt;. These will cache your Unity builds, often completing much faster.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this example, we'll use &lt;code&gt;buildalon-macos&lt;/code&gt; for best performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Note on Build Numbers:&lt;/strong&gt;&lt;br&gt;
Unity projects use the build number set in the editor (&lt;code&gt;ProjectSettings.asset&lt;/code&gt;) by default. In a CI/CD pipeline, it's best practice to override this so every build has a unique identifier. In the example below, we'll use &lt;code&gt;${{ github.run_number }}&lt;/code&gt; as the build number, while keeping the version string (e.g. 1.0) defined in your project settings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-iOS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build iOS&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon-macos&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v6&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;lfs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

      &lt;span class="c1"&gt;# Setup Unity, License, and Build the Project&lt;/span&gt;
      &lt;span class="c1"&gt;# See our "Automating Unity Builds" article for details on these steps&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/unity-setup@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# Use 'iOS', 'Standalone', or 'VisionOS'&lt;/span&gt;
          &lt;span class="na"&gt;build-targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;iOS&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/activate-unity-license@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;license&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Personal'&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secrets.UNITY_EMAIL&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secrets.UNITY_PASSWORD&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/unity-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Unity Project&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# Use 'iOS', 'StandaloneOSX', or 'VisionOS'&lt;/span&gt;
          &lt;span class="na"&gt;build-target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;iOS&lt;/span&gt;
          &lt;span class="c1"&gt;# Use run_number as the build number (e.g. 1.0.42)&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;-quit -batchmode -executeMethod Buildalon.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild -buildNumber ${{ github.run_number }}&lt;/span&gt;
          &lt;span class="na"&gt;log-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;iOS-Build&lt;/span&gt;

      &lt;span class="c1"&gt;# Build the XCode project and sign it&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/unity-xcode-builder@v1&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xcode-build&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;project-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workspace }}/Builds/iOS/**/*.xcodeproj&lt;/span&gt;
          &lt;span class="c1"&gt;# 'development' creates a build signed for specific devices&lt;/span&gt;
          &lt;span class="na"&gt;export-option&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;development&lt;/span&gt;
          &lt;span class="na"&gt;team-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.APPLE_TEAM_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;app-store-connect-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.APP_STORE_CONNECT_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;app-store-connect-key-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.APP_STORE_CONNECT_KEY_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;app-store-connect-issuer-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v6&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;iOS-Build&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.xcode-build.outputs.executable }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Grab the Artifact
&lt;/h3&gt;

&lt;p&gt;Once the workflow finishes successfully, you can find your build file waiting for you in the &lt;strong&gt;Summary&lt;/strong&gt; page of the workflow run.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
    &lt;strong&gt;The Catch:&lt;/strong&gt; To install a development build, the destination device's &lt;strong&gt;UDID&lt;/strong&gt; must be registered in the Apple Developer Portal &lt;em&gt;before&lt;/em&gt; the build runs. If you get a new device, you'll need to add it to the portal and trigger a fresh build to update the provisioning profile.&lt;br&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Installing the Build
&lt;/h3&gt;

&lt;p&gt;Since this is a raw development build, you can't just tap a link to install it. You'll need to use one of the following methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;iOS/visionOS:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Finder (macOS):&lt;/strong&gt; Connect your device via USB. In Finder, select your device from the sidebar and drag the &lt;code&gt;.ipa&lt;/code&gt; onto the general information window.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  &lt;strong&gt;macOS:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;  Simply download and unzip the artifact, then run the &lt;code&gt;.app&lt;/code&gt; or installer. Note that you may need to right-click and select "Open" to bypass security warnings if it's not notarized yet.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deploying to TestFlight
&lt;/h2&gt;

&lt;p&gt;Sideloading is great for quick tests, but for beta testing with your team (or the world), &lt;strong&gt;TestFlight&lt;/strong&gt; is the way to go. This works for iOS, tvOS, visionOS, and macOS apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create a Testing Group
&lt;/h3&gt;

&lt;p&gt;First, organize your testers in App Store Connect.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Navigate to &lt;strong&gt;App Store Connect&lt;/strong&gt; &amp;gt; &lt;strong&gt;My Apps&lt;/strong&gt; &amp;gt; Select your App &amp;gt; &lt;strong&gt;TestFlight&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; Under &lt;strong&gt;Internal Testing&lt;/strong&gt;, click the &lt;strong&gt;(+)&lt;/strong&gt; button to create a group (e.g., "Internal Testers").&lt;/li&gt;
&lt;li&gt; Add your team members to this group.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. Update the Workflow
&lt;/h3&gt;

&lt;p&gt;Now we just need to tweak our workflow to tell it to upload to App Store Connect instead of just signing for development. Change the &lt;code&gt;export-option&lt;/code&gt; to &lt;code&gt;app-store-connect&lt;/code&gt; and specify your test group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/unity-xcode-builder@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;project-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workspace }}/Builds/iOS/**/*.xcodeproj&lt;/span&gt;
          &lt;span class="na"&gt;export-option&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-store-connect&lt;/span&gt;
          &lt;span class="c1"&gt;# ... credentials ...&lt;/span&gt;
          &lt;span class="c1"&gt;# Automatically invite this group when the build processes&lt;/span&gt;
          &lt;span class="na"&gt;test-groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Internal&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Testers"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the build finishes, Buildalon will upload the archive. Apple usually takes a few minutes to process it. Once that's done, your "Internal Testers" will get an email, and they can start testing immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Download &amp;amp; Play
&lt;/h3&gt;

&lt;p&gt;Your testers just need to grab the &lt;strong&gt;TestFlight&lt;/strong&gt; app from the App Store. Once they accept the invite, your game will appear there, ready to install.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Want to tell your testers what's new? Add the &lt;code&gt;release-notes&lt;/code&gt; parameter to the &lt;code&gt;buildalon/unity-xcode-builder&lt;/code&gt; step. You can even pass &lt;code&gt;${{ github.event.head_commit.message }}&lt;/code&gt; to automatically use your commit message!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  About Buildalon
&lt;/h2&gt;

&lt;p&gt;Buildalon provides verified GitHub Actions and dedicated build infrastructure to streamline Unity development. &lt;a href="https://www.buildalon.com?utm_source=devto" rel="noopener noreferrer"&gt;Register for Buildalon&lt;/a&gt; to get support with your Unity build automation needs.&lt;/p&gt;

</description>
      <category>unity3d</category>
      <category>githubactions</category>
      <category>ios</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Unit Testing for Unity Developers</title>
      <dc:creator>Alon Farchy</dc:creator>
      <pubDate>Wed, 22 Jan 2025 16:04:02 +0000</pubDate>
      <link>https://dev.to/virtualmaker/unit-testing-for-unity-developers-52lp</link>
      <guid>https://dev.to/virtualmaker/unit-testing-for-unity-developers-52lp</guid>
      <description>&lt;p&gt;Let's face it Unity developers — you write buggy code. &lt;strong&gt;I write buggy code&lt;/strong&gt;. &lt;strong&gt;&lt;em&gt;AI writes buggy code&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Many software engineers consider unit testing as the key to catching bugs early and preventing regressions. But do they work for Unity developers?&lt;/p&gt;

&lt;p&gt;In this article, I'll share with you how we do testing at Virtual Maker. We'll learn the difference between unit tests, integration tests, and end-to-end tests, and why I think you should avoid writing the latter.&lt;/p&gt;

&lt;p&gt;Then, we'll dive into some code and learn how to write tests using the NUnit framework in Unity. To top it off, we'll learn how to run tests from the command line and using GitHub Actions to truly automate the testing process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Unit Testing at Virtual Maker&lt;/li&gt;
&lt;li&gt;Types of Tests&lt;/li&gt;
&lt;li&gt;Writing Tests in Unity using NUnit&lt;/li&gt;
&lt;li&gt;Edit Mode Tests&lt;/li&gt;
&lt;li&gt;Play Mode Tests&lt;/li&gt;
&lt;li&gt;Run Tests from the Command Line&lt;/li&gt;
&lt;li&gt;Running Tests in Automation using GitHub Actions&lt;/li&gt;
&lt;li&gt;Summary&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Unit Testing at Virtual Maker
&lt;/h2&gt;

&lt;p&gt;At Virtual Maker, we use unit testing to extensively test our Unity plugins and ensure they work in a variety of scenarios.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://www.flexalon.com?utm_source=devto" rel="noopener noreferrer"&gt;Flexalon 3D Layouts&lt;/a&gt;, we test each layout component with different configurations and edge cases. Similarly, in &lt;a href="https://www.unityproxima.com?utm_source=devto" rel="noopener noreferrer"&gt;Proxima Inspector&lt;/a&gt;, we test the protocol between Unity and the browser, to ensure that the right data is sent for each type of component and gameObject.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/xYAgaOcm8c8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;On top of ensuring that all edge cases work correctly, unit tests help us to prevent regressions. Whenever we fix bugs or add new features, we invariably introduce some new bugs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faeli037k534nzjl6e4bl.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faeli037k534nzjl6e4bl.jpeg" alt="Fixing bugs can cause new bugs" width="639" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To catch these, we set up our tests to run automatically whenever we make a pull request in GitHub, so any of these bugs are caught well before a change makes it in into a release.&lt;/p&gt;

&lt;p&gt;But not all tests are created equal. In my past job at Microsoft, we spent a heck of a lot of time writing tests that didn't catch &lt;em&gt;any&lt;/em&gt; bugs. Worse, we wrote tests that were so brittle that we spent more time investigating issues and fixing &lt;em&gt;the tests&lt;/em&gt; than actually working on the product.&lt;/p&gt;

&lt;p&gt;So what makes one test good and another test bad?&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of Tests
&lt;/h2&gt;

&lt;p&gt;Often, what separates a good test from a bad test is a matter of &lt;strong&gt;scope&lt;/strong&gt;. Ask yourself the question: how many components need to work correctly for a test to pass?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit Tests&lt;/strong&gt;: Test a single method or class in isolation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration Tests&lt;/strong&gt;: Test the multiple components work together correctly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End-to-end Tests&lt;/strong&gt;: Test that game features work as expected from as close to a player's perspective as possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Unit Testing
&lt;/h3&gt;

&lt;p&gt;Advocates for &lt;strong&gt;unit testing&lt;/strong&gt; argue that tests should be as small as possible — test a single method or class in isolation. These tests are quick to run and easy to debug. For complex functions, especially math functions, they can also provide a lot of value.&lt;/p&gt;

&lt;p&gt;For example, Proxima Inspector has a &lt;code&gt;CircularList&lt;/code&gt; class that stores Unity logs so that you can see past logs after connecting the inspector. This class was tricky to get right, so we write some unit tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;EnumerableWrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;CircularList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Constructs a circular list with a size of 2&lt;/span&gt;
    &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AreEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ItemsAdded&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AreEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AreEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AreEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test checks that the &lt;code&gt;CircularList&lt;/code&gt; class wraps around when it reaches its capacity. It adds three items to the list, then checks that the list only contains the last two items.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integration Testing
&lt;/h3&gt;

&lt;p&gt;Scoped unit testing is great for checking the behavior of complex functions and classes. But often, it's not enough. In Flexalon, most of the difficult bugs come from the interactions between multiple Flexalon components.&lt;/p&gt;

&lt;p&gt;In this case, we should think of how to test the Flexalon package as an isolated component.&lt;/p&gt;

&lt;p&gt;How can we test this scenario? The test needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a scene.&lt;/li&gt;
&lt;li&gt;Add some gameObjects with Flexalon components.&lt;/li&gt;
&lt;li&gt;Have Flexalon run its layout.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note 1: You can can still use a "unit testing framework" to write integration tests, or even end-to-end tests. Don't look at me, I don't make the rules.&lt;/p&gt;

&lt;p&gt;Note 2: "Integration testing" can also refer to the integration between modules, processes, or even distributed computers. Here, I'm using the term to refer to the scope of the test as bigger than a single class or method. Think of testing a whole package or library.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's an example of a real test from Flexalon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;FillChild&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Create and configure a Flexible Layout&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateFlex&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;flexObj&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FlexalonObject&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="n"&gt;flexObj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Width&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Add a child to the layout&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateCube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FlexalonObject&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WidthOfParent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.5f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Add another child to the layout&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;child2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateCube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Update the layout&lt;/span&gt;
    &lt;span class="nf"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Check the results&lt;/span&gt;
    &lt;span class="nf"&gt;AssertTransform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;AssertTransform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(-&lt;/span&gt;&lt;span class="m"&gt;0.5f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nf"&gt;AssertTransform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.5f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since there are many such tests, we wrote helper methods for common functionality: create Flexalon components, update the layout, and check the results.&lt;/p&gt;

&lt;p&gt;Importantly, these tests run in &lt;strong&gt;edit mode&lt;/strong&gt;. We don't need to play the game to run these tests, and we don't have to perform any asynchronous actions. &lt;strong&gt;Flexalon was designed this way&lt;/strong&gt; so that it would be easy to test. By calling &lt;code&gt;Update()&lt;/code&gt;, we force Flexalon to immediately process all layout updates, even though it would normally wait for the next frame.&lt;/p&gt;

&lt;p&gt;This is key: write your components so they are easy to test in isolation. If your tests are not fast and reliable, they will quickly lose their value. This brings us to...&lt;/p&gt;

&lt;h3&gt;
  
  
  End-to-end Testing
&lt;/h3&gt;

&lt;p&gt;In end-to-end testing, we try to test the product as a user would use it. Typically, this involves setting up some input simulation so that the test can act as a player, play the game, and then check the results.&lt;/p&gt;

&lt;p&gt;With end-to-end tests, you can be sure that the product is really working as expected. By bother testing every edge of every component in isolation when you can just test the real player experience?&lt;/p&gt;

&lt;p&gt;Or, so the thinking goes.&lt;/p&gt;

&lt;p&gt;In reality, end-to-end are often &lt;strong&gt;slow&lt;/strong&gt;, &lt;strong&gt;brittle&lt;/strong&gt;, and &lt;strong&gt;hard to maintain&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's use a simple example. Suppose you want to test that the player can change the volume. First, the player has to open the main menu, then drag the volume slider. What are the problems?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Slow:&lt;/strong&gt; If an animation to open the main menu takes 2 seconds, then the test needs to wait 2 seconds. Multiply this by the number of tests that have to open the main menu.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Brittle:&lt;/strong&gt; What if the animation takes a little longer to run because the test computer is slow? We aren't trying to test performance here, yet the test will fail. What if the volume slider position changes depending on screen size? Eventually, you find yourself bending over backwards trying to stabilize the testing environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hard to maintain:&lt;/strong&gt; Suppose a designer decides to change the main menu animation to 3 seconds. Now all the tests that depend on the main menu need to be updated to wait for 3 seconds instead of 2.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clever developers will try to circumvent these problems by adding features that make the tests faster and more reliably. For example, instead of waiting 2 seconds for an animation to play, we can add a special &lt;code&gt;open-main-menu-completed&lt;/code&gt; event to the game that the test can wait for. Then, crank up the time scale to 10x speed so that the all animations complete in 0.2 seconds.&lt;/p&gt;

&lt;p&gt;While the intentions here are good, the fallacy is that now developers are spending more time debugging tests and writing test infrastructure instead of improving the product. In one of my past projects, we actually spent &lt;em&gt;more&lt;/em&gt; time debugging issues with the tests than we did with the product.&lt;/p&gt;

&lt;h4&gt;
  
  
  Ok, ok, but should I EVER write end-to-end tests?
&lt;/h4&gt;

&lt;p&gt;Probably not.&lt;/p&gt;

&lt;p&gt;Instead, design your app so that the parts that are difficult to get right can be tested in isolation. Leave the end-to-end testing to the humans. Or sentient AI, as the case may be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing Tests in Unity using NUnit
&lt;/h2&gt;

&lt;p&gt;Unity has a built-in unit testing framework called &lt;a href="https://docs.unity3d.com/Packages/com.unity.test-framework@1.1/manual/index.html" rel="noopener noreferrer"&gt;NUnit&lt;/a&gt;. To get started, you just need to install the NUnit package from the Unity Package Manager.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to Window &amp;gt; Package Manager.&lt;/li&gt;
&lt;li&gt;In the Package Manager window, select Unity Registry from the dropdown.&lt;/li&gt;
&lt;li&gt;Search for NUnit and click Install on the NUnit package.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Edit Mode vs Play Mode Tests
&lt;/h3&gt;

&lt;p&gt;In Unity, there are two type sof tests: edit mode tests and play mode tests.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Edit Mode Tests:&lt;/strong&gt; These tests run within the Unity Editor. They are ideal for both unit tests and integration tests. Wherever possible, you should use edit mode tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Play Mode Tests:&lt;/strong&gt; These tests can run in the Unit Editor in play mode or in a standalone player. These types of tests should be used as a last resort since they are slower and less reliable. Whenever possible, it is better to update your components to support edit mode tests than to write play mode tests.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Edit Mode Tests
&lt;/h2&gt;

&lt;p&gt;For demonstration, we'll use this simple &lt;code&gt;SimpleCircleLayout&lt;/code&gt; component that arranges its children in a circle around its center.&lt;/p&gt;

&lt;p&gt;This is a super simplified version of the &lt;a href="https://www.flexalon.com/docs/circleLayout?utm_source=devto" rel="noopener noreferrer"&gt;FlexalonCircleLayout&lt;/a&gt; component, which has tests that are similar to the ones we'll write here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SimpleCircleLayout&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MonoBehaviour&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Radius&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;LayoutChildren&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Transform&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Radius&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Mathf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Radius&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Mathf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;angle&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Mathf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PI&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a Assembly Definition for Edit Mode Tests
&lt;/h3&gt;

&lt;p&gt;Before we can write tests, we need to create a special assembly where our tests will live.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open Window &amp;gt; General &amp;gt; Test Runner.&lt;/li&gt;
&lt;li&gt;If you don't have any tests in your project you'll see this:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbe0rkk7jobl389r7h5mb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbe0rkk7jobl389r7h5mb.png" alt="Test Runner - Create Edit Mode Tests" width="800" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Click Create EditMode Test Assembly Folder. This will create a new "Tests" folder with an assembly definition file. You can rename this however you like.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In order to test your code, it needs to be in its own &lt;a href="https://docs.unity3d.com/6000.0/Documentation/Manual/assembly-definitions-intro.html" rel="noopener noreferrer"&gt;assembly definition file&lt;/a&gt;. If your script is in your main project, right click the Assets folder in the Project window and select Create &amp;gt; Assembly Definition.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxc8twdgl5j7ki3ua5mck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxc8twdgl5j7ki3ua5mck.png" alt="Create Assembly Definition" width="800" height="673"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Drag your assembly definition into the references of the test assembly definition.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's the final test assembly definition for Flexalon:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzvljghl73vce1m43pq9c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzvljghl73vce1m43pq9c.png" alt="Flexalon Edit Mode Test Assembly" width="594" height="802"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Write your Test Class
&lt;/h3&gt;

&lt;p&gt;Now that we have our assembly set up, we can start writing tests. Create a new file called &lt;code&gt;SimpleCircleLayoutTests.cs&lt;/code&gt; in your test folder, next to the assembly definition file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;NUnit.Framework&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestFixture&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SimpleCircleLayoutTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;TestLayoutChildren&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GameObject&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;AddComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SimpleCircleLayout&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Radius&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;child1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GameObject&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;child2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GameObject&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;child3&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GameObject&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;child1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetParent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;child2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetParent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;child3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetParent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LayoutChildren&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;TestUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AssertVector3Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;child1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;TestUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AssertVector3Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1.73f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;child2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;TestUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AssertVector3Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1.73f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;child3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this first test, we create a &lt;code&gt;SimpleCircleLayout&lt;/code&gt; component and add three child objects to it. We then call the &lt;code&gt;LayoutChildren&lt;/code&gt; method and assert that the children are positioned correctly in a circle around the center. Finally, we use the &lt;code&gt;Assert&lt;/code&gt; class to check if the positions are as expected.&lt;/p&gt;

&lt;p&gt;You can add other tests by creating new methods with the &lt;code&gt;[Test]&lt;/code&gt; attribute. Each test method should be self-contained and test a specific aspect of the component.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running Edit Mode Tests
&lt;/h3&gt;

&lt;p&gt;To run your edit mode tests, go to the Unity Test Runner window (Window &amp;gt; General &amp;gt; Test Runner) and click the Run All button. The test results will be displayed in the Test Runner window, showing you which tests passed and which failed.&lt;/p&gt;

&lt;p&gt;You'll notice, oddly, that the tests fail with the message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TestLayoutChildren (0.012s)
---
Expected: (-1.00, 1.73, 0.00)
  But was:  (-1.00, 1.73, 0.00)
---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But why?&lt;/p&gt;

&lt;h3&gt;
  
  
  Improving equality tests for Vector3 and Quaternion
&lt;/h3&gt;

&lt;p&gt;Unity's &lt;code&gt;Vector3&lt;/code&gt; and &lt;code&gt;Quaternion&lt;/code&gt; types are floating-point values, which can lead to precision issues. The actual Y value is not &lt;em&gt;exactly&lt;/em&gt; 1.73, Unity is just printing out the first 2 digits after the decimal.&lt;/p&gt;

&lt;p&gt;To address this, you can use a helper class to checks if the difference between two values is within a certain tolerance (0.01).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;NUnit.Framework&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine.TestTools.Utils&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestUtil&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Vector3EqualityComparer&lt;/span&gt; &lt;span class="n"&gt;vector3Comparer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.01f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;QuaternionEqualityComparer&lt;/span&gt; &lt;span class="n"&gt;quaternionComparer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.01f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;AssertVector3Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Vector3&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Vector3&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Using&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vector3Comparer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Vectors are Equal"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;AssertQuaternionEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Quaternion&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Quaternion&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Using&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quaternionComparer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Quaternions are Equal"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if we replace the &lt;code&gt;Assert.AreEqual&lt;/code&gt; calls with &lt;code&gt;TestUtil.AssertVector3Equal&lt;/code&gt;, the tests will pass.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frra7ywgzyfvqzdkp9vdw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frra7ywgzyfvqzdkp9vdw.png" alt="Flexalon Edit Mode Test Run" width="619" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup and Teardown
&lt;/h3&gt;

&lt;p&gt;In some cases, you may find it convenient to perform common setup and teardown steps before and after each test. You can use the &lt;code&gt;[SetUp]&lt;/code&gt; and &lt;code&gt;[TearDown]&lt;/code&gt; attributes to mark methods that run before and after each test, respectively.&lt;/p&gt;

&lt;p&gt;For example, suppose we want to test the circle layout with different numbers of children.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;NUnit.Framework&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestFixture&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SimpleCircleLayoutTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;SimpleCircleLayout&lt;/span&gt; &lt;span class="n"&gt;_layout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SetUp&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_layout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GameObject&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;AddComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SimpleCircleLayout&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Radius&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TearDown&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Teardown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;GameObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DestroyImmediate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gameObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;TestOneChild&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GameObject&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetParent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;_layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LayoutChildren&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;TestUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AssertVector3Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;TestTwoChildren&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;child1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GameObject&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;child2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GameObject&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;child1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetParent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;child2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetParent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;_layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LayoutChildren&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;TestUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AssertVector3Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;child1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;TestUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AssertVector3Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(-&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;child2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the &lt;code&gt;Setup&lt;/code&gt; method creates a new &lt;code&gt;SimpleCircleLayout&lt;/code&gt; component before each test, and the &lt;code&gt;Teardown&lt;/code&gt; method destroys it after each test. This ensures that each test starts with a clean slate and doesn't interfere with other tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Attributes
&lt;/h3&gt;

&lt;p&gt;Unity's test framework provides several other attributes that you can use to organize and manage your tests effectively:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;[TestFixture]&lt;/code&gt;: Indicates a class that contains tests.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[Test]&lt;/code&gt;: Marks a method as a test.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[SetUp]&lt;/code&gt;: Identifies a method that runs before each test. It's used to set up conditions required for the tests.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[TearDown]&lt;/code&gt;: Identifies a method that runs after each test. It's used to clean up any resources or state.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[OneTimeSetUp]&lt;/code&gt;: Runs once before all tests in the test class.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[OneTimeTearDown]&lt;/code&gt;: Runs once after all tests in the test class.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[UnityTest]&lt;/code&gt;: For play mode tests (read on below). This tells Unity to run the test as a coroutine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Generally speaking, be wary of &lt;code&gt;[OneTimeSetUp]&lt;/code&gt; and &lt;code&gt;[OneTimeTearDown]&lt;/code&gt;, as they can lead to dependencies between tests. For example, suppose we created the &lt;code&gt;SimpleCircleLayout&lt;/code&gt; in &lt;code&gt;[OneTimeSetUp]&lt;/code&gt; instead of &lt;code&gt;[SetUp]&lt;/code&gt;. The same layout would be shared between all tests. If one test adds children and doesn't clean up after itself, the next test would run with extra children, leading to unexpected failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Play Mode Tests
&lt;/h2&gt;

&lt;p&gt;Play mode tests can run in the Unity Editor in play mode or in a standalone player. This can make play mode tests slow and brittle.&lt;/p&gt;

&lt;p&gt;But there are some scenarios where play mode tests makes sense because the behavior you want to takes multiple frames to complete.&lt;/p&gt;

&lt;p&gt;For example, Flexalon has a &lt;code&gt;FlexalonInteractable&lt;/code&gt; component that allows the player to click and drag an object. We need to test that if the player clicks on the interactable and drags it out of a layout, then the object ends up in the correct position and the layout updates correctly. Here's a real example:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;video controls="" loop=""&amp;gt;
    &amp;lt;source src="/images/blog/flexalon-grab-vr.mp4" type="video/mp4"&amp;gt;
&amp;lt;/source&amp;gt;&amp;lt;/video&amp;gt;
&amp;lt;p&amp;gt;Testing the VR grab interactables in Flexalon 3D Layouts&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;UnityTest&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IEnumerator&lt;/span&gt; &lt;span class="nf"&gt;DraggableRemove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dragTarget&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateDragTarget&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;interactable1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateInteractable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dragTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;interactable2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateInteractable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dragTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;MoveFromTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.5f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2.5f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AreEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dragTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;childCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interactable1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;dragTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Indeed, these tests are the slowest and most brittle tests in Flexalon. In particular, the &lt;code&gt;MoveFromTo&lt;/code&gt; helper function took some quite some time to get right. But we feel these tests are worth the tradeoff because they test a critical part of the product, and there are many possible edge cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example that Requires Play Mode Tests
&lt;/h3&gt;

&lt;p&gt;To demonstrate play mode tests, let's add a &lt;code&gt;RotateOnce&lt;/code&gt; component. The component will rotate a gameObject once and only once around the Z-axis over a duration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RotateOnce&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MonoBehaviour&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3.0f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;_startTime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnEnable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_startTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;_startTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Quaternion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Euler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Mathf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;360&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a Assembly Definition for Play Mode Tests
&lt;/h3&gt;

&lt;p&gt;Play mode tests need to be in a separate assembly from edit mode tests, since they cannot reference the UnityEditor namespace. Follow the same steps as for edit mode tests, but this time click "Play Mode".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg3blvkgnk20w9zebrp3h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg3blvkgnk20w9zebrp3h.png" alt="Test Runner - Create Play Mode Tests" width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Play Mode Test Basics
&lt;/h3&gt;

&lt;p&gt;Now that we have our assembly set up, we can start writing play mode tests. Create a new file called &lt;code&gt;RotateOnceTests.cs&lt;/code&gt; in your test folder, next to the assembly definition file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;NUnit.Framework&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestFixture&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RotateOnceTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;UnityTest&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IEnumerator&lt;/span&gt; &lt;span class="nf"&gt;TestRotateOnce&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rotateOnce&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GameObject&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;AddComponent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RotateOnce&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;rotateOnce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3.0f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;WaitForSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;TestUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AssertQuaternionEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Quaternion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Euler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;120&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;rotateOnce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;WaitForSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;TestUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AssertQuaternionEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Quaternion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Euler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;240&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;rotateOnce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;WaitForSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;TestUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AssertQuaternionEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Quaternion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Euler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;360&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;rotateOnce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;WaitForSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Make sure it only rotates once!&lt;/span&gt;
        &lt;span class="n"&gt;TestUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AssertQuaternionEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Quaternion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Euler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;360&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;rotateOnce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this test, we create a &lt;code&gt;RotateOnce&lt;/code&gt; component that will rotate once over 3 seconds, and then check the rotation at different points in time.&lt;/p&gt;

&lt;p&gt;For this test to work, we need to use the &lt;code&gt;[UnityTest]&lt;/code&gt; attribute instead of &lt;code&gt;[Test]&lt;/code&gt; and change the return type to &lt;code&gt;IEnumerator&lt;/code&gt;. This treats the method as a coroutine, and you can use &lt;code&gt;yield return&lt;/code&gt; to perform asynchronous operations. &lt;a href="https://docs.unity3d.com/6000.0/Documentation/Manual/coroutines.html" rel="noopener noreferrer"&gt;Learn more about coroutines in Unity&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running Play Mode Tests
&lt;/h3&gt;

&lt;p&gt;To run your play mode tests, go to the Unity Test Runner window (Window &amp;gt; General &amp;gt; Test Runner) and select PlayMode from the dropdown. Click the Run All button to run the tests. The test results will be displayed in the Test Runner window, showing you which tests passed and which failed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ca558v6n89jpc8h518x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ca558v6n89jpc8h518x.png" alt="Flexalon Edit Mode Test Run" width="619" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip: If you add &lt;code&gt;Debug.Log&lt;/code&gt; statements to your test, they'll appear in the test results. This can be helpful for debugging failing tests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Run Tests from the Command Line
&lt;/h2&gt;

&lt;p&gt;Running tests manually is fine for small projects, but as your project grows, you'll want to automate your tests to catch issues early and ensure consistent quality. Unity provides command line options to run tests, allowing you to integrate them into your build pipeline or continuous integration (CI) system.&lt;/p&gt;

&lt;p&gt;To run your tests from the command line, use the &lt;code&gt;-runTests&lt;/code&gt; argument followed by the &lt;code&gt;-testPlatform&lt;/code&gt; to specify where to run the tests (EditMode or PlayMode).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Unity.exe &lt;span class="nt"&gt;-batchmode&lt;/span&gt; &lt;span class="nt"&gt;-nographics&lt;/span&gt; &lt;span class="nt"&gt;-runTests&lt;/span&gt; &lt;span class="nt"&gt;-projectPath&lt;/span&gt; &lt;span class="s2"&gt;"path/to/your/project"&lt;/span&gt; &lt;span class="nt"&gt;-testPlatform&lt;/span&gt; EditMode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: Do &lt;strong&gt;not&lt;/strong&gt; pass the &lt;code&gt;-quit&lt;/code&gt; flag to the command line, as it will quit Unity before any tests run. NUnit will automatically quit Unity after the tests are done.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are additional options that you can use to filter which tests to run. See the &lt;a href="https://docs.unity3d.com/Packages/com.unity.test-framework@2.0/manual/reference-command-line.html" rel="noopener noreferrer"&gt;full list of command line arguments&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Analyzing Test Results
&lt;/h3&gt;

&lt;p&gt;The command will generate a file named something like &lt;code&gt;TestResults-638683971208353675.xml&lt;/code&gt; in your project root directory, which you can inspect to see the results of your test. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;test-case&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"1305"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Collider2DFixedAndComponent"&lt;/span&gt; &lt;span class="na"&gt;fullname=&lt;/span&gt;&lt;span class="s"&gt;"FlexalonAdapterTests.Collider2DFixedAndComponent"&lt;/span&gt; &lt;span class="na"&gt;methodname=&lt;/span&gt;&lt;span class="s"&gt;"Collider2DFixedAndComponent"&lt;/span&gt; &lt;span class="na"&gt;classname=&lt;/span&gt;&lt;span class="s"&gt;"FlexalonAdapterTests"&lt;/span&gt; &lt;span class="na"&gt;runstate=&lt;/span&gt;&lt;span class="s"&gt;"Runnable"&lt;/span&gt; &lt;span class="na"&gt;seed=&lt;/span&gt;&lt;span class="s"&gt;"4201476"&lt;/span&gt; &lt;span class="na"&gt;result=&lt;/span&gt;&lt;span class="s"&gt;"Passed"&lt;/span&gt; &lt;span class="na"&gt;start-time=&lt;/span&gt;&lt;span class="s"&gt;"2024-11-26 03:42:24Z"&lt;/span&gt; &lt;span class="na"&gt;end-time=&lt;/span&gt;&lt;span class="s"&gt;"2024-11-26 03:42:24Z"&lt;/span&gt; &lt;span class="na"&gt;duration=&lt;/span&gt;&lt;span class="s"&gt;"0.001536"&lt;/span&gt; &lt;span class="na"&gt;asserts=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we see the result of test &lt;code&gt;FlexalonAdapterTests.Collider2DFixedAndComponent&lt;/code&gt; with &lt;code&gt;result="Passed"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Tests in Automation using GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Now that we know how to run tests from the command line, you can easily add it to any continuous integrate (CI) pipeline that you have set up.&lt;/p&gt;

&lt;p&gt;If you're using GitHub actions, we wrote the article &lt;a href="https://www.virtualmaker.dev/blog/automating-unity-builds-with-github-actions/?utm_source=devto" rel="noopener noreferrer"&gt;Automating Unity Builds with GitHub Actions&lt;/a&gt; that will help you get started.&lt;/p&gt;

&lt;p&gt;You can then update your workflow file to run the tests. We recommend injecting this after the &lt;code&gt;Project Validation&lt;/code&gt; step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Edit Mode Tests&lt;/span&gt;
&lt;span class="na"&gt;using&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/unity-action@v1&lt;/span&gt;
  &lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build-target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.build-target }}&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;-runTests -batchmode -testPlatform EditMode -testResults "${{ env.UNITY_PROJECT_PATH }}/Logs/EditMode-test-results.xml"&lt;/span&gt;
    &lt;span class="na"&gt;log-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EditMode-Tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You also need to update the actions/upload-artifacts step to add the test results to the uploaded artifacts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;${{ github.workspace }}/**/*.xml # &amp;lt;- ADD THIS LINE&lt;/span&gt;
      &lt;span class="s"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The Unity NUnit package provides a powerful way to write tests to prevent bugs in your project. We covered the differences between unit testing, integration testing, and end-to-end testing, and when it's appropriate to use each.&lt;/p&gt;

&lt;p&gt;We also learned how to write edit mode tests and play mode tests, and how to run them from the command line or in automation using GitHub Actions.&lt;/p&gt;

&lt;p&gt;Armed with this knowledge, &lt;strong&gt;I expect bug free code from you from now on&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Go forth and test!&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.virtualmaker.dev?utm_source=devto" rel="noopener noreferrer"&gt;Original Article&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.virtualmaker.dev?utm_source=devto" rel="noopener noreferrer"&gt;Virtual Maker Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.flexalon.com?utm_source=devto" rel="noopener noreferrer"&gt;Flexalon 3D Layouts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>unity3d</category>
      <category>unittest</category>
      <category>gamedev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Automating Unity Builds with GitHub Actions</title>
      <dc:creator>Alon Farchy</dc:creator>
      <pubDate>Thu, 09 Jan 2025 15:40:15 +0000</pubDate>
      <link>https://dev.to/virtualmaker/automating-unity-builds-with-github-actions-1inf</link>
      <guid>https://dev.to/virtualmaker/automating-unity-builds-with-github-actions-1inf</guid>
      <description>&lt;p&gt;When working on a Unity project, you may find yourself making many builds to play test your changes. Since Unity locks the editor while building, this can leave you twiddling your thumbs for what seems like hours. By automating your builds, you can get your your time back, and at the same time streamline the development process for your team.&lt;/p&gt;

&lt;p&gt;In this guide, we'll walk through how to use &lt;strong&gt;GitHub Actions&lt;/strong&gt; to automate Unity builds. We’ll learn step by step how to create a GitHub workflow, add the necessary secrets, and choose an appropriate build machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Build Automation (CI/CD)?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Continuous Integration (CI)&lt;/strong&gt; and &lt;strong&gt;Continuous Deployment (CD)&lt;/strong&gt; are practices that automate the process of building, testing, and deploying your project. CI/CD pipelines can be set up to run automatically whenever you push changes to your repository, ensuring you have a build ready to test at any time.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are GitHub Actions?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; is a feature of GitHub that allows you run automated steps when changes happen in your GitHub repository. You can create custom workflows that run on specific triggers, such as pushing code to a repository or opening a pull request. These workflows can be used to build, test, and deploy your code automatically.&lt;/p&gt;

&lt;p&gt;If you're new to Git and GitHub, learn how to setup Git for Unity in our &lt;a href="https://www.virtualmaker.dev/blog/git-and-unity-a-comprehensive-guide-to-version-control-for-game-devs" rel="noopener noreferrer"&gt;Comprehensive Guide to Version Control for Game Devs&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is &lt;span&gt;Buildalon&lt;/span&gt;?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.buildalon.com?utm_source=devto" rel="noopener noreferrer"&gt;Buildalon&lt;/a&gt; turns GitHub Actions into a powerful CI/CD tool for Unity developers. It provides a set of free open-source GitHub Actions that we'll need to build a Unity project, and (optionally) build machines that support fast incremental builds.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffa2pljr2k9dftujiz1dj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffa2pljr2k9dftujiz1dj.png" alt="Buildalon Components" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Buildalon Quick Start (optional)
&lt;/h2&gt;

&lt;p&gt;In the rest of this article we'll cover the nitty-gritty details of writing GitHub Actions workflow to automate Unity builds.&lt;/p&gt;

&lt;p&gt;However, you can also use the &lt;a href="https://www.buildalon.com/start?utm_source=devto" rel="noopener noreferrer"&gt;Buildalon Quick Start&lt;/a&gt; to generate a workflow. This is the fastest way to get started, and you can come back to learn the details later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyebcgdc313qck1tgyacc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyebcgdc313qck1tgyacc.png" alt="Buildalon Quickstart" width="800" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Your Build Workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Create a Workflow File
&lt;/h3&gt;

&lt;p&gt;To get started, you need to create a workflow file in your repository. This file will define the steps that GitHub Actions will take to build your Unity project. Create the file &lt;code&gt;unity-build.yml&lt;/code&gt; and put in a directory named &lt;code&gt;.github/workflows&lt;/code&gt; in your repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Add Triggers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Triggers&lt;/strong&gt; are events that tell GitHub to start a workflow. Triggers help streamline development by automatically initiating builds, tests, or deployments whenever key events occur, reducing the need for manual intervention.&lt;/p&gt;

&lt;p&gt;These example triggers will run the workflow on every push to the main branch and on every pull request to any branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Learn more about the different &lt;a href="https://www.buildalon.com/docs/workflows/triggers?utm_source=devto" rel="noopener noreferrer"&gt;triggers&lt;/a&gt; available.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Add a job
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;job&lt;/strong&gt; is a set of steps that run sequentially on the same build machine. Each job can run on a different machine, allowing you to parallelize your workflow and speed up the build process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example: Define the 'build' Job&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;UNITY_PROJECT_PATH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.os }}&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;buildalon-windows&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon-windows&lt;/span&gt;
            &lt;span class="na"&gt;build-target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;StandaloneWindows64&lt;/span&gt;
            &lt;span class="na"&gt;build-args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 'env' section defines environment variables that can be used in the job, and we'll need to declare the &lt;code&gt;UNITY_PROJECT_PATH&lt;/code&gt; variable for later steps.&lt;/p&gt;

&lt;p&gt;The 'runs-on' section specifies which build machines should be used, based on a runner label. Here we use a matrix strategy to define different operating systems and build targets. You can add more build targets in the future and run them in in parallel.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;buildalon-windows&lt;/code&gt; label tells GitHub to run this workflow on a Buildalon Windows runner. You can also use &lt;code&gt;buildalon-macos&lt;/code&gt; or &lt;code&gt;buildalon-ubuntu&lt;/code&gt; for macOS and Linux builds, respectively. Buildalon runners support incremental builds, which will speed up your builds considerably.&lt;/p&gt;

&lt;p&gt;If you prefer to use GitHub runners, change the label to &lt;code&gt;windows-latest&lt;/code&gt;, &lt;code&gt;macos-latest&lt;/code&gt;, or &lt;code&gt;ubuntu-latest&lt;/code&gt;. For advanced users, you can also set up your own &lt;a href="https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners" rel="noopener noreferrer"&gt;self-hosted runners&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;StandaloneWindows64&lt;/code&gt; build target specifies that we want to build a 64-bit Windows standalone application. You can add more build targets by including them in the matrix, and these should match the names of the available &lt;a href="https://docs.unity3d.com/ScriptReference/BuildTarget.html" rel="noopener noreferrer"&gt;Unity build targets&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can use &lt;code&gt;build-args&lt;/code&gt; to pass additional arguments to Unity when it is run from the command line.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Add Steps
&lt;/h3&gt;

&lt;p&gt;The minimum steps we need to build a Unity project are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checkout the repository - &lt;a href="https://www.github.com/actions/checkout" rel="noopener noreferrer"&gt;actions/checkout&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Install Unity - &lt;a href="https://www.buildalon.com/docs/actions/unity-setup?utm_source=devto" rel="noopener noreferrer"&gt;buildalon/unity-setup&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Activate the Unity license - &lt;a href="https://www.buildalon.com/docs/actions/activate-unity-license?utm_source=devto" rel="noopener noreferrer"&gt;buildalon/activate-unity-license&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Add the Buildalon Unity Plugin - &lt;a href="https://www.buildalon.com/docs/unity-plugin?utm_source=devto" rel="noopener noreferrer"&gt;com.virtualmaker.buildalon&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Build the project - &lt;a href="https://www.buildalon.com/docs/actions/unity-action?utm_source=devto" rel="noopener noreferrer"&gt;buildalon/unity-action&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Upload the build artifacts - &lt;a href="https://www.github.com/actions/upload-artifact" rel="noopener noreferrer"&gt;actions/upload-artifact&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below is an example set of steps for making a Windows desktop build. These steps were generated using &lt;a href="https://www.buildalon.com/start?utm_source=devto" rel="noopener noreferrer"&gt;Buildalon Quick Start&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Since the set of steps is a bit different for each platform, we recommend using &lt;a href="https://www.buildalon.com/start" rel="noopener noreferrer"&gt;Buildalon Quick Start&lt;/a&gt; to generate the steps for your specific platform. For example, an additional step — &lt;a href="https://www.buildalon.com/docs/actions/unity-xcode-builder?utm_source=devto" rel="noopener noreferrer"&gt;buildalon/unity-xcode-builder&lt;/a&gt; — is required to run XCode to build an iOS project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v6&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;clean&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.inputs.clean == 'true' }}&lt;/span&gt;
          &lt;span class="na"&gt;lfs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;submodules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;recursive'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/unity-setup@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;build-targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;matrix.build-target&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/activate-unity-license@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;license&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Personal'&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secrets.UNITY_USERNAME&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secrets.UNITY_PASSWORD&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Add Build Pipeline Package&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.UNITY_PROJECT_PATH }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm install -g openupm-cli&lt;/span&gt;
          &lt;span class="s"&gt;openupm add com.virtualmaker.buildalon&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/unity-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Project Validation&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;log-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;project-validation'&lt;/span&gt;
          &lt;span class="na"&gt;build-target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;matrix.build-target&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-quit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-batchmode&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-executeMethod&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Buildalon.Editor.BuildPipeline.UnityPlayerBuildTools.ValidateProject'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/unity-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;matrix.build-target&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}-Build'&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;log-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;matrix.build-target&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}-Build'&lt;/span&gt;
          &lt;span class="na"&gt;build-target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;matrix.build-target&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-quit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-batchmode&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-executeMethod&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Buildalon.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;matrix.build-args&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v6&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;upload-artifact&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Upload&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;matrix.build-target&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Artifacts'&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;success() || failure()&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;compression-level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
          &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.run_number&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}.${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.run_attempt&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}-${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;matrix.os&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}-${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;matrix.build-target&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}-Artifacts'&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;${{ github.workspace }}/**/*.log&lt;/span&gt;
            &lt;span class="s"&gt;${{ env.UNITY_PROJECT_PATH }}/Builds/StandaloneWindows64/**/*.exe&lt;/span&gt;
            &lt;span class="s"&gt;${{ env.UNITY_PROJECT_PATH }}/Builds/StandaloneWindows64/**/*.dll&lt;/span&gt;
            &lt;span class="s"&gt;${{ env.UNITY_PROJECT_PATH }}/Builds/StandaloneWindows64/**/*_Data&lt;/span&gt;
            &lt;span class="s"&gt;${{ env.UNITY_PROJECT_PATH }}/Builds/StandaloneWindows64/MonoBleedingEdge/&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Clean Artifacts&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pwsh&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;# Clean Logs&lt;/span&gt;
          &lt;span class="s"&gt;Get-ChildItem -Path "${{ env.UNITY_PROJECT_PATH }}" -File -Filter "*.log" -Recurse | Remove-Item -Force&lt;/span&gt;

          &lt;span class="s"&gt;$artifacts = "${{ env.UNITY_PROJECT_PATH }}/Builds"&lt;/span&gt;
          &lt;span class="s"&gt;Write-Host "::debug::Build artifacts path: $artifacts"&lt;/span&gt;

          &lt;span class="s"&gt;if (Test-Path -Path $artifacts) {&lt;/span&gt;
            &lt;span class="s"&gt;try {&lt;/span&gt;
              &lt;span class="s"&gt;Remove-Item $artifacts -Recurse -Force&lt;/span&gt;
            &lt;span class="s"&gt;} catch {&lt;/span&gt;
              &lt;span class="s"&gt;Write-Warning "Failed to delete artifacts folder file: $_"&lt;/span&gt;
            &lt;span class="s"&gt;}&lt;/span&gt;
          &lt;span class="s"&gt;} else {&lt;/span&gt;
            &lt;span class="s"&gt;Write-Host "::debug::Artifacts folder not found."&lt;/span&gt;
          &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Add Secrets
&lt;/h3&gt;

&lt;p&gt;To activate the Unity license, the build machine needs access to your Unity username and password. You should never hardcode these values in your workflow file, as they can be exposed to the public. Instead, you can use GitHub Secrets to securely store sensitive information.&lt;/p&gt;

&lt;p&gt;Go to your repository on GitHub, click on &lt;code&gt;Settings&lt;/code&gt; &amp;gt; &lt;code&gt;Secrets &amp;amp; Variables&lt;/code&gt; &amp;gt; &lt;code&gt;Actions&lt;/code&gt; and add the following secrets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;UNITY_USERNAME&lt;/code&gt;: Your Unity username, usually your email.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UNITY_PASSWORD&lt;/code&gt;: Your Unity password.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3admp3ofy814va24g7s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3admp3ofy814va24g7s.png" alt="Locating GitHub Secrets" width="800" height="106"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8wfmeoj81ew86ut50di.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8wfmeoj81ew86ut50di.png" alt="Adding GitHub Secrets" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  For Unity Pro:
&lt;/h4&gt;

&lt;p&gt;If you're using Unity Pro, you also need to add the &lt;code&gt;UNITY_SERIAL&lt;/code&gt; secret with your serial key. Then go back to your workflow file and update the &lt;code&gt;activate-unity-license&lt;/code&gt; step to use the secrets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;buildalon/activate-unity-license@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;license&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Professional'&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secrets.UNITY_USERNAME&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secrets.UNITY_PASSWORD&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
    &lt;span class="na"&gt;serial&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secrets.UNITY_SERIAL&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Install the Buildalon GitHub App
&lt;/h3&gt;

&lt;p&gt;To use Buildalon runners, you need to install the Buildalon GitHub App on your repository. This app gives you access to Buildalon runners to run your workflows.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://github.com/apps/buildalon/" rel="noopener noreferrer"&gt;Install the Buildalon App&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Buildalon runners are optimized for Unity builds and enable incremental builds, which will speed up your build times considerably.&lt;/p&gt;

&lt;p&gt;If you prefer to use GitHub's runners, you can skip this step, but make sure you update the runner label in your workflow file (for example, &lt;code&gt;windows-latest&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7: Push Your Changes to Start a Build
&lt;/h3&gt;

&lt;p&gt;Once you've added the workflow file and secrets to your repository, push your changes to GitHub. If your trigger conditions are met, GitHub Actions will automatically start the workflow and build your Unity project. You can find the status of the workflow in the &lt;code&gt;Actions&lt;/code&gt; tab of your repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8: Monitor Your Build
&lt;/h3&gt;

&lt;p&gt;Once the workflow is running, you can monitor its progress in the &lt;code&gt;Actions&lt;/code&gt; tab of your repository. You can view the logs of each step to see what's happening during the build process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmxel5zgj83k1zbyj7di.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmxel5zgj83k1zbyj7di.png" alt="Click on GitHub Actions Tab" width="800" height="94"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faiabbk0edanv5y3bzer6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faiabbk0edanv5y3bzer6.png" alt="View GitHub Run Logs" width="800" height="694"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If there are errors, you can usually find them in the &lt;code&gt;Summary&lt;/code&gt; page. Fix the errors in your project and push the changes to trigger a new build.&lt;/p&gt;

&lt;p&gt;For more help with common errors, take a look at the &lt;a href="https://www.buildalon.com/docs/troubleshooting?utm_source=devto" rel="noopener noreferrer"&gt;Buildalon troubleshooting page&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 9: Retrieve Your Build Artifacts
&lt;/h3&gt;

&lt;p&gt;Once the build is complete, you can download the build artifacts from the bottom of the &lt;code&gt;Summary&lt;/code&gt; page. These artifacts will contain the build files generated by Unity, such as the executable and data files.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgb1s8oe959qsyr8iyw1o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgb1s8oe959qsyr8iyw1o.png" alt="Download Artifacts from the Summary Page" width="800" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;🎉 Congratulations, you now have automated builds! 🎉&lt;/p&gt;

&lt;p&gt;Here are some ideas on how to augment your new workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add more build targets to the matrix to build for different platforms.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.buildalon.com/docs/workflows/test?utm_source=devto" rel="noopener noreferrer"&gt;Run unit tests automatically&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.buildalon.com/docs/workflows/deploy?utm_source=devto" rel="noopener noreferrer"&gt;Deploy your builds to an app store&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/webhooks/about-webhooks" rel="noopener noreferrer"&gt;Add webhooks&lt;/a&gt; to notify your team when a build is complete.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/managing-a-branch-protection-rule" rel="noopener noreferrer"&gt;Create a branch protection policy&lt;/a&gt; to ensure that all code changes are tested before merging.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.virtualmaker.dev/blog/automating-unity-builds-with-github-actions?utm_source=devto" rel="noopener noreferrer"&gt;Original Article&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.virtualmaker.dev?utm_source=devto" rel="noopener noreferrer"&gt;Virtual Maker Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.buildalon.com?utm_source=devto" rel="noopener noreferrer"&gt;Buildalon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.virtualmaker.dev/products?utm_source=devto" rel="noopener noreferrer"&gt;More Unity Tools&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>unity3d</category>
      <category>gamedev</category>
      <category>devops</category>
      <category>githubactions</category>
    </item>
  </channel>
</rss>
