Hack’n’slash Interlude #1: Automating your Unity imports

Let’s take a step back and see how to improve our import workflow!

This article is also available on Medium.

Last time, we saw how to import a FBX file into our Unity project – a 3D model with some animations on it to improve the visuals of our hero. But when we added the asset to our project, we then had to spend quite a bit of time configuring the import settings.

Now – what if we want to add another character in our project? We’ll most likely want to set it up in a pretty similar way… but will we have to re-take all these steps again?

A better idea would be to automate this process so that, when we drag a new “character” asset into our project folder, all these settings are directly re-applied by Unity.

Today, we are going to look at two techniques to auto-set the import settings of your assets in Unity: the project presets and the AssetPostprocessor class.

Creating project presets by hand

Back in the 2018 version, Unity added a really interesting feature to the editor: the Presets. These assets store a bunch of settings across multiple components so that you can re-inject these onto another object later on in a flash.

Those presets can also be used whenever you import a new asset to configure it quickly based on a previous template thanks to the Preset Manager window that you can access in the Edit > Project Settings:

Example Preset Manager settings from the Unity docs

Here, for example, this image taken from the Unity docs shows us three presets:

  • the first “BoxCollider” preset will be applied to any new BoxCollider component (whenever you add one on an object) and auto-change its settings to match the ones stored in the BoxCollider preset
  • then, we have two presets for FBX imported assets that are only applied if either one of the two filters matches the path of the asset; so the first “FBXImporter” preset will be used for any asset that has the .fbx extension and is in a folder named Characters/ (thanks to the glob search filter), and the second “CookieImporter2” preset will be used specifically for Cookie-typed components

To create a preset, you need to select the “reference” asset to get the template configuration from, click the sliders icon in the top-right corner of the Inspector and finally click the “Save current to…” button:

This will open a file browser to choose where you want to store your preset asset. It can then picked in/dragged to a slot in the Preset Manager, or used directly on another object by clicking to the same sliders icon and then choosing the preset asset in the list.

Note that you can even mix these presets with a little bit of C# to get different presets per folder in your project 😉

This is pretty cool because it is quick to prepare and doesn’t require any complex code… but in my opinion, it can be a bit limiting, and also it requires that you have already an asset to extract the template config from… this is not necessarily the case if you are in a brand new project!

That’s why we’re going to look at a more in-depth option: creating our own custom AssetPostprocessor

Writing a custom AssetPostprocessor

If you want to specify and prepare your import settings so that various properties and options are automatically applied on your assets when they are imported in the project, you can also define a custom C# class that inherits from Unity’s AssetPostprocessor class.

Note: contrary to the presets, this technique only works for asset import (you can’t apply the settings afterwards, it can only be used at the moment of an import).

This class can declare one or more methods to inject your own logic so it’s run at some precise points of the import process: before/after a texture is imported, before/after a 3D mesh is imported, before/after all imported assets have been processed, etc.

Here, we are going to design our postprocessor class to automate the import of characters in our project and configure all new FBX files the same as our guard. We’ll however differentiate between the files with and without an embedded mesh, like we did last time with our first and other FBX assets.

Why naming conventions are important

There are various ways of exploring your file and extracting info on it so that you know if it has a model nested inside or not.

But, in truth, most of the time, the best way to automate your workflow is to decide beforehand on a precise naming convention and have all the designers and devs follow it to a tee. This way, you can assume some rules on the name of your assets, and you have a more robust management of the project done by actual humans (instead of a fuzzy logic run by the computer that may not apply to this specific situation).

Of course, it does require that your team understands and uses the convention. Usually, you’ll also develop little tools for the rest of the software in the production chain so that this naming stuff is (semi-)automated and people don’t have to worry too much about it.

