Citrus Engine: Creating Menu with Starling & Feathers

My tutorials about creating menu on Citrus Engine are very outdated. The concept might be still relevant, but the code surely won’t compiled unless you have the old version of Citrus Engine. Not to mention that the old CE was only for browser game only, not mobile.

So, this time, in this article, I’ll tell you about how to build game menu on Citrus Engine, which will work on mobile, surely with the help of Starling Framework and Feathers UI, as well as some Starling Extension. Almost forget, this tutorial will also tell you how to create a simple screen transitions.

Citrus Engine Starling Feathers

Yes, we’ll do it via code, not visual editor (Flash IDE)  like on the old tutorial. But don’t worry, Starling & Feathers are very capable for creating complex UI components. So, your work is just simply placing the components on the screen.

Getting Started
Using Flash Develop, you’ll need to create a new mobile project. Yes, mobile, so you can see the result on your mobile devices. As always, I won’t explain the process, since it’s quite basic and you can get some tutorials elsewhere.

The basic idea for the game structure is similar as the old tutorial. Separate each game screen into CE states. And use the Main class as a state machine or screen manager or screen selector, well, whatever you’ll name it. Probably you can read it to get some picture about what will we achieve here.

Alright, let’s create the Main class

package    
{     
    import citrus.core.starling.StarlingCitrusEngine;
    import citrus.core.starling.StarlingState;     
    import flash.events.Event;     
    import starling.core.Starling;     
    import citrus.core.starling.ViewportMode;     
    import com.greensock.TweenLite;     
    import state.MenuState;     
  
    public class Main extends StarlingCitrusEngine     
    { 
        public function Main()     
        {     
            super();     
        }     
      
        override protected function handleAddedToStage(event:Event):void     
        {     
            super.handleAddedToStage(event);     
            
            Starling.handleLostContext = true;     
            Starling.multitouchEnabled = true;     
            
            _baseWidth = 800;     
            _baseHeight = 480;     
            
            _viewportMode = ViewportMode.LETTERBOX;     
            
            setUpStarling(true);     
        }     
        
        override public function handleStarlingReady():void     
        {     
            super.handleStarlingReady();     
           
            state = new MenuState();     
        }     
        
        public function changeState(nextState:StarlingState):void     
        {     
            futureState = nextState;     
           
            TweenLite.to(state, 1, { alpha:0, onComplete:toNewState } );     
            
            function toNewState():void     
            {     
               state = futureState;     
            }     
        }     
    }     
}

We configure the game on the handleAddedToStage() method. Set handleLostContext and multitouchEnabled to true. Google that if you need some explanations about that property.

Also set the _baseWidth and _baseHeight. And set the viewportMode to LetterBox. This means we will build the game for 800x480 devices, but the game will also nicely scaled up and down if it run on a device other than that. For clearer explanation, simply go here.


Changing States & Transition
Now take a look at changeState() method. This method will be used to change state across the game screen. The great thing is that Citrus Engine already provide a support for screen transition by creating a futureState property.

So, inside the method what we do is pass the state object from method’s argument to futureState. Then apply a transition effect to the current state. I’m using TweenLite from greensock here, but surely will also work with other tweening library. Using its alpha property so we can have a nice fading effect. And when the transition is done, then we execute the next state by passing futureState to the current state.

All done for the Main class. Let’s move to the Menu state. This class will extend from Starling state.


Creating Background
To make it more complex, I will use a tiled scrolling background instead of just a static background. And to achieve that, we need this Starling extension: Scroll Image from Tom Krecha

I'm using this tileable & simple image as the background:

Citrus Engine Menu Background


atlas = new TextureAtlas(Texture.fromBitmap(new EmbedConst.ATLAS_IMAGE()), XML(new EmbedConst.ATLAS_XML()));    

var tile:ScrollTile = new ScrollTile(atlas.getTexture(TexNameConst.BG), true);
 
bg = new ScrollImage(stage.stageWidth, stage.stageHeight);
bg.addLayer(tile);
addChild(bg);

First, we need to create atlas that hold the required texture. Then create a ScrollTile object. And pass the texture on its constructor arguments. Now we make the real backround image, using ScrollImage class, and set its size in a same size as the stage width and height. Add the previously created tile to its layer using addLayer() method. Then add the background to the stage.

