Thursday, November 06, 2008
Quite a descriptive title for this post for a change.
651_jellyVectors.jpg

In this section of 651 we're running two effects, a bog standard RGB plasma effect and then a "Jelly vector" effect.

To create a plasma you're going to have to suck up to Math.sin, he's your daddy for this.

Firstly we pre-calc a colour table, eg.

        public function ColourTable(){
            colourTable=new Array();
            var cnt:Number=-1;
            var col:Number;
            var r:int;
            var g:int;
            var b:int;
            var offset:Number=3.1415;

//To avoid /4 for each pixel every frame, we just make the colour table 4 times as big
            while(++cnt!=256*4){
                r = 128 + 128 * Math.sin(offset * cnt / 32);
                g = 128 + 128 * Math.sin(offset * cnt / 64);
                b = 128 + 128 * Math.sin(offset * cnt / 128);
                col=(r << 16)+(g << 8)+b;
                colourTable.push(col);
                colourTable.push(col);
                colourTable.push(col);
                colourTable.push(col);
            }
        }

Here we're just creating what is in effect a gradient, so we have an array which smoothly goes from one colour to the last one. When it comes to the look-up when we're plotting we'd need to divide the value by 4, so to avoid this we make the colour table 4 times larger than is really needed ( Often it's a balance between memory usage vs speed. An easy way to think of it is with loops. If your game didn't have any loops and you just copy / pasted the same thing over and over it would run quicker, but take a lot more memory, and be pretty insane ).

Right the colour table is done, next up we create instances of our Pixel class,

            activePixelsStorage=new Array();
            var pixelObj:Pixels;
            var j:int=-1;
            var k:int;
            while(++j!=120){
                k=-1;
                while(++k!=120){
                    pixelObj=new Pixels(new Point(j,k),colourTable);
                    activePixelsStorage.push(pixelObj);
                }
            }    

It's just like doing a tile based engine, each instance of our Pixel class is passed an x/y position so our plasma is 120 pixels wide by 120 high. That's pretty tiny so we double the scale of the sprite in which we're plotting and add a blur filter just to smooth it out. It's a lot less expensive than plotting a 240x240 plasma.

On to the actual Pixel class:

    public class Pixels{
//---------------------------------------------------------------------------------------
// Properties
//---------------------------------------------------------------------------------------
        private var xPos:int;
        private var yPos:int;

        private var cX:Number;
        private var cY:Number;
        
        private var jointDist:Number;
        
        private var offset:int;

        private var cT:Array;
        
//---------------------------------------------------------------------------------------
//Constructor
//---------------------------------------------------------------------------------------
        public function Pixels(pos:Point,colourTableArg:Array):void{
            cX=xPos=pos.x;
            cY=yPos=pos.y;
            var xDist:int=120-cX;            //Distance from the bottom
            var yDist:int=120-cY;

            cT=colourTableArg;
            
            var distance:Number=Math.round((Math.sqrt((xDist*xDist)+(yDist*yDist))/2));
            var distX:Number=256 * Math.sin(distance/8);
            var distY:Number=256 * Math.cos(distance/8);

            jointDist=distX+distY;
       }
        
//---------------------------------------------------------------------------------------
//Public
//---------------------------------------------------------------------------------------
        public function toString():String {
            return "Pixels";
        }        

//---------------------------------------------------------------------------------------
        public function pixelmainloop(x:Number,y:Number,plotbm:BitmapData):void{
            offset = (Math.cos((xPos+x)*0.0525) + Math.sin((yPos+y)*0.0255))*256 + jointDist;

            if(offset<0){
                offset=(offset ^ -1) + 1;
            }
            
            plotbm.setPixel(xPos,yPos,cT[offset]);
        }
    }

I'm not going to go into too much detail with this, as it'll take ages to be honest. The most interesting part is the pixelmainloop, where we pass in the x/y ( As well as the bitmapData we're plotting too, more on that soon ), and from those coords we create an offset into the colour table. To create the smooth curves that makes a plasma look so sexy we use some lovely sin and cos ( That's the bit I'm skipping explaining in any real detail. It takes quite a bit of tweaking to get something looking how you like and different values really give different results, for example:

            offset = (Math.cos((xPos+x)*0.0525) + Math.sin((yPos+y)*0.0255))*64 + jointDist;
            offset=offset>>3;

That's what's used in the credits plasma / kaleidoscope effect, which uses exactly the same colour table values but looks totally different ).

