Multiple Methods with different logic

Hello,

I am creating a roguelike where monsters are spawned, all from a Monster class. Monsters essentially all have the same attributes and methods. However, I would like to change the logic inside the methods themselves to be tailored for specific species: for example, a troll might have a strong preference for melee attacks, while an imp would prefer ranged abilities. Both monsters would call an Attack method, which take the same arguments and also return the same outputs, however their internal logic would be different.

Ex:

class Monster
{
     AttackType Attack(Character targetChar)
    {
            // Troll will do one thing, while Imp will do another
            // Different logic to determine what each monster species will choose as an attack
           // Two examples of what might be returned:
            return meleeAttack(targetChar, damage);
          //or
           return rangedAttack(targetChar, damage);
    }


}

I have been considering just inheriting from the Monster class (which would be abstract in this case) and overriding the methods which I would define as abstract. However, I was wondering if there is a better way to maybe approach this kind of problem, since I would probably have a few methods inside of Monster that I would like to keep private, i.e. inaccessible to other . I have also been looking at using the delegate function parameter, it seems to be sort of what I am looking for.

The classic example is the abstract class with overrides for unique behaviors. Yet, the situation presented in Unity doesn’t fit 100%. In the classic example there is a common, neutral base class from which derived classes provide unique behaviors. The storage of these objects is the base class, which is to say that code using this class does not know (and shouldn’t have to know) what the derived type actually is. Code using such objects rely upon the virtual behavior defined by abstract functions to handle mutation of the behavior.


In Unity, however, these classes are usually components attached to a GameObject. For those to instantiate correctly, the framework creates them, and therefore must know the derived type. It doesn’t work easily to limit Unity’s framework to only knowledge of the base type. If I assumed ‘monster’ is the base, while ‘troll’ and ‘imp’ are derived from ‘monster’, the objects you attach these scripts to must select an ‘imp’ or a ‘troll’. They can’t instantiate a ‘monster’ because it is an abstract class, and there is no factory easily accessible (that I’ve seen) for the framework to instantiate.


This isn’t a problem, you can instantiate trolls and imps without issue. My point is that the design does not 100% fit into the entire model of abstract base classes, and my summary message by detailing this observation is that, as your post hints, it may be that an abstract base class, the classic design example, has a better alternative for the situation presented in Unity.


Your thought about delegates is exactly on point in this line of thinking. Delegates have lots of potential uses, but among them is to mutate the behavior of a class similar to that of abstract base classes, but it can work better in the kind of scenario presented by Unity, where the class is applied as a component from the viewpoint of the derived object, not the base object.


Put another way, virtual functions (a C++ term) or abstract functions (the C# name for the same thing) are, under the hood, implemented as pointers to functions (or methods as C# names them). Virtual (or abstract) base classes are fashioned with an “internal” or “invisible” table of function pointers which are assigned when the derived object is configured. You can do nearly the same thing with delegates, without having to create abstract classes (in a system like Unity where they are not as easily or natively supported).


When using delegates for this design, you’d write your code to call methods using variables declared as delegates which are configured during initialization so that the class behaves in a fashion fairly identical to that expected of a virtual descendent (the derived class that overrides abstract methods in C#). The missing part, however, is the fact that a derived class can introduce new and unique member variables and methods applicable only to that derivative, so the result isn’t quite as organized and clean when those differences are complex.


Therein comes a determining factor. If the differences between your monsters are not large, and do not involve significant unique member variables, the delegate oriented approach may do quite well and offer some net gain because Unity’s design doesn’t quite lend itself to abstract base components perfectly. On the other hand, if the differences between monsters is vast, and there are a considerable number of methods and member variables applicable only to certain types of monsters, you may need to consider a different design, possibly including the abstract base.


Another alternative, however, is to consider Unity’s own use of components, and mimic that. You could establish a central monster class which is configured by attaching behavioral component classes which ultimately configure the unique behaviors defining each character. This can be quite flexible, bypasses those issue about abstract bases and does organize code well. It is also a bit more complicated, and you’ll end up with a puzzle about where methods do work (the components may often have to operate by method calls from the owning Update function, but are operating in a scope that doesn’t have access to the underlying MonoBehaviour class, and thus need a reference to it, or will be calling back to the MonoBehviour class for some certain work (like access to the gameObject or transform members).

I think your best bet would be to use an abstract class as you’ve considered. You can still have private methods in your abstract class, but you can also have protected methods that are accessible to the derived class but inaccessible to other classes.

Thank you very much for the well thought out answer, I didn’t expect to get something like that!

I think I will initially try to approach this problem with abstract classes, the reason being that it seems easier to implement initially (I would describe myself as an intermediate coder) The other reason is that because my game is turn based and using TileMaps, I am not asking for real-time performance, my key goal is to write well organized component managers that provide a solid interface for each system in my game to communicate with each other, ex: Dungeon Map Manager, Monster Manager, Player Manager). I am teaching myself how to set up event systems for that very reason, and I think a lot of my child classes will be inheriting a lot of boiler plate code.

I will be reading up more about delegates, they seem incredibly useful, and I might still change my mind, since right now the actual player/monster system is still being planned out on paper, and not code :).