Sharing data between Editor and Runtime classes in a custom Unity importer

Hey all! I am writing a custom importer for Unity that parses a proprietary point cloud format.

Here is my folder structure:

Editor/
   MyCustomImporter.cs
Runtime/
   PointCloudRenderer.cs
   FrameManager.cs

At import time, MyCustomImporter will fire a hook when my format import is detected and unzip it to a folder on the user machine. MyCustomImporter inherits, unsurprisingly, form ScriptedImporter.

At runtime, the FrameManager will read the files from this temp unzipped folder as buffers and pass them to PointCloudRenderer for rendering. Both classes inherit from MonoBehaviour and PointCloudRenderer does rendering via Graphics.DrawProceduralNow().

What I am struggling with is code organisation and data sharing between the Editor and Runtime scripts.

Here is my import script annotated with some comments:

[ScriptedImporter(1, "MY_FILE_FORMAT")]
public class MyCustomImporter : ScriptedImporter {
   public override void OnImportAsset(AssetImportContext ctx) {
    string zipFilePath = ctx.assetPath;
    dirPath = Path.Combine(Application.temporaryCachePath, Path.GetFileNameWithoutExtension(zipFilePath));
    if (Directory.Exists(dirPath)) {
      Directory.Delete(dirPath, true);
    }
    Directory.CreateDirectory(dirPath);
    UnzipFile(dirPath);
    
    // 1. Create a new GameObject
    GameObject gameObject = new GameObject("Point Cloud");
  
    // 2. Add PointCloudRenderer as a component to the GameObject
    PointCloudRenderer renderer = gameObject.AddComponent<PointCloudRenderer>();
    // 3. Add FrameManager as a component to the GameObject
    FrameManager frameManager = gameObject.AddComponent<FrameManager>();
    // 4. I want to pass the temp dirPath location from the editor script to the runtime script
    frameManager.dirPath = dirPath;
    frameManager.Initialize();
    // 5. Connect my PointCloud renderer with the frameManager so they can talk to each other
    renderer.frameManager = frameManager;
  
    // 6. Add the GameObject as a node
    ctx.AddObjectToAsset("prefab", gameObject);
    ctx.SetMainObject(gameObject);
  }
}

My questions are:

  1. Is this a correct approach for sharing data (mainly dirPath) from editor to runtime scripts?
  2. Should FrameManager inherit from MonoBehaviour or ScriptableObject is better alternative. I read that ScriptableObject is perfect for data storing, however FrameManager does not have any data really, it just reads bytes from disk and forwards them to PointCloudRenderer.
  3. Please notice how I “link” the two MonoBehaviour classes on line 23. Is this correct? Is there a better “Unity approach” to this?

Thanks in advance!

I guess: no. It looks like you save the zip under Application.temporaryCachePath which will be inaccessible in builds. Or only on your machine. Have you tried making a build yet?

Normally you would store such assets either under Resources or StreamingAssets. Though I think you can’t store prefabs in the latter.

If so, this should be neither SO nor MB. A static class with static methods should suffice. Like public static PointCloudData LoadPointCloud(string path)

The loader should also not be responsible for assigning this to some other class. The loader would depend on the renderer. Instead, the renderer uses the Loader to get the data it needs, when it needs it.

That’s perfectly okay although I would prefer to inline this in the MB. In Awake, call GetComponent<Thing> and make sure the MB also has the [RequireComponent(typeof(Thing))] attribute so that there can be no question that the component must exist.

This is better because it avoids exposing frameManager publicly because then any other script could assign a different manager or set it to null.

Also, what parts of a “frame” needs “managing”?

It is perfectly clear what a PointCloudRenderer does, but it’s exceptionally vague what a “FrameManager” does. I remember way back when someone made an FPS counter label and it was in a script of that name.

I consider naming something “Manager” a code smell as it implies the class’ purpose has not been clearly defined. In 99% of all cases, suffixing any type with “Manager” only adds more words to it but no meaning.

Consider NetworkManager from Netcode for Xyz. Now we have to call NetworkManager.Singleton.StartHost() which is pretty bonkers because there’s no “managing” involved at all. It would have sufficed to have Netcode.Singleton.StartHost() and it already improves clarity and meaning.

Final note: the comments are largely redundant. They just spell out what I read in the next line. The code reads perfectly fine without the comments, and it will be easier to read too.

This is pretty cluttered and within the IDE also with varying colors:


    GameObject gameObject = new GameObject("Point Cloud");
  
    // 2. Add PointCloudRenderer as a component to the GameObject
    PointCloudRenderer renderer = gameObject.AddComponent<PointCloudRenderer>();
    // 3. Add FrameManager as a component to the GameObject
    FrameManager frameManager = gameObject.AddComponent<FrameManager>();
    // 4. I want to pass the temp dirPath location from the editor script to the runtime script
    frameManager.dirPath = dirPath;
    frameManager.Initialize();
    // 5. Connect my PointCloud renderer with the frameManager so they can talk to each other
    renderer.frameManager = frameManager;
  
    // 6. Add the GameObject as a node
    ctx.AddObjectToAsset("prefab", gameObject);
    ctx.SetMainObject(gameObject);

vs this (also using var keyword to further deduplicate repetitivenessnessesses:


    var gameObject = new GameObject("Point Cloud");
    var renderer = gameObject.AddComponent<PointCloudRenderer>();
    var frameManager = gameObject.AddComponent<FrameManager>();

    frameManager.dirPath = dirPath;
    frameManager.Initialize();
    renderer.frameManager = frameManager;
  
    ctx.AddObjectToAsset("prefab", gameObject);
    ctx.SetMainObject(gameObject);

Comments should only be used to document something that isn’t obvious from reading the code, such as “X needs to be called before Y because …”.

Thank you for the timely feedback! I ended up implementing via a simple class with static methods as you suggested.

I am using Unity strictly as an editor to allow my client to view the data and modify it via GUI. A custom exporter will be written that exports the data to my personal Metal renderer for visualisation.

All of your other input on code naming and organisation is much appreciated.