Author Topic: Simple Water Box Effect  (Read 2540 times)

xhunterko

  • Contributor
  • ****
  • Posts: 449
  • Karma: +0/-0
    • View Profile
Simple Water Box Effect
« on: Sat, May 12, 2012 »
Hey there!

So I've been hunting around the net for a simple water thing to put in my game, and found a few, but none were compatable with flixel. I had giving up hope of finding anything, but then I remembered that photonstorms power tools had a sine wave effect. I thought it crazy, but I decided to try and hack it any way. I did some poking in the effects code, did some tweaking, and, it worked! It actually worked! Now, it's not real water. It won't drain into little particles or anything, but it has a wave effect going on that says, hey, this blue thing is water! Or it can be a red moving thing for lava. Anyways. To get the effect working, you need to:

Step 1: Get photonstorms power tools if you don't already have them! Seriously, they're great stuff! You can find them here: http://www.photonstorm.com/flixel-power-tools

Step 2: After you've added them to your flixel 2.55 project, if they aren't already in there, using flash develop, open the file sinewaveFx.as.

Step 3: After you've done that, modify the code you see to the one below, or just copy and paste it from here to your file:

Code: [Select]
/**
 * SineWaveFX - Special FX Plugin
 * -- Part of the Flixel Power Tools set
 *
 * v1.0 First release
 *
 * @version 1.0 - May 21st 2011
 * @link http://www.photonstorm.com
 * @author Richard Davey / Photon Storm
*/

