6 Game Mechanic State pattern
Andrei Andreev edited this page 2023-07-16 09:31:18 +02:00

Most of the game mechanics in AD follow consistent logic and structure. Based on this assumption, we created a framework for working with them in the form of GameMechanicState class which simplified and made cleaner game state access all over the code. This generalization also well with the Effects system explained here: Link

But, the game mechanic declaration doesn't consist only of state class - additionally, we need to bind GameDatabase configs, add declaration function, and add some additional functions/accessors that are tightly coupled with the game mechanic. Currently, there's no standard for such declarations, and the following pattern is expected to give the guidelines for a consistent game mechanic code.

Structure

  1. (Optional) GameDatabase declaration. For game mechanics that have configurable elements.
  2. State class that extends GameMechanicState. This is a staple element between player object (PO) and game logic that allows the separation of PO structure from the logic code.
  3. Accessor object created with the createAccessor function. It has index property which contains all the GameDB entries wrapped into State class instances. Apart from that, it is itself a callable function that accepts an index as an argument. Example: Achievement(101). This is the very method that should be used when accessing state objects in-game logic code.
  4. (Very Optional) Additional method or getter declaration for Accessor object. Very situational. Most likely, you want to place this functionality in the collection object (explained below). Example: NormalChallenge.current
  5. Collection object. Contains all sorts of functions for non-state-specific operations. Usually contains the all property that contains all state instances. Since createAccessor creates a sparse array, GameDB-based all should be created with a compact array extension, for instance: all: Achievement.index.compact()

Example

GameDB file

// 1. GameDB declaration
GameDatabase.challenges.normal = [
  {
    id: 1,
    legacyId: 1,
    isQuickResettable: false,
    description: "Reach Infinity for the first time.",
    reward: "First Dimension Autobuyer"
  },
  ...
};

File with game logic

// 2. Class that extends `GameMechanicState`
class NormalChallengeState extends GameMechanicState {
  constructor(config) {
    super(config);
    ...
  }
  ...
}

// 3. Accessor object
/**
 * @param {number} id
 * @return {NormalChallengeState}
 */
const NormalChallenge = NormalChallengeState.createAccessor(GameDatabase.challenges.normal);

// 4. Additional method or getter declaration for Accessor object
Object.defineProperty(NormalChallenge, "current", {
  get: () => (player.challenge.normal.current > 0
    ? NormalChallenge(player.challenge.normal.current)
    : undefined),
});

// 5. Collection object
const NormalChallenges = {
  /** @type {NormalChallengeState[]} */
  all: NormalChallenge.index.compact(),
  completeAll() {
    for (const challenge of NormalChallenges.all) challenge.complete();
  },
  ...
};