August 29, 2004
The State pattern and game development ( IV and final )
Well, finally it's time to develop a full game.
This is how our microgame ( "Drops everywhere" ) will work. Drops keep popping on the screen, but we ensure that the number of drops on the screen is lower than a given number. We can remove a drop by clicking on it. And we have to maintain a low number of drops during a given time. If we succeed, we can play again, or finish the game. If we play again, the maximum number of drops will be lower, so we’ll have to click more of them in the same time. The game goes on and on until we decide to finish it, or until the number of drops is higher than the allowed number.
You can see a statechart diagram here.
The code is distributed in three layers. There’s a model, a view, and a controller, although it is not structured as a MVC. The controller listens to the events fired by the model, and notifies the model when there is an action in the view ( a button click, for instance ).
The initialization process of the game is the following: when the swf is loaded, an instance of DropsController is created. This object creates an instance of DropsWorld, registers itself as an event listener, and starts the state machine associated to the world. The class DropsWorld extends another class called EventSource( in the package net.designnation.events ), that provides the ability to fire events , and to register listeners to those events. The controller ( DropsController ) also creates an empty MovieClip, that will be the main “clock”.
So, when the world is created, we have to start its state machine:
public function initWorld( param: Object ) { this.stageMC = param.baseline; this.initBehaviour( ); var theClass: DropsWorld = this; this.base_MC.onEnterFrame = function( ) { theClass.doProcess( ); } this.mySMachine.startMachine( ); } private function doProcess( ) { this.BEngineVal.doProcess( ); }
So a new cycle of the state machine will be executed with every execution of the base_MC.onEnterFrame event.
We define the state machine here:
private function initBehaviour( ) { var initGame : State= new State( "initGame", new CallbackDecl( this, "initGameAction" ) ); var startGame : State= new State( "startGame", new CallbackDecl( this, "startGameAction" ) ); var createDrop : State= new State( "createDrop", new CallbackDecl( this, "createDropAction" ) ); var overDrops : State = new State( "overDrops", new CallbackDecl( this, "overDropsAction" ) ); var endOfTime : State= new State( "endOfTime", new CallbackDecl( this, "endOfTimeAction" ) ); var defeat : State= new State( "defeat", new CallbackDecl( this, "defeatAction" ) ); var victory : State= new State( "victory", new CallbackDecl( this, "victoryAction" ) ); var endOfGame : State= new State( "endOfGame", new CallbackDecl( this, "endOfGameAction" ) ); new Transition( "initGameToStartGame", initGame, startGame, new CallbackDecl( this, "initGameToStartGameEval" ) ); //---------------------------------------------------------- new Transition( "startGameToCreateDrop", startGame, createDrop, new CallbackDecl( this, "startGameToCreateDropEval" ) ); //---------------------------------------------------------- new Transition( "createDropToEndOfTime", createDrop, endOfTime, new CallbackDecl( this, "createDropToEndOfTimeEval" ) ); new Transition( "createDropToOverDrops", createDrop, overDrops, new CallbackDecl( this, "createBubbleToOverDropsEval" ) ); new Transition( "createDropToSelf", createDrop, createDrop, new CallbackDecl( this, "createDropToSelfEval" ) ); //---------------------------------------------------------- new Transition( "overDropsToDefeat", overDrops, defeat, new CallbackDecl( this, "overDropsToDefeatEval" ) ); //----------------------------------------------------------- new Transition( "endOfTimeToVictory", endOfTime, victory, new CallbackDecl( this, "endOfTimeToVictoryEval" ) ); new Transition( "endOfTimeToStartGame", endOfTime, startGame, new CallbackDecl( this, "endOfTimeToStartGameEval" ) ); //------------------------------------------------------------- new Transition( "defeatToEndOfGame", defeat, endOfGame, new CallbackDecl( this, "defeatToEndOfGameEval" ) ); //------------------------------------------------------------- new Transition( "victoryToEndOfGame", victory, endOfGame, new CallbackDecl( this, "victoryToEndOfGameEval" ) ); this.mySMachine.resetToInit( initGame ); }
Once the states and the transitions are defined, we must implement the callbacks needed.
The class that we use to manage the drops is a very light controller. It also extends EventSource, so it will be able to fire an event when the drop is clicked. I know the way I’ve implemented this functionallity can start an endless discussion, but I’ve decided that a drop is a drop, not a MovieClip, so the MovieClip with the drop graphic is aggregated to the Drop class, and the Drop class does not extend MovieClip.
I’d also like to say that obiously this is not the final implementation. A lot of the DropsWorld code should be common for any game that we develop, so it will be wise to put that common functionallity on a base class, and make DropsWorld extend that class. The game itself is not completely finished ( there’s an obvious lack of feedback ), but, in general, I believe this example can be considered as a start point. And, for your information, the development time has been of about an hour and a half.
Well, the final result is here:
And the source code can be downloaded here ( don’t forget to set your classpath ).
Packages:
net.designnation.behaviours
net.designnation.data
net.designnation.events
net.designnation.physics
net.designnation.PoppingDrops ( game classpath )
I’d also like to thank Celia Carracedo for the game graphics.
And, finally, a disclaimer. There are some days when I find really dificult to express my thoughts in english. Today was one of them.
Posted by Cesar Tardaguila Date: August 29, 2004 08:36 PM