Three.js Using 2D texture\sprite for animation (planeGeometry)
I've noted in my comment to Lee Stemkoski that spritesheets that have more than one row do not work the same when using the newer THREE.TextureLoader()
.
I am using the following 4x4 sprite image in my tests.
With no modification to Lee Stemkoski's TextureAnimator
function, assuming you have a full 16 tile spritesheet.
var texture = new THREE.TextureLoader().load('grid-sprite.jpg');
var annie = new TextureAnimator(texture, 4, 4, 16, 150);
The animated texture runs backwards. Codepen Demo
So I made my own which I call ððð THREE.SpriteSheetTexture
ððð
THREE.SpriteSheetTexture = function(imageURL, framesX, framesY, frameDelay, _endFrame) {
var timer, frameWidth, frameHeight,
x = 0, y = 0, count = 0, startFrame = 0,
endFrame = _endFrame || framesX * framesY,
CORSProxy = 'https://cors-anywhere.herokuapp.com/',
canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
canvasTexture = new THREE.CanvasTexture(canvas),
img = new Image();
img.crossOrigin = "Anonymous"
img.onload = function(){
canvas.width = frameWidth = img.width / framesX;
canvas.height = frameHeight = img.height / framesY;
timer = setInterval(nextFrame, frameDelay);
}
img.src = CORSProxy + imageURL;
function nextFrame() {
count++;
if(count >= endFrame ) {
count = 0;
};
x = (count % framesX) * frameWidth;
y = ((count / framesX)|0) * frameHeight;
ctx.clearRect(0, 0, frameWidth, frameHeight);
ctx.drawImage(img, x, y, frameWidth, frameHeight, 0, 0, frameWidth, frameHeight);
canvasTexture.needsUpdate = true;
}
return canvasTexture;
}
And what you need to know about it
imageURL
is the URL of your spritesheet
framesX
is how many frames fit along the x axis (left and right)
framesY
is how many frames fit along the y axis (up and down)
delay
is how long it the texture waits to change to the next frame
_endFrame
is optional - How many frames are there (in case it doesnt use a full row)
That all looks something like this
texture = new THREE.SpriteSheetTexture('https://s3-us-west-2.amazonaws.com/s.cdpn.io/68819/grid-sprite.jpg', 4, 4, 100, 16);
var material = new THREE.MeshBasicMaterial({
map: texture
});
geometry = new THREE.BoxGeometry( 200, 200, 200 );
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
And there was much rejoicing!!! Codepen Demo Here
@Cmndo to make frames flow moves in the right order you just need to update this:
texture.offset.y = currentRow / this.tilesVertical;
to this:
texture.offset.y = this.tilesVertical - (currentRow / this.tilesVertical);
In this example: https://github.com/stemkoski/stemkoski.github.com/blob/master/Three.js/Texture-Animation.html
I had the same question a while ago, and so I have written up a complete example of animating using a spritesheet as the texture for a PlaneGeometry, and then updating the texture at regular intervals -- check out the example at
http://stemkoski.github.io/Three.js/Texture-Animation.html
and view the commented source code for additional explanation.
Update (2021):
Here is an updated version of the function I recommend using. It fixes the issue with the incorrect tile display order, it automatically updates the next frame, and it returns an object you can use to stop and re-start the animation as desired.
function TextureAnimator(texture, tilesHoriz, tilesVert, tileDispDuration)
{
let obj = {};
obj.texture = texture;
obj.tilesHorizontal = tilesHoriz;
obj.tilesVertical = tilesVert;
obj.tileDisplayDuration = tileDispDuration;
obj.numberOfTiles = tilesHoriz * tilesVert;
obj.texture.wrapS = THREE.RepeatWrapping;
obj.texture.wrapT = THREE.RepeatWrapping;
obj.texture.repeat.set( 1/tilesHoriz, 1/tilesVert );
obj.currentTile = 0;
obj.nextFrame = function()
{
obj.currentTile++;
if (obj.currentTile == obj.numberOfTiles)
obj.currentTile = 0;
let currentColumn = obj.currentTile % obj.tilesHorizontal;
obj.texture.offset.x = currentColumn / obj.tilesHorizontal;
let currentRow = Math.floor( obj.currentTile / obj.tilesHorizontal );
obj.texture.offset.y = obj.tilesVertical - currentRow / obj.tilesVertical;
}
obj.start = function()
{ obj.intervalID = setInterval(obj.nextFrame, obj.tileDisplayDuration); }
obj.stop = function()
{ clearInterval(obj.intervalID); }
obj.start();
return obj;
}