All that's left for the plasma part is the mainloop that we run on the enterFrame.

            sinOffset++;
            var radian:Number = sinOffset/60;
            paletteShiftX = 128-Math.sin(radian)*255;
            paletteShiftY = 128-Math.cos(radian)*255;

            plotbm.lock();

            var pixelObj:Pixels;
            for each(pixelObj in activePixelsStorage){
                pixelObj.pixelmainloop(paletteShiftX,paletteShiftY,plotbm);
            }

            plotbm.unlock();

            if(plotbm==bm1){
                plotbm=bm2;
            } else {
                plotbm=bm1;
            }

            bmData1.bitmapData=plotbm;

Nothing too tricky here. We just increase the position ( Offset ) into the colour table every frame, and then use for...each ( Much quicker ) to loop through all our Pixel instances calling the pixelmainloop and passing the args.
The part that may be of interest is the plotbm var. To increase speed slightly we double buffer the plasma bitmap, so when one bitmapData is being displayed we're plotting to the other one which is no longer being shown.
To try and explain that a little better, we have two bitmapData objects, bm1 and bm2. bmData1 is our bitmap ( I find the difference between the two confusing as hell in as3. It makes total sense, it just doesn't seem to stay in my brain very well ) which is attached to the our holder sprite for the plasma ( The one we doubled in size and added a blur to as mentioned earlier ).
So lets say we have something like this:
holderSprite.bmData1.bm1;
And that's what you see on screen. If you can see bm1 that means we're plotting to bm2, and visa versa.

This is why we pass the currently hidden bitmapData to each instance of the Pixel class every frame rather than just passing one value in during it's construction.

That's plasmas for you. I've only really given the core concept as hopefully a spring board for your own experiments.

The Jelly cube is going to be much more straight forward, because someone else wrote the clever bit. After x amount of time we run a really quick white up over the whole stage, and that's where we remove the plasma all together and replace it with a papervision cube.

Ultra simple, we just rotate him and scale him. The twister code came from the excellent zupko who kindly open sourced it. Now we've got a twisty cube, what about the texture ?

This is another big fat cheat. At best you can get a plasma running the size we have at around 40fps, so there's no way we could do it realtime and run the cube. One idea I had early on was to use draw() on every frame of the plasma and store those away, then update the texture every frame on the cube using those stored away bitmaps.
I didn't go this route as I was concerned about the amount of memory it would use and I was concerned that using draw() may have had a negative performance hit when actually running the plasma ( I'm possibly paranoid about that and it would more than likely be fine, but it felt like quite a bit of data to be copying every frame when you want everything running as quickly as possible ).

The solution ? flv baby. Unless it's youTube the flv format seems to be badly over looked when it can be used for all types of tricks ( I did quite a bit of video work in games at preloaded long before flv came out so I've learnt what the advantages of using video are early on ).
I just ran the plasma for a little while grabbing the frames, cropped them up, created a copy running backwards and then joined the two together, so runs as A > B > A.
All that was left then was to created a flv texture for each side of the cube, and papervision along with Flash did everything for me.

The only thing left to cover off is the black outline on the cube. Again ultra simple, it's just a glow filter. Set it to black, turn up the strength, turn down the blurring and you've got a sexy outline.

Phew. I think this is going to be last in-depth-ish tut on the 651 effects. Not only does it takes ages, but I think the rest of the effects not touched on so far can be summarised in one post.

Squize.


