Validating Unity Builds the easier way


So there's one thing you do in every single game project, no matter what, at least once: you make a build (...unless you're just releasing the project source, or using a smaller engine like Pico)

I find making a good, solid build is repeatedly one of the most stressful things when producing a game - you suddenly need to check so many different parts of your project, suddenly find new bugs previously hidden, suddenly find the previous "fix later" tasks are "fix now or else".


One thing that's eluded me up unto this point is a way of automating more straightforward tasks of validation. For example:

  1. say you have a script which enables a stairway when you do an action, and that script is missing a target object,
  2. it won't error until a player is at that point in the game, maybe just before a checkpoint, so they lose a lot of progress.
  3. and it's likely the player won't be able to give a good description of what went wrong, giving you some feedback like: "I got to this place, did some stuff, but couldn't see a path anywhere? Did something break?"
  4. Which makes it harder for you to track the actual bug down - a simple missing reference.


All this is me leading up to the point of this devlog - in Unity, turns out there's a straightforward way to implement your own pre-build checks, and block a build if they fail! Hooray!

~

Links

~

How to use 'em

Ok, so you've got the links, and now you want to do some checks across a scene/project before running:

At it's most basic, you literally just need a class which implements one of the interfaces:

using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
using UnityEngine.SceneManagement;
 
public class MyBuildProcessor : IProcessSceneWithReport
{
    public int callbackOrder => 0;
    public void OnProcessScene(Scene scene, BuildReport report)
    {
        // do scene check stuff here
    }
}

This class should sit inside an Editor folder, as we're implementing UnityEditor namespaces. Once that's done, Unity will automatically find it, check which order to run them in with callbackOrder, and that's it!

The code inside can do stuff that any old MonoBehaviour component can do. Of course, note that this runs once, and is part of making your build, so tread lightly with actually editing the scene unless you know what you're doing.

So now we have our processor, which is ran as Unity processes every scene for a build - let's actually verify some code. Say we have our staircase script example I mentioned earlier, let's pretend it needs a Collider on it to act:

public class UnlockStairwayOnEnter : MonoBehaviour
{
    public GameObject stairwayToUnlock;
 
    private void OnTriggerEnter(Collider other)
    {
        if(other.CompareTag("Player")
            stairwayToUnlock.SetActive(true);
    }
} 

(For know-it-alls: yes we can add a RequireComponent attribute, but let's skip past that for this demonstration)

We have 2 things to check here:

  1. Does this component have a valid stairway to open?
  2. And does this component have a Collider to listen for (which is set as a Trigger)

So, in our processor script, we can do the following in OnProcessScene:

// (note: this is formatted weirdly to fit in itch.io's
// teeny column space for code excerpts)
 
// see last few lines of method
bool foundAnyStairErrors = false;
 
// the Scene itself isn't part of the hierarchy,
// so loop through every root object
foreach ( var rootGameObject in scene.GetRootGameObjects() )
{
    // get every single stairway, including inactive ones
    var stairways = rootGameObject
                      .GetComponentsInChildren
                        <UnlockStairwayOnEnter>
                        (true);
        
    foreach ( var stair in stairways )
    {
        // if no target, error
        if ( !stair.stairwayToUnlock )
        {
            Debug.LogError( 
                $"Stairway [{stair.gameObject.name}]
                doesn't have a target to unlock!" );
            foundAnyStairErrors = true;
        }
        // check for a collider, then check if it's a trigger
        if ( stair.TryGetComponent(out Collider collider) )
        {
            // alternatively, you can just set it true here,
            // but there's an argument to be made for checking 
            // it by hand, in case you've overloaded the object
            // with other components that depend on collision
            if(collider.isTrigger == false)
            {
                Debug.LogError( 
                    $"Stairway [{stair.gameObject.name}]'s
                    collider is not set as a trigger!" );
                foundAnyStairErrors = true;
            }
        }
        else
        {
            Debug.LogError( 
                $"Stairway [{stair.gameObject.name}]
                doesn't Collider to trigger with!" );
            foundAnyStairErrors = true;
        }
    }
}
 
// finally, if any errors were found, stop the build
// (You need to use this specific exception type)
// (It's better to log out all errors then halt a build)
if( foundAnyStairErrors )
    throw new BuildFailedException( 
                "Found error(s) with Stairways!" );

And that's it - an automatic way of validating for easy-to-miss problems in a scene.

~

When it comes to checking a project, you can implement IPreprocessBuildWithReport in a class; as an example of what I'm doing:

  1. Use the AssetDatabase to find ScriptableObjects which contain a key for which Ink story knot to play
  2. Fetch all possible story knots, and store them in a list,
  3. Then run through every single Scriptable, checking every single Story knot against the list of all Knots
  4. Finally, throw an error if a Scriptable's knot isn't found

Strings can be the bane of a build, especially if a name subtly changes, or a prefix changes. Hunting down every case of a string can be soul-exhausting, so automating the validation when it really matters is wonderful!

I'm not sure of any limits with build processors, this is my first proper foray into this space, so there's plenty to explore, but I wanted to share

~

Happy Building

And yeah, that's it! Easy checks for making your build process easier, quicker, and less worrisome about having missed a single bad string or reference.

Hope this helps, here's to another week of working on Day of Maintenance 🔧🤖🔧

Get A Day of Maintenance (Jam Version)

Leave a comment

Log in with itch.io to leave a comment.