Thursday, July 30, 2009
I'm sure everyone reading this has already checked this out, but for context and in case you've missed it, Mochi have posted some notes from their casual connect talk including a graph showing some data from the survey they and FGL, NG and others are running ( Check the full article here ).

Am I being naughty reproducing the graph here ? I'm sure I'll get told off if I am and remove it, until then:

ad-revenues-copy.jpg

( For a bigger version please check out the link to the post on the mochi blog. I'm only posting this here as it's in context ).

These figures are in reply to "How do you make money from Flash games" survey question.

Some interesting figures there. The one that caught my eye most is the number of people who make no revenue. Is that people who haven't made a game yet and therefore have made no revenue from it, or people who value what they do as art and are happy just for people to play it and receive the feedback from gamers without making anything at all, or is it people who's games are so toilet they can only give them away without even dropping some ads in there ?

Also I was surprised that ads are so far in the lead ( Well, semi-surprised, mochi are using this as a tool as well as a data gathering exercise ), but does that show that a lot of games aren't being sponsored ? Obviously it's a lot easier to just drop the ads api in there than to whore round your game, or even use somewhere like FGL who take on a lot of the pain for you.

But what does that mean ? Is there a shortage of sponsor dollars at the moment ? Are sponsors getting more selective, even with games at the lower end of the market ( What was once a $50 game is now not even worth that as you can get a $100 game for $50 now ? ) or is there a large volume of games being released that just are so poor it's not even worth putting them up for sponsorship ? Greedily reskinned tutorials and the like.

Interesting figures, and just a load of questions from me rather than any insightful answers. Perhaps it's too early in the surveys life to be giving more accurate figures ( Such as just over 10% of replies say they make their money from mtx, that seems a very high percentage seeing how few games are using them. I know heyzap are interested in anything in swf format, but even still that seems high ).
Also there is another question in there, about your primary money earning sources in order, which I think will be the surveys money shot, in that should show which areas are the most profitable and should be targeted by devs the most.

I think everyone involved in the survey should be applauded, it's long over due and will help a lot of people having these figures available.

As always if you've got any views on this, don't fear the comments button, it's there for you.

Squize.

Thursday, July 30, 2009 2:19:09 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [6]  |  Trackback
 Wednesday, July 29, 2009
We're like everyone else, we love a bit of attention. It's good for the ego, and good for the soul.

But spammers, your attention is a little like that slightly smelly and always drunk uncle that you'd only meet at weddings. The uncle who it wasn't 'til years later you realised was a little bit too touchy feely.

That's right spammers, I'm equating you to a made up story about being touched up by a relative as a child. That's how we see your attention. Deep down inside flattering ( It all counts ), but in every other way, as unwelcome as molestation.

Our comments are moderated, we get fired off an email every time we get one, and to be honest, we're getting a bit sick of getting emails with links to your products. It's nice that you start off all friendly and un-spam like, just like our imaginary uncle in so many ways.

So do us a quick favour, to save us reading emails we really don't want to read, and then deleting comments which really shouldn't be there, how about you move onto the next blog ?
I'm sure some of our readers are interested in low cost loans, but I'm guessing they're not going to get one via a link in a comment on a blog that prides itself in being 70% swear words.

You may have nothing better to do to build up your web empire, I mean I imagine you get a huge kick back for every loan which is taken up, but we're bored of clicking delete.

We're cool yeah ? No hard feelings ? We'd just prefer it if you'd fuck off. Thanks then, take care, bye.

Squize.

PS. I know I'm just talking to myself, but it really helps some times. Give it a go if you've got a blog, you'll feel much better that you've vented.

Wednesday, July 29, 2009 3:36:15 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [3]  |  Trackback
 Saturday, July 25, 2009
We've had a plan for a while now for gathering the stats from cronusX and displaying them in widgets on a custom page. We weren't allowed to data mine the version of cronusX on Candystand, we're only go to do it in the viral version, which is good 'cause it actually buys us some time to get them done.

We managed to get some data when we were beta testing the game, so there are some figures for test data ( I did a quick post with some stats here ).

First widget is the number of asteroids destroyed, here's a clipped screeny

widget_rock.png

