How do you all structure your "messaging" system between gameobjects

When I raycast against objects and need to call a method on them I used to use interfaces but now I think I’m just going to make one big base class and then override whatever methods I want the object to inherit.

public class Entity : MonoBehaviour
{
    // whenever something attacks us
    public virtual void Attack(Entity e) {}
    public virtual void Attack(Character c) {}
    public virtual void Attack(Player p) {}
}

And just have all of my objects inherit from entity and when I do my raycasts against random objects

        if (Input.GetMouseButtonDown(1)) {
            RaycastHit hit; Vector3 start = new Vector3(head.transform.position.x, head.transform.position.y, head.transform.position.z);
            if (Physics.Raycast(start, head.transform.forward, out hit, 5f, ~LayerMask.NameToLayer("Player"))) {
                hit.transform.GetComponentInParent<Entity>().Place(this);
            }
        }

And they can just have specific behaviour based on whatever was interacting with them

    public override void Attack(Player p)
    {
        Debug.Log("was attacked by a player");
    }

I feel like this is easier than interfaces because I don’t have to implement a crap load of methods on each child class and I don’t have to do a bunch of checks to see what kind of object initiated the interaction.

How do you all do it?

Please watch this video

2 Likes

Thx for reply Anders. That’s cool but let’s say I have a potion object with IUse and I want a whole bunch of other objects to use the potion in a different way. I need to get a reference to the object that is using the potion when I call the interface.

I would have to pass a whole bunch of different objects as parameters no?

I dont really see a problem. Normally a game action, event or what you want to call it, needs to operate on a single interface. If not its a bit of a code smell

Example from my game

public interface IPhysicalHandCollidable
{
    void OnCollide(NVRHand hand, Collision col);
}
public void OnCollisionEnter(Collision collision)
{
    var collidable = NVRInteractables.GetInteractable(collision.collider) as IPhysicalHandCollidable;

    if (collidable != null)
    {
        collidable.OnCollide(hand, collision);
    }
}

Used like

public void OnCollide(NVRHand hand, Collision col)
{
    if(IsLockedBack)
    {
        var force = Vector3.Project(col.relativeVelocity, SlamDirection.up);

        var angle = Vector3.Angle(SlamDirection.up, col.relativeVelocity);

        if (angle < 60 && force.magnitude > 0.25f)
        {
            ReturnSlide();
            hand.ForceGhost();
        }              
    }
}
1 Like

events and subscriptions usually, there are exceptions, but for the most part…

I think events and actions are two different things. Events are nice to decouple UI from actions in the domain. Like a enemy kill event to update the score in the UI etc.

But I wouldnt use it for actions. Some kind of command pattern maybe.

The key here is interfaces because if you tie it to concrete implementarion there is zero flexibility.

depends, actions often happen in response to some form of event, but I do agree interfaces make life sooooo much easier, you can have 100 actions, much like any ability in MMO game, and they all conform to the interface, so, can be used without a sea of case or if statements.

In the case or raycasting and triggering something, yes, an interface, but in the title of messaging, events/subscriptions are a good answer… was trying to add to yours, not contradicting

So like the code I posted above by just adding all the behaviours in one big parent class I can just do one liners in my objects like so.

public override void Attack(Player p) {}
public override void Attack(NPC npc) {}

If I only want to process attacks from player objects or NPC objects then I can just use one liners like that.

But in order to do this with interfaces I would have to Implement a separate interface for every object or somehow cast the parameter to the correct object.

public void Attack(Player p) {}
public void Attack(Goblin g) {}
public void Attack(Rabbit r) {}
.. so on, so on

You see what I mean? I need a reference to the object that is starting the attack.

Thats not how abstraction work. You need to find common things so a attack can be generlized. I mean the attack action. The underlying code can differ. But the interface need to stay the same.

Maybe swap it around the IAttacker attacking a IAttackable etc.

Edit: take my above code as example its a PhysicableHandCollifable (VR game with hands). The interface lets any behaviour interact with the hand. But the hand doesnt know anything about the collidable more than its a collidable.

