Liquid Text Effect

Haven’t been too active around here lately due to a major on going project at work lately as well as the fact that my wife and I are in the slow process of moving homes. Really, the only free time it seems I have these days is during the lunch hour. So, over the past two days, I came up with this fun little toy during lunches.

Having recently re-watched Terminator 2 or 3 or 4 or whichever one had that liquid metal dude, I wanted to do something liquid metally. And below is the text effect result. You can change what it says by typing something new into the text box and clicking the “change” button. You can also try different colors using the color picker in the top left (a deep red blood color may be cool for halloween, a blue for nice looking water or black can create a nice new oily BP logo). You can also mouse over the text to swish it up a bit.

Get Adobe Flash player

The code is by no means optimized or even cleaned up, but if you’d like to play around, it’s all below. To use as is, you’ll need the Arial Black font and the great minimalcomps from Keith (Bit-101) Peters.

package  {
 
	import com.bit101.components.ColorChooser;
	import com.bit101.components.InputText;
	import com.bit101.components.PushButton;
	import com.bit101.components.Style;
	import flash.utils.setTimeout;
 
	import com.greensock.easing.Quad;
	import com.greensock.TweenLite;
 
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;	
	import flash.events.Event;
	import flash.events.MouseEvent;	
	import flash.filters.BevelFilter;
	import flash.filters.BlurFilter;
	import flash.filters.DropShadowFilter;
	import flash.geom.Point;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;
 
	/**
	 * some liquidy metal madness
	 * @author Devon O.
	 */
 
	 [SWF(width='640', height='400', backgroundColor='#444444', frameRate='40')]
	public class Main extends Sprite {
 
		[Embed(source = "../assets/ariblk.ttf", fontFamily = "ariblk", mimeType = "application/x-font", embedAsCFF = "false")] public static const EFONT:Class;
		public static const NUM_BLOBS:int = 500;
		public static const PROXIMITY:int = 35;
 
		private var _holder:Sprite = new Sprite();
 
		private var _display:BitmapData;
 
		private var _textData:BitmapData;
		private var _text:TextField;
		private var _textHolder:Sprite;
 
		private var _textColor:uint = 0xFF333333;
 
		private var _blobs:Vector.<Circle> = new Vector.<Circle>(NUM_BLOBS, true);
		private var _toPoints:Vector.<SimplePoint> = new Vector.<SimplePoint>(NUM_BLOBS, true);
 
		private var _layout:BitmapLayout;
 
		private var _blur:BlurFilter = new BlurFilter(8, 8, 5);
		private var _bevel:BevelFilter = new BevelFilter(4, 80, 0xFFFFFF, 1, 0x111111, 1, 8, 8, 2, 1);
		private var _shadow:DropShadowFilter = new DropShadowFilter(4, 80, 0x000000, 1, 4, 4, 1, 1, false, false);
		private var _pt:Point = new Point();
 
		private var _colorPicker:ColorChooser;
		private var _input:InputText;
		private var _enterButton:PushButton;
 
		public function Main() {
			init();
		}
 
		private function init():void {
			initText();
			setText("LIQUID");
 
			initDisplay();
			initBalls();
 
			initUI();
 
			addEventListener(Event.ENTER_FRAME, drawDisplay);
		}
 
		private function initDisplay():void {
			_display = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0x00000000);
			var view:Bitmap = new Bitmap(_display);
			view.filters = [_bevel, _shadow];
			addChild(view);
		}
 
		private function initUI():void {
			Style.BUTTON_FACE = 0x333333;
 
			_colorPicker = new ColorChooser(this, 10, 10, 0x333333, colorHandler);
			_colorPicker.usePopup = true;
 
			_input = new InputText(this, _colorPicker.x + _colorPicker.width + 10, 10, "LIQUID");
 
			_enterButton = new PushButton(this, _input.x + _input.width + 10, 10, "CHANGE", swapText);
		}
 
		private function colorHandler(event:Event):void {	
			var r:uint = _colorPicker.value >> 16;
			var g:uint = _colorPicker.value >> 8 & 0xFF;
			var b:uint = _colorPicker.value & 0xFF;
			_textColor = 0xFF << 24 | r << 16 | g << 8 | b;
		}
 
		private function initBalls():void {
			for (var i:int = 0; i < NUM_BLOBS; i++) {
				var b:Circle = new Circle();
				var pt:SimplePoint = _layout.getPoint();
				b.cx = b.tx = b.x = pt.x;
				b.cy = b.ty = b.y = pt.y;
				b.range = randRange(2, 1, 2);
				b.angle = randRange(360, 0);
				b.speed = randRange(.13, .05, 2);
				b.scaleX = b.scaleY = randRange(1, .5, 2);
				_blobs[i] = b;
				_toPoints[i] = new SimplePoint(b.cx + randRange(150, -150), b.cy + randRange(150, -150));
				_holder.addChild(b);
			}
		}
 
		private function initText():void {
			_text = new TextField();
			_text.defaultTextFormat = new TextFormat(new EFONT().fontName, 150, 0x000000);
			_text.autoSize = TextFieldAutoSize.LEFT;
			_text.embedFonts = true;
			_textHolder = new Sprite();
			_textHolder.addChild(_text);
			_textData = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0x00000000);
		}
 
		private function swapText(event:MouseEvent):void {	
			var i:int = NUM_BLOBS;
			while (i--) {
				if (i == 0) {
					TweenLite.to(_blobs[i], .60, { x:_toPoints[i].x, y:_toPoints[i].y, ease:Quad.easeOut, onComplete:completeText } );
				} else {
					TweenLite.to(_blobs[i], .60, { x:_toPoints[i].x, y:_toPoints[i].y, ease:Quad.easeOut } );
				}
			}
		}
 
		private function completeText():void {
			setText(_input.text);
 
			setTimeout(tweenBack, 200);
		}
 
		private function tweenBack():void {
			var i:int = NUM_BLOBS;
			while (i--) {
				var b:Circle = _blobs[i];
				var pt:SimplePoint = _layout.getPoint();
				b.cx = pt.x;
				b.cy = pt.y;
				b.range = randRange(2, 1, 2);
				b.angle = randRange(360, 0);
				b.speed = randRange(.13, .05, 2);
				TweenLite.to(b, .80, { x:b.cx, y:b.cy, ease:Quad.easeOut } );
			}
		}
 
		private function setText(words:String):void {
			_text.text = words;
			_text.x = (stage.stageWidth - _text.width) >> 1;
			_text.y = (stage.stageHeight - _text.height) >> 1;
 
			_textData.fillRect(_textData.rect, 0x00000000);
			_textData.draw(_textHolder);
 
			if (_layout == null) _layout = new BitmapLayout(_textData);
			else _layout.data = _textData;
		}
 
		private function shimmy():void {
			var i:int = NUM_BLOBS;
			while (i--) {
				var b:Circle = _blobs[i];
 
				b.inner.x =  Math.sin(b.angle) * (b.range);
				b.inner.y =  Math.cos(b.angle) * (b.range * 6);
 
				b.angle += b.speed;
 
				var p1:SimplePoint = new SimplePoint(b.cx, b.cy);
				var p2:SimplePoint = new SimplePoint(stage.mouseX, stage.mouseY);
 
				var d:Number = getDist(p1, p2);
				if ( d < (PROXIMITY * PROXIMITY) ) {
					b.tx = stage.mouseX;
					b.ty = stage.mouseY
				} else {
					b.tx = b.cx;
					b.ty = b.cy;
				}
				b.x += (b.tx - b.x) * .25;
                b.y += (b.ty - b.y) * .25;
			}
		}
 
		private function getDist(p1:SimplePoint, p2:SimplePoint):Number {
			return (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y);
		}
 
		private function drawDisplay(event:Event):void {
			shimmy();
 
			_display.fillRect(_display.rect, 0x000000);
			_display.draw(_holder);
			_display.applyFilter(_display, _display.rect, _pt, _blur);
			_display.threshold(_display, _display.rect, _pt, ">",  0x001F0000, _textColor, 0x00FF0000, false);
		}
 
		private function randRange(max:Number, min:Number = 0, decimals:int = 0):Number {
			if (min > max) return NaN;
			var rand:Number = Math.random() * (max-min + Math.pow(10, -decimals)) + min;
			return int(rand * Math.pow(10, decimals)) / Math.pow(10, decimals);
		}
	}
}
package  {
 
	import flash.display.Shape;
	import flash.display.Sprite;
 
	public class Circle extends Sprite {
 
		private var _cx:Number;
		private var _cy:Number;
		private var _tx:Number;
		private var _ty:Number;
		private var _range:Number;
		private var _speed:Number;
		private var _angle:Number;
		private var _inner:Shape = new Shape();
 
		public function Circle() {
			_inner.graphics.beginFill(0xFFFFFF);
			_inner.graphics.drawCircle(0, 0, 6);
			_inner.graphics.endFill();
			addChild(_inner);
		}
 
		public function get cx():Number { return _cx; }
 
		public function set cx(value:Number):void {
			_cx = value;
		}
 
		public function get cy():Number { return _cy; }
 
		public function set cy(value:Number):void {
			_cy = value;
		}
 
		public function get range():Number { return _range; }
 
		public function set range(value:Number):void {
			_range = value;
		}
 
		public function get speed():Number { return _speed; }
 
		public function set speed(value:Number):void {
			_speed = value;
		}
 
		public function get angle():Number { return _angle; }
 
		public function set angle(value:Number):void {
			_angle = value;
		}
 
		public function get inner():Shape { return _inner; }
 
		public function get tx():Number { return _tx; }
 
		public function set tx(value:Number):void {
			_tx = value;
		}
 
		public function get ty():Number { return _ty; }
 
		public function set ty(value:Number):void {
			_ty = value;
		}
	}
}
package  {
 
	import flash.display.BitmapData;
 
	public class BitmapLayout {
 
		private var _data:BitmapData;
 
		public function BitmapLayout(data:BitmapData) {
			_data = data;
		}
 
		// tip of the hat to the HYPE framework cats for this bit
		public function getPoint():SimplePoint {
			var pt : SimplePoint;
 
			do {
				pt = new SimplePoint();
				pt.x = (Math.random() * _data.width);
				pt.y = (Math.random() * _data.height);
 
				var c:uint = _data.getPixel32(pt.x, pt.y);
 
				var alpha:uint = c >> 24;
 
				if (alpha == 0) pt = null;
 
 
			} while (pt == null);
 
			return pt;
		}
 
		public function get data():BitmapData { return _data; }
 
		public function set data(value:BitmapData):void {
			_data = value;
		}
	}
 
}
package  {
 
	public class SimplePoint {
 
		private var _x:Number;
		private var _y:Number;
 
		public function SimplePoint(x:Number = 0, y:Number = 0 ) {
			_x = x;
			_y = y;
		}
 
		public function get x():Number { return _x; }
 
		public function set x(value:Number):void {
			_x = value;
		}
 
		public function get y():Number { return _y; }
 
		public function set y(value:Number):void {
			_y = value;
		}
	}
}

