15 min read

(For more resources related to this topic, see here.)

Introducing the entity system

The entity system exists to spawn and manage entities in the game world. Entities are logical containers, allowing drastic changes in behavior at runtime. For example, an entity can change its model, position, and orientation at any point in the game.

Consider this; every item, weapon, vehicle, and even player that you have interacted with in the engine is an entity. The entity system is one of the most important modules present in the engine, and is dealt regularly by programmers.

The entity system, accessible via the IEntitySystem interface, manages all entities in the game. Entities are referenced to using the entityId type definition, which allows 65536 unique entities at any given time.

If an entity is marked for deletion, for example, IEntity::Remove(bool bNow = false), the entity system will delete this prior to updating at the start of the next frame. If the bNow parameter is set to true, the entity will be removed right away.

Entity classes

Entities are simply instances of an entity class, represented by the IEntityClass interface. Each entity class is assigned a name that identifies it, for example, SpawnPoint.

Classes can be registered via, IEntityClassRegistry::RegisterClass, or via IEntityClassRegistry::RegisterStdClass to use the default IEntityClass implementation.

Entities

The IEntity interface is used to access the entity implementation itself. The core implementation of IEntity is contained within, CryEntitySystem.dll, and cannot be modified. Instead, we are able to extend entities using game object extensions (have a look at the Game object extensions section in this article) and custom entity classes.

entityId

Each entity instance is assigned a unique identifier, which persists for the duration of the game session.

EntityGUID

Besides the entityId parameter, entities are also given globally unique identifiers, which unlike entityId can persist between game sessions, in the case of saving games and more.

Game objects

When entities need extended functionality, they can utilize game objects and game object extensions. This allows for a larger set of functionality that can be shared by any entity.

Game objects allow the handling of binding entities to the network, serialization, per-frame updates, and the ability to utilize existing (or create new) game object extensions such as Inventory and AnimatedCharacter.

Typically in CryENGINE development, game objects are only necessary for more important entity implementations, such as actors.

The entity pool system

The entity pool system allows “pooling” of entities, allowing efficient control of entities that are currently being processed. This system is commonly accessed via flowgraph, and allows the disabling/enabling groups of entities at runtime based on events.

Pools are also used for entities that need to be created and released frequently, for example, bullets.

Once an entity has been marked as handled by the pool system, it will be hidden in the game by default. Until the entity has been prepared, it will not exist in the game world. It is also ideal to free the entity once it is no longer needed.

For example, if you have a group of AI that only needs to be activated when the player reaches a predefined checkpoint trigger, this can be set up using AreaTrigger (and its included flownode) and the Entity:EntityPool flownode.

Creating a custom entity

Now that we’ve learned the basics of the entity system, it’s time to create our first entity. For this exercise, we’ll be demonstrating the ability to create an entity in Lua, C#, and finally C++.

.

Creating an entity using Lua

Lua entities are fairly simple to set up, and revolve around two files: the entity definition, and the script itself. To create a new Lua entity, we’ll first have to create the entity definition in order to tell the engine where the script is located:

<Entity Name=”MyLuaEntity” Script=”Scripts/Entities/Others/MyLuaEntity.lua” />


Simply save this file as MyLuaEntity.ent in the Game/Entities/ directory, and the engine will search for the script at Scripts/Entities/Others/MyLuaEntity.lua.

Now we can move on to creating the Lua script itself! To start, create the script at the path set previously and add an empty table with the same name as your entity:

MyLuaEntity = { }


When parsing the script, the first thing the engine does is search for a table with the same name as the entity, as you defined it in the .ent definition file. This main table is where we can store variables, Editor properties, and other engine information.

For example, we can add our own property by adding a string variable:

MyLuaEntity = { Properties = { myProperty = “”, }, }


It is possible to create property categories by adding subtables within the Properties table. This is useful for organizational purposes.

With the changes done, you should see the following screenshot when spawning an instance of your class in the Editor, via RollupBar present to the far right of the Editor by default:

Common Lua entity callbacks

The script system provides a set of callbacks that can be utilized to trigger specific logic on entity events. For example, the OnInit function is called on the entity when it is initialized:

function MyEntity:OnInit() end


Creating an entity in C#

The third-party extension, CryMono allows the creation of entities in .NET, which leads us to demonstrate the capability of creating our very own entity in C#.