package org.flixel.plugin.photonstorm.FX
{
import flash.display.BitmapData;
import flash.geom.Point;
import flash.geom.Rectangle;

import org.flixel.*;
import org.flixel.plugin.photonstorm.*;

/**
* Creates a sine-wave effect through an FlxSprite which can be applied vertically or horizontally
*/
public class SineWaveFX extends BaseFX
{
private var waveType:uint;
private var waveVertical:Boolean;
private var waveLength:uint;
private var waveSize:uint;
private var waveFrequency:Number;
private var wavePixelChunk:uint;
private var waveData:Array;
private var waveDataCounter:uint = 0;
private var waveLoopCallback:Function;

public static const WAVETYPE_VERTICAL_SINE:uint = 0;
public static const WAVETYPE_VERTICAL_COSINE:uint = 1;
public static const WAVETYPE_HORIZONTAL_SINE:uint = 2;
public static const WAVETYPE_HORIZONTAL_COSINE:uint = 3;

public function SineWaveFX()
{
}

/**
* Creates a new SineWaveFX Effect from the given FlxSprite. The original sprite remains unmodified.<br>
* The resulting FlxSprite will take on the same width / height and x/y coordinates of the source FlxSprite.<br>
* For really cool effects you can SineWave an FlxSprite that is constantly updating (either through animation or an FX chain).
*
* @param source The FlxSprite providing the image data for this effect. The resulting FlxSprite takes on the source width, height, x/y positions and scrollfactor.
* @param type WAVETYPE_VERTICAL_SINE, WAVETYPE_VERTICAL_COSINE, WAVETYPE_HORIZONTAL_SINE or WAVETYPE_HORIZONTAL_COSINE
* @param size The size in pixels of the sine wave. Either the height of the wave or the width (for vertical or horizontal waves)
* @param length The length of the wave in pixels. You should usually set this to the width or height of the source image, or a multiple of it.
* @param frequency The frequency of the peaks in the wave. MUST BE AN EVEN NUMBER! 2, 4, 6, 8, etc.
* @param pixelsPerChunk How many pixels to use per step. Higher numbers make a more chunky but faster effect. Make sure source.width/height divides by this value evenly.
* @param updateFrame When this effect is created it takes a copy of the source image data and stores it. Set this to true to grab a new copy of the image data every frame.
* @param backgroundColor The background color (0xAARRGGBB format) to draw behind the effect (default 0x0 = transparent)
* @return An FlxSprite with the effect running through it, which should be started with a call to SineWaveFX.start()
*/
public function createFromFlxSprite(source:FlxSprite, type:uint, size:uint, length:uint, frequency:uint = 2, pixelsPerChunk:uint = 1, updateFrame:Boolean = false, backgroundColor:uint = 0x0):FlxSprite
{
var result:FlxSprite = create(source.pixels, source.x, source.y, type, size, length, frequency, pixelsPerChunk, backgroundColor);

updateFromSource = updateFrame;

if (updateFromSource)
{
sourceRef = source;
}

return result;
}

/**
* Creates a new SineWaveFX Effect from the given Class (which must contain a Bitmap).<br>
* If you need to update the source data at run-time then use createFromFlxSprite
*
* @param source The Class providing the bitmapData for this effect, usually from an Embedded bitmap.
* @param x The x coordinate (in game world pixels) that the resulting FlxSprite will be created at.
* @param y The x coordinate (in game world pixels) that the resulting FlxSprite will be created at.
* @param type WAVETYPE_VERTICAL_SINE, WAVETYPE_VERTICAL_COSINE, WAVETYPE_HORIZONTAL_SINE or WAVETYPE_HORIZONTAL_COSINE
* @param size The size in pixels of the sine wave. Either the height of the wave or the width (for vertical or horizontal waves)
* @param length The length of the wave in pixels. You should usually set this to the width or height of the source image, or a multiple of it.
* @param frequency The frequency of the peaks in the wave. MUST BE AN EVEN NUMBER! 2, 4, 6, 8, etc.
* @param pixelsPerChunk How many pixels to use per step. Higher numbers make a more chunky but faster effect. Make sure source.width/height divides by this value evenly.
* @param backgroundColor The background color in 0xAARRGGBB format to draw behind the effect (default 0x0 = transparent)
* @return An FlxSprite with the effect running through it, which should be started with a call to SineWaveFX.start()
*/
public function createFromClass(source:Class, x:int, y:int, type:uint, size:uint, length:uint, frequency:uint = 2, pixelsPerChunk:uint = 1, backgroundColor:uint = 0x0):FlxSprite
{
var result:FlxSprite = create((new source).bitmapData, x, y, type, size, length, frequency, pixelsPerChunk, backgroundColor);

updateFromSource = false;

return result;
}

/**
* Creates a new SineWaveFX Effect from the given bitmapData.<br>
* If you need to update the source data at run-time then use createFromFlxSprite
*
* @param source The bitmapData image to use for this effect.
* @param x The x coordinate (in game world pixels) that the resulting FlxSprite will be created at.
* @param y The x coordinate (in game world pixels) that the resulting FlxSprite will be created at.
* @param type WAVETYPE_VERTICAL_SINE, WAVETYPE_VERTICAL_COSINE, WAVETYPE_HORIZONTAL_SINE or WAVETYPE_HORIZONTAL_COSINE
* @param size The size in pixels of the sine wave. Either the height of the wave or the width (for vertical or horizontal waves)
* @param length The length of the wave in pixels. You should usually set this to the width or height of the source image, or a multiple of it.
* @param frequency The frequency of the peaks in the wave. MUST BE AN EVEN NUMBER! 2, 4, 6, 8, etc.
* @param pixelsPerChunk How many pixels to use per step. Higher numbers make a more chunky but faster effect. Make sure source.width/height divides by this value evenly.
* @param backgroundColor The background color in 0xAARRGGBB format to draw behind the effect (default 0x0 = transparent)
* @return An FlxSprite with the effect running through it, which should be started with a call to SineWaveFX.start()
*/
public function createFromBitmapData(source:BitmapData, x:int, y:int, type:uint, size:uint, length:uint, frequency:uint = 2, pixelsPerChunk:uint = 1, backgroundColor:uint = 0x0):FlxSprite
{
var result:FlxSprite = create(source, x, y, type, size, length, frequency, pixelsPerChunk, backgroundColor);

updateFromSource = false;

return result;
}

/**
* Internal function fed from createFromFlxSprite / createFromClass / createFromBitmapData
*
* @param source The bitmapData image to use for this effect.
* @param x The x coordinate (in game world pixels) that the resulting FlxSprite will be created at.
* @param y The x coordinate (in game world pixels) that the resulting FlxSprite will be created at.
* @param type WAVETYPE_VERTICAL_SINE, WAVETYPE_VERTICAL_COSINE, WAVETYPE_HORIZONTAL_SINE or WAVETYPE_HORIZONTAL_COSINE
* @param size The size in pixels of the sine wave. Either the height of the wave or the width (for vertical or horizontal waves)
* @param length The length of the wave in pixels. You should usually set this to the width or height of the source image, or a multiple of it.
* @param frequency The frequency of the peaks in the wave. MUST BE AN EVEN NUMBER! 2, 4, 6, 8, etc.
* @param pixelsPerChunk How many pixels to use per step. Higher numbers make a more chunky but faster effect. Make sure source.width/height divides by this value evenly.
* @param backgroundColor The background color in 0xAARRGGBB format to draw behind the effect (default 0x0 = transparent)
* @return An FlxSprite with the effect running through it, which should be started with a call to SineWaveFX.start()
*/
private function create(source:BitmapData, x:int, y:int, type:uint, size:uint, length:uint, frequency:uint = 2, pixelsPerChunk:uint = 1, backgroundColor:uint = 0x0):FlxSprite
{
if (type == WAVETYPE_VERTICAL_SINE || type == WAVETYPE_VERTICAL_COSINE)
{
waveVertical = true;

if (pixelsPerChunk >= source.width)
{
throw new Error("SineWaveFX: pixelsPerChunk cannot be >= source.width with WAVETYPE_VERTICAL");
}
}
else if (type == WAVETYPE_HORIZONTAL_SINE || type == WAVETYPE_HORIZONTAL_COSINE)
{
waveVertical = false;

if (pixelsPerChunk >= source.height)
{
throw new Error("SineWaveFX: pixelsPerChunk cannot be >= source.height with WAVETYPE_HORIZONTAL");
}
}

updateWaveData(type, size, length, frequency, pixelsPerChunk);

// The FlxSprite into which the sine-wave effect is drawn

if (waveVertical)
{
sprite = new FlxSprite(x, y).makeGraphic(source.width, source.height/2 + (waveSize * 3), backgroundColor);
}
else
{
sprite = new FlxSprite(x, y).makeGraphic(source.width + (waveSize * 3), source.height/2, backgroundColor);
}

// The scratch bitmapData where we prepare the final sine-waved image
canvas = new BitmapData(sprite.width, sprite.height, true, backgroundColor);

// Our local copy of the sprite image data
image = source.clone();

clsColor = backgroundColor;
clsRect = new Rectangle(0, 0, canvas.width, canvas.height);

copyPoint = new Point(0, 0);

if (waveVertical)
{
copyRect = new Rectangle(0, 0, wavePixelChunk, image.height);
}
else
{
copyRect = new Rectangle(0, 0, image.width, wavePixelChunk);
}

active = true;

return sprite;
}

/**
* Update the SineWave data without modifying the source image being used.<br>
* This call is fast enough that you can modify it in real-time.
*
* @param type WAVETYPE_VERTICAL_SINE, WAVETYPE_VERTICAL_COSINE, WAVETYPE_HORIZONTAL_SINE or WAVETYPE_HORIZONTAL_COSINE
* @param size The size in pixels of the sine wave. Either the height of the wave or the width (for vertical or horizontal waves)
* @param length The length of the wave in pixels. You should usually set this to the width or height of the source image, or a multiple of it.
* @param frequency The frequency of the peaks in the wave. MUST BE AN EVEN NUMBER! 2, 4, 6, 8, etc.
* @param pixelsPerChunk How many pixels to use per step. Higher numbers make a more chunky but faster effect. Make sure source.width/height divides by this value evenly.
*/
public function updateWaveData(type:uint, size:uint, length:uint, frequency:uint = 2, pixelsPerChunk:uint = 1):void
{
if (type > WAVETYPE_HORIZONTAL_COSINE)
{
throw new Error("SineWaveFX: Invalid WAVETYPE");
}

if (FlxMath.isOdd(frequency))
{
throw new Error("SineWaveFX: frequency must be an even number");
}

waveType = type;
waveSize = uint(size * 0.5);
waveLength = uint(length / pixelsPerChunk);
waveFrequency = frequency;
wavePixelChunk = pixelsPerChunk;
waveData = FlxMath.sinCosGenerator(waveLength, waveSize, waveSize, waveFrequency);

if (waveType == WAVETYPE_VERTICAL_COSINE || waveType == WAVETYPE_HORIZONTAL_COSINE)
{
waveData = FlxMath.getCosTable();
}
}

/**
* Use this to set a function to be called every time the wave has completed one full cycle.<br>
* Set to null to remove any previous callback.
*
* @param callback The function to call every time the wave completes a full cycle (duration will vary based on waveLength)
*/
public function setLoopCompleteCallback(callback:Function):void
{
waveLoopCallback = callback;
}

/**
* Called by the FlxSpecialFX plugin. Should not be called directly.
*/
public function draw():void
{
if (ready)
{
if (lastUpdate != updateLimit)
{
lastUpdate++;

return;
}

if (updateFromSource && sourceRef.exists)
{
image = sourceRef.framePixels;
}

canvas.lock();
canvas.fillRect(clsRect, clsColor);

var s:uint = 0;

copyRect.x = 0;
copyRect.y = 0;

if (waveVertical)
{
for (var x:int = 0; x < image.width; x += wavePixelChunk)
{
copyPoint.x = x;
copyPoint.y = waveSize + (waveSize / 2) + waveData[s];

canvas.copyPixels(image, copyRect, copyPoint);

copyRect.x += wavePixelChunk;

s++;
}
}
else
{
for (var y:int = 0; y < image.height; y += wavePixelChunk)
{
copyPoint.x = waveSize + (waveSize / 2) + waveData[s];
copyPoint.y = y;

canvas.copyPixels(image, copyRect, copyPoint);

copyRect.y += wavePixelChunk;

s++;
}
}

// Cycle through the wave data - this is what causes the image to "undulate"
var t:Number = waveData.shift();
waveData.push(t);

waveDataCounter++;

if (waveDataCounter == waveData.length)
{
waveDataCounter = 0;

if (waveLoopCallback is Function)
{
waveLoopCallback.call();
}
}

canvas.unlock();

lastUpdate = 0;

sprite.pixels = canvas;
sprite.dirty = true;
}
}

public function toString():String
{
var output:Array = [ "Type: " + waveType, "Size: " + waveSize, "Length: " + waveLength, "Frequency: " + waveFrequency, "Chunks: " + wavePixelChunk, "clsRect: " + clsRect ];

return output.join(",");
}

}

}



