Hi.
I have a “Character” class that contains list of abilities:
public class Character {
private List<AAbility> Abilities = new List<AAbility>();
}
AAbility is an abstract class that covers wide range of abilities.
bublic abstract class AAbility {
... general logic here ...
}
Abilities can be anything from lighting attacks to dashes.
Currently there are around 20 of them.
public class LightningStrike : AAbility {
... logic here ...
}
public class LightningDash : AAbility {
... logic here ...
}
etc.
Which abilities a character has is defined in Character constructor.
Information is stored in XML file.
List of abilities for each character is stored as text.
Text corresponds to ability classes names.
E.g.:
When loading, you can never re-create a MonoBehaviour or ScriptableObject instance directly from JSON. The reason is they are hybrid C# and native engine objects, and when the JSON package calls new to make one, it cannot make the native engine portion of the object.
Instead you must first create the MonoBehaviour using AddComponent() on a GameObject instance, or use ScriptableObject.CreateInstance() to make your SO, then use the appropriate JSON “populate object” call to fill in its public fields.
If you want to use PlayerPrefs to save your game, it’s always better to use a JSON-based wrapper such as this one I forked from a fellow named Brett M Johnson on github:
Do not use the binary formatter/serializer: it is insecure, it cannot be made secure, and it makes debugging very difficult, plus it actually will NOT prevent people from modifying your save data on their computers.
In the odd situation this comes up I generally make a look-up table of some sort. Usually this is a scriptable object with a dictionary (using Odin serialisation). if I can easily reference the SO. Otherwise, you could just manually write out a dictionary in a static class too, though it gets tedious to update honestly.
But also what Kurt said. Why use XML when you have scriptable objects.
A factory method with a switch case is a valid approach. But I would use the nameof operator instead of magic strings, so that the code won’t stop working if an ability class is renamed.
This pattern does break the open-closed principle, but in practice it shouldn’t be that bad, since you only need to modify one centralized script and not any of its existing clients.
It can lead to the occasional delayed runtime exception when a dev adds a new ability but forgets to register it in the factory method. To combat this a unit test can be created that uses TypeCache to validate that all classes have been properly registered.
Alternatively, if slightly worse performance isn’t an issue, reflection can be used to automate setting up the factory and to avoid breaking the open-closed principle.
private static readonly Dictionary<string, Type> namesToTypes = new Dictionary<string, Type>();
static AAbility()
{
foreach(Type type in typeof(AAbility).Assembly.GetTypes().Where(t => t.BaseType == typeof(AAbility) && !t.IsAbstract))
{
namesToTypes[type.Name] = type;
}
}
public static T CreateInstance<T>(string name) where T : AAbility
{
return (T)Activator.CreateInstance(namesToTypes[name]);
}
I didn’t see ScriptableObjects as good option for me. I will need to export big amount of data from MS Access into Unity. Around 100 of characters. Currently I have generic prefab for a character and stats including list of abilties set in XML.
I will look into ScriptableObjects though.
I understood the point with Load/Save.
SisusCo thank you for the reply.
I’m actually using something like a factory to create new characters.
Yeees. This was my actual concern. :))
Currently factory class handles this with switch cases.
I was wondering if it’s possible to “cast” a string into a class. Activator.CreateInstance() is the thing I was interested in.
Wow! That’s really cool!
Theoretically performance tradeoff is not big. Operation happens rarely (on loading scene, on special events during combat) and number of calls is small (up to 30 at once).
Thank you very much SisusCo!
P.S.:
I will still look into SO I think.
There is a good point related to Save/Load.
At some point characters can have misc stat modifiers that will make characters different from stats in XML. I will see which approach is better. Both have their strong and weak points.
Personally I would try to get away from as many long chains of software as I could and just import it all into ScriptableObjects at a single point in time, get it under source control inside of Unity.
That has the side benefit that you can start making a better character editor immediately, by building editor tooling on top of the existing ability that Unity already gives you to edit ScriptableObjects.
Thank you for the update.
I didn’t work with SO before. How difficult it is to modify some aspect of the objects on 100 characters?
E.g. add a state to animator mechanim, or add additional child object, or modify a component on one of child objects?
Currently a character is a prefab and all above is very easy to modify since it’s single point of reference.
When people ask “How difficult…” about something I’m always curious as to what units you expect the reply in.
“Oh I would say it is about 27 difficulty.”
Seriously, at runtime or in an editor script, put them in a collection and iterate them.
None of those things happen to a ScriptableObject. Those things happen to GameObjects, which may have scripts (Components) plugged in, which may have public fields that have ScriptableObjects in them.
I suggest going and doing some ScriptableObject tutorials because me typing in a box isn’t going to do it justice.
Relatively to non-scriptable objects approach using your experience and objective judgement.
Are there any pros and cons?
As I understood it will be easier to setup save/load with SOs.
To provide more info on this I’m using following approach.
Each characters assets (animations, sounds, etc) are stored in separate folder under “Resources”.
Factory class uses empty prefab (without any resources), assigns all resources from character folder on runtime and creates a complete game object.