Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

How to extend PSDImporter and edit created GameObjects?

Discussion in '2D' started by Xarbrough, Nov 3, 2020.

  1. Xarbrough

    Xarbrough

    Joined:
    Dec 11, 2014
    Posts:
    1,188
    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.
     
    tonytopper likes this.
  2. Xarbrough

    Xarbrough

    Joined:
    Dec 11, 2014
    Posts:
    1,188
    So it seems I was missing a tiny detail that made my importer not work, but here is a functional example:

    Code (CSharp):
    1. [ScriptedImporter(1, "psd", AutoSelect = false)]
    2. public class PSDImporterOverride : PSDImporter
    3. {
    4.     public override void OnImportAsset(AssetImportContext ctx)
    5.     {
    6.         base.OnImportAsset(ctx);
    7.  
    8.         var go = ctx.mainObject as GameObject;
    9.         var renderers = go.GetComponentsInChildren<SpriteRenderer>();
    10.         foreach (var r in renderers)
    11.         {
    12.             if (r.gameObject.name.StartsWith("Item_"))
    13.                 r.gameObject.AddComponent<BoxCollider2D>();
    14.         }
    15.     }
    16. }
    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).
     
  3. tonytopper

    tonytopper

    Joined:
    Jun 25, 2018
    Posts:
    225
    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.
     
  4. Xarbrough

    Xarbrough

    Joined:
    Dec 11, 2014
    Posts:
    1,188
    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:

    Code (CSharp):
    1.     using System.Collections.Generic;
    2.     using System.IO;
    3.     using UnityEditor;
    4.     using UnityEditor.AssetImporters;
    5.     using UnityEditor.U2D;
    6.     using UnityEditor.U2D.PSD;
    7.     using UnityEngine;
    8.     using UnityEditorInternal;
    9.     using UnityEngine.Rendering;
    10.  
    11.     /// <summary>
    12.     /// The PSDImporter creates a GameObject hierarchy from imported
    13.     /// Photoshop documents with the PSB extension. During this process
    14.     /// we add custom components, modify names and add tags to special
    15.     /// objects such as items, proxies or tools.
    16.     /// </summary>
    17.     [ScriptedImporter(version: 22, "psb")]
    18.     public class PSDImporterOverride : PSDImporter
    19.     {
    20.         private AssetImportContext context;
    21.         private string fileName;
    22.  
    23.         public override void OnImportAsset(AssetImportContext ctx)
    24.         {
    25.             base.OnImportAsset(ctx);
    26.             this.context = ctx;
    27.             this.fileName = Path.GetFileNameWithoutExtension(ctx.assetPath);
    28.  
    29.             if (fileName.StartsWithFast("Room_") == false)
    30.                 return;
    31.  
    32.             PostprocessPrefab();
    33.         }
    34.  
    35.         private void PostprocessPrefab()
    36.         {
    37.             var go = context.mainObject as GameObject;
    38.  
    39.             if (go == null)
    40.             {
    41.                 context.LogImportError(
    42.                     "Unable to tag items because the main object is not a prefab.");
    43.                 return;
    44.             }
    45.  
    46.             AddCustomComponents(go);
    47.             EnsureTagsExists("Item", "Proxy");
    48.             SetupItemsAndToolsInHierarchy(go.transform);
    49.             ExpandSortingOrder(go);
    50.             CreateSpriteAtlas();
    51.         }
    52.  
    53.         private void AddCustomComponents(GameObject go)
    54.         {
    55.             string layerName = "Room";
    56.  
    57.             if (fileName.Contains("CloseUp_"))
    58.                 layerName += "_CloseUp";
    59.  
    60.             // The sorting group will ensure the correct sorting layer
    61.             // for rendering, but it will not influence 2D/UI raycasting.
    62.             // So, we also need to set the layers on individual SpriteRenderers.
    63.             var sortingGroup = go.AddComponent<SortingGroup>();
    64.             sortingGroup.sortingLayerName = layerName;
    65.  
    66.             foreach (var spriteRenderer in go.GetComponentsInChildren<SpriteRenderer>())
    67.                 spriteRenderer.sortingLayerName = layerName;
    68.  
    69.             go.AddComponent<ItemStateManager>();
    70.             go.AddComponent<RoomState>();
    71.         }
    72.  
    73.         private static void EnsureTagsExists(params string[] tags)
    74.         {
    75.             var existingTags = InternalEditorUtility.tags;
    76.             foreach (string tag in tags)
    77.                 EnsureTagExists(existingTags, tag);
    78.         }
    79.  
    80.         private static void EnsureTagExists(string[] existingTags, string tag)
    81.         {
    82.             if (ArrayUtility.Contains(existingTags, tag))
    83.                 return;
    84.  
    85.             InternalEditorUtility.AddTag(tag);
    86.         }
    87.  
    88.         private void SetupItemsAndToolsInHierarchy(Transform transform)
    89.         {
    90.             GameObject go = transform.gameObject;
    91.  
    92.             // At this point, the item is not yet tagged,
    93.             // so don't use the IsItem or other extension methods.
    94.             if (IsProxy(go))
    95.             {
    96.                 SanitizeSpecialName(go);
    97.                 go.tag = "Proxy";
    98.                 go.name = go.name.Replace("_Proxy", "");
    99.             }
    100.  
    101.             else if (IsCollectible(go, "Item_"))
    102.                 SetupCollectibleGameObject(go, "Item");
    103.  
    104.             for (int i = 0; i < transform.childCount; i++)
    105.                 SetupItemsAndToolsInHierarchy(transform.GetChild(i));
    106.         }
    107.  
    108.         private static bool IsProxy(GameObject go)
    109.             => go.name.Contains("_Proxy");
    110.  
    111.         private static bool IsCollectible(GameObject go, string prefix)
    112.             => go.name.StartsWithFast(prefix);
    113.  
    114.         private void SetupCollectibleGameObject(GameObject go, string tag)
    115.         {
    116.             go.tag = tag;
    117.             var collider = go.AddComponent<CircleCollider2D>();
    118.             collider.radius *= 1.3f;
    119.             SanitizeSpecialName(go);
    120.         }
    121.  
    122.         private void SanitizeSpecialName(GameObject go)
    123.         {
    124.             // Item names should not contain any spaces, but this mistake
    125.             // can happen because Photoshop doesn't trim layer names.
    126.             if (go.name.Contains(" "))
    127.             {
    128.                 Debug.LogWarning(
    129.                     $"Fixing invalid space(s) in item name: '{go.name}' " +
    130.                     $"of imported asset '{context.mainObject.name}'.");
    131.  
    132.                 go.name = go.name.Replace(" ", "");
    133.             }
    134.         }
    135.  
    136.         private static void ExpandSortingOrder(GameObject go)
    137.         {
    138.             // By default, the PSDImporter will order according to the
    139.             // layers in Photoshop from 1 to LayerCount. But we
    140.             // would like to add particle effects in between:
    141.             foreach (var spriteRenderer in go.GetComponentsInChildren<SpriteRenderer>())
    142.                 spriteRenderer.sortingOrder *= 10;
    143.         }
    144. }
     
  5. tonytopper

    tonytopper

    Joined:
    Jun 25, 2018
    Posts:
    225
    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.