Tip: So, what did I change? Compare this one to the original. All I did was find out where photonstorm was calculating the math to pull off the effect. Apparently, he's using the height of the flxsprite class to do so. So what to do? Divide it by 2 so only the top half of the sprite gets the sinewave effect applied to it. Couldn't be simpler!

Step 4: To get the effect in your game, just do the following for a simple sprite that doesn't rely on an embedded image:

Code: [Select]
//First off, don't forget to import them!
import org.flixel.plugin.photonstorm.*;
import org.flixel.plugin.photonstorm.FX.*;

//Call the effects private variables
private var sinewave:SineWaveFX;
private var wibbleWobble:FlxSprite;
//Want more then one? Then you have to name it sinewave2,wibbleWobble2, etc.

//Put this in your create structure
// Get a sinewave effect from FlxSpecialFX
sinewave = FlxSpecialFX.sineWave();

var pic:FlxSprite = new FlxSprite(224, 320, null); // AssetsRegistry.overdoseEyePNG);
//if you don't want to use a png, add the next line like I do. It makes a flxSprite instead
pic = new FlxSprite(224, 320 + 8, null).makeGraphic(32, 32, 0xff0000FF);

wibbleWobble = sinewave.createFromFlxSprite(pic, SineWaveFX.WAVETYPE_VERTICAL_SINE, pic.height/4, pic.width);
wibbleWobble.alpha =  0.85;
//You don't have to add alpha if you want. but yes, you do have to add it to the wibbleWobble to get it to work
sinewave.start();
add(wibbleWobble);

