1
\$\begingroup\$

So I posted not too long ago looking for an alternative system to what I'm using for game metrics, meaning information I post each update when something specific happens (enemy damaged, killed, a wave starts, player taking damage, etc). I got suggestions to implement something more similar to "events" instead, where I could have a general interface to define events and create derivative classes to represent specific cases. This would be fine, but I'm having a hard time figuring out how to define achievements now in terms of these new events.

My current system, I have long values for each type of metric: player, enemy, kill, damage, damage types, weapons, and various other pieces of information. Each value is a single bit and I compose them together so I can later check specific bits to see if a certain metric was posted and apply it to my achievement progress tracker.

For example, let's say ENEMY = 1, DAMAGE = 2, PISTOL = 4. I compose these together to indicate that an enemy was damaged by the pistol this update and the resultant value is 7. I can later look at the individual bits for different cases. Say I have a general achievement for tracking number of kills regardless of enemy type, I could extract the bit relevant to KILL and ENEMY and see if one of those was posted, and if so, increment the milestone. However, I can also use this same metric to apply to an achievement that's tracking kill counts for specific enemies if I wanted to.

I also have more complicated achievements that use a rudimentary state machine with logic deciding what each state does with the metrics that are posted and determine the next state.

The reason this was problematic is because even though this system is great and works well, it means I only have 64 values I can use to compose together, and with the number of weapons and enemies I'm adding, I'm going to run out fairly quickly.

When I tried implementing "metric events" instead, I found that defining milestone achievements (and likely state machine achievements) was impossible because I could only take the metric events with all the information composing that class, and couldn't "compose" them together and then pick them apart. It also made it a lot more difficult to figure out how to save that information to a file for keeping track of achievements in save files.

Does anyone have a suggestion as to how I could approach this? If it helps, you can find my current implementation of my achievement system here:

Achievements System

You can also find the AchievementController class under the controllers package one level up. This controller defines the achievements themselves, if you want to take a look at how I define my achievements.

I really appreciate any insight people may have. Links to articles detailing how to properly implement an achievement and metrics system would be very helpful, or if anyone has their own examples of systems they've implemented, that would be awesome.

\$\endgroup\$
  • \$\begingroup\$ Can't you draw some inspiration from the results popping out from 'game achievements api' and 'game achievements unity' web searches? There are a couple of interesting results that might give you an idea or two I guess. \$\endgroup\$ – Aybe Feb 25 at 1:59
  • \$\begingroup\$ @Aybe Because I'm not using Unity, and most results are just plugins that do it for you. I'm writing the system from scratch. In Java, not that it matters. The best result I could find was a system that defined properties and had achievements subscribe to these properties, but a number of my current achievements wouldn't work with such a system. \$\endgroup\$ – Darin Beaudreau Feb 25 at 3:28
  • \$\begingroup\$ Of course it's Unity and C# but still, can't you grab some ideas from them by looking at the docs and the examples ? From what I've seen, they're abstract enough to handle any kind of achievement. \$\endgroup\$ – Aybe Feb 25 at 5:26
  • \$\begingroup\$ It might be helpful if you would provide a concrete example for a situation where you would want to award an achievement. \$\endgroup\$ – Philipp Feb 25 at 10:44
  • 1
    \$\begingroup\$ @DarinBeaudreau Are you sure you want to make this project in Java? Because you seem to be thinking more like a C programmer than a Java programmer. \$\endgroup\$ – Philipp Feb 25 at 13:46
1
\$\begingroup\$

When you have singular events which should generate an achievement as soon as they occur, like "player killed themselves with the EN cannon", then you might want to have a method for each Achievement which receives an event and then returns whether that event fulfills the condition for that achievement or not. Example:

 AchievementEMCannonSelfKill extends Achievement {
      // ...
      @Override public bool check(AchievementEvent event) {
          if (event instanceof KillEvent) {
              KillEvent killEvent = (KillEvent)event;
              return (killEvent.victim == player && killEvent.weapon = EN_CANNON );
           else {
               return false;
          }
      }
 }

Your class which processes all achievement events would have a list of not yet awarded Achievements and call the .check method on each of them to check if the event it just received fulfills that achievement.

Now what about those achievements which don't just check one event but events over a period of time, like the "Killed boss using only the nailgun"?

When you store all events over a period of time in a Collection (like an ArrayList), you can easily filter it to get only the events you need. A good way to check if something did or did not happen within a given time frame is the Java Stream API. For example, the method stream.anyMatch and stream.noneMatch can be used to find if there is any event in your event collection which can be cast to a WeaponDamageEvent where the conditions victim == finalBoss && inflictor == player && weapon != NAIL_GUN.

You could also implement achievements like this as a kind of state machine. The AchievementBossKilledOnlyWithNailGun could be switched to the "on" state when it checks the event which says that the boss game object is created, get switched to "off" when it receives an event which says that the player damaged the boss with the wrong weapon, and returns true if it receives the boss kill event while in the "on" state.

\$\endgroup\$
  • \$\begingroup\$ Why would I define a class for every achievement? That's incredible verbose and seems like a very bad practice to me. It would make migrating to data-driven achievement definitions impossible if I wanted to go that route (depends how many achievements I end up coming up with). \$\endgroup\$ – Darin Beaudreau Feb 25 at 18:20
  • 1
    \$\begingroup\$ @DarinBeaudreau I doubt you will be able to build a data-driven architecture for achievements. Achievements can have very obscure requirements, so they will usually require some kind of hardcoded logic to implement. But if you think that a class for every achievement is too much, you could also pass the condition method as a lambda function to each instance of Achievement. \$\endgroup\$ – Philipp Feb 25 at 23:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.