Entity System Proposal
From Peragro Tempus Wiki
Contents |
Motivation
The entity system represents the framework for all of the gameplay code in a game. Since development of gameplay occurs iteratively and requirements change, the entity system should be designed such that changes can be made and tested easily. In a MMORPG, there are many different types and instances of entities in the game world. In addition to the issue of scale, entities must be serialized across the network and persisted to the server's database. In synchronizing entities, server authority must not be compromised. World Designers should be able to define a hierarchy of reusable entity templates, and create and modify instances of entities in the game world over the network in real-time.
Requirements
- Editing in real-time over network
- Automatic serialization to network and database
- Lightweight multi-threaded behaviors
- Scripting language for gameplay code
- Client/server separation
- Server authority
- Streaming of entities to client
Overview
Components
Components are all defined in C++ and are the basic bulding blocks for entities, such as "mesh", "cdobject", or "item". Each component has a number of functions and properties. For example, the "mesh" component would have a property for which mesh file to use. Components can depend on each other. For example, both "mesh" and "cdobject" can depend on a common "physicalposition" component, which they both use.
Behaviors
Behaviors are event handlers written in Python. Behaviors consist of a set of methods that handle the events that they wish to subscribe to.
Entity Templates
By grouping a number of components, setting properties, defining properties, and attaching behaviors, you create an entity template. Examples of templates are: "monster", and "weapon". Templates can inherit from other templates. So you can have a "skeletonmonster" that inherits from "monster" and defines or sets some additional properties or behaviors specific to the skeleton monster.
Entities
Entities are "complete" templates. That is, entities have all properties and behaviors defined. They are ready for instantiation.
Entity Instances
These are instantiations of entities. Entity instances can only set properties, they cannot define new properties or behaviors. They are the actual things loaded into memory that you might see in the game world.
Networking and Client/Server Interaction
Synchronization of Properties
Properties have flags that indicate the how often they are synchronized and at what priority, whether they are server-authoritative or client-authoritative, and if they are private (not to be transmitted to other players).
To elaborate on the client/server-authoritative flag, it is mainly a security thing. The server won't apply property updates from a client to a server-authoritative property. Things like walking where the client is performing an action have to be handled through action system, not through property synchronization.
Two sets of entities exist on both the client and server: global entities and local entities. Global entities transmit updates regardless of player position. Local entities are synchronized within a certain radius of the player. The server will tell the client when an entity enters and exits the local area. When an entity exists the local area, the client doesn't have to delete it then, since other entities may be referring to it. A reference is simply a property which refers to the ID of an instance of the specified type.
A lot of properties the server simply doesn't care about, so these would be marked as client-only, or server-only for the opposite case.
Actions
Actions are requested by sending a request from one side (client or server) to the other with optional parameters. The other side validates the request and if it passes, carries out the action.
Actions are handled by the behavior. The behavior receives two events: "validate" and "perform" in which it validates the request and performs the action, respectively.
Actions should be primarily used for actions sent by the client which require server validation, like walking (requires collision detection) or equipping an inventory item.
In response to an action, the server usually wants to send updates to the nearby players.
Storage
Templates, entity definitions, and behaviors are stored in files. They are parsed when the server starts. Entity instances are stored in separate tables, with one table for each entity type. These tables are created if they don't exist when the server loads. Properties that need to be serialized are marked with a savable flag.
The server will periodically save entity instances in memory to the database, or when told to do so due to some major change.
Editing
Entity instances can be changed in real-time over the network by the editor.
Changes to templates, entity definitions, behaviors, or components must be committed to the SVN repository. The server must then be told to perform an "svn update" (and compile any new C++ code if present).
The server will then take the following steps on the next reload:
- Create any new tables for new entities and fill them with default values
- Execute any data migration functions that may be present
Server
Initialization
- Read entity templates
- Create table for each "complete" entity template if table does not already exist
- If the table does exist, run data migration functions, if present
- Load game world
On Connect
- Check client version - if old, give URL of update server
- Send client all "complete" entity definitions and client-side behaviors
Walking Around
- Periodically send any local changes within certain radius of player and global changes
- Stream new entity when entity is within radius of player
Shutdown
Client
Initialization
- Load meshes, textures, and sounds.
- Wait for user to connect
On Connect
- Tell server what version of the content we have - download updates if necessary
- Read "complete" entity definitions and behaviors sent by the server and keep in memory
Walking Around
- Receive and apply any updates from server
- Instantiate entities that the server tells us have come into the local area
- Delete any entities the server says have left the local area as long as there are no references to those entities
- If there are references, wait until references no longer exist, then remove entity
- Send walk action requests to server
- Do we want to do client-side movement even if it gets out of sync?
Disconnecting
- Tell server we are disconnecting
- Go back to main menu
Example
class Door(Entity):
# Components
transform = TransformComponent()
mesh = MeshComponent()
# Properties
state = EnumProperty(["Open", "Opening", "Closing", "Closed"], default=2)
# Constants
duration = 1000
minDistance = 2
# Actions
openAction = Action()
closeAction = Action()
# Client side
class ClientDoor(Door):
# Components
interactable = InteractableComponent()
# Event handlers
interactable.onInteract = onInteract
state.onUpdate = onStateUpdate
def onStateUpdate(self):
if self.state == "Opening":
self.transform.rotateZ(-pi/2, self.duration)
elif self.state == "Closing":
self.transform.rotateZ(pi/2, self.duration)
def onInteract(self, player):
if self.state == "Closed":
self.openAction()
else:
self.closeAction()
# Server side
class ServerDoor(Door):
# Event handlers
openAction.OnPerform = onOpenAction
closeAction.OnPerform = onCloseAction
def onOpenAction(self, player):
if player.distanceTo(self.transform) <= self.minDistance:
if self.state == "Closing" or self.state == "Closed":
self.state = "Opening"
self.localEntities.sendUpdate(self.state)
sleep(self.duration)
self.state = "Open"
self.localEntities.sendUpdate(self.state)
def onCloseAction(self, player):
if player.distanceTo(self.transform) <= self.minDistance:
if self.state == "Opening" or self.state == "Open":
self.state = "Closing"
self.localEntities.sendUpdate(self.state)
sleep(self.duration)
self.state = "Closed"
self.localEntities.sendUpdate(self.state)

