How do I add custom components or generally modify the created prefab GameObjects when importing a PSB file with the PSDImporter?
I’m using the Character Rig option so that the importer splits existing layers into individual sprites and then creates a prefab with GameObjects for each layer. This works perfectly, but now I would like to add a custom component to each layer GameObject with a special name prefix.
Ideally, I’d like to handle this the way it used to work with the AssetPostprocessor e.g. OnPostprocessModel. Or do I need to implement my own scripted importer for this? The docs show how to set an override importer, but I’d like to do this for all PSD files automatically before import and I also don’t see how I could cleanly modify the created GameObjects since most of the PSDImporter API is private.
1 Like
So it seems I was missing a tiny detail that made my importer not work, but here is a functional example:
[ScriptedImporter(1, "psd", AutoSelect = false)]
public class PSDImporterOverride : PSDImporter
{
public override void OnImportAsset(AssetImportContext ctx)
{
base.OnImportAsset(ctx);
var go = ctx.mainObject as GameObject;
var renderers = go.GetComponentsInChildren<SpriteRenderer>();
foreach (var r in renderers)
{
if (r.gameObject.name.StartsWith("Item_"))
r.gameObject.AddComponent<BoxCollider2D>();
}
}
}
The mainObject is actually the prefab and not a texture, so I can edit it after the import happens.
Now I’d also like to set my override importer as the default for any PSB asset, not sure if I can make this work via the AssetPostprocessor or if I’d rather to the prefab modification in PostProcessAllAssets (not sure if that would work).
Diving into asset import customization myself. I need to set the sorting layer of the SpriteRenderer. I kind of thought the PSD Importer settings would have this but I don’t see that anywhere.
Figuring out where to jack in has been a time-consuming pain. I have tried OnPostprocessSprites, OnPostprocessPrefab, and OnPostprocessAllAssets and can’t seem to get everything I’d like to make a graceful solution. It’s been good to learn this part of Unity’s stack but I can’t help but feel like it’s taking too long.
Going to try OnPostprocessGameObjectWithUserProperties next.
If anyone has recommendations regarding customizing the PSD Importer process, seems like the community could use it.
You can set the sorting layer in the prefab that is being generated during the import process. Then if you drag the PSD into the scene it will place the prefab, which is also updated if you let the importer run again by making a change and incrementing the version count. This was in Unity version 2021.1.12f1:
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.AssetImporters;
using UnityEditor.U2D;
using UnityEditor.U2D.PSD;
using UnityEngine;
using UnityEditorInternal;
using UnityEngine.Rendering;
/// <summary>
/// The PSDImporter creates a GameObject hierarchy from imported
/// Photoshop documents with the PSB extension. During this process
/// we add custom components, modify names and add tags to special
/// objects such as items, proxies or tools.
/// </summary>
[ScriptedImporter(version: 22, "psb")]
public class PSDImporterOverride : PSDImporter
{
private AssetImportContext context;
private string fileName;
public override void OnImportAsset(AssetImportContext ctx)
{
base.OnImportAsset(ctx);
this.context = ctx;
this.fileName = Path.GetFileNameWithoutExtension(ctx.assetPath);
if (fileName.StartsWithFast("Room_") == false)
return;
PostprocessPrefab();
}
private void PostprocessPrefab()
{
var go = context.mainObject as GameObject;
if (go == null)
{
context.LogImportError(
"Unable to tag items because the main object is not a prefab.");
return;
}
AddCustomComponents(go);
EnsureTagsExists("Item", "Proxy");
SetupItemsAndToolsInHierarchy(go.transform);
ExpandSortingOrder(go);
CreateSpriteAtlas();
}
private void AddCustomComponents(GameObject go)
{
string layerName = "Room";
if (fileName.Contains("CloseUp_"))
layerName += "_CloseUp";
// The sorting group will ensure the correct sorting layer
// for rendering, but it will not influence 2D/UI raycasting.
// So, we also need to set the layers on individual SpriteRenderers.
var sortingGroup = go.AddComponent<SortingGroup>();
sortingGroup.sortingLayerName = layerName;
foreach (var spriteRenderer in go.GetComponentsInChildren<SpriteRenderer>())
spriteRenderer.sortingLayerName = layerName;
go.AddComponent<ItemStateManager>();
go.AddComponent<RoomState>();
}
private static void EnsureTagsExists(params string[] tags)
{
var existingTags = InternalEditorUtility.tags;
foreach (string tag in tags)
EnsureTagExists(existingTags, tag);
}
private static void EnsureTagExists(string[] existingTags, string tag)
{
if (ArrayUtility.Contains(existingTags, tag))
return;
InternalEditorUtility.AddTag(tag);
}
private void SetupItemsAndToolsInHierarchy(Transform transform)
{
GameObject go = transform.gameObject;
// At this point, the item is not yet tagged,
// so don't use the IsItem or other extension methods.
if (IsProxy(go))
{
SanitizeSpecialName(go);
go.tag = "Proxy";
go.name = go.name.Replace("_Proxy", "");
}
else if (IsCollectible(go, "Item_"))
SetupCollectibleGameObject(go, "Item");
for (int i = 0; i < transform.childCount; i++)
SetupItemsAndToolsInHierarchy(transform.GetChild(i));
}
private static bool IsProxy(GameObject go)
=> go.name.Contains("_Proxy");
private static bool IsCollectible(GameObject go, string prefix)
=> go.name.StartsWithFast(prefix);
private void SetupCollectibleGameObject(GameObject go, string tag)
{
go.tag = tag;
var collider = go.AddComponent<CircleCollider2D>();
collider.radius *= 1.3f;
SanitizeSpecialName(go);
}
private void SanitizeSpecialName(GameObject go)
{
// Item names should not contain any spaces, but this mistake
// can happen because Photoshop doesn't trim layer names.
if (go.name.Contains(" "))
{
Debug.LogWarning(
$"Fixing invalid space(s) in item name: '{go.name}' " +
$"of imported asset '{context.mainObject.name}'.");
go.name = go.name.Replace(" ", "");
}
}
private static void ExpandSortingOrder(GameObject go)
{
// By default, the PSDImporter will order according to the
// layers in Photoshop from 1 to LayerCount. But we
// would like to add particle effects in between:
foreach (var spriteRenderer in go.GetComponentsInChildren<SpriteRenderer>())
spriteRenderer.sortingOrder *= 10;
}
}
Hmm … I had been leery of overriding the PSDIMporter base class but you’re making me rethink that.
As a temporary stop-gap, I just made a custom inspector button to set all the sorting layers, but I am going to get sick of clicking that button for each character I import.
Thank you for sharing your example.