Is my approach is good for generics?

I am trying to create a rpg and I used generics for the data type. Here is the code about the question.

public abstract class Entity<E> : MonoBehaviour where E : EntityData
{
    public E entityData;

    public virtual void SpawnEntity()
    {
        entityData.CreateMutableData.Invoke();
    }
    public void CreateData(ImmutableEntityData data)
    {
        entityData.Create(data);
    }
}
public class Slime<E> : Entity<SlimeData>
{

    private void Awake()
    {
        SpawnEntity();
        entityData.jumpSpeed = 5;
    }
}
public class Player : Entity<PlayerData>
{
    private void Awake()
    {
        SpawnEntity();
       
    }

}

As you can see, I use the generics to not type cast while trying to get value from the data. For example, slime data has jump speed field and I can get the value without doing (entityData as SlimeData).jumpSpeed. Is it a good approach or do you have any suggestion about that?

I’m not much for inheritance because it makes things brittle, hard to change, builds this enormous rigid inflexible unchangeable hierarhcy.

I vastly prefer interfaces.

Using Interfaces in Unity3D:

Check Youtube for other tutorials about interfaces and working in Unity3D. It’s a pretty powerful combination.

1 Like

I think you made a typo in your code box #2 with Slime<E> you probably meant just Slime. If not, well, that’s not a smart thing to do, mixing generics with concrete types.

Other than that, generics are great, but I’m not sure in the context of what you’re doing. Typically you want them for generic stuff, pun not intended. High-level infrastructure doesn’t count as generic stuff, it’s highly domain-specific, even if you do believe you’re making things more flexible.

If you really want flexibility, working with interfaces is the way to go. You can even make them blank, so they really work like tags basically. Though I’m not necessarily recommending blank interfaces.

1 Like

Yeah, I forgot to delete the E on the Slime, it wasn’t at there normally, i was trying something at that moment. By the way, can you help me that how can I implement interfaces? For example I have these codes,

public class HealthController
{
    [SerializeField]
    private int currentHealth;
    public int maxHealth;
    public UnityEvent doAfterZeroHealth;

    public HealthController(int maxHealth)
    {
        this.maxHealth = maxHealth;
        currentHealth = maxHealth;
    }

    public int Health { get { return currentHealth; } private
            set
        {
            if(currentHealth == 0)
            {
                doAfterZeroHealth.Invoke();
            }
        }
    }
    public void ChangeHealth(int changeAmount)
    {
        if(currentHealth < maxHealth)
        {
            if (changeAmount > 0) IncreaseHealth(changeAmount);
            else DecreaseHealth(changeAmount);
        }
    }
    private void IncreaseHealth(int inceraseAmount)
    {
        if (currentHealth + inceraseAmount < maxHealth)
        {
            currentHealth += inceraseAmount;
        }
        else
            currentHealth = maxHealth;
    }
    private void DecreaseHealth(int decreaseAmount)
    {
        if (currentHealth - decreaseAmount > 0)
        {
            currentHealth -= decreaseAmount;
        }
        else
        {
            currentHealth = 0;
        }
    }
}
public class PlayerData : EntityData
{
    public HealthController healthController;

    public override void Create(ImmutableEntityData data)
    {
        ImmutablePlayerData playerData = data as ImmutablePlayerData;
        healthController = new HealthController(playerData.baseMaxHealth);
    }
}
public class Player : Entity<PlayerData>
{
    private void Awake()
    {
        SpawnEntity();
     
    }

}

Now, if I create an interface called “IDamagable”, which class should I implement to? I think it is health controller but if i do that, i wouldn’t use interfaces for tag like “object is IDamagable” since the interface is on the health controller. Note that, I will use composition over inheritence, inheritence is only for the common things that all object will have.

Any class that takes damage.

Could be one that counts down hitpoints.

Could be one that does physics breaking.

That’s what I would use interfaces for.

Something IMovable interface could take an argument such as void Move( float time);

An airplane might fly.

A slime might slime along.

etc.

1 Like

