Composing Game Metrics

So I'm running into a bit of a roadblock with the way I'm currently keeping track of game metrics. By "metrics", I mean little bits of information that I can compose together to get useful information about my game. My primary application for this being so that I can detect certain events in the game and disperse achievements.

The way my current system works, I have metrics represented as a long value, where each bit represents a certain metric... for example, there's a flag for enemies, a flag for the player, a flag for a kill, a flag for each weapon, a flag for when a wave starts, etc, etc. I can then compose these into a single value to tell me things like "an enemy of this type was just killed by this weapon". My achievements controller looks at each metric posted in the last update and I have state machines that transition to the next state based on what metrics are posted.

The problem with this method is... I'm quickly running out of bits to use for my long value. I have plenty more weapons I want to add, so this hard limitation on the number of bits I can use as flags means it's probably time to find a better way to keep track of these.

Does anyone have a recommendation? Preferably one that would require the least amount of effort to refactor my current metrics and achievement systems to integrate?

EDIT: Is there a better term than "metric" to use? I haven't been able to find articles on this topic of game engine design because whenever I use the word "metric", all I find are articles about revenue-related analytical metrics.

You are using Java, so you can and should use the power of object-oriented programming. Represent each type of metric event with a different class, where each class has exactly the fields it needs. When all these classes implement the same interface (or extend the same base class) MetricEvent you can easily store them in the same data structures and process them in the same way.

For example, the class for a KillEvent could look like this:

public class KillEvent implements MetricEvent {
public EnemyType enemy
public WeaponType weapon

// constructor
public KillEvent(EnemyType enemy, WeaponType weapon) {
this.enemy = enemy;
this.weapon = weapon;
}

// you might also want to override equals so any counter-implementations can
// check if two MetricEvents are of the same type and have the same parameters
@Override public boolean equals(Object others) {
if (other.getClass() != getClass()) return false;
KillEvent otherKillEvent = (KillEvent) others;
return (otherKillEvent.enemy == enemy && otherKillEvent.weapon == weapon);
}
// if you override equals you must also override hashCode
@Override public int hashCode(Object others) {
// this implementation assumes EnemyType and WeaponType can be converted to 16 bit integers.
// if this is not the case, you might have to find a different way to calculate a hash code
return enemy<<16 + weapon;
}
}

• I'll have to come up with a list of all the possible events that can be created to see if this is a good solution or not. The "KillEvent" idea can encompass quite a bit, but I haven't even decided on all the different achievements I'll have. – Darin Beaudreau Feb 12 '19 at 16:31
• @DarinBeaudreau That's great, because due to the flexibility of object-oriented designs you can easily add more kinds of events later and process them all with the same manager-class. – Philipp Feb 12 '19 at 16:34
• @DarinBeaudreau I mean, this is definitely better than flipping bits on a Long. With the long, you can only have 64 flags (since it's 64 bits), shared between "weapon types", "enemy types", "player", and "event types" (probably among others). With subclasses of MetricEvents, you break out event types into their own chunks - and can add new types of MetricEvents at any time without worrying about conflicting with existing things. And it's way easier to work with than a collection of bits. – Delioth Feb 13 '19 at 18:30
• Yeah, I just liked the idea of bit flags because it seemed elegant and efficient. I guess I'll go with this, as it's probably the best solution. – Darin Beaudreau Feb 13 '19 at 21:28