Rather than just having a number and that's it, I'm trying to liven these widgets up by hopefully making them interesting. One thing the uk newspapers love doing is when say a new Dinosaur is discovered, they compare it to either black cabs or double decker buses, as apparently if they just put a figure we can't picture it. Shove a bus next to it, and we get it.

So in the spirit of a dumbed down newspaper article, I thought let's compare the weight of the total number of asteroids destroyed to double decker buses.

Figuring out the value is where it gets boring, and as it bored me I'm just passing it on. We're all about sharing. Plus regular readers will know every single time I put any sort of maths on here I get it wrong, so this post is like a sanity check for my calculations.

How big is an asteroid in the game ? Around 96 pixels. The player ship is around 45 pixels, so my train of thought was to set a real life size for the ship, multiply that by 4, and that would be the cubic size of an asteroid. I figured I'd just compare the ship to something in real life, and a F-15 Eagle seemed a good size. They're 63 feet long so let's pretend our ship is 63 feet long and wide too.
That makes our 96 pixel asteroid 252 cubic feet. In meters that's 76. The reason to covert to meters ? 'Cause this page gives us the some nice weight figures which need meters. Apparently we're looking at 3 tons per cubic meter, therefore our rocks weight is 76*3=228 tons. We're nearly there. All we need now is the all important weight of a bus, and the net never lets you down does it ? I went for unladen which is 12 tons. Almost there.

(numberOfRocks*228)/12

That's it. All this effort just for a stat. I think there are going to be another 8 or so widgets, so I'm going to have to come up with more silly things to compare the stats to. Now best you move along otherwise you're going to be asleep soon.

Squize.

Saturday, July 25, 2009 5:59:13 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [4]  |  Trackback
 Wednesday, July 22, 2009
Nice descriptive title for a change, mainly 'cause I've spent ages hunting around for a solution myself, so if you're coming here for just this very reason, I understand your pain.

We had issues with mochibot on Invaders Must Die, but by then I'd passed it over to Mousebreaker so they had to deal with it in-house. It's the first time I've hit it face on so to speak.

To set the scene and get you in the mood, I've just finished the viral version of cronusX and wanted to try the version control that mochi offer, as that was one of the best things about gameJacket, plus I've added a twitter option to the game which I know is going to bite me on the arse with people bitching it's spyware etc. etc. so I wanted the option to rip that out if it was becoming too much of a ball ache.
I've also included mochi-bot tracking so Candystand can see how it's doing out in the wild, so I've dropped that in too.

Everything worked first time ( That's where it differs from gameJacket and it's constant security check failures ), gave it a quick test, all good. Ah, not all good, the transition craps out to a black screen.

Testing it locally ( Turns out I've not installed the browsers debug version of FP10 on the pc, how good am I ? ) it spat out an error,

Error # 2121: Sandbox Security Violation: BitmapData.draw

etc.

Joy.

Lots of googling ( To no real avail, hence this post ), seeing the only advice on the mochi forums that I could find was someone copying out some text from the Flash help ( Cheers for that, solves everything ) it turns out that the mochi-bot can't be on your root display object, as you get all the security violations when trying to use draw(); that you would if you tried loading in say an image and using draw(); on that ( It's a cross domain thing. To be honest life is too short to go into it, I could just copy from the as3 docs, but... ).

Basically, long story cut shorter, if you do something like:

MochiBot.track(this,"yourMagicNumber");

In your preloader ( Assuming that "this" is your main preloader class than extends MovieClip ) then you can't use draw(); in your main code.

To get around this, give it a movieclip instead, eg.

mochiBotHolder=new MovieClip();
stage.addChild(mochiBotHolder);
mochiBotHolder.addEventListener(Event.ADDED_TO_STAGE,triggerMochiBotTracking);

private function triggerMochiBotTracking(e:Event):void{
    mochiBotHolder.removeEventListener(Event.ADDED_TO_STAGE,triggerMochiBotTracking);
    Security.allowDomain("http://core.mochibot.com/my/core.swf");
    MochiBot.track(mochiBotHolder,"yourMagicNumber");
}