Inheritance commonly makes sense only if you go one step deep at most, for some systemic work. Rarely you might encounter two steps. Anything beyond that is looking for trouble in Unity.

(Obviously this is a generalization. I’m not saying it doesn’t exist, but it can be avoided.)

Anything that is damagable. That’s the basic principle, that’s why we use adjectives for the interface names.
In fact, that HealthController is not IDamagable. Can you damage a health controller? Nope.

1 Like

Then I will implement that to the mono behaivour one, it seems more make sense, I asked that because I have data classes for every mono behaivour class. Then should data classes have methods or methods should be in the mono behaivour classes? I am sorry that i asked a lot question but i try to understand.

What are data classes? Do you mean ScriptableObjects? If you don’t, you ought to.
ScriptableObjects are data-only MonoBehaviour-likes, that serialize just the same, but do not live on the loop, and can be sprinkled with some code.

If you’re coming from vanilla OOP, Unity is slightly skewed in every sense, but that’s actually not a bad thing.

1 Like

I use SO for the base data which is immutable and create a mutable data that are what I mentioned above by saying data from them. Mutable data are stored in plain class and i am attaching it to the mono behaivour one. What i asked above is should I keep the methods on the mutable data class or mono-b class or also base immutable data?

I’m guessing by this you mean you’re feeding the relevant data from SOs to your MBs. It’s ok to have some means of mutable data sets for the runtime. And the methods should go wherever they belong.

If the methods are controlling the MB’s instance data, they belong to that MB. If you have some data-specific methods, keep them with data. Other than that I’m not sure if I understand the question.

1 Like

I mean if you’re asking that in the sense of some general architecture, that doesn’t exist.

Architectures can differ a lot. It largely depends on the type of your game and the way you’ve designed your core and surrounding systems. A turn-based game will be developed differently than FPS. The differences can be huge actually, from where the data is located, to how centralized the code is, what managers will govern the state, and also how you intend to cross-message across domains.

A small project can have nearly every top-level behavior in a single class. Then you distribute specific and repeating behaviors in a spine model.

A huge game with many concerns is probably using a multi-layered infrastructure and has dedicated departments for the UI, gameplay, savegame, animation, rendering, pathfinding, scene management, event systems, console logging, web request, who knows what else. All of this needs to talk to some central management, so to organize all of this, the concerns of the actual gameplay state and model become tiny in comparison.

2 Likes

So I believe the best answer is to simply go after whatever system you find the most friendly to work with. On one hand you want everything accessible, just there, ready to be used, on the other, you want to avoid exactly that, because that leads to a swamp, and good amount of isolation and modular and independent solutions will take you very far, because it is inevitable you will refactor or insert many things during the development.

Only from experience you’ll be able to find the sweet spot. And it’s never perfect. Don’t waste too much time early on, because the real achievement is to get past the point of no return, somewhere in-between the early- and the early-to-mid stages of development, that part is always messy as you’re consolidating standards and still learning what the hell it is that you’re making.

1 Like

Understand it thanks, i created an IDamagable interface and attached to the player hitbox MB

public class PlayerHitbox : MonoBehaviour, IDamagable
{
    private Player player;

    private void Awake()
    {
        player = GetComponentInParent<Player>();
    }
    public void TakeDamage(int damage)
    {
        player.entityData.healthController.ChangeHealth(-damage);
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("trash"))
            TakeDamage(5);
    }
}

attached this class to the child of the player object, health controller is the health component of the player data as it is obvious, and created IMovable interface and attached them to the player object itself

public class Player : Entity<PlayerData>, IMovable
{
    public void Move(float speed)
    {

    }
    private void Awake()
    {
        SpawnEntity();
      
    }
    private void Update()
    {
        print(entityData.healthController.Health);
    }

}

As you said, architecture can change but i am trying to understand the composition and interface logic. Thanks for your helping, it is more clear now.

1 Like

Definitely, if i keep to think instead of doing, i wouldn’t do much thing.

1 Like

