Overview
This guide covers integrating SubakoAchv into HTML5 games built with Canvas, WebGL, or game frameworks like Phaser, PixiJS, or custom engines. We'll cover game loop integration, real-time notifications, and performance considerations.
Game Manifest Example
Here's a complete manifest for a typical web game with various achievement types:
{
"gameId": "space-shooter",
"name": "Space Shooter",
"version": "1.0.0",
"achievements": [
{
"id": "first_kill",
"name": "First Blood",
"description": "Destroy your first enemy",
"points": 5,
"icon": "assets/achievements/first_kill.svg",
"trigger": { "type": "event-once", "event": "enemy_destroyed" }
},
{
"id": "enemy_slayer",
"name": "Enemy Slayer",
"description": "Destroy 50 enemies",
"points": 20,
"icon": "assets/achievements/slayer.svg",
"trigger": { "type": "event-count", "event": "enemy_destroyed", "value": 50 }
},
{
"id": "score_master",
"name": "Score Master",
"description": "Reach a score of 10,000",
"points": 30,
"icon": "assets/achievements/score.svg",
"trigger": { "type": "stat-gte", "stat": "score", "value": 10000 }
},
{
"id": "survivor",
"name": "Survivor",
"description": "Complete a level without taking damage",
"points": 25,
"icon": "assets/achievements/survivor.svg",
"trigger": { "type": "manual" }
},
{
"id": "completionist",
"name": "Completionist",
"description": "Unlock all other achievements",
"points": 50,
"icon": "assets/achievements/completionist.svg",
"hidden": true,
"trigger": { "type": "manual" }
}
]
}
Basic Game Integration
Initialize the SDK when your game loads:
import { createSubako, loadManifest } from './vendor/subako-achv/index.js';
class Game {
constructor() {
this.canvas = document.getElementById('gameCanvas');
this.ctx = this.canvas.getContext('2d');
this.sdk = null;
this.score = 0;
this.enemiesDestroyed = 0;
}
async init() {
// Load the achievement manifest
const manifest = await loadManifest('./manifest.json');
// Initialize SDK with player info
this.sdk = createSubako({
gameId: manifest.gameId,
manifest: manifest,
playerId: this.getPlayerId(),
displayName: this.getPlayerName()
});
// Set up achievement notifications
this.setupNotifications();
// Start the game
this.start();
}
getPlayerId() {
// Use localStorage or your auth system
return localStorage.getItem('playerId') || 'guest-' + Date.now();
}
getPlayerName() {
return localStorage.getItem('playerName') || 'Player';
}
setupNotifications() {
// Show toast when achievement unlocks
this.sdk.on('unlock', ({ achievement }) => {
this.showAchievementToast(achievement);
});
// Update progress bars
this.sdk.on('progress', ({ achievement, ratio }) => {
this.updateProgressUI(achievement.id, ratio);
});
}
showAchievementToast(achievement) {
// Create a toast notification
const toast = document.createElement('div');
toast.className = 'achievement-toast';
toast.innerHTML = `
<img src="${achievement.icon}" alt="${achievement.name}" />
<div>
<div class="title">Achievement Unlocked!</div>
<div class="name">${achievement.name}</div>
<div class="desc">${achievement.description}</div>
</div>
`;
document.body.appendChild(toast);
// Remove after 3 seconds
setTimeout(() => toast.remove(), 3000);
}
updateProgressUI(achievementId, ratio) {
const progressBar = document.getElementById(`progress-${achievementId}`);
if (progressBar) {
progressBar.style.width = `${ratio * 100}%`;
}
}
}
Game Loop Integration
Emit events and update stats during gameplay:
class Game {
// ... previous code ...
start() {
this.gameLoop();
}
gameLoop() {
this.update();
this.render();
requestAnimationFrame(() => this.gameLoop());
}
update() {
// Update game state
this.checkCollisions();
this.updateEntities();
}
onEnemyDestroyed(enemy) {
// Update game state
this.enemiesDestroyed++;
this.score += enemy.points;
// Emit achievement events
this.sdk.emit('enemy_destroyed');
this.sdk.updateStat('score', this.score);
// Check for special events
if (this.enemiesDestroyed === 10) {
this.sdk.emit('kill_streak_10');
}
}
onPlayerHit(damage) {
this.health -= damage;
this.sdk.updateStat('damage_taken', (prev) => prev + damage);
if (this.health <= 0) {
this.gameOver();
}
}
onLevelComplete(levelNum) {
this.sdk.emit('level_completed');
this.sdk.updateStat('level', levelNum);
// Check for no-damage completion
if (this.damageTakenThisLevel === 0) {
this.sdk.unlock('survivor');
}
}
onPowerUpCollected(type) {
this.sdk.emit('powerup_collected');
this.sdk.updateStat('powerups_collected', (prev) => prev + 1);
}
gameOver() {
this.sdk.emit('game_over');
this.sdk.updateStat('games_played', (prev) => prev + 1);
this.sdk.updateStat('high_score', (prev) => Math.max(prev, this.score));
}
}
Canvas Game Example
Here's a minimal Canvas game with achievement tracking (similar to the demo):
class CanvasGame {
constructor() {
this.canvas = document.getElementById('game');
this.ctx = this.canvas.getContext('2d');
this.player = { x: 100, y: 100, size: 20 };
this.enemies = [];
this.coins = 0;
}
async init() {
const manifest = await loadManifest('./game-manifest.json');
this.sdk = createSubako({
gameId: manifest.gameId,
manifest: manifest
});
this.sdk.on('unlock', ({ achievement }) => {
console.log('Unlocked:', achievement.name);
this.showNotification(achievement);
});
this.setupInput();
this.spawnLoop();
this.gameLoop();
}
setupInput() {
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowUp') this.player.y -= 5;
if (e.key === 'ArrowDown') this.player.y += 5;
if (e.key === 'ArrowLeft') this.player.x -= 5;
if (e.key === 'ArrowRight') this.player.x += 5;
});
this.canvas.addEventListener('click', (e) => {
this.shoot(e.offsetX, e.offsetY);
});
}
shoot(x, y) {
// ... bullet logic ...
// Check if enemy hit
const hitEnemy = this.checkHit(x, y);
if (hitEnemy) {
this.onEnemyKilled(hitEnemy);
}
}
onEnemyKilled(enemy) {
this.sdk.emit('enemy_killed');
this.coins += enemy.coinValue;
this.sdk.updateStat('coins', this.coins);
}
onCoinCollected(value) {
this.coins += value;
this.sdk.updateStat('coins', this.coins);
this.sdk.emit('coin_collected');
}
showNotification(achievement) {
// Display achievement unlock UI
const notification = document.createElement('div');
notification.className = 'achievement-notification';
notification.textContent = `Achievement Unlocked: ${achievement.name}`;
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 3000);
}
}
Performance Considerations
- Batch events: If you're emitting many events per frame, consider batching them or using stats instead
- Avoid frequent stat updates: Instead of updating stats every frame, update them at key moments (level complete, enemy killed, etc.)
- Cache achievement list: Call
sdk.list()once and update your UI incrementally via events - Defer notifications: Queue achievement notifications and show them during natural breaks (between levels, after death)
Optimized Event Emission
class Game {
constructor() {
this.pendingEvents = [];
this.pendingStats = {};
}
// Instead of emitting immediately
onEnemyKilled() {
this.pendingEvents.push('enemy_killed');
this.pendingStats.enemies_killed = (this.pendingStats.enemies_killed || 0) + 1;
}
// Flush events at the end of each frame
flushAchievements() {
for (const event of this.pendingEvents) {
this.sdk.emit(event);
}
for (const [stat, value] of Object.entries(this.pendingStats)) {
this.sdk.updateStat(stat, value);
}
this.pendingEvents = [];
this.pendingStats = {};
}
gameLoop() {
this.update();
this.render();
this.flushAchievements(); // Flush at end of frame
requestAnimationFrame(() => this.gameLoop());
}
}
UI Integration
Create an achievement panel that shows progress:
class AchievementUI {
constructor(sdk) {
this.sdk = sdk;
this.container = document.getElementById('achievement-panel');
this.render();
}
render() {
const achievements = this.sdk.list();
const points = this.sdk.totalPoints();
this.container.innerHTML = `
<div class="points-summary">
${points.earned} / ${points.possible} points
</div>
<div class="achievements-grid">
${achievements.map(a => this.renderAchievement(a)).join('')}
</div>
`;
}
renderAchievement({ achievement, unlocked, ratio, current, target }) {
return `
<div class="achievement ${unlocked ? 'unlocked' : 'locked'}">
<img src="${achievement.icon || 'locked.svg'}" alt="${achievement.name}" />
<div class="info">
<div class="name">${achievement.name}</div>
<div class="description">${achievement.description}</div>
<div class="progress-bar">
<div style="width: ${ratio * 100}%"></div>
</div>
<div class="progress-text">${current} / ${target}</div>
</div>
<div class="points">${achievement.points} pts</div>
</div>
`;
}
update() {
this.render();
}
}
// Usage
const ui = new AchievementUI(sdk);
sdk.on('unlock', () => ui.update());
sdk.on('progress', () => ui.update());
Save/Load Integration
Integrate achievement saves with your game's save system:
class GameSaveManager {
constructor(sdk) {
this.sdk = sdk;
}
saveGame() {
const gameState = {
level: this.level,
score: this.score,
playerPosition: this.player.position,
// ... other game state
};
const achievementSave = this.sdk.exportSave();
const saveData = {
game: gameState,
achievements: achievementSave,
timestamp: Date.now()
};
localStorage.setItem('gameSave', JSON.stringify(saveData));
}
loadGame() {
const raw = localStorage.getItem('gameSave');
if (!raw) return false;
const saveData = JSON.parse(raw);
// Restore game state
this.level = saveData.game.level;
this.score = saveData.game.score;
this.player.position = saveData.game.playerPosition;
// Restore achievements
this.sdk.importSave(saveData.achievements);
return true;
}
}
Framework-Specific Tips
Phaser
class GameScene extends Phaser.Scene {
async create() {
const manifest = await loadManifest('manifest.json');
this.sdk = createSubako({ gameId: manifest.gameId, manifest });
this.sdk.on('unlock', (data) => {
this.showAchievement(data.achievement);
});
}
onEnemyKilled() {
this.sdk.emit('enemy_killed');
}
}
PixiJS
const app = new PIXI.Application();
const manifest = await loadManifest('manifest.json');
const sdk = createSubako({ gameId: manifest.gameId, manifest });
// Use PixiJS events
app.stage.on('enemyKilled', () => {
sdk.emit('enemy_killed');
});
Next Steps
- Try the Snake demo to see a complete implementation
- Check the Web Apps guide for general SDK usage
- View the example manifest for reference
- Explore the dashboard to test your achievements