To start, open the Game/Scripts/Entities directory, and create a new file called MyCSharpEntity.cs. This file will contain our entity code, and will be compiled at runtime when the engine is launched.

Now, open the script (MyCSharpEntity.cs) IDE of your choice. We’ll be using Visual Studio in order to provide IntelliSense and code highlighting.

Once opened, let’s create a basic skeleton entity. We’ll need to add a reference to the CryENGINE namespace, in which the most common CryENGINE types are stored.

using CryEngine; namespace CryGameCode { [Entity] public class MyCSharpEntity : Entity { } }


Now, save the file and start the Editor. Your entity should now appear in RollupBar, inside the Default category. Drag MyEntity into the viewport in order to spawn it:

We use the entity attribute ([Entity]) as a way of providing additional information for the entity registration progress, for example, using the Category property will result in using a custom Editor category, instead of Default.

[Entity(Category = “Others”)]


Adding Editor properties

Editor properties allow the level designer to supply parameters to the entity, perhaps to indicate the size of a trigger area, or to specify an entity’s default health value.

In CryMono, this can be done by decorating supported types (have a look at the following code snippet) with the EditorProperty attribute. For example, if we want to add a new string property:

[EditorProperty] public string MyProperty { get; set; }


Now when you start the Editor and drag MyCSharpEntity into the viewport, you should see MyProperty appear in the lower part of RollupBar.

The MyProperty string variable in C# will be automatically updated when the user edits this via the Editor. Remember that Editor properties will be saved with the level, allowing the entity to use Editor properties defined by the level designer even in pure game mode.

Property folders

As with Lua scripts, it is possible for CryMono entities to place Editor properties in folders for organizational purposes. In order to create folders, you can utilize the Folder property of the EditorProperty attribute as shown:

[EditorProperty(Folder = “MyCategory”)]


You now know how to create entities with custom Editor properties using CryMono! This is very useful when creating simple gameplay elements for level designers to place and modify at runtime, without having to reach for the nearest programmer.

Creating an entity in C++

Creating an entity in C++ is slightly more complex than making one using Lua or C#, and can be done differently based on what the entity is required for. For this example, we’ll be detailing the creation of a custom entity class by implementing IEntityClass.

Creating a custom entity class

Entity classes are represented by the IEntityClass interface, which we will derive from and register via IEntityClassRegistry::RegisterClass(IEntityClass *pClass).

To start off, let’s create the header file for our entity class. Right-click on your project in Visual Studio, or any of its filters, and go to Add | New Item in the context menu. When prompted, create your header file ( .h). We’ll be calling CMyEntityClass.

Now, open the generated MyEntityClass.h header file, and create a new class which derives from IEntityClass:

#include <IEntityClass.h> class CMyEntityClass : public IEntityClass { };


Now that we have the class set up, we’ll need to implement the pure virtual methods we inherit from IEntityClass in order for our class to compile successfully.

For most of the methods, we can simply return a null pointer, zero, or an empty string. However, there are a couple of methods which we have to handle for the class to function:

  • Release(): This is called when the class should be released, should simply perform “delete this;” to destroy the class
  • GetName(): This should return the name of the class
  • GetEditorClassInfo(): This should return the ClassInfo struct, containing Editor category, helper, and icon strings to the Editor
  • SetEditorClassInfo(): This is called when something needs to update the Editor ClassInfo explained just now.

IEntityClass is the bare minimum for an entity class, and does not support Editor properties yet (we will cover this a bit further later).

To register an entity class, we need to call IEntityClassRegistry::RegisterClass. This has to be done prior to the IGameFramework::CompleteInit call in CGameStartup. We’ll be doing it inside GameFactory.cpp, in the InitGameFactory function:

IEntityClassRegistry::SEntityClassDesc classDesc; classDesc.sName = “MyEntityClass”; classDesc.editorClassInfo.sCategory = “MyCategory”; IEntitySystem *pEntitySystem = gEnv->pEntitySystem; IEntityClassRegistry *pClassRegistry = pEntitySystem- >GetClassRegistry(); bool result = pClassRegistry->RegisterClass(new CMyEntityClass(classDesc));


Implementing a property handler

