Skip to content
This repository has been archived by the owner on Jan 3, 2023. It is now read-only.

Latest commit

 

History

History
133 lines (78 loc) · 9.18 KB

0032.md

File metadata and controls

133 lines (78 loc) · 9.18 KB
  DSP: 0030
  Title: Entities Components and Systems
  Author: Nicolas Earnshaw 
  Status: Accepted
  Type: Informational
  Created: 2018-03-07

Entities Components and Systems

Introduction

Three dimensional scenes in Decentraland are based on an Entity-Component-System (ECS) architecture, where everything in a scene is an entity, and each entity can include components that determine its characteristics.

An ECS is a well known and proven game development pattern for writing code that’s high-performance by default. We want to help developers create better and more robust content for Decentraland, eliminating unnecessary complications and making the learning curve as smooth as possible.

Abstract

This document describes how the content in Decentraland scenes is structured and why we chose this architecture.

Entities and Components

Entities are the basic unit for building everything in Decentraland scenes. All visible and invisible 3D objects and sources of audio in your scene will each be an entity. An entity is nothing more than a container that holds components. The entity itself has no properties or methods of its own, it simply groups several components together.

Components define the traits of an entity. For example, a transform component stores the entity's coordinates, rotation and scale. A BoxShape component gives the entity a cube shape when rendered in the scene, a Material component gives the entity a color or texture. You could also create a custom health component to store an entity's remaining health value, and add it to entities that represent non-player enemies in a game.

Components are meant to store data about their parent entity. They only store this data, they shouldn't modify it themselves. All changes to the values in the components are carried out by Systems. Systems are completely decoupled from the components and entities themselves. Entities and components are agnostic to what systems are acting upon them.

Components like Transform, Material or any of the shape components are closely tied in with the rendering of the scene. If the values in these components change, that alone is enough to change how the scene is rendered in the next frame.

Entities can be nested inside other entities to form a tree structure. If you're familiar with web development, think of entities as the equivalent of Elements in a DOM tree, and of components as attributes of those elements.

Scene state

In previous versions of the Decentraland SDK, the scene state was stored in an object that was separate from the entities themselves. As of version 5.0, the scene state is directly embodied by the components that are used by the entities in the scene. When any component in the scene changes value, this means that the scene state is also changing.

The game loop

The game loop is the backbone of a Decentraland scene's code. It cycles through part of the code at a regular interval and does the following:

  • Listen for user input
  • Update the scene
  • Re-render the scene

In most traditional software programs, all events are triggered directly by user actions. Nothing in the program's state will change until the user clicks on a button, opens a menu, etc.

But interactive environments and games are different from that. Not all changes to the scene are necessarily caused by a user's actions. Your scene could have animated objects that move on their own or even non-player characters that have their own AI. Some user actions might also take multiple frames to be completed, for example if the opening of a door needs to take a whole second, the door's rotation must be incrementally updated about 30 times as it moves.

We call each iteration over the loop a frame. Decentraland scenes are rendered at 30 frames per second whenever possible. If a frame takes more time than that to be rendered, then less frames will be processed.

In each frame, the scene is updated; then the scene is re-rendered, based on the updated values of each component in the entities of the scene.

Systems

Entities and components are places to store information about the objects in a scene. Systems hold functions that change the information that's stored in components.

Systems are what make a static scene dynamic. They're able to execute functions periodically on every frame of the scene's game loop, changing what will be rendered over time or in response to user interaction.

Each System has an update() method that's executed on every frame of the game loop, following the update pattern.

You can have multiple systems in a scene to decouple different behaviors, making code cleaner and easier to scale. For example, one system might handle physics, another might make an entity move back and forth continuously, another could handle the AI of characters.

Multiple systems can act on a single entity, for example a non-player character might move on its own based on its AI but also be affected by gravity when trying to walk from off a cliff.

Component groups

Component groups keep track of all entities in the scene that have certain components in them. Once a component group is created, it automatically keeps its list up to date with each new entity or component that is added or removed.

If you attempt to update all the entities in the scene on every frame, that could have a significant cost in performance. By referring only to the entities in a component group, you ensure you're only dealing with those that are relevant.

Component groups can be referenced by the functions in a System. For example, your scene can have a PhysicsSystem that calculates the effect of gravity over the entities of your scene. Some entities in your scene, such as trees, are fixed, so it would make sense to avoid wasting energy in calculating the effects of gravity on these. You can then define a component group that keeps track of entities that aren't fixed and then have PhysicsSystem only deal with the entities in this group.

Putting it all together

The engine is what sits in between entities, components and component groups on one hand and systems on the other. It calls system's functions, updates groups when entities are added, etc.

All of the values stored in the components in the scene represent the scene's state at that point in time. With every frame of the game loop, the engine runs the update() function of each of the systems to update the values stored in the components.

After all the systems run, the components on each entity will have new values. When the engine renders the scene, it will use these new updated values and users will see the entities change to match their new states.

// Create a group to track all entities with a Transform component
const myGroup = engine.getComponentGroup(Transform)

// Define a System
export class RotatorSystem implements ISystem {
  // The update function runs on every frame of the game loop
  update() {
    // The function iterates over all the entities in myGroup
    for (let entity of myGroup.entities) {
      const transform = entity.getComponent(Transform)
      transform.rotate(Vector3.Left(), 0.1)
    }
  }
}

// Add the system to the engine
engine.addSystem(new RotatorSystem())

// Create an entity
const cube = new Entity()

// Give the entity a transform component
cube.addComponent(new Transform({
    position: new Vector3(5, 1, 5)
  }))

// Give the entity a box shape
cube.addComponent(new BoxShape())

// Add the entity to the engine
engine.addEntity(cube)

In the example above, a cube entity and a RotatorSystem system are added to the engine. The cube entity has a Transform, and a BoxShape component. In every frame of the game loop, the update() function of RotationSystem is called, and it changes the rotation values in the Transform component of the cube entity.

Note that most of the code above is executed just once when loading the scene. The exception is the update() method of the system, which is called on every frame of the game loop.

Rationale for using Entities and Components

In previous versions of the Decentraland SDK, scenes were built following a style that was inspired in React. This style of coding proved to be very difficult to master for game developers, as it was based on a paradigm that was very different from what they're used to working with. Web developers were able to write simple scenes using this, but they needed to reinvent the wheel when it came to applying normal game patterns to their scenes.

By building scene content based on the widespread concepts of entities and components, it's easy to apply game patterns that are common practices in other platforms. It also helps make the learning curve smoother for those with experience developing games in other platforms.

Entities and components are also a great foundation on which to build graphical development tools, as it's easier to map each entity and component with visual elements on screen. This will also improve the experience of creating content for everyone.