Saturday, April 27, 2013

Friday, April 19, 2013

Easiest way to make animation from png spritesheet

In this tutorial I will show you how I made an aimation using just 8 png images from a sprite sheet. I suppose this is the most easiest and straigt forward way to do it. There are tools like TexturePacker but I will skip it in this tutorial and concentrate only on the mechanics how I did this animation.

I assume that you have installed FlashDevelop and have created blank AS3 project named SpriteAnimation in a folder with the same name. The project has white background, 30 fps and it is 200x200 size.

I will use this sprite sheet that I found on internet for free to be exact on this site as3gamegears



So this is a field scarecrow that has a sprite sheet of 8 postions. I used Gimp to divide this 8 postions into 8 png images which are 64x64 pixels. The mechanism to create animation from this 8 images is simple: take all images, put them in a AS3 Sprite and each frame change visibility of each image. So we go first image is visible all other are invisible, then first image is invisible second is visible, all other are invisible. Than third image is visible all other are invisible etc...
When we show the 8-th image we switch back to the first and repeat the same process again.


I created a folder in my FlashDevelop project called assets where I put all eight images.
I also created separate class named Scarecrow that will hold all images inside and keep track which image should be shown next.

So here is the code of Scarecrow.as

package 
{
    import flash.display.DisplayObject;
    import flash.display.Sprite;
    import flash.events.Event;
    /**
     * ...
     * @author SpinnerBox
     */
    public class Scarecrow extends Sprite
    {
        [Embed(source = "/../assets/pic1.png", mimeType="image/png")]
        private var Pic1:Class;
        private var pic1:DisplayObject = new Pic1();
       
        [Embed(source = "/../assets/pic2.png", mimeType="image/png")]
        private var Pic2:Class;
        private var pic2:DisplayObject = new Pic2();
       
        [Embed(source = "/../assets/pic3.png", mimeType="image/png")]
        private var Pic3:Class;
        private var pic3:DisplayObject = new Pic3();
       
        [Embed(source = "/../assets/pic4.png", mimeType="image/png")]
        private var Pic4:Class;
        private var pic4:DisplayObject = new Pic4();
       
        [Embed(source = "/../assets/pic5.png", mimeType="image/png")]
        private var Pic5:Class;
        private var pic5:DisplayObject = new Pic5();
       
        [Embed(source = "/../assets/pic6.png", mimeType="image/png")]
        private var Pic6:Class;
        private var pic6:DisplayObject = new Pic6();
       
        [Embed(source = "/../assets/pic7.png", mimeType="image/png")]
        private var Pic7:Class;
        private var pic7:DisplayObject = new Pic7();
       
        [Embed(source = "/../assets/pic8.png", mimeType="image/png")]
        private var Pic8:Class;
        private var pic8:DisplayObject = new Pic8();
       
        private var picsObject:Object;
        private var currentPic:uint;
        private var animCounter:uint;
        private var nextPicTime:uint;
       
        public function Scarecrow()
        {
            picsObject = new Object();
            currentPic = 1;
            nextPicTime = 3;
            animCounter = 0;
           
            addChild(pic1);
            picsObject["1"] = pic1;
           
            pic2.visible = false;
            addChild(pic2);
            picsObject["2"] = pic2;
           
            pic3.visible = false;
            addChild(pic3);
            picsObject["3"] = pic3;
           
            pic4.visible = false;
            addChild(pic4);
            picsObject["4"] = pic4;
           
            pic5.visible = false;
            addChild(pic5);
            picsObject["5"] = pic5;
           
            pic6.visible = false;
            addChild(pic6);
            picsObject["6"] = pic6;
           
            pic7.visible = false;
            addChild(pic7);
            picsObject["7"] = pic7;
           
            pic8.visible = false;
            addChild(pic8);
            picsObject["8"] = pic8;
           
            addEventListener(Event.ENTER_FRAME, playAnimation);
           
        }
       
        public function setPicVisible(picNum:uint):void
        {
            for (var i:int = 1; i <= 8; i += 1)
            {
                var strPicNum:String = String(i);
                if (i == picNum)
                {
                    picsObject[strPicNum].visible = true;
                }
                else
                {
                    picsObject[strPicNum].visible = false;
                }
            }
        }
       
        public function playAnimation(e:Event = null):void
        {
            if (animCounter >= nextPicTime)
            {
                setPicVisible(currentPic);
                if (currentPic == 8)
                {
                    currentPic = 1;
                }
                else
                {
                    currentPic += 1;
                }
                animCounter = 0;
            }
            else
            {
                animCounter += 1;
            }
        }
    }

}