Done? Not yet. To make it even prettier, let’s apply some diagonal scrolling animation. Override the update() method, and add this code:

if (bg)    
{     
        bg.tilesOffsetX += .2;     
        bg.tilesOffsetY += .2;     
}

Creating Title
Title is just a static image. So nothing to explain here.

title = new Image(atlas.getTexture(TexNameConst.TITLE));    
title.alignPivot();     
title.x = stage.stageWidth / 2;     
title.y = title.height / 2 + 50;     
addChild(title);

Creating Buttons
Since all the buttons will have similar properties, this method will save you several lines of code.

private function getButton(name:String, scale:Number = 1, hasDownTexture:Boolean = true):Button    
{     
     var up:Texture = atlas.getTexture(name);     
     var down:Texture;     
            
     if (hasDownTexture)     
     {     
          down = atlas.getTexture(name.replace("1", "2"));     
     }     
     else     
     {     
          down = null;     
     }     
            
     var button:Button = new Button(up, "", down);     
     button.name = name;     
     button.scaleX = button.scaleY = scale;     
     button.alphaWhenDisabled = 1;     
     button.scaleWhenDown = .9;     
     button.addEventListener(Event.TRIGGERED, buttonClicked);     
            
     return button;     
}

Nothing special, just define the button’s up & down textures, some properties and event listener when the buttpn clicked. 

We're using the Button class from Starling not Feathers. It's enough for our need for now.

The buttons will have two textures: when it is on normal state and when it's clicked. For example:

citrus engine game buttoncitrus engine game button



Now let create some real buttons. But before that, we’ll make a layout for that.

var layout:HorizontalLayout = new HorizontalLayout();    
layout.horizontalAlign = HorizontalLayout.HORIZONTAL_ALIGN_CENTER;     
layout.verticalAlign = HorizontalLayout.VERTICAL_ALIGN_MIDDLE;     
layout.gap = 10;

We create a horizontal layout, set it’s alignment to center for both horizontal and vertical alignment. And set the gap for each button with 10 pixels.
           
var buttonContainer:LayoutGroup = new LayoutGroup();     
buttonContainer.layout = layout;     
buttonContainer.width = 800;     
buttonContainer.height = 100;     
buttonContainer.alignPivot();     
buttonContainer.x = stage.stageWidth / 2;     
buttonContainer.y = stage.stageHeight - buttonContainer.height / 2 - 30;     
addChild(buttonContainer);

Then we’ll pass the layout to a container. Set the container size, pivot point, and position.

With this container no need to set the position of each buttons, since you already set the container position. So simply add  your buttons, and done. Your button will be perfectly arranged on the screen.

play = getButton(TexNameConst.PLAY_1);    
buttonContainer.addChild(play);     
            
score = getButton(TexNameConst.SCORE_1);     
buttonContainer.addChild(score);

All done. And here it is the full code of MenuState class.

