Type Object

Intent: Allow the flexible creation of new “classes” by creating a single class, each instance of which represents a different type of object.

Acron Mapping

Pattern role

Acron equivalent

Type/Breed object

Python dataclass or dict loaded from JSON / .prc config

Instance

Actor or NodePath + Python wrapper that holds a reference to the type object

Type registry

Module-level dict keyed by type name, loaded at startup

How to Implement

  1. Define a BreedData dataclass with all per-type fields (max health, speed, model path, animations, sounds).

  2. Load breed definitions from a JSON file (or Config.prc overrides) at game startup into a BREEDS dict.

  3. Each game-entity instance stores a reference to its breed: self.breed = BREEDS['dragon'].

  4. Instance-specific state (current health, position) lives on the instance; shared immutable data lives on the breed.

  5. Load the shared model once per breed via ModelPool — different instances share geometry automatically.

Code Sketch (Python)

import json
from dataclasses import dataclass
from typing import Dict

@dataclass(frozen=True)
class MonsterBreed:
    name: str
    max_health: int
    speed: float
    model_path: str
    attack_anim: str
    attack_damage: int

# Load breeds at startup
def load_breeds(path='data/breeds.json') -> Dict[str, MonsterBreed]:
    with open(path) as f:
        raw = json.load(f)
    return {entry['name']: MonsterBreed(**entry) for entry in raw}

BREEDS = load_breeds()


class Monster:
    def __init__(self, breed_name: str, pos):
        self.breed = BREEDS[breed_name]
        self.health = self.breed.max_health
        # ModelPool caches the bam — all 'dragon' instances share geometry
        self.actor = Actor(self.breed.model_path)
        self.actor.reparentTo(render)
        self.actor.setPos(pos)

    def attack(self, target):
        self.actor.play(self.breed.attack_anim)
        target.health -= self.breed.attack_damage

    def destroy(self):
        self.actor.cleanup()
        self.actor.removeNode()

Example data/breeds.json:

[
  {"name": "dragon", "max_health": 200, "speed": 3.0,
   "model_path": "assets/dragon.bam", "attack_anim": "bite", "attack_damage": 40},
  {"name": "goblin", "max_health": 30, "speed": 6.0,
   "model_path": "assets/goblin.bam", "attack_anim": "slash", "attack_damage": 8}
]

Pitfalls

  • Mutable breed data: mark the dataclass frozen=True or use a plain dict read in read-only fashion. If instances can modify breed fields they’ll corrupt all other instances of that type.

  • Breed inheritance: breeds sometimes need a parent breed (e.g. fire_dragon extends dragon). Implement manually by merging dicts at load time — avoid Python class inheritance for data objects.

  • Animation per breed: Actor requires explicit animation dict at construction. Store animation file paths in the breed and pass them: Actor(breed.model_path, {'attack': breed.attack_anim_path}).