13 October 2014


As you know that Citrus Engine and Starling seems to be officially support Dragon Bones as their main skeletal animation library. It’s Dragon Bones that have their sub-forum on Starling forum, not other skeletal animation tools/libs like Brashmonkey Spriter and Esoteric Spine.

Dragon Bones Citrus Engine

But, even though Dragon Bones have their own place at the community, it still goddamn hard to find a good & complete tutorial how to get the best of that tool. What you get is a very basic getting started tutorial, also some complicated example files.  So you’ll also need to read the documentation and searching on the forum.
And most of them are for Starling, not Citrus Engine specific. Well, actually, everything that work on Starling will also work on CE.

I can only found one good CE + Dragon Bones tutorials, here at PseudoSamurai. If it’s your first time using Dragon Bones, then you’d better follow that link. Since I’ll post more advanced tutorial here.
Well, not that advanced. Just some modification of the DB “Cyborg” example .fla file and how to implement it on CE.

Animation Setup
As I already stated above, We’ll use the DB Cyborg.fla file. Get it here: DB Sample.
The .fla file is a example how to setup character that using nested armature to create a character that can switch its weapon.
But with some modification. I set the animation label to be match with CE animation setup: idle, walk, jump, and duck. And I remove some unnecessary animation frames.

Citrus Engine Dragon Bones

Actually, if you know how to setup DB armature for CE, you can use this DB character for your hero. And if you take a look at Example_Cyborg_SwitchWeapon.as file, then you can implement a switching weapon feature by yourself. But for the sake of this article,  I’ll just write it here and add some minor additions: blinking eye & shooting animation.

Blinking Eye
First thing first, open the Head movieclip until it show its timeline. You’ll see three layers, each contains helmet, face, and eye. Convert all these objects to MovieClip.
By converting all graphics to MovieClip then it will marked as Skeleton/Armature by Dragon Bones. So it have independent animation. In this case it's the blinking eye.
And then animate the eye. It just reducing the eye’s height, nothing more. Pretty simple.

Citrus Engine Dragon Bones Tutorial
the middle layer is the eye layer


Shooting Animations
Open the armOutside movieclip. You can see each weapon are separated into frames. There are 4 weapons: assault rifle, sniper rifle, machine gun, and rocket launcher. Assault rifle at frame 1 with label weapon1, sniper rifle at frame 2 with label weapon2, etc.

Now add additional keyframe for each weapon frame. For example, for a frame  labeled with weapon1, create a new keyframe with weapon1shoot label. Then on the stage, simply move and rotate the arm and the gun to create a shooting effect. Do the same thing to other weapon frames.

And since DB also support animation blending,  no need to set up more complex and tween animation. DB will do the rest.

Citrus Engine Dragon Bones Tutorial

Do the same thing for armInside movieclip too.

Open the Dragon Bones extension panel. Then hit the ‘import’ button. Check the animation, adjust everything until you get the desired effect.
Also make sure that there are 4 skeletons: cyborg, armOutside, armInside, and Head.
Originally, this DB .fla example will only have 3 skeletons cyborg, armOutside, and armInside, but we've convert the head's graphics into MovieClip, and add the blinking animation, so now you can see, it become a DB skeleton.

Open the Head skeleton. Set the Play Times to 0 to make it loop. So the eye will blink endlessly and continuously.
The other values are up to you to set the blinking speed & duration, but my settings are like this:

Citrus Engine Dragon Bones Tutorial

Make sure everything is set up properly. Then export it to PNG + XML file.
Extract the zip file.
And start coding.


To the Code
Create a Starling CE project on your IDE. Setup the Main and State class as well. I’m using Nape here, but that shouldn’t be a problem if you using Box2D.

I’ll post the complete code of the State class, and then the explanations is after that.

