Friend Foe identification

posted in trill41 for project ABx
Published August 07, 2019
Advertisement

The game mechanics doesn't allow to use damage skills on allies and heal or protection skills on foes. This makes it necessary that the game can identify allies and foes. A simple if statement seems to be sufficient for that, but it's not. There can be many different combinations of relations between the groups in a game, see the image.

friend_foe.png.90b84f89e2cef83f6be1d56ba3a6565a.png

 

So there are different groups which have different relations to each other. So how to solve this problem? One could have the idea to maintain a list of friend and foe groups, but well, not really, because that's probably the most inefficient way to do this. Instead I chose to use a Bit mask which encodes the friends as well as the foes. The Actor class was extended as shown bellow:


class Actor
{
private:
    /// Lower 16 bit friends, upper 16 bit foes
    /// This gives us 3 types of relation: (1) friend, (2) foe and (3) neutral
    /// and (4) in theory, both but that would be silly.
    unsigned groupMask_{ 0 };
    /// Get lower 16 bits of the group mask
    unsigned GetFriendMask() const { return groupMask_ & 0xffff; }
    /// Get upper 16 bits of the group mask
    unsigned GetFoeMask() const { return groupMask_ >> 16; }
public:
    void AddFriendFoe(unsigned frnd, unsigned foe)
    {
        groupMask_ |= (frnd | (foe << 16));
    }
    void RemoveFriendFoe(uint32_t frnd, uint32_t foe)
    {
        groupMask_ &= ~(frnd | (foe << 16));
    }
    bool IsAlly(const Actor* other) const
    {
        // Return true if they have matching bits in the friend mask
        return ((GetFriendMask() & other->GetFriendMask()) != 0);
    }
    bool IsEnemy(const Actor* other) const
    {
        // Return true if we have a matching bit of our foe mask in their friend mask
        return ((GetFoeMask() & other->GetFriendMask()) != 0);
    }
};

This makes it possible to have 16 different groups on one map, which have different relations to each other. To define who is friend with whom and who is an enemy of whom, only the AddFriendFoe() method has to be called when the Actor is created:


void CreateActorsForGame()
{
    static const unsigned GROUPMASK_NONE = 0;
    static const unsigned GROUPMASK_1 = 1;
    static const unsigned GROUPMASK_2 = 1 << 1;
    static const unsigned GROUPMASK_3 = 1 << 2;
    static const unsigned GROUPMASK_4 = 1 << 3;
    static const unsigned GROUPMASK_5 = 1 << 4;
    static const unsigned GROUPMASK_6 = 1 << 5;
    static const unsigned GROUPMASK_7 = 1 << 7;
    static const unsigned GROUPMASK_8 = 1 << 8;
    // Can have up to 8 more
    static const unsigned GROUPMASK_ALL = 65536;

    Actor trojan1;
    trojan1.AddFriendFoe(GROUPMASK_1, GROUPMASK_2 | GROUPMASK_3);
    Actor trojan2;
    trojan2.AddFriendFoe(GROUPMASK_1, GROUPMASK_2 | GROUPMASK_3);

    Actor pacifist;
    pacifist.AddFriendFoe(0, 0);

    Actor roman;
    roman.AddFriendFoe(GROUPMASK_2, GROUPMASK_1 | GROUPMASK_3);

    Actor player;
    player.AddFriendFoe(GROUPMASK_3, GROUPMASK_1 | GROUPMASK_2);
}

 

Previous Entry Projectiles
Next Entry Event system
0 likes 0 comments

Comments

Scienthsine
Quote

One could have the idea to maintain a list of friend and foe groups, but well, not really, because that's probably the most inefficient way to do this.

In terms of? There are tradeoffs to different methods, that might be worth mentioning/exploring. The list method may allow for a generic implementation of "relationships" between groups. Have you actually benchmarked/measured any of it? Even if yours is faster, don't under-estimate the value of using collections, in any language that has expressive methods/functions for them. Coding efficiency must also be balanced and taken into consideration.

Also, as you listed, your system has the potentially invalid/nonsensical state where two groups could be both friend and foe. In general, the possibility for invalid state should be avoided.

August 07, 2019 06:03 PM
trill41
59 minutes ago, Scienthsine said:

In terms of? There are tradeoffs to different methods, that might be worth mentioning/exploring. The list method may allow for a generic implementation of "relationships" between groups. Have you actually benchmarked/measured any of it?

You are right, the wording was a bit bad. I can imagine situations where a list maybe the way to go. I did not benchmark any of those, but I imagine some bit operations should be faster than looking up an item in a list (if lookup is not O(1)). Another point was, I'm a "lazy typer", and this are just some lines of code. And I didn't want to mess up the Actor class to much, since it's already complicated.

59 minutes ago, Scienthsine said:

Also, as you listed, your system has the potentially invalid/nonsensical state where two groups could be both friend and foe. In general, the possibility for invalid state should be avoided.

I wouldn't say invalid, nonsense, yes, but that could also lead to funny results ?. But even if you have 2 lists for friends and foes, it has the same problem. Groups can be in both of them, if you don't take care. However, I agree that my method may be more error prone.

I was looking for solutions how other games solved this problem. I think this must be a common problem in games, but I didn't find much (maybe I didn't look hard enough). So I thought I throw it in and see what others think.

Thank you for the comment.

August 07, 2019 07:02 PM
ThorMalleuson
1 hour ago, Scienthsine said:

Also, as you listed, your system has the potentially invalid/nonsensical state where two groups could be both friend and foe. In general, the possibility for invalid state should be avoided.

Or better, an uneasy alliance between two groups.

August 07, 2019 07:49 PM
Scienthsine
1 hour ago, trill41 said:

I wouldn't say invalid, nonsense, yes, but that could also lead to funny results ?. But even if you have 2 lists for friends and foes, it has the same problem. Groups can be in both of them, if you don't take care. However, I agree that my method may be more error prone.

True, true, and probably not.

Looks good to me. :) 

The only real downside I see is that it's less scalable than if you used a more naive, possibly less performant approach. Changing the underlying code to increase the number of groups will be nasty, especially if you exceed the double-word size of your platform. If the game design doesn't require/allow/warrant more though, then it's irrelevant.

Historically, I imagine older games used bitmasks quite a bit for this sort of thing. Nowdays, I would imagine most engines leverage collection/set library or language features.

There certainly _are_ a few instances where bitmasks may be much, much better. For instance, if you draw enemies as red, and friendlies as their team color, then sending this bit mask to the shader seems much easier.

Anyway, looks good to me. Maybe replace those magic number with some properly defined constants.  

August 07, 2019 09:13 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement

Latest Entries

Advertisement