IsoVille

Overview

A simple Isometric Demo with pinch, pan and zoom support.

Try It

Use the following Loom CLI commands to run this example:

loom new MyIsoVille --example IsoVille
cd MyIsoVille
loom run

Screenshot

IsoVille Screenshot

Code

src/IsoVille.ls

package
{

    import system.platform.Platform;
    import loom.Application;

    import loom.gameframework.ITicked;
    import loom.gameframework.TimeManager;    

    import loom2d.math.Point;

    import loom2d.display.Sprite;
    import loom2d.display.Image;
    import loom2d.textures.Texture;
    import loom2d.textures.SubTexture;
    import loom2d.display.StageScaleMode;

    import loom2d.events.Touch;
    import loom2d.events.TouchEvent;
    import loom2d.events.TouchPhase;    


    import loom2d.ui.TextureAtlasManager;
    import loom2d.ui.TextureAtlasSprite;
    import loom2d.ui.SimpleLabel;

    public class IsometricAtlasSprite extends TextureAtlasSprite
    {
        public var waitCount:int = 15;
        public var velX:Number = 1;
        public var velY:Number = 1;
        public var posX:Number = 15 * 90;
        public var posY:Number = 15 * 90;
        public var texPrefix:String = null;

        public function setIsometricPosition(_x:int, _y:int):void
        {
            x = _x;
            y = _y;

            depth = (_y * 10000) - x;
        }

        public function set isoX(_value:int):void
        {
            setIsometricPosition(_value, y);
        }

        public function get isoX():Number
        {
            return x;
        }

        public function set isoY(_value:int):void
        {
            setIsometricPosition(x, _value);
        }

        public function get isoY():Number
        {
            return y;
        }
    }    

    public class IsoVille extends Application implements ITicked
    {
        public var sceneRootNode:Sprite;
        public var backgroundNode:Image;

        public var wanderers:Vector.<IsometricAtlasSprite> = new Vector.<IsometricAtlasSprite>();

        public var minViewX:int, maxViewX:int, minViewY:int, maxViewY:int;

        override public function run():void
        {

            stage.scaleMode = StageScaleMode.LETTERBOX;

            TextureAtlasManager.register("atlas", "assets/atlas.xml");

            backgroundNode = new Image(Texture.fromAsset("assets/background.png"));
            backgroundNode.x = -2048;
            backgroundNode.y = -2048;
            backgroundNode.scale = 4096;
            stage.addChild(backgroundNode);

            sceneRootNode = new Sprite();
            sceneRootNode.depthSort = true;
            stage.addChild(sceneRootNode);

            minViewX = 1000000;
            maxViewX = -1000000;
            minViewY = 1000000;
            maxViewY = -1000000;

            for(var i:int = 0; i<25; i++)
            {
                for(var j:int = 0; j<25; j++)
                {
                    var sprite = new IsometricAtlasSprite();
                    sprite.atlasName = "atlas";
                    sprite.textureName = "" + (Math.floor(Math.random() * 5) + 1) + ".png";

                    sceneRootNode.addChild(sprite);

                    sprite.pivotX = sprite.width/2;
                    sprite.pivotY = sprite.height/2;

                    sprite.pivotY += sprite.height/10;

                    //setAnchorPoint(new CCPoint(0.5, 0.1));

                    sprite.scale = 2.0;

                    sprite.setIsometricPosition( (i+j) * 90, (i-j) * 90);

                    // Note the x/y position for clamping the view.
                    if(sprite.x < minViewX) minViewX = sprite.x;
                    if(sprite.x > maxViewX) maxViewX = sprite.x;
                    if(sprite.y < minViewY) minViewY = sprite.y;
                    if(sprite.y > maxViewY) maxViewY = sprite.y;
                }
            }

            for(var k:int = 0; k<25; k++)
            {
                var wander = new IsometricAtlasSprite();
                wander.atlasName = "atlas";
                wander.textureName = "grey_walk_west0018.png";

                wander.center();

                //wander.setAnchorPoint(new CCPoint(0.5, 0.5));
                //wander.pivotX = wander.width/2;
                //wander.pivotY = wander.height/2;                    

                wander.setIsometricPosition(500, 0);

                sceneRootNode.addChild(wander);
                wanderers.pushSingle(wander);
            }

            sceneRootNode.x = -2000;
            sceneRootNode.y = 0;

            stage.addEventListener( TouchEvent.TOUCH, function(e:TouchEvent) { 

                var point:Point;
                var touch = e.getTouch(stage, TouchPhase.BEGAN);

                if (touch)
                {
                    point = touch.getLocation(stage);
                    dragStart(touch.id, point.x, point.y);
                }

                touch = e.getTouch(stage, TouchPhase.MOVED);
                if (touch)
                {
                    point = touch.getLocation(stage);
                    dragMover(touch.id, point.x, point.y);
                }                

                touch = e.getTouch(stage, TouchPhase.ENDED);
                if (touch)
                {
                    point = touch.getLocation(stage);
                    dragEnd(touch.id, point.x, point.y);
                }                

            } );            

        }

        public var dragStartTouchX:Number, dragStartTouchY:Number;
        public var dragStartNodeX:Number, dragStartNodeY:Number;
        public var dragLastX:Number, dragLastY:Number;
        public var dragVerletX1:Number, dragVerletY1:Number, dragVerletTime:Number;
        public var dragVerletX2:Number, dragVerletY2:Number;
        public var pinchLastX:Number, pinchLastY:Number;
        public var pinchStartLength:Number, pinchOriginalScale:Number, pinchTouchId:int = -1;
        public var pinchOffsetX:Number, pinchOffsetY:Number;
        public var dragTouchId = -1;

        public function dragStart(touchId:int, x:Number, y:Number):void
        {
            if(dragTouchId == -1)
            {
                // Start dragging.
                dragTouchId = touchId;

                dragStartTouchX = x;
                dragStartTouchY = y; 

                dragStartNodeX = sceneRootNode.x;
                dragStartNodeY = sceneRootNode.y;

                dragLastX = x;
                dragLastY = y;

                dragVerletTime = Platform.getTime();

                dragVerletX1 = dragStartNodeX + x;
                dragVerletY1 = dragStartNodeY + y;
                dragVerletX2 = dragStartNodeX + x;
                dragVerletY2 = dragStartNodeY + y;
            }
            else if(pinchTouchId == -1)
            {
                // Start pinching.
                pinchTouchId = touchId;

                pinchOriginalScale = sceneRootNode.scale;
                pinchStartLength = Math.sqrt((x - dragStartTouchX) * (x - dragStartTouchX)+ (y - dragStartTouchY) * (y - dragStartTouchY));

                pinchLastX = x;
                pinchLastY = y;
            }
        }

        public function dragMover(touchId:int, x:Number, y:Number):void
        {
            // Update the state of the pinch or drag points.
            if(touchId == pinchTouchId)
            {
                pinchLastX = x;
                pinchLastY = y;
            } 
            else if(touchId == dragTouchId)
            {
                dragLastX = x;
                dragLastY = y;
            }

            if(pinchTouchId == -1)
            {
                // If we are dragging and not pinching...
                if(dragTouchId != -1)
                {
                    // Update our Verlet velocity for flicking.
                    if(true)
                    {
                        dragVerletX1 = dragVerletX2;
                        dragVerletY1 = dragVerletY2;

                        dragVerletX2 = dragStartNodeX + (x - dragStartTouchX);
                        dragVerletY2 = dragStartNodeY + (y - dragStartTouchY);

                        // Cap the verlet velocity in screen coordinates.
                        var absXCap = (stage.stageWidth) * 0.1;
                        var absYCap = (stage.stageHeight) * 0.1;
                        var maxLength = (absXCap + absYCap);

                        var driftX = dragVerletX2 - dragVerletX1;
                        var driftY = dragVerletY2 - dragVerletY1;

                        var actualLength = Math.sqrt(driftX * driftX + driftY * driftY + 1);
                        var normalizeFactor = actualLength / maxLength;
                        if(normalizeFactor > 1)
                        {
                            if(normalizeFactor < 10)
                            {
                                //Console.print("normalizing due to actualLength=" + actualLength + " nf=" + normalizeFactor + " max=" + maxLength);
                                driftX = driftX / normalizeFactor;
                                driftY = driftY / normalizeFactor;
                            }
                            else
                            {
                                //Console.print("cancelling due to actualLength=" + actualLength + " nf=" + normalizeFactor + " >10" + " max=" + maxLength);
                                driftX = dragVerletX2 - dragVerletX1;
                                driftY = dragVerletY2 - dragVerletY1;
                            }
                        }

                        dragVerletX2 = dragVerletX1 + driftX;
                        dragVerletY2 = dragVerletY1 + driftY;
                    }

                    // Just dragging, so update position.
                    sceneRootNode.x = dragStartNodeX + (x - dragStartTouchX);
                    sceneRootNode.y = dragStartNodeY + (y - dragStartTouchY);
                }
            }
            else
            {
                // Otherwise, we're pinching (scaling).
                var curLength = Math.sqrt(
                      (pinchLastX - dragLastX) * (pinchLastX - dragLastX)
                    + (pinchLastY - dragLastY) * (pinchLastY - dragLastY));

                var scaleFactor = (curLength / pinchStartLength);

                sceneRootNode.scale = scaleFactor * pinchOriginalScale;

                var halfWidth = stage.stageWidth * 0.5;
                var halfHeight = stage.stageHeight * 0.5;

                sceneRootNode.x = (dragStartNodeX - halfWidth) * scaleFactor + halfWidth;
                sceneRootNode.y = (dragStartNodeY - halfHeight) * scaleFactor + halfHeight;

            }
        }

        public function dragEnd(touchId:int, x:Number, y:Number):void
        {
            //Console.print("Touch end, inertia is: " + (dragVerletX2 - dragVerletX1) + ", " + (dragVerletY2 - dragVerletY1) + " pos=" + sceneRootNode.getPositionX() + ", " + sceneRootNode.getPositionY());

            dragTouchId = -1;
            pinchTouchId = -1;
        }

        public function onTick():void
        {
            // Apply drift if appropriate.
            if(dragTouchId == -1 && pinchTouchId == -1)
            {
                var driftX = dragVerletX2 - dragVerletX1;
                var driftY = dragVerletY2 - dragVerletY1;
                dragVerletX2 = dragVerletX1 + driftX * 0.9;
                dragVerletY2 = dragVerletY1 + driftY * 0.9;

                var newPosX = sceneRootNode.x + driftX;
                var newPosY = sceneRootNode.y + driftY;

                // Clamp to be in range of buildings so we don't fly off into
                // infinity.
                var widthOffset = (stage.stageWidth * 0.5);
                var heightOffset = (stage.stageHeight * 0.5);

                newPosX = Math.clamp(newPosX / sceneRootNode.scale, -(maxViewX - widthOffset), -(minViewX - widthOffset)) * sceneRootNode.scale;
                newPosY = Math.clamp(newPosY / sceneRootNode.scale, -(maxViewY - heightOffset), -(minViewY - heightOffset)) * sceneRootNode.scale;

                sceneRootNode.x = newPosX;
                sceneRootNode.y = newPosY;
            }


            // Move the wanderers around.
            for(var i:int=0; i<25; i++)
            {
                var wander = wanderers[i];
                wander.waitCount = wander.waitCount - 1;
                if(wander.waitCount > 0)
                {
                    // Set the frame.
                    if(wander.texPrefix != null)
                    {
                        var curFrame = wander.waitCount % 20;
                        if(curFrame < 10)
                            wander.textureName = wander.texPrefix + "000" + curFrame + ".png";
                        else
                            wander.textureName = wander.texPrefix + "00" + curFrame + ".png"; 
                    } 

                    wander.posX = wander.posX + wander.velX;
                    wander.posY = wander.posY + wander.velY;

                    // NOTE: Due to using cocos2d's Z-sorting, this is the slowest part
                    // of the demo. A more optimized iso sort would allow you to have
                    // many many more sprites on screen. Note that perf here is proportional
                    // to total sprites in the iso sort, not the number of wanderers.
                    wander.setIsometricPosition((wander.posX + wander.posY) - 25, (wander.posX - wander.posY) + 110);
                }
                else
                {
                    var speed = Math.floor((Math.random() * 2) + 1);
                    if(Math.random () > 0.5)
                    {
                        if(Math.random() > 0.5)
                        {
                            wander.texPrefix = "grey_walk_north";
                            wander.velX = speed;
                            wander.velY = 0;
                        }
                        else
                        {
                            wander.texPrefix = "grey_walk_east";
                            wander.velX = 0;
                            wander.velY = speed;
                        }
                    }
                    else
                    {
                        if(Math.random() > 0.5)
                        {
                            wander.texPrefix = "grey_walk_south";
                            wander.velX = -speed;
                            wander.velY = 0;
                        }
                        else
                        {
                            wander.texPrefix = "grey_walk_west";
                            wander.velX = 0;
                            wander.velY = -speed;
                        }           
                    }

                    wander.waitCount = 90 / speed;
                }
            }
        }
    }
}