Anyway – I’m going to use this naming convention technique here and assume that:

  • all my character FBX files are imported in a Imports/Characters/<character_model> folder, where the last part depends on the model and allows me to quickly identify which file I’m talking of (e.g. here: Imports/Characters/CastleGuard)
  • the file (or one of the files) with the model embedded inside has to be prefixed with the underscore _ symbol, so it is marked as the “model file” – ideally, this should be a basic FBX file in terms of animation, with just a T-Pose for example
  • it contains a 3D mesh and also textures and materials that we want to extract to a Textures/Characters/<character_model> folder, where the last part is the same as the one above (e.g. here: Textures/Characters/CastleGuard)
  • the other files in the same folder are animations for the same character, i.e. they will use the same avatar but we will disregard their materials/textures because we don’t need the 3D mesh inside, only the animation data is relevant
  • whenever you add a @ symbol at the end of a character FBX file, all the animations inside it will be marked to loop

So, for my guard model, I’ll need to have the following setup:

That designates the first file as the “model file” and the two others as matching animation FBX files that should each loop.

Of course, be careful: you need to name the assets properly before you import them in Unity, since our import process will rely on these naming conventions! This is just to give you an idea of how you can prepare some files for our soon-to-be asset postprocessor 😉

A first approach of the character asset postprocessor

Ok – time to actually write this thing!

First things first, let’s create a new file called CharacterAssetImporter.cs that we place in a Editor/ folder in our project assets directory:

In Unity, the Editor folder is a special directory that the engine knows not to build with the rest of your game: when you build your project, any Editor folder (and all their subdirectories) in your project will be simply ignored.

To begin with, I want to declare a few static variables so that I can easily change the name of the folders to read from and write to:

This allows me, for example, to add a basic util function to check if the asset to import should be processed by this asset importer script – I’ll basically look at the extension of the file and insure it is an FBX, and also at its path to insure it is in the right folder:

I can also make another function, _GetCharacterFolder(), to automatically parse the asset path and get the model-dependent folder name:

Now, let’s start to write some specific hooks to inject our own import processing logic.

If you look at the docs, you’ll find one called OnPreprocessModel(): it sounds really nice for our use case – this hook is called just before importing a .fbx, .mb… file, which is exactly what we want. In this method, we can therefore reproduce our manual settings about the axis conversion baking, the rig type and the materials type for example.

So we could go ahead and follow this logic:

This code contains a lot of things already; in particular, because our class inherits from the AssetPostprocessor built-in, Unity will directly give us access to two nice variables: assetPath (the asset of the path currently being processed) and assetImporter (the reference to the actual asset importer).

This means we’ll be able to check the assetPath for specific characters such as the underscore _ prefix or the @ suffix easily. And also, here, we can cast the assetImporter to a ModelImporter variable (remember that this function is only run for models so the cast is valid) to access more specific fields like the bakeAxisConversion boolean or the type of rig to use. Most of the settings are different between the “model file” and the other ones, except for the “Model”-related options.

But you see they are also a few comments for the more complex parts, like the materials and textures extraction and I haven’t got the code for these yet. That’s because we need to anticipate some difficulties before we dive into the code…

Understanding the import process… and anticipating dependencies!

The first problem is for the materials/textures extraction part: Unity provides us with a nice ExtractTextures() method, but there is no “ExtractMaterials()”; so we’ll need to do something a bit more convoluted to get these materials.

And, most importantly, there will be some dependencies between our assets, their sub-assets and the additional assets we generate during the import process. More specifically: the avatar generated by the “model file” needs to be used by the other FBX files, the textures and materials of this “model file” need to be extracted as regular assets into the right directories and the textures need to be properly assigned to the materials.

The tricky thing with this import process is that we can’t control the order in which assets are imported: the whole thing happens in bulk so that it doesn’t take too long. Which is great, obviously. But… which, in our case, also means that we can’t perform the entire import process in just one go, naively.

For example, let’s focus on our avatar for a second.

Suppose the “model file” is imported first: we generate the avatar, and we can thus reload it afterwards when we get to the processing of the other FBX files. Great. Now, suppose the “model file” is imported last: this time, we don’t have the avatar generated early enough for us to “inject” it as the reference in the other files!

This means that for the avatar and the texture/material setup, we’re going to have to find some techniques to delay the logic until the assets are ready.

