Reputation: 4056
Is there a way of mimicking MonoBehaviour copy semantics in ScriptableObjects?
Say I have a MonoBehaviour like so:
public class DummyClassBehaviour : MonoBehaviour {
public DummyClass DummyClassTest; //ScriptableObject
public DummyClassBehaviour DummyBehaviourTest; //Another DummyClassBehaviour
}
And a ScriptableObject:
public class DummyClass : ScriptableObject {
public string Text = "";
}
When I duplicate(CTRL+D) a GameObject w/ DummyClassBehaviour attached, 'DummyBehaviourTest' copies as you would expect: If it references a MonoBehaviour in the GameObject I'm copying, the copy mechanism updates the reference to the same MonoBehaviour type in the new GameObject. If it references a MonoBehaviour in another GameObject, that reference remains unchanged.
The ScriptableObject, on the other hand, always references the original. So I end up with N GameObject's all sharing the same ScriptableObject (DummyClass) from the original GameObject. I'm using ScriptableObjects to allow serialization of non-Monobehaviour data classes.
Upvotes: 4
Views: 9108
Reputation: 4056
As far as I can tell, and please someone correct me if I'm wrong, you cannot modify the serialization behavior of a ScriptableObject to match that of a MonoBehaviour. Namely that it should update references if a duplicate is made.
Instead I opted for a less than optimal solution, but it works. My class is assigned a unique identifier that gets serialized like everything else. I use this ID in DummyBehaviour.Awake() to create a lookup table that I can then use to reassign my DummyClass.
I'm not going to accept my own answer because I don't feel it answers my original question fully, but it's related:
[System.Serializable]
public class DummyClass {
// Unique id is assigned by DummyBehaviour and is unique to the game object
// that DummyBehaviour is attached to.
public int UniqueID = -1;
public string Text = "";
// Override GetHashCode so Dictionary lookups
public override int GetHashCode(){
int hash = 17;
hash = hash * 31 + UniqueID;
return hash;
}
// override equality function, allows dictionary to do comparisons.
public override bool Equals(object obj)
{
if (object.ReferenceEquals(obj, null))return false;
DummyClass item = obj as DummyClass;
return item.UniqueID == this.UniqueID;
}
// Allow checks of the form 'if(dummyClass)'
public static implicit operator bool(DummyClass a)
{
if (object.ReferenceEquals(a, null)) return false;
return (a.UniqueID==-1)?false:true;
}
public static bool operator ==(DummyClass a, DummyClass b)
{
if (object.ReferenceEquals(a, null))
{
return object.ReferenceEquals(b, null);
}
return a.Equals(b);
}
public static bool operator !=(DummyClass a, DummyClass b)
{
if (object.ReferenceEquals(a, null))
{
return object.ReferenceEquals(b, null);
}
return !a.Equals(b);
}
}
And my MonoBehaviour:
[ExecuteInEditMode]
public class DummyBehaviour : MonoBehaviour {
public List<DummyClass> DummyClasses = new List<DummyClass>();
// reassign references based on uniqueid.
void Awake(){
Dictionary<DummyClass,DummyClass> dmap = new Dictionary<DummyClass,DummyClass>();
// iterate over all dummyclasses, reassign references.
for(int i = 0; i < DummyClasses.Count; i++){
DummyClass2 d = DummyClasses[i];
if(dmap.ContainsKey(d)){
DummyClasses[i] = dmap[d];
} else {
dmap[d] = d;
}
}
DummyClasses[0].Text = "All items same";
}
// helper function, for inspector contextmenu, to add more classes from Editor
[ContextMenu ("AddDummy")]
void AddDummy(){
if(DummyClasses.Count==0)DummyClasses.Add(new DummyClass{UniqueID = 1});
else {
// Every item after 0 points to zero, serialization will remove refs during deep copy.
DummyClasses.Add(DummyClasses[0]);
}
UnityEditor.EditorUtility.SetDirty(this);
}
}
Upvotes: 2