Table of Contents
This chapter will cover the basic steps of creating a non-player entity. While the entity presented is quite simple in terms of functionality, it covers all the common essentials required in order to get a new entity up and running in the engine (including exporting the model from 3D Studio Max and configuring the model to work correctly).
Before creating an entity, we need to determine what functionality is required. For this tutorial we will to create an NPC which will greet a player when they get within a certain radius (think of a person who stands in a supermarket entrance greeting people).
Entity requirements:
-
It should be placeable in the World Editor so that the run-time instance is created by the SpaceLoader entity on the server.
-
Model should be loaded asynchronously on the client.
-
The entity will not move. It should stand on the spot as placed in the World Editor.
-
A server-side trap should be used to trigger a greet action. The server should notify all clients in the area that it has greeted a player (including which player).
-
When the entity greets a player, a wave animation should be played on all clients in the area.
-
A message, generated on the server, should be displayed above the Greeter's head for a couple of seconds.
-
It should be possible to deactivate (and reactivate) the Greeter from the client, but only if the client is within the trigger radius.
We shall give this entity the class name
Greeter
.
For this tutorial, we have provided the 3D Studio Max source file to the Barbarian model (a fantasy themed human). The model needs to prepared for use by the engine and needs to be configured to satisfy the requirements of the Greeter entity. This section assumes the BigWorld exporters have already been installed (see the Content Tools Reference Guide).
Detailed documentation about the exporters and tools can be found in the Content Tools Reference Guide and the Content Creation Manual.
Note
If you want to skip this section, the barbarian model has been
pre-prepared for this tutorial (in C:/bigworld/tutorial/res/characters
).
-
Open
tutorial/sourceart/barbarian.max
in 3D Studio Max. -
Copy the textures to
tutorial/res/characters
. The textures must be in the target directory before exporting (the exporter will display an error message and fail if they are not). -
Re-apply the textures to the model so that the Max scene points to the textures copied in the step above.
-
Go to File → Export and choose the BigWorld visual exporter.
-
Save the model to
tutorial/res/characters/barbarian.model
.
In order to automatically play an idle animation and to create the action required for the Greeter entity to wave, we need to add animations to the model and configure the appropriate actions. This is done in the Model Editor.
While an animation is a raw sequence of key frames, an action is a higher level concept. Actions are animation wrappers that contain extra information such as animation blending and what game-play situations will trigger the animation (e.g. idling, walking or running based on the velocity of the entity).
The Greeter will have two actions: an Idle action which is automatically selected when the entity is standing still, and a Wave action which will be explicitly invoked from the Greeter's Python scripts.
-
Open up
tutorial/res/characters/barbarian.model
in Model Editor. -
Before we can setup the actions, we need to add references to the idle and wave animations. In the Animations tab, click the "New animation" button and select the
tutorial/res/characters/idle_a.animation
animation file. A new animation will be added to the list which can be previewed in the 3D view. Repeat this for them_waveonehand.animation
file. -
To setup an Idle action that is automatically invoked when the entity is standing still,
-
Open the Actions tab in Model Editor.
-
Click the New Action button and select the m_idle animation in the pop-up dialog. Set the action name to Idle.
-
Select the new Idle action from the list.
-
Setup the parameters in the Match section to allow the action matcher to automatically select the action when the entity is not moving. To do this set the following values:
-
Minimum speed=0.0, Maximum speed=0.0
-
Minimum turn=-360.0, Maximum turn=360.0
-
Minimum direction=-360.0, Maximum direction=360.0
As you can see, the action will be picked whenever the speed of the entity is exactly zero and is facing in any direction.
-
-
-
To setup a Wave action that is invoked explicitly by the Python scripts (i.e. not automatically picked by the engine),
-
Click the New Action button and select the m_wave animation in the pop-up dialog. Set the action name to Wave.
No match settings need to be set for this action since we will manually invoke the action from the Python scripts.
-
In order to insert the model as an entity into a space, we need to create the entity scripts. These are written in Python and perform game-specific logic and are split up into three parts: base, cell, and client.
Please refer to the Python API reference documents[9] for detailed information on the API's mentioned here.
First off, we need to tell the engine about our new entity. Every
entity must be defined in the entities.xml
file
located at tutorial/res
path. We
add the Greeter to the ClientServerEntities block since the
entity will exist on both client and server.
<root> <ClientServerEntities> <Avatar/> <Greeter/> </ClientServerEntities> <ServerOnlyEntities> <Space/> </ServerOnlyEntities> </root>
Remember that since the entity name corresponds with a Python class name, the name used here must conform with Python naming rules.
In order to allow the engine to know what methods and properties the entity has, we need to create a special file known as the entity definition file. In some ways this file is the most important part of an entity, as it defines how properties and methods are handled by the engine (e.g. property type, whether or not a property or method is exposed to clients, prioritisation of remote method calls and property updates, and configuring distance based LoD parameters for individual properties).
See the Server Programming Guide chapter The Entity Definition File for a detailed description of entity definitions.
For the Greeter entity, create a new file named
Greeter.def
and place it in tutorial/res/scripts/entity_defs/
. We will
define the following information for our entity:
-
Three properties:
-
A
radius
property which controls the trigger region for the Greeter. This is exposed to the World Editor so that it can be tweaked by the world builder. Its type is FLOAT, it has a default value of 3 metres and is declared as CELL_PRIVATE (since this property is only needed on the cell part of the entity and does not need to be publicly accessible by other entities).Note
To provide a more intuitive interface for the World Editor, some extra meta-data has been defined for this property. The RADIUS widget allows the property to be manipulated via a visual spherical widget.
-
A property named
activated
which is a boolean property representing whether or not the Greeter is currently active. It's flags is set to ALL_CLIENTS so that changes to this property on the cell are automatically propagated to the clients. -
The
createOnCell
property which indicates which space the entity should be created in (a requirement for entities loaded via theSpaceLoader
entity).
-
-
Two methods:
-
A client-side method named
greet
. This will be remotely called by the server on all nearby clients whenever the entity greets a player (i.e. whenever the server-side trap is triggered). It takes two parameters, the ID of the entity is greeting, and a personalised greet message. -
A method called
toggleActive
which is exposed to the client which allows the client to toggle the Greeter on and off. By default methods are not callable by the client (for security purposes), so the <Exposed> keyword is used to explicitly expose it to clients. It does not take any arguments.
-
<root> <Properties> <radius> <Type> FLOAT <Widget> RADIUS <colour> 255 0 0 192 </colour> <gizmoRadius> 2 </gizmoRadius> </Widget> </Type> <Flags> CELL_PRIVATE </Flags> <Default> 3.0 </Default> <Editable> true </Editable> </radius> <activated> <Type> INT8 </Type> <Flags> ALL_CLIENTS </Flags> <Default> 1 </Default> </activated> <createOnCell> <Type> MAILBOX </Type> <Flags> BASE </Flags> </createOnCell> </Properties> <ClientMethods> <greet> <Arg> UINT32 </Arg> <!-- Entity ID of who we are greeting --> <Arg> STRING </Arg> <!-- Our greeting message --> </greet> </ClientMethods> <CellMethods> <toggleActive> <Exposed/> </toggleActive> </CellMethods> <BaseMethods> </BaseMethods> </root>
Example
tutorial/res/scripts/entity_defs/Greeter.def
The base part of the entity is the first part that gets created by the server. The base is created on one of the BaseApp processes, and is used to define entity logic which does not require spatial information (e.g. character inventory). The base part of an entity does not migrate between BaseApps after it has been created.
The base script for the Greeter entity is very simple and performs two tasks:
-
It creates the cell part of the entity within the cell specified by the
createOnCell
property (as setup by theEntityLoader
class when it loads the entity information from the space's chunk file). -
It destroys itself when the cell part of the entity disappears.
import BigWorld class Greeter( BigWorld.Base ): def __init__( self ): BigWorld.Base.__init__( self ) self.createCellEntity( self.createOnCell ) def onLoseCell( self ): self.destroy()
Example
tutorial/res/scripts/base/Greeter.py
The cell part of an entity represents the current position, orientation, and movement for an entity within a particular space. Managed by the CellApp processes, the cell part of an entity can be moved between CellApp processes at any time based on CPU load. Generally, all entity logic that requires access to spatial information is implemented in the cell part of an entity (e.g. any code that needs to find out about other nearby entities, such as AI).
The cell part of the Greeter performs the following tasks:
-
Creates a trap when the entity is created using the radius specified in the World Editor.
-
Greets any Avatars that walk into the trap by calling
greet
on all clients that have the Greeter entity within their AoI. -
Allow clients to toggle
activated
state of the entity, but only if they are within the radius. Note that exposing a method to the client implicitly adds an argument which is the ID of the Avatar entity which invoked the method. This can (and should) be used to validate that the Avatar is actually allowed to perform the desired command (remember, never trust the client).
Cell entities must derive from the
BigWorld.Entity
class.
import BigWorld import Avatar import random MESSAGES = [ "Hello BigWorld", "Have a nice day" ] class Greeter( BigWorld.Entity ): def __init__( self ): BigWorld.Entity.__init__( self ) # Setup the trap self.addProximity( self.radius, 0 ) def onEnterTrap( self, entityEntering, range, controllerID ): # If we are not active, do nothing. if not self.activated: return # Filter by entity class type if not isinstance( entityEntering, Avatar.Avatar ): return # Notify clients. self.allClients.greet( entityEntering.id, random.choice(MESSAGES) ) def toggleActive( self, sourceID ): # Get the entity who called us. If the entity can't be found then they # obviously not near by so just bail out. try: sourceEntity = BigWorld.entities[ sourceID ] except KeyError: return # Get the distance between ourself and the Avatar dist = sourceEntity.position.distTo( self.position ) # Do a check to make sure they are close enough. if dist > self.radius: return # All good, toggle our state. The activated property will be automatically # propagated to all clients once this server tick is complete. self.activated = not self.activated
Example
tutorial/res/scripts/cell/Greeter.py
The client part of the entity is automatically created by the engine whenever an entity appears within your Avatar's area of interest (AoI). It is the job of the client scripts to coordinate all resources and logic required to represent the entity on the client based on the information provided by the server.
The client-side of an entity must derive from
BigWorld.Entity
. The bare-bones Greeter module
script looks like this:
# Greeter.py import BigWorld import GUI import Math class Greeter( BigWorld.Entity ): def __init__( self ): BigWorld.Entity.__init__( self )
Basic
structure of
tutorial/res/scripts/cell/Greeter.py
To avoid stalling the main thread client when the entity is
created, we will use the prerequisites functionality to load the model
asynchronously in the background loading thread. This is done by
implementing the prerequisites
method which
returns a list of resources to be loaded. This means that whenever the
server notifies the client that a Greeter entity has entered the AoI
for the client, the client will first schedule the resources to be
loaded asynchronously.
GREETER_MODEL_NAME = "characters/barbarian.model" class Greeter( BigWorld.Entity ): .... def prerequisites( self ): return [ GREETER_MODEL_NAME ]
Once the prerequisite resources have been loaded, the
onEnterWorld
method is called. Since the
entity class instance can leave the AoI and then re-enter the AoI, the
bulk of the initialisation code will be done in here rather than in
__init__
(so it can re-initialised each
time).
For the Greeter
entity, the primary
entity model (Entity.model
) is set, and a network
filter is setup. Since the entity will not be moving around, we can
use a simple DumbFilter
which simply snaps the
entity to the last network update.
class Greeter( BigWorld.Entity ): .... def onEnterWorld( self, prereqs ): # Setup our model. self.model = BigWorld.Model( GREETER_MODEL_NAME ) # Setup an appropriate filter. self.filter = BigWorld.DumbFilter() def onLeaveWorld( self ): # Clean up. self.model = None self.filter = None
The bulk of the client-side logic for the Greeter entity will go
in the implementation of the greet
method.
This method is remotely called from the cell part whenever an
Avatar
enters the trap.
class Greeter( BigWorld.Entity ): .... def greet( self, targetID, msg ): # Grab the entity instance, if for some reason we don't have it just do nothing. try: targetEntity = BigWorld.entities[targetID] except KeyError: return # Try to play the Wave action. If it doesn't exist, print a warning. try: self.model.Wave() except AttributeError: print "WARNING: Greeter model missing Wave action (%s)" % self.model.sources # Display the greet message above our head. addressee = targetEntity.name if targetID == BigWorld.player().id: addressee += "! Yes you" self._displayMessage( "Hey %s! '%s'!" % (addressee, msg) )
The script that displays a text message above the Greeter's head
will be implemented in a private helper method called
_displayMessage
(note the usage of an
underscore to denote a private member - this is not required but it is
a useful convention to follow). The
TextGUIComponent
class from the GUI module will
be used and will be inserted into the 3D scene using the
GUI.Attachment
class (as opposed to being
rendered in screen space). The text is attached to the root node of
the entity model and is positioned above the head of the model by
inspecting the model's height
attribute.
class Greeter( BigWorld.Entity ): .... def _displayMessage( self, msg ): # First make sure any previous message is cleared. self._clearMessage() # Create our text component. Since we want to display it in the world # we shall explicitly set our width and height in world units. text = GUI.Text( msg ) text.explicitSize = True text.size = ( 0, 0.5 ) # Specifying 0 for x to auto-calculate aspect ratio. text.colour = (255, 0, 0, 255) # Change the colour. text.filterType = "LINEAR" # Don't use point filtering. text.verticalAnchor = "BOTTOM" # Position relative to the bottom of the text. # The origin of our model is at our feet. To place the text above # our head, move it up on the Y by our model's height. text.position = (0, self.model.height + 0.1, 0) # Setup our GUI->World attachment. Tell it that we want the GUI # component to always face the camera. atch = GUI.Attachment() atch.component = text atch.faceCamera = True # Attach to our model's root node. self.model.root.attach( atch ) # Save a reference to the attachment so we can clean it up later. self._messageAttachment = atch # Setup the timer. self._setMessageHideTimer()
To make the message disappear after a certain amount of time,
the BigWorld.callback
function is used. The
hide message timer functionality is wrapped up in some additional
helper methods.
-
_clearMessage
clears any existing message attachment above the entity's head. -
_setMessageHideTimer
sets up the timer, while first cancelling any existing timer. -
_cancelMessageTimer
cancels the timer by passing the previously created timer handle intoBigWorld.cancelCallback
. -
_handleMessageHideTimer
is the Python callable that is given toBigWorld.callback
. It is executed after the timer has elapsed, clearing the stored timer handle and removing the current message.
class Greeter( BigWorld.Entity ): .... def _clearMessage( self ): self._cancelMessageTimer() if self._messageAttachment is not None: self.model.root.detach( self._messageAttachment ) self._messageAttachment = None def _setMessageHideTimer( self, timeout=5.0 ): self._cancelMessageTimer() self._messageTimerHandle = \ BigWorld.callback( timeout, self._handleMessageHideTimer ) def _cancelMessageTimer( self ): if self._messageTimerHandle is not None: BigWorld.cancelCallback( self._messageTimerHandle ) self._messageTimerHandle = None def _handleMessageHideTimer( self ): self._messageTimerHandle = None self._clearMessage()
The engine will automatically notify the entity script whenever
a property has been changed by the server. It does this by looking for
a method on the entity class named
set_propertyName
which is expected to take a
single parameter for the previous value of the property. The Greeter
script will take advantage of this notification and display a message
whenever the activated
state has changed.
def set_activated( self, oldValue ): if self.activated: self._displayMessage( "Alright! I'm now ready to GREET." ) else: self._displayMessage( "Shutting up now." )
The editor script for an entity allows programmatic control over how the entity behaves in the World Editor. For the Greeter entity, the script will simply override the default model used to represent the entity in the editor (it otherwise defaults to a red box).
Editor scripts are located in res/scripts/editor
.
class Greeter: def modelName( self, props ): return "characters/barbarian.model"
To test the entity it will first need to be placed into a space in World Editor. Open the spaces/main and place the entity by dragging the Greeter entity into the scene from the Resources tab. Save the space.
If you are not using a Windows mount, update the resources on the server side and then restart the server. If all is well, you should be able to connect as per-normal and see the Greeter in the space.

Greeter entity in action
If you do not see the entity, there are a couple of things to check:
-
Check the server startup logs for any Python exceptions.
-
Check the cell logs to make sure the entity is actually being created. You should see a message along the lines of:
CellApp INFO Cell::createEntity: New Greeter (2)
-
Check the client for any client-side Python errors (e.g. bring up the in-game client console or use Debug View).
Note that at this point the only way to toggle the activation state is to use the in-game Python console. For example, on the client,
>>> $B.entities.items() # Find the ID for the Greeter [(2402, Greeter at 0x088CFFE8), (2405, PlayerAvatar at 0x088CFC10)] >>> greeter = $B.entities[2402] >>> greeter.cell.toggleActive()
While the entity satisfies the basic requirements, there are some improvements that could be made.
-
The most obvious improvement would be to allow the user to toggle the active state of the Greeter entity by clicking on the entity. This could be achieved by leveraging the entity targeting system of the client. See the Client Python API documentation for
BigWorld.target
. -
If many player entities enter the trap at the same time, the client will try to greet everyone at once. Instead of simply playing the wave animation immediately when the
greet
method is called on the client, the client-side script could be designed so that greets are queued up so that the next greet will not commence until the previous greet has completed. This could be achieved by passing a callback intomodel.Wave()
so that the scripts get notified when the current action has completed. See the Client Python API forActionQueuer.__call__
for information on how you can use action callbacks. -
Currently the Greeter entity simply plays the Wave action. It would be nice if the entity looked towards you while it is greeting you. A head tracker can be created by using the
BigWorld.Tracker
class coupled with theBigWorld.TrackerNodeInfo
class. -
The player can cause the Greeter to spam greetings if they quickly move in and out of the trap radius. To avoid this problem, the cell part of the entity should keep track of recent greets (associate an entity ID with a time stamp). It should only re-greet a player if some time has elapsed since the previous greeting. This list should be added as a new property in
Greeter.def
, and additional logic placed inGreeter.onEnterTrap
.