If I do an IAttackable interface and then raycast an object and get the interface and call Attack then how do I send a reference of the attacker? I thought maybe using generics I could do this but I just dont get it.

I mean, I get what you are saying you need to “generalize” the attack. Like I could just pass parameters like dmg, knockbackRadius, etc etc. But that seems so limited? There could be a crap load of parameters I need to pass and I would rather just pass the object that is attacking.

So

Lets pretend you have

interface IAttackable
{
    void Attack();
}
    

then, lets say

class Rabbit : Monobehavior, IAttackable
{

// mucho stuff
    public void Attack();
}
    

So, you go to attack something, all you need test for is say GetComponent<IAttackable>.Attack()
or, to revert it, maybe you add a TakeDamage so you Attack(IAttackable victim) and in that you reference your victim with victim.TakeDamage … which you added to your interface…

see?

Another way would be that all your enemies stem from one Emey class that has an Attack or TakeDamage, but, this way, they need have absolutely nothing in common, such as it could be a destroyable wall…

1 Like

Hey thx for reply bugfinders.

I don’t see how to pass a reference to the victim though, can you show the syntax?

GetComponent<IVictim>().Damaged()

Are you saying the attacker needs to have an IAttacker interface and you pass that in the damaged call?

GetComponent<IVictim>().Damaged(this)

But even if that were so, its just an interface, you cannot grab the attacker from it.

public void Damaged(IAttacker attacker) {
    
}

Did you read the whole post? I showed an example interface, applying it to a class, how you could call the attack, and how to maybe call attack with the victim being any class that is IAttackable, and what you might do?

You don’t need for the defender to have knowledge of the Attacker, just to have knowledge of certain information the Attacker has.

One solution would be the IAttacker interface to have properties that are only getters of that info, for example damage, knockback radius etc. then you call the Damage(this) as in you example. I don’t like that solution as it creates a tight couple between the victim and the attacker. That way the victim can have knowledge of the attacker’s properties it needs.

In you case a better solution would be to create a new type that holds all the information you want to pass from the attacker to the victim. For example an AttckInfo class. This class will only have data that the attacker will fill, like damage, knockback etc and the victim will consume. The IVictim would have a Damage(AttackInfo attackInfo) method and the attacker will create a new AttackInfo, fill it with the information it has, and pass it as a parameter to the IVictim’s Damage method.

1 Like

Thx meredoth. I think I’ll do that. I have a ton of info that I need to change/pass to the objects but if I make a struct to hold every possible variation of this info then the interface would only need one method to be implemented. :+1:

Don’t make it a struct, as it is value type and you would loose performance if it has a lot of data and you pass it by value. You can try to pass it by reference but then you have to be careful for defensive copies, plus the struct initialization problems that exist with the default parameterless constructor etc. Why bother with all that, just make it a class.

public interface IAttackable
{
    void Attacked(AttackInfo info);
}

public class AttackInfo
{
    public GameObject attacker;
    public RaycastHit hit;
    public int damage;
    etc, etc, etc
}

Why do you need the Attacker in the AttackInfo? Just pass as data everything you need that are common to all the attackers. By having a gameobject Attacker you loose type safety. You don’t need for example Attacker.transform or Attacker.HitPoints just Transform and HitPoints that each attacker will fill, like the damage in your example

Let’s say the attacker hits a spiked wall and I want to reflect some damage back to the attacker. I need a reference to it.

Then the ‘Attacker’ is also an ‘IVictim’ from an attack from the wall. I am not against the attacker info and methods, just against the generalized gameobject type.

Create interfaces that have specific behaviors about what an object can do not what it is. If an attacker for example can take damage then it is not an attacker but an IDamageable, this will be an IDamageable field.If an attacker can catch fire then it is an IFlammable etc.

You fill that info in your AttackInfo packet and then you use what you need from the defender by checking for null. For example if an attacker attacks a fire,if the IFlammable is not null, then you can call the IFlammable’s method TakeFireDamage() where the IFlammable field has been filled by the attacker if it implements the IFlammable interface etc.

1 Like