3 Comments »

  1. Hendrik says:

    Awesome effect man, I love playing with it.
    Sort of rediscovered your blog again today after not having visited it for a while. Lots of awesome new experiments!! Thnx for sharing the knowledge, keep it up!

  2. Devon O. says:

    Hey Hendrik, Thanks for the nice comment – and welcome back..

  3. Devon, love your humor:
    1) “re-watched Terminator 2 or 3 or 4 or whichever one had that liquid metal dude”.
    2) “a nice new oily BP logo”.

    Besides from that, impressive what you are capable of in your lunch-breaks, love it!

    Felix

RSS feed for comments on this post. / TrackBack URI

Leave a Reply

Devon O. Wolfgang

Technical Reviewer of “The Essential Guide to Flash CS4 AIR Development”

Contributing Author of “Flash AS3 for Interactive Agencies”

Senior Flash Engineer PopCap Games, International Ltd.

Portfolio

Santabot: A Unity3D Flash Game


All right, so a Christmas game like “Santabot vs. The Flying Saucers from Mars” may be a day late[...]

Magnify – a jQuery Plugin


Let me begin by saying right up front, I have not given up on Flash[...]

It’s a Starling Halloween


Getting some practice for the upcoming Zombie Apocalypse[...]

Getting Started with Proscenium