A little on the Scarecrow.as class:

- I created object named picsObject that will hold all image objects and therefore all image will be accessible by just typing picsObject[String(picNum)]. First I set all 7 images to be invisible but just the first is visible. 

 - Then I create a function called setPicVisible(picNum) that will show only the image that corresponds to the picNum parameter. 

- Next is the playAnimation(e:Event = null)  that works on ENTER_FRAME event and will play the animation on each frame. One thing to note that the frame rate is 30 fps which will be too much for our animation so I switch image on each third frame instead of each next frame. I do that by using this simple if (animCounter >= nextPicTime) / else  statement. 

My Main.as class looks like this

package
{
    import flash.display.Sprite;
    import flash.events.Event;
   
    /**
     * ...
     * @author SpinnerBox
     */
    public class Main extends Sprite
    {
        private var scarecrow:Scarecrow;
       
        public function Main():void
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
       
        private function init(e:Event = null):void
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            // entry point

            scarecrow = new Scarecrow();
            scarecrow.x = 50;
            scarecrow.y = 50;
            addChild(scarecrow);
        }
       
    }
   
}


I just created new Scarecrow object and added it on the stage.

Well here is the result:

Wednesday, April 17, 2013

Collision detection - Hero vs Multiple enemies

Hi. In this tutorial I will show you how to use QuadTree implementation in AS3 to detect whether a Hero object on the stage colide with many Enemy objects.

I asume you have installed FlashDevelop and created AS3 blank project called CollisionDetection in folder with same  name.

Firstly I reused a code from other site but mine code is implemented fully in AS3 and is fitted to suit my needs i.e checking if a Hero object collides with many Enemies. First I recommend read the tutorial from the site before implementing it in AS3. Roughly QuadTree is a data structure that has 4 and only 4 child nodes. It is the same principal as binary tree except it has two child nodes.

How do we use the QuadTree structure to detect collision detection???

Well we can create arrays of objects and use "for loops" to cycle trough each object and check for collision, but the problem that arises is that we might have many operations per frame (in games) so that might crash flash or not work at all on slower computers. So we use a QuadTree to separate the 2D space in 4 subparts and check if there is a collision between objects in a smaller area.
There is no need to check objects that are on the different sides of the stage.
During the separation we check if objects are fitted in quadrant. If there are many objects in a given quadrant we separate that quadrant into again four subquadrants and check again.

First create three folders uder the root of the FlashDevelop project. Call them CollisionDetectionPkg, EnemyPkg, and HeroPkg.

In CollisionDetectionPkg add new blank AS3 class and name it QuadTree.as.
In EnemyPkg add new AS3 class and name it Enemy.as
In HeroPkg follows the same thing, add blank class Hero.as

Our Main.as class will be manager for adding multiple enemies and also do all the stuff for checking and retrieving objects that might collide with Hero object.

QuadTree.as