//And that's it!

And there you have it! Easy as that! If you want lava, color it red and add the glow effect that I outline, then add some particles coming off it. It's a flxsprite so it should allow for collisions too. I'll test it and let you know. So, there you go, very simple water. It won't break up into particles or anything fancy but it will get the look done.

Enjoy!
« Last Edit: Sat, May 12, 2012 by xhunterko »
Now on twitter: http://twitter.com/xhunterko I made a game that's in alpha you can buy here: http://xhunterko.itch.io/wave-miner-alpha

hoolay

  • Member
  • **
  • Posts: 32
  • Karma: +0/-0
    • View Profile
    • ad-creatif
Re: Simple Water Box Effect
« Reply #1 on: Sun, May 13, 2012 »
It looks interresting, thank you.
Don't you have a small preview ?

xhunterko

  • Contributor
  • ****
  • Posts: 449
  • Karma: +0/-0
    • View Profile
Re: Simple Water Box Effect
« Reply #2 on: Sun, May 13, 2012 »
Let me know if this link works:

http://www.newgrounds.com/dump/item/773188fb228d2c23bdc4b4c9c01d6e9c

Also. The glow effect I described on here earlier? It may not work with these. As it requires an actual png or other file as a class I think. But there may be another work around that I'm looking at and I'll get back with it later.