Now: it’s essential to note that we have three types of dependency here (for the avatar, for the material and for the textures):

  1. the avatar asset will only be available once we are finished processing the “model file” and, since we don’t control the order of the assets… this means we can’t assume it’s ready before we have completely processed all files
  2. on the other hand, the textures will be available as soon as the model phase of the import process is done: we can extract them directly with ExtractTextures() as soon as we get the FBX file
  3. and for the materials, we need to have our FBX file (because it contains the material as a sub-asset) but we also need the textures we extracted before – so we’ll need to insure we have prepared the import of all our files

Because we have multiple types of dependencies, this means we can devise multiple techniques to solve our problems.

Extracting the textures and the materials

To begin with, let’s take care of the simple code – let’s extract the textures with the ExtractTextures() method and move our image sub-assets to the right texture folder:

At this point, we have generated one or more texture assets (extracted from our FBX file) that we need to associate with some material(s). The material(s) are also part of the FBX file but, contrary to the textures, we don’t have an “ExtractMaterial()” built-in method.

The solution here is to use another hook, the OnPostprocessAllAssets(), that is called at the end of the import process: in this method, we’ll be able to access the extracted textures, extract the materials and link the textures to the materials.

First, if the asset is a FBX model, we’ll extract the material(s) sub-asset(s) from it:

Important note: make sure to declare the OnPostprocessAllAssets() method as static, otherwise it won’t run during the import process!

Most of this code is about using the right functions from Unity’s AssetDatabase namespace: we load the sub-assets, extract them as regular assets, mark them as dirty and force the assets re-update.

Then, we can check for texture assets, search for the associated material and, if it exists, assign the texture to the proper texture slot. Let’s prepare some util functions to know if and how we can process the asset:

Note: of course, feel free to add more file extensions if your image use different file formats as the ones listed here! 😉

Now, by default, the materials we create use a Unity standard shader and are therefore PBR (physical-based render) materials with various textures: an albedo, a normal map, a specular map…

As usual, we can rely on a naming convention to know which type of texture we are dealing with. Here, because I’m using Mixamo models, my textures can look like this:

  • <material name>__diffuse.png: the base colour texture
  • <material name>__normal.png: the normal map
  • <material name>__specular.png: the specular/glossiness map

where the <material name> is the name of the material asset we need to inject these textures in (that we extracted from our FBX file).

We can define a _ParseTexture() function to split our texture asset path and get the material name on one side, and the texture type on the other:

📌  Tip: if you need to return more than one value from a C#, you can use a tuple like here to pack together multiple results. They can be of different types, and you can then deconstruct them later on to retrieve the single values.

Finally, let’s use all of this in our OnPostprocessAllAssets() method to link our textures in the matching materials:

Referencing the “model file” avatar

For the avatar, we said before that we have to wait for all the assets to be fully imported to make sure the avatar asset has been generated. My technique will be the following:

  • for each FBX file, if it is not the “model file”, we will try to get the associated “model file” (i.e. the FBX file in the same folder with a name that starts with an underscore _), get the avatar sub-asset in it and set it as the source avatar
  • if the avatar cannot be loaded, we will say the asset is “incomplete” by increasing a counter
  • finally, if any of the assets is “incomplete” (so, if the counter is not null), then we will force a full reload to load and link the avatar asset in a second phase

Also, since there is only one avatar for a “model file”, we can cache it the first time we load it and re-use this reference afterwards.

All of this boils down to this C# snippet:

Setting up our animation clips

The final step is to restore our “Animation” settings: we need to rename the animations and, if the asset ends with the @ symbol, set it to loop. To do this, we’ll use a final hook: the OnPreprocessAnimation() method.

The code is pretty self-explanatory – we’ll simply get the default clip animations, update the anim names based on the asset name and optionally set the loopTime field:

Conclusion

In this interlude, we improved our import workflow by setting up an AssetPostprocessor: we now have an automated process to directly configure our character FBX models.

Next time, we will work on a basic attack system with multi-punches combos

Leave a Reply

Your email address will not be published.