StupidRPG: Everything is an Entity

The most basic element in SRPG is an Entity, which you can think of as a bag of stuff (where ‘stuff’ can mean a name, a list of things, references to other Entities, or functions). Items, locations, and NPCs are all Entities (plus more stuff).

The basic entity definition (or prototype) looks like this:

ECS: Entity Prototype
1
2
3
4
5
6
7
8
9
10
var Entity = function(){
this.key = ''; // Identifier
this.parent = null; // Parent entity
this.children = []; // Child entities
this.components = []; // Component list, for convenience / searching
this.onComponentAdd = []; // Add component callback
this.tags = []; // Tag list; generally the same as the component list
this.persist = ['parent']; // Raw attributes to persist
this.persistActive = true; // Save objects by default
};

Nothing super exciting here. The base entity mostly defines ‘housekeeping’ items: an identifier, references to parent and child entities, etc. I want to focus on Components right now, as that’s the most important part of ‘everything is an entity.’

Functionality and data associated with game objects are defined by Components. You can think of a Component as a ‘type of thing’. Examples include Place (a location), Region (a group of Places), Living (an entity that is alive), Container (an object that holds other objects), and so forth. Most components correlate to some kind of real-world designation.

The most basic Component is called Thing. When I said ‘everything is an entity,’ I could have instead said that ‘everything is a thing.’ A Thing, of course, is also an Entity, but as a Thing it has a variety of in-world properties.

Things have a bunch of properties included by default, acting as the building blocks of item interaction in the game world:

  • name (e.g. ‘blue flowers’)
  • descriptions (a list of descriptions for different contexts)
  • place (the current location of the thing)
  • spawn (the starting location of the thing)

There are a bunch of additional elements that make up a thing (mostly callback functions to handle basic behaviors: picking up and dropping the thing, adding more components to the thing, which data to save for the thing, etc). Certain basic items in the game don’t need any additional components, but most will have their interactivity and behaviors further expanded.

For example, the Living component adds:

  • hp (hit points)
  • onTick (a callback function, run on each game ‘tick’, allowing the object to take actions)
  • onHit (callback, run any time the object is attacked)
  • onDeath (callback, run when the object’s HP is reduced to 0)

Example: Containers

The Container component is a bit more advanced, as it is actually dependent on the Holder component. Components can specify a dependency list (meaning other component(s) the base object should also have). The Holder component specifies functionality used by both the Container (an object in which other objects can be placed) and Supporter (an object on which other objects can be placed) components.

The Holder component specifies:

  • capacity (how many objects can be held)
  • isTransparent (whether the object can be seen through)
  • canTakeFrom and canPutInto (whether things can currently be placed in/on the holder)
  • inventory (a list of objects being held)

The Container component adds:

  • isOpen (whether the container is open)
  • onOpen and onClose (callbacks, run when the container is opened/closed)
  • onList (callback, overrides a basic Thing callback and controls how the object’s description is printed)

The onList callback is particularly notable, as it changes based on the isTransparent property (from Holder) and the isOpen property (from Container). If the container is open or transparent, its contents are listed when the object is described. Otherwise, only the object itself is described.

The Supporter component also has an onList callback, but it’s much simpler as it doesn’t need to pay attention to transparency or openness.

Component Interactions

In general, there are no hard-coded restrictions on which components (or how many) can be applied to a particular entity. This has advantages (e.g. you can do interesting things like allowing a location to be alive and move around the world) and disadvantages (e.g. whatever happens when you add two components, each of which overrides the ‘onList’ behavior). Interactions can become complex or hard to predict. I was aware of this while building the system, and felt that the disadvantages could usually be worked around, while the advantages were particularly exciting to me.