In order to handle Editor properties, we’ll have to extend our IEntityClass implementation with a new implementation of IEntityPropertyHandler. The property handler is responsible for handling the setting, getting, and serialization of properties.

Start by creating a new header file named MyEntityPropertyHandler.h. Following is the bare minimum implementation of IEntityPropertyHandler. In order to properly support properties, you’ll need to implement SetProperty and GetProperty, as well as LoadEntityXMLProperties (the latter being required to read property values from the Level XML).

Then create a new class which derives from IEntityPropertyHandler:

class CMyEntityPropertyHandler : public IEntityPropertyHandler { };


In order for the new class to compile, you’ll need to implement the pure virtual methods defined in IEntityPropertyHandler. Methods crucial for the property handler to work properly can be seen as shown:

  • LoadEntityXMLProperties: This is called by the Launcher when a level is being loaded, in order to read property values of entities saved by the Editor
  • GetPropertyCount: This should return the number of properties registered with the class
  • GetPropertyInfo: This is called to get the property information at the specified index, most importantly when the Editor gets the available properties
  • SetProperty: This is called to set the property value for an entity
  • GetProperty: This is called to get the property value of an entity
  • GetDefaultProperty: This is called to retrieve the default property value at the specified index

To make use of the new property handler, create an instance of it (passing the requested properties to its constructor) and return the newly created handler inside

IEntityClass::GetPropertyHandler().


We now have a basic entity class implementation, which can be easily extended to support Editor properties. This implementation is very extensible, and can be used for vast amount of purposes, for example, the C# script seen later has simply automated this process, lifting the responsibility of so much code from the programmer.

Entity flownodes

You may have noticed that when right-clicking inside a graph, one of the context options is Add Selected Entity. This functionality allows you to select an entity inside a level, and then add its entity flownode to the flowgraph.

By default, the entity flownode doesn’t contain any ports, and will therefore be mostly useless as shown to the right.

However, we can easily create our own entity flownode that targets the entity we selected in all three languages.

Creating an entity flownode in Lua

By extending the entity we created in the Creating an entity using Lua section, we can add its very own entity flownode:

function MyLuaEntity:Event_OnBooleanPort() BroadcastEvent(self, “MyBooleanOutput”); end MyLuaEntity.FlowEvents = { Inputs = { MyBooleanPort = { MyLuaEntity.Event_OnBooleanPort, “bool” }, }, Outputs = { MyBooleanOutput = “bool”, }, }


We just created an entity flownode for our MyLuaEntity class. If you start the Editor, spawn your entity, select it and then click on Add Selected Entity in your flowgraph, you should see the node appearing.

Creating an entity flownode using C#

Creating an entity flownode in C# is very simple due to being almost exactly identical in implementation as the regular flownodes. To create a new flownode for your entity, simply derive from EntityFlowNode, where T is your entity class name:

using CryEngine.Flowgraph; public class MyEntity : Entity { } public class MyEntityNode : EntityFlowNode { [Port] public void Vec3Test(Vec3 input) { } [Port] public void FloatTest(float input) { } [Port] public void VoidTest() { } [Port] OutputPort BoolOutput { get; set; } }


We just created an entity flownode in C#. This allows us to utilize TargetEntity in our new node’s logic.

Creating an entity flownode in C++

In short, entity flownodes are identical in implementation to regular nodes. The difference being the way the node is registered, as well as the prerequisite for the entity to support TargetEntity.

Registering the entity node

We utilize same methods for registering entity nodes as before, the only difference being that the category has to be entity, and the node name has to be the same as the entity it belongs to:

REGISTER_FLOW_NODE(“entity:MyCppEntity”, CMyEntityFlowNode);


The final code

Finally, from what we’ve learned now, we can easily create our first entity flownode in C++:

