02 October 2012

Just got mail from JM Kraappa, asking about how to create game structure, leveling, etc in my game: Escape from Nerd Factory. So I decided to write a series of tutorial about that.



=====================
If you want to read the other part, here are all the links:
1. Game Structure
2. Adding Screen Transition
3. Head-Up Display
4. Working with Data
5. Creating Level using Tiled Map Editor
=====================


First things first:
1. My enggrish is rly sux as hell, so I’m sorry if this article will burn your eyeballs
2. I’m sure this is not a best practice for create game in CE, but rather some kind of method that I always use for creating all of my games, including the games made with CE.
3. I’m not a pro programmer, so I’m using “whatever works” as my idealism. Open-mouthed smile
4. Yay, my first english article in my blog

Actually, I just using game structure from The Phenomenal Emmanuele Feronato, with a lot of twist of course.

The idea is simply divide your game screens into a separate states. So we will have more than just one state in our game project. And it’ll grow even bigger if we’re separate each level in a different states.

Better look at the diagram:
diagram

We’ve got the idea, now let’s jump to the implementation.

MainClass.as
I assume that you’ve already know how to set up a project with CE, so I’ll skip that part, and start throwing the code for the main class:

Before I explain what happens in main class, there is a little bit alteration to the CE CitrusEngine class. I change the access modifier for the method handleAddedToStage(e:Event) to protected. So we can access it in our main class that extending CitrusEngine class. Without that changes, you’ll got some problem in the future, especially if you implement Live Updates API from Mochi.

package src 
{
    import com.citrusengine.core.CitrusEngine;
    import flash.events.Event;
    import flash.utils.getDefinitionByName;
    import src.state.GameOverState;
    import src.state.level.*;
    import src.state.LevelSelectState;
    import src.state.MenuState;    

    public class MainClass extends CitrusEngine 
    {
        public static const MENU_STATE:String = "menu_state";
        public static const LEVEL_SELECT_STATE:String = "level_select_state";
        public static const GAME_OVER_STATE:String = "game_over_state";        

        public function MainClass() 
        {
            super();      
        }        

        override protected function handleAddedToStage(e:Event):void 
        {
            super.handleAddedToStage(e);            

            changeState(MENU_STATE);
        }        

        public function changeState(newState:String):void
        {
            switch(newState)
            {
                case MENU_STATE:
                    state = new MenuState();
                    break;                    

                case LEVEL_SELECT_STATE:
                    state = new LevelSelectState();
                    break;                    

                case GAME_OVER_STATE:
                    state = new GameOverState();
                    break;
            }
        }        

        public function showGameState(level:int):void
        {
            Level1, Level2, Level3, Level4, Level5, Level6;            

            var gameLevel:Class = getDefinitionByName("src.state.level.Level" + String(level)) as Class;
      
            state = new gameLevel(level);
        }
    }
}

The main class itself is just like a state manager, it main job is to change the current state to the new one.

13-15: I create a public static constant for each state’s names. So I can access it in other classes.

17: We have nothing else in constructor but a super() method.

22: Override handleAddedToStage() method and call changeState(MENU_STATE) to make sure we see the menu state when the game loads for the first time.

29: Method changeState(newState: String), public method to change the current state with the new one. It’s public, so we can access it anywhere. Just simply a switch statements, nothing much to explain.

There is another method to change state, and no need the state name constants and switch statements:
public function changeState(state:State):void
{
      this.state = state;
}

then we can call it like this:
changeState(new MenuState);

it’s up to you to pick which method is best for your personal style.

47: The tricky part for this class: showGameState(level:int) method. Because we’ll create a multiple level for the game, and also I divide each level into separate state, so the method to changing state to GameState need a bit of special treatment.

As you can see, I list all the levels in the first line of the function block. Why? Because we can’t simply access the level class it via import, so we need to make a list of them. I’m not really sure what is the exact cause.

To avoid using a very long switch statements, we use a helper method from AS3.0: getDefinitionByName(), so we can just access the level class via String.
As a note, you need to write complete path to the class like this: "src.state.level.Level". Otherwise, it won’t work.

BaseState.as
After the main class is completely done, we can start writing some codes for the state. Because all state, except GameState, has a similar behaviour, it’s better to create a parent state for all of these states. Yep, we’re creating BaseState class:
package src.state 
{
    import com.citrusengine.core.CitrusEngine;
    import com.citrusengine.core.State;
    import com.pzuh.utils.Basic;
    import flash.display.MovieClip;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import src.MainClass;    

    public class BaseState extends State 
    {
        protected var mainClass:MainClass;        

        protected var screen:MovieClip;        

        public function BaseState() 
        {
            mainClass = CitrusEngine.getInstance() as MainClass;
        }        

        override public function initialize():void 
        {
            super.initialize();            

            initScreen();
        }        

        protected function initScreen():void
        {
            if (screen == null)
            {
                throw new Error("ERROR: You forgot to instantiate new screen object :)");
            }            

            addChild(screen);            

            Basic.registerMouseEvent(screen, buttonClicked, buttonOver, buttonOut);
        }    

        protected function buttonClicked(event:MouseEvent):void
        {
            //a concrete implementation of this method  need to be defined in child classes
        }    

        protected function buttonOver(event:MouseEvent):void
        {
            var button:MovieClip = event.target as MovieClip;        

            button.gotoAndStop(2);
        }        

        protected function buttonOut(event:MouseEvent):void
        {
            var button:MovieClip = event.target as MovieClip;            

            button.gotoAndStop(1);
        }        

        override public function destroy():void 
        {
            Basic.unregisterMouseEvent(screen, buttonClicked, buttonOver, buttonOut);
         
            removeChild(screen);
            screen = null;            

            mainClass = null;            

            super.destroy();
        }
    }
}