Now I'm not sure if you need the allowDomain(), but it works with it, and that's good enough for me after losing a good hour and a half to this little gem of a problem.
Because the mochibot code needs to hit loaderInfo.loaderURL via the clip you pass it, you have to add your holder mc to the stage, and then wait until it's actually there before calling MochiBot.track, otherwise you're in the land of null properties.

Now I'm not sure if you can kill that holder mc of after calling the track() method, it's like the allowDomain(), it works as it is, and I'm happy with that.

This has proved to be a fairly techy / geeky post, quite rare from me.

Squize.

Tuesday, July 21, 2009 11:00:58 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [6]  |  Trackback
 Tuesday, July 14, 2009
In the last two articles  we made a maze and then destroyed it by adding a lot of free space. In part 3 of this articles I'm going to add some rooms to the empty space and add some doors ...

Part 3 - part 1 - why seperate things and make rooms?

At a first glance it might not be necessary to seperate data into dungeon, room and cell, but thinking ahead a bit it should make sense ...
My idea is that you can have the dungeon which holds the complete map (with the basic room data rendered into it) and the rooms so you access them easier and most important do some magic with them later. One of the neat things is that you could use a different tiling for rooms making them more detailed or use the additional map data for skinning (when rendering the dungeon into a tile based map).

The main difference between a room and a dungeon is that the room consists of empty cells and has walls along it's boundaries. So the first additional method we'd add to the Room class will be the init method, which simply sets all cells so they form a rectangular room.

public function initCells ():void {
            
    var x:uint;
    var y:uint;
    var cellTmp:Cell;
            
    for (x = 0; x < this.iWidth; x++) {
        for (y = 0; y < this.iHeight; y++) {
                    
            cellTmp = this.cell2(x, y);
            cellTmp.setWalls(WallType.OPEN);
                  
            if (x == 0) cellTmp.setWall(Dir.WEST, WallType.WALL);
            if (x == (this.iWidth - 1)) cellTmp.setWall(Dir.EAST, WallType.WALL);
            if (y == 0) cellTmp.setWall(Dir.NORTH, WallType.WALL);
            if (y == (this.iHeight - 1)) cellTmp.setWall(Dir.SOUTH, WallType.WALL);
        }
    }
}

We could later add methods to create different rooms (ie. two overlapping rectangles, circular rooms), but for now that will do ...

I also added a getter setter for Offset to the room, so we can modify the x and y pos of the bounding rect that we stored in the map class (you'll see later what's that for).