Thursday, November 06, 2008 5:24:29 PM (W. Europe Standard Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  |  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
 Thursday, October 23, 2008
Here's another really old trick we used in 651.

651_bobs.jpg

Way back in Amigaland software sprites ( ie, sprites which were plotted by the blitter as opposed to being hardware based, like a mouse pointer ) were called "Bobs" ( Blitter OBjects ). As with everything you could only ever run a certain amount before you started running out of cpu time, so when the first infinite bob effects started appearing in demos every one passed a little bit of involuntary wee.

//------------------------------------------------
// Bob properties
//------------------------------------------------
        private var ball:Sprite;
        
        private var bm1:BitmapData;
        private var bm2:BitmapData;
        private var bm3:BitmapData;
        private var bmData1:Bitmap;
        private var bmData2:Bitmap;
        private var bmData3:Bitmap;

        private var currentBitmapNumber:int;

Just set up 3 bitmaps, and then...

//Set up the sprites
            container=new Sprite();
            stage.addChild(container);
            
            playField=new Sprite();
            container.addChild(playField);

Create a holder sprite + add it to the stage, and then a further sprite within that. Also add your bob to the playField ( Not the container or the stage )

Next up, our mainloop,

//---------------------------------------------------------------------------------------
        private function mainloop(e:Event):void{
            moveBob();
            copyBitmap();
}

moveBob() is however you want to move the bob around the screen, use your nice sin based movement that you've got tucked away. All it's doing is just moving one bob ( ball:Sprite in this case ) around the screen.

The funky bit is the copyBitmap() method,

//---------------------------------------------------------------------------------------
        private function copyBitmap():void{
            container.addChild(this["bmData"+currentBitmapNumber]);
            this["bm"+currentBitmapNumber].draw(playField);

            if(++currentBitmapNumber==4){
                currentBitmapNumber=1;
            }
        }

It just simply loops through all our bitmaps, copying what's in our playField ( ie the ball ) to the next bitmap. Just written down like this it's a bit tricky to grasp, think of it like an old flick book. You move the bob, you take a copy of the whole screen and store that in a bitmap and then display that, you then move the bob again, and take another grab of it and so on. We use 3 bitmaps because the image will be slightly different on all of them, creating the sense of movement ( Otherwise it wouldn't animate and would just look like a trail behind the bob ).

I can recommend giving it a quick play, it'll take 5 mins to set yourself up with a working example and once it's running infront of you it'll click into place how it actually does work.

Squize.

Thursday, October 23, 2008 2:22:11 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [4]  |  Trackback
 Tuesday, October 21, 2008
Now 651 is history I thought it may be of interest to go through how some of the parts work.

Let's start with the boring bit for today, the actual structure. To make testing it easier, and to be able to swap and change the order to make sure it felt right, I used a pretty simple yet modular approach,

//------------------------------------------------
// Demo classes
//------------------------------------------------
        private var logo:Logo;
        private var credits:Credits;
        private var twister:Twister;
        private var vectorBalls:VectorBalls;
        private var pimp:Pimp;
        private var showReel:ShowReel;
        private var water:Water;
        private var fin:Fin;
        
        private var sequenceOrder:Array=new Array("logo","twister","vectorBalls","pimp","credits","showReel","water","fin");
        private var sequenceOffset:int;


The sequenceOrder array kinda speaks for itself. The other part of the code is just as straight forward:

//---------------------------------------------------------------------------------------
        public function sequence():void{
            switch (sequenceOrder[sequenceOffset]){
                case "logo":
                    logo=new Logo();
                    break;    
                case "twister":
                    twister=new Twister();
                    break;    
                case "credits":
                    credits=new Credits();
                    break;    
                case "vectorBalls":
                    vectorBalls=new VectorBalls();
                    break;    
                case "pimp":
                    pimp=new Pimp();
                    break;    
                case "showReel":
                    showReel=new ShowReel();
                    break;    
                case "water":
                    water=new Water();
                    break;    
                case "fin":
                    fin=new Fin();
                    break;    
            }
        }

//---------------------------------------------------------------------------------------
        public function finished():void{
            if(++sequenceOffset==sequenceOrder.length){
//Finished            
            } else {
                sequence();
            }
        }


Each segment is totally independent, ie it has it's own init and housekeeping routines, there's no co-dependency at all. To start the demo the sequenceOffset var is set to 0 and then the sequence() method is called.
When a segment has finished, it calls it's houseKeeping() method to dispose of all the bitmaps and removes all the sprites from the stage, and then calls the finished() method ( Hence it being public ).

That's all there is to the underlying structure which runs the demo, it really doesn't get any more straight forward.

Squize.

Tuesday, October 21, 2008 8:37:29 PM (W. Europe Daylight Time, UTC+02:00)  #    Disclaimer  |  Comments [0]  |  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, 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