Gaming Your Way

May contain nuts.

Jelly cube with an animated plasma texture

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. This will create an interesting effect, and if you're going for something colourful and eye-catching as a baddie in a platformer or a fun screensaver for a partypoker website, then it's ideal. 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.


Comments are closed