Ok, breakfast finished, lets tackle this ^^
You have two major issues:
With your current setup you can’t achieve proper inheritance because generics are essentially the opposite of inheritance. While inheritance is about generalising data and allow functionality to differ, for generic classes this is reversed. Generic classes themselfs can not be used, only when supplied with the required type arguments. Providing different types will create seperate and disconnected distinct classes with different data but the same functionality.
The second issue is that the JsonUtility and Json in general does not support inheritance at all. So when using an object mapper and you have json data that is different for each game, you have to use distinct classes (since inheritance is not supported). Distinct classes means you can not use inheritance at all to handle all your minigames.
I’m not a fan of object mappers, especially when the data is highly dynamical (for example web API responses). An alternative could be something like my SimpleJSON framework. It’s a pure json parser / generator that just simplifies the reading / parsing and the generation of JSON data. This will give you 100% flexibility how you want to construct your json and you can support inheritance / polymorphism with some additional work.
Serializing your different minigame data can be done through inheritance and virtual methods that takes care of saving and loading the relevant data. The only special handling we need is when deserializing the data because we have to recreate the actual types and not based on the field type like most json serializers do. That means you need a way to identify which “kind” of minigame a certain data set is and you need to create an instance of that special kind. From there on we can generalise everything else again because once we have the concrete instance the virtual methods will take care about the loading of their specific data needs.
One way is to store the full class name in a special field in the json data which the loading code can use to create an instance of this class through reflection. Another way is to use some sort of IDs and a factory class that can create the proper game type object for a given ID.
I would avoid using too complex class compositions and don’t spread the required data across several distinct classes. Since each minigame is already a specialized class, you need that class distinction anyways. I don’t see any benefit of your “MinigameSetup” class. All the data is the same except for the Levels list. If you just implement the common data in the minigame class itself and each minigame takes care about its specialized extra data it becomes much simpler from the data point of view.
Since your Minigame class is a MonoBehaviour, it’s not clear where and how those individual games are actually created. If they are all already attached to a gameobject in a scene, of course you would not need that factory class or the type field in the json data.
Generally I would use a general purpose interface for saving / loading the data. So it would look something like this:
public interface IJSONSerializable
{
JSONNode SaveToJSON();
void LoadFromJSON(JSONNode aNode);
}
public class Minigame: MonoBehaviour, IJSONSerializable
{
public bool debug;
public List<Reward> rewards;
public virtual JSONNode SaveToJSON()
{
var node = new JSONObject();
node["debug"] = debug;
foreach(var reward in rewards)
node["rewards"].Add(reward.SaveToJSON());
return node;
}
public virtual void LoadFromJSON(JSONNode aNode)
{
debug = aNode["debug"];
rewards = new List<Reward>();
foreach(JSONNode rewardNode in aNode["rewards"])
{
var reward = new Reward();
reward.LoadFromJSON(rewardNode);
rewards.Add(reward);
}
}
}
public class MinigameA: Minigame
{
public List<T> levels;
public override JSONNode SaveToJSON()
{
var node = base.SaveToJSON();
foreach(var level in levels)
node["levels"].Add(level.SaveToJSON());
return node;
}
public override void LoadFromJSON(JSONNode aNode)
{
base.LoadFromJSON(aNode);
levels = new List<MinigameALevel>();
foreach(JSONNode levelNode in aNode["levels"])
{
var level= new MinigameALevel();
level.LoadFromJSON(levelNode);
levelsAdd(level);
}
}
}
public class MinigameLevel : IJSONSerializable
{
public int levelID;
public virtual JSONNode SaveToJSON()
{
var node = new JSONObject();
node["levelID"] = levelID;
return node;
}
public virtual void LoadFromJSON(JSONNode aNode)
{
levelID = aNode["levelID"];
}
}
public class MinigameALevel: MinigameLevel
{
public int gameItems;
public override JSONNode SaveToJSON()
{
var node = base.SaveToJSON();
node["gameItems"] = gameItems;
return node;
}
public override void LoadFromJSON(JSONNode aNode)
{
base.LoadFromJSON(aNode);
gameItems = aNode["gameItems"];
}
}
Such an approach gives you the highest flexibility. If you want to store the configuration data in a sub object called “_gameData” you can simply add this since the structure of the JSON data is not dictated through a class but through the saving and loading code. So instead of:
node["debug"] = debug;
you can also do
node["_gameData"]["debug"] = debug;
and you automatically get another sub object and the debug field is a member of that subobject.
Depending on your needs you may not even need your concrete minigame level classes. You could directly use the JSON data to build your level if you like. This would save a lot of those manual conversions.
Keep in mind that this is just an example. There are other ways for sure. However you have to find the right mix between reusability, flexibility and generalisation. Things which are fundamentally different can not be pressed meaningful into the same concept.
Finally an important advice: Don’t try to use inheritance to make your code shorter / simpler. Use inheritance where it makes sense from a data modelling point of view. Inheritance implies an “is a” relationship. So a derived class IS also a base class. If that doesn’t hold true your usage of inheritance is already flawed. Specifically in your case: You defined a _gameData field in the base class of a certain type, but the derived class needs a completely different type.
Final note: SimpleJSON was written in pure C# with no Unity dependency. So it’s a pure C# framework. However all classes of the framework have been created as partial classes. This allows easy extension files to be made. In the repositiory I have a specific extension for Unity and another one for some common .NET types which makes reading / writing them easier. Of course you can create your own extensions specifically for your application. This could simplify serializing certain custom types (thinking of Reward or your level classes). Though I would recommend to not overuse this “feature” ^^.