Skip to content

Instantly share code, notes, and snippets.

@ebhoren
Created October 10, 2017 01:36
Show Gist options
  • Save ebhoren/9c5901d3b246f2d029d7fec5cb279ab2 to your computer and use it in GitHub Desktop.
Save ebhoren/9c5901d3b246f2d029d7fec5cb279ab2 to your computer and use it in GitHub Desktop.
aframe-spritesheet component
/**
* A-Frame Spritesheet Component for A-Frame.
* Enables dynamic control of animation spritesheets
*/
AFRAME.registerComponent('sprite-sheet', {
schema: {
progress: { type: 'number', default: 0 },
frameName: { type: 'string', default: null },
firstFrame: { type: 'number', default: 0 },
lastFrame: { type: 'number', default: null },
dataJSON: { type: 'string', default: null },
debug: { default: false },
},
/**
* Called once when component is attached.
*/
init: function() {
this.mapCanvas = document.createElement( 'canvas' );
this.textureCtx = this.mapCanvas.getContext( '2d' );
this.texture = new THREE.Texture(this.mapCanvas);
this.parseJSONData(this.data.dataJSON);
// callback for when the image has loaded
this.el.addEventListener('materialtextureloaded', () => {
this.imageLoaded = true;
// save reference to the original image
this.spriteSheetImage = this.el.object3D.children[0].material.map.image;
this.el.object3D.children[0].material.map = this.texture;
this.setTextureRepeat();
this.adjustTexture(this.currentFrame);
});
this.currentFrame = 0;
},
/**
* Called when component is attached and when component data changes.
*/
update: function() {
// no actual animation
if (!this.framesData) return;
// if no last frame is specified use the number of available frames
let lastFrame = this.numFrames - 1;
if (this.data.frameName && this.frameNameToIndex) {
let frameIndex = this.frameNameToIndex[this.data.frameName];
if (frameIndex != null){
this.currentFrame = frameIndex;
this.resizeTextureForFrame(frameIndex);
this.setTextureRepeat();
}
else
console.warn(`Spritesheet error - No such frame with name ${this.data.frameName}`);
} else {
this.currentFrame = Math.round(this.data.progress * (lastFrame - this.data.firstFrame)) + this.data.firstFrame;
}
this.adjustTexture(this.currentFrame);
},
/**
* Called when a component is removed (e.g., via removeAttribute).
*/
remove: function() {
if( this.texture ) this.texture.dispose();
// Cleanup
this.mapCanvas = null;
this.textureCtx = null;
this.texture = null;
this.spriteSheetImage = null;
this.spriteSheetData = null;
this.framesData = null;
this.imageLoaded = null;
this.currentFrame = null;
this.frameNameToIndex = null;
this.numFrames = null;
this.frameWidth = null;
this.frameHeight = null;
this.lastDrawnFrame = null;
delete this.mapCanvas;
delete this.textureCtx;
delete this.texture;
delete this.spriteSheetImage;
delete this.spriteSheetData;
delete this.framesData;
delete this.imageLoaded;
delete this.currentFrame;
delete this.frameNameToIndex;
delete this.numFrames;
delete this.frameWidth;
delete this.frameHeight;
delete this.lastDrawnFrame;
},
parseJSONData: function(data) {
this.spriteSheetData = JSON.parse(data);
this.framesData = Object.keys(this.spriteSheetData.frames).map(key => {
return this.spriteSheetData.frames[key];
});
// create a dictionary to map from keyframe names to frame number
this.frameNameToIndex = {};
Object.keys(this.spriteSheetData.frames).map((key, index) => {
this.frameNameToIndex[key] = index;
return null;
});
this.numFrames = this.framesData.length;
this.resizeTextureForFrame(0);
},
resizeTextureForFrame: function(frame) {
this.frameWidth = this.framesData[frame].sourceSize.w;
this.frameHeight = this.framesData[frame].sourceSize.h;
this.mapCanvas.width = pow2ceil(this.frameWidth);
this.mapCanvas.height = pow2ceil(this.frameHeight);
},
setTextureRepeat: function() {
this.texture.repeat.set(
this.frameWidth / this.mapCanvas.width,
this.frameHeight / this.mapCanvas.height
);
this.texture.offset.x = 0;
this.texture.offset.y = 1 - this.frameHeight / this.mapCanvas.height;
},
/**
* Adjust the texture to a specific frame index
* @param {number} frameNum
*/
adjustTexture: function(frameNum) {
// image hasn't loaded, can't draw anything
if (!this.imageLoaded) return;
// no need to draw the same frame twice
if (this.lastDrawnFrame === frameNum) return;
this.adjustFrameBySpriteSheet(frameNum);
this.lastDrawnFrame = frameNum;
},
/**
* Adjust the spritesheet texture to a certain frame on a TexturePacker JSON spritesheet
* @param {number} frameNum
*/
adjustFrameBySpriteSheet: function(frameNum) {
let frameData = this.framesData[frameNum];
this.textureCtx.clearRect(0, 0, this.mapCanvas.width, this.mapCanvas.height);
if( this.data.debug === true ) {
this.textureCtx.fillStyle = 'green';
this.textureCtx.fillRect(0, 0, this.mapCanvas.width, this.mapCanvas.height);
}
this.textureCtx.save();
if (frameData.rotated) {
// drawing is rotated, so axes are rotated
let drawData = {
dX: this.frameHeight - (frameData.frame.h + frameData.spriteSourceSize.y),
dY: frameData.spriteSourceSize.x,
width: frameData.frame.h,
height: frameData.frame.w
};
this.textureCtx.rotate(-90 * Math.PI / 180);
this.textureCtx.translate(-this.frameHeight, 0);
this.textureCtx.drawImage(this.spriteSheetImage,
frameData.frame.x, frameData.frame.y, frameData.frame.h, frameData.frame.w,
drawData.dX,
drawData.dY,
drawData.width,
drawData.height
);
} else {
let drawData = {
dX: this.frameWidth/2 + (frameData.spriteSourceSize.x - frameData.sourceSize.w / 2),
dY: this.frameHeight/2 + (frameData.spriteSourceSize.y - frameData.sourceSize.h / 2),
width: frameData.frame.w,
height: frameData.frame.h
};
this.textureCtx.drawImage(this.spriteSheetImage,
frameData.frame.x, frameData.frame.y, frameData.frame.w, frameData.frame.h,
drawData.dX,
drawData.dY,
drawData.width,
drawData.height
);
}
this.textureCtx.restore();
this.texture.needsUpdate = true;
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment