Scripting version control actions in the Unity Editor

With every new title we are working on, we strive to improve the way we work and optimize our development processes. Recently, while working on the upcoming title, Returner Zhero, we had the pleasure of working with Framebunker team, who helped us to identify some pain points and opportunities. (They are super nice people with years of experience in gaming industry, go check them out!).

Our game developer and programmer, Casper Nymose, shares his experience of how based on their feedback we created a “Request system” for audio assets and describes the benefits of using one. Using the system, our developers can request for an audio event to be made in FMOD, and our audio designer can create the event, assign the FMOD event reference to an asset, and submit that asset in version control, with very few steps taken inside one editor window.

 

The process

The typical process of creating an audio asset with an event reference used to be:
1. The developer creates an audio asset, puts in placeholder sound.
2. Developer contacts audio designer with a request.
3. Insert wait time.
4. The audio designer creates FMOD event, submits the new FMOD project.
5. The audio designer tells the developer the name of the event.
6. Insert wait time.
7. Developer assigns the event to asset and submits it.

Through the request system, we managed to convert the process to:

1. The developer creates an audio asset, marks it as a request, puts in a placeholder sound, submits it, and never has to touch this asset again.
2. Audio designer opens the request window in the Unity Editor and sees that there is a new request with a comment about the request.
3. Audio designer creates the event in FMOD, submits the FMOD project.
4. Through the request window, the audio designer assigns the FMOD event reference to the asset, and through the same window, he submits the updated asset to version control

 

 Benefits to the new workflow

Not only several trivial and error-prone steps of communication and actions in the editor are eliminated, but also the developer no longer has to send a message on another platform to the audio designer.
The audio designer no longer has to send matching responses with the correct FMOD event paths, and finally, the developer no longer has to assign that event to the audio asset to update the sound in the game.
And since we automatically kick off new builds of the game when submits are made to the version control, the next build will automatically have the new audio event reference included, with no other action taken than the audio designer fulfilling the request!
In the end, this means oceans of time saved for both developers and the audio designer.

This is what an asset looks like on the developer’s end:

And this is what the request window looks like to the audio designer:

The code

One of the most convenient parts of this process is the ability to submit the modified assets directly from the editor, without having to know their file paths, and Unity provides some utility for scripting these actions under the UnityEditor.VersionControl namespace.
The cool thing about it, is that it can be utilized in any process that involves modifying assets in some way, be it replacing placeholder visual FX objects in prefabs with production-ready ones, or creating material variants from textures.
First of all, the user must be connected to their version control server, in the following tab, found at ‘Project Settings -> Editor’.

The version control namespace in the Unity Editor contains some key classes:

Provider – This class generates tasks (submitting/checking out/reverting), and validates these tasks before you create them.
Task – A task generated by the Provider. After executing a task, we can know whether it was successful or not. Another useful function is the .Wait() method that blocks any actions in the editor until the task is in a finished state.
AssetList / Asset – A version control asset is simply based on the AssetDatabase path of your asset, and an AssetList is a list of these. We must always create tasks with instances of AssetList.
ChangeSet – A reference to a changeset in the version control client. Note that creating an instance of ChangeSet will not create a changeset in the client until it has been submitted through Provider.Submit(), which also has an option to only save the changeset, without submitting it. Existing changeset owned by the active user can also be found and modified through Provider.ChangeSets()
This is the Fileset class that wraps the creation of AssetLists and ChangeSet instances so that users can avoid using UnityEditor.VersionControl in their editor classes.

A Fileset is generated from an array of UnityEngine.Object (ScriptableObject, prefabs and other assets), and an optional changeset description.

using UnityEngine;
using UnityEditor;
using UnityEditor.VersionControl;

public class Fileset
{
    public AssetList m_Assets;
    public ChangeSet m_ChangeSet;

    public int Count { get { return m_Assets.Count; } }

    public Asset this[int iIndex]
    {
        get { return m_Assets[iIndex]; }
    }

    public Fileset(Object[] objects, string sSubmitMessage = "")
    {
        m_Assets = new AssetList();
        for (int i = 0; i < objects.Length; i++)
        {
            AddObject(objects[i]);
        }
        m_ChangeSet = new ChangeSet(sSubmitMessage);
    }

