- Home /
Reading data of inherited class in parent
I'm trying to solve a problem with data access in one of my projects. Basically, I am having a set of minigames working in a very similar manner from the outside (instantiation, external data load, rewarding, UI screens...), so I am trying to encapsulate most of it into a Minigame class which controls this common behaviour. Additionally, I have a separate class for each minigame which inherits the Minigame class a takes care of specific parts.
Every minigame uses a specific data structure called setup containing level definitions, locations and rewards. The problem I have is there is a lot of similar code I have to copy-paste among the child classes just because it is using different setup type.
Here I am having a short example. Both LoadRewards() and LoadLevelID() functions work fine in MinigameA, but throws NullReferenceException in Minigame class... Is there a way to overcome this?
The reason for using new int levelID in MinigameALevel is I am serializing it from/to Json and the variable is ignored if it's not directly there.
public class Minigame: MonoBehaviour
{
public MinigameSetup<MinigameLevel> _gameData;
public void LoadRewards()
{
foreach (Reward r in _gameData.rewards)
{
LoadReward();
}
}
public void LoadLevelID()
{
foreach (MinigameLevel ml in _gameData.levels)
{
Debug.Log (ml.levelID.ToString());
}
}
}
public class MinigameA: Minigame
{
public new MinigameSetup<MinigameALevel> _gameData;
}
public class MinigameSetup<T> where T: MinigameLevel
{
public bool debug;
public List<T> levels;
public List<Reward> rewards;
}
public class MinigameLevel
{
public int levelID;
}
public class MinigameALevel: MinigameLevel
{
public new int levelID;
public int gameItems;
}
Do you know which variable is being null and causing NullReferenceException?
Answer by Bunny83 · Jan 06, 2021 at 01:24 PM
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" ^^.
Answer by Fritched · Jan 06, 2021 at 12:52 AM
@Angeluss If I understand your scenario properly it seems to want to do some common code work in a base class and the variance in a derived class. There may be one thing tripping you up. There's a _gameData object in your Minigame class which acts as your base class. And another one in the derived class with the new keyword. It seems this derived _gameData object hides your base class one. https://stackoverflow.com/questions/3649174/new-keyword-in-property-declaration-in-c-sharp
Your best bet would be to set your base class methods to virtual so they can be overwritten in the derived classes. Then you can call the base class to do the common work. I've created an example. I hope this helps.
using UnityEngine;
using System.Collections.Generic;
public class MinigameBase : MonoBehaviour
{
public MinigameSetup<MinigameLevel> _gameData;
internal virtual void LoadRewards()
{
//DO Stuff, removed for brevity
}
internal virtual void LoadLevelID()
{
//DO Stuff, removed for brevity
}
}
public class MinigameA : MinigameBase
{
//public new MinigameSetup<MinigameALevel> REMOVED from inherited class
//This hides the parent data object.
//https://stackoverflow.com/questions/3649174/new-keyword-in-property-declaration-in-c-sharp
internal override void LoadRewards()
{
//Do stuff in derived class
//DO STUFF
// Called to do stuff in base class. Can be called before code in derived class
base.LoadRewards();
}
internal override void LoadLevelID()
{
//Do stuff in derived class
//DO STUFF
// Called to do stuff in base class. Can be called before code in derived class
base.LoadLevelID();
}
}
public class MinigameSetup<T> where T : MinigameLevel
{
public bool debug;
public List<T> levels;
public List<Reward> rewards;
}
public class MinigameLevel
{
public int levelID;
}
public class MinigameALevel : MinigameLevel
{
public new int levelID;
public int gameItems;
}
public class Reward
{
public string reward;
}
The
public new int levelID;
inside the $$anonymous$$inigameALevel class makes no sense, remove it.
edit
Note that your approach of your _gameData variable doesn't work because generic class types with different generic type parameters are not compatible at all. You can achieve interface contravariance or covariance when restricting the generic type parameters to be soley input or output. However since T is used in a List (which by definition is read and write) this whole setup wont work. You can't generalize what's fundamentally not even a similar thing. People always get generics wrong. A List<int>
and a List<string>
are not the same class and are completely incompatible. The same holds true for the different types "$$anonymous$$inigameLevel" and "$$anonymous$$inigameALevel".
Thanks, I am aware of both of these. However I don't have a clue of how to do it better. For the first case I haven't found any other way to include levelID in JsonUtility.ToJson($$anonymous$$inigameALevel)
Is there any other solution?
The reason for generics is similar. $$anonymous$$inigameSetup is something I want to access from both parent and child class. From the parent, I want to handle its common part which is $$anonymous$$inigameSetup, while from the child class I need to access the whole $$anonymous$$inigameSetup. I hope there is some easier and less dirty solution, that's why I raised the question.
There are other ways I tried with $$anonymous$$inigameSetup...
I split it into two classes - the common part and a $$anonymous$$igame specific part for each $$anonymous$$igame. This works fine, but it splits my json loader in two parts. As I need other people to handle them, I cannot afford to overcomplicate the json structure. $$anonymous$$inigameSetup must be serialized directly from one $$anonymous$$igame file.
Inheritance $$anonymous$$inigameASetup: $$anonymous$$inigameSetup seems like an option, but here I need to move all the $$anonymous$$inigameASetup connected functionality to $$anonymous$$inigameA, because the $$anonymous$$inigameASetup is defined only there. Ending up with few hundred lines copy-pasted among all the specific $$anonymous$$igame files. Simply what I really need to achieve is a $$anonymous$$inigameSetup in my $$anonymous$$inigame class and its extension $$anonymous$$inigameASetup in $$anonymous$$inigameA class, both being accessible as _gameData within their class.
Interesting. Kind of halfway solution. I still would like to get rid of any use of LoadRewards() in child classes so they don't have to be defined for each $$anonymous$$igame, but it gives full control of the functionality from the base class, meaning possible bugs or improvements can be handled only in $$anonymous$$inigame.
Anyway the main problem stays. $$anonymous$$aybe I haven't describe the core of it enough. It is in $$anonymous$$inigameSetup... What I need to do is load it from json like this... _gameData = JsonUtility.FromJson<$$anonymous$$inigameSetup<$$anonymous$$atchLevel>>(data);
for each individual $$anonymous$$igame type. So it has to have a specific level class type defined for json to parse it correctly. That's why I used new expression for overriding the original common $$anonymous$$inigameSetup<$$anonymous$$inigameLevel> in the child class. Anyway I know $$anonymous$$atchLevel inherits $$anonymous$$inigameLevel so it is just an extension of $$anonymous$$inigameLevel and I need to be able to access it as a $$anonymous$$inigameLevel from the parent $$anonymous$$inigame class. The question is: What is the right approach for letting the code know that the <$$anonymous$$inigameSetup<$$anonymous$$atchLevel>> is just an extension of $$anonymous$$inigameSetup<$$anonymous$$inigameLevel> so it can work with it?
I could be wrong but I don't think you can work with a specific implementation of a base class with generic types defined in the derived class within the base class itself. The base class won't know about the specific implementation details. You'll still have to do anything that sets one $$anonymous$$inigame apart from another in the $$anonymous$$igame code. I'll see if I can come up with a solution that involves a generic parameter for your Json deserialization.
I'm not sure if this solves all your issues but it might get you close. It could also be a few things you might have tried from the sounds of your message. Take a look. https://drive.google.com/file/d/1qsrgL338pRDdhdsKtfaQgRN$$anonymous$$iJuEVe8t/view?usp=sharing