Impact

This forum is read only and just serves as an archive. If you have any questions, please post them on github.com/phoboslab/impact

1 decade ago by monkeyArms

-----------------------
NOTE: This plugin code has been updated as of 2-20-2012
------------------------

Here is a plugin that allow you to add "layers" to an animation sheet - it injects 4 new methods into ig.AnimationSheet:

addLayer(path) :: adds another layer over the existing animation sheet.
path is the http path to the animation sheet to layer over the rest - must be the same dimensions as your base animation sheet (duh).

removeLayer(path) :: can you guess what this does?

saveState( name ) :: saves a composite with all of the current layers. the name argument can be any string that makes sense to you.

revertToState( name ) :: use the same string you supplied for the name argument in saveState() to revert to the composite that was generated at that time.


--------------------------------------------

USE:
1. create a "plugins" directory within /impact/lib/, create a new file in this directory named 'layeredAnimation.js', copy/paste the plugin code below, then save the file.
2. In your entity class, include 'plugins.layeredAnimation' within .requires(), and use the addLayer() method to add additional layers over your base sprite sheet, i.e.:

this.animSheet = new ig.AnimationSheet( 'media/some-character.png', 64, 100 );

// save the base sprite sheet as a 'state':
this.animSheet.saveState( 1 );

// overlay a second sprite sheet over the first:
this.animSheet.addLayer( 'media/some-character-HELMET.png' );

// generate a composite with the current 'layers' that can be reverted to with revertToState():
this.animSheet.saveState( 2 );


Here is the plugin code:

ig.module(
	'plugins.layeredAnimation'
)
.requires(
	'impact.animation'
)
.defines( function()
{
	  
	ig.AnimationSheet.inject({


		init: function( path, width, height )
		{
			this.parent( path, width, height );
			
			this.copyImage = new ig.Image( path+'?nocache=true' );
			this.layers = [];
			this.states = [];
			this.numLayers = 0;
			this.numActiveLayers = 0;
			this.composited = false;
			this.forceRedraw = false;
			this.currentState = null;
			this.updateData = false;
		},
		
		
		addLayer: function( path )
		{
			var found = false;
			
			for(i=0; i < this.numLayers; i++) {
				if (this.layers[i].img.path == path) {
					this.layers[i].active = true;	
					found = true;
				}
			}
			if (!found) {
				this.layers.push({
					img: new ig.Image( path ),
					active: true
				});
				this.numLayers++;
			}
			this.composited = false;
			this.numActiveLayers++;
		},
		
		
		removeLayer: function( path )
		{
			var found = false,
				i;
			
			for(i=0; i < this.numLayers; i++) {
				if (this.layers[i].img.path == path) {
					this.layers[i].active = false;
					this.numActiveLayers--;
					this.composited = false;
					this.forceRedraw = true;
					found = true;
				}
			}
			
			return found;
		},
		
		
		saveState: function( name )
		{
			var i,
				state = {
				name: name,
				composited: false,
				data: null,
				layers: []
			};
			
			for(i=0; i < this.numLayers; i++) {
				if (this.layers[i].active) {
					state.layers.push( this.layers[i] );
				}
			}
			
			this.states.push( state );
			this.currentState = name;
		},
		
		
		revertToState: function( name )
		{
			var i, len;
			
			for(i=0, len = this.states.length; i < len; i++) {
				if (this.states[i].name == name) {
					this.updateData = this.states[i].data;
					this.layers = this.states[i].layers;
					this.currentState = name;
					
					return true;	
				}
			}
			
			return false;
		}
		
	});

	
	ig.Animation.inject({	
		
		
		update: function()
		{
			var layersLoaded = true,
				allStatesComposited = true,
				data,
				i,
				len;
			
			if (!this.sheet.composited && (this.sheet.forceRedraw || this.sheet.numLayers > 0)) {

				if (!this.sheet.image.loaded || !this.sheet.copyImage.loaded) {
					layersLoaded = false;
				} else {
					for(i=0; i < this.sheet.numLayers; i++) {
						if (!this.sheet.layers[i].img.loaded) {
							layersLoaded = false;
							break;
						}
					}
				}

				if (layersLoaded) {

					if (this.sheet.states.length == 0) {
						data = this.createComposite( this.sheet.layers );

						if (data) {
							this.sheet.composited = true;	
							this.sheet.image.data = data;
							this.sheet.forceRedraw = false;
						}
					} else {

						for(i=0, len=this.sheet.states.length; i < len; i++) {

							data = this.createComposite( this.sheet.states[i].layers );
							if (data) {
								this.sheet.states[i].composited = true;	
								this.sheet.states[i].data = data;
							} else {
								allStatesComposited = false;	
							}
						}
						
						if (allStatesComposited) {
							this.sheet.forceRedraw = false;
							this.sheet.composited = true;	
							this.sheet.image.data = data;
						}
					}
				}
			} else if (this.sheet.updateData) {

				this.sheet.image.data = this.sheet.updateData;
				this.sheet.updateData = false;
			}

			this.parent();
		},
		
		
		createComposite: function( layers )
		{
			var bufferCanvas = ig.$new( 'canvas' ),
				buffer = bufferCanvas.getContext( '2d' ),
				data,
				i,
				len;

			bufferCanvas.width = this.sheet.image.data.width;
			bufferCanvas.height = this.sheet.image.data.height;

			buffer.drawImage( this.sheet.copyImage.data, 0, 0 );
			
			for(i=0, len=layers.length; i < len; i++) {
				if (layers[i].active) {
					buffer.drawImage( layers[i].img.data, 0, 0 );
				}
			}

			return bufferCanvas;
		}


	});
	
});

