First up, the name of the question is terrible and I am open to suggestions. For context: I'm still relatively new to C#/Unity and it's hard to ask questions properly when I don't know the terminology. I'm reasonably experienced in Java/Python/javascript though so I'm not new to programming in general. Also since I have no idea how to ask this question in a structured way I'm just going to show all the relevant stuff and hopefully someone who understands it can put succinctly what I'm trying to ask.
I have this struct:
[System.Serializable]
public struct FieldLayer3D {
public Vector3 position;
public Quaternion rotation;
public Vector3 scale;
[SerializeReference] public FieldGeneric3D field; //this is where we have a problem
public BlendType blendType;
public float opacity;
public Channel channel;
public float smoothParameter;
public NormaliseType NormaliseType;
public float bias;
public bool invertLayer;
}
My plan was to use arrays of these to define some procedurally generated content and let me combine noise and patterns in ways similar to graphics programs combine layers of images. I need the larger class that will contain an array of FieldLayer3Ds to be serializable so I can save/load my work. The FieldGeneric3D typed variable "field" is what defines the type of noise/pattern/whatever and FieldGeneric3D is an abstract class defined as follows:
[System.Serializable]
public abstract class FieldGeneric3D {
//These are the fields for "FieldLayer3D"
public abstract float FieldAt(Vector3 p);
}
The reason I defined it this way is because each field needs its own set of variables that are different from each other, but will always value types like Vector3 or float such as in these two examples:
[System.Serializable]
public class FieldSDFBox3D: FieldGeneric3D {
public Vector3 boxSize;
public FieldSDFBox3D(Vector3 boxSize) { this.boxSize = boxSize; }
public override float FieldAt(Vector3 p) { return PatternGenerator.Sample_SDF_Box(p, boxSize); }
}
[System.Serializable]
public class FieldSimplex3D : FieldGeneric3D {
public int seed;
public FieldSimplex3D(int seed) { this.seed = seed; }
public override float FieldAt(Vector3 p) {
NoiseGenerator noiseGenerator = new NoiseGenerator(seed);
return noiseGenerator.Sample_Simplex(p);
}
}
However, this is what I see when I use the FieldLayer3D struct in a MonoBehaviour:
I was expecting something analogous to how materials work that you could drop in one of the script files that defines the appropriate class and I was HOPING that after you dragged in the script it would let you mess with public variables for the instance of the object.
Solutions I've thought and the issues with them:
Just use an enum instead of this class structure thing and let whatever object is controlling the field generation handle it. This doesn't work because I need to be able to set variables for each layer and the types of variable are different depending on the type of field.
Make FieldGeneric3D inherit from ScriptableObject and create assets for every layer I ever want to make. This is problematic because can you imagine how messy and clunky things would get in a program like photoshop if every time you wanted to add a layer you needed to save it as a new separate file? This just isn't workable, and the same goes for making it a MonoBehaviour because then I need to fill my scene with dummy GameObjects for every layer of noise. I also think these would be difficult to simplify and serialize. It is possible Ill make the parent class that combines and stores these layers a ScriptableObject because I'd like the combination to be a savable asset without too much trouble but I'm leaning towards using JSON for the whole saving and loading of layers.
Use the enum approach but just include a bunch of null variables that are only ever read from if the correct type of layer is used. This isn't ideal for extensibility in the future because I'll need to update things in multiple places. It also means that if I add a new type of FieldGeneric3D with new variables I'll need to add those variables to the FieldLayer3D class which might break saving/loading for all the ones I've already saved. I also don't know much about writing my own editor or anything so going about and conditionally hiding all the unused variables sounds like something that could result in issues if I implement it wrong. Leaving them all always visible is not ideal because I need other people to be able to use this tool without too much confusion or reading of manuals.
Some fancy custom editor shenanigans. Seems like it would take a long time to learn how to do properly, so I was trying to just make simple classes and hope Unity would understand my intent.
The ultimate question is this I suppose: How do I achieve what I want to achieve, within the Unity ecosystem in such a way as to be editable in the inspector, save/loadable, understandable by other people, and without future additions becoming a major headache?
Also, I have seen this How do I expose a field of an abstract type to the Inspector? question and the only thing I could see there of help was "Composition over Inheritance" but I'm not sure how that would be applied here.
To clarify, I am willing to do any of the four ideas I had if the answer is just that that is the best option available to me.