Skip to main content

Event-Based Tracking

achievements-engine is built on an event-driven architecture. This guide explains how to leverage the event system for tracking achievements and reacting to state changes.

There are two main ways to use events:

  1. Listening to built-in events: React to events that the engine emits, such as achievement:unlocked.
  2. Using eventMapping: Emit your own custom events to update metrics, decoupling your application logic from the achievement system.

Listening to Built-in Events

The engine emits four core events:

  • achievement:unlocked: Fired when an achievement is unlocked.
  • metric:updated: Fired when a metric value changes.
  • state:changed: Fired after any state change (metrics or achievements).
  • error: Fired when an error occurs.

Subscribing to Events

You can use engine.on() to subscribe to these events.

import { AchievementEngine } from 'achievements-engine';

const engine = new AchievementEngine({
achievements,
storage: 'localStorage'
});

// Listen for achievement unlocks
engine.on('achievement:unlocked', (event) => {
console.log(`🎉 ${event.achievementTitle}`);
// e.g., show a notification
});

// Listen for metric updates
engine.on('metric:updated', (event) => {
console.log(`${event.metric}: ${event.oldValue}${event.newValue}`);
});

Unsubscribing from Events

It's important to unsubscribe from events to prevent memory leaks. The on method returns an unsubscribe function.

const unsubscribe = engine.on('achievement:unlocked', (event) => {
console.log('Achievement unlocked!');
});

// Later, to stop listening:
unsubscribe();

One-Time Listeners

Use engine.once() to listen for an event just once.

engine.once('achievement:unlocked', (event) => {
console.log('First achievement unlocked!', event.achievementTitle);
});

Event Types

Each event has a specific payload with detailed information. For example, the AchievementUnlockedEvent:

interface AchievementUnlockedEvent {
achievementId: string;
achievementTitle: string;
achievementDescription: string;
achievementIconKey?: string;
timestamp: number;
}

Using eventMapping for Metric Updates

The eventMapping feature allows you to automatically update metrics by emitting your own custom events. This is a powerful way to decouple your application logic from the achievement system.

Direct String Mapping

Map an event name directly to a metric name.

Configuration:

const engine = new AchievementEngine({
achievements,
storage: 'localStorage',
eventMapping: {
'enemy:defeated': 'enemiesDefeated',
'item:collected': 'itemsCollected'
}
});

Emitting Events: Instead of engine.update(), you use engine.emit().

// This will update the 'enemiesDefeated' metric
engine.emit('enemy:defeated', 10); // The new value is 10

// This will update the 'itemsCollected' metric
let items = 0;
items++;
engine.emit('item:collected', items);

MetricUpdater Function

For more complex logic, use a function to transform event data into metric updates.

Configuration:

import type { MetricUpdater } from 'achievements-engine';

const levelUpUpdater: MetricUpdater = (eventData, currentMetrics) => {
const currentLevel = currentMetrics.level || 0;
return {
level: currentLevel + 1,
experience: 0 // Reset experience on level up
};
};

const engine = new AchievementEngine({
achievements,
storage: 'localStorage',
eventMapping: {
'player:levelup': levelUpUpdater,
'player:gain_exp': (eventData, currentMetrics) => {
const currentExp = currentMetrics.experience || 0;
return { experience: currentExp + eventData.amount };
}
}
});

Emitting Events:

engine.emit('player:levelup');
engine.emit('player:gain_exp', { amount: 50 });

Complete Example

Here's a full example that uses both listening to events and eventMapping.

// achievements.ts
export const gameAchievements = {
enemiesDefeated: {
10: { title: 'Novice Slayer', description: 'Defeat 10 enemies' },
},
level: {
5: { title: 'Level 5', description: 'Reach level 5' },
}
};

// engine.ts
import { AchievementEngine } from 'achievements-engine';
import { gameAchievements } from './achievements';

const engine = new AchievementEngine({
achievements: gameAchievements,
storage: 'localStorage',
eventMapping: {
'enemy:defeated': 'enemiesDefeated',
'player:levelup': (eventData, currentMetrics) => {
const currentLevel = currentMetrics.level || 0;
return { level: currentLevel + 1 };
}
}
});

// Listen for unlocks
engine.on('achievement:unlocked', (event) => {
console.log(`🎉 Achievement Unlocked: ${event.achievementTitle}`);
});

export default engine;

// game.ts
import engine from './engine';

class Game {
private enemiesDefeated = 0;

defeatEnemy() {
this.enemiesDefeated++;
engine.emit('enemy:defeated', this.enemiesDefeated);
}

levelUp() {
engine.emit('player:levelup');
}
}

// Usage
const game = new Game();
game.defeatEnemy(); // emits 'enemy:defeated', which updates the metric
game.levelUp(); // emits 'player:levelup', which updates the metric