1 decade ago by Xander

Whoa, this looks very sweet! Thanks for posting this. :)

1 decade ago by Bradley

Hm, something not quite right. Top layers just aren't matching up, as if the additional layers' sheets are the wrong dimensions, but I know they are. I have three sheets, each one is 488x400 pixels, 4 columns, 3 rows, for 12 total frames.

1 decade ago by monkeyArms

How can you have 3 rows if your height is 400? 400 divided by 3 is not a whole number.....

1 decade ago by Bradley

Oh, silly me!!! It works fine. I just needed to clear my cache. Awesome sauce!

1 decade ago by Bradley

Weltmeister, will not load a project when I use this. I get this error in Safari:

TypeError: 'undefined' is not a function (evaluating 'this.animSheet.addLayer( 'media/robot_helmet.png' )')

Here is a fix that gets me by for now:

if (this.animSheet.addLayer) this.animSheet.addLayer( 'media/robot_helmet.png' );

1 decade ago by monkeyArms

Quote from Bradley
Weltmeister, will not load a project when I use this....


I forgot to mention that you will need to include 'plugins.layeredAnimation' within requires() for any entity class that uses this - actually if you do that you don't need to require it in your main.js file. Initial post edited to reflect this - my bad.

1 decade ago by Bradley

Cool, that's all good now. Now I'm having a new problem. I get an error on line 66 when I have multiple entities using this plugin (instances of the same definition), when I attempt to remove a layer. It appears that this.sheet.numLayers is incorrect. I think it should be two, since I have added two layers to each of my robot entities, but instead, it is 12. I have 6 robots, so it appears that something is in the wrong scope, and so it is incrementing globally... I could be wrong, I'm still trying to understand your code.

1 decade ago by monkeyArms

Hmmm - how are you generating your robot entities? That should not happen if you are using spawnEntity() or placing the entities through weltmeister.

1 decade ago by Bradley

I am placing them in Weltmeister.

1 decade ago by monkeyArms

Can you share your entity code?

1 decade ago by Bradley

I could, but that might be a little complicated. Maybe I should build a test case and see if I can replicate the problem there.

1 decade ago by Bradley

Here's a simplified version of what I'm working with. The problem occurs when you have multiple robots and you call removeArm().