package  
{    
    import citrus.core.starling.StarlingState;    
    import citrus.objects.platformer.nape.Hero;    
    import citrus.objects.platformer.nape.Platform;    
    import citrus.physics.nape.Nape;    
    import citrus.view.starlingview.StarlingArt;    
    import dragonBones.Armature;    
    import dragonBones.Bone;    
    import dragonBones.events.AnimationEvent;    
    import dragonBones.factorys.StarlingFactory;    
    import dragonBones.objects.XMLDataParser;    
    import flash.events.TimerEvent;    
    import flash.geom.Point;    
    import flash.geom.Rectangle;    
    import flash.ui.Keyboard;    
    import flash.utils.Timer;    
    import starling.textures.Texture;    
    import starling.textures.TextureAtlas;    
  
    public class GameState extends StarlingState    
    {    
        [Embed(source="../assets/texture.png")]    
        private static const CYBORG_PNG:Class;    
        
        [Embed(source="../assets/texture.xml",mimeType="application/octet-stream")]    
        private static const CYBORG_TEX_DATA:Class;    
        
        [Embed(source="../assets/skeleton.xml",mimeType="application/octet-stream")]    
        private static const CYBORG_SKELETON:Class;    
        
        private var factory:StarlingFactory;    
        private var armature:Armature;    
        private var rightHand:Bone;    
        private var leftHand:Bone;    
        
        private var weaponID:int = 1;    
        
        private var canShoot:Boolean = true;    
        
        private var shootTimer:Timer;    
        
        public function GameState()    
        {    
            super();    
        }    
        
        override public function initialize():void    
        {    
            super.initialize();    
            
            StarlingArt.setLoopAnimations(["idle"]);    
            
            var nape:Nape = new Nape("nape");    
            nape.visible = true;    
            add(nape);    
            
            var floor:Platform = new Platform("floor", {width: 1400, height: 20});    
            floor.x = 700;    
            floor.y = stage.stageHeight;    
            add(floor);    
            
            for (var i:int = 1; i <= 2; i++)    
            {    
                var wall:Platform = new Platform("wall" + String(i), {width: 20, height: 400});    
                wall.y = 200;    
                
                if (i == 1)    
                {    
                    wall.x = 0;    
                }    
                else    
                {    
                    wall.x = 1400;    
                }    
                
                add(wall);    
            }    
            
            var atlas:TextureAtlas = new TextureAtlas(Texture.fromBitmap(new CYBORG_PNG()), XML(new CYBORG_TEX_DATA()));    
            
            factory = new StarlingFactory();    
            factory.addSkeletonData(XMLDataParser.parseSkeletonData(XML(new CYBORG_SKELETON())));    
            factory.addTextureAtlas(atlas, "CyborgCitrus");    
            
            armature = factory.buildArmature("cyborg");    
            
            rightHand = armature.getBone("armOutside");    
            leftHand = armature.getBone("armInside");    
            
            var hero:Hero = new Hero("hero", {registration: "topLeft", width: 70, height: 200});    
            hero.view = armature;    
            hero.x = 700;    
            hero.y = 100;    
            add(hero);    
            
            view.camera.setUp(hero, new Rectangle(0, 0, 1400, 400), new Point(.5, .5));    
            
            _ce.input.keyboard.addKeyAction("ChangeWeapon", Keyboard.A);    
            _ce.input.keyboard.addKeyAction("Shoot", Keyboard.S);    
            
            shootTimer = new Timer(300, 0);    
            shootTimer.addEventListener(TimerEvent.TIMER, shootHandler);    
        }    
        
        override public function update(timeDelta:Number):void    
        {    
            super.update(timeDelta);    
            
            if (_ce.input.isDoing("Shoot") && canShoot)    
            {    
                rightHand.childArmature.animation.gotoAndPlay("weapon" + weaponID + "shoot", .1);    
                leftHand.childArmature.animation.gotoAndPlay("weapon" + weaponID + "shoot", .1);    
                
                rightHand.childArmature.addEventListener(AnimationEvent.COMPLETE, animComplete, false, 0, true);    
                leftHand.childArmature.addEventListener(AnimationEvent.COMPLETE, animComplete, false, 0, true);    
                
                canShoot = false;    
                shootTimer.start();    
            }    
            
            if (_ce.input.justDid("ChangeWeapon"))    
            {    
                changeWeapon();    
            }    
        }    
        
        private function shootHandler(event:TimerEvent):void    
        {    
            canShoot = true;    
            
            shootTimer.stop();    
        }    
        
        private function changeWeapon():void    
        {    
            if (weaponID < 4)    
            {    
                weaponID++;    
            }    
            else    
            {    
                weaponID = 1;    
            }    
           
            rightHand.childArmature.animation.gotoAndPlay("weapon" + weaponID);    
            leftHand.childArmature.animation.gotoAndPlay("weapon" + weaponID);    
            
            switch (weaponID)    
            {    
                case 1:     
                    shootTimer.delay = 500;    
                    break;    
                
                case 2:     
                case 4:     
                    shootTimer.delay = 1000;    
                    break;    
                
                case 3:     
                    shootTimer.delay = 300;    
                    break;    
            }    
        }    
        
        private function animComplete(event:AnimationEvent):void    
        {    
            rightHand.childArmature.animation.gotoAndPlay("weapon" + weaponID, .1);    
            leftHand.childArmature.animation.gotoAndPlay("weapon" + weaponID, .1);    
            
            rightHand.childArmature.removeEventListener(AnimationEvent.COMPLETE, animComplete);    
            leftHand.childArmature.removeEventListener(AnimationEvent.COMPLETE, animComplete);    
        }    
    }    
}