We have two variables here:
1. mainClass to access the MainClass aka CitrusEngine
2. screen, a movie clip that hold the view for the states.

The other thing are really self explanatory. Just added a mouse event to screen’s button, and remove it on state destroy() method.

Because I’m too lazy always repeating add mouse event to each buttons, I create a helper method to automatically add mouse event to child object of the screen. That’s it: Basic.registerMouseEvent(screen, buttonClicked, buttonOver, buttonOut);

Another important note, since no citrus objects involved here, we MUST define a object removal for every objects we’ve created on the destroy() method. Remove all event listener, movie clip, and other non-citrus objects.
-------------------------------------
The idea of this class is just overlaying the citrus engine state with a movie clip. So, the menu creations will be more simpler as you can build your menu in Flash IDE, wrap it in a single MovieClip, and then just instantiate it in the state class.

If you’re a working with external files, just ask your artist to create a complete screen with button and so on, compile it in a SWC or SWF files, then your job is just to load it inside state class.
We’ll see the example in the next section.


 
MenuState.as
package src.state 
{
    import flash.events.MouseEvent;
    import src.MainClass;
 
    public class MenuState extends BaseState 
    {     
        public function MenuState() 
        {
            super();
        }            

        override protected function initScreen():void 
        {
            screen = new MenuScreenMC();            

            super.initScreen();
        }        

        override protected function buttonClicked(event:MouseEvent):void 
        {
            var buttonName:String = event.target.name;            

            if (buttonName == "playx")
            {
                mainClass.changeState(MainClass.LEVEL_SELECT_STATE);
            }
        }
    }
}


The concrete implementation of BaseState class. As you can see it only overrides two method:
1. initScreen(). Instantiate a MenuScreenMC() here.
2. buttonClicked(). Define each action for the buttons when they’re clicked.

When the user click play button, then call changeState() method form mainClass. Bang! The game screen changed. Smile

The MenuScreenMC is look like this:

menus

As you can see, I create all necessary things here: screen background and button. And give the button an instance name: “playx”, so we can access it via its name.

The other state are pretty much same as menu state. Let’s look at LevelSelectState:

levelSelect

As you can see, there are menu button and six blank buttons. Not really blank, it contains a text field to display the level number. We’ll put the level number via code. But don’t forget to give them an instance name: level_1, level_2, and so on.

override protected function initScreen():void 
{
    screen = new LevelSelectMC();         
   
    super.initScreen();          
   
    var button:MovieClip;           

    for (var i:int = 0; i < screen.numChildren; i++)
    {
       if (screen.getChildAt(i).name.search("level") != -1)
       {
          button = screen.getChildAt(i) as MovieClip;
          button.mouseChildren = false;                    

          button.textx.text = button.name.split("_")[1];
       }
    }
}


That’s the code to add level number to the buttons. Pretty simple, loop through the screen childs, if the name contains “level”, that’s what we looking for.

Simply split its name using “_” as delimiter, so if the button is named “Level_1” then we’ll got an array with two strings: “level” and ”1”. The level number is in index 1, that’s it why we put [1] after split() method.

Also, set property of its mouseChildren to false, so the text field won’t receive any mouse event. Otherwise it’ll cause an error.

And this is the listener for the level buttons.
override protected function buttonClicked(event:MouseEvent):void 
{
    var buttonName:String = event.target.name;            

    if (buttonName == "menux")
    {
       mainClass.changeState(MainClass.MENU_STATE);
    }
    else if (buttonName.search("level") != -1)
    {
       var button:MovieClip = event.target as MovieClip;
       var level:int = button.name.split("_")[1];                

       mainClass.showGameState(level);
    }
}


Pretty similar as before, make sure it’s a level button, gather its level number, and then use it to change the current state into GameState.

GameState
Since game state just extends CE State class, not BaseState class we created before, so it’s up to you how to build the levels. You can use Level Editor, Flash IDE, or else.

And I’ll not explain them here as I never use that kind of tools to build my level. I mostly just write my level code in the class.

Running Time:
And this is it the final product:



 ----------------------------------------
Well, I think it’s enough. I’ll continue it in the next series. It’ll be about creating Head Up Display (HUD), adding transition to your state, and managing game data.

You can download the source code here:

3 comments:

  1. Thanks for such a place !! I am working on a game project and i am following your tutorial but I am having errors and warnings ! inside the MenuState :

    mainClass.changeState(MyCitrus_Demo.LEVEL_SELECT_STATE); it says Multiple markers at this line:
    -mainClass
    -Call to a possibly undefined method changeState

    Can you please help me ? Thanks in advance

    ReplyDelete
  2. Check your MainClass.as, make sure you're not mistyped changeState() method.
    And make sure its access modifier is set to 'public'

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete