Assume you have a custom file you want to create an importer for. It could be an .xls file or whatever. In this case we're going to use a JSON file because it's easy but we're going to pick a custom extension to make it easy to tell which files are ours?
Let's assume the format of the JSON file is
{
"someValue": 123,
"someOtherValue": 456.297,
"someBoolValue": true,
"someStringValue": "this is a string",
}
Let's save that as Example.test
somewhere outside of assets for now.
Next make a MonoBehaviour
with a custom class just for the data. The custom class is solely to make it easy to deserialize the JSON. You do NOT have to use a custom class but it makes this example shorter. We'll save this in TestData.cs
using UnityEngine;
using System.Collections;
public class TestData : MonoBehaviour {
[System.Serializable]
public class Data {
public int someValue = 0;
public float someOtherValue = 0.0f;
public bool someBoolValue = false;
public string someStringValue = "";
}
public Data data = new Data();
}
If you were to manually add that script to a GameObject you'd see something like
Next make an Editor
folder somewhere under Assets
. I can be at any level. Inside the Editor folder make a TestDataAssetPostprocessor.cs
file and put this in it.
using UnityEditor;
using UnityEngine;
using System.Collections;
public class TestDataAssetPostprocessor : AssetPostprocessor
{
const string s_extension = ".test";
// NOTE: Paths start with "Assets/"
static bool IsFileWeCareAbout(string path)
{
return System.IO.Path.GetExtension(path).Equals(
s_extension,
System.StringComparison.Ordinal);
}
static void HandleAddedOrChangedFile(string path)
{
string text = System.IO.File.ReadAllText(path);
// should we check for error if the file can't be parsed?
TestData.Data newData = JsonUtility.FromJson<TestData.Data>(text);
string prefabPath = path + ".prefab";
// Get the existing prefab
GameObject existingPrefab =
AssetDatabase.LoadAssetAtPath(prefabPath, typeof(Object)) as GameObject;
if (!existingPrefab)
{
// If no prefab exists make one
GameObject newGameObject = new GameObject();
newGameObject.AddComponent<TestData>();
PrefabUtility.CreatePrefab(prefabPath,
newGameObject,
ReplacePrefabOptions.Default);
GameObject.DestroyImmediate(newGameObject);
existingPrefab =
AssetDatabase.LoadAssetAtPath(prefabPath, typeof(Object)) as GameObject;
}
TestData testData = existingPrefab.GetComponent<TestData>();
if (testData != null)
{
testData.data = newData;
EditorUtility.SetDirty(existingPrefab);
}
}
static void HandleRemovedFile(string path)
{
// Decide what you want to do here. If the source file is removed
// do you want to delete the prefab? Maybe ask if you'd like to
// remove the prefab?
// NOTE: Because you might get many calls (like you deleted a
// subfolder full of .test files you might want to get all the
// filenames and ask all at once ("delete all these prefabs?").
}
static void OnPostprocessAllAssets (string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
foreach (var path in importedAssets)
{
if (IsFileWeCareAbout(path))
{
HandleAddedOrChangedFile(path);
}
}
foreach (var path in deletedAssets)
{
if (IsFileWeCareAbout(path))
{
HandleRemovedFile(path);
}
}
for (var ii = 0; ii < movedAssets.Length; ++ii)
{
string srcStr = movedFromAssetPaths[ii];
string dstStr = movedAssets[ii];
// the source was moved, let's move the corresponding prefab
// NOTE: We don't handle the case if there already being
// a prefab of the same name at the destination
string srcPrefabPath = srcStr + ".prefab";
string dstPrefabPath = dstStr + ".prefab";
AssetDatabase.MoveAsset(srcPrefabPath, dstPrefabPath);
}
}
}
With that saved you should be able to drag and drop the Example.test
file we created above into your Unity Assets folder and you should see the corresponding prefab created. If you edit Example.test
you'll see the data in the prefab is updated immediately. If you drag the prefab into the scene hierarchy you'll see it update as well as Example.test
changes. If you move Example.test
to another folder the corresponding prefab will move with it. If you change a field on an instance then change the Example.test
file you'll see only the fields you didn't modify on the instance get updated.
Improvements: In the example above, after you drag Example.test
into your Assets
folder you'll see there's both an Example.test
and an Example.test.prefab
. It would be great to know to make it work more like the model importers work we're you'd magically only see Example.test
and it's an AssetBundle
or some such thing. If you know how please provide that example