user3684950
user3684950

Reputation: 27

Tricky Inheritance-Related Architecture

I'm trying to design a system in C# for different character types in a video game. I have a simple inheritance tree for my character types with a Humanoid base class that has several derived classes such as Human, Alien, etc. Each of these character types includes a body with several parts. Currently these parts are stored in an instance of a Body class and each part is described by an instance of BodyPart class.

The main complication comes when I want to have derived classes for BodyPart since each character type's body parts will have some additional specific properties. The main reason I want the BodyPart instances to be grouped in a Body class is so that I can use an indexer since I need to able to iterate over them and still be able to use dot notation to access specific parts easily.

The following pseudocode should give you an idea of what I'm trying to achieve and how I have already tried to implement it.

public class BodyPart
{
    public float health;
    //...
}

public class HumanBodyPart : BodyPart
{
    public float exampleHumanStuff;
    //.. human specific stuff
}

public class AlienBodyPart : BodyPart
{
    public float exampleAlienStuff;
    //.. alien specific stuff
}

public class Humanoid
{
    public class Body<T> where T : BodyPart
    {
        public T head;
        public T leftArm;
        public T rightArm;
        //...

        public T this[int i]
        {
            get
            {
                switch (i)
                {
                    case 0:
                        return head;
                    case 1:
                        return leftArm;
                        //...
                }
            }
        }
    }

    public virtual Body<BodyPart> body { get; }

    public void Example {
        body.head.health = 50;
        body[2].health = 55;
    }
}

public class Human : Humanoid
{
    public Body<HumanBodyPart> humanBody;
    public override Body<BodyPart> body { get { return (Body<BodyPart>)humanBody; } }

    public void Example
    {
        body.rightArm.exampleHumanStuff = 5;
    }
}

public class Alien : Humanoid
{
    public Body<AlienBodyPart> alienBody;
    public override Body<BodyPart> body { get { return (Body<BodyPart>)alienBody; } }

    public void Example
    {
        body.leftArm.exampleAlienStuff = 5;
    }
}

The dead end with this approach is that the Body class is not contravariant (I think?) so casting a Body<HumanBodyPart> to Body<BodyPart> won't work. But I can't figure out another way to access the Body<HumanBodyPart> class instance in Human from the base Humanoid class so that in the base class it's treated as a Body<BodyPart>. Is there a better way of doing this?

Upvotes: 0

Views: 51

Answers (2)

Guru Stron
Guru Stron

Reputation: 142213

You can make Humanoid generic of BodyPart itself:

public class Humanoid<T> where T : BodyPart
{
    public class Body 
    {
        public T head;
        //...

        public T this[int i]
        {
            get
            {
                switch (i)
                {
                    case 0:
                        return head;
                        //...
                }
                return default;
            }
        }
    }

    public Body body { get; }

    public void Example()
    {
        body.head.health = 50;
        body [2].health = 55;
    }
}

public class Human : Humanoid<HumanBodyPart>
{
    public void Example()
    {
        body.rightArm.exampleHumanStuff = 5;
    }
}

public class Alien : Humanoid<AlienBodyPart>
{
    public void Example()
    {
        body.leftArm.exampleAlienStuff = 5;
    }
}

Also this class hierarchy does not support all possible use-cases (for example collection of different Humanoid's, but you can workaround some with non-generic interfaces (for Humanoid and Body)).

As for variance it is supported only for interfaces and delegates (and arrays, but don't use it) in C#. In your case something like this will work:

public interface IBody<out TInner>
{
    public TInner head { get; }
    public TInner leftArm { get; }
    public TInner this[int i] { get; }
}

IBody<HumanBodyPart> humanBody = ...;
IBody<BodyPart> body = humanBody; 

Upvotes: 1

zsoltTakacs
zsoltTakacs

Reputation: 53

I assume you want to assign a specific BodyPart of the Humanoid class. I would use this:

public class Humanoid <T> where T : BodyPart
{
    public class Body<T>
    {....

And in the end when you describing a Human you can use:

public class Human : Humanoid <HumanBodyPart>
{.......

Upvotes: 1

Related Questions