Related
I'm currently using Phaser 3, although my question isn't technically restricted to that framework, as it's more of a general JS/canvas/maths question, but:
I have a line drawn with graphics(). It’s anchored at one end, and the other end is draggable. I made a quick demo and so far, so good - you can see what I have already on CodePen.
Dragging the marker around and redrawing the line is no problem, but what I’d like is for that line to have a maximum length of 100, so even if you’re still dragging beyond that point, the line would still follow the mouse, but not get any longer than 100. Dragging inside that maximum radius, the line would shrink as normal.
I’ve put together a visual that hopefully explains it:
The issue is that I suspect this is VERY MATHS and I am very, very weak with maths. Could anyone explain like I’m five what I need to do to my code to achieve this?
Edit: Adding code in a snippet here, as requested:
var config = {
type: Phaser.AUTO,
width: 800,
height: 400,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: {
preload: preload,
create: create,
update: update
}
};
var path;
var curve;
var graphics;
var game = new Phaser.Game(config);
function preload() {
this.load.spritesheet('dragcircle', 'https://labs.phaser.io/assets/sprites/dragcircle.png', { frameWidth: 16 });
}
function create() {
graphics = this.add.graphics();
path = { t: 0, vec: new Phaser.Math.Vector2() };
curve = new Phaser.Curves.Line([ 400, 390, 300, 230 ]);
var point0 = this.add.image(curve.p0.x, curve.p0.y, 'dragcircle', 0);
var point1 = this.add.image(curve.p1.x, curve.p1.y, 'dragcircle', 0).setInteractive();
point1.setData('vector', curve.p1);
this.input.setDraggable(point1);
this.input.on('drag', function (pointer, gameObject, dragX, dragY) {
gameObject.x = dragX;
gameObject.y = dragY;
gameObject.data.get('vector').set(dragX, dragY);
});
this.input.on('dragend', function (pointer, gameObject) {
let distance = Phaser.Math.Distance.Between(curve.p0.x, curve.p0.y, curve.p1.x, curve.p1.y);
console.log(distance);
});
}
function update() {
graphics.clear();
graphics.lineStyle(2, 0xffffff, 1);
curve.draw(graphics);
curve.getPoint(path.t, path.vec);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/phaser/3.55.2/phaser.min.js"></script>
You are right, you would need some math, but phaser has many helper functions, that will do the heavy lifting.
The main idea is, of this solution is
define a maxLength
get the the new point on drag, and create a real Phaser Vector2
here is some math is needed, to create the vector, just calculate destination point minus origin point
new Phaser.Math.Vector2(pointer.x - point0.x, pointer.y - point0.y) (origin point being the starting point of the desired vector, and destination point being the mouse pointer)
calculate the length of the created vector and compare it with the maxLength
if too long adjust the vector, with the handy function setLength (link to the documentation, this is where you would have needed math, but thankfully Phaser does it for us)
set the new coordinates for point1 and the curve endpoint
Here a quick demo (based on your code):
var config = {
type: Phaser.AUTO,
width: 500,
height: 170,
scene: {
preload: preload,
create: create,
update: update
}
};
var curve;
var graphics;
var game = new Phaser.Game(config);
function preload() {
this.load.spritesheet('dragcircle', 'https://labs.phaser.io/assets/sprites/dragcircle.png', { frameWidth: 16 });
}
function create() {
graphics = this.add.graphics();
curve = new Phaser.Curves.Line([ config.width/2, config.height - 20, config.width/2, 10 ]);
// define a length, could be a global constant
let maxLength = curve.p0.y - curve.p1.y;
var point0 = this.add.image(curve.p0.x, curve.p0.y, 'dragcircle', 0);
var point1 = this.add.image(curve.p1.x, curve.p1.y, 'dragcircle', 0).setInteractive();
this.input.setDraggable(point1);
// Just add for Debug Info
this.add.circle(curve.p0.x, curve.p0.y, maxLength)
.setStrokeStyle(1, 0xffffff, .5)
this.input.on('drag', function (pointer) {
let vector = new Phaser.Math.Vector2(pointer.x - point0.x, pointer.y - point0.y);
let distance = Phaser.Math.Distance.Between( point0.x, point0.y, pointer.x, pointer.y);
if(distance > maxLength){
vector.setLength(maxLength);
}
point1.x = point0.x + vector.x;
point1.y = point0.y + vector.y;
curve.p1.x = point1.x;
curve.p1.y = point1.y;
});
// NOT REALLY NEEDED
/*this.input.on('dragend', function (pointer, gameObject) {
let distance = Phaser.Math.Distance.Between(curve.p0.x, curve.p0.y, curve.p1.x, curve.p1.y);
console.log(distance);
});*/
}
function update() {
graphics.clear();
graphics.lineStyle(2, 0xffffff, 1);
curve.draw(graphics);
}
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Optional - Code Version using Phaser.GameObjects.Line:
This uses less code, and thanks to the Line GameObject (link to Documentation), you can directly use the vector to update the line, and also don't need the update function, graphics and so.
const config = {
type: Phaser.CANVAS,
width: 500,
height: 160,
scene: {
create
}
};
const game = new Phaser.Game(config);
const MAX_LINE_LENGTH = 100;
function create() {
let points = [ {x: config.width/2, y: config.height - 20}, {x: config.width/2, y: config.height - 120} ];
let point0 = this.add.circle(points[0].x, points[0].y, 6)
.setStrokeStyle(4, 0xff0000);
let point1 = this.add.circle(points[1].x, points[1].y, 6)
.setStrokeStyle(4, 0xff0000)
.setInteractive();
this.input.setDraggable(point1);
// Just add for Debug Info
this.add.circle(point0.x, point0.y, MAX_LINE_LENGTH)
.setStrokeStyle(1, 0xffffff, .5);
let line = this.add.line(points[0].x, points[0].y, 0, 0, 0, -100, 0x00ff00)
.setOrigin(0);
this.input.on('drag', function (pointer) {
let vector = new Phaser.Math.Vector2(pointer.x - point0.x, pointer.y - point0.y);
let distance = Phaser.Math.Distance.Between( point0.x, point0.y, pointer.x, pointer.y);
if(distance > MAX_LINE_LENGTH){
vector.setLength(MAX_LINE_LENGTH);
}
point1.x = point0.x + vector.x;
point1.y = point0.y + vector.y;
line.setTo(0, 0, vector.x, vector.y);
});
}
<script src="//cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
I have a canvas for the game world and a canvas for the display screen. I also have a polygon with nodes V(x,y) to serve as a viewport that follows the player and his rotation. I would like to know how to clip from the game world along the polygon, rotate and draw to the smaller canvas.`
//main looping function
var requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
//joystick setup
var leftManager = null;
var rightManager = null;
//precalculated math
var twoPi = Math.PI*2;
var halfPi = Math.PI/2;
var thirdOfCircleInRadians = twoPi/3;
//game canvas setup
var gameCvs = document.getElementById('gameCanvas');
gameCvs.width = 480;
gameCvs.height = 320;
//gameCvs.width - 960;
//gameCvs.height = 640;
var gameCtx = gameCvs.getContext("2d");
//game loop
var lastTime = 0;
function main() {
var now = Date.now();
var dt = lastTime==0? 0.016 : (now - lastTime) / 1000.0;
update(dt);
render(dt);
lastTime = now;
requestAnimFrame(main);
}
//collision class shorthand
var V = SAT.Vector;
var C = SAT.Circle;
var P = SAT.Polygon;
var R = new SAT.Response();
P.prototype.draw = function (ctx,type) {
ctx.save();
switch(type){
case 'van': ctx.fillStyle = "rgba(66, 66, 66, 0.5)"; break;
case 'col': ctx.fillStyle = "rgba(0, 0, 0, 1.0)"; break;
default: ctx.fillStyle = "rgba(0, 0, 0, 1.0)"; break;
}
ctx.translate(this.pos.x, this.pos.y);
ctx.beginPath();
var points = this.calcPoints;
ctx.moveTo(points[0].x, points[0].y);
var i = points.length;
while (i--) ctx.lineTo(points[i].x, points[i].y);
ctx.closePath();
//stroke to see through camera, when camera is not drawn use fill
ctx.stroke();
//ctx.fill();
ctx.restore();
};
//first for collisions, second for vanity. first is black, second is grey
var O = function(colPolygon,vanPolygon){
this.colPolygon = colPolygon;
this.vanPolygon = vanPolygon;
this.visible = false;
};
var objectVendor = function(type,position){
switch(type){
case 'tree':
return new O(new P(position,[
new V(10.5,19.5),
new V(20.5,9.5),
new V(23,-4),
new V(15,-16.5),
new V(-4,-19.5),
new V(-18,-14.5),
new V(-23,-0.5),
new V(-18.5,14.5),
new V(-8,20)
]),new P(position,[
new V(21,39),
new V(41,19),
new V(46,-8),
new V(30,-33),
new V(-8,-39),
new V(-36,-29),
new V(-46,-1),
new V(-37,29),
new V(-16,40)]));
break;
default: return false; break;
}
return false;
}
//Camera and Player Polygons
var cameraPoly = new P(new V(0,0),[
new V(-240,-160),
new V(240,-160),
new V(240,160),
new V(-240,160)
]);
var player = new P(new V(0,0),[
new V(5,2.5),
new V(7.5,2),
new V(7.5,-2),
new V(5,-2.5),
new V(-5,-2.5),
new V(-7.5,-2),
new V(-7.5,2),
new V(-5,2.5)
]);
//players start position on the screen, and starting angle, init velocity
player.pos = new V(240,160);
player.setAngle(1);
//players velocity for movement
player.vel = new V(0,0);
var world = {
objects: [],
visibleObjects: [],
worldCvs: null,
worldCtx: null,
init: function(){
//set up world canvas
this.worldCvs = document.createElement('canvas');
this.worldCvs.width = 480;
this.worldCvs.height = 480;
this.worldCtx = this.worldCvs.getContext("2d");
//populate world with stuff
this.objects.push(objectVendor('tree',new V(100,100)));
this.objects.push(objectVendor('tree',new V(150,200)));
this.objects.push(objectVendor('tree',new V(75,300)));
},
update: function(dt){
this.visibleObjects = [];
cameraPoly.setAngle(player.angle);
//cameraPoly.pos = player.pos;
cameraPoly.pos = new V(player.pos.x+(110*Math.cos(player.angle+halfPi)),player.pos.y+(110*Math.sin(player.angle+halfPi)));
//update objects to mark if they are in view
var i = this.objects.length;
while(i--){
if(SAT.testPolygonPolygon(this.objects[i].vanPolygon, cameraPoly, R)){
this.visibleObjects.push(this.objects[i]);
}
}
//}
},
draw: function(dt){
this.worldCtx.setTransform(1,0,0,1,0,0);
this.worldCtx.clearRect(0,0,this.worldCvs.width,this.worldCvs.height);
player.draw(this.worldCtx);
var i = this.visibleObjects.length;
while(i--){
this.visibleObjects[i].colPolygon.draw(this.worldCtx,'col');
this.visibleObjects[i].vanPolygon.draw(this.worldCtx,'van');
}
//for testing
cameraPoly.draw(this.worldCtx);
/*
this.worldCtx.save();
this.worldCtx.beginPath();
var i = cameraPoly.calcPoints.length;
this.worldCtx.moveTo(cameraPoly.calcPoints[0].x,cameraPoly.calcPoints[0].y);
while(i--){
this.worldCtx.lineTo(cameraPoly.calcPoints[i].x,cameraPoly.calcPoints[i].y);
}
this.worldCtx.clip();
this.worldCtx.restore();
*/
}
}
function render(dt){
gameCtx.setTransform(1,0,0,1,0,0);
gameCtx.clearRect(0,0,gameCvs.width,gameCvs.height);
world.draw();
//gameCtx.save();
//gameCtx.translate(cameraPoly.pos.x,cameraPoly.pos.y);
//gameCtx.translate(gameCtx.width/2,gameCtx.height/2);
//gameCtx.rotate(-player.angle+halfPi);
//gameCtx.translate(-world.worldCvs.width/2,-world.worldCvs.height/2);
gameCtx.drawImage(world.worldCvs,0,0);
//gameCtx.restore();
}
function update(dt){
world.update();
}
function init(){
//joystick setup
leftManager = nipplejs.create({
zone:document.getElementById("leftJoystick"),
color:"black",
size:75,
threshold:1.0,
position:{
top:"50%",
left:"50%"
},
mode:"static",
restOpacity:0.75,
});
rightManager = nipplejs.create({
zone:document.getElementById("rightJoystick"),
color:"black",
size:75,
threshold:1.0,
position:{
top:"50%",
right:"50%"
},
mode:"static",
restOpacity:0.75,
});
//joystick event setup
leftManager.get().on('move end', function(evt,data){
//console.log(evt);
//console.log(data);
});
rightManager.get().on('move end', function(evt,data){
//console.log(evt);
//console.log(data);
});
world.init();
main();
}
init();
`
I'm using libraries SAT.js and nipplejs.js currently.
Typically this is done in a little different of a way than you seem to be thinking of it. Instead of thinking about the viewport existing somewhere in the world, you should think about the viewport being fixed and the world being transformed behind it; you don't copy part of the world to the viewport, you draw the world offset and rotated by a certain amount, and only draw the parts that are inside the viewport. Matrices are an easy and common way to represent this transformation. You may want to read more about them here.
In practice, this would just amount to changing your existing call to worldCtx.setTransform() at the beginning of each draw frame. That link has information about how to calculate a good transform matrix, and you can find similar resources all over the place since it's pretty standard math.
In particular, you'll want to multiply a rotation and a translation matrix. Translation matrices are only possible if you use a matrix with higher-order than your coordinate space; for 2D, a 3x3 matrix, and for 3D, a 4x4 matrix. You could instead choose to just add some offset to your coordinates as you draw them, but worldCtx.setTransform already takes a matrix with a 3rd column for putting flat offsets into.
Changing the render function to the following will solve the problem, just rushing myself and didn't think things through very well.
`
function render(dt){
gameCtx.setTransform(1,0,0,1,0,0);
gameCtx.clearRect(0,0,gameCvs.width,gameCvs.height);
world.draw();
gameCtx.translate(gameCvs.width/2,gameCvs.height/2);
gameCtx.rotate(-player.angle+Math.PI);
gameCtx.translate(-cameraPoly.pos.x,-cameraPoly.pos.y);
gameCtx.drawImage(world.worldCvs,0,0);
}`
What this is doing is resetting any transformations on the context, clearing it for a new redrawing, creating the world canvas, translating to display center, rotating by the proper amount for reference point, translating to reference center point on negative axis to move game canvas proper amount so that drawing at 0,0 is in the correct location. Thank you for the reference material!
I had drawn an animation in canvas like this and rendered a map using openlayers4. I want to add this canvas to the map[openlayers canvas] in next step.
I had used ol.source.ImageCanvas add a boundary to openlayers, so I try to add the canvas with animation using ImageCanvas, but failed.
What's more, openlayers API said ol.source.ImageCanvas method only the image canvas can be added. I didn't know whether the animate canvas so does.
Should I insit on using ImageCanvas method or try others?
Can someone give me an example if I abandon the ImageCanvas method?
After some tries, I got a solution! Haha!
First: the ol.source.ImageCanvas can still use, but you will get a stopped animate just like a screenshot.
Second: must know the ol.map.render() in openlayers3 or openlayers4, whose description is:
Request a map rendering (at the next animation frame).
Thus, you can use it to refresh the map and get the next animation of canvas.
The following is snippets of my code:
var topoCanvas = function(extent, resolution, pixelRatio, size, projection) {
// topo features;
var features = topojson.feature(tokyo, tokyo.objects.counties);
var canvasWidth = size[0];
var canvasHeight = size[1];
var canvas = d3.select(document.createElement('canvas'));
canvas.attr('width', canvasWidth).attr('height', canvasHeight);
var context = canvas.node().getContext('2d');
var d3Projection = d3.geo.mercator().scale(1).translate([0, 0]);
var d3Path = d3.geo.path().projection(d3Projection);
var pixelBounds = d3Path.bounds(features);
var pixelBoundsWidth = pixelBounds[1][0] - pixelBounds[0][0];
var pixelBoundsHeight = pixelBounds[1][1] - pixelBounds[0][1];
var geoBounds = d3.geo.bounds(features);
var geoBoundsLeftBottom = ol.proj.transform(geoBounds[0], 'EPSG:4326', projection);
var geoBoundsRightTop = ol.proj.transform(geoBounds[1], 'EPSG:4326', projection);
var geoBoundsWidth = geoBoundsRightTop[0] - geoBoundsLeftBottom[0];
if (geoBoundsWidth < 0) {
geoBoundsWidth += ol.extent.getWidth(projection.getExtent());
}
var geoBoundsHeight = geoBoundsRightTop[1] - geoBoundsLeftBottom[1];
var widthResolution = geoBoundsWidth / pixelBoundsWidth;
var heightResolution = geoBoundsHeight / pixelBoundsHeight;
var r = Math.max(widthResolution, heightResolution);
var scale = r / (resolution / pixelRatio);
var center = ol.proj.transform(ol.extent.getCenter(extent), projection, 'EPSG:4326');
d3Projection.scale(scale).center(center).translate([canvasWidth / 2, canvasHeight / 2]);
d3Path = d3Path.projection(d3Projection).context(context);
d3Path(features);
context.stroke();
// above code is add a topoJson boundary to canvas
// below code is add an animation to canvas
var settings = createSettings(tokyo, {
width: canvasWidth,
height: canvasHeight
});
// reset the projection and bounds for animation canvas
settings.projection = d3Projection;
settings.bounds = geoBounds;
var mesh = buildMeshes(tokyo, settings);
when(render(settings, mesh, {
width: canvasWidth,
height: canvasHeight
})).then(function(masks) {
when(interpolateField(stations, data, settings, masks)).then(function(field) {
// wind moving animation
animate(settings, field, canvas);
// refresh the map to get animation
window.setInterval(function() {
map.render();
}, 50);
});
});
return canvas[0][0];
}
I'm quite new in html5 and three.js. I've been experimenting a bit with it, and basically what I want done is to have a Mesh (I'm using planeGeometry, as the tutorial I followed used it). The Mesh shows different Textures, which can change later on.
Here's what my code looks like:
angelTexture = THREE.ImageUtils.loadTexture("images/textures/chars/angel/angel.png");
angelTexture.offset.x = -0.75;
angelTexture.offset.y = -0.75;
angelMesh = new THREE.Mesh( new THREE.PlaneGeometry(79, 53, 79, 53), new THREE.MeshBasicMaterial( { map: angelTexture, wireframe: false } ));
angelMesh.position.x = 0;
angelMesh.position.y = 0;
scene.add(angelMesh);
The problem is that whenever I offset, the Mesh seems big enough to show all the other Sprites (I'm using the texture as a 2D Sprite that I offset to animate it). The result is quite disastrous and I am still figuring out how to control how big the Mesh is so that it shows only one snapshot of the Sprite. All my attempts seem only to resize the Mesh as well as the underlying Texture and still shows all the Sprites.
Can someone point me in the right direction? Thanks in advance.
...
My friend came up with a solution...
I missed the repeat property.
angelTexture = THREE.ImageUtils.loadTexture("images/textures/chars/angel/angel.png");
angelTexture.offset.x = -0.75;
angelTexture.offset.y = -0.75;
angelTexture.repeat.x = 0.25;
angelTexture.repeat.y = 0.25;
scene.add(angelMesh);
Hope this helps others having the same problem.
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;
}
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
To make frames move in the right direction use:
texture.offset.y = (1 - currentRow / _tilesVertical) - (1 / _tilesVertical);
instead of
texture.offset.y = currentRow / this.tilesVertical;
I want to draw image sprite using canvas.
The code not working. How to improve my code.
I have some Error.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var Game = {
draw_image: function(img, sourceX, sourceY, sourceW, sourceH, destX, destY, destW, destH){
var img = new Image(); // Create new img element
img.src = 'images/background.png'; // Set source path
img.onload = function(){
canvas.width = 1000;
canvas.height = 500;
ctx.drawImage(img, sourceX, sourceY, sourceW, sourceH, destX, destY, destW, destH);
};
}
var BACKGROUND = {
image_top: { x: 5, y: 5, w: 1280, h: 480 , dx:0 ,dy:0 ,dw:500 ,dh:500 },
image_body: { x: 5, y: 495, w: 1280, h: 480 , dx:0 ,dy:150 ,dw:500 ,dh:350},
image_bottom: { x: 5, y: 985, w: 1280, h: 480 , dx:0 ,dy:300 ,dw:500 ,dh:200 }
};
for(var n = 0 ; n < BACKGROUND.length ; n++) {
draw_image(nameImage, BACKGROUND[n].x,BACKGROUND[n].y, BACKGROUND[n].w, BACKGROUND[n].h, BACKGROUND[n].dx, BACKGROUND[n].dy, BACKGROUND[n].dw, BACKGROUND[n].dh );
}
};
To create a sprite animation it's important to know how it works.
You need your spritesheet make with pixel precision ( 1 pixel can mess up your animation ).
Like here, the character is always in the same size area, make it simple when you make your sprites.
With this you can make an object for each sprite you have like :
function Sprite(_position, _numberFrame, _framesize, _image, _duration){
this.position = _position; //Array like { x : 0, y : 0 }
this.rendersize = _rendersize; //Array like { width : 50, height : 80 }
this.framesize = _framesize; //Array like { width : 50, height : 80 }
this.image = _image; //Image object
this.chrono = new Chrono(_duration); //Explanation below
}
For more animation precision you can add a chrono who will manage the time of your animation :
function Chrono(_duration){
this.currentTime = 0;
this.lastTime = 0;
this.timeElapse = 0;
this.duration = _duration;
}
Chrono.prototype.countTime = function(){
this.currentTime = Date.now();
if(this.lastTime != 0)
this.timeElapse += this.currentTime - this.lastTime;
this.lastTime = Date.now();
if(this.timeElpase >= this.duration && this.lastTime != 0){
this.timeElapse = 0;
return TRUE;
} else {
return FALSE;
}
}
Then the function to animate your sprite may like :
Sprite.prototype.render = function(){
if(this.position.x <= this.image.width && this.chrono.countTime()){
this.position.x += this.framesize.x;
} else {
this.position.x = 0;
}
ctx.drawImage(this.image,
this.position.x, this.position.y,
this.framesize.width, this.framesize.height,
this.rendersize.width, this.rendersize.height
);
}
I hope I was clear and helpful,
Cheers
PS: Comments for question or optimisation ideas
You have to many problems with this code to make this sprite animation works. I wouldn't go to point any of the problems with your code, but I highly recommend to read a little bit about functions and variable scope before try to write this kind of code.
Another simple (and best for newbies) solution can be to use a canvas framework as EaselJS, with this you can do something like this to animate an sprite:
var data = {
images: ["images/background.png"],
frames: {width:50, height:50},
animations: {run:[0,4], jump:[5,8,"run"]}
};
var animation = new createjs.BitmapAnimation(data);
animation.gotoAndPlay("run");