Gaming Your Way

May contain nuts.

AS3 OOP, without the bollocks, part 1

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.

Comments (12) -

  • lpmaurice

    7/1/2009 1:53:04 AM |

    Actually... what you wrote is considered as anti-oop as it can be. To me, it sure is better than the old AS on the timeline way, but having classes know they need to ask for instances of classes that act as globals (Main, stage, etc) is not a good idea as it means they're ultra coupled. You should instead inject dependencies. Like:

    var gameController:GameController = new GameController();
    gameController.stage = myStage;

    That way, you can set a different stage. One of the main rules of oop is "tell, don't ask".

  • John Cotterell

    7/1/2009 6:34:24 AM |

    @lpmaurice you're right in theory, but like lots of OOP, it's allowing contingency for things we know in practice aren't going to happen.

    @squize im not sure i'd have bothered separating init and main. you may have more than one init, but never at the same time, so it could just be a method of main. But I prefer as few classes as is possible without making a mess.

  • Tom

    7/1/2009 7:15:25 AM |

    Howdy,

    Just a quick note to say i'm still reading.. your posts are always good for a laugh.. no wait that sounded bad? :)

    Cheers for the effort in this one, interesting to see how other people handle the referencing problem - it is definitely one of the more difficult things when moving to OOP.

  • Iain

    7/1/2009 10:08:02 AM |

    Hi squize, thanks for letting us through the keyhole. Like your structure in general, but I'm going to disagree with this bit:

    "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"

    Really? I do this and can't remember ever screwing it up. Singletons are the least OOP thing in all of OOP, so I don't think we should just default to them without good reason. As much as possible I use events so I don't need any dependencies. For example, when my equivalent of the attract class is ready to start it will dispatch an event, which will be picked up by my main class, which will then call start on my game class. It's a tiny bit more code, but it's de-coupled, and I can swap out or reuse either the game or attract easily.

    Re. your general point about state of OOP info in Flash games, I'd say it's a shame that some people are writing 2000-line classes and thinking that's oop.

    But really these things are boring details and nothing to do with the fun of developing games, which is watching things bounce around on the screen, right?

  • Squize

    7/1/2009 12:06:16 PM |

    Oh guys, proper responses ? Come on, you know comments are just so visitors can swear on a blog without being told off.

    LP: Injections just don't it for me, for a number of reasons. It means any class that creates instances of another one has to inject the child class with any references it needs.
    That to me is prone to errors and a bit of a pain. Let's say my stage reference changes like in your example, I'm having to go through all those classes to ensure it points to the correct one ( Or, they're hitting a static class with all those kind of properties in, which isn't a million miles away from how I'm doing it now, it's just I'm doing it at a really low level so I can find my way around ).
    Also with injections if you need to use any of that data in the constructor then I guess you have to pass it in as an arg / param when you create your new instance.

    To be honest I prefer empowering each class so it's more self aware. Each class just by having that one ref to Main can find out exactly what it wants ( So long as there's a getter or public access etc ) and I'm not having to update params passed at construction time as a game is updated and I realised the baddieHandler needs a ref to the particleHandler 'cause the baddie children classes need to call it.

    John, yeah Main and Init are just a personal pref. of mine. I like Main to be a singleton and as independent of whatever else is to come as much as possible. The Init here is stripped down a little, I normally have the fps counter class triggered in there, and other bits and bobs, and I just don't want to clutter up Main with that.

    No worries Tom. Take it on board I'm far from what I'd consider an expert at oop, it's just this is my way and it works really well for me, to the point that it never changes from one project to the next. So this structure was used in Polarity same as it was GoldenBalls. I never have to fuck about with _root ( or root ) references so doing it this way if you need to drop the entire game into a mc in another swf, it's down without altering any code in the game at all ( I did this just the other day that's why I know :) ).

    Iain I think I've kinda covered off why I'm not a fan of passing params in my reply to lp ( Yeah that's right, I'm wishing I'd stayed the fuck away from this now :) ).

    I've never been a real oop zealot, esp. when it comes to games, 'cause it is all about the bouncy things running 1fps quicker than everyone else's bouncy things rather than writing oh-so-well structured code which no one will ever see ever.
    But following a recent disagreement on FK.games I came to realise that as2 has been out for 6 years, and yet if you look for a game tutorial using oop they're either ultra simple and very purist oop, or the author thinks var score:Number=0; is oop.

    I guess I'm preaching to the converted here, but I think the indie scene would move on much quicker and reach greater heights if there was more of a push towards oop tuts that are based on real life, rather than pureMVC style bollocks, which I think just switches people off to the whole concept of oop ( I know it did me ) or the whole as1.5 tuts.

    Right, I better go and resize that flow diagram that fitted the blog so well last night on the mac, but in the cold light of morning looks shit on the pc.

  • Iain

    7/1/2009 1:06:37 PM |

    "I'm not having to update params passed at construction time as a game is updated and I realised the baddieHandler needs a ref to the particleHandler 'cause the baddie children classes need to call it."

    Mate I've been there! That's why I do have a game singleton for all the game-play stuff, just not for the UserInterface / menus etc stuff, where trad OOP approaches are tried and tested.

    What are your thoughts on PushButtonEngine? I think they don't have a big chance of uptake because any flash dev who would want to use it wouldn't have the required technical knowledge - because of the AS1.5 issue.

  • Squize

    7/1/2009 1:56:08 PM |

    It's funny that a lot of people have the view that oop is really restrictive, a really regimented thing, that tries to force us all into coding the same way.
    In real life, I imagine 20 coders could comment here and all 20 would do it differently from each other to some degree.
    It's like Monopoly, where no family in the world plays by the proper rules, so everyone has their own little variation on it, yet it still works.

    I've really not looked at PushButton beyond a bit of blurb on the site. Personally I can think of very few tasks which are more thankless than doing something like that, as it has to be all things to everyone, which it can't ever be, so as the developer of a generic engine you'll be getting the shit for things that aren't in the engine ( As you could never even imagine a fraction of things that people would want ) and all peoples lame code which doesn't work but they blame it on the engine.
    As to the concept, it may get some GameMaker indies to dabble in Flash, or be used for beginners to get that head start before writing up their own stuff or for old farts to look through the code with a view to stealing the useful things :)

    As with all things like this, the 1st party games will probably rock, and all the 3rd party things will just shoe horn in as many features as they can and be a bit toilet.

  • Marcus Geduld

    7/1/2009 1:59:46 PM |

    Hi. Nice article. But I have a question.

    Your document class, Main, has a reference to this.root. My understanding is, in AS3, root is a reference to the document class itself. If I'm right, there's no difference between a document class's this property and its this.root property. (Of course, for other classes, this and this.root ARE different. this references the class itself; this.root references the document class.

    Since your article confused me, I created this test:

    package
    {
      import flash.display.Sprite;
      import flash.events.Event;
      
      public class DocumentClass extends Sprite
      {
        public function DocumentClass()
        {
          addEventListener( Event.ADDED_TO_STAGE, addedToStageHandler );
        }
        
        private function addedToStageHandler( event : Event ) : void
        {
          trace( this );
          trace( this.root );
        }
        
      }
    }

    Sure enough, my traces resulted in...

    [object DocumentClass]
    [object DocumentClass]

    Is your reference to this.root just a holdover from your AS 2 days, or am I missing something?

  • Marcus Geduld

    7/1/2009 2:13:00 PM |

    "LP: Injections just don't it for me, for a number of reasons. It means any class that creates instances of another one has to inject the child class with any references it needs.
    That to me is prone to errors and a bit of a pain."

    This is what factories are for!

    The "problem" with your method is that it tightly couples your classes. However, if you always use these classes as is, that's not really a problem. The general OOP assumption is that you're going to moving individual classes from project to project and that you're going to be ripping classes out of projects and replacing them with new classes. In such cases, it's wise for your classes to have as little knowledge of each other as possible. If that's never a part of your workflow, you don't need to worry about injections.

    I agree with other posters here that, in general, your non-injection method is a bad practice. However, it doesn't pay to be dogmatic. For your workflow, I can see how it works.

  • Squize

    7/1/2009 2:17:06 PM |

    ha, busted :)

    Yeah, more as2 which I haven't shaken off. When I first started as3 it was such a miracle to be able to publish even the simplest things without getting 400 errors that once I got past that hurdle, I didn't want to upset anything.
    I always had the view that one day I'd go back and tidy these things up so I'm not copying and pasting junk code forever more, but it's never happened.

    Basically if you see something that looks stupid and wrong in my code, it more than likely is :)

    Thanks for flagging it up though Marcus.

  • Squize

    7/1/2009 2:27:09 PM |

    Oops, missed your follow up post.

    Yes doing it the way I do it is not oop compliant to the nth degree, but as every project always has a Main singleton, any class I write can expect that

    var main:Main=Main.getInstance();

    Will always produce the goods, so it's never been an issue for me. If anything it makes me code to a pattern on every project so I never really go off on a tangent on any game and produce code in a format which is only ever any use to that one project ( If that makes sense. I'm kind of a victim and beneficiary of my own approach ).

    Now I'm starting to understand the appeal of as1.5 tuts ;)

    ( Just thinking, I think this may all stem from,

    move.l 4,a6

    On the Amiga, where 4 was the only absolute memory address and everything was an offset from that. Main is my 4 ).

  • John Cotterell

    7/1/2009 7:43:44 PM |

    @ Iain

    I actually have a 500 line class for one game. No, I don't think that's OOP, it was a lazy mess I was making with my first as3 game. I felt like a kid who'd just rolled around in the mud when I'd finished it.

Comments are closed