package CollisionDetectionPkg
{
    import flash.display.DisplayObject;
    import flash.display.Sprite;
    import flash.geom.Rectangle;

    public class QuadTree
    {
        private var _stage:DisplayObject;
        private var MAX_OBJECTS:int = 10;
        private var MAX_LEVELS:int = 5;
       
        private var level:int;
        private var objects:Array; // sprites that are put on the stage
        private var bounds:Rectangle;
        private var nodes:Array; //array of QuadTree objects
       
        /*
          * Constructor
        */
        public function QuadTree(pLevel:int, pBounds:Rectangle, stage:DisplayObject)
        {
            _stage = stage;
            level = pLevel;
            objects = new Array();
            bounds = pBounds;
            nodes = new Array(4);
        }
       
        /*
         * Clears the quadtree
         */
        public function clear():void
        {
            for (var i:int = 0; i < objects.length; i += 1)
            {
                if (objects[i] != null)
                {
                    objects.splice(i, 1);
                }
            }
           
            for (var j:int = 0; j < nodes.length; j += 1)
            {
                if (nodes[j] != null)
                {
                    nodes.splice(j, 1);
                }
            }
        }
       
        /*
         * Splits the node into 4 subnodes
        */
        private function split():void
        {
            var subWidth:int = int(bounds.width / 2);
            var subHeight:int = int(bounds.height / 2);
            var x:int = int(bounds.x);
            var y:int = int(bounds.y);
           
            nodes[0] = new QuadTree(level + 1, new Rectangle(x + subWidth, y, subWidth, subHeight), _stage);
            nodes[1] = new QuadTree(level + 1, new Rectangle(x, y, subWidth, subHeight), _stage);
            nodes[2] = new QuadTree(level + 1, new Rectangle(x, y + subHeight, subWidth, subHeight), _stage);
            nodes[3] = new QuadTree(level + 1, new Rectangle(x + subWidth, y + subHeight, subWidth, subHeight), _stage);
        }
       
        /*
         * Determine which node the object belongs to. -1 means
         * object cannot completely fit within a child node and is part
         * of the parent node
         */
        private function getIndex(pRect:Rectangle):int
        {
            var index:int = -1;
            var verticalMidpoint:Number = bounds.x + (bounds.width / 2);
            var horizontalMidpoint:Number = bounds.y + (bounds.height / 2);
           
            // Object can completely fit within the top quadrants
            var topQuadrant:Boolean = (pRect.y < horizontalMidpoint && pRect.y + pRect.height < horizontalMidpoint);
            // Object can completely fit within the bottom quadrants
            var bottomQuadrant:Boolean = (pRect.y > horizontalMidpoint);
 

            // Object can completely fit within the left quadrants
            if (pRect.x < verticalMidpoint && pRect.x + pRect.width < verticalMidpoint)
            {
                if (topQuadrant)
                {
                    index = 1;
                }
                else if (bottomQuadrant)
                {
                    index = 2;
                }
            }
            // Object can completely fit within the right quadrants
            else if (pRect.x > verticalMidpoint)
            {
                if (topQuadrant)
                {
                    index = 0;
                }
                else if (bottomQuadrant)
                {
                    index = 3;
                }
            }
       
            return index;
        }
       
        /*
         * Insert the object into the quadtree. If the node
         * exceeds the capacity, it will split and add all
         * objects to their corresponding nodes.
         */
        public function insert(pRect:Sprite):void
        {
            if (nodes[0] != null)
            {
                var index:int = getIndex(pRect.getRect(_stage));
               
                if (index != -1 && nodes[index] != null)
                {
                    nodes[index].insert(pRect);
                    return;
                }
            }
           
            objects.push(pRect);
       
            if (objects.length > MAX_OBJECTS && level < MAX_LEVELS)
            {
                if (nodes[0] == null)
                {
                    split();
                }
               
                var i:int = 0;
                while (i < objects.length)
                {
                    var index1:int = getIndex(objects[i].getRect(_stage));
                    if (index1 != -1 && nodes[index1] != null)
                    {
                        nodes[index1].insert(objects[i]);
                        objects.splice(i, 1);
                    }
                    else
                    {
                        i += 1;
                    }
                }
            }
           
        }
       
        /*
         * Return all objects that could collide with the given object
         */
        public function retrieve(returnObjects:Array, pRect:Sprite):Array
        {
            var index:int = getIndex(pRect.getRect(_stage));
          
            if (nodes[0] != null && index != -1 && nodes[index] != null)
            {
                nodes[index].retrieve(returnObjects, pRect);
            }
       
            for (var i:int = 0; i < objects.length; i += 1)
            {
                if (objects[i] != null)
                {
                    returnObjects.push(objects[i]);
                }
            }
       
            return returnObjects;
         }
    }

}

-----------------------------------------------------------------------------------------------------------------

Enemy.as

our Enemy is 50x50 white rectangle with red border



 



package EnemyPkg
{
    import flash.display.Sprite;
    import flash.events.Event;
    /**
     * ...
     * @author SpinnerBox
     */
    public class Enemy extends Sprite
    {
        private var unitSprite:Sprite;
        private var _main:Main;
       
        public function Enemy(main:Main)
        {
            _main = main;
            unitSprite = new Sprite();
            unitSprite.graphics.lineStyle(2, 0xff0000, 1);
            unitSprite.graphics.beginFill(0xffffff, 1);
            unitSprite.graphics.drawRect(-25, -25, 50, 50);
            unitSprite.graphics.endFill();
            addChild(unitSprite);
           
            addEventListener(Event.ENTER_FRAME, onEnterFrame);
        }
       
        public function destroy():void
        {
            removeEventListener(Event.ENTER_FRAME, onEnterFrame);
            _main.stage.removeChild(this);
        }
       
        private function onEnterFrame(e:Event):void
        {
            this.x -= 2;
        }
       
    }

}