So I had a chance this weekend to sit down and play around with Adobe’s new 3D framework for Stage3D, Proscenium, and thought I’d share a few of the results (a word of caution, there are no preloaders for any of the examples and may load a bit slowly). The first shows some reflections and [...]

Particle Editor for Starling Framework

An in-browser particle editor for the Starling 2D Framework for Flash Player 11.

So Long and Thanks for all the Flash on the Beach

So, another Flash on the Beach has just has just drawn to a close[...]

Game Development Tips from the Trenches of PopCap

Well, this is a post that’s a bit overdue, but, thanks to a well timed bank holiday, I finally had the opportunity to sit down and type up what I’ve been meaning to for some time now…

Old Skool Demoscene FX as 3D Textures

Many moons ago, I got the idea to create some demoscene plane deformation effects in Flash based on the formulas found here: http://www.iquilezles.org/www/articles/deform/deform.htm. I posted my less than desired results up on wonderfl. Thankfully, fellow wonderfl user, Hasufel, forked my attempt and optimized the hell out of it coming up with this. Well, today, for no [...]

Making The Gaming Scene (A Change in Careers)

And for my second blog post of the day, a much more personal note. After about three years of working with, what I would consider as objectively as possible, the best digital agency in Ireland, vStream Digital Media, I have made the immense career decision to leave the agency world and enter the arena of [...]

