best class structure for enemies and damage?

I’m having a hard time wrapping my head around how best to set this up. I’ll explain what I have so far.

• NPC class that has basic stats like health.
• EnemySpirit, EnemyRock, EnemyFireCaster, etc classes for each type of enemy. Inherits NPC class. Needs to process the damage received from a player spell.
• Player class that is the player. Casts spells with the intension of directing them toward the Enemy* class component. Handles player input, for now.

Player can access public void TakeDamage() from EnemySpirit with something like target.GetComponent().TakeDamage(n). That’s fine if every enemy happens to be an EnemySpirit, but the moment I broaden my game to include EnemyRock enemies and EnemyFireCaster enemies, that code breaks because they are not of the EnemySpirit class obviously.

Here are some solutions I have considered:
• Have NPC keep a variable that indicates what class the enemy is. It would be set whenever the enemy is created. That way the code could be written like this: target.GetComponent().ForwardDamageFromNPCtoEnemy(n). NPC would simply have a long list of all the enemy classes and could essentially choose the right version (GetComponent from a switch(). This list could be put on the Player, NPC, in a static singleton… not sure where the best place might be, but NPC for now.
• Have NPC handle the damage. I don’t see how this could be done because the entire purpose of the Enemy class is to handle the buffs and debuffs the enemy has, which affect how much damage they take. The NPC class would end up having to manage every possible enemy class I create which defeats the purpose of enemy classes to begin with.
• Interfaces? I am less experienced with interfaces. Would this be a possible solution?
• Generics? I am inexperienced with these and don’t know if this is a possible solution or not.
• Have Player somehow figure out what class the GameObject (which is “target” in my code) like
var mysteryClass = target.FigureOutWhatEnemyThisIsAndReturnClass();
target.GetComponent().TakeDamage(n)
but I don’t even know how you’d write such code correctly.

Maybe I’m just going about this all wrong. I’m open to any and all advice. I’m early in the coding stages of doing this, so it’s not difficult to go in any direction.

You’re already ahead of the game if you’re considering these things.

The parts you are missing is this:

  1. random people here cannot suggest the “best” because that is dependent on:
  • a. what you are doing in this game
  • b. how your brain thinks about code problems
  • c. how you evolve your approach as you make this game
  1. you also don’t know enough to decide finally because:
  • a. the game doesn’t exist; it is vaporware right now
  • b. you may learn more (hopefully as you iterate and create the game
  • c. software is SOFT, so you can change it when you learn

I recommend that you keep the above list of concerns in mind, along with just a general ongoing sense of questions such as:

  • “is this becoming fragile and error-prone?”
  • “am I repeating a lot of boilerplate?”
  • “am I painting myself into a corner?”
  • “why do I have to modify 27 places to add a new spell?”

ALSO:

Avoid code like this. Yes I know it’s common. But guess what? That’s where bugs are bred and fester.

If you have more than one or two dots (.) in a single statement, you’re just being mean to yourself.

How to break down hairy lines of code:

http://plbm.com/?p=248

Break it up, practice social distancing in your code, one thing per line please.

3 Likes

This got me thinking about I might tackle one of the points I brought up:
• Have NPC handle the damage.

After giving it some thought, I would indeed be duplicating quite a bit of code with having spell processing on every enemy class. I already know that most of the attacks will take on one of a few specific forms. I could write the basic form of what the spell does in NPC and just have the enemy classes pass to NPC the tiny details to adjust it. That would mean writing most spells only once in NPC and it would solve my problem of targeting the enemy’s TakeDamage() function.

Thanks for the quick reply.

1 Like

NPC can obtain his damage from anywhere.
So let’s say you place 17 NPC in a prebuilt world, they slightly vary from each other (spell casts) but are not prefabs.

why no have the NPC all reference a dedicated spell script to obtain the damage of the current spells they have equipt? But when I say reference a dedicated spell script; what I mean is to reference a single instance of this script. One which you, as the developer only have to modify once from a single component station. Instead of say later on you decide the fire spell needs to deal more damage, and you have to go and modify 14 different prefabs that use the fire spell. Could be easier if all those prefab or GO reference the one script.

It seems to me that TakeDamage should be a virtual method in NPC for your case, since every Enemy class inherits from NPC. Here’s a very basic example:

public enum DamageType { Normal, Magic }

// The DamageInfo is so the NPC knows more about what kind of damage it is and
// where it comes from. You could embed the actual damage amount in it if you want.
public struct DamageInfo
{
    public DamageType type;
    public Object damageSource;
}

public abstract class NPC : MonoBehaviour
{
    [SerializeField] float _life;
 
    // You can write abstract instead of virtual here, then you wouldn't have a
    // default implementation of TakeDamage and you'd have to implement it in
    // every class that inherits from NPC.
    public virtual void TakeDamage(float damage, DamageInfo info)
    {
        _life = Mathf.Max(_life - damage, 0);
    }
}

public class SpiritEnemy : NPC
{
    // This method checks that the damage type is Magic and, if it is, it calls the
    // the base implementation of TakeDamage defined in NPC. You could do whatever
    // you want with the damage here, though; you don't have to call the base method.
    // Since we have a default implementation of this method in NPC, you could
    // even skip implementing TakeDamage here.

    public override void TakeDamage(float damage, DamageInfo info)
    {
        if (info.type != DamageType.Magic)
            return;
        base.TakeDamage(damage, info);
    }
}

Then your player could do var npc = target.GetComponent<NPC>(); and if an NPC is found, you do something like this:

var info = new DamageInfo {type = DamageType.Normal, source = this };
npc.TakeDamage(damage, info);

Now, if you want to use interfaces, it’s very easy, and it would allow you to damage all kinds of things, not just NPCs. You could damage rocks, trees, etc.

// First you need to create an IDamageable interface:
public interface IDamageable
{
    public void TakeDamage(float damage, DamageInfo info);
}

// Then you implement it in your NPC like this:
public abstract class NPC : MonoBehaviour, IDamageable

// Then instead of doing
var npc = target.GetComponent<NPC>();
// You do
var damageable = target.GetComponent<IDamageable>();
// And call TakeDamage on it:
var info = new DamageInfo {type = DamageType.Normal, source = this };
damageable.TakeDamage(damage, info);

If you want other non NPC things to be damageable, you just add a public TakeDamage method and implement the
IDamageable interface on their scripts.

EDIT: Forgot to make NPC inherit from MonoBehaviour in the examples.

1 Like

AnimalMan: It’s complicated because I have the base damage of the spell, the talent points affecting the damage output, and buffs the player may have, all of which my alter the damage dealt, so at least some data will have to come from the individual spell classes. What you suggested about having a main spell class is a great idea though and I have just made my first version of that based on your post. I mostly have it working so far.

Oscar: virtual, abstract, and override, oh my! It’s been a while since I’ve touched that stuff. I’m going to have to watch a YT video to recover that memory file. I will definitely look into this though. In the past hour or so I actually ran into the unexpected problem of having Start() not work in the parent class and found some posts about needing to make it virtual to possibly solve the problem. So it looks like I’m going down the virtual road one way or another now.

Thanks for the replies. I try to answer some questions about software I know, but C# I’m not confident enough to really answer much. I give my input on Blender and Maya forums where I am more grounded. Helpful posts like these on forums make the world go 'round.

:slight_smile: That’s nice of you.

Virtual methods are what you need. 9 out of 10 times, when you have a bunch of classes that all respond to the same kinds of calls in different ways, you’re dealing with polymorphism. And polymorphism almost begs for virtual methods. The gist of polymorphism is this:

You can use a child class as if it were the parent class. Like, in your case, if you call GetComponent<NPC>() , you can get a script that is really a SpiritEnemy, because a SpiritEnemy is also an NPC.

This is specially useful in combination with virtual methods. Virtual methods allow child classes to redefine what a method defined in the parent class does. You can mark a method with virtual in a parent class, then a child class can reimplement that method by marking it with override. For example:

public class NPC : MonoBehaviour
{
    public virtual void SaySomething()
    {
        Debug.Log("I'm an NPC");
    }
}

public class Enemy : NPC
{
    public override void SaySomething()
    {
        Debug.Log("I'm an Enemy");
    }
}

In this example, var npc = GetComponent<NPC>(); could get a plain NPC or an Enemy. Then, if you call npc.SaySomething(); and it’s an Enemy instead of a plain NPC, it will print “I’m an Enemy” instead of “I’m an NPC”. If you wanted Enemies to print both “I’m an Enemy” and “I’m an NPC”, you could make it like this:

public class Enemy : NPC
{
    // This way, Enemy.SaySomething will run the code that is defined in NPC,
    // after doing it's own thing.
    public override void SaySomething()
    {
        Debug.Log("I'm an Enemy");
        base.SaySomething();
    }
}

Now, when you mark a class as abstract, all you’re doing is saying “I don’t want to be able to create instances of this class.” You can’t add an abstract script to a GameObject. To use an abstract class, you need to make child classes. I thought it made sense to make NPC an abstract class, as you’d probably want to avoid using it directly. But, if you want, you could remove the abstract word from example in my first post without problems.

Abstract classes can have abstract methods. Abstract methods are like virtual methods, but they don’t have implementations. Like, in this example, you could have something like this:

public abstract class NPC : MonoBehaviour
{
    public abstract void SaySomething();
}

public class Enemy : NPC
{
    public override void SaySomething()
    {
        Debug.Log("I'm an Enemy");
    }
}

Previously, the Enemy class could skip overriding SaySomething, but now it needs to do it to avoid compile-time errors. Abstract methods need to be implemented in child classes. In practice, it saves you from having to write a base implementation for a virtual method when you know it will always be overriden.

Finally, Interfaces are a bit like abstract classes that can only have abstract methods. The advantage of them over abstract classes is that you can inherit from multiple interfaces at the same time, so it’s really helpful for making things modular and flexible. I would avoid interfaces until you’ve refreshed your knowledge on basic virtual methods, though, to avoid making the learning curve too steep. I’m pretty sure that once you get the basics of virtual methods, interfaces will be very easy.

Yes, they would allow you to change your code to this:
target.GetComponent<ITakeDamage>().TakeDamage(n)

Your enemies would be rewritten to:
public class EnemySpirit : NPC, ITakeDamage

Now you don’t need to worry about the specific enemy type.

Oscar: Thanks for the explanation. You should write tutorials. You seem like you’d be great at it. I will be going over all of this stuff today to see what I can do with it. Thanks a million.

Stardog: Good to know. Thanks!

@Bluelattice5555
You could get inspired from the architecture of Unity itself to organize your game code. Unity architecture is component based. In a component based architecture, an object does not get its features by inheritance but by composition. In your game for instance, if an object can take damage, it would have an “Health” component. If it is able to talk, it would have a “Dialogue” component… and so forth. Therefore, you can create different NPCs classes by combining different components and saved them as prefabs.