package state   
{    
    import citrus.core.starling.StarlingState;    
    import feathers.layout.HorizontalLayout;    
    import feathers.controls.LayoutGroup;    
    import flash.desktop.NativeApplication;    
    import starling.display.Button;    
    import starling.display.DisplayObject;    
    import starling.display.Image;    
    import starling.events.Event;    
    import starling.extensions.krecha.ScrollImage;    
    import starling.extensions.krecha.ScrollTile;    
    import starling.textures.Texture;    
    import starling.textures.TextureAtlas;    
    import constant.TexNameConst;    
    import constant.EmbedConst;    
    import state.GameState;    
    
    public class MenuState extends StarlingState    
    {    
        private var atlas:TextureAtlas;    
        
        private var bg:ScrollImage;    
        
        private var title:Image;    
        
        private var play:Button;    
        private var options:Button;    
        private var score:Button;    
        private var exit:Button;    
        
        private var dispObjectArray:Vector.<DisplayObject> = new Vector.<DisplayObject>();    
        
        public function MenuState()    
        {    
            super();    
        }    
        
        override public function initialize():void    
        {    
            super.initialize();    
            
            atlas = new TextureAtlas(Texture.fromBitmap(new EmbedConst.ATLAS_IMAGE()), XML(new EmbedConst.ATLAS_XML()));    
            
            var tile:ScrollTile = new ScrollTile(atlas.getTexture(TexNameConst.BG), true);    
            
            bg = new ScrollImage(stage.stageWidth, stage.stageHeight);    
            bg.addLayer(tile);    
            addChild(bg);    
            
            title = new Image(atlas.getTexture(TexNameConst.TITLE));    
            title.alignPivot();    
            title.x = stage.stageWidth / 2;    
            title.y = title.height / 2 + 50;    
            addChild(title);    
            
            var layout:HorizontalLayout = new HorizontalLayout();    
            layout.horizontalAlign = HorizontalLayout.HORIZONTAL_ALIGN_CENTER;    
            layout.verticalAlign = HorizontalLayout.VERTICAL_ALIGN_MIDDLE;    
            layout.gap = 10;    
            
            var buttonContainer:LayoutGroup = new LayoutGroup();    
            buttonContainer.layout = layout;    
            buttonContainer.width = 800;    
            buttonContainer.height = 100;    
            buttonContainer.alignPivot();    
            buttonContainer.x = stage.stageWidth / 2;    
            buttonContainer.y = stage.stageHeight - buttonContainer.height / 2 - 30;    
            addChild(buttonContainer);    
            
            play = getButton(TexNameConst.PLAY_1);    
            buttonContainer.addChild(play);    
            
            score = getButton(TexNameConst.SCORE_1);    
            buttonContainer.addChild(score);    
            
            options = getButton(TexNameConst.OPTIONS_1);    
            buttonContainer.addChild(options);    
       
            exit = getButton(TexNameConst.EXIT_1);    
            buttonContainer.addChild(exit);    
           
            dispObjectArray.push(bg, title, buttonContainer);    
        }    
      
        override public function update(timeDelta:Number):void    
        {    
            super.update(timeDelta);    
            
            if (bg)    
            {    
                bg.tilesOffsetX += .2;    
                bg.tilesOffsetY += .2;    
            }    
        }    
        
        private function buttonClicked(event:Event):void    
        {    
            var button:String = Button(event.target).name;    
            
            switch (button)    
            {    
                case TexNameConst.PLAY_1:     
                    Main(_ce).changeState(new GameState());    
                    break;    

               
                case TexNameConst.OPTIONS_1:     
                    break;    
                
                case TexNameConst.SCORE_1:     
                    break;    
                
                case TexNameConst.EXIT_1:     
                    NativeApplication.nativeApplication.exit();    
                    break;    
            }    
        }    
        
        private function getButton(name:String, scale:Number = 1, hasDownTexture:Boolean = true):Button    
        {    
            var up:Texture = atlas.getTexture(name);    
            var down:Texture;    
            
            if (hasDownTexture)    
            {    
                down = atlas.getTexture(name.replace("1", "2"));    
            }    
            else    
            {    
                down = null;    
            }    
            
            var button:Button = new Button(up, "", down);    
            button.name = name;    
            button.scaleX = button.scaleY = scale;    
            button.alphaWhenDisabled = 1;    
            button.scaleWhenDown = .9;    
            button.addEventListener(Event.TRIGGERED, buttonClicked);    
            
            return button;    
        }    
        
        override public function destroy():void    
        {    
            for (var i:int = dispObjectArray.length - 1; i >= 0; i--)    
            {    
                removeChild(dispObjectArray[i], true);    
            }    
            
            atlas.dispose();    
            atlas = null;    
            
            super.destroy();    
        }    
    }    
}

And the result:



You can hit the play button and you’ll be moved to Game screen. Nothing much to do on the game. Buy you can see how the screen transition work. Also the in-game UI & HUD. We’ll cover that on the next article.

Source Code:

Download Source Code

Well, another shameless promotions.
If you’re looking for an UI assets for your games, then you can take a look, and probably buying from my collection here:

Casual Cartoon Game GUI Pack

RPG Fantasy Game GUI Pack

Casual Cartoon Game GUI Pack

For more packs as well as game assets other than just the GUI pack, you can go here: www.GameArt2D.com

6 comments: