Tips and tricks


CGI Сoffee

Tips and tricks on various CGI-related topics.


Protect Unity Prefabs From Changes With the ISerializationCallbackReceiver Interface

Unity prefabs are awesome. They are easy to create, easy to instantiate, and with the current versions of the Unity Editor can be used to create prefab variants and even nest prefabs within prefabs, allowing you to modularize design of systems and scene objects.

But this simplicity comes at the cost of a higher risk of screwing things up the larger your project or the team grow...

All because of this little "Apply All" Inspector button, which is both a blessing and a curse:

Unity Apply All overrides button

You know what I'm talking about. Imagine having a prefab for an object or an important interactive actor, and scattering it all over the scenes in the project, each one — with its own unique overrides for the position, variable values or even MonoBehavior component scripts attached or removed. Obviously they all are based on the same shared prefab asset on disk, which is expected to have a particular set of initial values and components, which are then either used "as is" or are slightly changed here and there in the scenes.

The problem is, it only takes one careless press of the "Apply All" button to screw up the prefab for every single instance of that prefab in all scenes! And chances are, you or a member of your team will eventually press this button and completely change the base prefab on disk, which all scene instances are based upon. And the more prefabs in your project, or members in your team, the higher the chance of this event happening. Sometimes stealthily, which is even worse... Thank Heavens for version control!

Unfortunately, Unity doesn't provide a way to disable this button for certain prefabs (at least I wasn't able to find a way to do so). That's why when designing my previous game, even though I was the only developer and scene builder, I decided to save myself the headache and created a custom bare-bones "ActorLoader" prefab. I would then exclusively use this prefab to place interactive instantiatiables in the scenes. Such a prefab had just a handful of fields which were meant to always be overriden and not to use base properties stored in the shared prefab: a prefab type enum (used to determine which prefab to instantiate from the Resources folder), starting Transform parameters and a couple of modifiers, all overridden right away to ensure that no changes to the base prefab asset would ever break ActorLoaders anywhere in the project. And this approach worked wonderfully.

Such "loaders" are in fact considered standard practice in the industry in contrast to scattering actual prefabs in the scenes, for a lot of reasons, which I won't list here. But this still doesn't solve the original problem of prefabs being modifiable with a press of a button!

A Solution!

Unity prefab is just a serialized object. Which means we can tap into the serialization routine and do something when Unity serializes or deserializes a prefab in the Editor. All you need to do is extend one or several MonoBehavior components attached to the prefab from the ISerializationCallbackReceiver interface, and implement a couple of methods.

Here's how I did it in my current project: inherited a component from MonoBehavior and the interface:

// See: https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html
public class MyClass : MonoBehaviour, ISerializationCallbackReceiver

And then implemented a custom ISerializationCallbackReceiver.OnBeforeSerialize() routine, which is executed right before Unity commits any changes to the serialized object, which is a prefab in our case. I've commented it to help you understand how it works. Nothing special, but gets the job done:

#region ISerializationCallbackReceiver
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
    // This can only be done inside UNITY_EDITOR since we can not check prefab status in a build!
    #if UNITY_EDITOR
    // IMPORTANT: EditorApplication.isPlayingOrWillChangePlaymode check must be done first.
    // Otherise Unity may report errors like "Objects are trying to be loaded during a domain backup"
    if (EditorApplication.isPlayingOrWillChangePlaymode || EditorApplication.isUpdating) return;
    // Validate prefab type
    if (UnityEditor.PrefabUtility.GetPrefabAssetType(this) != UnityEditor.PrefabAssetType.Regular) return;
    // Override properties only if this is a prefab asset on disk and not any of its scene instances
    if (UnityEditor.PrefabUtility.GetPrefabInstanceHandle(this)) return;
    // Finally, re-set any fields to initial or specific values for the shared asset prefab on disk
    // This protects these fields when "Apply Override" gets called from any of prefab's scene instances
    // Example: enforce default position, rotation and scale values for the prefab
    transform.localPosition = Vector3.zero;
    transform.localRotation = Quaternion.identity;
    transform.localScale = Vector3.one;
    // Example: your variables here
    // myFloat = 1f;
    // myAssetRef = null;
    // myClassInstance.myString = "Default string";
    #endif
}

// OnAfterDeserialize can be left empty
void ISerializationCallbackReceiver.OnAfterDeserialize() { }
#endregion

As you can see, this method makes sure that each serialization call to the shared prefab asset on disk goes through a custom procedure which re-sets values for particular fields and references, and can even check whether specific components are attached to the prefab root or any of its children, and either add them if they're missing, or remove ones that shouldn't be there. This procedure is called any time prefab is serialized, which includes the aforementioned "Apply All" button or any other ways to apply changes to the original asset.

And there you have it! With this approach any time you create a new prefab you can decide which shared asset fields and references should be protected from accidental overrides, and simply specify default/initial values for them inside the ISerializationCallbackReceiver.OnBeforeSerialize() method.

Using Cloth Simulation and Delta Mush Deformer for Skin Collision in Animation

I'd like to present the results of a study I conducted recently:

Story

Working on the animated short film I am constantly updating a list of tasks to do in order to achieve a descent final result. One particular topic took my sleep away recently when I remembered that in order to correct skinning errors on animated characters you'd either have to sculpt and then script a bunch of blend shapes or would need a muscle system available in your DCC software.

I then turned to Maya, which has had a muscle system for ages.

Maya Muscle system

Maya provides several deformers to manage soft- and hard-tissue interaction between muscles like smart-, self- and multi-object collisions. Even though they aren't terribly performant, they get the job done.

Maya multi-object collision

Unfortunately as soon as I would've used any of Maya's Muscle system tools I'd be tied down to a 185$ a month subscription plan for the whole duration of the project which would be outrageous. There is a lite-version of Maya available for indie gamedevs - Maya LT - which only costs 30$ a month, but it doesn't come with the Muscle system.

Maya vs Maya LT comparison

Hence I had to improvise and develop a simple and straightforward solution for this problem in a DCC of my choice.

VDB cloud with Softimage ICE and Redshift - a step-by-step tutorial

Since my first post about Softimage and OpenVDB was more of a brief overview than a tutorial, I decided to create a step-by-step guide on how to create a .vdb cloud-like grid with Softimage ICE using Mr.Core's (Oleg Bliznuk's) compounds and explain how they work on the way.

rendered vdb clouds

To follow the tutorial, download the compounds and connect the extracted workgroup in Softimage (File -> Plugin Manager -> Workgroups -> File -> Connect):

vdb compounds workgroup

Create and render OpenVDB volume grids with Softimage and Redshift renderer

Note: a detailed tutorial is now available.

Since current version of Redshift requires OpenVDB-compliant voxel grids for its volume rendering, we need to somehow generate and export .vdb files from Softimage (not everybody has access to Houdini, you know).

OpenVDB logo

Thanks to user Mr.Core from SI Community we have a set of ICE-compounds to do just that.

Mr.Core provided compounds to voxelize particles and geometry as well as perform actions on .vdb grids and polygonize them.

Here's an example on how to make a vdb cloud in Softimage, export it to a file and render with Redshift:

Softimage hotkeys mapping infographic

Since Autodesk officially EOL'd Softimage, XSI blogs and discussion groups have been gradually vanishing from the web which is a shame.

As a tribute to the great piece of software that Softimage is I will try to consolidate the most useful XSI-related stuff from the net.

Today we have a Softimage hotkeys infographic originally posted at XSIBase which is now non-existent. It's just too good to just perish so here are cleaned up versions in glorious PNG.