I am creating an achievement system for my game engine. I represent an achievement as a class takes a predicate function to determine if requirement for unlock has been met.
class Achievement {
name: string
description: string
requirement: (event: GameEvent): bool
constructor(name: string, description: string, requirement: (event: GameEvent): bool) {
this.name = name
this.description = description
this.requirement = requirement
}
}
An example achievement might look like
const firstItem = new Achievement(
"First Item",
"You picked up your first item!",
(event: GameEvent) => { return event.label == "itemCollected" }
)
I'm wondeing what the best approaches are for storing and loading achievements. I could encode them in a JSON document
// assets/achievements.json
{
"firstItem": {
"name": "First Item",
"description": "You picked up your first item!",
"requirement": "event.label == 'itemCollected'"
}
}
But it feels awkward to store the requirement code as a string, having to read it and pass it to eval()
class AchievementLoad {
path: string
constructor(path: string) {
this.path = path
}
recursiveLoad(path: string): Achievement[] {
const json = JSON.parse(path)
const achievements = []
for (const key in json) {
const achievement = json[key]
const requirement = eval(achievement.requirement)
achievements.push(new Achievement(achievement.name, achievement.description, requirement))
}
return
}
}
This introduces coupling between my source code and the asset save format, which seems to violate the Single-Responsibility Principle as changing the way one behaves may require me to change the other, and is further worsened by there being no type safety mechanism to alert me of incompatible changes. The ergonomics and safety suffer.
Alternatively I've considered storing the achievements in the source code itself, either as objects to be manually instantiated and exported
// src/achievements.ts
const firstItem = new Achievement(
"First Item",
"You picked up your first item!",
(event: GameEvent) => { return event.label == "itemCollected" }
)
const firstQuest = new Achievement(
"First Quest",
"You completed your first quest!",
(event: GameEvent) => { return event.label == "questCompleted" }
)
export { firstItem, firstQuest }
Or as classes themselves extending Achievement
// src/achievements/FirstItem.ts
class FirstItem extends Achievement {
constructor() {
super(
"First Item",
"You picked up your first item!",
(event: GameEvent) => return event.label == "itemCollected"
)
}
}
export { FirstItem }
In either case, I'm not sure how professional this would be, especially given that I am going the JSON approach for other types of assets in my game.
What are the standard approaches and best practices here? Are there worthwhile alternatives I haven't considered or details I've overlooked?