
Posted May 15, 2021 by BigHandInSky
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:
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!
~
~
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:
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:
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
~
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 🔧🤖🔧