If you don’t mind, I have 2 questions.
1- Let’s say I have 2 interface which are IFlyable and IKnocbackable and Slime class. Some of slimes can be knocbacklable and flyable, some of them are knockbackable and can’t fly and some of them are can’t knocbackable and flyable, some of them are can’t fly and knockbackable. Should I create 4 slime class for different situations?
2- Player can’t fly at the beginning and after wearing an item, it gains fly ability, should i implement IFlyable to the player since it will gain flying ability if wears that item. Thanks in advance.

That’s a funny case and the answer is no. That would compromise the whole point.
Obviously if you have some slimes that are knocbackable and flyable, then the slimes are knocbackable and flyable.

It’s some instances of these slimes that seem to have forgotten how to do it.
So make sure you can inform such instances appropriately.

You can introduce ‘species’ or ‘templates’ where some traits get inhibited automatically.
Inhibitors may also be separate components, see answer below.

No, that’s the case for a composition. You add a Flyer component to the player, which itself is IFlyable.
Make sure you discover what is doable by composition alone. Components aren’t there for nothing.

Hm, then should I add the components as a list instead of by one by? I have an idea about that:

public class SlimeData : EntityData
{
   // public HealthController healthController;
  //  public FlyController flyController;

    public List<BaseComponent> components;
    public int jumpSpeed;

    public override void Create(ImmutableEntityData data)
    {
        components.Add(new HealthController(123));
        components.Add(new FlyController());
      /*  ImmutableBlueSlimeData slimeData = data as ImmutableBlueSlimeData;
        healthController = new HealthController(slimeData.baseMaxHealth);
        flyController = new FlyController();*/
    }
}

If I don’t have to create classes for every situation, I guess i need list of components to do that. If I create a slime that can’t be knockbackable but can fly, then i will just do “components.Add(flycomponent)”. I was confused that you said that you shouldn’t add IDamagable to the health component, but if it has a health component, it should be damagable, isnt it? it confuses me some. Then shouldnt I add the IDamagable to the health component like you said that IFlyable should be added to the fly component? If my questions are weird, sorry. I am just trying to learn.

Well it certainly can sound confusing when you’re overthinking it like that.
First of all, these are all just useful mnemonics, there is no universal ontology or absolute semantics in oop that can guide every single design people can think of.

In my mind, no, health receiver can receive and process damage, but it itself is not damagable. What is damagable is what logically should be damagable – a character or object in your game. Health component doesn’t sound like it lives inside your game world, it is quite apparently an artificial construct, an implementation detail, an emergent artifact from how you designed this system. Don’t get lost in such tangents.

Decide the domain in which you’re inventing categories for yourself, you can’t just cross between different domains and expect the categories to align naturally. Also talking about this extensively doesn’t lead you anywhere.

Instead go make a system, find out why it’s bad. Literally every system is bad somehow, but the more experience you have, the more easier it becomes to locate where and how exactly it hurts your progress, limits your options, whatever.

Try to keep your ultimate goal in front of you. Is it to find the meaning of life? Probably not. It is so that some player can smack a goblin with a +2 sword of furies? Much more manageable.

1 Like

Yeah, as you said, I am overthinking :confused: I was using inheritence over composition and very rarely using interfaces, that’s why i am struggling in that situation. Thanks for your suggestions, actually i did some player enemy entity system in unity before but it was bad because i used inheritence.

1 Like

Note that the particular form of struggling you are doing is truly just overthinking it.

All of this stuff can be done any number of possible ways, all of them valid in some context.

You need to go DO it and by doing it you will begin to learn the pros and cons of each way.

While you may THINK you are learning by asking 57 questions here, you actually aren’t learning.

Yes, we’re answering your questions, but the deeper into the interface / composition / inheritance weeds we get, the less you are actually retaining, and the less this will actually help you make a game.

Learning is the modification of behavior as the result of experience, not as the result of just reading words.

Go get that experience. Come back often. Check in. Show what you’re doing and what your pain points are. “I did it this way but now it’s painful when I add a new Slime that also can be poisoned…” etc. But DO the work first because often what you contemplate as “this will be painful” will actually be quite easy.

2 Likes