Variables declaration
23-30: Embed the necessary DB files. _PNG & _TEX_DATA for creating the Texture Atlas, and _SKELETON for the DB animation.
32: factory variable to build the DB armature.
33. armature variable to store reference to the created armature
34-35: Variables to store reference to the left hand and right hand bones.

37: weaponID. Is information which weapon is currently active. From 1 to 4. Based on the both hands frame label: weapon1, weapon2, and so on.
39-41: Needed to create shooting behavior.

Initialize() Method
52: Add the ‘idle’ animation to become loop-able. Otherwise, it won’t loop.
58-78: Create the level. Well, just a floor and walls.
80: Create the Texture Atlas using the embedded image and XML.
82-84: Initialize the factory object. Add previously created Texture Atlas. Also the skeleton data.
86-89: Initialize the armature, leftHand, and rightHand object.

91-95: Create a Hero, and pass the armature object to its view property.
97: Set up camera to follow the hero

99-100: Add two new keyboard functions. Keyboard A button for switching weapon, and the S button for shooting.

102-103: Initialize the shoot timer.

Update() method
Override the update() method, and add the codes to check the pressed button.

112-114: If S button is pressed and the value for canFire variable is true, then for both of the hands, move to its shoot animation frames. If the current weapon is machine gun which is labeled as weapon1, then it will play weapon1shoot frame. And so on.
From the hand bones, access its childArmature, then access the animation property, and finally move to the target frame using gotoAndPlay() method.
Set the fadeInTime parameter to 0.1 so the animation blending is not too long.

115-116: Add Complete event for both hands. So if the shoot animation for weapon1shoot is completed, it will jump to weapon1 frame.
The event is defined on animComplete() method at line 166. And in this method the event listener for both hands also will be removed.

118-119: Set the canFire value to false and start the timer. If the timer is completed, it will fire the shootHandler() method that will reset the canFire value to true and stop the timer. So you can achieve a shooting delay, or a fire rate effect.

122: If the A button is pressed it will execute changeWeapon() method.

Switching Weapon
135-144: If changeWeapon() method is fired, then the first thing to do is to change the weaponID, in this case it just increase it.
If the value of weaponID is more than 4, then revert back to 1.

146-147: Then simply change the current frame to the next weapon frame, using that weaponID for sure.
The process is similar like how we change to shooting animation before.

