Table of Contents
In A Basic Client-Only Game (CLIENT_ONLY) we set up a basic client resources tree that would allow us to walk around a simple space using a first-person view. In this chapter of the tutorial we will extend the game to the server, so that multiple clients can log in and see each other walking around.
Prior to progressing through this part of the tutorial it is necessary to install and configure the BigWorld server. If you haven't already done this please proceed to the Server Installation Guide
At this point it is also relevant to address the issue of sharing
files between Linux and Windows machines. Since there are many files that
are read by both the client and the server
(tutorial/res/scripts/entity_defs/
,
space data, etc), it is necessary to keep them all on a single file system
that is shared between the client and server, rather than having to keep
them synchronised manually. Please refer to the Client Programming Guide's section on Shared Development Environments for more information on this
topic.
*
For the purposes of this tutorial, we will assume that you have
mounted your Windows directory tree at
on your Linux file system.
$HOME
/bigworld_windows_share
In BigWorld, spaces are separate coordinate systems. Each space can have one or more geometry mappings (as created in the World Editor). Cell entities are associated with a single space at any one time. These may be used to implement things like planets, mission instances, apartments or game sharding.
A new space is created by creating a cell entity in a new space. It is typical to have an entity type that is responsible for space creation.
Every entity must be defined in the
entities.xml
file located at tutorial/res
path. In this case the Space
entity exists only on the server, so it should be defined within the
ServerOnlyEntities block:
<root> <ClientServerEntities> <Avatar/> </ClientServerEntities> <ServerOnlyEntities> <Space/> </ServerOnlyEntities> </root>
The Space
entity type has a single string
property spaceDir
. This will be used to indicate
which space geometry to load.
<root> <Properties> <spaceDir> <Type> STRING </Type> <Flags> BASE </Flags> </spaceDir> </Properties> <ClientMethods> </ClientMethods> <CellMethods> <addGeometryMapping> <Arg> STRING </Arg> </addGeometryMapping> </CellMethods> <BaseMethods> </BaseMethods> <root>
Example
tutorial/res/scripts/entity_defs/Space.def
The base entity calls
self.createInNewSpace()
to create a new space,
put its cell entity in it, and tells the cell to add a space geometry
mapping. It registers itself globally as
"DefaultSpace" so that the base entity can easily be
found later.
import BigWorld class Space( BigWorld.Base ): def __init__( self ): BigWorld.Base.__init__( self ) # Create this entity in a new space self.createInNewSpace() self.cell.addGeometryMapping( self.spaceDir ) self.registerGlobally( "DefaultSpace", self.onRegistered ) def onRegistered( self, succeeded ): if not succeeded: print "Failed to register space." self.destroyCellEntity() def onLoseCell( self ): # Once our cell entity is destroyed, it's safe to clean up the Proxy. # We can't just call self.destroy() in onClientDeath() above, as # destroyCellEntity() is asynchronous and the cell entity would still # exist at that point. self.destroy()
Example
tutorial/res/scripts/base/Space.py
The cell entity maps the geometry to load after receiving a call
from the base to addGeometryMapping()
, with an
appropriate path to a geometry (e.g.
spaces/main
).
import BigWorld class Space( BigWorld.Entity ): def __init__( self, nearbyEntity ): BigWorld.Entity.__init__( self ) # This is the first entity created for the space assert( nearbyEntity is None ) def onDestroy( self ): # Destroy the space and all entities in it self.destroySpace() def addGeometryMapping( self, geometryToMap ): # The base informs us what geometry to map. BigWorld.addSpaceGeometryMapping( self.spaceID, None, geometryToMap )
Example
tutorial/res/scripts/cell/Space.py
Just like the client, the server uses personality scripts to perform
bootstrap functionality on each CellApp and BaseApp. For the moment, we
are only interested in the onBaseAppReady
callback in the BaseApp personality script, which we will use to create a
space.
Our initial revision of
tutorial/res/scripts/base/BWPersonality.py
is
displayed below:
# Base bootstrap script import BigWorld def onInit( isReload ): pass def onBaseAppReady( isBootstrap, didAutoLoadEntitiesFromDB ): # Only on the first baseapp if isBootstrap: # Create a Space entity that will create a space with our geometry. BigWorld.createBaseLocally( "Space", spaceDir = "spaces/main" )
Example
tutorial/res/scripts/base/BWPersonality.py
A Space
entity is created with the
spaceDir
property set to
"spaces/main".
Our initial revision of
tutorial/res/scripts/cell/BWPersonality.py
is
displayed below:
# Base bootstrap script import BigWorld def onInit( isReload ): pass def onCellAppReady( isFromDB ): pass
Example
tutorial/res/scripts/cell/BWPersonality.py
Our implementation of the scripts is trivial and provides only stub implementations of callbacks that will be explained later.
For a complete list of the available personality script callbacks,
see the documentation for the BWPersonality
module
in BaseApp Python API, CellApp Python API, and Client Python API.
The next step is to define the server-side logic that goes with our
Avatar
class. Even if we did not want to define any
server-side logic for our Avatar
, we would still
need to provide at least stub implementations of
Avatar.py
in the base
and cell
directories so that the base and cell
parts of our Avatar
entity can be created.
First we need to define the base part of the Avatar in
tutorial/res/scripts/base/Avatar.py
:
import BigWorld # Must derive from BigWorld.Proxy instead of BigWorld.Base if this entity type # is to be controlled by the player. class Avatar( BigWorld.Proxy ): def __init__( self ): BigWorld.Proxy.__init__( self ) # Set our spawn position. self.cellData[ "position" ] = (0,0,0) # Spawn in the default space. self.createCellEntity( BigWorld.globalBases[ "DefaultSpace" ].cell ) def onClientDeath( self ): # We ensure our cell entity is destroyed when the client disconnects. self.destroyCellEntity() def onLoseCell( self ): # Once our cell entity is destroyed, it is safe to clean up the Proxy. We cannot # just call self.destroy() in onClientDeath() above, as destroyCellEntity() # is asynchronous and the cell entity would still exist at that point. self.destroy()
Example
tutorial/res/scripts/base/Avatar.py
The constructor for the base entity creates the cell entity in our
space created earlier. It was registered in
BigWorld.globalBases
as
"DefaultSpace".
There is a little bit of housekeeping here too — we have
provided implementations for the onClientDeath
and onLoseCell
callbacks, which clean up the cell
and base parts of the entity when the client disconnects from the
server.
At this stage we do not need to define any interesting logic on the
cell entity, so we provide a stub implementation in
tutorial/res/scripts/cell/Avatar.py
:
import BigWorld class Avatar( BigWorld.Entity ): def __init__( self, nearbyEntity ): BigWorld.Entity.__init__( self )
Example
tutorial/res/scripts/cell/Avatar.py
We need to add code to our basic client to have it connect to a
server. If you have used FantasyDemo, you will have experienced the
various GUI-based methods that can be used to connect to a server. Since
we are not writing GUI code yet, we will just enter the address of our
server into tutorial/res/scripts_config.xml
and have
the personality script read it from there.
We will also add an entry to control whether the client should attempt to connect to a server, or just explore the space offline as in the previous stage of the tutorial.
The relevant changes to
tutorial/res/scripts_config.xml
are displayed
below:
... <server> <online> true </online> <!-- Whether the client actually connects to the server. --> <host> 10.40.3.23 </host> <!-- The server to connect to. Ideally we would allow this to be entered via an in-game GUI (or leverage the server discovery stuff) but for now we'll just hardcode it. --> </server> ...
Example
tutorial/res/scripts_config.xml
Note
If you are using multiple users on the same server machine, you
will need to specify the port as well as the IP address. The port for
the LoginApp can be found by inspecting the
loginapp/nubExternal/address
watcher value on the
Web Console. For example, if the IP address is 10.40.3.23
and it is on port 20013
, then put
10.40.3.23:20013
inside the <host>
tag.
You may need to update the port after restarting the server.
The next step is to implement the function call
initOnline
in the client personality script
tutorial/res/scripts/client/BWPersonality.py
and
switch between calling it and calling initOffline
based on the online option in
tutorial/res/scripts_config.xml
.
To achieve that, make the changes to
tutorial/res/scripts/client/BWPersonality.py
as
illustrated below.
... def init( scriptConfig, engineConfig, prefs ): if scriptConfig.readBool( "server/online" ): initOnline( scriptConfig ) else: initOffline( scriptConfig ) ... def initOnline( scriptConfig ): class LoginParams( object ): pass def onConnect( stage, step, err = "" ): pass # Connect to the server with an empty username and password. This works # because the server has been set up to allow logins for any user/pass. BigWorld.connect( scriptConfig.readString( "server/host" ), LoginParams(), onConnect )
Example
tutorial/res/scripts/client/BWPersonality.py
Notice that we no longer need to do client-side space creation, geometry mapping, or entity creation; these functions now happen on the server side. The client will automatically perform the necessary client-side actions based on the server-side game state.
The last line of initOffline
in the
personality script sets the camera to use first-person mode. We chose to
do this in the first part of the tutorial because we wanted to get a
client up and running as quickly and simply as possible, and using
first-person mode allowed us to ignore the issue of rendering the player
himself.
However, since we are now implementing a client-server game where multiple clients can log in and inhabit the same space, it will be helpful if they have models so that they can see each other!
We have provided a basic biped model in
res/characters/bipedgirl.model
, which we will use for
all Avatars. Edit the enterWorld
callback for the
Avatar
class in
tutorial/res/scripts/client/Avatar.py
as
follows:
... class Avatar( BigWorld.Entity ): def enterWorld( self ): # Set the position/movement filter to correspond to an avatar self.filter = BigWorld.AvatarFilter() # Load up the bipedgirl model self.model = BigWorld.Model( "characters/bipedgirl.model" ) ...
Example
tutorial/res/scripts/client/Avatar.py
The BigWorld server uses the file
for configuring options on the various server components. For a
comprehensive list of configuration options along with a detailed
description, see the Server Operations Guide's
section Server Configuration with bw.xml.
your_game
/res/server/bw.xml
Typically, the bw.xml
file
includes a BigWorld provided default configuration
file which contains recommended default values for all the available
configuration options. This is achived by using the
<parentFile> tag as follows:
<root> ... <parentFile> server/development_defaults.xml </parentFile>
You will notice that in the example above, the included file is
development_defaults.xml
. This file provides good
working defaults for a game development environment that will generate
more warnings and intentionally crash the server in certain circumstances
to ensure that critical issues are caught prior to the release of a game.
The development defaults file however is only a small file that modifies a
subset of values from the file
bigworld/res/server/production_defaults.xml
. The
production defaults file aims to provide a comprehensive set of options
and default values to be used for a game in a live production environment
and can be used as a reference point when searching for a specific
option.
While it is anticipated that the majority of configuration options will not need to be modified, if you need to change a value or are simply curious as to the purpose of an option, complete documentation for the BigWorld server configuration options can be found in the Server Operations Guide's section Server Configuration with bw.xml.
To get our basic game up and running, we need to set a few options to specify what entity type the player should be connected to once logged in, and to allow players to log in with unknown usernames (just for convenience while developing).
Save the following in
tutorial/res/server/bw.xml
:
<root> <parentFile> server/development_defaults.xml </parentFile> <billingSystem> <entityTypeForUnknownUsers> Avatar </entityTypeForUnknownUsers> <shouldAcceptUnknownUsers> true </shouldAcceptUnknownUsers> <shouldRememberUnknownUsers> false </shouldRememberUnknownUsers> </billingSystem> </root>
Example
tutorial/res/server/bw.xml
At this point of the tutorial, it is assumed that you have set up your Linux machine as described in the Server Installation Guide. In particular, this assumes you have installed BWMachined on your Linux machine and have installed the Web Console somewhere on the local network. For details on Web Console see the Server Operations Guide's section Cluster Administration Tools, in WebConsole).
Before we can start the server, we need to specify where the server
should get its binaries and resources from. This is a concept similar to
the paths.xml
files used by the client and
tools.
We firstly need to know the directory the game resources are located
on the Linux machine. If you have been developing the game resources on
your Windows machine and have shared them using the setup_win_dev script,
the resources are most likely located in
.
Check the directory where you believe the resources are located actually
contain the correct files. For example:
$HOME
/bigworld_windows_share
$ ls $HOME/bigworld_windows_share bigworld fantasydemo my_game readme.html rpm template tutorial
We now run the bw_configure script providing the location of the game resources we wish to use. This will differ slightly depending on the BigWorld Edition you are using.
$ bw_configure Game resource path [~/my_game/res]: ~/bigworld_windows_share/tutorial/res Writing to /home/fred/.bwmachined.conf succeeded Installation root : /opt/bigworld/current/server BigWorld resources: /opt/bigworld/current/server/res Game resources : /home/fred/bigworld_windows_share/tutorial/res
The contents of the file
[7] has now become:
$HOME
/.bwmachined.conf
# Generated by ./bw_configure /opt/bigworld/current/server;/home/fred/bigworld_windows_share/tutorial/res:/opt/bigworld/current/server/res
Example
$HOME
/.bwmachined.conf
This file can then be edited whenever required to update the resource paths as your game development proceeds.
$ bw_configure Installation root [~/mf]: ~/mf Game resource path [~/my_game/res]: ~/mf/tutorial/res Writing to /home/fred/.bwmachined.conf succeeded Installation root : /home/fred/mf BigWorld resources: /home/fred/mf/bigworld/res Game resources : /home/fred/mf/tutorial/res
The contents of the file
[8] has now become:
$HOME
/.bwmachined.conf
# Generated by ./bw_configure /opt/bigworld/current/server;/home/fred/bigworld_windows_share/tutorial/res:/opt/bigworld/current/server/res
Example
$HOME
/.bwmachined.conf
This file can then be edited whenever required to update the resource paths as your game development proceeds.
You can now use the WebConsole's ClusterControl module to start the server. You should see six active processes in the process listing. Once the server is up and running, run the client and you should be able to connect to the server and control a basic biped Avatar from a 3rd person perspective. Connect multiple clients and watch each other moving around.