    public void AddObject(Object addObject)
    {
        string sPath = AssetDatabase.GetAssetPath(addObject);
        if (!string.IsNullOrEmpty(sPath))
        {
            Asset asset = Provider.GetAssetByPath(sPath);
            m_Assets.Add(asset);
        }
    }

    public void SetSubmitMessage(string sSubmitMessage)
    {
        m_ChangeSet = new ChangeSet(sSubmitMessage);
    }
}

 

Next is the Version Control Utility class that handles kicking off tasks like submitting and checking out files, and returnings whether the task was successful or not.
Here is an opportunity to return an enum that defines error states such as VERSIONCONTROL_INACTIVE, if that level of information is desired.
Note that calling .Wait on the generated tasks makes the editor freeze up while waiting for the action to complete. We do this because the operation is asynchronous, and we experienced issues with attempting to read the task progress while it was running.

 

using UnityEngine;
using UnityEditor.VersionControl;
using UnityEditor;

public class VersionControlUtility
{
	public static bool VersionControlActive { get { return Provider.isActive; } }

	public static bool CheckoutFileset(Fileset fileset)
	{
		if (!VersionControlActive || fileset.Count == 0)
			return false;

		AssetList aCheckoutAssets = new AssetList();
		for (int i = 0; i < fileset.Count; i++)
		{
			if ((fileset[i].state & Asset.States.CheckedOutLocal) == 0 && (fileset[i].state & Asset.States.LockedRemote) == 0)
			{
				aCheckoutAssets.Add(fileset[i]);
			}
		}
		if (aCheckoutAssets.Count == 0)
			return true;
		if (Provider.CheckoutIsValid(aCheckoutAssets))
		{
			Task checkoutTask = Provider.Checkout(aCheckoutAssets, CheckoutMode.Asset);
			checkoutTask.Wait();
			if (checkoutTask.success)
			{
				return true;
			}
		}
		return false;
	}

	public static bool SubmitFileset(Fileset fileset)
	{
		AssetDatabase.SaveAssets();
		if (!VersionControlActive)
			return false;

		if (Provider.SubmitIsValid(fileset.m_ChangeSet, fileset.m_Assets))
		{
			Task submitTask = Provider.Submit(fileset.m_ChangeSet, fileset.m_Assets, fileset.m_ChangeSet.description, false);
			submitTask.Wait();
			if (submitTask.success)
			{
				return true;
			}
		}
		return false;
	}

	public static bool RevertFileset(Fileset fileset)
	{
		if (!VersionControlActive)
			return false;

		if (Provider.RevertIsValid(fileset.m_Assets, RevertMode.Normal))
		{
			Task revertTask = Provider.Revert(fileset.m_Assets, RevertMode.Normal);
			revertTask.Wait();
			if (revertTask.success)
			{
                AssetDatabase.SaveAssets();
                return true;
			}
		}
		return false;
	}
}

As a note, never attempt to perform more than one task in a single update of the editor. When first implementing this, we ran into issues with checking out and submitting files in the same block of code, which for whatever reason resulted in empty changelists, and the Perforce visual client getting out of sync with the Unity Editor version control state.

 

Lastly, a use-case:

//This is executed when the user presses the "Check out assets" button, 
//after having assigned FMOD event references in the request window.

//Get the assets the user has filled in audio event references for and create a Fileset
AudioAsset[] aSubmitAssets = aValidAssets.ToArray();
Fileset checkoutSet = new Fileset(aSubmitAssets);

//If we successfully check out the files
if (VersionControlUtility.CheckoutFileset(checkoutSet))
{
    //Modify all the files accordingly
    for (int i = 0; i < aSubmitAssets.Length; i++)
    {
            aSubmitAssets[i].m_Event = m_Events[i];
            aSubmitAssets[i].m_IsRequest = false;
            aSubmitAssets[i].m_SubmissionReady = true;
            aSubmitAssets[i].m_RequestDescription = "";
            AudioAssetEditor.RebuildEventInfo(aSubmitAssets[i]);
            EditorUtility.SetDirty(aSubmitAssets[i]);
    }
    ShowNotification(new GUIContent("Checkout Success!"));
}
else
{
    ShowNotification(new GUIContent("Checkout Failed!"));
} 

Learn more about the author: https://connect.unity.com/u/58502e0732b3060024578e1a


		
We have placed cookies on your computer to help make this website better. Read the cookies policy
yes, I accept the cookies