So we now have a room, but how do we get that sucker into the map, or (as just putting it into the map isn't really an issue) where can we place it *best*.

There are a few things that I want to watch when placing the rooms ...
- a room should not overlap any existing room, we rather don't at it
- placing a room at a place where it doesn't touch anything, is something we don't want, too
- rooms overlapping corridors should be avoided
- rooms touching dead ends is something we want (what's nice than finding a room after a long winded corridor?)
- rooms touching any sort of wall is OK, too

Yet again we (as humans) could just look at the map and say "here, there and there" and done, but that stupic piece of plastic cannot... we need to apply some sort of scoring to the whole placement mess.

Here's a bit of pseudo code ...
  • start with a VERY high best score, lets say 999999999999 and set current score to 0 ...
  • Loop over every cell in the dungeon
    • at any given position check if the new room overlaps any rooms already in there
      if so, we add 5000 to our current score, otherwise we add nothing
    • now loop over every cell in the room and compare it with the current dungeon cell (offsetting the room to the current position)
      • if the current room cell touches an empty cell (in the dungeon), add 10
      • if we touch a wall, add 3
      • if we touch a dead end, add 1
    • if the final current score is lower than the best score, sreplace the best score and store the current position as the best possible location
  • if the score is higher than the "room overlaps room" score, assume that it only can be placed overlapping a room and drop it, otherwise add it to the dungeon
Here is the scoring code:

private const TOUCH_DEADEND:int = 1;
private const TOUCH_WALL:int = 3;
private const TOUCH_EMPTY:int = 10;
private const OVERLAP_ROOM:int = 5000;
private const OVERLAP_CORRIDOR:int = 100;
        
public function fitMazeRoom (myRoom:Room):Boolean {
    
    var iBestScore:int = 999999999;
    var iScore:int = 0;
    var pBestPos:Point = new Point(0, 0);
    var pOffset:Point = new Point(0, 0);
    
    var cellDungeon:Cell;
    var cellNext:Cell;
    
    var rectTmp:Rectangle = myRoom.rectBound.clone();
    
    var i:uint;
    
    var x:int;
    var y:int;
    
    var xx:int;
    var yy:int;
    
    var iRoomID:uint;
    
    var bAddRoom:Boolean = false;
    
    // loop over map (- roomsize)
    for (y = 0; y < (this.iHeight - myRoom.iHeight + 1); y++) {
        for (x = 0; x < (this.iWidth - myRoom.iWidth + 1); x++) {
            
            // do the scoring ...
            iScore = 0;
            
            // check room/room overlapping
            rectTmp.x = x;
            rectTmp.y = y;
            for (i = 0; i < this._aRoom.length; i++) {
                if ((this._aRoom[i] as Room).rectBound.intersects(rectTmp)) {
                    iScore += OVERLAP_ROOM;
                }
            }
            
            // check room/dungeon overlapping
            for (yy = 0; yy < myRoom.iHeight; yy++) {
                for (xx = 0; xx < myRoom.iWidth; xx++) {
                    pOffset.x = (x + xx);
                    pOffset.y = (y + yy);
                    
                    cellDungeon = this.cell(pOffset);
                    if (cellDungeon.iType == RoomType.CORRIDOR) iScore += OVERLAP_CORRIDOR;
                    
                    if (yy == 0) {
                        iScore += this.getCellScore(this.cell(this.getNextPos(pOffset, Dir.NORTH)), Dir.NORTH);
                    }
                    if (xx == (myRoom.iWidth - 1)) {
                        iScore += this.getCellScore(this.cell(this.getNextPos(pOffset, Dir.EAST)), Dir.EAST);
                    }
                    if (yy == (myRoom.iHeight - 1)) {
                        iScore += this.getCellScore(this.cell(this.getNextPos(pOffset, Dir.SOUTH)), Dir.SOUTH);
                    }
                    if (xx == 0) {
                        iScore += this.getCellScore(this.cell(this.getNextPos(pOffset, Dir.WEST)), Dir.WEST);
                    }
                }
            }
            
            if (iScore < iBestScore) {
                iBestScore = iScore;
                pBestPos = new Point(x, y);
            }
            
        }
    }
    
    // add to dungeon if it doesn't overlap any other rooms
    if (iBestScore < OVERLAP_ROOM) {
        myRoom.pOffset = new Point(pBestPos.x, pBestPos.y);            
        bAddRoom = true;
    }
    
    return bAddRoom;
    
}

private function getCellScore (cellNext:Cell, iDir:int):int {
    
    var iScore:int = 0;
    
    if (cellNext.iType == RoomType.CORRIDOR) {
        if (cellNext.isDeadEnd) {
            iScore += TOUCH_DEADEND;
        } else if (cellNext.hasWall(Dir.getOppositeDir(iDir))) {
            iScore += TOUCH_WALL;
        } else {
            iScore += TOUCH_EMPTY;
        }
    } else {
        if (cellNext.iType == RoomType.ROOM) {
            if (cellNext.hasWall(Dir.getOppositeDir(iDir))) {
                iScore += TOUCH_WALL;
            } else {
                iScore += TOUCH_EMPTY;
            }
        } else {
            iScore += TOUCH_EMPTY;    
        }
    }
    
    return iScore;
    
}

That's ugly and not very fast, but it works.

Some additional info about adding the room to the dungeon: whenever we place a room cell in the dungeon map it's a good idea to check if it overwrites a corridor and if it's a cell on the outer bounds of the room add a new wall to the touching cell (if it's not empty) ...

So far so good, we have rooms in the map, but they cannot yet be reached because we're missing doors ...

Part 3 - part 2 - adding doors and cleaning up

You might ask why I haven't added the doors as soon as I've added the room to the dungeon (and I might just reply that I just didn't mention it), but nope, I didn't add doors - that's the next step.

The reason is quite simple, though. I don't want doors cluttered all over the space and because of that I added another (optional) thing to the room data: hasDoorInDirection ... this way we make sure that there is only one door per wall / room when we add doors ...

Yet again we loop over all rooms and over their outer bounding cells, if we touch another cell, store the current position as possible door location. Then pick a random one per direction and check if it touches another room and if this room might already have a door ...
I guess that's easier to explain with some more code:

private function createDoors (myDungeon:Dungeon, bOneDoorPerRoom:Boolean = true):void {
    
    var rnd:MersenneTwister = MersenneTwister.getInstance();
    
    var i:uint;
    var j:uint;
    
    var x:uint;
    var y:uint;

    var cellTouch:Cell;
    var myRoom:Room;
    
    var aDoor:Array;
    var pDoor:Point;
    var pNext:Point;
    
    for (i = 0; i < myDungeon.aRoom.length; i++) {
        
        myRoom = (myDungeon.aRoom[i] as Room);
        
        aDoor = [[], [], [], []];

        // collect possible door locations ...
        for (y = 0; y < myRoom.iHeight; y++) {
            for (x = 0; x < myRoom.iWidth; x++) {
                
                pDoor = new Point(myRoom.pOffset.x + x, myRoom.pOffset.y + y);
                
                if (y == 0 && pDoor.y > 0) {
                    pNext = myDungeon.getNextPos(pDoor, Dir.NORTH);
                    cellTouch = myDungeon.cell(pNext);
                    if (!cellTouch.isUnused || cellTouch.iType == RoomType.CORRIDOR) {    // the check for a cooridor is needed because they might be just one cell long ...
                        aDoor[Dir.NORTH].push(pDoor);
                        if (cellTouch.isDeadEnd) aDoor[Dir.NORTH].push(pDoor); // double chances for dead ends ...
                    }
                }
                
                if (x == (myRoom.iWidth - 1) && pDoor.x < (myDungeon.iWidth - 1)) {
                    pNext = myDungeon.getNextPos(pDoor, Dir.EAST);
                    cellTouch = myDungeon.cell(pNext);
                    if (!cellTouch.isUnused || cellTouch.iType == RoomType.CORRIDOR) {
                        aDoor[Dir.EAST].push(pDoor);
                        if (cellTouch.isDeadEnd) aDoor[Dir.EAST].push(pDoor); // double chances for dead ends ...
                    }
                }
                
                if (y == (myRoom.iHeight - 1) && pDoor.y < (myDungeon.iHeight - 1)) {
                    pNext = myDungeon.getNextPos(pDoor, Dir.SOUTH);
                    cellTouch = myDungeon.cell(pNext);
                    if (!cellTouch.isUnused || cellTouch.iType == RoomType.CORRIDOR) {
                        aDoor[Dir.SOUTH].push(pDoor);
                        if (cellTouch.isDeadEnd) aDoor[Dir.SOUTH].push(pDoor); // double chances for dead ends ...
                    }
                }
                
                if (x == 0 && pDoor.x > 0) {
                    pNext = myDungeon.getNextPos(pDoor, Dir.WEST);
                    cellTouch = myDungeon.cell(pNext);
                    if (!cellTouch.isUnused || cellTouch.iType == RoomType.CORRIDOR) {
                        aDoor[Dir.WEST].push(pDoor);
                        if (cellTouch.isDeadEnd) aDoor[Dir.WEST].push(pDoor); // double chances for dead ends ...
                    }
                }
            }
        }
        
        // now just pick one door per side ...
        for (j = 0; j < Dir.NUM_BASEDIR; j++) {
            
            if (aDoor[j].length > 0) {
                
                pDoor = aDoor[j][rnd.Range(0, (aDoor[j].length - 1))];
                pNext = myDungeon.getNextPos(pDoor, j);
                
                if (!myRoom.hasDoor(j)) {
                    myRoom.setDoor(j, pDoor);
                    
                    if (bOneDoorPerRoom && myDungeon.cell(pNext).iType == RoomType.ROOM) {
                        myDungeon.getRoom(myDungeon.cell(pNext).iValue).setDoor(Dir.getOppositeDir(j), pNext);
                    }
                    
                    myDungeon.cell(pDoor).setWall(j, WallType.DOOR);
                    myDungeon.cell(pNext).setWall(Dir.getOppositeDir(j), WallType.DOOR);
                }
            }
        }
    }
}

Viola done ... but wait one more thing, cleaning up ...

The last step might not be needed, but imho it makes some nice dungeons: after we've added all the rooms and doors, we remove all remeaning dead ends. This way there will be no corridors just ending somewhere and the map looks nicer.

So we just run the removeDeadEnds method again, this time with 100% ... now:done.

As with the last parts, the link to a working demo of the whole mess is here or here.

nGFX

Tuesday, July 14, 2009 11:56:39 AM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [4]  |  Trackback
 Monday, July 13, 2009
I just needed to do some eye-candy the other day as a hour break from the current project, so I came up with this ribbon effect

ribbon_grab.png

Originally I did it with sprites and masks but it proved to be running too slow on most peoples machines, so a quick hack later, and it's using copyPixels and running a lot quicker and I managed to make the ribbon itself smoother.
A fairly simple old school effect, but it does look kinda cool.

Check it out here.

Squize.

Monday, July 13, 2009 8:51:34 AM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, July 09, 2009
"Be as select in those you endeavour to please, as in those whom you endeavor to imitate. Without love of fame you cannot do anything excellent"

Sir Joshua Reynolds 1772.

cronusx.jpg

X++, with the new name of cronusX, is live on Candystand.com today.

I'd like to thank everyone involved in it, especially Olli and all of you who took the time to provide great feedback here during it's development.

Enjoy the destruction

Squize.

 | 
Thursday, July 09, 2009 2:24:31 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [5]  |  Trackback
 Wednesday, July 08, 2009
9thJuly.png

...tomorrow

Wednesday, July 08, 2009 3:45:05 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, July 03, 2009
This time it's one of ours! Seems like forever since we've had something of our own to talk about ( There will be another game on the 9th of July to mention too, but more of that nearer the time ).

AQI Zoneout is the fella this time.

zoneOut_grab.jpg



It's a really quite good puzzle game that I worked on for our friends at theBasement.tv ( Check their demo reel out, seeing that really made me want to work with those guys )

It was a slightly weird development process for me, as I broke off before the game was actually finished for various asset related reasons, so that in combination with it already been planned out really well beforehand stops it feeling like it's mine, it's more something I worked on rather than made, if that makes sense.

As a project though, it was a real joy and a real pleasure to get to work with ickydime on a game together, you couldn't ask for a better go-between at a company, and the work that was done to the game after it left my sticky fingers has really raised it up, there's a lot of love in there.

I was asked as part of this pimp to also mention Miles design who were the agency for the project, Fat Atom who did the sweet php magic so you can see your name on the high-score table and of course cleanairmatters.org who are the end client.

That's more than enough words, give it a go, it'll help eat into your Friday before home time, hurray!

[ Update, it's now on Kong and NG. So if you don't want to play it on it's nice custom page, go to either site and look at the ads whilst you play ]

Squize.

Friday, July 03, 2009 2:51:33 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Wednesday, July 01, 2009
I've found myself defending oop recently, and at the same time bemoaning the lack of good oop tutorials with regards game development.

Rather than promising to write a full blown tutorial, as to be honest there's no way I'll stick to it, after around 3 parts at most I'll be bored stupid, I thought I'd try and explain the structure behind an existing game which we've already posted the source to. So it may be an idea to open this link in it's own tab.

Ready ? Ok, let's go.

opCFlow.png


Look at that, made with a free package so you get the watermark, nothing's too cheap for you dear reader.

This at heart is how I've structured all my games since starting with as2. It only varies in that there are sometimes more classes in there, the actual hierarchy never changes.

Ok lots of code here is never that great to read, but there's no way around it, sorry,

package {
    import Classes.Init;
    
    import flash.display.DisplayObject;
    import flash.display.Sprite;
    import flash.display.Stage;
    import flash.display.StageQuality;
    import flash.display.StageScaleMode;
    import flash.events.Event;

    [SWF(width="600", height="400", frameRate="35", backgroundColor="#000000")]

    public class Main extends Sprite{

//---------------------------------------------------------------------------------------
// Properties
//---------------------------------------------------------------------------------------
        private static var instance:Main;
        private static var __parent:DisplayObject;
        private static var stage:Stage;
        private static var init:Init;
        
//---------------------------------------------------------------------------------------
// Static ( Singleton )
//---------------------------------------------------------------------------------------
        public function Main(){
            if(instance){
                throw new Error( "Singleton and can only be accessed through Singleton.getInstance()" );
            } else {
                instance=this;
            }        

            instance=this;
//When we are running from our preloader, comment this out
            waiting();
        }

//---------------------------------------------------------------------------------------
// Public
//---------------------------------------------------------------------------------------
        public override function toString():String {
            return "Main";
        }        

//---------------------------------------------------------------------------------------
        public function waiting():void{
            addEventListener(Event.ADDED_TO_STAGE,mainAddedToStage);
        }

//---------------------------------------------------------------------------------------
// Getters
//---------------------------------------------------------------------------------------
        public static function getInstance():Main {
            return instance;
        }

//----------------------------------------------------------------------
        public function getMainMovie():DisplayObject{
            return __parent;    
        }

//----------------------------------------------------------------------
        public function getStage():Stage{
            return stage;    
        }

//----------------------------------------------------------------------
        public function getInit():Init{
            return init;    
        }

//---------------------------------------------------------------------------------------
// Private
//---------------------------------------------------------------------------------------
        private function mainAddedToStage(e:Event):void{
            stage=this.stage;
            stage.showDefaultContextMenu=false;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.quality=StageQuality.LOW;
            stage.stageFocusRect=false;

            __parent=this.root;
            
            init=new Init();
        }


//---------------------------------------------------------------------------------------
    }
}

Hopefully this should be fairly straight forward ( I did actually grab it from somewhere else when looking for an as3 version which matched what I was doing in as2, so sorry original author, I can't tell anymore what's yours and what's mine ). We're basically waiting around until this instance is added to the stage ( If you try and read some properties before it's technically been created then you're going to get errors. ) and then running the mainAddedToStage method. Importantly we have some getter methods set up here, as we need them ( More later ). This is our document class, and as such it has a "direct" link with the swf ( For want of a better term ). From now on throughout every other class, any reference to stage or Main is obtained from this class.
The very last line you'll see we create a new instance of the Init class ( init=new Init(); ), so let's check that class out.

package Classes {
    import flash.display.DisplayObject;
    import flash.display.Sprite;
    import flash.display.Stage;

    public class Init {
//---------------------------------------------------------------------------------------
// Properties
//---------------------------------------------------------------------------------------
        private var gameController:GameController;
        private var attract:Attract;
        private var soundHandler:SoundHandler;
        
//------------------------------------------------
// System
//------------------------------------------------
        private var main:Main;
        private var mainMovie:DisplayObject;
        private var stage:Stage;
        
//---------------------------------------------------------------------------------------
//Constructor
//---------------------------------------------------------------------------------------
        public function Init(){
            main=Main.getInstance();
            mainMovie=main.getMainMovie();
            stage=main.getStage();

            soundHandler=new SoundHandler();
           
            gameController=new GameController();
            attract=new Attract();
        }

//---------------------------------------------------------------------------------------
// Public
//---------------------------------------------------------------------------------------
        public function toString():String {
            return "Init";
        }        

//---------------------------------------------------------------------------------------
// Getters
//---------------------------------------------------------------------------------------
        public function getAttract():Attract{
            return attract;
        }

//---------------------------------------------------------------------------------------
        public function getGameController():GameController{
            return gameController;
        }

//---------------------------------------------------------------------------------------
        public function getSoundHandler():SoundHandler{
            return soundHandler;
        }

//---------------------------------------------------------------------------------------
    }
}

This again is a bit of a nothingy class. In theory it could be shoved into the Main class with no real harm, the reason I don't is pure lazyness. I like being able to copy the Main class to every new project without having to think about it, i.e aside from the [swf] tag I never have to alter it. It just feels cleaner having that first class do very little.

If you look at our Init class you'll see we just create instances of each class we need ( Refer to our cheapo diagram to help clarify ), and set up some getter methods again ( Just to clarify, a method is exactly the same as a Function, it's just that if you use a function in oop it's called a method instead. I don't know why either ), so other classes can get a reference to these instance's if they need to.

How do I decide which classes sit on which row of the hierarchy ? Main is our document class, so he's at the top and doesn't really do much. Init is Main's love child, and doesn't do much either, he's just creating the actual game classes.
The next row is more interesting. Attract is our front-end. It deals with the title screen, the game over screen, hi-score entry if the game supports it, instructions etc. Basically everything that happens before starting the game and after the game has finished.
Let's jump over to SoundHandler. This is just what it says. It's here because it's used by both Attract and GameController, as both need to have sounds. GameController is a simple class like Init, it handles the creation of all the child classes needed for the game ( Check the diagram ).

Let me try and rationalise why Attract, GameController, SoundHandler are all on the same row, in effect why they're equal to each other. We've already established that Attract and GameController need to be aware of the SoundHandler class as both need sounds. In the grand scheme of things, the Attract class doesn't really need to know about GameController ( Remember Attract is everything but the actual game ) and conversely the GameController really doesn't give a crap about Attract. If one of these classes is working, then the other isn't doing anything, so it makes sense to me that they're on the same row.
Does that mean they never need to chat to each other ? No, but it's at very key points. For example, when the player presses the "Play Game" button in Attract, we kill off all the Attract mode things and then call startGame() method in GameController.

Now we come to something cool. Quite a few times I've seen people moving over to oop, and it's all going well, until they need a reference to a different class. How the hell do we get the reference we need ? No one wants to pass a reference to a class via a constructor, as that's just messy and nasty and really easy to screw up, and it's not like we can be old school and use _root.myMethod(); when we're oop gods.

Notice all my constructors are the same, eg,

//------------------------------------------------
// System
//------------------------------------------------
        private var main:Main;
        private var mainMovie:DisplayObject;
        private var stage:Stage;
        
//---------------------------------------------------------------------------------------
//Constructor
//---------------------------------------------------------------------------------------
        public function GameController(){
            main=Main.getInstance();
            mainMovie=main.getMainMovie();
            stage=main.getStage();
        }

We get a reference to our document class, Main and store it in our local var main. We also store away a reference to our mainMovie ( To be honest I don't think I've used that ref. in as3 yet, it's just a throw back to my as2 code, but I'm a creature of habit so it'll stay there 'til as4 ) and the Stage.

So this is the GameController class, and it's game over, so we want to call the Attract class as he deals with that. This is how we get our reference:

main.getInit().getAttract().gameOver();

Check back with our Main class code up there, see getInit() in the getters section ? It returns a reference to our Init instance. Then look at the Init getters, getAttract() gives us our instance of Attract, and from there we're just calling the method gameOver in the Attract class.
Don't worry if this doesn't make sense straight away, it's quite a bit to get your head around in one go. Basically so long as we have a reference to Main in any class, we can get a reference to any other class no matter how far away from the callee class we are. References aren't a pain in the arse anymore, they're as simple as this.

You may be thinking, how the hell am I going to remember all those getOurInstanceName() method calls. Auto-completion. Flex does all this for you so it's never an issue ( And I'm sure Flash Develop does the same ). I don't know about the Flash IDE, I really don't hold much faith in it doing it, so now may be the time to install Flash Develop and have a play with that.

See we've got the basics of oop hierarchy covered, and I've not had to slip into terms like singleton and composition. Feel free to google both terms, see what they actually mean, and hopefully when you read the flowery descriptions you'll realise that you already get them as we've covered them here.

As always feel free to fire over questions or point out glaring errors in what I've done. Like I said all my games pretty much follow this pattern, which makes sharing code between projects a lot easier plus I'm not having to think about how classes are inter-connected. I'm not suggesting you just blindly copy what I've done, but perhaps plan out your next game using some simple flowchart boxes so you can see where classes should be connected and where it'll be a waste of time to do so.

Squize.

Wednesday, July 01, 2009 1:27:56 AM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [12]  |  Trackback