[EDIT: latest source and demo is here http://www.java-gaming.org/index.php/topic,18019.0.html, in the topic ‘Games Showcase’ under title ‘Multiplayer top-down view shooter’.]
Hi Everyone,
I’ve been working on this server-client network game framework for ages and I’ve decided that it’s at a stage where I’d like you to take a look and let me know what you think.
Of course I want to make a MMOG like everyone else :), and eventually I want this framework to be that good, but in the meantime I think that it could do LAN and maybe small internet games pretty well. At the very least it should demonstrate some different ways of dealing with latency (lag) between computers.
I’ve made 2 webstart demos with GUI options 8) that you can adjust to test it out. Once you set up a server game, you should be able to set up a client game and join in whether the client game is on the same computer or on another machine connected on the internet or LAN: http://www.freewebs.com/commanderkeith/sydneyengine/SydneyEngineDemo.jnlp
I’ve added tool-tips to the labels so they should provide some info on what you’re adjusting. Remeber to click the update button after adjusting stuff. The keys to move are left, right, up & down as well as page up and down to zoom.
I haven’t made a proper game with it yet, but I did convert Kev Glass’s great Phys2D API to use the framework and made a little 2D top-down view demo where you’re a ship in space and you can crash into other players if they join your game. There are 2 things about the Phys2D API that mean that my framework doesn’t work well with it, but both are solvable[1]. Here’s the webstart: http://www.freewebs.com/commanderkeith/sydneyengine/physics/SydneyEngineDemoPhysics.jnlp
http://www.freewebs.com/commanderkeith/sydneyengine/screenShot.jpg
There are 3 parts to the framework, which for now I’ve called the sydney engine:
-
The network package which has interfaces and classes that are just meant to send byte arrays. Right now it uses super-stripped down NIO TCP communication.
-
The ‘SuperSerializable’ streams which just serialize (turn objects into bytes) and deserialize the game objects that are sent between clients and server. They’re as fast as regular java.io.ObjectIn/OutputStreams but they update existing objects instead of replacing them, which doesn’t cause garbage-collector slow-downs.
-
The engine package, which has interfaces and classes that manage all of the threads, messages, and the game world updates. The engine has a couple of threads:
- A thread that listens for incoming connections (ConnectionListener) and spawns other threads (ConnectionWelcomer) to join the client to the server game.
- A thread that does the game loop (ServerController or ClientController), timing the game world updates and rendering.
- A thread that queues messages to be sent to the server from the client or vice versa.
- For the ServerController, a thread that waits for incoming messages from clients so that they can be relayed to other clients straight away.
The framework’s aim is to efficiently join and remove players into the game, send player events to all other players and also to synchronise game worlds between all machines by sending the server’s game world to all clients. This sounds easy, but it was hard since you need to manage threads without too much synchronization block code, and you must guarantee that messages do not go missing, double-up or get out of order.
But the other job is in the client code which needs to hide the latency between machines by appearing smooth. There are 2 methods that I’ve been working on to mitigate network latency/lag, and I’ll just cut and paste the explanation from the lousy javadoc I’ve been working on :P. Note that UserEvents are just key presses and other client events.
// For UserEvent.eventTimingStrategy == ORIGINAL_TIME_SYNC,
// userEvent.getTimeStamp() returns the original gameWorld time that the UserEvent occurred on the original machine. This strategy will mean that everything happens at the same time on each computer (as soon as the computer knows about it), at the expense of smoothness. The clients will appear to ‘jump’ or ‘teleport’ around. Smoothness can be improved by setting a large delayBeforeEventAppliedSeconds for each player, however this decreases responsiveness for those players with large delayBeforeEventAppliedSeconds. The physics demo uses this approach.
// For eventTimingStrategy == ORIGINAL_TIME_UNSYNC,
// userEvent.getTimeStamp() returns the original gameWorld time that the UserEvent occured on the original machine, plus a certain fixed delay which can be different for each player. This strategy ensures that there is the same time between UserEvents (from the same player) on all VM’s whether client or server. But there will be ‘alternate realities’ since the client and server see things happening at different times. For example, in a racing game if the client and server both accelerate at the same time (and at the same rate), then each will think he is in winning when in actual fact they are tying. The other simple demo uses this technique, but I’ve made it so that it can switch between eventTimingStrategies.
Apart from these, I have heard of averaging techniques where, for example in the racing scenario above, a lagged player’s car will accelerate on other computers more than is normal so that the lagged player will catch up to where it would be if there was no lag. I haven’t implemented this in any demo yet.
I’m going to put the source up real soon to get some feedback for improvements, I just need to clean it up a bit. The current (sparse) javadoc is here: http://www.freewebs.com/commanderkeith/sydneyengine/javadoc.zip
Many thanks to the people on this forum for their help and encouragement with things along the way :). Thanks to Kev Glass and Endolf for their network library code and Kev’s Phys2D API, Adam Martin for the ClassLocator code, and Jeff Kesselman and Thijs for the pointers to use SNTP for finding clock differences. 8)
Keith
[1] You’ll notice that the physics game crashes after more than 15 minutes since too many objects are made in the physics calculations which max’s out the unique int hashCode that every object is given. Also, if the lag is set too high then client smoothness deteriorates since the physics world does not perfectly reverse positive time updates with negative time updates.
An important limitation: can’t have more than 15 clients join the game world. That is, after there have been 15 join requests, the game could go berserk since there are only 2^4 ‘VM numbers’ numbers that are set aside.