149-163: Just some cosmetic. Change the shootTimer delay or fire rate according to the weapon type. Machine gun at 500 ms, sniper and rocket launcher at 1 second, etc.

Done
Compile and see the result.


Arrow keys to move the character, Spacebar to jump, A key to change weapon, and S key to shoot.

Alright, the tutorial is done.
Now you can achieve a nested animation and also weapon switching feature for your game, just like the old Flash movieclip, with gotoAndPlay and so on.
Well, not as straight forward than movieclip, but still, this is a great feature from Dragon Bones.

And finally, the source code.

Source Code
Download source here. It’s FlashDevelop project. CE & Nape .swc file included. As well as the .fla file contains the DB animation.
Download Source Code

26 August 2014

Super Sprite Bundle Cartoon Smart


Another September 2014 Update
Well, not a sprite pack for this update, but now it's an iOS game template. Yeah, some of my game assets are included on this Zombie game template: Hillbilly Brothers vs Zombies

http://gameartpartners.com/downloads/hillbilly-brothers-vs-zombies-and-fantastic-worlds-ios-starter-kit-bundle/?ref=1

Only $249 and you'll get full game template, from code to the graphic assets.
So, if you want to create an iOS game, especially with zombie theme, then highly probably that this game template is a good start.
Halloween is about to come, btw. So, sure, a zombie game is a good idea... :)


September 2014 Update
The 4th of The Super Sprite Bundle series released already. Well yeah, that was fast. It just a few weeks after the 3rd was released.

Nothing more to talk about, so it's 14 game asset packs for just $69. But there's a  discount on its site. Don't miss it.

 Click the image to see more details.


August 2014 Update
Alright, the third series of The Super Sprite Bundle now released. 10 items for just $49.
Check it out:

the super sprite bundle 3 cartoon smart
 Click the image to see more details.

Also, CartoonSmart now have their own game asset marketplace called GameArtPartners.com. All my game assets as well as the bundles now listed on that site. So you have plenty of choice where you'll buy my game assets.

=============

May 2014 Update.
Well, instead creating a new post for the announcement, I think it's better to merge it to the previous post.
So, to all my blog readers & visitors I want tell you that the second sprite bundle released already: The Super Sprite Bundle 2.
Don't forget to check this out:

The Super Sprite Bundle 2
Click the image to see detailed information for this pack.


=============
Just want to inform all of my blog readers and visitors, especially the ones that looking to buy my game assets, that starting on February 2014, I partnered with CartoonSmart to release my items as a bundle on their site, called The Super Sprite Bundle.
Check it out:

2d game asset super sprite bundle
Click the image to see detailed information for this pack.

As you can see, it only $39 and you get several items at once. From platformer tilesets, game GUI packs, and character spritesheets are included.
And the most importantly, it’s coming as a single license only. No need to messed up with licenses provided by marketplace sites which sometimes not quite suitable for game developers. So for anyone who looking for affordable game assets, this Sprite Bundle definitely what are you looking for.

We’re planning to release another bundle next month. See what will happen…

If it still can’t make you satisfied, well, probably we can make our own deal instead. Simply drop me a mail.

25 August 2014

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

10 March 2014

I’m enjoying diving deeper to Box2D at the moments. And now I want to share about Raycast. As well as how to make a Spiderman, Worm, or Bionic Commando-like rope swing, because it’s one of implementation of Raycast.

spider-man-meme-rope

Raycast
Raycasting is simply create a straight line between two point. Sound pretty simple, but it can be quite useful. Let say you want your AI character have a Line of Sight (LOS) function that will detect what objects are on his sight. Another example if you want to slice an object. Or probably simulate a light ray. I think it can be used on another scenario, that I haven’t discover yet.

I won’t explain about Box2D raycast feature here, as I also quite new to this thing. If you want more detailed explanation, maybe you can google that.

