Search FlashMOG Docs Only

Shooter Tutorial

The purpose of this tutorial is to show how FlashMOG can be used to create a multiplayer game. The result is only a very rudimentary 2-player game (inspired by the "Boot Hill" -- the first video game I ever saw), but should serve as a decent starting point for creating your own multiplayer games. This tutorial assumes that you have Flash CS4 or later and that you already have FlashMOG server configured and running with the test movies. If you don't know how, please refer to the installation documentation or try the tutorial on Setting up FlashMOG on a Cloud Server.

NOTE: You absolutely must have the basic FlashMOG Server set up and running and will need to know the host that your FlashMOG Server is bound to because you'll need to configure the client to connect to that host. Seriously, if you haven't set up the server, figure out how to do that first.

The basic approach for this tutorial is that I've done all the hard work for you. I'm going to give you the files and explain how to configure them, then I'll explain how they work.

Set up the server

In order to get the server set up for this game, you are going to need to add a service class and restart your server.

If your server is currently running, stop it.

Since FlashMOG loads and instantiates all of its service classes at startup, we have to restart it to load the new service class we'll be adding.

If you have read the docs on setting up FlashMOG server, you should know how to stop the server. You don't need to stop the crossdomain server, just FlashMOG Server. Do so now.

Download the GameService class and put it in your services folder

If written a basic service class for FlashMOG server that provides methods relevant to a multiplayer game. It's not going to support a million users or anything, but it works for this little game.

Download and extract it somewhere. It should contain only one file–GameService.php. Put that file in the services directory in your FlashMOG Server directory:

Start FlashMOG Server

If you have read the docs on setting up FlashMOG server, you should also know how to start the server. You don't need to restart the crossdomain server if it's already running. You just need to restart FlashMOG Server. Do so now.

Check the FlashMOG Server log file. Assuming you are logged in to your server using SSH and your current working directory is the root of the server directory, you can use tail:

FlashMOG:~# tail data/server_log.log

You should see these two lines somewhere in the output:

 including GameService.php
 instantiating class  GameService

Those two lines indicate that the GameService class has been found an instantiated.

Set up the game client

I wrote a simple game client and I'm letting you download it. You'll just need to change which host it tries to connect to.

Download the client archive and extract it

Download to a computer with Flash CS4 or later installed on it. Unzip the file. It contains the following:

  • shooter.fla - This contains graphics and code. Most of the work to create the game is in here.
  • Corey O'Neil's collision detection class - Used to detect collisions between bullets and players. You can visit Corey's website for more info.
  • Senocular's KeyObject class - Used to detect keystrokes so you can control your player. I believe this page is the primary link for it.
  • FlashMOG 0.3.1 client source files - The unadulterated, unchanged, identical client source from FlashMOG 0.3.1 plus a couple of extra classes to create a modal dialog.

[OPTIONAL] Download and install the font 'GoldRush'

When I created this game, I chose to use an unusual font, GoldRush, which I downloaded for free. I think you can download it here. If not, you can probably find it by googling "goldrush font download". Download that font and install it. If you don't know how to install fonts, I'm sorry. That's beyond the scope of this tutorial.

If you don't want to download the font, you'll need to make a change in the Flash Movie in the next section.

Open shooter.fla and make some changes

If you already have Flash open and just installed the GoldRush font. Restart the Flash application or you may not have access to the newly installed font.

Open the file shooter.fla in flash and goto the first frame in the actions layer.

Open the actionscript window and change this line. Replace '' with the FMOG_HOST value you used when you set up FlashMOG Server. If you are using some port other than 1234, change that too.

// define a new Service
var gameService:FlashMOGService = new FlashMOGService('', 1234, 'GameService');

OPTIONAL: If you chose not to install the GoldRush font, then you'll want to edit the font object OldTymeFont in the library of shooter.fla to some font that is actually installed on your machine. If you do have the GoldRush font installed, you don't need to edit this font object.


Double-click it in the library or right-click (windows) or ctrl-click it (mac) to edit the font object's properties. Change the font from GoldRush to something boring like Arial.


Publish the flash movie and upload to your web server

Publish the movie. This should result in shooter.html and shooter.swf being created in the same directory as shooter.fla. Put shooter.html and shooter.swf in your public_html directory on your web server. It doesn't need to be in any particular location in the web root, you just need to be able to find it on your web server. If it is in the web root and your server address is, then it's probably

Test the game

Load the game URL in a browser. Once the flash movie loads in your browser, it will attempt to connect to the server -- first on port 843 to find a policy file, and then to whatever port you specified. Since it's a two-player game, you may need to recruit a friend to help. Or, if you don't have any friends, you can open a second browser window. The game should be pretty self-explanatory.

How it works

I'll try to describe briefly here how the code works overall and then drill down frame by frame.


There are 4 layers. The 'trace' layer isn't used. I had a text box on it for convenient trace output at one point. There are six frames, each of which has a frame label on the 'frameLabels' layer.

The library has a button component, a list component, a font object, some sounds, and some cowboy-related movie clips and images.

The cowboy movie is perhaps the most important one. It has six frames of its own: standing, walking, shootUp, shootStraight, shootDown, and shot. It has layers for frame labels, actions, torso, legs, and an 'ouch' layer which has the splash of color. The only action on each action frame is stop(); The cowboy movie is sent to each if its various frames by the game logic in the root timeline.

I'll now discuss the frames in the root timeline and how they work.

Frame 1, labeled "connect"

This frame connects to the service and sets up some event listeners for certain service events. The movie starts here and stays on this frame until the service fires the CONNECT event. The only elements on the page are a text message:

frame 1 waiting to connect message

Here is the actionscript. Note the various gotoAndStop commands connected to various service events:

// stop the movie on this frame

var oldeTymeFont:Font = new OldeTymeFont();

// define a var to hold error messages
var lastErrorMessage:String = '';

// import FlashMOG
import net.flashmog.FlashMOGService;

// define a new Service
var gameService:FlashMOGService = new FlashMOGService('', 1999, 'GameService');

// add event listeners for the various conditions

// when the service connects
gameService.addEventListener(Event.CONNECT, gameConnect);
function gameConnect(evt:Event):void {
	trace('gameConnect running!  GameService has connected.');

// when the service closes...this can be triggered when the server shuts down
// or the socket connection is lost for some reason
gameService.addEventListener(Event.CLOSE, connectionClose);
function connectionClose(evt:Event):void {
	trace('connectionClose running!  GameService has closed.');

// when there's an IO Error - like if the remote host can't be reached
gameService.addEventListener(IOErrorEvent.IO_ERROR, onIO);
function onIO(evt:IOErrorEvent):void {
	lastErrorMessage = 'GameService has encountered an IO error: ' + evt.text;

// a security error - like when the policy file isn't found or denies access
gameService.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurity);
function onSecurity(evt:SecurityErrorEvent):void {
	lastErrorMessage = 'GameService has encountered a Security error: ' + evt.text;

// connect to the server!

Frame 2, labeled "username"

This frame prompts the user for their username. There's a static text field to prompt, a text input with instance name usernameText, a button component named submitButton, and a dynamic text field named usernameStatusText.

The actionscript on this page stops the playhead, defines a client-side service method registerHandler which the server can call, and adds an event listener to the button which calls the method registerUsername on the server.

/* stop the playhead */

var localUser:Object = new Object();

/* this function will be called by the server to report success
   or failure when trying to register a username */
gameService.client.registerHandler = function(success:Boolean, username:String, clientKey:int, msg:String=""):void {
trace('register handler running');
	if (!success) {
		usernameStatusText.text = msg;
	} else {
		localUser['username'] = username;
		localUser['clientKey'] = clientKey;
		usernameStatusText.text = 'Your name is now ' + username;
		submitButton.removeEventListener(MouseEvent.CLICK, submitClick);

/* when someone clicks the button, we call a server method */
function submitClick(evt:MouseEvent) {
	if (usernameText.length > 0) {
		usernameStatusText.text = "Registering username with server...";
	} else {
		usernameStatusText.text = "You must enter a name!";
submitButton.addEventListener(MouseEvent.CLICK, submitClick);

Here's the source of the server-side method registerUsername:

 * This function checks to see if a username
 * is in use and, if not, adds the user to the list
 * of waiting users
 * @param string $username The desired name
function registerUsername($username) {
$name trim($username);
$success true// assume success until we actually fail
$msg '';
    if (
strlen($name) == 0) {
$success false;
$msg "Your name must have more than just spaces";
    } else {
// loop through all the clients connected to the server
        // to see if one already has the name we want
foreach($this->server->clients as $client_key => $client) {
            if (isset(
$client->data['username']) && ($client->data['username'] == $name)) {
$success false;
$msg 'The name ' $name ' is already in use';
    if (
$success) {

/* assign $name to the current user */
$this->calling_client->data['username'] = $name;
/* mark the user as waiting to start a game */
$this->calling_client->data['status'] = self::STATUS_WAITING;
// notify all the waiting players about the other waiting players
$players $this->getWaitingPlayers();
$this->server->clients as $client_key => $client) {
            if ((
$client->data['status'] == self::STATUS_WAITING) && ($client_key != $this->calling_client_key)) {

    /* call the client method we defined called 'registerHandler' */
// registerUsername()

Frame 3, labeled "challenge"

If the user selected a valid username in frame 2, they are sent to frame 3 which presents a list of the other players with a status of STATUS_WAITING. It has a dynamic text box named greetingText, a List component named playerList, and a Button component named challengeButton. It also has some instructions about how to operate your gunslinger in the game.

The actionscript on this page is fairly involved because of the need to create a modal dialog to handle user challenges and the variety of things can happen such as cancelling a challenge or accepting a challenge. The reason this code is fairly complicated is mostly because we have to allow players to communicate with each other to start a game and we must also prevent players from challenging more than a single player at once, etc. This code is not perfect, but it should function reasonably well as a demo.


import net.flashmog.ModalDialog;
ModalDialog.btnWidth = 150;

greetingText.text = "Your name is " + localUser['username'];

// we need a var to remember the latest challenger between the challenge and
// modal dialog acceptance
var latestChallenger:Object = null;

// we need a var to remember the last client we have challenged
// in case we want to cancel the challenge
var lastClientChallenged:Object = null;

// game-related variables
var gameInfo:Object = new Object();

// this function will be called by the server when it sends the list of waiting players
gameService.client.refreshWaitingPlayers = function(players:Array):void {
	for(var i:Number = 0; i < players.length; i++) {
		if (players[i].client_key != localUser['clientKey']) {
			playerList.addItem({label:players[i].username, data:players[i].client_key});

// callback function for the challenge accept/deny dialog
function challengeCallback(buttonIndex:int) {
	switch (buttonIndex) {
		case 0:
			greetingText.text = localUser['username'] + ' is a coward';
			latestChallenger = null;
			challengeButton.enabled = true;
		case 1:
			// start the game!
			challengeButton.removeEventListener(MouseEvent.CLICK, challengeClick);
			challengeButton.removeEventListener(MouseEvent.CLICK, challengeClick);
			lastErrorMessage = 'The challenge dialog encountered an unexpected value: ' + buttonIndex;

// called by the server when someone challenges this player
gameService.client.showChallenge = function(challenger:Object):void {
	latestChallenger = challenger;
	var prompt:String = challenger.username + ' has challenged you to a gunfight!';
	var buttons:Array = new Array('Chicken Out', 'Shoot That Varmint!');,

// called by the server when someone retracts a previous challenge to this player
gameService.client.cancelChallenge = function(msg:String):void {
	greetingText.text = msg;

// called by the server when someone retracts a previous challenge to this player
gameService.client.challengeFail = function():void {
	greetingText.text = 'The client you challenged is no longer available';

// this issues a call to the server to ask for a list of the waiting players

// callback function for the dialog shown when this client has challenged some other user
function cancelChallengeCallback(buttonIndex:int) {
	lastClientChallenged = null;

// when someone clicks the challenge button, we call a server method to issue a challenge
// to the selected player
function challengeClick(evt:MouseEvent) {
	if (playerList.selectedItem) {
		// if we don't disable the button, it still
		// be listening to key events.
		challengeButton.enabled = false;
		var prompt:String = 'Challenging ' + playerList.selectedItem.label + '...';
		var buttons:Array = new Array('Cancel');,
		lastClientChallenged = new Object();
		lastClientChallenged.clientKey =;
	} else {
		errorText.text = "You must select an opponent!";

// this is the function called when a challenge is accepted.
// it is called on both the challenger's and the challengee's machines
gameService.client.startGame = function(opponentKeyArg:int, opponentNameArg:String, gameKeyArg:int, playerNumberArg:int):void {
trace('startGame running');
	gameInfo.opponentKey = opponentKeyArg
	gameInfo.opponentName = opponentNameArg;
	gameInfo.key = gameKeyArg;
	gameInfo.playerNumber = playerNumberArg;
	if (!latestChallenger) {

challengeButton.addEventListener(MouseEvent.CLICK, challengeClick);
challengeButton.enabled = true;

The server-side code that corresponds to this frame is also fairly involved because we must allow players to challenge each other, accept a challenge, reject a challenge, cancel a challenge, etc., all while keeping the various players' clients in sync. The trick here is to deal with the latency between clients and the server and keep all the parties in a mutually consistent state.

/* sends a list of the waiting players */
public function requestWaitingPlayers() {
$players $this->getWaitingPlayers();

private function 
getWaitingPlayers() {
$players = array();
$i 0;
$this->server->clients as $client_key => $client) {
        if ((
$client->data['status'] == self::STATUS_WAITING)) {
$players[$i] = array();
$players[$i]['username'] = $client->data['username'];
$players[$i]['client_key'] = $client_key;
 * issues a challenge to a user
 * @access public
 * @param int $client_key The array key in the server's $clients
 * array of the user being challenged
 * @return void
public function challengeUser($client_key) {
// use $client_key to retrieve the appropriate client
if (!isset($this->server->clients[$client_key])) {
// call the client method we defined called 'challengeFail'

$challenged_client $this->server->clients[$client_key];

$challenger = array();
$challenger['username'] = $this->calling_client->data['username'];
$challenger['client_key'] = $this->calling_client_key;
// change status of involved parties to 'challenging' to prevent
    // multiple challenges or other weirdness
$this->calling_client->data['status'] = self::STATUS_CHALLENGING;
$this->server->clients[$client_key]->data['status'] = self::STATUS_CHALLENGING;

// call the client method we defined called 'showChallenge'

 * This routine is called by a user when they choose to accept a
 * challenge.  It should remove any modal dialog or click screen
 * type stuff and put the two into a game.
 * @access public
 * @param int $challenger_client_key
 * @return void
public function acceptChallenge($challenger_client_key) {
    if (!isset(
$this->server->clients[$challenger_client_key])) {
$challenger $this->server->clients[$challenger_client_key];
// create a new game
$game = new FlashMOGClientGroup();

// store the game in the $games array
$game_key FlashMOGUtils::get_available_key($this->games);
$this->games[$game_key] = $game;
// set the players' status
$challenger->data['status'] = self::STATUS_PLAYING;
$this->calling_client->data['status'] = self::STATUS_PLAYING;
$challenger->{get_class($this)}->startGame($this->calling_client->client_key$this->calling_client->data['username'], $game_key0);
$this->calling_client->{get_class($this)}->startGame($challenger->client_key$challenger->data['username'],  $game_key1);

 * This function retracts a challenge issued to some client
 * @access public
 * @param int $challenged_client_key The key in the main server's $clients
 * array for the other client that was previously challenged
 * which we would now like to 'unchallenge'
public function cancelChallenge($challenged_client_key) {
// first check to see if the $challenged_client_key refers to anything
if (isset($this->server->clients[$challenged_client_key])) {
$challenged_client $this->server->clients[$challenged_client_key];
        if (
$challenged_client->data['status'] == self::STATUS_CHALLENGING) {
$msg $this->calling_client->data['username'] . ' has retracted the challenge';
$challenged_client->data['status'] == self::STATUS_WAITING;
$this->calling_client->data['status'] == self::STATUS_WAITING;


Frame 4, labeled "game"

Frame 4 is the game frame. This stage on this frame is completely blank in the FLA file because the only things that appear on the screen are all dynamically created from the library. The actionscript is fairly complex as it must implement all of the routines necessary to listen for keystrokes, move players, communicate with the server, and shoot bullets. I have used Senocular's KeyObject class and Corey O'Neil's collision detection detection classes.

The code in this frame implements 3 client-side methods and their 3 counter parts on the server: movePlayer, shootBullet, and cowboyShot. Whenever your cowboy locally does one of these things, he calls the corresponding server method which relays the action to the opponent's client.

// utility to handle key presses
import com.senocular.utils.KeyObject;
// utility to handle collision detection
import coreyoneil.collision.CDK;
import coreyoneil.collision.CollisionGroup;

//todo - make sure bullet keys match on all players
// right now, we have race conditions for the bullet indexes
// and we may try to remove bullets that have already removed
// themselves


var targetScore:int = 5; // game ends when someone gets this score

var key:KeyObject = new KeyObject(stage);

var keyCodeUp:int = 87;
var keyCodeDown:int = 83;
var keyCodeLeft:int = 65;
var keyCodeRight:int = 68;
var keyCodeFire:int = 32;

var cos45:Number = Math.cos(Math.PI/4);
var cos30:Number = Math.cos(Math.PI/6);
var sin30:Number = Math.sin(Math.PI/6);
var cowboySpeed:int = 10; // cowboy speed in pixels/frame
var bulletSpeed:int = 50; // bullet speed in pixels/frame

// some spur sounds
var spur1:Spur1 = new Spur1();
var spur2:Spur2 = new Spur2();

// create boundary boxes for all the cowboys
var bounds:Array = new Array();
bounds[0] = new Object();
bounds[0].minX = 0;
bounds[0].minY = 0;
bounds[0].maxX = 500;
bounds[0].maxY = 600;

bounds[1] = new Object();
bounds[1].minX = 500;
bounds[1].minY = 0;
bounds[1].maxX = 1000;
bounds[1].maxY = 600;

// create an array with references to all the players
var players:Array = new Array();

// create two cowboys
players[0] = new cowboy(); // left cowboy
players[0].key = 0;
players[0].x = 50;
players[0].y = 300;
//players[0].priorX = 50;
//players[0].priorY = 300;
players[0].scaleX = 0.3;
players[0].scaleY = 0.3;
players[0].bnds = bounds[0];
players[0].bulletDirection = 1;
players[0].priorFrame = 'standing';
players[0].nameString = 'cowboy0';
players[0].score = 0;
players[0].collisionGroup = new CollisionGroup(players[0]);

players[1] = new cowboy(); // right cowboy
players[1].key = 1;
players[1].x = 950;
players[1].y = 300;
//players[1].priorX = 950;
//players[1].priorY = 300;
players[1].scaleX = -0.3;
players[1].scaleY = 0.3;
players[1].bnds = bounds[1];
players[1].bulletDirection = -1;
players[1].priorFrame = 'standing';
players[1].nameString = 'cowboy1';
players[1].score = 0;
players[1].collisionGroup = new CollisionGroup(players[1]);

var localCowboy:MovieClip = players[gameInfo.playerNumber];

// initialize a couple of cowboy properties
localCowboy.bullet = false; // indicates if the cowboy has a bullet in the air
localCowboy.walking = false; // true if the cowboy is moving but not firing

// var to pause the entire game when someone gets shot or at the beginning
var gamePaused:Boolean = false;
var unPauseTimer:Timer;

// var to track all the bullets
var bulletsFired:int = 0;
var bullets:Array = new Array();

// create a scoreboard
var scoreDisplay:Array = new Array();
for (var i=0; i<players.length; i++) {
	var tf:TextField = new TextField();
	tf.autoSize = TextFieldAutoSize.LEFT;
	tf.background = false;
	tf.border = false;
	tf.embedFonts = true;

	var format:TextFormat = new TextFormat();
	format.font = oldeTymeFont.fontName;
	format.color = 0x552E11;
	format.size = 64;

	tf.defaultTextFormat = format;

	tf.text = '0';

	scoreDisplay[i] = tf;

scoreDisplay[0].x = 10;
scoreDisplay[1].x = stage.stageWidth - (scoreDisplay[1].width + 10);

function enterFrameFunc(event:Event) {
	if (gamePaused) return;
	var upPressed:Boolean = key.isDown(keyCodeUp);
	var downPressed:Boolean = key.isDown(keyCodeDown);
	var leftPressed:Boolean = key.isDown(keyCodeLeft);
	var rightPressed:Boolean = key.isDown(keyCodeRight);
	var firePressed:Boolean = key.isDown(keyCodeFire);

	// determine vertical movement vector
	var verticalVector:Number = 0;
	if (upPressed && !downPressed) {
		verticalVector = -1;
	} else if (downPressed && !upPressed) {
		verticalVector = 1;

	// determine horizontal movement vector
	var horizontalVector:Number = 0;
	if (rightPressed && !leftPressed) {
		horizontalVector = 1;
	} else if (leftPressed && !rightPressed) {
		horizontalVector = -1;

	// if there is both horiz and vert movement, scale each vector
	// to keep diagonal movement speed consistent with lateral movement
	//   speed
	if ((verticalVector != 0) && (horizontalVector != 0)) {
		horizontalVector *= cos45;
		verticalVector *= cos45; // sin45 = cos45

	// determine whether to change frames on the cowboy
	var newFrame:String = null;
	if (firePressed && !localCowboy.bullet) {
		// if cowboy is firing, he isn't walking
		localCowboy.walking = false;
		if (verticalVector < 0) {
			newFrame = 'shootUp';
		} else if (verticalVector > 0) {
			newFrame = 'shootDown';
		} else {
			newFrame = 'shootStraight';
		var xVector:Number = (verticalVector == 0) ? (localCowboy.bulletDirection) : (localCowboy.bulletDirection*cos30);
		var yVector:Number = (verticalVector == 0) ? 0 : sin30*(Math.abs(verticalVector)/verticalVector)

		// tell other members of this game that bullet is shooting
		var bulletKey:String = String(gameInfo.playerNumber) + '_' + String(bulletsFired);
		gameService.server.shootBullet(bulletKey, gameInfo.key, gameInfo.playerNumber, localCowboy.x, localCowboy.y, xVector, yVector);
		shootBullet(bulletKey, localCowboy, localCowboy.x, localCowboy.y, xVector, yVector);

	} else if ((verticalVector != 0) || (horizontalVector != 0)) {
		if (!localCowboy.walking) {
			newFrame = 'walking';
		localCowboy.walking = true;
	} else {
		newFrame = 'standing';
		localCowboy.walking = false;

	// calculate the distance moved in each dimension
	var xMove:Number = horizontalVector * cowboySpeed;
	var yMove:Number = verticalVector * cowboySpeed;
	// calculate the new position
	var newX:int = localCowboy.x + xMove;
	var newY:int = localCowboy.y + yMove;
	// keep cowboy inside boundaries
	newX = (newX < localCowboy.bnds.minX) ? localCowboy.bnds.minX : newX;
	newX = (newX > localCowboy.bnds.maxX) ? localCowboy.bnds.maxX : newX;
	newY = (newY < localCowboy.bnds.minY) ? localCowboy.bnds.minY : newY;
	newY = (newY > localCowboy.bnds.maxY) ? localCowboy.bnds.maxY : newY;
	// move this cowboy on the remote clients BUT ONLY IF SOMETHING HAS CHANGED
	if (localCowboy.walking || (newFrame != localCowboy.priorFrame)) {
		gameService.server.movePlayer(gameInfo.key, gameInfo.playerNumber, newX, newY, newFrame);

	// move the local player and set the new frame
	localCowboy.x = newX;
	localCowboy.y = newY;

	if ((newFrame != null) && (newFrame != localCowboy.priorFrame)) {
		localCowboy.priorFrame = newFrame;
		if (newFrame == 'standing') {
		} else {

} // enterFrameFunc
addEventListener(Event.ENTER_FRAME, enterFrameFunc);

function updateFunc(tevt:TimerEvent) {
//	trace('this is updateFunc, the original timer event');
//	tevt.updateAfterEvent();
addEventListener(TimerEvent.TIMER, updateFunc);

function shootBullet(bulletKey:String, cowboy:Object, x0:int, y0:int, xVector:Number, yVector:Number):void {

	var bulletSound:BulletSound = new BulletSound();;
	var b:bullet = new bullet();
	b.x = x0;
	b.y = y0;
	b.xVector = xVector;
	b.yVector = yVector;
	b.nameString = 'bullet #' + bulletKey;
	b.cowboyWhoFired = cowboy;
	b.cowboyWhoFired.bullet = true;

	// if the localCowboy has fired the bullet, we need to 
	// enable collision checking against other players
	if (cowboy == localCowboy) {
		b.dangerousBullet = true;
		for(var i=0; i<players.length; i++) {
			if (players[i] != localCowboy) {
	} else {
		b.dangerousBullet = false;
	b.rotation = (180/Math.PI) * Math.atan(yVector/xVector);
	b.scaleX *= cowboy.bulletDirection
	b.addEventListener(Event.ENTER_FRAME, moveBullet);

trace('setting bullets array[' + bulletKey + ']');
	b.key = bulletKey;
	bullets[bulletKey] = b;

} // shootBullet()

function moveBullet(e:Event):void {
	if (gamePaused) return;
	var b:Object =;
	// move the bullet
	b.y += bulletSpeed * b.yVector;
	b.x += bulletSpeed * b.xVector;
	// check for collision against each player that is not
	// the localCowboy
	if (b.dangerousBullet) {
		for(var i=0; i<players.length; i++) {
			if (players[i] != localCowboy) {
				var collisions:Array = players[i].collisionGroup.checkCollisions();
				if (collisions.length > 0) {
					gameService.server.cowboyShot(gameInfo.key, gameInfo.playerNumber, i, b.key);
					cowboyShot(localCowboy, players[i], b);
	if (
		( <= -10)
		|| ( <= -10)
		|| ( >= stage.stageHeight)
		|| ( >= stage.stageWidth)
} // moveBullet

function removeBullet(b:Object) {
	b.removeEventListener(Event.ENTER_FRAME, moveBullet);
	b.cowboyWhoFired.bullet = false;
	if (b.dangerousBullet) {
		for(var i=0; i<players.length; i++) {
			if (players[i] != localCowboy) {

	delete bullets[b.key];

} // removeBullet()

function cowboyShot(playerShooting:Object, playerShot:Object, bullet:Object) {
	// update the score and score display
	scoreDisplay[playerShooting.key].text = playerShooting.score;
	if (bullet) {
		trace('cowboyShot removing bullet' + bullet.key);
	} else {
		trace('cowboyShot NO BULLET');

	// check for end-of-game situation
	if (playerShooting.score >= targetScore) {

	gamePaused = true;
	// create a timer to unpause the game
	var unPauseTimer:Timer = new Timer(2000, 1);
	unPauseTimer.addEventListener(TimerEvent.TIMER, function(tevt:TimerEvent){
		for(var i=0; i<players.length; i++) {
		gamePaused = false;
} // cowboyShot()

function gameOver() {
	gamePaused = true;
	for(var i=0; i<players.length; i++) {
	// todo - remove listeners, clean up, garbage collect, and
	// return to 'waiting' stage
	var tf = new TextField();
	tf.autoSize = TextFieldAutoSize.LEFT;
	tf.background = false;
	tf.border = false;
	tf.embedFonts = true;

	var format:TextFormat = new TextFormat();
	format.font = oldeTymeFont.fontName;
	format.color = 0x552E11;
	format.size = 64;

	tf.defaultTextFormat = format;

	tf.text = 'Game Over';
	tf.x = (stage.stageWidth - tf.width) / 2;
	tf.y = (stage.stageHeight - tf.height) / 2;

 * this routine allows the server to 'shoot' a cowboy
gameService.client.cowboyShot = function(playerShootingNumber:int, playerShotNumber:int, bulletKey:String):void {
trace('client.cowboyShot, bulletkey=' + bulletKey);
	var pShooting:Object = players[playerShootingNumber];
	var pShot:Object = players[playerShotNumber];
	var b:Object;
	if (bullets[bulletKey]) {
		trace('client.cowboyShot, bullet is defined:' + bulletKey);
		b = bullets[bulletKey];
	} else {
		trace('client.cowboyShot, NO BULLET:' + bulletKey);
		b = null;
	cowboyShot(pShooting, pShot, b);
} // gameService.client.cowboyShot()

 * this routine allows the server to move players within this client
gameService.client.movePlayer = function(playerNumber:int, newX:int, newY:int, newFrame:String):void {
	var player:MovieClip = players[playerNumber];
	if (!player) {
		trace('PLAYER ' + playerNumber + ' does not exist!');
	var updateAfter:Boolean = false;
	if (player.x != newX) {
		player.x = newX;
		updateAfter = true;
	if (player.y != newY) {
		player.y = newY;
		updateAfter = true;

	if ((newFrame != null) && (newFrame != player.priorFrame)) {
		updateAfter = true;
		player.priorFrame = newFrame;
		if (newFrame == 'standing') {
		} else {
	if (updateAfter) {
		var tevt:TimerEvent = new TimerEvent(TimerEvent.TIMER, false, false);
} // gameService.client.movePlayer()

// this routine files a bullet on behalf of the remote player
gameService.client.shootBullet = function(bulletKey:String, playerNumber:int, x0:int, y0:int, xVector:Number, yVector:Number) {
	var player:Object = players[playerNumber];
	if (!player) {
		trace('PLAYER ' + playerNumber + ' does not exist!');

	shootBullet(bulletKey, player, x0, y0, xVector, yVector);
} // gameService.client.shootBullet()

The server-side PHP has the 3 complimentary methods:

 * This routine moves a player and sets their frame
 * @access public
 * @param int $game_key The array key of the game where the player is playing
 * @param int $player_number The number of the player to be moved.
 * note that this corresponds to the key of the player within the game
 * @param float $x The new x-coordinate of the player
 * @param float $y The new y-coordinate of the player
 * @param string frame The frame within the player movie clip to which
 * we are to move the playhead.
public function movePlayer($game_key$player_number$x$y$frame) {
    if (!isset(
$this->games[$game_key])) {
// should we send an error?  disconnect the client?
$game $this->games[$game_key];
$game->members as $key => $client) {
// may want to check and make sure $client instanceof FlashMOGClient
if ($client->client_key != $this->calling_client->client_key) {
// movePlayer()

 * This routine causes a player to fire a bullet
 * @access public
 * @param int $game_key The array key of the game where the player is playing
 * @param int $player_number The number of the player to be moved.
 * note that this corresponds to the key of the player within the game
 * @param float $x0 The initial x-coordinate of the bullet
 * @param float $y0 The initial y-coordinate of the bullet
 * @param float $xVector The x-direction of the bullet
 * @param float $yVector The y-direction of the bullet
public function shootBullet($bullet_key$game_key$player_number$x0$y0$x_vector$y_vector) {
    if (!isset(
$this->games[$game_key])) {
// should we send an error?  disconnect the client?
$game $this->games[$game_key];
$game->members as $key => $client) {
// may want to check and make sure $client instanceof FlashMOGClient
if ($client->client_key != $this->calling_client->client_key) {
// shootBullet()

 * Causes a cowboy to respond to being shot
public function cowboyShot($game_key$player_shooting_number$player_being_shot_number$bullet_key) {
    if (!isset(
$this->games[$game_key])) {
// should we send an error?  disconnect the client?
$game $this->games[$game_key];
$game->members as $key => $client) {
// may want to check and make sure $client instanceof FlashMOGClient
if ($client->client_key != $this->calling_client->client_key) {

Frame 5, labeled "close"

Frame 5 is where our movie goes when the server connection closes. It's essentially a trivial frame with a trivial message in a static text box:

The actionscript is also trivial:

/* stop the playhead! */

There is no special server-side code called by this frame.

Frame 6, labeled "error"

Frame 6 is where our movie goes when an error is encountered. It has a dynamic textbox labeled errorText.

frame 6, for showing errors

The actionscript is very simple. When we enter the frame, we stop the playhead and load this text box with whatever value is in the var lastErrorMessage:

/* stop the playhead! */

/* we need to put the last message into the errorText field
   so it gets displayed to the user */
errorText.text = lastErrorMessage;

There is no server-side code called by this frame.

Room for improvement

This is just a very basic proof-of-concept game and there's a LOT of room for improvements. In particular, there is nothing in place to prevent cheating. Since bullet collisions are determined by the clients, it would be pretty easy to engineer a cheat client. Also, the code could be more carefully engineered to guard against race conditions in the messaging between clients and the server. For instance, what happens when two players challenge each other simultaenously? Server latency may result in a hung modal dialog or the challenge may fail. Also, the GameService class doesn't implement a __disconnect method. This method could properly cancel any pending challenges issues by the disconnecting client and perhaps refresh each player's list of waiting clients. There's plenty to be done.

FlashMOG Community Forum • Information


Sorry but this board is currently unavailable.