Feeling Lucky?

Images to dice, kick ascii style [...]

Adventures in Playbook Land

Adventures in Playbook Land


Now that the ordeal is over, I thought I’d take the time to sit down and share my account of what it was like to develop a Blackberry Playbook application using the Adobe Flex SDK[...]

Flash

Draw it for Me

So many ideas – so little time….

Kinect Application Running in Dublin

So, on Friday I just wrapped up our latest project at vStream Digital Media, a Kinect powered flipbook that lets users flip through the hand written notebooks of Philip Lynott of Thin Lizzy. The app uses OpenNI, runs in Adobe AIR and is currently on display at a pretty bitchin’ Phil Lynott exhibition running in [...]

Beach Ball Kinect Party

So we finally got a Kinect camera hooked up to a pc at work and, while it doesn’t seem to be legal to use it for commercial projects (but, hey, I’m no lawyer), the boss asked me to get it figured out and come up with some ideas just in case it would be feasible [...]

Facebook and Flash – A Book Review

Now, I should begin by saying I absolutely hate building Facebook applications. And I build a lot of them at work. Every time I get the word from above that we’re doing another FB app, I just groan – both inwardly and out. It’s become a running joke of the office. Why do I dislike [...]

Multitouch Fluid Dynamics with AIR for Android and RTMFP

The other day I was having some fun playing around with Eugene Zatepyakin’s (aka @inspirit) FluidSolverHD (Actionscript port of C++ fluid dynamics library, MSAFluid. Or is MSAFluid, the processing/java port of the C++ library? In any case it’s a very cool fluid dynamics thingamabob – the HD version using Alchemy). After a bit of tinkering, [...]