ig.module(
	'game.entities.robot'
)
.requires(
	'impact.entity',
	
	'plugins.layeredAnimation'
)
.defines(function(){
	
EntityRobot = ig.Entity.extend({
	
	size: {x: 50, y: 70},
	offset: {x: 26, y: 18},
	
	type: ig.Entity.TYPE.B, // Evil enemy group
	checkAgainst: ig.Entity.TYPE.A, // Check against friendly
	collides: ig.Entity.COLLIDES.PASSIVE,
	
	className:"EntityRobot",
		
	flip: false,
	
	speed: 40,
	
	numArms:2,
	
	animSheet: new ig.AnimationSheet( 'media/robot.png', 122, 86 ),
	
	init: function( x, y, settings ) {
		
		this.parent( x, y, settings );
		
		this.addAnim( 'hover', 0.08, [0] );
		this.addAnim( 'shoot', 0.06, [1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 5, 6, 6, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 10, 11], true );
		
		this.animSheet.addLayer( 'media/robot_helmet.png' );
		this.animSheet.addLayer( 'media/robot_arm.png' );
						
	},
	
	removeArm: function() {
		if (this.numArms == 2)
		{
			this.animSheet.removeLayer('media/robot_arm.png');
			this.numArms = 1;
		}
	},
	
	update: function() {
		// near an edge? return!
		if( !ig.game.collisionMap.getTile(
				this.pos.x + (this.flip ? +4 : this.size.x -4),
				this.pos.y + this.size.y+1
			)
		) {
			this.flip = !this.flip;
		}
		
		var xdir = this.flip ? -1 : 1;
		this.vel.x = this.speed * xdir;
		
		this.currentAnim.flip.x = this.flip;
		
		this.parent();
	},
	
	handleMovementTrace: function( res ) {
		this.parent( res );

		// collision with a wall? return!
		if( res.collision.x ) {
			this.flip = !this.flip;
		}
	},	
	
	check: function( other ) {
		other.receiveDamage( 10, this );
	}

});


});

1 decade ago by Bradley

Works well when you only have one robot; however, it looks like the other added layers briefly disappear when you remove the arm.

1 decade ago by monkeyArms

It looks like two issues: the first is a bug in my code - I will fix and post as soon as I can. the second is that you will need to declare the animation sheet within the entity's init() method, NOT as an object property, i.e.:

remove line 29:
animSheet: new ig.AnimationSheet( 'media/robot.png', 122, 86 ),

and replace it with this inside the init() method:

this.animSheet = new ig.AnimationSheet( 'media/robot.png', 122, 86 );

1 decade ago by alexandre

That looks like one sweet plugin, monkeyArms. Thanks for sharing.

1 decade ago by monkeyArms

@Bradley - I just updated the original post with a code fix - hope it works ok for you

1 decade ago by Bradley

You're a hero, btw. This makes Impact even more powerful.

1 decade ago by alexandre

Not sure what I'm doing wrong but I get nothing shown except base sheet. I did clear Chrome cache, made sure image sizes are all the same.

init: function (x, y, settings)
{
	this.parent (x, y, settings);

	// Setup the animations
	this.animSheet = new ig.AnimationSheet ('media/face.png', 8, 8);
	this.addAnim ('idle', 0.08, [0]);
	this.animSheet.addLayer('media/mouth.png');
	this.animSheet.addLayer('media/eyes.png');
	this.currentAnim = this.anims.idle;
},

1 decade ago by alexandre

Do you have a live demo of something that uses this plugin? I'll step through the code and see where I erred.

1 decade ago by monkeyArms

@alexandre - Are you getting an error in Chrome's console?

1 decade ago by alexandre

None whatsoever.

1 decade ago by alexandre

Stepping through the ig.Animation.update code injected by layeredAnimation, I see that, for my code posted above, animation's sheet.numLayers property equals 2.

1 decade ago by monkeyArms

you seem to be doing everything correctly - maybe try re-uploading the images?

1 decade ago by alexandre

Did that. No change. Hoping that at some point someone will post something online that uses this plugin. I'll validate with my browser, setup and code.

1 decade ago by alexandre

Okay found something. My canvas was scaled:
ig.main ('#canvas', Copymouse, 30, 64, 64, 2);

Changing that to
ig.main ('#canvas', Copymouse, 30, 64, 64, 1);

works.

Background map is 8 rows by 8 cols. Each tile sheet is 8*8. It should work.

1 decade ago by monkeyArms

Interesting - I never ran into any problems because all my tests were run with a scale of 1. I think I know what the issue is - I'll look into fixing that sometime this week.

Thanks for the feedback!

1 decade ago by alexandre

No, thank you ! This stuff will be very useful.

1 decade ago by Bradley

I've just noticed a significant drop in frame rate the instant layers are being added or removed. It is noticeable in Safari Mobile, and equally noticeable in Safari desktop version when the activity window is open.

I'm not dealing with very large sprite sheets.

1 decade ago by Arantor

That's because at the point of adding a layer, the image has to actually be loaded (as opposed to preloaded during the normal loader stage), and if the scale isn't 1, it's also got to be resized right there too.
Page 1 of 2
« first « previous next › last »