Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question Alternatives for multiple parent class

Discussion in 'Scripting' started by pi_314159265, Dec 27, 2022.

  1. pi_314159265

    pi_314159265

    Joined:
    Aug 14, 2022
    Posts:
    27
    In a game i am trying to code i am running in issues with the class design of my abilities. I will try to outline the (relevant parts) of my current approach

    I have 3 classes which inherit from each other:
    Code (CSharp):
    1. public class Ability : Monobehaviour {
    2. public GameObject source // The GameObeject using the Ability
    3.  
    4. public virtual void Awake() {
    5. // Set the source
    6. }
    7.  
    8. public virtual bool CheckCastRequirements() {
    9. // here are some tests, if source can cast at the moment
    10. // returns true or false depending on the ckecks
    11. }
    12.  
    13. public class TargetedAbility : Ability {
    14. public GameObject target
    15.  
    16. public override bool CheckCastRequirements() {
    17. if (!base.CheckCastRequirements) return false;
    18. // Check if the source GameObject has a target
    19. // returns true or false depending on the ckeck
    20. }
    21.  
    22. public class HostileTargetedAbility : TargetedAbility {
    23.  
    24. public override bool CheckCastRequirements() {
    25. if (!base.CheckCastRequirements) return false;
    26. // Check if the target is hostile
    27. // returns true or false depending on the ckeck
    28. }
    29.  
    Now i have some other classes (i.e. type of abilities), which need other functionalities

    Code (CSharp):
    1. public class TimedEffect : Ability {
    2.  
    3. public void MakeTimeEffectSymbols
    4. // Code handle displaying appropiated Symbols to indicate
    5. // TimeEffects on GameObjects
    6. }
    7.  
    8. public class HostileDebuff : TimedEffect {
    9. // A HostileDebuff is both a TimedEffect and a HostileTargetedAbility
    10. // i could base this class on either TimedEffect or HostileTargetedAbility
    11. // but TimedEffect has a lot more code at the moment
    12. // I copied code from HostileTargetedAbility to this class
    13. }
    14.  
    My Question is: How can i Avoid the code duplication happening in HostileDebuff?
    To be more precise googleing the proplem let me to interfaces, but i had no success using interfaces. My Problem is that i can not implement the method "CheckCastRequirements" from "HostileTargetedAbility" in an interface, because i need public variables from ability. Also i can not declare the public variable target in an interface
     
  2. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    You have a good opportunity to favor composition over inheritance.

    Imagine you had an Animal class, then you create a Bird class that inherits from Animal and adds the method Bird.Fly(). Then, you want to create a class RoboBird that inherits from Robot, so it can't also inherit from Bird. An easy way to avoid code duplication between RoboBird and Bird is to create a separate Wings class. Then both Bird and RoboBird can have Wings, and they can both fly with the same code without inheriting from the same class.

    Here, it seems to me that the TargetedAbility you want to make could have a HostileDebuff it applies, instead of being a HostileDebuff. This also has some added benefits, like it'd be easy for your Ability to have a list of HostileDebuffs it applies instead of just one. Even more, you could have a list of TimedEffects in your TargetedAbility, that way the same code can fit all sorts of TimedEffects.
     
    Max-om and Bunny83 like this.
  3. pi_314159265

    pi_314159265

    Joined:
    Aug 14, 2022
    Posts:
    27
    Hello oscarAbraham,
    thank you for your reply, this could be a solution, but i would have to try if other problems will arise from this.
    • I have already implemented this functionality in TimedEffect (which is not clear from the name). TimedEffect gets a list of targets and for each target a list of Buffs / Debuffs to apply to them.
    I am also interested in an answer to the more abstract question:
    Given 3 classes A,B,B', where B,B' inherit from A (but not from each other). Is it possible to set up a class C, which uses functionality from B and B' (i.e. additional global class variables / functions, which may also use class variables of A) without duplicating code?
     
  4. AngryProgrammer

    AngryProgrammer

    Joined:
    Jun 4, 2019
    Posts:
    437
    There is no multiple inheritance in C# but it can be simulated by:
    1. composition;
    2. interface.
     
    Last edited: Dec 28, 2022
    Bunny83 likes this.
  5. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    6,015
    Don't fall down the inheritance rabbit hole. It doesn't benefit you much in the world of game dev code.

    Unity gives you an ideal design pattern with its use of components. This can be simulated in C# with interfaces or shallow inheritance (base class with a number of immediate children (which can be serialised with SerializeReference)).

    Often in the pure C# world I use interfaces to define necessary values that all implementers will supply, and write extension methods for said interface(s) to avoid repeated code.
     
  6. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    The closest you can get to it in c# is to use Interface default members. You know a class can inherit from multiple interfaces, right? Well in c# 8 you can set default implementations in interface methods, so you can share code that way. You can solve your public variable needs by adding properties to your interfaces.

    I really really don't recommend it though. There's a thing called the diamond problem where if two different parents both have a member with the same signature, things quickly become a mess.

    Look, inheritance in itself can get messy very quickly. It's easy to start having things in parent classes that not all child classes need. Sometimes, one needs to make a change in a parent class and finds that it breaks things in other child classes. That's why Composition over Inheritance is one of the few things most experts agree on with respect to OOP. I'm not saying it's better to never use inheritance, but it can be good to avoid overusing it; I think multiple inheritance is definitely overusing it.

    In general, it sounds like your code relies a bit too much on inheritance, even without multiple inheritance. If you want to make your code easier to reason about, easier to change and easier to maintain, my true advice is to think about how you can share code through small classes and structs that are composited together.
     
    Last edited: Dec 28, 2022
  7. pi_314159265

    pi_314159265

    Joined:
    Aug 14, 2022
    Posts:
    27
    I am running into (at least) one Problem with this approach. Before a can explain my problem i have to go into a bit more detail. Abilitys are activated by a player. This player might have a target (GameObject) at the time of activation of the abilities (for some abilities it is necessary).
    A TimedEffect (often) also has a target (or more than one). The target could be the player target, but this is not necessary. There are several possibilites what the target of the TimedEffect could be. I would see this cases
    1. The target of the TimedEffect is the user (=source) of the ability, i.e. "increase your damage dealt by x% for y time"
    2. The target of the TimedEffect is the player target and needs to be an ally , i.e. "increase the damage dealt by x% for y time of your targeted ally"
      1. It could also be all allies
    3. The target of the TimedEffect is the player target and needs to be an enemy, i.e. "increase the damage taken by x% for y time of your targeted enemy"
      1. It could also be all enemies
    4. The target of the TimedEffect is the player target and needs to be an enemy and also the user, "i.e. leech x amount of health per second from targeted enemy for y time"
      1. In my Opinion this needs to be one TimedEffect because both should end at the same time (this is easy if the time runs out, but there might be other triggers, i.e. the enemy / player dies)
    In any of this cases i would need to work out the correct targets of the TimedEffect from the source and possible the target of the player activating the ability. Since i dont want to duplicate code, my approach would be 6 different classes for each of this cases (alle inheriting from TimedEffect).
    I dont see a good solution for
    1. transporting information (which only be present in certain child-classes of ability) to the TimedEffect
    2. Working out, which child-class of ability can have which kind of TimedEffect
    Of course it would be possible to give an ability not one array with TimedEffects, but (at least) six arrays of child-classes of TimedEffects (and some of this arrays are only present in certain childs-classes of ability), but this seems rather complicated to me.
     
  8. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    I'd have to know more about your project, but it seems to me that the Player and the Ability have everything any possible TimedEffect could need, am I right? Why not just pass them both as parameters to the timed effects and let each effect do what they need to do to whomever they need to do it.

    From a technical perspective, I see no reason why any Ability could not have any timed effect. If it's about game mechanics, things that are technically possible but you don't want to allow, you could add some sort of validation method to abilities. If it's about data compatibility, I see two alternatives:
    1. You have effects ignore their call to action when they don't have what they need. For example, if an effect needs an enemy and there are no enemies, it doesn't do anything.
    2. You divide your effect class into multiple classes, but trying to create as few classes as possible.
    I like the first option better. Maybe you could even have just one class for abilities and define concrete kinds of abilities by just setting their list of effects.
     
  9. pi_314159265

    pi_314159265

    Joined:
    Aug 14, 2022
    Posts:
    27
    My main concern ist data compability.
    As you mention i would pass the ability (the player is attached to the ability) to the TimedEffect. I would also take your mentioned first approach, but im not quite sure how. Let me try to eloborate where my problem is:
    1. At some point in the activation of the ability i would have to do something with the TimedEffects. Probably it would be somethink like
      Code (CSharp):
      1. foreach (TimedEffect te in timedEffects) {
      2. te.DoYourTimedEffectThing(this);
      3. }
    2. Now this is of class "Ability" (and not "TargetedAbility" (see my first post)). Hence it does not have the class variable target
    3. Some of the TimedEffects need a target in their function "DoYourTimedEffectThing". If i tried to write any code with ability.target i would get an error message?
      1. Is it possible to Overload the DoYourTimedEffectThing method to take a child class instead of the parent class? I.e. TimedEffect has a function DoYourTimedEffectThing(Ability ability) and a Child (of TimedEffect) has a function DoYourTimedEffectThing(TargetedAbility ability), which gets called instead?
     
    Last edited: Dec 28, 2022
  10. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    This is going to be of the top of my mind and with limited knowledge; it my give you some ideas, though. From your previous post, it seems to me there are three things that a TimedEffect could need:
    1. A reference to the ability's user.
    2. A list of enemy targets
    3. A list of ally targets.
    Number 2 and 3 could even be combined into a single targets list. You then have a method in your User class
    IsEnemy(target)
    to know if a particular target is a foe or an ally when needed. It's not necessarily better than having two separate lists; I'm just giving you options.

    Also, you don't need to have explicit lists of targets. You could instead have some virtual property
    targetCount
    and a virtual method
    GetTarget(index)
    . This way, single target abilities could return a count of 1 and only return a valid target for GetTarget(0).

    Anyway, abilities without targets would return no targets from their target related properties, but they would still have target properties. All abilities would probably have a user property. And that's that.

    Hm... well, one final thing to consider could be to put all that info in some struct or class called something like
    AbilityInfo
    and pass that to Effects instead of passing Ability to them. That way you can pass them only exactly the info you want to pass them. Also, that way you can apply them from places outside of abilities.
     
    Last edited: Dec 28, 2022
    pi_314159265 likes this.