So this is my Ray class that wrapped Box2D raycast function and adds a feature to draw the line on the screen. Box2D debug draw doesn’t render the raycast graphic, so you need to create it by yourself.

package  
{    
    public class Ray     
    {     
        public static const RAY_CAST:String = "ray_cast";     
        public static const RAY_CAST_ONE:String = "ray_cast_one";     
        public static const RAY_CAST_ALL:String = "ray_cast_all";     
      
        public var startPoint:b2Vec2;     
        public var endPoint:b2Vec2;     
        
        public var onContact:Signal;     
        
        public var fraction:Number = 1;     
        
        public var fixture:b2Fixture;     
        public var fixtures:Vector.<b2Fixture>;     
        
        private var sprite:CitrusSprite;     
        private var shape:Shape;     
        
        private var box2D:Box2D;     
        
        private var type:String;     
        
        private var lineGraphic:Boolean = true;     
        
        public function Ray(box2D:Box2D, type:String = RAY_CAST, lineGraphic:Boolean = true)     
        {     
            this.box2D = box2D;     
            
            this.type = type;     
            this.lineGraphic = lineGraphic;     
            
            if (this.lineGraphic)     
            {     
                sprite = new CitrusSprite("raySprite");     
                CitrusEngine.getInstance().state.add(sprite);     
                
                shape = new Shape();     
            }     
            
            startPoint = new b2Vec2();     
            endPoint = new b2Vec2();     
          
            if (this.type == RAY_CAST)     
            {     
                onContact = new Signal(b2Fixture, b2Vec2, b2Vec2);     
            }     
        }     
        
        public function createRay():void     
        {     
            startPoint.x = startPoint.x / box2D.scale;     
            startPoint.y = startPoint.y / box2D.scale;     
            
            endPoint.x = endPoint.x / box2D.scale;     
            endPoint.y = endPoint.y / box2D.scale;     
            
            switch (type)     
            {     
                case RAY_CAST:     
                    box2D.world.RayCast(rayCastHandler, startPoint, endPoint);     
                    break;     
                
                case RAY_CAST_ONE:     
                    fixture = box2D.world.RayCastOne(startPoint, endPoint);     
                    break;     
                
                case RAY_CAST_ALL:     
                    fixtures = box2D.world.RayCastAll(startPoint, endPoint);     
                    break;     
            }     
            
            drawLine();     
        }     
        
        private function rayCastHandler(fixture:b2Fixture, intersection:b2Vec2, normal:b2Vec2, fraction:Number):Number     
        {     
            endPoint.x = intersection.x;     
            endPoint.y = intersection.y;     
            
            onContact.dispatch(fixture, intersection, normal);     
            
            return this.fraction;     
        }     
        
        private function drawLine():void     
        {     
            if (!lineGraphic)     
            {     
                return;     
            }     
            
            shape.graphics.clear();     
            shape.graphics.lineStyle(1);     
            shape.graphics.moveTo(startPoint.x * box2D.scale, startPoint.y * box2D.scale);     
            shape.graphics.lineTo(endPoint.x * box2D.scale, endPoint.y * box2D.scale);     
            
            sprite.view = shape;     
        }     
        
        public function destroy():void     
        {     
            if (type == RAY_CAST)     
            {     
                onContact.removeAll();     
                onContact = null;     
            }     
            
            if (lineGraphic)     
            {     
                shape = null;     
                sprite = null;     
            }     
            
            startPoint = null;     
            endPoint = null;     
        }     
    }     
}

I’m using Starling Graphic Extension here, because by default, Starling doesn’t have a graphics API that can be used to draw vector objects: line, square, circle, etc like Flash.

There are three constant, it will define what type of raycast will be used. RAY_CAST is the default, RAY_CAST_ONE will return first fixture that intersect with the ray, and RAY_CAST_ALL will return all fixtures.
Also there are two point, startPoint and the endPoint.

Fixture variable will hold a reference of the fixture returned if we use RaycastOne. And fixtures variable, a vector that hold reference of fixtures returned by RaycastAll.

