Monday, March 01, 2010
We're at the start of week 3 of the new game, Destroy More Cars, which unsurprisingly is the sequel to our Destroy All Cars game.

It's the first outing for our blitter engine, so I thought I'd touch on that quickly.

dmc_grab1.jpg



If you've read the previous post you'll see there that I recommend a blitter engine for CS5 / iPhone development so I've been working on ours so it's there ready for the next project.

At the core of the blitter is the Canvas class. This holds the bitmap we're plotting to, does the lock / unlock thing, clears the canvas when needed ( By copying the background bitmap on there rather than fillRect ) and calls the children display objects.

Next up is the PlayField class. These are in effect layers. When I code I usually create sprites as holders and add these to the stage in order to simulate IDE layers, the PlayField class is just the same really.

At the bottom of this hierarchy are the building blocks, the Bobs. These are just simple sprites that are plotted to the canvas using copyPixel ( I've started work on a ComplexBob class which uses draw(), slower but it supports all the things that copyPixel can't, such as scale, alpha, rotation etc. Pain in the arse to code though, so slightly on the backburner and I'm just using bitmap's as simple sprites in the mean time ).

To make this truly useful I've tried to copy the existing DisplayObject as closely as possible, so for example if we've got an animated bob we can use bob.gotoAndPlay(bob.currentframe+1), to add a bob to a playField it's as simple as playField.addChild(bob) etc.
Depth sorting is in there too which makes life so much simpler ( The PlayField class is basically a list manager, making sure the bobs are always in the order we want. That's all depth sorting is really, just specifying what order to plot things in ).

One thing I stumbled upon the other day was the very sexy stage.invalidate(); To put it simply it's a way of flagging up the stage has been made dirty ( Altered ). If you have a stage.addEventListener(Event.RENDER,update);
listener, then when a stage.invalidate() is called at the end of the frame just before plotting everything our update method is called ( Via the RENDER event ). This means we don't have to call the update method as we usually would during our enterFrame main loop, in effect it handles itself, like the real display list does.

With using that, Vector lists which we loop through, lock / unlock etc. it all runs pretty quickly. Benchmarks I find are always a bit throwaway unless they're part of a really detailed set of comparisons, it's so easy to put a bit of spin on them to prove your code is the fastest.
Obviously I can't be bothered to do proper benchmarks, so prepare for some spin. DMC is running 6 layers of parallax on a 640x480 screen. It's got a handful of box2D objects ( The images are bitmaps rather than plotted via the blitter ) . In the screen grab above I think it's running 70 rain particles, but when I was testing I cranked that up to 500 just to push things. We've also got up to 80 glass particles ( Those little blue pixels at the top right of the grab ), soft particles for the smoke ( It's the first time I've used for particles for that as opposed to just pre-rendered images ) and there are 20 bits of debris spinning around in 3D, along with car door which rotates around in 3D too. With all that running it didn't drop a frame, stayed at a rock solid 35fps.

I think damage maps may speed things up in certain situations, but I'm more than happy with how it's performing right now, so that can be done if it's needed.

And that dear reader is how our blitter works.

Squize.

Monday, March 01, 2010 10:46:19 PM (W. Europe Standard Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, October 15, 2009
I've done this effect to death, used it again in the upcoming DAC ( Whose launch has been delayed due to having to add achievements, luckily someone else is doing that ) so I figured it's time to put it out to pasture and share it around.

'cause we've not been posting as often here, I thought I'd treat everyone to some nice grabs to break up the code slightly.

Basically we want to create a simple gradient rectangle movieclip, like so

swatch_grab.png

And we end up with something looking like this,

gradient_grab.png

A simple rectangle that's slightly bigger than your stage, that fades out to one side. It needs to be bigger than your stage 'cause we want all the gradient part with the alpha blending to be off-screen when we first apply it as a mask.

Next up, the boring old code bit.

package Classes {
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.DisplayObject;
    import flash.display.Sprite;
    import flash.display.Stage;
    
    import gs.TweenLite;
    
    public class Transition {

//---------------------------------------------------------------------------------------
// Assets
//---------------------------------------------------------------------------------------
        [Embed("../_assets/assets.swf",symbol="transitionMaskMC")]
        private var transitionMaskMC:Class;

//---------------------------------------------------------------------------------------
// Properties
//---------------------------------------------------------------------------------------
        private var playField:Sprite;

        private var grabBitmapData:BitmapData;
        private var grabBitmap:Bitmap;

        private var transitionMask:Sprite;
            
//------------------------------------------------
// System
//------------------------------------------------
        private var main:Main;
        private var mainMovie:DisplayObject;
        private var stage:Stage;

        private var callBack:Function;
        
//---------------------------------------------------------------------------------------
//Constructor
//---------------------------------------------------------------------------------------
        public function Transition(){
            main=Main.getInstance();
            mainMovie=main.getMainMovie();
            stage=main.getStage();

            grabBitmapData=new BitmapData(640,480,false,0);
            grabBitmap=new Bitmap(grabBitmapData);
            grabBitmap.cacheAsBitmap=true;
            
            transitionMask=new transitionMaskMC();
            transitionMask.cacheAsBitmap=true;
        }
        
//---------------------------------------------------------------------------------------
// Public
//---------------------------------------------------------------------------------------
        public function toString():String {
            return "Transition";
        }        

//---------------------------------------------------------------------------------------
        public function grabScreen(callBackArg:Function):void{
            callBack=callBackArg;

            if(playField==null){
                playField=main.getInitObj().getPlayField().transition;
            }

            grabBitmapData.draw(stage);
            transitionMask.x=-160;
            grabBitmap.mask=transitionMask;
            playField.addChild(grabBitmap);
            playField.addChild(transitionMask);

            callBackArg();

            TweenLite.to(transitionMask, 1.5, {x:640, delay:0.2,onComplete:houseKeeping});

        }

//---------------------------------------------------------------------------------------
        public function houseKeeping():void{
            playField.removeChild(transitionMask);
            playField.removeChild(grabBitmap);
        }

//---------------------------------------------------------------------------------------
    }
}

There's really no way to make code look interesting in a blog is there ?

            grabBitmapData=new BitmapData(640,480,false,0);
            grabBitmap=new Bitmap(grabBitmapData);
            grabBitmap.cacheAsBitmap=true;
            
            transitionMask=new transitionMaskMC();
            transitionMask.cacheAsBitmap=true;

Here in our constructor we're just creating a bitmap which we're going to use when we take a snap shot of the whole screen, and create our transisitonMask ( ie, the gradient mc we've just made ) instance.
The key thing is to turn cacheAsBitmap=true on both, otherwise the gradient mask won't work, it'll just be a boring old normal gradient free mask.

            grabBitmapData.draw(stage);
            transitionMask.x=-160;
            grabBitmap.mask=transitionMask;
            playField.addChild(grabBitmap);
            playField.addChild(transitionMask);

            callBackArg();

            TweenLite.to(transitionMask, 1.5, {x:640, delay:0.2,onComplete:houseKeeping});

This is the guts of what we're doing. Firstly use the always sexy draw() to grab the whole screen, then position the mask so our alpha blended bit is off screen. We want the whole snap shot bitmap to be masked normally at first.
We then simply add both the bitmap and mask to the screen. You'll notice the playField reference there. What I do, as I'm a layer loving kinda guy, is create a simple class that creates all the holders for all the things in a game at the depths I want. It's like codey version of using layers that way. So for your transition "layer", that should be on top of everything else ( You could also add it directly to the stage, same thing, I'm just paranoid that I'll need a layer above the transition for some reason ).
We've got the bitmap, the mask, the bitmap is now masked so all we need to do is cheat, and use the very lovely TweenLite. That just slides our mask all the way across the screen, in effect removing our mask, but with the nice alpha blending on the edge it makes it appear soft. Kinda like in Star Wars.

Notice the callback and the slight delay in the tween in there ? What we do is call the transition method, and then jump back to our main code where we're setting up the screen "beneath" the transition, so it wipes away to reveal our next screen.

[Update: Destroy all Cars is now ( Finally ) live, so you can see it action and see if you want to go to the effort of using it yourself ]

Squize.

Thursday, October 15, 2009 11:57:14 AM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, August 06, 2009
So after the set of articles about random level generation I wanted to put that into use (alas not in the scale I planed it out - mind you) - so I came up with a nice little 3 week project - that was 5 weeks ago.

The basic idea is a simple top/down shooter blended with a good portion of Gauntlet added (hordes of enemies, maybe). And while it's fun to watch levels being created by code it involves a good deal of additional work, like changing the tilesets, adding dirt and stains.

The X entries seemed to be a good idea, so I'm going to publish a few wip builds soon, too (alas not as many as Squize).

In order to see something at all (except the test visuals from the article tests) I needed to convert the cell based dungeons into someting tile based, and write a simple scroller to display the created maps. I decided that each cell should consist of 6 by 6 tiles, so I could use a one tile wide border for the walls (if any) and still have 4 tiles walking space. Next point on the list was the question about how to store the data generated - as I didn't wanted to convert cells/tiles at runtime before they're being displayed. Storing them in an array seemed a good idea, but I wanted to try something new...

Not to mention that converting 50x50 cell dungeon would result in an 300x300 array.

After a few minutes I thought that using BitmapData would be a nice try to store the map, as it'll give me a quite quick direct access 3 dim array (x,y, R,G,B,A) which I could use multiple times. so I used the alpha chanel for the tileset, the red one for the actual tile, blue for pathfinding and green for effects/2nd layer.

Right, why storing a tileset?

In my idea each room has basically the same tiles (ie. walls, doors, corners and floor) and the conversion would be much easier if I would use the same tiles over and over. So I came up with storing tiles in tilesets, which offsets the tiles on the tilesheet.
The creation process is quite easy this way:

First I have to create a tileset and give it a name, say "Corridor".

myTileset = new Tileset("Corridor");

Then add tiles to that tileset, giving it a name and and x/y offset in the tilesheet (I wrote data class for that):

myTileset.addTile(new Tile("empty", 0, 0));
myTileset.addTile(new Tile("Floor00", 1, 0));
myTileset.addTile(new Tile("WallN", 0, 1));
myTileset.addTile(new Tile("CornerInN", 1, 1));


Internally I use the tile's index, but the cell/tile converter just uses the name:

if (!myCell.isUnused) {
                        
    iTile = myTileset.index(strFloor);
    iPath = 0
    iSpecial = 0;
    bmpdMap.fillRect(new Rectangle(xx, yy, iTilesPerCell, iTilesPerCell), this.rgba(iTile, iSpecial, iPath, iTileset));
    
    // walls
    for (i = 0; i < Dir.NUM_BASEDIR; i++) {
        
        if (!myCell.isOpen(i)) {
            cx = xx + aWall[i].x;
            cy = yy + aWall[i].y;
            
            iTile = myTileset.index("Wall" + Dir.shortName(i));
            iPath = 255;    // so you can't walk there (for the bool map pathfinding)
            iSpecial = 0;
            bmpdMap.fillRect(new Rectangle(cx, cy, aWall[i].width, aWall[i].height), this.rgba(iTile, iSpecial, iPath, iTileset));
            
            // door
            if (myCell.hasDoor(i)) {
                
                // door frames are painted "above" the floor, so I use the special layer for that
                iTile = myTileset.index(strFloor);
                iPath = 127;
                iSpecial = myTileset.index("Door" + Dir.shortName(i) + "0");
                bmpdMap.setPixel(cx + aDoor[i][0].x, cy + aDoor[i][0].y, this.rgba(iTile, iSpecial, iPath, iTileset));
                
                iSpecial = myTileset.index("Door" + Dir.shortName(i) + "1");
                bmpdMap.setPixel(cx + aDoor[i][1].x, cy + aDoor[i][1].y, this.rgba(iTile, iSpecial, iPath, iTileset));
                
            } // ToDo: add windows if possible ...
            
        }
        
    }
    
    // ... draw corner and outer coners [skipped]
}

Ah. Nice and easy :)

So by using a different tileset you can easily control the visuals of a room.

Next thing on the list: the scroller - more thinking here ...

I wrote the Scroller class as extension to Sprite so I could add my own sprites and use the Sprites scrollrect for clipping. Adding a Scoller to the stage became easy as 123:

this._Scroller = new Scroller(620, 460, 20, 20); // width, height, tilewidth and tileheight
this._Scroller.name = "scroller";
this._Scroller.x = 10;
this._Scroller.y = 10;

this.addChild(this._Scroller);

this._Scroller.setTileset(this._bmpdTileset, this._TilesetCollection);
this._Scroller.setMap(this._bmpdMap); // the freshly generated map
this._Scroller.setCenter(310, 230);   // alows offsettin the origin, so 0,0 can be at the center of the scroller

this._Scroller.xPos = 0;
this._Scroller.yPos = 0;
this._Scroller.draw();
// updates the visuals of the scroller

the way the scroller is set up (I still need to optimize redrawing, though) makes it possible to use it with tween utils like TweenLite:
TweenLite.to(this._Scroller, 1, [xPos: 100, yPos: 100, onUpdate: this._Scroller.draw});

So after a few days of coding it looks like this:
DialZ_pre_00_small.jpg
(scaled version)

The visibilty test is in place (you can only see the room you're currently in and vector boundaries are created (the green rect in the room, door triggers (blue rect)). The map in the left corner is shwoing the pathfinding bounds (helpfull for testing, too) and will be replaced by a minimap later (circular, hopefully).

Right now I'm writing the vector intersection methods (I'm using math instead of the tiledate for collisions) - so I thing the next entry will feature that.

nGFX

Thursday, August 06, 2009 12:28:03 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Saturday, July 25, 2009
We've had a plan for a while now for gathering the stats from cronusX and displaying them in widgets on a custom page. We weren't allowed to data mine the version of cronusX on Candystand, we're only go to do it in the viral version, which is good 'cause it actually buys us some time to get them done.

We managed to get some data when we were beta testing the game, so there are some figures for test data ( I did a quick post with some stats here ).

First widget is the number of asteroids destroyed, here's a clipped screeny

widget_rock.png

Rather than just having a number and that's it, I'm trying to liven these widgets up by hopefully making them interesting. One thing the uk newspapers love doing is when say a new Dinosaur is discovered, they compare it to either black cabs or double decker buses, as apparently if they just put a figure we can't picture it. Shove a bus next to it, and we get it.

So in the spirit of a dumbed down newspaper article, I thought let's compare the weight of the total number of asteroids destroyed to double decker buses.

Figuring out the value is where it gets boring, and as it bored me I'm just passing it on. We're all about sharing. Plus regular readers will know every single time I put any sort of maths on here I get it wrong, so this post is like a sanity check for my calculations.

How big is an asteroid in the game ? Around 96 pixels. The player ship is around 45 pixels, so my train of thought was to set a real life size for the ship, multiply that by 4, and that would be the cubic size of an asteroid. I figured I'd just compare the ship to something in real life, and a F-15 Eagle seemed a good size. They're 63 feet long so let's pretend our ship is 63 feet long and wide too.
That makes our 96 pixel asteroid 252 cubic feet. In meters that's 76. The reason to covert to meters ? 'Cause this page gives us the some nice weight figures which need meters. Apparently we're looking at 3 tons per cubic meter, therefore our rocks weight is 76*3=228 tons. We're nearly there. All we need now is the all important weight of a bus, and the net never lets you down does it ? I went for unladen which is 12 tons. Almost there.

(numberOfRocks*228)/12

That's it. All this effort just for a stat. I think there are going to be another 8 or so widgets, so I'm going to have to come up with more silly things to compare the stats to. Now best you move along otherwise you're going to be asleep soon.

Squize.

Saturday, July 25, 2009 5:59:13 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [4]  |  Trackback
 Tuesday, July 14, 2009
In the last two articles  we made a maze and then destroyed it by adding a lot of free space. In part 3 of this articles I'm going to add some rooms to the empty space and add some doors ...

Part 3 - part 1 - why seperate things and make rooms?

At a first glance it might not be necessary to seperate data into dungeon, room and cell, but thinking ahead a bit it should make sense ...
My idea is that you can have the dungeon which holds the complete map (with the basic room data rendered into it) and the rooms so you access them easier and most important do some magic with them later. One of the neat things is that you could use a different tiling for rooms making them more detailed or use the additional map data for skinning (when rendering the dungeon into a tile based map).

The main difference between a room and a dungeon is that the room consists of empty cells and has walls along it's boundaries. So the first additional method we'd add to the Room class will be the init method, which simply sets all cells so they form a rectangular room.

public function initCells ():void {
            
    var x:uint;
    var y:uint;
    var cellTmp:Cell;
            
    for (x = 0; x < this.iWidth; x++) {
        for (y = 0; y < this.iHeight; y++) {
                    
            cellTmp = this.cell2(x, y);
            cellTmp.setWalls(WallType.OPEN);
                  
            if (x == 0) cellTmp.setWall(Dir.WEST, WallType.WALL);
            if (x == (this.iWidth - 1)) cellTmp.setWall(Dir.EAST, WallType.WALL);
            if (y == 0) cellTmp.setWall(Dir.NORTH, WallType.WALL);
            if (y == (this.iHeight - 1)) cellTmp.setWall(Dir.SOUTH, WallType.WALL);
        }
    }
}

We could later add methods to create different rooms (ie. two overlapping rectangles, circular rooms), but for now that will do ...

I also added a getter setter for Offset to the room, so we can modify the x and y pos of the bounding rect that we stored in the map class (you'll see later what's that for).

So we now have a room, but how do we get that sucker into the map, or (as just putting it into the map isn't really an issue) where can we place it *best*.

There are a few things that I want to watch when placing the rooms ...
- a room should not overlap any existing room, we rather don't at it
- placing a room at a place where it doesn't touch anything, is something we don't want, too
- rooms overlapping corridors should be avoided
- rooms touching dead ends is something we want (what's nice than finding a room after a long winded corridor?)
- rooms touching any sort of wall is OK, too

Yet again we (as humans) could just look at the map and say "here, there and there" and done, but that stupic piece of plastic cannot... we need to apply some sort of scoring to the whole placement mess.

Here's a bit of pseudo code ...
  • start with a VERY high best score, lets say 999999999999 and set current score to 0 ...
  • Loop over every cell in the dungeon
    • at any given position check if the new room overlaps any rooms already in there
      if so, we add 5000 to our current score, otherwise we add nothing
    • now loop over every cell in the room and compare it with the current dungeon cell (offsetting the room to the current position)
      • if the current room cell touches an empty cell (in the dungeon), add 10
      • if we touch a wall, add 3
      • if we touch a dead end, add 1
    • if the final current score is lower than the best score, sreplace the best score and store the current position as the best possible location
  • if the score is higher than the "room overlaps room" score, assume that it only can be placed overlapping a room and drop it, otherwise add it to the dungeon
Here is the scoring code:

private const TOUCH_DEADEND:int = 1;
private const TOUCH_WALL:int = 3;
private const TOUCH_EMPTY:int = 10;
private const OVERLAP_ROOM:int = 5000;
private const OVERLAP_CORRIDOR:int = 100;
        
public function fitMazeRoom (myRoom:Room):Boolean {
    
    var iBestScore:int = 999999999;
    var iScore:int = 0;
    var pBestPos:Point = new Point(0, 0);
    var pOffset:Point = new Point(0, 0);
    
    var cellDungeon:Cell;
    var cellNext:Cell;
    
    var rectTmp:Rectangle = myRoom.rectBound.clone();
    
    var i:uint;
    
    var x:int;
    var y:int;
    
    var xx:int;
    var yy:int;
    
    var iRoomID:uint;
    
    var bAddRoom:Boolean = false;
    
    // loop over map (- roomsize)
    for (y = 0; y < (this.iHeight - myRoom.iHeight + 1); y++) {
        for (x = 0; x < (this.iWidth - myRoom.iWidth + 1); x++) {
            
            // do the scoring ...
            iScore = 0;
            
            // check room/room overlapping
            rectTmp.x = x;
            rectTmp.y = y;
            for (i = 0; i < this._aRoom.length; i++) {
                if ((this._aRoom[i] as Room).rectBound.intersects(rectTmp)) {
                    iScore += OVERLAP_ROOM;
                }
            }
            
            // check room/dungeon overlapping
            for (yy = 0; yy < myRoom.iHeight; yy++) {
                for (xx = 0; xx < myRoom.iWidth; xx++) {
                    pOffset.x = (x + xx);
                    pOffset.y = (y + yy);
                    
                    cellDungeon = this.cell(pOffset);
                    if (cellDungeon.iType == RoomType.CORRIDOR) iScore += OVERLAP_CORRIDOR;
                    
                    if (yy == 0) {
                        iScore += this.getCellScore(this.cell(this.getNextPos(pOffset, Dir.NORTH)), Dir.NORTH);
                    }
                    if (xx == (myRoom.iWidth - 1)) {
                        iScore += this.getCellScore(this.cell(this.getNextPos(pOffset, Dir.EAST)), Dir.EAST);
                    }
                    if (yy == (myRoom.iHeight - 1)) {
                        iScore += this.getCellScore(this.cell(this.getNextPos(pOffset, Dir.SOUTH)), Dir.SOUTH);
                    }
                    if (xx == 0) {
                        iScore += this.getCellScore(this.cell(this.getNextPos(pOffset, Dir.WEST)), Dir.WEST);
                    }
                }
            }
            
            if (iScore < iBestScore) {
                iBestScore = iScore;
                pBestPos = new Point(x, y);
            }
            
        }
    }
    
    // add to dungeon if it doesn't overlap any other rooms
    if (iBestScore < OVERLAP_ROOM) {
        myRoom.pOffset = new Point(pBestPos.x, pBestPos.y);            
        bAddRoom = true;
    }
    
    return bAddRoom;
    
}

private function getCellScore (cellNext:Cell, iDir:int):int {
    
    var iScore:int = 0;
    
    if (cellNext.iType == RoomType.CORRIDOR) {
        if (cellNext.isDeadEnd) {
            iScore += TOUCH_DEADEND;
        } else if (cellNext.hasWall(Dir.getOppositeDir(iDir))) {
            iScore += TOUCH_WALL;
        } else {
            iScore += TOUCH_EMPTY;
        }
    } else {
        if (cellNext.iType == RoomType.ROOM) {
            if (cellNext.hasWall(Dir.getOppositeDir(iDir))) {
                iScore += TOUCH_WALL;
            } else {
                iScore += TOUCH_EMPTY;
            }
        } else {
            iScore += TOUCH_EMPTY;    
        }
    }
    
    return iScore;
    
}

That's ugly and not very fast, but it works.

Some additional info about adding the room to the dungeon: whenever we place a room cell in the dungeon map it's a good idea to check if it overwrites a corridor and if it's a cell on the outer bounds of the room add a new wall to the touching cell (if it's not empty) ...

So far so good, we have rooms in the map, but they cannot yet be reached because we're missing doors ...

Part 3 - part 2 - adding doors and cleaning up

You might ask why I haven't added the doors as soon as I've added the room to the dungeon (and I might just reply that I just didn't mention it), but nope, I didn't add doors - that's the next step.

The reason is quite simple, though. I don't want doors cluttered all over the space and because of that I added another (optional) thing to the room data: hasDoorInDirection ... this way we make sure that there is only one door per wall / room when we add doors ...

Yet again we loop over all rooms and over their outer bounding cells, if we touch another cell, store the current position as possible door location. Then pick a random one per direction and check if it touches another room and if this room might already have a door ...
I guess that's easier to explain with some more code:

private function createDoors (myDungeon:Dungeon, bOneDoorPerRoom:Boolean = true):void {
    
    var rnd:MersenneTwister = MersenneTwister.getInstance();
    
    var i:uint;
    var j:uint;
    
    var x:uint;
    var y:uint;

    var cellTouch:Cell;
    var myRoom:Room;
    
    var aDoor:Array;
    var pDoor:Point;
    var pNext:Point;
    
    for (i = 0; i < myDungeon.aRoom.length; i++) {
        
        myRoom = (myDungeon.aRoom[i] as Room);
        
        aDoor = [[], [], [], []];

        // collect possible door locations ...
        for (y = 0; y < myRoom.iHeight; y++) {
            for (x = 0; x < myRoom.iWidth; x++) {
                
                pDoor = new Point(myRoom.pOffset.x + x, myRoom.pOffset.y + y);
                
                if (y == 0 && pDoor.y > 0) {
                    pNext = myDungeon.getNextPos(pDoor, Dir.NORTH);
                    cellTouch = myDungeon.cell(pNext);
                    if (!cellTouch.isUnused || cellTouch.iType == RoomType.CORRIDOR) {    // the check for a cooridor is needed because they might be just one cell long ...
                        aDoor[Dir.NORTH].push(pDoor);
                        if (cellTouch.isDeadEnd) aDoor[Dir.NORTH].push(pDoor); // double chances for dead ends ...
                    }
                }
                
                if (x == (myRoom.iWidth - 1) && pDoor.x < (myDungeon.iWidth - 1)) {
                    pNext = myDungeon.getNextPos(pDoor, Dir.EAST);
                    cellTouch = myDungeon.cell(pNext);
                    if (!cellTouch.isUnused || cellTouch.iType == RoomType.CORRIDOR) {
                        aDoor[Dir.EAST].push(pDoor);
                        if (cellTouch.isDeadEnd) aDoor[Dir.EAST].push(pDoor); // double chances for dead ends ...
                    }
                }
                
                if (y == (myRoom.iHeight - 1) && pDoor.y < (myDungeon.iHeight - 1)) {
                    pNext = myDungeon.getNextPos(pDoor, Dir.SOUTH);
                    cellTouch = myDungeon.cell(pNext);
                    if (!cellTouch.isUnused || cellTouch.iType == RoomType.CORRIDOR) {
                        aDoor[Dir.SOUTH].push(pDoor);
                        if (cellTouch.isDeadEnd) aDoor[Dir.SOUTH].push(pDoor); // double chances for dead ends ...
                    }
                }
                
                if (x == 0 && pDoor.x > 0) {
                    pNext = myDungeon.getNextPos(pDoor, Dir.WEST);
                    cellTouch = myDungeon.cell(pNext);
                    if (!cellTouch.isUnused || cellTouch.iType == RoomType.CORRIDOR) {
                        aDoor[Dir.WEST].push(pDoor);
                        if (cellTouch.isDeadEnd) aDoor[Dir.WEST].push(pDoor); // double chances for dead ends ...
                    }
                }
            }
        }
        
        // now just pick one door per side ...
        for (j = 0; j < Dir.NUM_BASEDIR; j++) {
            
            if (aDoor[j].length > 0) {
                
                pDoor = aDoor[j][rnd.Range(0, (aDoor[j].length - 1))];
                pNext = myDungeon.getNextPos(pDoor, j);
                
                if (!myRoom.hasDoor(j)) {
                    myRoom.setDoor(j, pDoor);
                    
                    if (bOneDoorPerRoom && myDungeon.cell(pNext).iType == RoomType.ROOM) {
                        myDungeon.getRoom(myDungeon.cell(pNext).iValue).setDoor(Dir.getOppositeDir(j), pNext);
                    }
                    
                    myDungeon.cell(pDoor).setWall(j, WallType.DOOR);
                    myDungeon.cell(pNext).setWall(Dir.getOppositeDir(j), WallType.DOOR);
                }
            }
        }
    }
}

Viola done ... but wait one more thing, cleaning up ...

The last step might not be needed, but imho it makes some nice dungeons: after we've added all the rooms and doors, we remove all remeaning dead ends. This way there will be no corridors just ending somewhere and the map looks nicer.

So we just run the removeDeadEnds method again, this time with 100% ... now:done.

As with the last parts, the link to a working demo of the whole mess is here or here.

nGFX

Tuesday, July 14, 2009 11:56:39 AM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [4]  |  Trackback
 Monday, June 29, 2009
This is part 2 of my collection of articles that'll deal with the theory (and the actual creation) of random dynamic levels for a (space)game. In part one we created a damn pretty maze and in part two we're going to modify it a good deal.

If you take a look at part one's output you'll notice that the code generates a pretty random maze. And there we got out first drawback: it's pretty darn random, way to random to assemble a man made structure and not quite what we would expect a spacestation / space ship to look like.

So the first modification I'm going to add will be a method that can reduce the randomness of the maze.

Part 2 - part 1 - making something not that random

My idea is to qualify the randomness by a percentage value, so a random factor of 0 will give you long straight passages that only change direction if the need to (random at that), while using a value of 100 the method will never (as far as it possible) return the same direction twice.

Of course that tiny little addition causes a lot of fuzz and requieres to rewrite a part of the core maze generator function. In part 1 I used a method to get all surrounding cells of a given point, but in order to use the direction modifier we need to use directions instead.

          //... skipped

          while (iCellCount < iTotalCells) {
                
                // get neighbor cells ...
                aCellDirections = myDungeon.getPossibleDirections(pCurrentCell);
                
                // set the cell
                if (aCellDirections.length != 0) {
                    
                    /* old way no direction modification used
                    iRndCell = rnd.Range(0, (aCellNeighbors.length - 1));
                    iRndDir = Dir.getDirFromPoint(pCurrentCell, aCellNeighbors[iRndCell]);
                    */

                    iRndDir = this.getFactoredRandomDir(iLastDir, aCellDirections, iDirChange);
                    pNextCell = myDungeon.getNextPos(pCurrentCell, iRndDir);
                    iLastDir = iRndDir;
                    
                    // remove walls
                    myDungeon.cell(pCurrentCell).setWall (iRndDir, WallType.OPEN);
                    // old way: myDungeon.cell(aCellNeighbors[iRndCell]).setWall(Dir.getOppositeDir(iRndDir), WallType.OPEN);
                    myDungeon.cell(pNextCell).setWall(Dir.getOppositeDir(iRndDir), WallType.OPEN);
                    
                    // store for later use ...
                    aCellStack.push(new Point(pCurrentCell.x, pCurrentCell.y));
                    // old way: pCurrentCell = new Point(aCellNeighbors[iRndCell].x, aCellNeighbors[iRndCell].y);
                    pCurrentCell = new Point(pNextCell.x, pNextCell.y);
                    
                    iCellCount++;
                } else {
                    pPopCell = aCellStack.pop();
                    pCurrentCell = new Point(pPopCell.x, pPopCell.y);
                }
                
            } // while

Some new variables in there: iLastDir (so we can keep track of the last direction used), pNextCell (a point that stores the next cell, basically just a temp. variable), iRndCell has been removed and aCellNeighbours has been renamed to aCellDirections ...

There are two new methdods: getPossibleDirections and getFactoredRandomDir. The first one returns an array that just contains directions that can be used (ie. cells that have not been visited yet), directions are simply stored as 0=North, 1=East and so one (I've encapsulated them into a Dir class to make it easier to read). The second method is a neat example how to make things overly complicated ...

        private function getFactoredRandomDir (iLastDir:int, aListDir:Array, iFactor:int = 50):int {
            
            var rnd:MersenneTwister = MersenneTwister.getInstance();
            var bChangeDir:Boolean = (rnd.Range(0, 99) < iFactor);
            
            var iReturn:int = iLastDir;
            
            // the last used dir is not in the list of possible new directions, so we need to pick a random one ...
            if (aListDir.toString().lastIndexOf(iLastDir.toString()) == -1) {
                iReturn = aListDir[rnd.Range(0, (aListDir.length -1))];
            } else {
                
                // we must change direction AND have at least 2 choices
                if (aListDir.length > 1) {
                    
                    if (bChangeDir) {
                        while (iReturn == iLastDir) {
                            iReturn = aListDir[rnd.Range(0, (aListDir.length -1))];
                        }
                    }
                    
                } else {
                    // just pick what's left ...
                    iReturn = aListDir[0];
                }
                
            }
            
            return iReturn;
            
            
        }


AS3 arrays (in CS3) don't have the nice method I know from c#: contains which would have been oh so easy to use here. I toyed for a fraction of a second with the idea to use a loop to check if a given value would be in an array, but then decided to go ... quick and dirty and use toString and lastIndexOf instead.

The code above is quite easy, so I only do a quick run through it...
- decide if we need to apply a direction change
- if we need to, check if the last dir is in the list of possible dirs, if not just pic a random new (this applies to both states: need to change and keep direction)
- otherwise just pick a random dir until it's not equal the last dir used

That's it.

Running the test app with different values seems to produce the desired results:
0% produces the most possible straight halls,
50% produces somewhat random halls
100% produces a maze with no straight hall at all.

Part 2 - part 2 - still way to much filled space ...

Looking at the maze reveals that there are no free spaces in it, of course we could just paint rooms over it, but I doubt it'll look like what I have in mind.
Randomly removing cells from the map is no option (even if we do check if we would just block a passage), but what about removing cells that just end the passage (ie: dead ends).
Looking at the maze again, it seems that we have (depending on the randomness of direction changes) a lot of them, so our next task would be to find those dead ends and remove them. The first "problem" that comes to me is that each time we remove dead ends, we'd create new ones. In order to clean up the map we only run the "removDeadEnds" methods a couple of times and we're done - right?

Not quite.

If we choose some unlucky values, it might happen that we kill the whole maze and that's something we don't want at all.

I decided to use a percentage of TotalCells that I want to be removed, so if we use 50%, the method should remove half of all available cells.

        public function removeDeadEnds (myDungeon:Dungeon, iRemoveDeadEnd:int = 20):Dungeon {
            
            var rnd:MersenneTwister = MersenneTwister.getInstance();
            
            var i:int;
            var j:uint;
            var iDir:int;
            var iRndCell:int;
            
            var iDeadEndsToRemove:int = Math.ceil((myDungeon.iWidth * myDungeon.iHeight) * iRemoveDeadEnd / 100);
            var iDeadEndCount:int = 0;
            
            var bExit:Boolean = false;
            
            var aTmp:Array;
            
            // the worst case may only return one dead end per run, so
            // to be sure we run it as many times as we may max need
            for (i = 0; i < iDeadEndsToRemove; i++) {
                
                aTmp = myDungeon.getDeadEnds();
                
                if (aTmp.length > 0 && !bExit) {

                    while (aTmp.length > 0) {
                    
                        // this is to make sure that the cells are somewhat even
                        // distributed if we do not use the whole lot
                        iRndCell = rnd.Range(0, (aTmp.length - 1));
                        iDir = myDungeon.cell(aTmp[iRndCell]).getDeadEndDir();
                        
                        myDungeon.cell(myDungeon.getNextPos(aTmp[iRndCell], iDir)).setWall(Dir.getOppositeDir(iDir), WallType.WALL);
                        myDungeon.cell(aTmp[iRndCell]).setWalls();
                        
                        aTmp.splice(iRndCell, 1);
                        
                        if (++iDeadEndCount >= iDeadEndsToRemove) {
                            bExit = true;
                            break;
                        }
                        
                    }
                } else {
                    break;
                }
                
            }
            
            return myDungeon;
            
        }


The comments should explain quite well what's going on in there. Only thing to mention is that I pic random dead ends if there are more available dead ends than cells to remove.

Compile and test ... and viola well done for today. :)

(I must admid it took longer to type all that than to code, so I had a bit of spare time left and coded something alse ;) )

I think that is enough for today, you can see the result (and from the upcoming articles, too) Random Dynamic Level Creation Test page (or here if the server is down).

nGFX

Monday, June 29, 2009 2:34:28 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, June 24, 2009
After a good while of non-techy pimpings I decided to go the x++ route and describe the process of diving into a new game ...

(CE is again on hold due to some gameplay issues I found during testplays - oh well)

There is no name to the game yet, but I can say as much as it will feature random dynamic level creation (as used in Diablo 1 for example), sci-fi themed, using RPG elements and use Unity (though the levels created will look quite different to Diablo, but the idea is the same).
I want to have random levels because that would add to the replay value of the game. I want a single game to last between 15 and 45 minutes. You should be able to start playing and kill the end-boss in that time. After that you should be able just play again, but with different set of maps ...

But to get things rolling a lot quicker I wanted to rapid prototype my ideas using flash/AS3 and then port it to c#.
The reason for this is quite simple: output. In order to "see" what the level will look like without having to worry about how to display them (ie. place 3d walls, create the assets). With using flash i can just grab the data from the generator classes and use the drawing api to quickly throw out a few lines to show the generated data.

Let's dive straight in.

Part 1 - part 1: To cell or to tile ... and is there anything we need before that?

Before I started I thought that it would be nice to move between maps (in case you thought you forgot something), to do so there needs to be a way to store the maps ... I thought of 2 methods:
a) store all visited maps
b) make them reproduceable

I prefer the later one.

So instead of using some built in random number generator I decided to use my "own" that takes a seed and the produces the same set of random numbers when using that seed - perfect.
I found some old AS1 source of some older rnd gen that I could have ported but a quick search showed that there are some more powerfull ones. After bit of research I decided to go with the Mersenne Twister algorithm. As I was too lazy to see if there was an AS3 port, I wrote my own implementation, though you could use any rnd method you want.

The next one was a bit tricky: cell based or tile based?
Tiles are easy to use and to handle, but they are limited to a single spot and mostly do only have a single "state", ie. you can walk on them or not. This might be ok in most cases but for what I have in mind they are too limited.

A cell in my case is a tile with 4 walls (north, east, south and west), if all 4 walls are set, the cell is "closed" ie. a solid rock in a clasical dungeon. The cell also stors a single integer value (so based on it's useage I could store an index in it.
Here is the code for the cell:

package com.gamingyourway.Dungeon {
    
    /**
     * Cell datatype
     * @version 2009 06 24
     * @author nGFX
     */

    public class Cell {
        
        public static const WALL:int = 0;
        public static const OPEN:int = 1;
                
        private var _aWall:Array;    // array of values ... 0=empty ...
        private var _iValue:int;    // stores a single value, ie. room num
        private var _bVisited:Boolean;    // has cell been visited
        private var _bCorridor:Boolean;    // is cell a corridor
        
        
        public function get aWall ():Array { return this._aWall; }
        public function set aWall (value:Array):void { this._aWall = value; }
        
        public function get iValue ():int { return this._iValue; }
        public function set iValue (value:int):void { this._iValue = value; }
        
        public function get bVisited ():Boolean { return this._bVisited; }
        public function set bVisited (value:Boolean):void { this._bVisited = value; }
        
        public function get bCorridor ():Boolean { return this._bCorridor; }
        public function set bCorridor (value:Boolean):void { this._bCorridor = value; }
        
        public function get wallCount ():int {
            
            var i:uint;
            var iCount:int = 0;
            
            for (i = 0; i < this._aWall.length; i++) {
                if (this._aWall[i] == 0) iCount++;
            }
            
            return iCount;
            
        }
        
        public function get isDeadEnd ():Boolean { return (this.wallCount == 3); }
        public function get isUnused ():Boolean { return (this.wallCount == 4); }
        
        /**
         * Creates a new empty (ie. all walls set) cells
         * @param    iValue    used to stor a single bit of info, ie. room num
         */

        public function Cell (iValue:int = 0) {
        
            this._aWall = [0, 0, 0, 0];
            this._iValue = iValue;
            this._bVisited = false;
            this._bCorridor = false;
            
        }
        
        /**
         * sets a single wall
         * @param    iDir    Direction of the wall
         * @param    iWall    value of the wall, 0 is a solid wall, any other value makes it "open"
         */

        public function setWall (iDir:int, iWall:int = 0):void {
            
            this._aWall[iDir] = iWall;
            
        }
        
        /**
         * return the value if the wall in iDIr
         * @param    iDir    direction of the wall to get
         * @return    value of the wall
         */

        public function getWall (iDir:int):uint {
            
            return this._aWall[iDir];
            
        }
        
        /**
         * shortcut for testing if there is a closed wall in iDir
         * @param    iDir    direction to test
         * @return    true if there is a wall
         */

        public function hasWall (iDir:int):Boolean {
            
            return (this._aWall[iDir] == 0);
            
        }
        
        /**
         * if the cell is a dead end, return the direction of the opening
         * @return    the direction of the opening
         */

        public function getDeadEndDir ():int {
            
            var iReturn:int = -1; // not a dead end
            
            if (isDeadEnd) {
                
                if (this._aWall[Dir.NORTH] != 0) iReturn = Dir.NORTH;
                if (this._aWall[Dir.EAST] != 0) iReturn = Dir.EAST;
                if (this._aWall[Dir.SOUTH] != 0) iReturn = Dir.SOUTH;
                if (this._aWall[Dir.WEST] != 0) iReturn = Dir.WEST;
                
            }
            
            return iReturn;
            
        }
        
        /**
         * returns a string representation of the cell
         * @return    a string for the falls of this cell
         */

        public function toString ():String {
            
            var i:uint;
            var strReturn:String = "";
            
            for (i = 0; i < this._aWall.length; i++) {
                strReturn += this._aWall[i].toString();
            }
            
            return strReturn;
            
        }
        
        
    }
    
}


Part 1 - part 2 - Storage and creation

To store the maps generated I wrote a simple Mapa datatype, it'll store a 2d array of cells along with some very basic methods to deal with the data.
The map type also stores width and height in an rectangle, to have an easy way to check if a point lies within the boundaries of the map.
Aditional methods so far:

hasCellInDir (pPos:Point, iDir:uint):Boolean
getNextPos (pPos:Point, iDir:uint):Point
getSurroundingCells (pPos:Point, bUsed:Boolean = false):Array


To create a map (let's call it dungeon for the sake of easiness) I didn't include the methods need to create a dungeon in the map class, instead I wrote a DungeonGenerator class, that returns a filled map class. This way I can mess around with the creation process without messing with the map class.

Part 1 - part 3 - Let's start with a simple maze ...

The most simple representation of a dungeon I can think of is a maze. Mazes are incredibly easy to create and they work oh so well with cells.

The walkthrough to create a maze:
1. create a map of "solid" cells
2. pick a random solid cell as starting point
3. get surrounding solid cells and pick a random one
4. knock the walls between these cells, store the "old" cell in a stack for later use
5. use the cell picked in 3 as new starting point and start over
6. if the current cell has no solid neighbours, pop one from the stack
7. repeat until there are no more solid cells


Easy, eh?

package com.gamingyourway.Dungeon {
    import de.drygoods.Random.MersenneTwister;
    import flash.geom.Point;
    
    /**
     * Dungeon generator
     * @version 2009 06 24
     * @author nGFX
     */
    public class DungeonGenerator {
        
        private var _Dungeon:Dungeon;
        private var _rnd:MersenneTwister;
        
        public function DungeonGenerator(iWidth:int, iHeight:int) {
            
            this._Dungeon = new Dungeon(iWidth, iHeight);
            
            this._rnd = MersenneTwister.getInstance();
            
        }
        
        public function createMaze (iDirChange:int = 100):Dungeon {
            
            var aCellStack:Array = new Array();
            var aCellNeighbors:Array;
            
            var iTotalCells:int = this._Dungeon.iWidth * this._Dungeon.iHeight;
            var iCellCount:int = 1;
            var iRndCell:int;
            var iRndDir:int;
            
            var pCurrentCell:Point = new Point(this._rnd.Range(0, this._Dungeon.iWidth -1), this._rnd.Range(0, this._Dungeon.iHeight -1));
            var pPopCell:Point;
            
            while (iCellCount < iTotalCells) {
                
                // get neighbor cells ...
                aCellNeighbors = this._Dungeon.getSurroundingCells(pCurrentCell);
                
                // set the cell
                if (aCellNeighbors.length != 0) {
                    
                    iRndCell = this._rnd.Range(0, (aCellNeighbors.length - 1));
                    iRndDir = Dir.getDirFromPoint(pCurrentCell, aCellNeighbors[iRndCell]);
                    
                    // remove walls
                    this._Dungeon.cell(pCurrentCell).setWall (iRndDir, 1);
                    this._Dungeon.cell(aCellNeighbors[iRndCell]).setWall(Dir.getOppositeDir(iRndDir), 1);
                    
                    // store for later use ...
                    aCellStack.push(new Point(pCurrentCell.x, pCurrentCell.y));
                    pCurrentCell = new Point(aCellNeighbors[iRndCell].x, aCellNeighbors[iRndCell].y);
                    
                    iCellCount++;
                } else {
                    pPopCell = aCellStack.pop();
                    pCurrentCell = new Point(pPopCell.x, pPopCell.y);
                }
                
            } // while
            
            
            return this._Dungeon;
            
        }
        
    }
    
}


The code of the dungeon generator ... for now only with the maze creation in it.
Note that the variable iDirChange is currently not used, but I'll go over it in the 2nd part of this article.

I think that is enough for today, you can see the result (and from the upcoming articles, too) Random Dynamic Level Creation Test page (or here).

See you next time when I add some direction modifications and take care of dead ends ...

nGFX


Wednesday, June 24, 2009 3:02:34 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [7]  |  Trackback
 Monday, March 23, 2009
Hi all.

Back on coding CE again (at least for a few hours today) after spending a good deal of time coding and designing the backend for MYW, writing a tiny versatile CMS and doing some 3d for X.

So today I'm  going to ask myself why the hell I have done a few things in CE the way I did them (I bet you are a lot wiser now).

Let's raise the question if it is worth to add the extra amount of work needed to draw a hard line between the game's "engine" and the game's UI - for your average flash game.

You may have noticed that neither Squize nor me tend to make out lives overly easy when it comes to games, for some odd reason we both tend to try to give out very best for each and every game (even if it is a pain in the back) - well call that stupid.

Give me a good reason to split game and game (play) UI ...
The best reason I can come up with is: reusability, and it is also the least used one.

In most cases I can come up with there seems to be no reason to really split things, because the game is a one-of-a-kind thing. Even for games that share a good deal of similarites (like Wintertales and LogiMotion), a whole lot of things need to be rewritten in order to reuse the engine.

That leaves the reusability inside a single game. !?

WTH?

Inside a single game? Yep!

A good deal of our games either uses an ingame editor (although not usable by the player)  or uses an external editor to generate the level data. Mostly, but not always they share the same visuals. For instance the editor for Logimotion uses smaller tiles than the game itself (so have room for the tools and don't have to scroll the map). That is a good reason to split things between the UI and the "engine".
Another good reason is when you have a stupid designer and you just code - you know those guys tend to be smart and change the size of the assets as often as you should change your underwear.

So why question that and not do it all the time?

Well, it takes a good deal more planing to really split things up. in an ideal world, the "game" knows nothing about the UI, but it still has to control it (ie. update the score, display infos). In my case this is done using callbacks.
A good example might be a game we did (but still isn't playable online): CC

cc_game_00.jpg
(CC game, using 40x40 tiles)

cc_editor_00.jpg
(CC editor, using 32x32 tiles)

As you're meant to be able to play the game inside the editor (without leaving it), the engine had to cope with different tile sizes and environments.

So whenever the game needs to comunicate with its surroundings I provide a callback method, if I would have made it "the right" way, I should have used an interface for that, but ... hell you can overdo it too.

To make it easier and not having an endless number of callback methds I used only a few and gave them params (which are stored in the game's class as constants: like:

public static const UPDATEUI_INFO:uint = 0;
public static const UPDATEUI_BTNPLAY:uint = 1;
public static const UPDATEUI_ITEM:uint = 2;
public static const UPDATEUI_WIN:uint = 3;

Whenever you finish a level, the game would just call the callback provided for UI updates and pass the parameter needed.

And is it really worth the extra work?
Not always.

I like to spend that extra piece of work for games that require and editor or might have parts in it that seem to be a good start for reusing it in a second game. Sometimes you notice halfway through the project that you need to change something to make the game work again (ie. different tile size, or different UI), sometimes (like I did for CE) you notice that the game will be a one of and you cause yourself a good deal of fuzz for "nice code" only.

Well, lets get back coding CE.

nGFX


Monday, March 23, 2009 11:18:59 AM (W. Europe Standard Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, January 08, 2009
Hi,
long time no post (but Squize has pretty much made it needless).

What happened so far ...

After a few days off from nearly everything except gaming (on my new shiny plasma) I finally got back to work on Calisto Eclipse. I decided to go the hard way and rewrite most of the menu code (before I even started coding on the game) and ended up with something I might just call the "ScreenFramework".

Basically I usually use the same style for coding:
1st there's a Singleton main controler class through which I can access all the low level stuff, because I hate having to post around references or "crosslink" classes.
This class also provides access to the ScreenFramework (name might change to something catchy), the SF class allows an easy way of switching screens using a predifined "transition".

So to get from "mcScreenMenu" (currently active) to "mcScreenMedal", it's just this (pseudo code):
SF.showScreen("mcScreenMedal", [optional transition code to use]);

... attach the new screen, perform transition, remove the old screen

All screens are "self contained" so before removing them I call the "dispose" method (did I mention that all screens use the SimpleScreen interface?), which cleans the mess the screen has caused in memory and might set a few things in the controler class.

As Calisto is also the testbed for another project we have been tinkering with for a while, it's not just developing the game but also a lot of backend server stuff, which is a pitty, because I really would like to share a good deal more of the development process for CE (although, after having the screen handling nailed I might just show that for the sake of it).

nGFX


Thursday, January 08, 2009 9:31:38 AM (W. Europe Standard Time, UTC+01:00)  #    Disclaimer  |  Comments [3]  |  Trackback
 Wednesday, December 03, 2008
December already, it really does fly.

I was chatting with our mate Jeff @ 8bitRocket the other week about function pointers / state machines and I thought it may be useful to quickly write something up about it here, as so much of my code relies on them and they do make life easier.

private var mainFunc:Function;

That's pretty much it.

I'm guessing all your routines have some sort of mainloop. Either you're old school and that's running on an enterFrame on each mc you're moving about or it's a public method in an object which you loop through and call every frame.

Let's take a space invaders mainloop as an example ( Psuedo-code ahead )

function mainloop():void{
  if(thisInvaderIsDeadFlag==true){
    return;
  }
  if(areWeDyingFlag==true){
    if(sprite.currentframe==endOfExplosionsFrame){
      thisInvaderIsDeadFlag=true;
    }
    return;
  }

  moveInvader();
  testForShootingAtPlayer();
  testForBeingHitByPlayersBullet();
}


That should hopefully be straight forward enough. In real life we wouldn't want to be testing for thisInvaderIsDeadFlag every time, we'd just remove this invader from our array of active ones, but this is just an example.

Now check this out,

mainFunc=mainloop_normal;

function mainloop():void{
    mainFunc();
}

function mainloop_normal():void{
    moveInvader();
    testForShootingAtPlayer();
    testForBeingHitByPlayersBullet();
}

Here we've set up our function pointer to point to mainloop_normal, and then we just call that one method in our mainloop() method. Cool so far ?

Now we're not checking to see if the invader is dead, or if it's exploding. That's good, because by default the vast majority of the time neither of those are going to be true, so it's a waste to check ( It's like waking up every morning and seeing if it's your birthday ).

At the moment it's more of a function pointer than a state machine as such, so next up is why doing it this way is so sexy...

function testForBeingHitByPlayersBullet():void{
    if(playerBullet.hitTest(thisInvaderSprite)){
       thisInvaderSprite.gotoAndPlay("firstExplodingFrame");
       mainFunc=mainloop_waitingToDie;
    }
}

Here's our testForBeingHitByPlayerBullet method from the main loop. Imagine that's just doing a hitTest, the invader to the player bullet. Bang, it's a collision, but instead of having to set our areWeDyingFlag from the original example, we just change the state machine.

function mainloop_waitingToDie():void{
  if(sprite.currentframe==endOfExplosionsFrame){
//Kill this invader totally, ie remove the mc
      mainFunc=null;
  }
}

In effect, no extra checks are needed every frame, we're only running what we need. We've got the advantage of a slight performance boost, but far more useful than that is the flexibility this gives us.

Say for example you want your invader to teleport in now, at the start instead of:

mainFunc=mainloop_normal;

we can now alter it to,

mainFunc=mainloop_waitingForInvaderToTeleportIn;

And run the code there waiting for your cool teleport effect to finish, and then just alter the mainFunc to carry on with our flow.

Running your code this way means you can chop and change states without having to have lots of extra conditionals in your code ( If the player is teleporting in, but that teleport tween isn't at frame 10 yet, then don't test for collisions with the player bullet etc. etc. ).

Any questions just hit that comment button,

Squize.

Wednesday, December 03, 2008 9:16:54 AM (W. Europe Standard Time, UTC+01:00)  #    Disclaimer  |  Comments [8]  |  Trackback
 Tuesday, November 04, 2008
So what's wrong with this code:

if (this._iDrawLayer & ChipsGame.VIEW_LAYER_A == ChipsGame.VIEW_LAYER_A) {
// draw contents of layer A ...
}

nothing, really. Nonetheless it's not working in CS3 (yet again, prove me wrong).

Basically it's just the unoptimized check if a certain bit is set or not. Let's do some traces:

trace ("with ():", ((this._iDrawLayer & ChipsGame.VIEW_LAYER_A) == ChipsGame.VIEW_LAYER_A))
trace ("without ():", (this._iDrawLayer & ChipsGame.VIEW_LAYER_A == ChipsGame.VIEW_LAYER_A))
trace ("values :", this._iDrawLayer & ChipsGame.VIEW_LAYER_A , ChipsGame.VIEW_LAYER_A)

The result is this:

with (): true
without (): 1
values: 1 1

Interesting, isn't it?
It seems like the compiler chains the & and the == for some reason that escapes me...

So if you get something undesiered with bitwise operators ... use ( and ) around it.

nGFX

Tuesday, November 04, 2008 3:33:54 PM (W. Europe Standard Time, UTC+01:00)  #    Disclaimer  |  Comments [3]  |  Trackback
 Thursday, October 30, 2008
Another quick break down of an effect in 651.

651_vectorBallGrab2.jpg

On paper this is quite impressive, 500 3D objects with depth of field running over a skybox. In real life, it's not exactly rocket science.

Although we used Papervision in 651, we didn't for the effect itself ( Just for the skybox, which uses the background from Orbs as it's texture ).

We start by creating the 3D objects and giving them random x,y,z coords ( For the very best introduction to 3D movement in Flash check these excellent tuts on Kirupa, everything you need for an effect like this is there ).

Right, we're spinning some objects around in 3D space, what about the depth sorting ?
This is just between us right ? It's not going any further ?
Ok then. We just don't bother. It's a very old trick ( I remember doing it back on the Amiga ). If the vector bobs are translucent enough, it's pretty hard to tell if they're not being z-sorted correctly. Looking at that grab above quickly I can't see anything too wrong in terms of sorting, and even if there was, the bobs would all have moved in the demo before you really notice it.
By dropping the sorting we've got more cpu power for more objects, and the more the merrier.

Next up, depth of field. Such a buzzy thing in Flash atm. We all know it's only a blur filter, but depth of field sounds so much cooler. To avoid an extra hit we pre-calculate each blur frame, we're not blurring in real time. Or even using the blur filter itself as it's all done in PaintShop Pro ( And each frame is imported into Flash ).
Each bob is a movieclip ( Which feels very old school already ) and when we're calculating the scale ( Based on it's z property, ie how close or far away from the camera it is ) we can also gotoAndStop() to the correct level of bluriness.

So to recap, the 3D code can be found in some great tuts, there's no z sorting, the depth of field is gotoAndStop and the skybox was done in about 10 lines of code. Drop a nice image over the top of some scanlines and it's all done.

It may seem like I'm talking the effect down quite a bit. I'm not really, it looks sweet, I just don't like people acting like their code is l33t and they're so clever for doing it. Smoke and mirrors is as good a technique as clever code, and there's not always a need to mystify everything.

Squize.

Thursday, October 30, 2008 12:42:37 PM (W. Europe Standard Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, October 29, 2008
Back again!

After a good deal of time I finally have something to post about - or let's face it moan about.

As the headling slightly might suggest I'm dealing with sound today.
I think that sound handling in AS3 is a nightmare compared to the ease of it in AS1/2 and I'm not the only one asking WTF?

So in order to play your sound you have to instanciate it, if it's exported CS3 kindly creates a class for you so you can easily use it ... (loading it from an external source is another story)

This is what the CS3 help gives us for embeded sounds ("working with embeded sounds"):

var drum:DrumSound = new DrumSound();
var channel:SoundChannel = drum.play();

My first question was: what do I need the SoundChannel for if I just want to play the sound?

Well, the rocket scientists at Adobe thought that it would be a good idea to add a play() command to the Sound, but not a stop(), so in order to stop our sound playing we *need* the SoundChannel - so we better store it for later use.

Anyway, to make my life easier I converted my SoundUtil class from AS2, basically it deals with the sounds so I don't have to think about it, it has a few usefull commands like playSFX (plays a sound effect, once), playMusic (which allows fading), crossfade ...
I usually used attached sounds (or from an external swf, but the SoundUtil dealt with it ...)
So in order to play music for the menu I'd just do:

SoundUtil.getInstance().playMusic("musicName", 2); // 2 would do a 2 sec. fade in

The AS3 version should work the same, although it uses static functions which then call the singleton's method.

Oh wait. We need to have a class to start the embeded sound ...

To get over that I wrote the add method, which basically takes the name of the sound (or the classname) and then does it's magic.

        public function add (strSound:String, bIsMusic:Boolean):void {
            
            var refClass:Class = getDefinitionByName(strSound) as Class;
            var sndTmp:Sound = new refClass();
                        
            var iTmp:int = this._aSound.length;
            
            this._aSound.push(sndTmp);
            this._objSound[strSound] = { id:iTmp, bIsMusic: bIsMusic };
            
            if (bIsMusic) {
                this._objSound[strSound].spDummy = new Sprite();
            }
            
        }

Ha! that was easy ...

As you see the sounds name gets stored in an object (I just use it as dictionairy), I store an Object with some more values along with the name. And you surely might ask WHY on earth I did create a Sprite for music files ...
Well I'm a lamer, I use the Sprite to attach an onEnterFrameTo it for things like fading :)

Fast forward ...

k. Let's say we play some music, and only wont it to play 2 times, after that the sound should be removed from memory. Luckily we have the onSoundComplete Event, it should return (CS3 help): "The Sound object on which a sound has finished playing."

For me it reads like it returns the Sound that is playing. FAIL!

It does however return a SoundChannel, which of course HAS no information (prove me wrong) about the Sound it belongs to ...
So how can I unload/cleanup a Sound when an onSoundComplete occurs, if I don't know which Sound is playing (and don't want to write a seperate Listner for each sound)?

Oh lucky me...

Thank fuck I store a lot of things in my information object (not only what is shown in the add method), for instance I store the SoundChannel I got from Sound's play() command and I store if a Sound is playing ...

After a few hours of using our favorite search engine I came up with something so stupid it might even be brilliant ...

private function onSoundComplete (e:Event):void {
            
      var strKey:String;
            
      for (strKey in this._objSound) {
           if (this._objSound[strKey].bIsMusic) {
               if (this._objSound[strKey].chChannel == e.target) {
                   this._objSound[strKey].chChannel.removeEventListener(Event.SOUND_COMPLETE, this.onSoundComplete);
                   this._objSound[strKey].bIsPlaying = false;
                   // do some cleanup
               }
           }
       }
            
}

Basically I loop over all music "files" that are playing and *compare* their SoundChannel with the one returned by the Event.
That's so insanely stupid! But it works. Sweet.

Maybe it helps some of you ...

nGFX

Wednesday, October 29, 2008 5:46:21 PM (W. Europe Standard Time, UTC+01:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Sunday, September 07, 2008

I've been silent the last couple of weeks, too silent I reckon, but ... yet again I've been oompha-loompha'ing for various projects.

One of this projects really raised the bar in several ways (I think I'll do a rundown afterward - deadline is tomorrow, really, no way of extending it, even for a few minutes).

So for now let us assume we're dealing with a purely hypothetic project (as this is not about what all went wrong during the assignment).

This fantasy project should contain this:
- a menu, 3 languages, able to load in various games
- multi language support for the games (which have been done by someone else)

As I'm used to work with Flash's strings panel it was a logical choice to use, basically you can set it to "auto" mode and all assigned Textfields will be replaced at runtime with the associated one, you can also set a new language while running and everything is fine.

Or so I thought.

The menu worked fine, but then I got the first game ... oh, now we Imagine the first game ...
Some files ...
- one 150MB fla, called "library" - LIBRARY? yes.
- one 90MB called "game" ...
- a few more files (levels), each around 90MB

OK, library mustn't mean something bad.

Or so I thought.

Library in this case means something bad. BAD. BAD. BAD.
It meant: shared library (for those of you lucky enough to have not heard about it yet ... read about here.)
Oh when you're back from reading about it, you surely think "oh, that is nice"

WAIT! Don't ever even think about thinking "oh, that is nice" when it comes to shared libraries. They are EVIL!
It's the foot and mouth disease of Flash's features.

(imagine using one)
You'll first notice that you lose control about preloading - shared libs work like things exported to the first frame BUT without being able to detect their size.

Fast forward now ...

So you have the menu using Flash's strings panel, working, now it's time to load in the first game ...
I chose to use the good old and trusty loadMovie method instead of the MovieclipLoader class. So it was just a simple load and detect the size, start when done.

OR SO I THOUGHT.

Well, I knew from other project that when loading an swf that uses Locale, the newly loaded swf (using Locale, too) replaces the language data from the holder swf. This is OK and it makes sense if you think about the fact that a static class is assigned to _global in AS2. So the game used the same language files the menu does ... easy.

The dirty details ...

The game loads in a couple of external mp3 files, so I had to take care of that, too - easy enough, so no problem there too..
This is what we see:
- loading starts, use getBytesLoaded / getBytesTotal to see when it's done ...
- tracing shows 140k of swf to load, fair enough.
- after loading that, nothing happened. For 900k nothing happened - Flash was loading in the shared lib.
- then I was able to watch the mp3 files loading and afterward start the game - finally.

The problem is that the user might think that the game isn't loading at all when the shared lib is loaded - THERE IS NO WAY OF WATCHING IT LOADING!!!!!!
First I thought I could trick my way out of it by loading the shared lib FIRST and then hope the cache would kick in when the game is later loaded - nice try buddy. No, it doesn't work (at least not very well).

I ended up by using a faked (and then real) file list to load the game, basically it shows that there are 2 files to load.
First the swf is loaded, then while waiting for it to load the shared lib, I fake a progress and later I add the mp3 files to that list also to watch them loading, too.

Now that this is off the list ...

Yehaa the game is loaded and ready to to be played ...
- level one works ...
- the "level done" screen pops up AND ... it is definitely NOT in the language I've been using in the menu. WTF?

In fact the game was now all in German, whereas I selected English for the menu.

WHY? What happened. I had no idea.

A lengthy search using your fav. search engine did not give us anything.

OK, so we're on our own again (hey, after all this is Flash).

Let the tracing begin ...

First thing I checked was the language set using Locale.getDefault():
- startup: "de" (because of my desktop language)
- set it to "en"
- (checking, yes now everything is "en")
- load game: still "en"
- play game: still "en"
- show level done: still ... "en"

BUT IT FUCKING SHOWS ALL FUCKING STRINGS IN FUCKING GERMAN!

More fast forward ...

A few hours later I have lost a good deal of hair, brain and energy and of course all the trouble is caused by ...  right . the shared library.
It seems like it overwrites the language data with it's own values but doesn't report it to Locale (so you can see it).

And they lived happily ever after ...

I ended up, oh, we "would" (we still imagine the project you know) with loading the game, loading all external files and THEN reload the language file.

nGFX

ps: next time I tell you about the joys of using art based levels that consist of over 900 vector based drawings, movieclips and components ... which take almost 10 minutes to copy and move ... joy.

Sunday, September 07, 2008 2:25:04 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Thursday, August 28, 2008
Collisions, they're always a part of my games that I'm never happy with. Not so much the actual this sprite has hit that sprite part ( The narrowphase of the check ), but the broadphase, ie deciding which checks are needed and which we can just ignore.

Different genres require different ways to test for collisions. For a long time now I've been using grid based checks ( As far back as this old beauty ) in arena based shoot'em ups. Simple enough, you split the screen up into overlapping sectors, and store each baddie in the sector it's occupying.
So let's say we've split the screen up into quarters, you check each baddie's position, and store it in one of the four arrays you've set aside for each sector. Then you can run through your bullets and see which sector they're in, so in theory you're only testing the baddies which are nearest to the bullet ( There's no point testing a bullet against a baddie which is on the other side of the screen ) which in an ideal world will reduce the checks by 75%. Not bad.

The problem I've always had with this is that it feels costly to maintain. I've always just cleared the sector arrays at the start of the baddie movement routines, I've never been clever enough to come up with a way to maintain it "properly". Therefore I could have baddies that have only moved a pixel or two since the last frame, there's no way they're going to have changed sectors, but I've had to treat them afresh.
That can't be good, but like I've said, I've never been able to come up with a clever way of negating that, so I've always just done it that big dumb way.

Recently quadtrees ( Check here for a great example, and an overview by the always excellent 8bitrocket can be found here ) and octtrees are very in vogue with Flash developers, so being a bandwagon jumper I thought I'd have a bit of that.

Again, I couldn't think of really good way to maintain the structure every frame, and it felt like you'd need a lot of objects to make it worthwhile ( Or just use it as a generic collision system for every game, but I'm not a fan of that. Collisions are a weird beast where very rarely does one hat fit all ).

One aspect that all the collision methods I mentioned above have, is shown below,

dbB1.png

I'm going to generalise a bit here, but let's say we've drilled down into the correct sector / node / whatever. Our bullet is travelling along that path ( Pick which ever direction you feel more comfortable with, in my head it's going up and right ). Chances are it's never going to hit that baddie ( I know the baddie could in theory move enough to come into collision with it, but we're generalising for a second ).
So we've gone to quite a bit of effort to narrow down our collision checks, and then we're still running a check per bullet every frame when most of the time it's not going to hit ( Think of your accuracy rating at any game that checks such things. 75% is pretty good in a game. That means that 25% of all the bullets are going to miss, yet we're having to test 100% of the bullets a 100% of the time ).

This all felt a bit sucky in my head. A lot of cpu time spent on something that wouldn't happen.

Let's talks about "Distance Based Broadphase". I made that up, it's more than likely already been around for years with a different name and I've just happened across a similar idea, but it explains what it is pretty well.

I've approached it in a different way than how I normally set up the whole bullets / baddies stuff. Using DBB every baddie has an array of all the player bullets ( Well a linked list for speed ), and every time the player shoots that new bullet is shoved into that array.
During the baddies main loop it runs through all the bullets it has in it's array and checks it's distance to it ( The narrowphase checks are just your bog standard circle to circle collisions ). If it's distance has increased, then the bullet is moving away from the baddie, and it won't hit it.

dbB2.png

So looking at that diagram above, lets say the bullet is flying up to the top left. The broadphase will keep checking as the distance from the bullet to the baddie is decreasing every frame, ie it's getting closer. It's possible that it could hit it, so it's worth checking.
Once the bullet goes past the sweet spot, it's moving away from the baddie. It'll never ever hit it, so we just remove it from the array and the baddie won't check for it again.

Whilst there's a possability of a collision it's worth checking, so it's not so costly ( If you're shooting at baddies from a distance then it's going to incur a cost until the bullet goes past the sweet spot, ie 'til the bullet gets to a point where it's not going to hit the baddie. The greater the distance the more the tests as it will take a while to actually get to the sweet spot ).
Going back to the first diagram, the bullet ( If moving to the top right ) is moving away from the baddie right from the start, so it's thrown away.
In effect we're checking the general direction until we get to the point of a hit, or a miss.

Now I've done some generalisation here. In real life your baddie will be flying around throwing some great shapes. For that, you just increase the size of the sweet spot to take it into account. If you have a fixed speed for a baddie you could work out exactly the sweet spot's size ( That is you'd work out if the baddie moving at it's max speed in a straight line to the bullets path how big the area to check would be ), or if you're lazy like me you just increase the size of the sweet spot by subtracting some pixels from the distance and testing it til it stops breaking.

Hopefully I've made some sense, it's proved to be quite a fair bit to explain. As always please feel free to post a comment if you have any questions or if I've got anything wrong. I'm sure I'll be editing this soon enough to clear things up.

Squize.

Thursday, August 28, 2008 2:44:27 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [7]  |  Trackback
 Wednesday, August 27, 2008
In the recent theme of speed testing I thought I'd better double check my atan2 results, and comparing the below method to the built in one I got the following:

runAtan2():  2161
runAtan2Quicker():  1159

( For more about how I test this stuff, please check this recent post )

Phew, something actually working quickly as it's meant to.

The following method is based on code by Jim Shima, http://dspguru.com/comp.dsp/tricks/alg/fxdatan2.htm

private static var coeff_1:Number = Math.PI / 4;
private static var coeff_2:Number;
private static var r:Number;
private static var angle:Number;
private static var absY:Number;

//--------------------------------------------------------------------------
public static function atan2(y:Number,x:Number):Number{
    coeff_2 = 3 * coeff_1;
    absY=y;
    if (absY < 0) absY = -absY;                    //Math.abs
    if(x>=0){
        r= (x - absY) / (x + absY);
        angle = coeff_1 - coeff_1 * r;
    } else {
        r = (x + absY) / (absY - x);
        angle = coeff_2 - coeff_1 * r;
    }
    return y < 0 ? -angle : angle;
}

I've used static properties / method 'cause it's part of my MathX class ( So if you're going to set up a similar way, create a class called MathX and then the usage would be,

var value:Number=MathX.atan2(y,x);

Exactly the same as the built in method ).

Squize.

Wednesday, August 27, 2008 5:05:00 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [4]  |  Trackback
 Saturday, August 23, 2008
I'm becoming a bit of a speed testing slut with as3, and seeing how this function is used in so many games, I thought I'd have a play and see which actually is the quickest way to do this.

Point.distance() seems to have everything going for it, but does it ?

import flash.utils.getTimer;
import flash.geom.Point;

var time:Number;

var point1:Point=new Point(100,100);
var point2:Point=new Point(200,200);

var dist:Number;
var dx:Number;
var dy:Number;

function runPythagorean():void{
    time = getTimer();
    for (var i:int = 0; i < 10000000; i++) {
         dx = point2.x-point1.x;
         dy = point2.y-point1.y;
         dist = Math.sqrt(dx*dx + dy*dy);
    }
    trace("runPythagorean(): ", (getTimer()-time));
}

function runDistance():void{
    time = getTimer();
    for (var i:int = 0; i < 10000000; i++) {
         dist = Point.distance(point1,point2);
    }
    trace("runDistance(): ", (getTimer()-time));
}

runPythagorean();
runDistance();

Slap that into Flash, and be surprised at the huge difference between the two ( For those of you who prefer just to look rather than get involved, on my machine I got the following:

runPythagorean():  1452
runDistance():  10485

Not really neck and neck ).

So the distance method is a none starter ( What the hell does Flash do behind the scenes ? It's only returning a Number, it shouldn't need to be doing anything other that the code in the runPythagorean() method ).

Don't know if I've posted about it here before either, but avoid Rectangle.intersects() for the same reasons too.

Whilst I was testing I thought I'd do the old shortcut of:

var sq:Function=Math.sqrt;
dist = sq(dx*dx + dy*dy);

Again, another surprise,

runPythagorean():  1464
runPythagoreanWithShortCut():  2824

Finally, I finished off with some "quicker" versions of sqrt, ie

//---------------------------------------------------------------------------------------
function sqrt(w:Number):Number{
// Fast Math sqrt function from
// http://osflash.org/as3_speed_optimizations#as3_speed_tests
    var thresh    : Number = 0.002;
    var b:Number = w * 0.25
    var a:Number;
    var c:Number;
    
    if (w == 0) return 0;
    
    do {
        c = w / b;
        b = (b + c) * 0.5;
        a = b - c;
        if (a < 0) a = -a;
    }
    while (a > thresh);
    
    return b;
}
        
//---------------------------------------------------------------------------------------
function fsqrt(w:Number):Number{
// SUPA fast but not very accurate sqrt
// Fast Math sqrt function from
// http://osflash.org/as3_speed_optimizations#as3_speed_tests
    var thresh    : Number = 2;            //1
    var b:Number = w * 0.25
    var a:Number;
    var c:Number;
    
    do {
        c = w / b;
        b = (b + c) * 0.5;
        a = b - c;
        if (a < 0) a = -a;
    }
    while (a > thresh);
    
    return b;
}


I've got both of these in my MathX class, which is a collection of quicker alternatives to the built in methods and various other little math based utils.
This test made me think I was going a bit mental,

runPythagorean():  1452
runQuickSqrt():  3514
runQuickFSqrt():  3237

Now I've not done these tests over and over and averaged them out, 'cause to be honest the results really aren't close enough to warrant it. Please feel free to try these out yourselves and post the results back here. Maybe those last two methods should be inlined rather than wrapped in functions, but I've really not got the inclination to try it out.

Squize.

Saturday, August 23, 2008 9:54:47 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [4]  |  Trackback
 Wednesday, July 02, 2008
Sorry folks yet again no image ... but some code :)

The current game (let's call it CC for the sake of it) is getting close to the point where I would declare the main game engine done, most of the events are processed now and the final enemies are going to be done today (hurray!).

As the title suggests (wow it's something post related) I want to write about the dumb ass AI one, nope rather 4 of the enemies in CC use, if you were so bold to call it AI.

While reading up the docs about the original game I read this:

"goes around objects to the left"

And there are 3 more which go around things to the right.

HA!

I first simply ignored the fact of "going around" and just coded a simple "if enemie hits wall turn left".
cc_ai_00.gif
(ok, I lied, there are some images)

So after noticing my mistake I removed the old code and started to write the one that should allow my enemy to move around things. Sounds easy enough.
cc_ai_01.gif
Oh, that's easy.

"Go ahead as long as there is something to the left, if not, turn left ..."

After a while I really lost my temper and just coded something that could deal with right turns as well, by checking 3 tiles + one, as you can see in the next image.
cc_ai_02.gif
Well that is stupid, isn't it?

Jein, it's a dummy approach. (jein is a pseudo German word, combining "ja" and "nein", yes and no).

cc_ai_03.gif
Some of the common situations, the green arrow shows the next direction
and in "D" shows the use of the 4th check.


In order to simplify (though, yet unoptimised) the checking of the tiles I set up an array of points, holding the offset for each of the checks. To make my life even easier I just numbered the directions (which is used all over the game):
0 = north, 1 = east ...

So the array for 0 (north) looks like this:

this._aTest.push( [new Point(0, -1), new Point( -1, -1), new Point( -1, 0), new Point(1, 0)] );

And because this one should move to the left I added a second array that holds the next direction to go to:

this._aDirNext = [3, 0, 1, 2,  1, 2, 3, 0];

(You might wonder why it has eight entries instead of the needed four, I'll come to that later)
I now could just lookup the next direction I need to face by simply checking with the current direction:

this._iDir = this._aDirNext[this._iDir];

Now, with that in place checking the movement was easy:

private function checkMoveBug ():void {
            
    var strTest:String = "";
    var i:uint;
    var xx:int;
    var yy:int;
    
    for (i = 0; i < 4; i++) {
        
        xx = this._pPos.x + this._aTest[this._iDir][i].x;
        yy = this._pPos.y + this._aTest[this._iDir][i].y;
        
        if (this._refChipsGame.getTile(this._refChipsGame.aMapGame[xx][yy][0]).objProperties.bMonster && this._refChipsGame.aMapGame[xx][yy][1] == -1) {
            strTest += "0";
        } else {
            strTest += "1";
        }
        
    }
            
    switch (strTest.substr(0, 3)) {
        case "000":
// "C"
        case "100":
        case "110":
            this._iDir = this._aDirNext[this._iDir]; // turn to the next dir
            break;
        case "111":
// "A"
        case "101":
            if (strTest.charAt(3) == "0") {
                this._iDir = this._aDirNext[this._iDir + 4];
// this one uses the second pair for "A"
            } else {
                this._iDir = this.getOppositeDir(this._iDir); // this one is used vor "D"
            }
            break
        /*
        case "011":
        case "001": // "B"
            these are not needed because we can just move ahead
            (there is something to the left)
            break;
        */

    }
    
}

The final version has the 4th check removed from the loop and just checks it for "111" and "101".

And because we use an array to store the test offsets, we can make the enemy around things to the right by just changing the values (north):

this._aTest.push( [new Point(0, -1), new Point( 1, -1), new Point( 1, 0), new Point(-1, 0)] );

and changing the aDirNext array to face right:

this._aDirNext = [1, 2, 3, 0, 3, 0, 1, 2];

Vioal. Done.

I hope this is quite understandable (the code is, my writing might not)

nGFX




Wednesday, July 02, 2008 8:45:01 AM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Sunday, June 29, 2008
Preloading with Flex for actionscript projects still seems to be really under-documented. Personally I've found it to be a bit of a joke that you've got to search half a dozen sites to find out how it's done, I mean it's preloading, it's what Flash does.

So I thought I'd add "my" approach here. It's what I'm using and seems to work well, it's been cobbled together by reading through the half a dozen websites, so I'm not claiming it's all my code or my idea, it's other peoples code who are clever than me shoved together.

Let's start at the begining,

package {
    import flash.display.DisplayObject;
    import flash.display.Loader;
    import flash.display.LoaderInfo;
    import flash.display.MovieClip;
    import flash.display.StageQuality;
    import flash.display.StageScaleMode;
    import flash.events.Event;
    import flash.utils.getDefinitionByName;

    [SWF(width="400", height="600", frameRate="40", backgroundColor="#FFFFFF")]

    public class Preloader extends MovieClip{
//---------------------------------------------------------------------------------------
// Properties
//---------------------------------------------------------------------------------------
        private var logoClass:Class;
        private var logoClassInstance:Object;
        
//---------------------------------------------------------------------------------------
// Constructor
//---------------------------------------------------------------------------------------
        public function Preloader() {
            stop();
            stage.showDefaultContextMenu=false;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.quality=StageQuality.LOW;

            addEventListener(Event.ENTER_FRAME,mainloop);
        }

//---------------------------------------------------------------------------------------
// Private
//---------------------------------------------------------------------------------------
        private function mainloop(e:Event):void{
            if(framesLoaded >= 2){
                nextFrame();
                triggerLogo();
                removeEventListener(Event.ENTER_FRAME,mainloop);
                addEventListener(Event.ENTER_FRAME,mainloop2);
            }            
        }

//---------------------------------------------------------------------------------------
        private function mainloop2(e:Event):void{
            if(framesLoaded == totalFrames){
//It's all loaded, has the logo finished ?
                if(logoClassInstance.animCompletedFlag==true){
                    removeEventListener(Event.ENTER_FRAME,mainloop2);
                    nextFrame();
                    triggerGame();
                }
            }            
        }

//---------------------------------------------------------------------------------------
private function triggerLogo():void{
            logoClass = getDefinitionByName("PreloaderLogo") as Class;
    if(logoClass) {
        logoClassInstance = new logoClass();
        addChild(logoClassInstance as DisplayObject);
    }
}

//---------------------------------------------------------------------------------------
private function triggerGame():void{
            var main:Class = getDefinitionByName("Main") as Class;
    if(main) {
        var app:Object = new main();
        addChild(app as DisplayObject);
             app.waiting();                        //Call the singleton to kick it all off
                logoClassInstance.dispose();
    }
}

//---------------------------------------------------------------------------------------
    }
}

Just to run through this nice and quickly, the preloader extends the MovieClip class as we're using 3 frames for the game ( The actual preloader class, the PreloaderLogo class and the Main one ( Which is the game itself )).
In the constructor we just do all the stage stuff that we want to do ( Hide that menu ), and then run an eventListener ( Mainloop ) which checks to see how much of the overall game has loaded. If frame 2 has loaded it means how logo is loaded, so we can fire that off ( See the trigger logo method, and below is the PreloaderLogo class )

package {  
    import flash.display.MovieClip;
    import flash.display.Sprite;
    import flash.events.Event;
    
    public class PreloaderLogo extends Sprite {
//---------------------------------------------------------------------------------------
// Assets
//---------------------------------------------------------------------------------------
        [Embed("/_assets/assets.swf",symbol="gywLogoMC")]
        private var gywLogoMC:Class;

//---------------------------------------------------------------------------------------
// Properties
//---------------------------------------------------------------------------------------
        private var gywLogo:MovieClip;
        public var animCompletedFlag:Boolean=false;
        
//---------------------------------------------------------------------------------------
// Constructor
//---------------------------------------------------------------------------------------
        public function PreloaderLogo(){
            gywLogo=new gywLogoMC();
            waiting();
        }

//---------------------------------------------------------------------------------------
        public function waiting():void{
            addEventListener(Event.ADDED_TO_STAGE,logoAddedToStage);
        }

//---------------------------------------------------------------------------------------
        public function dispose():void{
            stage.removeChild(gywLogo);
        }

//---------------------------------------------------------------------------------------
// Private
//---------------------------------------------------------------------------------------
        private function logoAddedToStage(e:Event):void{
            stage.addChild(gywLogo);
            gywLogo.gotoAndPlay(1);
            gywLogo.addEventListener(Event.ENTER_FRAME,waitingToEnd);
        }

//---------------------------------------------------------------------------------------
        private function waitingToEnd(e:Event):void{
            if(gywLogo.currentFrame==gywLogo.totalFrames){
                gywLogo.gotoAndStop(gywLogo.totalFrames);
                gywLogo.removeEventListener(Event.ENTER_FRAME,waitingToEnd);
                animCompletedFlag=true;
            }
        }


//---------------------------------------------------------------------------------------
    }
}

All that's happening there is the class embeds our logo and plays the animation. The waiting() method checks to see if this class has been added to the display list, if we don't wait then you open up a world of pain where the class can't find the stage.
The waitingToEnd method is as simple as it gets, once the animation has finished it just sets the animCompletedFlag to true.

Going back to the preloader class, after the PreloaderLogo class has been triggered we're running mainLoop2. That's just a check to see if the whole game ( ie all 3 frames ) has loaded. If it has when then check for the animCompletedFlag to be true. If it isn't it means the preloader logo is still running, if it is true, then we're done. The game has loaded and our sexy intro anim is done. From there we do exactly what we did before and trigger our Main class ( The game itself ).

The last part of this is the setting in Flex itself. Right click your project and select properties. From there go to the "ActionScript Compiler" options and pass the following arguments to the compiler
flex.png
(click image to enlarge)

And finally after jumping through an insane amount of hoops you should have a working preloader. The logo class can be whatever you want, and there can be more than 3 frames, if for example you want a loader bar to be displayed quickly and then bring in your logos.

*Update - I've posted the source to Operation Cortex, which includes the preloader code which should make life easier*

Squize.

Sunday, June 29, 2008 3:30:55 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, November 16, 2007

I was just tinkering and wondered what would be quicker, using clone() on a Rectangle or just copying each property by hand.

Totally blown away by the difference,


import flash.utils.getTimer;
import flash.geom.Rectangle;

var time:Number = getTimer();

function runClone():void{    
    var rect:Rectangle=new Rectangle(0,0,100,100);
    var destRect:Rectangle=rect.clone();
    time = getTimer();
    for (var i:int = 0; i < 10000000; i++) {
        destRect=rect.clone();
    }
    trace("runClone: ", (getTimer()-time));
}

function runCopy():void{    
    var rect:Rectangle=new Rectangle(0,0,100,100);
    var destRect:Rectangle=rect.clone();
    time = getTimer();
    for (var i:int = 0; i < 10000000; i++) {
        destRect.x=rect.x;
        destRect.y=rect.y;
        destRect.width=rect.width;
        destRect.height=rect.height;
    }
    trace("runCopy: ", (getTimer()-time));
}

runClone();
runCopy();

And here's my results ( Please feel free to post your own, or to point out anything dumb I may have done to affect the outcome )

runClone:  11567
runCopy:  65

Squize.



Friday, November 16, 2007 1:32:43 AM (W. Europe Standard Time, UTC+01:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Tuesday, October 30, 2007
In a recent comment posted here I was asked about the slopes in the current platformer, and I promised I'd go over it, so here it is ( Albiet slightly rushed and with no nice diagrams, deadlines and all that ).

In a tile based engine it's fairly simple to work out where the position of the sprite is in relation to the tile he's standing on. So as you move over a tile you're updating the x position which is obvious enough.
Also usually with map data you have an attribute value / byte for each tile, again if you've worked with tiles you're with me so far.

To simulate a slope easily you have an array of y positions for the sprite. In my case I have 8 values for every sloping tile, as the player moves at 4 pixels per frame, and the tiles are 32px wide, so it's 32/4 = 8 possible positions for every tile on the horizontal.

Let's say our slope looks like this ( In array form )

[0,2,4,6,8,12,14,16];

If we're at position 0 on the x of the tile, ie the left hand side, we read the first value in the slope array, in this case 0, and we decrease the sprites._y by that amount ( Decrease 'cause we're moving him up ), so the next step will make the sprite 2 pixels higher up the screen than he was last move and so on.

Above I mentioned using attributes, this is how I get an offset into the array of slopes ( They're just stored as a 2 dimensional array ). If you didn't use an attribute in the map data, just used the tile number, then you'd need to have an array for every single tile ( Where the vast majority would be flat ), which is a hell of a lot of work for no reason, and a waste of resources.

So again we're stepping on our tile, and it's attribute is 1. We know to look into the slope array at attribute-1 ( Arrays start at 0 ), which gives us our first saved slope value, and from there we can get the correct y position for each step of the way.

Simple as you like, hardly any real cpu overhead, and so far with what I've been working on it seems to cope with any angle I've thrown at it. If you wanted to add more realistic movement ( So the sprite stuggles up a hill, sprints down it ) you could store another array with the players max moving speed for each position and add that to the overal speed.

Squize.

Tuesday, October 30, 2007 6:23:32 PM (W. Europe Standard Time, UTC+01:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Thursday, July 26, 2007
How pure is pure oop, how usefull are design patterns and how to abuse asBroadcast?

Well, for the current game I'm bound to f8/AS2 because the client's audience is still mostly on f8 and the attempts to make them update the player greatly failed and resulted in a lot of "what is an update of the flash player", "what is a flash player" and "I don't know how" emails ...

The game currently uses my usual structure, that is a single class for the game and some classes for UI, sprites, tiles ... I usually made the game class "LMGame" (LM for the game's name) a singleton, but I wondered if that would be a good practice after all.
(You might guess it, this is a more or less philosophic question.)

I wanted to reduce refrences and singletons to a minimum. (Squize wrote about the problems earlier)

So far I came up with few different methods for accessing the LMGame class from various other classes during the game ...
If needed I pas a reference to the LMGame / needed class but you must admit that this feels rather clumsy.
For the Sprites however I didn't want pass a reference to the LMGame class just to say "Hello" or "I'm dead" ... so I tried a rather unusual way for sprite -> game communication:

While the game has a list of sprites and could call functions directly, I decided to rely heavily on asBroadcast.
I installed a stack (FIFO), to pass values if needed and tried if things were working like I expected ... and they did ... :)

A sprite is moved with this._asBroadcast.broadcastMessage("onTickEvent"); (from the LMGame class' onEnterFrame) and if a sprite dies it places it's id into the stack and calls this._asBroadcast.broadcastMessage("onSpriteDie"); (which is triggered by the LMGame class).

This might seem overly complicated but it really "looks" wonderfull in code and it saves a great deal of thinking, too.

As this is just a "try", I'm not sure if it's going to become very usefull, but I just like trying new "ideas" ...

nGFX
Thursday, July 26, 2007 2:09:01 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Monday, May 07, 2007
As promised way to long ago (here), I finally manged to get the brush example done.
Not the best way, not the best code, but an example nonetheless.

(just press the left mousebutton and move the mouse ...)

And the zipped fla (F8): drawexample.zip (6,11 KB)

And for those hwo don't want to d/load the fla ... just the code. You just need this two images set for export in the first frame:
(Brush, with alpha)
(color / pattern)

import flash.display.BitmapData;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.ColorTransform;

var bmpColor:BitmapData = BitmapData.loadBitmap("color_00.png");                // the color to paint with
var bmpWall:BitmapData = new BitmapData(480, 200, false, 0xc0c0c0);                // a bmp to paint in

var bMouseDown:Boolean = false;
var iPaint:Number = 0;    // "color on brush"

this.onEnterFrame = function ():Void {
    
    var bmpAlpha:BitmapData = BitmapData.loadBitmap("brush_shape_and_alpha.png");     // the "brush"
    
    var iPosX:Number = this._xmouse - (bmpAlpha.width * 0.5);
    var iPosY:Number = this._ymouse - (bmpAlpha.height * 0.5);

    if (bMouseDown) {
        
        // modify alpha ...
        bmpAlpha.colorTransform(bmpAlpha.rectangle, new ColorTransform(1, 1, 1, 1, 0, 0, 0, iPaint));
            
        bmpWall.copyPixels(bmpColor, bmpAlpha.rectangle, new Point(iPosX, iPosY), bmpAlpha, new Point(0,0), true);
        this.attachBitmap(bmpWall, 0); // show ... (not the best way, imho ...)
        
        // Disolve paint ...
        iPaint -= 5;
        if (iPaint <= -250) {
            iPaint = 0; // just resti t
        }
    }
}

this.onMouseDown = function ():Void {
    bMouseDown = true;
}

this.onMouseUp = function ():Void {
    bMouseDown = false;
}




nGFX
Monday, May 07, 2007 10:26:39 AM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Friday, April 27, 2007
Ha! The title says it all, yet again a text only post ...

While working on the current game (the same project Squize is working on, see his last post), I needed way to be able to paint an area in a real life fashion (that is, the paint on the brush runs out just a few centimeters before the end of the wall).

I must admit, that I didn't have much play with f8's BitmapData before, I just don't needed it. Until now.

I just hate it to get into something "new" in the middle of a project, but unlike some of Flash's other new things BitmapData is fairly easy to get into. Except the part where you start to mess with matrixes. I had some great examples which showed a lot of the stuff I thought I need, but I just wasn't in the mood to dig into using matrixes for creating the desired effect.

But there is hope ...
Using 3 BitmapData objects, 2 PNGs and copyPixels with alpha ...

Part of the code:

var bmpAlpha:BitmapData = BitmapData.loadBitmap("brush_shape_and_alpha.png");
var bmpBrush:BitmapData = BitmapData.loadBitmap("paintpattern.png");
        
// mod alpha
// iPaintAlpha is a value from 0 (paint it) to -255 (paint nothing)
bmpAlpha.colorTransform(bmpAlpha.rectangle, new ColorTransform(1, 1, 1, 1, 0, 0, 0, this._iPaintAlpha));
        
this._bmpPaintArea.copyPixels(bmpBrush, bmpAlpha.rectangle,
    new
Point(this._xmouse, this._ymouse), bmpAlpha, new Point(0,0), true);
      
this._iPaintAlpha--;
      


Et viola ...

Instead of using some matrix stuff, I just "alpha" the alpha chanel mask with a ColorTransform and use this to copy the "paintpattern" into the painting area ...

I'll upload an example file once I've reached my current milestone ...

nGFX

Friday, April 27, 2007 12:25:28 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [0]  |  Trackback