--------------------------------------------------------------------------------------------------------

Hero.as

Our hero is 60x60 white rectangle with green border








package HeroPkg
{
    import flash.display.Sprite;
    /**
     * ...
     * @author SpinnerBox
     */
    public class Hero extends Sprite
    {
        private var unitSprite:Sprite;
       
        public function Hero()
        {
            unitSprite = new Sprite();
            unitSprite.graphics.lineStyle(2, 0x00ff00, 1);
            unitSprite.graphics.beginFill(0xffffff, 1);
            unitSprite.graphics.drawRect(-30, -30, 60, 60);
            unitSprite.graphics.endFill();
            addChild(unitSprite);
        }
       
    }

}

---------------------------------------------------------------------------------------------------------

and Main.as

In main we add ENTER_FRAME listener to add new enemies and to check collisions using the QuadTree constructed in the constructor.
Note: We can use hitTestObject function only on DisplayObject or Sprite types of objects in AS3 so in insert() and retrieve() we send Sprite type of object instead of Rectangle and then we get the bounding rectangle by using getRect() function and sending  main.stage object to it like this

objects[i].getRect(_stage);

Remeber that _stage = main.stage. Once that our Hero rectangle has collided with enemy the enemy object gets destroyed.

package
{
    import CollisionDetectionPkg.QuadTree;
    import EnemyPkg.Enemy;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.display.StageScaleMode;
    import flash.geom.Rectangle;
    import HeroPkg.Hero;
  
    /**
     * ...
     * @author SpinnerBox
     */
    public class Main extends Sprite
    {
        private var enemyArray:Array;
        private var enemy:Enemy;
        private var hero:Hero;
        private var unitCounter:uint = 0;
        private var unitRate:uint = 40;
        private var quadTree:QuadTree;
      
        public function Main():void
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
      
        private function init(e:Event = null):void
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            // entry point
            stage.scaleMode = StageScaleMode.NO_SCALE;
          
            quadTree = new QuadTree(0, new Rectangle(0, 0, 700, 550), this.stage);
            enemyArray = new Array();
          
            hero = new Hero();
            hero.x = 100;
            hero.y = 200;
            stage.addChild(hero);
      
            addEventListener(Event.ENTER_FRAME, onEnterFrame);
        }
      
        private function onEnterFrame(e:Event = null):void
        {
            hero.x = stage.mouseX;
            hero.y = stage.mouseY;
          
            if (unitCounter >= unitRate)
            {
                for (var i:uint = 1; i <= 5; i += 1 )
                {
                    var enemy:Enemy = new Enemy(this);
                    enemy.x = 750;
                    enemy.y = i*100;
                    stage.addChild(enemy);
                    enemyArray.push(enemy);
                }
              
                unitCounter = 0;
            }
            else
            {
                unitCounter += 1;
            }
          
            quadTree.clear();
            for each( var newEnemy:Enemy in enemyArray)
            {
                quadTree.insert(newEnemy);
            }

            var returnObjects:Array = new Array();

            quadTree.retrieve(returnObjects, hero);
            for (var k:int = 0; k < returnObjects.length; k += 1)
            {
                // Run collision detection algorithm between enemies and hero
                if (returnObjects[k].hitTestObject(hero))
                {
                    if (returnObjects[k].parent != null)
                    {
                        returnObjects[k].destroy();
                        returnObjects.splice(k, 1);
                    }      
                }
            }
          
          
            // clear memory from returned objects
            for (var m:int = 0; m < returnObjects.length; m += 1)
            {
                if (returnObjects[m] != null)
                {
                    returnObjects.splice(m, 1);
                }
            }
          
            // clear enemies if they reach x = -100
            for (var j:uint = 0; j <= enemyArray.length; j += 1 )
            {
                if (enemyArray[j] != null && enemyArray[j].x < -100)
                {
                    enemyArray[j].destroy();
                    enemyArray.splice(j, 1);
                }
            }
        }
    }
  
}


 Final result: When green square touches red square they get destroyed.


















I tried the compiled swf file on old Intel Celeron machine and it works still fairlly smooth.

Again thanks to TutsPlus and Steven Lambert for his awesome tutorial. Cheers :)

Friday, April 5, 2013

My first game - screenshots

My space man goes rampage :)

Where the bullets come from ??? :)


testting collision detection


Monday, April 1, 2013

Hardware: shipbreakers

From the creators of Homeworld. I really like the trailer. Hope this will be released soon.