Fraction is still a mystery for me. But from what I read somewhere, it’s used by the default Raycast to determine its behaviour. Set it to 0, it will stop at the first target on contact. Set it to 1 it will return all fixtures on contact. Set it to –1 it will ignore everything.

Other variables are self explanatory, I think.

Let’s jump to the createRay() method instead of the constructor, as it’s pretty clear already. In the createRay() method, it convert the startPoint and endPoint to box2D measurement, by simply divide it by box2D scale property. Then we can do the raycasting, based on the raycast type. 
The default raycast will need a callback function, RayCastHandler(). While the others aren’t.

On the rayCastHandler() method, you’ll get information about what fixture that intersect the ray, it’s intersection & normal point, and the fraction. Btw, I still don’t understand what’s fraction function parameter supposed to do. :\

I have a signal here, that will be dispatched when the ray hit something. It will contain all parameter, except fraction.

Okay, we’ve got the raycast wrapper set-up, now we need to implement it on the Hero class. I create a CustomHero class that extends CE default Hero class:

package    
{     
    public class CustomHero extends Hero     
    {     
        private var LOS:Ray;     
        
        public function CustomHero(name:String, params:Object = null)     
        {     
            super(name, params);     
            
            LOS = new Ray(_box2D);     
            LOS.fraction = 0;     
            LOS.onContact.add(LOSContactHandler);     
        }     
        
        override public function update(timeDelta:Number):void     
        {     
            super.update(timeDelta);     
            
            LOS.startPoint.x = x;     
            LOS.startPoint.y = y;     
            
            LOS.endPoint.x = x + 200;     
            LOS.endPoint.y = y;     
            
            if (_inverted)     
            {     
                LOS.endPoint.x = x - 200;     
            }     
            
            LOS.createRay();            
        }     
        
        private function LOSContactHandler(fixture:b2Fixture, intersection:b2Vec2, normal:b2Vec2):void     
        {     
            GameState(_ce.state).setText("target: " + fixture.GetBody().GetUserData().name);     
        }     
    }     
}


Simply create a Ray object called LOS. It will use the default RayCast. And set the fraction to 0, so it will return the first object that intersect with the ray.

Because we want to create a LOS, so it will need stick to the hero all the time, and have a fixed length. On the update() method, set the startPoint to the exact same position as the hero. And the add the length value (200) to the hero x position. Make sure it will also inverted when the hero facing opposite direction. Then call the createRay() function. On the LOSContactHandler(), it’ll just write the object’s name to a TextField.

An the result is something like this:


Move around, and the text field will tell you the object’s name that collide with the ray.
Btw, you can click & hold on the ceiling platform to activate the spidey rope.
How to create that? Just keep reading… Open-mouthed smile

Spiderman Swing


bio
Not Spiderman

The idea just to create a distance joint between the hero to a specified point. To get the target position, we use the raycast function. It will create a ray between hero and the mouse position. If there are a fixture being intersect with the ray, then it’s the target point.

I’ll just paste my GameState code here, with a lot of unnecessary code omitted:

