Have Unity Support Your Custom File! (Part 2/6)

Miijii
Miijii’s Unified Works
7 min readMar 1, 2023

Learn about one of Unity’s greatest features in detail: ScriptableObjects.

A display of a character’s known spells as Scriptable Objects.
Scriptable Objects representing Maple’s different spells. 🧙‍♀️

“That’s a lot of objects,” you might be wonder. Rest assure. When it comes to ScriptableObjects, this is what’s expected. Other than the image shown on top, another very relatable use-case scenario for ScriptableObjects involves making a card game (e.g. Magic: The Gathering or Yu-Gi-Oh: Master Duel). For each card, you would display a name, a description, a list of attacks, the amount of health, mana, attack power, or defense it has, etc. You are able to create a container of data that can be modularited and reused not only during Runtime, but Edit-Time as well!

The use of ScriptableObjects is one of the most important things to know about when working with the Unity Game Engine. Not only can you save a enomorous amount of data, they become independent from their class instances (thus provides you, the Developer, modularity over your data). Using ScriptableObjects also help reduce the amount of memory used by your project, and it’s achieved by eliminating the copying of values.

Most common use of scriptables would be applied during Runtime. However, you are very well able to use them in the Unity Editor. They can be used for any forms of configuration. A very perfect example of ScriptableObjects being used for configurations outside of the actual game are Unity’s relatively new feature “Addressables”.

Unity’s Addressable System also uses Scriptable Objects for configurations prior to game execution.

Truthly, they aren’t just “Data Containers”. They’re “Hyper-Active Data Containers”! You can use almost all methods that MonoBehaviour class have, for exanmle, OnEnable(), Awake(), OnDisable(), OnValidate(), OnDestory(), and Reset(). Of cource you can put your own methods too to really have it do things. A primary example is with the XVNMLAsset object.

An asset called XVNMLAsset!

Don’t worry about what this does for now. We’ll be using this XVNMLAsset class as our reference point for supporting our custom files. For what is a Scriptable Object, all you need to know is that it’s:

“…A data container that you can use to save large amounts of data, independent of class instances.” (Official Unity Documentation)

So, what does that mean for you, the Developer, when it comes to supporting a custom file? Very simple: we create our file assets using Scriptable Objects that. We don’t know what this file type will be, but we’ll start by creating a class that inherits from ScriptableObjects.

using UnityEngine;

public sealed class MyCustomAsset : ScriptableObject
{
private int m_instanceID;
public int InstanceID
{
get {
if (m_instanceID == 0)
m_instanceID = GetInstanceID();
return m_instanceID;
}
}
private int m_hashCode;
public int HashCode => InstanceID.GetHashCode();

// Imagine we have field and properties representing our file type

public override int GetHashCode(){
return HashCode;
}
}

That’s really much it. Although the InstanceID does not need to be known explicitly in our custom asset (since all UnityEngine.Object types will have an instance id already assigned to them), this helps visualize the “behind-the-scenes” of what makes up a native Unity asset. Whenever you call GetInstanceID(), you’ll be returned a positive value (call from the origin object) or a negative value (call from an instance of the origin object). The InstanceID of an object is the object’s Unique Identifier. However, when switching from Editor and Runtimes sessions, its value can change. Such that you want to, for example, load an object state from a save file, this is a a big “No”, for it’s not reliable for executing actions between those two sessions.

The HashCode is also optional, but you are able to create your HashCodes however you want by combining HashCodes from your data fields. HashCode are used to correspond to a value of an object (which for that object, it’s values are collected in a hash-based collection). As much as I wish to say that the hash code acts as Unique Identifiers as to an object’s values, that’s only partially true. There are some cases where 2 seperate objects will generate the same code.

“Two objects that return different hash codes means objects are not equal but the reverse is not true. Means, equal hash codes do not imply object equality, because different (unequal) objects can have identical hash codes.” (GeeksForGeeks, 2020)

Now, if we were to create a new C# Script in our Assets/Scripts folder, add our MyCustomAsset ScriptableObject code, we can go into our editor and see that…

