Do you like my tutorials?

Then consider supporting me on Ko-fi

Talking about Javascript and Users contributions.

This is an iteresting post made by Roger Engelbert, the author of step by step creation of a 3D helicopter game using Away3D and one game, many frameworks.

Today he’s going to show you how to achieve Flash-like image manipulations with EaselJS, the same framework I used to make a javascript port of Circle Chain game.

« I‘ve decided to take a very simple application done in Flash and transform it in JavaScript for Canvas, using an open source framework, trying to use as many features typically associated with Flash as I could cram in a tiny bit of code, and see how the framework handled them (mainly: events, bitmap data manipulation, the display list and masks).

The application is the core logic inside an old project, FlashNose3D, which I used years ago to talk about PureMVC. Only here, I’m just using the animation bit.

The framework I picked was EaselJS, which is based on ActionScript.

Here is the test application in Flash, using circles:

And here are the classes:

Main.as

package  {
 
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Point;
 
	public class Main extends Sprite {
 
		private var _slices:Array;
 
		function Main () {
			_slices = [];
 
			var startWidth:int = 400;
			var numSlices:int = 40;
			var fraction:int = startWidth/(numSlices - 1);
			var s:Slice;
			for (var i:int = 0; i < numSlices; i++) {
				s = new Slice ((startWidth - i * fraction) * 0.5, i);
				s.x = stage.stageWidth >> 1;
				s.y = stage.stageHeight >> 1;
				this.addChild(s);
				_slices.push(s);
			}
			addEventListener(Event.ENTER_FRAME, on_Loop);
		}
 
 
		private function on_Loop (event:Event):void {
			var len:int = _slices.length;
			var s:Slice;
			var previous:Slice;
			for (var i:int = len-1; i >= 0; i--) {
				s = _slices[i];
				if (i == len-1) {
					s.update(new Point(mouseX, mouseY));
				} else {
					previous = _slices[i + 1];
					s.update(new Point(previous.x, previous.y));
				}
			}		
		}
	}
}

Slice.as

package  {
 
	import flash.display.Sprite;
	import flash.geom.Point;
 
	public class Slice extends Sprite {
 
 
		private var _vx:Number = 0;
		private var _vy:Number = 0;
		private var _friction:Number = 0.2;
		private var _spring:Number = 2;
 
		function Slice (radius:int, index:int) {
 
			this.graphics.beginFill(index * 50);
			this.graphics.drawCircle(0, 0, radius);
			this.graphics.endFill();
		}
 
 
		public function update (target:Point) {
 
			var diffx:Number;
			var diffy:Number;
			var ax:Number;
			var ay:Number;
 
			diffx = target.x - x;
			diffy = target.y - y;
 
			ax = diffx * _spring;
			ay = diffy * _spring;
 
			_vx += ax;
			_vy += ay
 
			_vx *= _friction;
			_vy *= _friction;
 
			this.x += _vx;
			this.y += _vy;
 
		}
 
	}
 
}

Then the EaselJS version. Click anywhere on the image to start the animation (although the effect looks better if you click on: nose, or mouth, or eyes).

And the code:

Main.js

//declare our globals
var NUM_SLICES = 50;
var FRICTION = 0.2;
var SPRING = 2;
 
(function(){
 
	this.animate;
	this.slices;
	this.stage;
	this.image;
 
 
 	this.init = function() {
 
		this.animate = false;
		this.slices = [];
 
		this.image = new Image();
		//handler to preload our image
		this.image.onload = handleImageLoad;
		this.image.src = "doctor3.jpg";
	}
 
	this.handleImageLoad = function () {
 
		var canvas = document.getElementById("testCanvas");
		stage = new createjs.Stage(canvas);
 
 
		var ratio = image.width/(NUM_SLICES + 1);
		var s;
		for (var i = 0; i < NUM_SLICES; i++) {
 
			if (i == 0) {
				s = new Slice ( image, -1 );
			} else {
				s = new Slice ( image, (image.width - i * ratio) * 0.5 );				
 
			}
			s.x = canvas.width * 0.5;
			s.y = canvas.height * 0.5;
 
			stage.addChild(s);
 
 
			slices.push(s);
		}
 
 
		//add stage event: Mouse Click
		stage.onClick = function (event) {
 
			if (animate) return;
 
			var p = slices[0].globalToLocal(event.stageX, event.stageY);
			var len = slices.length;
 
			for (var i = 0; i < len; i++) {
				slices[i].setNose(p.x, p.y);			
			}
 
			animate = true;
		}
		//render stage
		stage.update();
 
		//register main loop event
		createjs.Ticker.setFPS(30);
		createjs.Ticker.addListener(window);
	}
 
 
	//the main loop
	this.tick = function () {
 
		if (animate) {
 
			var len = slices.length;
			var s;
			var previous;
 
			for (var i = len-1; i >= 0; i--) {
				s = slices[i];
				if (i == len-1) {
					s.update(stage.mouseX, stage.mouseY);
				} else {
					previous = slices[i + 1];
					s.update(previous.x, previous.y);
				}
			}
 
		}
		//render stage
		stage.update();
	}
 
 
})();
 
window.onload = init;

Slice.js

(function(window) {
 
 
	this.circleMask;
    this.vx;
    this.vy;
 
    function Slice(image, radius) {
 
        this.initialize();
 
        var canvas = document.getElementById("testCanvas");
        this.image = image;
 
        this.regX = image.width * 0.5;
        this.regY = image.height * 0.5;
 
        vx = 0;
        vy = 0;
 
        if (radius > 0) {
 
        	this.circleMask = new createjs.Shape();
	        this.circleMask.graphics.beginFill("0xFFF");
			this.circleMask.graphics.drawCircle(0,0,radius);
			this.circleMask.graphics.endFill(); 	
        }    
    }
 
    Slice.prototype = new createjs.Bitmap();
 
    Slice.prototype.circleMask;
    Slice.prototype.vx;
    Slice.prototype.vy;
 
    Slice.prototype.Bitmap_initialize = Slice.prototype.initialize;
 
    Slice.prototype.initialize = function() {
        this.Bitmap_initialize();
    }
 
 
    Slice.prototype.setNose = function (noseX, noseY) {
 
        this.regX = noseX;
        this.regY = noseY;
 
        if (this.circleMask) {
            this.mask = this.circleMask;
        }
    }
 
    Slice.prototype.update = function (targetX, targetY) {
 
        var diffx, diffy, ax, ay;
 
        diffx = targetX - this.x;
        diffy = targetY - this.y;
 
        ax = diffx * SPRING;
        ay = diffy * SPRING;
 
        vx += ax;
        vy += ay
 
        vx *= FRICTION;
        vy *= FRICTION;
 
        this.x += vx;
        this.y += vy;
 
        if (this.circleMask) {
            this.circleMask.x = this.x;
            this.circleMask.y = this.y;
        }
 
    }
 
    Slice.prototype.normalize = function (p) {
    	var length = Math.sqrt(p.x*p.x+p.y*p.y);
    	return 	new createjs.Point((p.x/length), (p.y/length));
	}
 
    window.Slice = Slice;
}(window));

Of course, the JS version uses an actual image, so the classes are a bit more extensive.

But you can see, among other things:

– how to write classes with EaselJS
– how you can extend a display object in EaselJS. In the code I extended the Bitmap class.
– how to draw vector shapes. I used a circle for the mask.
– how to mask a bitmap with a vector shape.
– how to add the main loop
– how to add mouse events on stage. The same logic would work for any display object.
– how to change the registration point of a display object with its properties regX and regY.

»

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.