Creating Your Own Game
The first thing you should do is read through the code for the included
games as that will give you a good idea of how the toolkit works under
normal circumstances. At some point you will probably also want to read
the documentation on the framework used to send data over the network,
known as
distributed objects.
You can get by without it but we may use a few terms in this document that
are explained in the distributed objects documentation.
As a starting point, we have also provided a game which is nothing
more than a template that you can copy and change the names to start
yourself off with all the necessary bits in place. This template game is
located in the sample
directory of the games archive.
It should be noted that the architecture of the sample game is not the only way to make games with the toolkit. However, much of this structure is useful for nearly all simple multiplayer games and it is certainly a good place to start until you are more familiar with the functionality available. That said, let us look at each of the elements of the sample game in turn:
SampleObject
This class extends
GameObject
and defines the data that will be shared between the clients and server
when playing your game. The standard game object defines a few pieces of
information which are used to manage the flow of the game.
GameObject.state
AWAITING_PLAYERS
to
IN_PLAY
and then to either GAME_OVER
or
CANCELLED
. As the events arrive indicating a change in this
attribute, the game manager and controller call methods appropriate to
each state (which will become apparent as you read about that below).
GameObject.players
GameObject.roundId
Additional information specific to your game would also be contained in this object. For example, one might have an object representing the board and a distributed set containing the pieces that are on the board. Perhaps an array defining the scores for each player. Again, refer to the included games for ideas and examples of how to structure things.
SampleManager
This class extends
GameManager
and is the main entity that lives on the server and manages your game's
state. It interacts with the players by making changes to the game object
and it manages the flow of the game. The following methods are called as
the game procedes through its normal lifetime:
startGame()
gameWillStart()
gameDidStart()
TurnGameManager
(which we don't document here but is useful for turn-based games) makes
use of this method to start the turn-taking process.
endGame()
gameWillEnd()
GameObject.GAME_OVER
state. In general, this method is used less often than
gameDidEnd()
(documented next) as that's where you would
unregister timers and do whatever other cleanup that might need to be done
after the game ends. However, for completeness and in the off chance that
something needs to be done before the game actually ends, this method exists.
gameDidEnd()
It should be noted that it is possible for gameWillEnd()
and gameDidEnd()
to be called even though your code did not
call endGame()
explicitly. If all players involved in the
game leave the game room, the game will be cancelled (it will transition
to the CANCELLED
state instead of the GAME_OVER
state) and the normal game-ending callbacks will be called to clean up
after the game. Your game ending code should check the
GameObject.state
when being called and avoid doing things
like computing scores and whatnot if the game was cancelled.
resetGame()
gameWillStart()
and gameDidStart()
will be
called as in a normal startup, but gameWillEnd()
and
gameDidEnd()
will not be called).
gameWillReset()
SampleController
This class extends
GameController
and manages the flow of the game on the client. It acts as the controller
in the standard model/view/controller paradigm where the model in this
case is the SampleObject
and the view will be explained
momentarily. As the controller, it reacts to changes in the model and to
user input. Like in the manager, there are calldown methods provided to
allow the controller to take action when the game state changes:
gameDidStart()
gameDidEnd()
gameWasCancelled()
resetGame()
gameDidStart
being called
again). In such cases, the client can call resetGame()
which
will result in a call to gameWillReset()
which they can
override and clear things out in preparation for the new game.resetGame()
method does not actually send
a request to the server to reset the game. It is assumed that the server
either knows the game will reset through the normal operation of the game
or that the client explicitly requested a reset (through some
game-specific mechanism) prior to calling reset game. For example, perhaps
a client submits a request for each move, then it computes the available
moves for the next turn and if the player has run out of moves, the game
is reset (rather than ended) and they start over. The client would submit
a move to the server in the normal course of play, and the game manager
would know after it received that move that the player had no moves left
and that the game needed to be reset, so it would call
resetGame()
on the server. Meanwhile, the client, after
submitting its move, also determines that there are no moves left to play,
so it calls resetGame()
on the client which results in the
interface being reset immediately rather than waiting for a round-trip to
the server to hear what it already knows.
gameWillReset()
The controller also handles input from the user which is generally first processed by the view and converted into actions that are meaningful in the context of the game. For example, the view might allow the player to place pieces on a board, in which case it would process mouse movement and mouse click events and eventually communicate to the controller a request like "place piece P at coordinates C".
Most likely the view will simply maintain a reference to the
controller and call methods on it, but a mechanism is also provided to
package up those requests and deliver them as events on the AWT thread to
be handled by the controller in the same stream as the network events
(which are also dispatched on the AWT thread). This is particularly useful
if the event comes as a result of, say, a timer expiring rather than some
underlying AWT event like a mouse click. To find out more about this
mechanism take a look at
Controller
and specifically
handleAction()
.
SamplePanel
This panel contains the various interface elements used by the game. It
usually doesn't do much except combine all the needed elements into a
single display that can be easily instantiated by the controller.
SampleBoardView
This interface element does the main work of displaying the game and
collecting user input and communicating it in a meaningful way to the
controller. In the sample game, there's not much to do, but look at the
code for the other included games to see the sorts of things done by the
view.
The board view, like any other user interface element that wishes to
display distributed object state associated with the game "room",
implements
PlaceView
.
By implementing this interface, it will automatically be notified when the
client has "entered" the game room and later when it has left. That is
accomplished with the following methods:
willEnterPlace()
PlaceObject
reference but it is indeed your game object and
can be casted appropriately), and is a good place to add listeners to the
game object and initialize the user interface based on information
therein.
Note: if the view wants to respond to changes in the game
state, there are a couple of options. It might add itself as an
AttributeChangeListener
and respond to changes to the
GameObject.state
attribute, or the game controller can call down to the view to let it know
when the game starts or ends or whatever it needs to communicate. It is
pretty likely that the view will need to listen to the game object to hear
about game-specific changes, so additional handling of changes to the
state
attribute are pretty easy to incorporate.
didLeavePlace()
sample.xml
In addition to your game code, you will need to create a game definition
file which is what the Game Gardens system will use to match-make and
start your game. Here is the sample configuration:
<?xml version="1.0" standalone="yes"?> <game> <!-- the string identifier for this game; this is used to name our jar --> <!-- file and to name other internal bits --> <ident>sample</ident> <!-- The controller and manager used for our game. --> <controller>com.whomever.sample.client.SampleController</controller> <manager>com.whomever.sample.server.SampleManager</manager> <!-- Herein we define how the game is matchmade and configured. --> <match type="table"> <!-- Properties configure the match maker, in this case: table. --> <min_seats>2</min_seats> <max_seats>4</max_seats> <start_seats>2</start_seats> </match> <!-- Parameters define values that the user can customize when --> <!-- creating a game and which are passed on to the game itself --> <!-- to customize the gameplay. --> <params> <range ident="board_size" minimum="16" maximum="48" start="32"/> <choice ident="rules" choices="standard,hand_of_three" start="standard"/> <toggle ident="monkeys" start="false"/> </params> </game>
It is mainly self-explanatory with the items in bold being the things
that absolutely must be customized. The match-making configuration also
requires a bit more explanation. Each entry in the
<params>
section provides a configurable parameter to
the person creating your game. Three types of parameters are currently
provided:
range
: allows an integer value to be chosen from a
specified range.
choice
: allows a single choice to be selected from a list
of choices.
toggle
: a simple on/off boolean toggle.
The values chosen by the player during the match-making phase are
communicated to the game code via the ToyBoxGameConfig
class. Here's an excerpt from SkirmishManager
to show how
this is used:
// documentation inherited protected void gameWillStart () { super.gameWillStart(); // get a casted reference to our game configuration _skonfig = (ToyBoxGameConfig)_config; // generate the game board int size = (Integer)_skonfig.params.get("board_size"); int featureDensity = (Integer)_skonfig.params.get("feature_density"); _skobj.setBoard(SkirmishBoard.generateBoard( size, size, featureDensity)); // start the vessels in the center of the "board" int dx = size/2-3, dy = size/2-3; // ... }
As you can see, the configuration values will never be null. They will either be the default value provided in your game configuration or some customized value provided by the user when configuring your game. This allows you to avoid duplicating the default values from your game configuration in your game manager.
sample.properties
The Narya system provides a mechanism for localizing your game that is
based on Sun's localization facilities. It is not a requirement that you
use this system except to provide translations for your game configuration
parameters.
This is accomplished by adding entries to the properties file:
rsrc/i18n/sample.properties
. The configuration shown above
would use the following translations:
m.range_board_size = Board size: m.choice_rules = Rules: m.choice_standard = Standard m.choice_hand_of_three = Hand of three m.toggle_monkeys = Include Monkeys?
A forthcoming article on how to actually use the localization services will explain where to put localized versions of your properties files and how to access those translations from within the game. The sample games make use of the localization services so in the meanwhile that's a good place to look.
Running your game
If you develop your game in the same directory as the sample games, you
can use the provided scripts to run your game during testing. In the
following examples sample
should be replaced with the
identifier you choose for your game.
# Running the server ant server # Running the client (must be done after the server is started and you can # run as many as you like as long as they have different usernames) ant -Dusername=george client
Uploading to Game Gardens
Once you have something up and running that you want to share with the
world, you can upload your game project on the
create a game page. It should
be pretty self-explanatory but you need to provide a name, some
description, your game.xml
file and your game's jar file and
you should be up and running. If you have problems getting your game
running on the site even though it works when you run it in the
development environment, check the forums or shoot an email to
gardens@threerings.net and
we'll try to work out the kinks.
Happy Gardening
That's about the size of it. Be sure to check out the message boards if you have questions or want to talk about game ideas and implementation details.