Glad you like it!

-Cheers!
-xhunterko
Now on twitter: http://twitter.com/xhunterko I made a game that's in alpha you can buy here: http://xhunterko.itch.io/wave-miner-alpha

hoolay

  • Member
  • **
  • Posts: 32
  • Karma: +0/-0
    • View Profile
    • ad-creatif
Re: Simple Water Box Effect
« Reply #3 on: Mon, May 14, 2012 »
it works,
I have a better idea now (without the glow effect as you said). I'm gonna try by myself. The PNG you are talking about is a kind of alpha map ?

xhunterko

  • Contributor
  • ****
  • Posts: 449
  • Karma: +0/-0
    • View Profile
Re: Simple Water Box Effect
« Reply #4 on: Tue, May 15, 2012 »
No. It's an actual png from a class. Here's the example I was talking about since I couldn't find it on here:

Code: [Select]
package 
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.filters.GlowFilter;
import flash.geom.Point;
import flash.geom.Rectangle;
import org.flixel.*;

public class GlowState extends FlxState
{
[Embed(source = '../assets/car.png')] private var carPNG:Class;

private var dude:FlxSprite;

public function GlowState()
{
}

override public function create():void
{
// Our PNG is 32x32, but we need room for a big glow around it, so will make a 64x64x bitmap and put the PNG in the middle
var bigBitmap:BitmapData = new BitmapData(64, 64, true, 0x00000000);
bigBitmap.copyPixels(Bitmap(new carPNG).bitmapData, new Rectangle(0, 0, 32, 32), new Point(16, 16), null, null, true);

dude = new FlxSprite;
dude.pixels = bigBitmap;

dude.x = 100;
dude.y = 100;

// Warning - this is quite an inefficient way to do it if you need to update the glow filter a lot during your game, as it uses a temp swap bitmap to achieve it
dude.framePixels.applyFilter(dude.framePixels, new Rectangle(0, 0, dude.width, dude.height), new Point(0, 0), new GlowFilter(0xFF8000, 0.5, 12, 12, 2, 3, false));

add(dude);
}

override public function update():void
{
}

}

}


As you can see, it requires a png as a Class as I haven't figured out how to do it with a FlxSprite yet. So for now, I'll have to try and see if I can't get something else to work.
Now on twitter: http://twitter.com/xhunterko I made a game that's in alpha you can buy here: http://xhunterko.itch.io/wave-miner-alpha