package    
{     
    public class GameState extends StarlingState     
    {     
        private var jointRay:Ray;     
        
        private var jointDef:b2DistanceJointDef;     
        private var joint:b2DistanceJoint;     
        
        private var jointLine:Shape;     
        private var jointSprite:CitrusSprite;     
        
        private var mouseHold:Boolean = false;
         
        public function GameState()     
        {     
            super();     
        }     
        
        override public function initialize():void     
        {     
            super.initialize();
             
            jointRay = new Ray(box2D);     
            jointRay.fraction = 0;     
            jointRay.onContact.add(jointRayContact);         
          
            jointSprite = new CitrusSprite("jointSprite");     
            add(jointSprite);     
            
            jointLine = new Shape();
             
            stage.addEventListener(TouchEvent.TOUCH, click);     
        }     
        
        private function click(event:TouchEvent):void     
        {     
            var touch:Touch = event.getTouch(stage);     
            
            if (touch)     
            {     
                if (touch.phase == TouchPhase.BEGAN)     
                {     
                    var posX:int = event.getTouch(event.currentTarget as DisplayObject).globalX;     
                    var posY:int = event.getTouch(event.currentTarget as DisplayObject).globalY;     
                    var worldPos:Point = ((view as StarlingView).viewRoot as Sprite).globalToLocal(new Point(posX, posY));     
                    
                    var hero:CustomHero = getObjectByName("hero") as CustomHero;     
                    
                    jointRay.startPoint.x = hero.x;     
                    jointRay.startPoint.y = hero.y;     
                    jointRay.endPoint.x = worldPos.x;     
                    jointRay.endPoint.y = worldPos.y;     
                    
                    jointRay.createRay();     
                    
                    mouseHold = true;     
                }     
                else if (touch.phase == TouchPhase.ENDED)     
                {     
                    if (joint)     
                    {     
                        var box2D:Box2D = getObjectByName("box2D") as Box2D;     
                        box2D.world.DestroyJoint(joint);     
                        
                        joint = null;     
                        jointDef = null;     
                        
                        jointLine.graphics.clear();     
                    }     
                    
                    mouseHold = false;     
                }     
            }     
        }     
        
        override public function update(timeDelta:Number):void     
        {     
            super.update(timeDelta);     
            
            if (mouseHold)     
            {     
                if (joint)     
                {                    
                    var box2D:Box2D = getObjectByName("box2D") as Box2D;     
                    
                    joint.SetLength(joint.GetLength() - (5 / box2D.scale));     
                    
                    drawLine();     
                }     
            }     
        }     
        
        private function drawLine():void     
        {     
            var box2D:Box2D = getObjectByName("box2D") as Box2D;     
            
            jointLine.graphics.clear();     
            jointLine.graphics.lineStyle(2, 0xFF0000);     
            jointLine.graphics.moveTo(joint.GetAnchorA().x * box2D.scale, joint.GetAnchorA().y * box2D.scale);     
            jointLine.graphics.lineTo(joint.GetAnchorB().x * box2D.scale, joint.GetAnchorB().y * box2D.scale);     
            
            jointSprite.view = jointLine;     
        }     
        
        private function jointRayContact(fixture:b2Fixture, intersec:b2Vec2, normal:b2Vec2):void     
        {     
            var hero:CustomHero = getObjectByName("hero") as CustomHero;     
            var box2D:Box2D = getObjectByName("box2D") as Box2D;     
            
            if (joint)     
            {     
                box2D.world.DestroyJoint(joint);     
            }     
            
            jointDef = new b2DistanceJointDef();     
            jointDef.Initialize(hero.body, fixture.GetBody(), hero.body.GetWorldCenter(), intersec);     
            
            joint = box2D.world.CreateJoint(jointDef) as b2DistanceJoint;     
        }
    }    
}

Initialize all the necessary objects: Ray, DistanceJointDef, DistanceJoint. And objects for the graphical representation of the hook: Shape & CitrusSprite.
We use default RayCast mode, and fraction of 0. Don’t forget to add touch event to the stage.

Jump to the click() method. To simulate a mouse hold event on Starling we need to detect the phase. Set the mouseHold property to true when it’s in Began phase. Otherwise, set it to false when in Ended phase.

In the Began phase, we create a ray. If the ray intersect with a fixture, then surely it will fire a signal. Catch it with jointRayContact() method and create the DistanceJoint object between the hero to the intersection point.
In the Ended phase, if a joint is exists, we need to destroy them. And also remove the graphical representation.

I also shorten the rope when players holding the mouse button. Just take a look on the update() method. I reduce the joint length 5 pixel per frame. Don’t forget to divide it with box2D scale factor.

DrawLine() method, well, just to draw a red line as a graphical representation for the joint.

Done
Ok, you can download the source code here:

download