OOP Practice/Structure for AS3 Game - actionscript-3

I'm a bit of a newb to as3 game development but I want to create a somewhat flexible base for a Connect Four game. I'd like to be able to skin the game board and game pieces. Here's what I'm thinking so far. If anyone has suggestions I'd really appreciate it:
GameController extends EventDispatcher
- Contains all game grid manipulation methods.
- Includes 2D array to keep track of GamePiece locations
- Dispatches events after validation when methods are invoked
GameClass extends Sprite:
- Holds visual elements of the board
- MouseEvent Listeners attached to visual elements, which invoke controller methods
- (Custom) ControllerEvent Listeners to update visual look when GameController dispatches
GamePiece Class extend Sprite:
- Holds piece's column/row location
- Holds currentPlayer index
- Loads PNG URL as skin
That's the rough outline. Any red flags or other suggestions are very much appreciated.

It sounds like the GridController is going to suffer from mixed responsibilities; in MVC architectures the Controller's responsibility is to shuffle data back and forth from the Model to the View. Personally I would consider having a GridModel which would hold the underlying multidimensional Array which represents the grid and methods for adding pieces, eg:
public class GridModel extends EventDispatcher {
private var _grid : Array;
public function GridModel(rows : uint, cols : uint) : void {
// Create the data structure which represents the Grid.
_grid = initialiseGrid(rows, cols);
}
public function addPiece(player : uint, col : uint) : void {
if (isValidMove(col)) {
// Update the datastructure, determine which row the piece ended
// up residing in.
const row : uint = // method omitted
// Notify the rest of the system that a move has been made.
dispatchEvent(new GridUpdateEvent(GridUpdateEvent.MOVE, player, col, row, _grid.concat());
}
else {
// Illegal Move, datastructure stays the same, notify the rest
// of the system.
dispatchEvent(new IllegalMoveEvent(IllegalMoveEvent.COLUMN_FULL, player, col, _grid.concat()));
}
}
}
The primary role of your Controller would now be to listen to the events dispatched by the model and then update the View (DisplayList) accordingly. Like-wise, your View should dispatch Events based on user interaction (eg: Player one has indicated that they wish to drop a piece into the 2nd column); the Controller can then call the relevant method on the Model.
The following snippet should give you some indication as to what the responsibilties of the Controller are; don't forget that you can (and should!) break your responsibilities down by making use of multiple Models, Views and, if required Controllers.
public class GameController {
private var _gridModel : GridModel;
private var _stateModel : GameStateModel;
private var _gridView : GridView;
public function GameController(gridModel : GridModel, gameStateModel : GameStateModel, gridView : GridView) {
_gridModel = gridModel;
_gameStateModel : gameStateModel;
_gridView = gridView;
addEventListeners();
}
private function addEventListeners() : void {
_gridModel.addEventListener(GridUpdateEvent.MOVE, onGridUpdateMoveEvent);
_gridView.addEventListener(ColumnSelectionEvent.SELECTED, onColumnSelectedEvent);
}
private function onColumnSelectedEvent(event : ColumnSelectionEvent) : void {
// Query the GameStateModel to find out whos turn it currently is.
const activePlayer : uint = _gameStateModel.activePlayer;
// Ask the GridModel to update.
_gridModel.addPiece(activePlayer, event.column);
}
private function onGridUpdateMoveEvent(event : GridUpdateEvent) : void {
// Update the view.
_gridView.insertPiece(event.player, event.row, event.col);
// Update the GameState to indicate it's the next player turns.
_gameSate.completeTurn();
}
}

Related

AS3 Dynamically create an instance of an object

I have a class which contains the following method to place an object on the stage.
public function createBox()
{
var box:Ball1 = new Ball1();
addChild(box);
box.addEventListener(Event.ENTER_FRAME, boxF);
}
What I would like to do is to is pass an object name to the method, and load that object instead, thus allowing me to use the same method for different objects.
A non-working example:
public function createBox( obj )
{
var box:obj = new obj();
addChild(box);
box.addEventListener(Event.ENTER_FRAME, boxF);
}
Is this possible?
Thanks
A more agile version of the existing answer is to use getDefinitionByName(), which allows you to construct a class based on an input string.
Using this function, you can rewrite the method to something like this:
public function produce(className:String):*
{
var type:Class = getDefinitionByName(className) as Class;
return new type();
}
Advanced:
To make this stricter and more maintainable (make it so only certain factories can create certain classes), you can use an interface to build a relationship between a given factory and the classes it can produce. A small example of this follows below.
Say a factory, EnemyFactory, creates objects that you would consider to be enemies in a game. We don't want to be able to create things like pickups, particles and other non-enemy type objects. We can create an interface IEnemyProduct which is implemented by classes that the EnemyFactory is allowed to create. The interface could be as simple as:
public interface IEnemyProduct{}
Which would be implemented by any enemy classes. The EnemyFactory's produce() function can then be modified to a more readable version, like this:
public function produce(enemyClassName:String):IEnemyProduct
{
var type:Class = getDefinitionByName(enemyClassName) as Class;
return new type() as IEnemyProduct;
}
In this case, produce() will return null if the produced class does not implement IEnemyProduct. The goal here is to make it obvious which factories are responsible for which objects, which is a big advantage once the project becomes larger. The use of an interface rather than a base class also means you can implement multiple interfaces and have the class produced by multiple factories.
You could maybe use a simple factory, or something similar:
public class Factory
{
public static function produce(obj:String)
{
if (obj == "obj1")
{
return new obj1();
} else if (obj == "obj2") {
return new obj2();
}
}
}

AS3 - Accessing subclass of subclass

Im making this game, and I really want to access a "subclass of subclass". So I have something like this: MainClass > MonsterLibrary > SampleMonster.
I want to add this Sample Monster from MainClass, but I have to use it through MonsterLibrary, so I dont have to add monster by monster at my MainClass. Every monster respawn would be written in MonsterLibrary class.
I guess it should be something like this.
public class MainGame extends MovieClip {
public function MainGame() {
var mylibrary:MonsterLibrary = new MonsterLibrary();
mylibrary.MonsterLibrary();
Main class.
public class MonsterLibrary extends MovieClip {
#all var here.#
public function MonsterLibrary(){
var monster:SampleMonster = new SampleMonster(330,250);
addChild(monster);
}
MonsterLibrary class.
public class SampleMonster extends MonsterLibrary{
public function SampleMonster(startX:Number, startY:Number) {
//SETTING STARTING LOCATION
x = startX;
y = startY;
SampleMonster class.
I know Im doing it wrong, but I have no idea how to make this work. I keep getting this error ->
1061: Call to a possibly undefined method MonsterLibrary through a reference with static type MonsterLibrary.
You get that error because you're trying to directly call the constructor of MonsterLibrary:
public function MainGame()
{
var mylibrary:MonsterLibrary = new MonsterLibrary();
mylibrary.MonsterLibrary(); // <- wrong
...
}
The MonsterLibrary() function is the constructor of the MonsterLibrary class and it's called automatically when you use the new operator to create a new instance of the class.
If you want your MonsterLibrary class to act as a monster factory (a class that creates monster objects), create a new function that returns a monster:
...
public function CreateMonster ( sType:String ):SampleMonster
{
var oMonster:MovieClip = null; // you can use BaseMonster instead of MovieClip
if ( sType == "SampleMonster" )
{
oMonster = new SampleMonster ( ... );
... // initialize the monster here
}
...
return ( oMonster );
}
...
// get a monster and add it to the stage
var oMonster:MovieClip = oMonsterLibrary.CreateMonster ( "SampleMonster" );
oStage.addChild ( oMonster );
Note that MonsterLibrary doesn't need to extend MovieClip - it really doesn't need to extend any type (other than Object) since (I assume) it's not a visual object. Its purpose is to be a factory, not to be on the screen. oStage in the code above is your top-level display object - it could be the actual stage or a DisplayObject that acts as your stage.
The SampleMonster type shouldn't extend MonsterLibrary - a particular monster is not a library. Your monsters should derive either directly from MovieClip or (better) from a common base monster class; something like:
public class BaseMonster extends MovieClip
{
...
}
Then your SampleMonster can derive from this base monster:
public class SampleMonster extends BaseMonster
{
...
}
I'm confused at what you are trying to accomplish.
Are you looking to have the MonsterLibrary return a monster every time you call a method ?
ie you could have a method in MonsterLibrary class like this :
public function getMonster():SampleMonster
{
var monster:SampleMonster = new SampleMonster(330,250);
return monster;
}
Then your MainGame might look like this :
public class MainGame extends MovieClip {
public function MainGame() {
var mylibrary:MonsterLibrary = new MonsterLibrary();
var newMonster:SampleMonster = mylibrary.getMonster();
addChild(newMonster);
Going further you could have a parameter for the getMonster method to specify a monster type.
for example :
public function getMonster(monsterType:int):Monster
{
// create the appropriate monster and return it
}
Keep in mind that in your code adding the monster to the Display List of the MonsterLibrary, means it will never be seen UNLESS you add MonsterLibrary to the Display List of MainGame
Also you have SampleMonster extending MonsterLibrary, which is not going to work.
SampleMonster should probably extend MovieClip or Sprite or if you intend on having multiple monsters you'd want to have a base Monster Class that any specific monster might extend.

AS3: if-function doesn't listen to a boolean in another class

So... I'm working on a chess-game, and trying to make it so that a "public static boolean" (turn) dictates which player can make a move. This boolean is in a class (Board.as) which imports all the classes for all the chess-pieces (e.g. QueenW.as (for the White Queen)).
I've tried multiple ways: Trying to make functions not run anymore, and replacing the pieces (which are buttons) to other objects (non-clickable movieclips). Decided to go with the latter. I've traced the boolean in a chess-piece class, as well as the Board-class, in an ENTER_FRAME function. Both seem to trace it correctly when the value changes.
Problem is: Flash doesn't remove the chess-pieces and replaces them with a non-clickable object, even though the class in which it should happen (Board.as) does listen to the boolean when tracing. Anybody knows a solution?
A little piece of my code, which is relative to the problem:
Board class (which is the Documentclass for my .fla file)
package
{
import QueenWclass; //imports the class used for example.
public class Board extends MovieClip
{
public static var turn:Boolean = new Boolean; //creates public static bool.
var queenW:QueenWclass = new QueenWclass(); //creates aforementioned chess-piece.
var queenWnoturn:QueenWnoturn = new QueenWnoturn; //creates a non-clickable object.
}
public function Board()
{
turn = true;
this.addEventListener(Event.ENTER_FRAME, frameEnter);
addChild(queenW); //adds pieces to the screen.
}
if (turn == true)
{
}
if (turn == false)
{
removeChild(queenW); //Removes chess-piece.
addChild(queenWnoturn); //Adds a non-clickable object.
}
}
And my QueenWclass.as class:
package
{
public class QueenWclass extends MovieClip
{
var queenW:QueenW = new QueenW();
}
public function QueenWclass()
{
addChild(queenW);
this.addEventListener(MouseEvent.CLICK, CLICKqueenW);
}
function CLICKqueenW(event.MouseEvent):void
{
Board.turn = false;
}
}
I hope I wrote this example correctly and understandably. There's no real timelimit to my project as I already had to turn it in an hour ago (but still got a 6/10 because of effort and how far I've come with this rather complex game). I just want to finish it for myself... Thanks in advance!
Maybe the code has not been copied correctly or there is a small problem.
This code:
if (turn == true)
{
}
if (turn == false)
{
removeChild(queenW); //Removes chess-piece.
addChild(queenWnoturn); //Adds a non-clickable object.
}
Will only run once, when "Board" is created, it will not run when the state of "turn" changes.
Well, you have nothing that's listening for the boolean's change. The code that's checking the boolean is located in constructor, while the actual change is done in a MouseEvent.CLICK event listener. You have to either implement a function that's called repeatedly via Event.ENTER_FRAME listening, SetInterval(), or TimerEvent.TIMER (with a timer), or implement a publicly available property as a function, that would check which turn is it and do corresponding actions. The latter is a little better, as it works only when something is changed.
private static var _turn:Boolean=false;
public static function get turn():Boolean { return _turn; } // getter part
public static function set turn(value:Boolean):void // setter part
{
if (_turn==value) return; // no need to change turn
_turn=value;
if (_turn) YouGetATurn(); else EnemyGetsATurn();
// this part is what will get called when you change Board.turn
}

Get instance objects which are not on first frame

I'm using actionscript 3.0 and Flash Professional CS5.5
I ran into a problem with instance objects which are not on the first frame.
My first frame is a menu and my second frame contains a TLF text field.
I have given the text field an instance name, let's say "username_txt".
Now if I turn off the "Automatically Declare Stage Instances" and do this in my main class
public class MainClass extends MovieClip {
public var username_txt:TLFTextField;
public function MainClass() {
username_txt.text = "anything";
}
}
I will receive a run time error stating that I try to access the property of a null object. (I have import all the necessary classes so that is not the problem)
This does not happen when I put the text right in the first frame though.
Any help?
Thanks in advance
As the text field has not yet been instantiated since the frame has not yet been reached, I'm unsure there is an elegant way to perform this task.
Perhaps a better model would be to decouple your data model from your views.
Create a singleton class to store data:
package
{
public class ApplicationModel
{
/** Singleton instance. */
private static var instance:ApplicationModel = new ApplicationModel();
/** Return singleton instance. */
public static function getInstance():ApplicationModel
{
return instance;
}
/** Data Model */
public var username:String;
/** Constructor as singleton enforcer */
public function ApplicationModel()
{
if (instance)
throw new Error("ApplicationModel is a singleton and can only be accessed through ApplicationModel.getInstance()");
}
}
}
Then from within anywhere such as keyframes or classes, you can get the instance of the object, implemented either as:
ApplicationModel.getInstance().username = "test";
Or a better practice would be:
var applicationModel:ApplicationModel = ApplicationModel.getInstance();
applicationModel.username = "test";
As per your example:
public class MainClass extends MovieClip {
public var username_txt:TLFTextField;
public function MainClass() {
ApplicationModel.getInstance().username = "anything";
}
}
Then, on the frame you need to update your TLFTextField (frame script of frame 2), you set the text based upon the model:
username_txt.text = ApplicationModel.getInstance().username
Your view will always update when needed.

AS3 How to extends Event class without keeping any existing event types?

I would like to extends the Event class to add some events I am using in game.
But I don't want the new Event Class to have the old public static types..
For instance I don't want to have:
NewEventClass.ENTER_FRAME
How do you go about extending the Event class without getting the old types mixed in?
Is there any way to outsmart AS3 to leave out the uneeded types?
Or should I avoid creating a new Event type altogether and just add the new strings?
Extending Event is only really necessary if you want to add some extra properties to it, for example:
public class EnemyEvent extends Event
{
// Constants used to represent event type
public static const ENEMY_KILLED:String = "killed";
// Event properties
public var score:int = 0;
/**
* Constructor
* Retain Event behaviours
*/
public function EnemyEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
}
}
So that when you dispatch this event from an enemy you can go:
var evt:EnemyEvent = new EnemyEvent(EnemyEvent.ENEMY_KILLED);
evt.score = myScoreValue;
dispatchEvent(evt);
And then make use of the score property from the listening method within the game engine:
enemy.addEventListener(EnemyEvent.ENEMY_KILLED, _countKill);
function _countKill(e:EnemyEvent):void
{
gameTotalScore += e.score;
if(gameTotalScore > 100) getAchievement();
e.target.removeEventListener(e.type, _countKill); // <-- woudn't work without extending event either
}
If you just need to store constants to use in addEventListener(x, ..), new Event(x), etc then you can just make a class that holds these and doesn't have anything to do with Event:
public class CustomEvents
{
public static const MY_CUSTOM_EVENT:String = "myCustomEvent";
}
So that you can just use these as needed:
new Event(CustomEvents.MY_CUSTOM_EVENT);
addEventListener(CustomEvents.MY_CUSTOM_EVENT, _listener);
The former method is still preferable as it's tidier and more logical.
Additionally; your note about your custom event having constants such as ENTER_FRAME isn't the case anyway, because they are static and belong to Event. You'll get this error if you try access ENTER_FRAME through the example in your answer:
1119: Access of possibly undefined property ENTER_FRAME through a
reference with static type Class.