There’s nothing. Don’t be discouraged, my friend. We’ve simply forgotten an extra step.

using UnityEngine;

// Add this Attribute
[CreateAssetMenu(fileName = "New Custom Asset", menuName = "CustomAsset")]
public sealed class MyCustomAsset : ScriptableObject
{
private int m_instanceId;
public int InstanceID
{
get {
if (m_instanceID == 0)
m_instanceID = GetInstanceID();
return m_instanceId;
}
}
private int m_hashCode;
public int HashCode => m_InstanceID.GetHashCode();

// Imagine we have field and properties representing our file type

public override int GetHashCode(){
return HashCode;
}
}

Still nothing. However, if you right click inside your Scripts folder (or any directory within your Assets folder), and go to “Create”, you’ll find something special ✨✨✨.

Our CustomAsset is included inside our Context Menu! 💞

And if you click on your newly created CustomAsset entry, you’ll see that it’ll create a “New Custom Asset” (the same as what was detailed in our CreateAssetMenu attribute), and you can name it whatever you want. You should then get this:

Our blank and soulless ScriptableObject 🥺

Now, for whatever you’re new Custom Asset is going to be, you can now give it substance! You can further construct your MyCustomAsset script to be any asset that you want to support, giving it the needed fields and property for that. We’ll go ahead and carry on with our Custom Asset to show you what you can do.

using UnityEngine;

[CreateAssetMenu(fileName = "New Custom Asset", menuName = "CustomAsset")]
public sealed class MyCustomAsset : ScriptableObject
{
private int m_instanceID;
public int InstanceID
{
get
{
if (m_instanceID == 0)
m_instanceID = GetInstanceID();
return m_instanceID;
}
}
private int m_hashCode;
public int HashCode => InstanceID.GetHashCode();

// Imagine we have field and properties representing our file type
public string assetName;

[TextArea(2,5)]
[SerializeField] private string assetDescription;

[SerializeField] private string assetFileExtension;

[SerializeField] private bool readOnly;

public override int GetHashCode()
{
return HashCode
+ assetDescription.GetHashCode()
+ assetFileExtension.GetHashCode()
+ readOnly.GetHashCode();
}
}
Looks uninspiring, but we’ll do something about that further in the series. 🤔

You can go much further with this. Remember our Card example from earlier? You can go as far as creating fields and properties that describes every aspect of your card. If you want to take it a step further, after this series, you could take a file with the extension “.digicard” and make it a JSON file (using the JsonUtility static class), and have that be imported into Unity. We’ll explore how we’ll be able to do that further in the series.

But before that, if you wanted to just make a Scriptable Object, this is perfectly fine. It’s honestly a good stopping point, and you can start attaching it to your GameObjects and reflecting that data on to your GameObject instance. However, we want to be able to:

  • Click and drag our custom asset (e.g. .digicard or .xvnml) from outside of Unity into the Assets folder, and have it be recognized.
  • Create a new file with our file extension and convert it to the corresponding asset.
  • Get acknowledged by Unity for being included into the final build of your game.

And for that, we won’t be utilizing the CreateAssetMenu attribute. Instead, we’re going to do something much cooler than that. Another neat feature that Unity provides us are Script Templates. We’ll be going over what those are in Part 3 of our series! As we go in deeper into the series, you’ll soon find out ScriptableObject’s “Hidden Power” which is necessary to give our file format support for Unity.

If you have further questions regarding Scriptable Objects at this point in time, or have questions regarding HashCodes, InstanceID, the copying of values between objects, and the like, be sure to leave a comment. If you enjoyed or learned from what you read, be sure to follow so that you don’t miss out on this series (and other topics as well, since I have so much I want to share with everyone). コーディング諦めないぞ!(Don't Stop Coding!)

Click here for Part 3: Script Templates!

--

--

Miijii
Miijii’s Unified Works

Miijii (Pen-Name) | Game Developer | Developing XVNML, XVNML2U, and Tomakunihahaji