SFS2X Docs / ExamplesFlash / spacewar-p1
The SpaceWar example is a tribute to to the homonymous game developed in the 60s, one of the earliest computer games in history! The purpose of this example is to showcase the capabilities of SmartFoxServer 2X MMO Rooms in a realtime game featuring flying starships, weapon shots, collisions... with the possibility of having thousands of them in the same Room.
The differences between regular Rooms and MMO Rooms have already been discussed in the overview of the Simple MMO World example, where the concepts of Area of Interest (AoI in short) and PROXIMITY_LIST_UPDATE event have been described in detail.
In addition to those features, this game shows the usage of MMOItems. An MMOItem is an object representing a non-player entity inside an MMO Room. Not to be confused with non-player characters (NPCs, which are treated just like regular users) MMOItems can be used as bonuses, triggers, bullets, etc. Such items are subject to the same rules of visibility of the players in the MMORoom (the AoI in other words) and from a client perspective their nearby existence is notified by the same PROXIMITY_LIST_UPDATE event mentioned before.
In this example MMOItems are used to represent the weapon shots moving in space.
The game mechanics are quite simple: after the connection to the server has been established, each player selects his starship among three types with different characteristics (maximum speed, maneuverability) and joins a Room representing a solar system. All starships are spawned in a small region of empty space (so that players can see each other immediately, to facilitate the testing) and can be controlled using the keyboard: left and rigth arrow keys to rotate the starship, up arrow key to activate the thruster and space key to fire the weapon.
Being a proof of concept, the game doesn't have a true objective: just fly around and try to hit your opponents struggling against inertia. In the last paragraph of this tutorial we will examine a number of additions and improvements to turn this example into a fully playable game featuring opponents destruction, planets and gravity, multiple weapons and much more.
You should read the comments to methods and properties in the source code for additional informations and possible code optimizations.
» Table of contents
- Basic concepts
- The game flow
- Controlling the starship
- Firing the weapon
- What next?
- More resources
|Page 1 of 3||Next »|
» Running the example
In order to run the example follow these steps:
- copy the SpaceWar folder from the /deploy/extensions folder to your SFS2X installation folder, under /SFS2X/extensions;
- copy the SpaceWar.zone.xml file (containing the Zone configuration) from the /deploy/zones folder to your SFS2X installation folder, under /SFS2X/zones;
- start SmartFoxServer 2X (v2.8 or later required);
- make sure the client will connect to the right IP address by editing the <ip> entry in the sfs-config.xml file in the /deploy/client folder;
- open the /deploy/client/SpaceWar.html file in a browser.
» Source code setup
The client-side assets are contained in the /source/client folder: create a new Flash Builder project, copy the content of the /source/client/src folder to your project's source folder, the /source/client/media folder in the project's root folder and link the libraries contained in the /source/client/libs folder.
The game features a server-side Extension, as described later on. In order to access its code, create and setup a new project in your Java IDE of choice as described in the Writing a basic Extension document. Copy the content of the /source/server/src folder to your project's source folder.
This example makes use of Starling, an open source game engine for Flash which leverages the rendering capabilities of the "Stage3D" technology made available by Adobe starting from version 11 of the Flash Player. With respect to the conventional approach used by Flash in the past, this technology provides an incredible boost in rendering performance by taking advantage of the hardware accelleration capabilities of modern computer's GPUs.
In addition, the Starling library mimics the conventional Flash display list architecture, making it quite easy to switch to this new approach in Flash games development.
If you are not yet familiar with Starling, we strongly recommend that you take a look at its documentation, and in particular to the great Starting with Starling video tutorials by Hemanth Sharma. This series will guide you in the process of creating a full game leveraging the Starling API: we could say "from zero to Hungry Hero"!...
Our example follows Hemanth's approach to spritesheets creation and usage, screens navigation, background parallax and text/font management. The Flex environment setup description is also very important to be able to compile the project's source code.
As we won't go into details about those topics in this tutorial, concentrating our attention on the multiplayer part of the game, again we recomment that you check the linked videos before proceeding.
Maybe the most complex topic when developing a realtime multiplayer game like this example, it's to deal with the clients synchronization. Each game type has its own strategies and techniques which include movement prediction, interpolation, latency compensation and what more.
When developing a strategy for synchronization we should always aim at minimizing the amount of data exchanged between the clients and the server. The reason is not to overwhelm the client and the server with messages to be processed, allowing more concurrent users on a single server instance and reducing hosting costs (less server instances, less CPU power per instance, less consumed bandwidth). For this purpose the mechanics of each game must be analyzed in depth to choose the best approach depending on its characteristics.
Our SpaceWar game is a simplified space simulation in which starships and weapons move on an immutable trajectory. This trajectory is a straight line (but the same concepts also apply in case of curved trajectories, for example due to a gravity effect) which, in the case of starships, can be modified by the engine thrust or a collision with a weapon shot, which both alter the ship velocity (speed and direction). Due to the very simple maths involved (essentially we have to sum vectors representing the speed), we decided that the best approach was to run the same simulation routine on both the server and the clients on a time basis: we set a framerate of 25 fps in the Flash client and a corresponding scheduled task (running every 1000 / 25 = 40 milliseconds) on the server, and execute the same simulation logic in each frame.
With this approach, the server continuously updates the position of all entities (starships and weapon shots) existing in the MMORoom and the same does each client on its own (of course taking into account the "known" entities only — in other words those falling within the user's Area of Interest). If no alterations to the state of such entities happen, the server and client simulations keep running in parallel without the need to exchange data between them (for example the clients declaring their position to the server continuously). We reduced the data exchanged between the server and the clients to zero (PROXIMITY_LIST_UPDATE event apart).
Of course this is not realistic, because the players interact with the game rotating their starships, turning the thruster on and firing shots: in other words altering the starships trajectories (not to mention possible computer slowdowns, in particular on the client side, which can make the two simulations diverge even if the player doesn't interact with the game).
In order to keep the simulations in-synch and still exchange a small amount of data, we promoted the server simulation to "master" (in other words we have an authoritative server): all actions causing a trajectory change, for example the player pressing or releasing the thrust key, must be communicated to the server, which updates the entities state accordingly and sends the updates to the clients which in turn reset their local simulations, ensuring an high degree of synchronization.
» The lag problem
The client synchronization mechanism described above works perfectly in a local scenario, where no latency exist between the server and the client. But what if we introduce the communication lag? Let's take a look at the following picture:
- The starship is moving along direction d0 at a velocity represented by the blue vector.
- At time t0 the player presses the thrust key after a 90 degrees rotation of the starship, sending a request to the server only (no changes in client simulation).
- At time t1 the server receives the request and processes it, summing the current velocity vector and the thrust vector. Now the resulting velocity direction is d1 and the new vector is sent to the client.
- At time t2 the client receives the update, but in the meanwhile (t2–t1) the starship in the client simulation moved to this new position: setting the new velocity now places the starship on the resulting d2 direction, making it out-of-synch with respect to the server (on the server the starship kept moving along direction d1in fact ).
The t1–t0 latency isn't very important and in any case we can think about some tricks to reduce the lag perceived by the player between the key press and the action being executed (we will discuss this again later in this document).
The t2–t1 lag is instead what causes the client and server simulations to diverge, and we have to take actions to compensate it. The solution adopted in our example is based on measuring the mean lag between the client and the server using the built-in feature of the SFS2X API. Given this value (which approximately indicates the t2–t1 time span), the position the starship had at time t1 and the new velocity vector (both saved in User Variables, as explained later), it is possible to calculate the actual position of the starship on the server side and reset the client simulation to align it with the server one.
The final result is pretty good, even in case of extreme (for a realtime game) latencies of 200 milliseconds or so.
In order to be able to fine tune the behavior of the starship types and weapons available in the game without the need to recompile the server side code each time, we created an external configuration file (SpaceWar.cfg) saved in the server side Extension folder.
This is a text file based on the popular JSON format, which can be easily converted to a SFSOject by means of the SFSObject.newFromJsonData method. This grants immediate access within the game Extension and easy transmission to the clients (see next paragraph). The method accepts a string to which we read the configuration file using the org.apache.commons.io.FileUtils class.
The configuration contains:
- a list of starships (three in total) for which maximum speed, rotation speed, acceleration, and used weapon can be set;
- a list of weapons (actually containing only one) which can be referenced by each starship and whose speed, hit radius, impact force and duration before self-destruction can be set.
In a full featured version of this game we might think of adding more data to this configuration, as mentioned in the "What next?" paragraph at the end of this tutorial.
On the server side this example makes use of both a Zone Extension and a Room Extension.
The Zone Extension takes care of loading the starships and weapons configuration described above (see the setupGame method in the main Extension class). The configuration is made available to the Room Extension by means of the handleInternalMessage method. We have to use this helper method because the two Extensions are loaded by different class loaders, and the only way to make them communicate is through this interface. More informations can be found in the Advanced Extension topics document.
Also, the Zone Extension sends the configuration to clients by means of a custom login handler. Usually the custom login is used to check the user credentials; in this case we don't need this: we just want to leverage the possibility of sending custom data to the client in the login response. This way we can avoid an additional specific request to be sent by the client to retrieve the configuration.
The Room Extension contains the core logic of the game, processing all events and client requests and running the master simulation mentioned above and described later in greater detail. Specifically, the SpaceWarRoomExtension class takes care of receiving the requests from and sending the responses to the clients, while the simulation logic is contained in the Game class. In this way we put in place a separation of duties which makes the code much more friendly to maintain.
This example features just one MMORoom representing the Sol solar system, even if actually there's no star or planets... just an empty space (again, see the "What next?" paragraph). The Room is defined in the Zone configuration xml file and in particular it contains the following configuration parameters:
<mmoSettings> <isActive>true</isActive> <defaultAOI>900,750,0</defaultAOI> <lowerMapLimit></lowerMapLimit> <higherMapLimit></higherMapLimit> <userMaxLimboSeconds>30</userMaxLimboSeconds> <proximityListUpdateMillis>20</proximityListUpdateMillis> <sendAOIEntryPoint>false</sendAOIEntryPoint> </mmoSettings>
These parameters set the properties of the MMORoom. Let's analyze them, with the exception of the isActive flag (which of course must be set to true to make this Room an MMORoom) and userMaxLimboSeconds (just check the tooltip in the AdminTool for its description).
In short, the Area of Interest determines the maximum distance at which users see each other, where "see" means that they are notified of each other's presence and can receive/send events form/to each other.
How to determine the size of the AoI? First of all we have to take into account the game characteristics: the stage size is 1000x800 pixels (the black area in the picture below) and the game features a scrolling background that gets triggered when the distance of user starship from the stage border is less than or equal to the 15% of the stage width/height (the gray rectangle). Doing the math we have that the user starship can reach a maximum horizontal distance from stage borders of 1000 - 15% = 850 pixels, and a maximum vertical distance of 800 - 15% = 680 pixels.
This is the theoretical minimum size of the AoI (the red rectangle): a smaller size would often cause the opponents' starships to pop in instead of entering the stage from outside its limits in a natural way. But if 850x680 pixels is the minimum AoI, why in the settings above this is set to 900x750 pixels?
The answer is simple: we have to take the latency into account! In fact when the server detects that an opponent (or a weapon shot, represented by an MMOItem) entered the player's Area of Interest and sends him the PROXIMITY_LIST_UPDATE event to notify it, the message takes a variable amount of time to be delivered. During this time period both the player and his opponent keep moving (in the worst case scenario one towards each other). When at last he receives the event and his client does the math to synchronize its simulation with the actual simulation on the server side (as discussed in the "Clients synchronization" paragraph), it is likely that he will see the opponent pop in his viewport, as appearing from nowhere.
In order to avoid this behavior (or at least minimize it) we can increase the theoretical AoI's size a little. The actual amount should be a compromise which takes into account the estimated lag, the starships' relative speed in the worst case scenario and the rate at which proximity updates are sent.
In this example we didn't set physical limits to the MMORoom. Starships can travel indefinitely in any direction. The system is capable of handling this scenario seamlessly, even if usually it is not recommended because malicious users could try to exhaust the system memory by moving their starships to extreme coordinates. Not very likely, but... who knows?
Proximity list update rate
The proximityListUpdateMillis parameter sets the rate at which the system sends the PROXIMITY_LIST_UPDATE event to clients to notify new users or MMOItems entering/leaving the player's Area of Interest. The lower the value is, the more the system is stressed, especially when thousands of users are inside the same MMORoom.
So, from the system resources point of view, the best approach would be an higher number. The other side of the coin is that this increases the overall latency, because the server doesn't send the update immediatel: more milliseconds are added to the existing network lag we already discussed.
Let's consider the case in which user A is moving towards user B (who isn't moving) and suddenly he enters B's Area of Interest. If we could measure the added latency, we would just need to compensate it in the client simulation just like we compensate the network lag, and maybe increase the size of the AoI to avoid starship A to pop in user B's viewport (and viceversa).
The problem is that we can't measure it, because we are always in the middle of two extremes: (1) A enters B's AoI an instant before the next proximity update si scheduled; (2) this happens an instant after the previous update was sent. In case (1) there's no additional latency because the event is fired immediately, so we just have to deal with the network lag; in case (2) the full value set in the proximityListUpdateMillis parameter is added to the network lag.
Unfortunately we are always stuck somewhere in the middle of the two situations because we don't know when the update event will be triggered by the scheduler, so we don't know the actual value of added latency to compensate; this, in the end, makes the server and client simulations go out-of-synch. As described later on, the simulation is quite resilient to out-of-synch conditions, because as soon as the trajectory of starship A changes, a new position update is sent (not based on the proximity update event anymore, because A didn't leave B's AoI) and the master and client simulations synchronization is restored. Still this would lead to graphical artifacts, like seeing the starship "jump" back and forth.
To avoid the issue we just described, we set the proximityListUpdateMillis value to 20 milliseconds and, when the proximity update is involved, we always compensate the added latency including half of this value in the math. Of course this will still lead to small errors in the simulation, but the subsequent corrections won't be noticeable.
As the configuration shows, the entry points of the opponents' starships or weapon shots is not sent in the PROXIMITY_LIST_UPDATE event. The reason is that we don't need this information on the client side, so we can save some bytes.
In fact, while the entry points could be needed in other type of games (see the Simple MMO World example), in this case we always reset the client simulation based on the starship's position and velocity vector sent by the server and the time passed since the update message was sent (network lag + half proximityListUpdateMillis value).
|Page 1 of 3||Next »|