#include “stdafx.h” #include “Nodes/G2FlowBaseNode.h” class CMyEntityFlowNode : public CFlowBaseNode { enum EInput { EIP_InputPort, }; enum EOutput { EOP_OutputPort }; public: CMyEntityFlowNode(SActivationInfo *pActInfo) { } virtual IFlowNodePtr Clone(SActivationInfo *pActInfo) { return new CMyEntityFlowNode(pActInfo); } virtual void ProcessEvent(EFlowEvent evt, SActivationInfo *pActInfo) { } virtual void GetConfiguration(SFlowNodeConfig &config) { static const SInputPortConfig inputs[] = { InputPortConfig_Void(“Input”, “Our first input port”), {0} }; static const SOutputPortConfig outputs[] = { OutputPortConfig_Void(“Output”, “Our first output port”), {0} }; config.pInputPorts = inputs; config.pOutputPorts = outputs; config.sDescription = _HELP(“Entity flow node sample”); config.nFlags |= EFLN_TARGET_ENTITY; } virtual void GetMemoryUsage(ICrySizer *s) const { s->Add(*this); } }; REGISTER_FLOW_NODE(“entity:MyCppEntity”, CMyEntityFlowNode);


Game objects

As mentioned at the start of the article, game objects are used when more advanced functionality is required of an entity, for example, if an entity needs to be bound to the network.

There are two ways of implementing game objects, one being by registering the entity directly via IGameObjectSystem::RegisterExtension (and thereby having the game object automatically created on entity spawn), and the other is by utilizing the IGameObjectSystem::CreateGameObjectForEntity method to create a game object for an entity at runtime.

Game object extensions

It is possible to extend game objects by creating extensions, allowing the developer to hook into a number of entity and game object callbacks. This is, for example, how actors are implemented by default.

We will be creating our game object extension in C++. The CryMono entity we created earlier in the article was made possible by a custom game object extension contained in CryMono.dll, and it is currently not possible to create further extensions via C# or Lua.

Creating a game object extension in C++

CryENGINE provides a helper class template for creating a game object extension, called CGameObjectExtensionHelper. This helper class is used to avoid duplicating common code that is necessary for most game object extensions, for example, basic RMI functionality.

To properly implement IGameObjectExtension, simply derive from the CGameObjectExtensionHelper template, specifying the first template argument as the class you’re writing (in our case, CMyEntityExtension) and the second as IGameObjectExtension you’re looking to derive from.

Normally, the second argument is IGameObjectExtension, but it can be different for specific implementations such as IActor (which in turn derives from IGameObjectExtension).

class CMyGameObjectExtension : public CGameObjectExtensionHelper<CMyGameObjectExtension, IGameObjectExtension> { };


Now that you’ve derived from IGameObjectExtension, you’ll need to implement all its pure virtual methods to spare yourself from a bunch of unresolved externals. Most can be overridden with empty methods that return nothing or false, while more important ones have been listed as shown:

  • Init: This is called to initialize the extension. Simply performSetGameObject(pGameObject); and then return true.
  • NetSerialize: This is called to serialize things over the network.

You’ll also need to implement IGameObjectExtensionCreatorBase in a new class that will serve as an extension factory for your entity. When the extension is about to be activated, our factory’s Create() method will be called in order to obtain the new extension instance:

struct SMyGameObjectExtensionCreator : public IGameObjectExtensionCreatorBase { virtual IGameObjectExtension *Create() { return new CMyGameObjectExtension(); } virtual void GetGameObjectExtensionRMIData(void **ppRMI, size_t *nCount) { return CMyGameObjectExtension::GetGameObjectExtensionRMIData (ppRMI, nCount); } };


Now that you’ve created both your game object extension implementation, as well as the game object creator, simply register the extension:

static SMyGameObjectExtensionCreator creator; gEnv->pGameFramework->GetIGameObjectSystem()- >RegisterExtension(“MyGameObjectExtension”, &creator, myEntityClassDesc);


By passing the entity class description to IGameObjectSystem::RegisterExtension, you’re telling it to create a dummy entity class for you. If you have already done so, simply pass the last parameter pEntityCls as NULL to make it use the class you registered before.

Activating our extension

In order to activate your game object extension, you’ll need to call IGameObject::ActivateExtension after the entity is spawned. One way to do this is using the entity system sink, IEntitySystemSink, and listening to the OnSpawn events.

We’ve now registered our own game object extension. When the entity is spawned, our entity system sink’s OnSpawn method will be called, allowing us to create an instance of our game object extension.

Summary

In this article, we have learned how the core entity system is implemented and exposed and created our own custom entity.

You should now be aware of the process of creating accompanying flownodes for your entities, and be aware of the working knowledge surrounding game objects and their extensions.

If you want to get more familiar with the entity system, you can try and create a slightly more complex entity on your own.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here