prevent looping in kineticjs sprite animation - javascript

I'm developing a board game with kineticjs, where a pice is animated simultaneously in 2 ways:
1 - animate with a tween the x and y coordinates, from place a to b. i do this without a problem, getting input from user.
2 - in a sprite, we should see the piece being lift up (frames 0 > 4), and staying up during the previous animation, and in the end of it, it animates from 4 to 8, and goes back goes to the initial idle state
So far I have this:
var animations = {
idle: [{x: 0, y: 0, width: 100, height: 100}],
up: [{x: 0, y: 0, width: 100, height: 100},
{x: 100, y: 0, width: 100, height: 100},
{x: 200, y: 0, width: 100, height: 100},
{x: 300, y: 0, width: 100, height: 100}],
down: [{x: 400, y: 0, width: 100, height: 100},
{x: 500, y: 0, width: 100, height: 100},
{x: 600, y: 0, width: 100, height: 100},
{x: 700, y: 0, width: 100, height: 100}]
};
/* coordinates_js is an array of the possible x and y coordinates for the player. like this, the player is created in the first coordinate, the starting point
*/
var player = new Kinetic.Sprite({
x: coordinates_js[0].x,
y: coordinates_js[0].y,
image: imageObj,
animation: 'idle',
animations: animations,
frameRate: 7,
index: 0,
scale: 0.4,
id: "player"
});
imageObj.onload = function() {
var targetx = null;
var targety = null;
var targetIndex = null;
var playerIndex = 0;
document.getElementById('enviar').addEventListener('click', function() {
targetIndex = document.getElementById("targetIndex").value;
var destino = coordinates_js[targetIndex];
var tween = new Kinetic.Tween({
node: player,
x: destino.x,
y: destino.y,
rotation: 0,
duration: 5
}).play();
console.log("x: " + destino.x + "y: " + destino.y)
playerIndex = targetIndex;
tween.play();
player.setAnimation("up");
player.afterFrame(3, function(){
console.log("idle")
player.setAnimation("idle");
})
}, false);
playLayer.add(player);
stage.add(playLayer);
player.start();
}
the proble here is that the sprite animation plays from 1 to 4 and goes to idle; i neet it to stay up until the end of the tween. i could have:
player.setAnimation("up");
player.afterFrame(3, function(){
console.log("idle")
player.setAnimation("idle");
})
this lifts up the piece, but wont let it drop. So, how can i do like in flash gotoAndPlay("up") to start, and in the end of the tween gotoAndStop("idle").
many thanks to you all

Have you seen the onFinish event for tweens?
http://www.html5canvastutorials.com/kineticjs/html5-canvas-transition-callback-with-kineticjs/
You could do something like this:
var tween = new Kinetic.Tween({
node: player,
x: destino.x,
y: destino.y,
rotation: 0,
duration: 5,
onFinish: function() {
player.setAnimation("idle"); //When tween finishes, go back to "idle" animation
}
}).play(); //Play the Tween
player.setAnimation("up"); //Start the "up" animation for the sprite

Related

Is there a better way to repeat display multiple images in the same spot?

I'm creating a Minigame using Canvas HTML5 and am trying to display LEDs on top of another image, currently it works like this: Image displayed
Currently, the Image does display and change as intended however the function is not initialised straight away (i'm guessing this is due to Javascript running on one core and how the setInterval function works) the code also seems really clunky and long-winded.
Is there a better way to achieve looping of these images to form an animation?
I intend to add more animations as the minigame is 'idle' and ideally the function controlling the looping of images should be easily broken.
var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
function drawSafeBuster(imageSources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
// get number of images
for (var src in imageSources) {
numImages++;
}
for (var src in imageSources) {
images[src] = new Image();
images[src].onload = function () {
if (++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = imageSources[src];
}
}
//Image path variables.
var imageSources = {
ledPath: './graphics/leds_safe_dial_minigame.png'
};
drawSafeBuster(imageSources, function (images) {
//Draw initial LED images.
context.drawImage(images.ledPath, 2, 0, 115, 100, 850, 300, 120, 100);
context.drawImage(images.ledPath, 2, 0, 115, 100, 1015, 300, 120, 100);
//LED Animation Loop
var ledRepeat = setInterval(function () {
context.fillStyle = '#999999';
var ledRepeat1 = setInterval(function () {
context.fillRect(850, 300, 120, 45);
context.fillRect(1015, 300, 120, 45);
context.drawImage(images.ledPath, 2, 0, 115, 100, 850, 300, 120, 100);
context.drawImage(images.ledPath, 2, 0, 115, 100, 1015, 300, 120, 100);
}, 500);
var ledRepeat2 = setInterval(function () {
context.fillRect(850, 300, 120, 45);
context.fillRect(1015, 300, 120, 45);
context.drawImage(images.ledPath, 120, 0, 115, 100, 850, 300, 120, 100);
context.drawImage(images.ledPath, 120, 0, 115, 100, 1015, 300, 120, 100);
}, 1500);
var ledRepeat3 = setInterval(function () {
context.fillRect(850, 300, 120, 45);
context.fillRect(1015, 300, 120, 45);
context.drawImage(images.ledPath, 238, 0, 115, 100, 850, 300, 120, 100);
context.drawImage(images.ledPath, 238, 0, 115, 100, 1015, 300, 120, 100);
}, 2500);
var clearInterval = setInterval(function () {
clearInterval(ledRepeat1);
clearInterval(ledRepeat2);
clearInterval(ledRepeat3);
}, 3500);
}, 4500);
});
}
I would suggest making each LED an object with its own state (on/off). Create a game object to track the game state and current time/tick. (Here's some light reading about game loops)
Not sure about your exact requirements, but here's an example of how I would approach something similar. In the ideal world each requestAnimationFrame() is 1/60th of a second ~ 60 frames per second... See the above link for why this may not be case and how to correct for it.
I've not used images for LEDs but this could be added to the LED object and used in the draw function.
let canvas, c, w, h,
TWOPI = Math.PI * 2;
canvas = document.getElementById('canvas');
c = canvas.getContext('2d');
w = canvas.width = 600;
h = canvas.height = 400;
let game = {
state: "RUNNING",
tick: 0,
actors: []
};
//LED object.
let LED = function(x, y, hue, radius, on, toggleRate) {
this.position = {
x: x,
y: y
};
this.hue = hue;
this.radius = radius;
this.on = on;
this.toggleRate = toggleRate;
this.update = function(tick) {
if (tick % this.toggleRate === 0) {
this.on = !this.on;
}
};
this.draw = function(ctx) {
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.radius, 0, TWOPI, false);
ctx.fillStyle = `hsl(${this.hue}, ${this.on ? 80 : 20}%, ${this.on ? 70 : 30}%)`;
ctx.fill();
ctx.beginPath();
ctx.arc(this.position.x + this.radius / 5, this.position.y - this.radius / 5, this.radius / 3, 0, TWOPI, false);
ctx.fillStyle = `hsl(${this.hue}, ${this.on ? 80 : 20}%, ${this.on ? 90 : 50}%)`;
ctx.fill();
};
}
//create LEDs
for (let i = 0; i < 10; i++) {
game.actors.push(
new LED(
100 + i * 25,
100,
i * 360 / 10,
8,
Math.random() * 1 > 0.5 ? true : false,
Math.floor(Math.random() * 240) + 60
)
);
}
function update() {
if (game.state === "RUNNING") {
//increase game counter
game.tick++;
//update actors
for (let a = 0; a < game.actors.length; a++) {
game.actors[a].update(game.tick);
}
} else {
//noop.
}
}
function clear() {
c.clearRect(0, 0, w, h);
}
function draw() {
//draw all actors
for (let a = 0; a < game.actors.length; a++) {
game.actors[a].draw(c);
}
}
function loop() {
update();
clear();
draw();
requestAnimationFrame(loop);
}
canvas.addEventListener('click', function() {
if (game.state === "RUNNING") {
game.state = "PAUSED";
} else {
game.state = "RUNNING";
}
console.log(game.state);
});
requestAnimationFrame(loop);
body {
background: #222;
}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
For the best quality always render to the canvas via requestAnimationFrame's callback. Rendering using timers can result in flickering and or shearing of the animation.
setTimeout or setInterval are throttled by most browsers when the page is not in focus or visible. Nor are the callbacks always on time
If timing is important to 1/60th second use performance.now and the time argument passed to the requestAnimationFrame callback. You will never be precisely on time as the animation is only displayed every 1/60th second (16.66666...ms) so draw the next frame of the animation as soon as possible after the time required (see example)
From the information you gave in the question I could not workout what your animation looked like so I made something up as an example.
Example
Uses promise rather than callback when loading media
Image has property added that represents the position of sub images (sprites). The function drawSprite takes the imageName, spriteIdx, and locationName to draw a sub image at a location on the canvas.
The main render loop is the function renderLoop it waits until media is loaded and then uses the timing to do the animation.
The array timing contains an object for each animation event. The object has the time offset of the event, the function to be called on that event, and the arguments passed to the function.
The last timing object in this example, just resets the start time to repeat the animation.
Note that this example does not check if the browser stops the animation and will cycle through all animations to catch up.
requestAnimationFrame(renderLoop);
canvas.width = 64;
canvas.height = 16;
const ctx = canvas.getContext("2d");
var mediaLoaded = false;
const images = {};
var currentStage = 0;
var startTime; // do not assign a value to this here or the animation may not start
loadMedia({
leds: {
src: "https://i.stack.imgur.com/g4Iev.png",
sprites: [
{x: 0, y: 0, w: 16, h: 16}, // idx 0 red off
{x: 16, y: 0, w: 16, h: 16}, // idx 1 red on
{x: 0, y: 16, w: 16, h: 16}, // idx 2 orange off
{x: 16, y: 16, w: 16, h: 16}, // idx 3 orange on
{x: 0, y: 32, w: 16, h: 16}, // idx 4 green off
{x: 16, y: 32, w: 16, h: 16}, // idx 5 green on
{x: 0, y: 48, w: 16, h: 16}, // idx 6 cyan off
{x: 16, y: 48, w: 16, h: 16}, // idx 7 cyan on
]
},
}, images)
.then(() => mediaLoaded = true);
const renderLocations = {
a: {x: 0, y: 0, w: 16, h: 16},
b: {x: 16, y: 0, w: 16, h: 16},
c: {x: 32, y: 0, w: 16, h: 16},
d: {x: 48, y: 0, w: 16, h: 16},
};
function loadMedia(imageSources, images = {}) {
return new Promise(allLoaded => {
var count = 0;
for (const [name, desc] of Object.entries(imageSources)) {
const image = new Image;
image.src = desc.src;
image.addEventListener("load",() => {
images[name] = image;
if (desc.sprites) { image.sprites = desc.sprites }
count --;
if (!count) { allLoaded(images) }
});
count ++;
}
});
}
function drawSprite(imageName, spriteIdx, locName) {
const loc = renderLocations[locName];
const spr = images[imageName].sprites[spriteIdx];
ctx.drawImage(images[imageName], spr.x, spr.y, spr.w, spr.h, loc.x, loc.y, loc.w, loc.h);
}
function drawLeds(sprites) {
for(const spr of sprites) { drawSprite(...spr) }
}
function resetAnimation() {
currentStage = 0;
startTime += 4500;
}
const timing = [
{time: 0, func: drawLeds, args: [[["leds", 0, "a"], ["leds", 2, "b"], ["leds", 4, "c"], ["leds", 6, "d"]]]},
{time: 500, func: drawLeds, args: [[["leds", 1, "a"]]]},
{time: 1500, func: drawLeds, args: [[["leds", 0, "a"], ["leds", 3, "b"]]]},
{time: 2000, func: drawLeds, args: [[["leds", 1, "a"]]]},
{time: 3000, func: drawLeds, args: [[["leds", 0, "a"], ["leds", 2, "b"], ["leds", 5, "c"], ["leds", 7, "d"]]]},
{time: 3250, func: drawLeds, args: [[["leds", 1, "d"]]]},
{time: 3500, func: drawLeds, args: [[["leds", 3, "d"]]]},
{time: 3750, func: drawLeds, args: [[["leds", 5, "d"]]]},
{time: 4000, func: drawLeds, args: [[["leds", 7, "d"]]]},
{time: 4250, func: drawLeds, args: [[["leds", 1, "d"]]]},
{time: 4500 - 17, func: resetAnimation, args: []},
];
function renderLoop(time) {
if (mediaLoaded) {
if (startTime === undefined) { startTime = time }
var offsetTime = time - startTime;
const stage = timing[currentStage];
if(offsetTime > stage.time) {
currentStage++;
stage.func(...stage.args);
}
}
requestAnimationFrame(renderLoop);
}
canvas {
border:1px solid black;
}
<canvas id="canvas"></canvas>
Image used in example

How should I place an object using Matter.Object.create in matter.js?

I'm using matter.js and I have vertex data for objects with absolute positions (from SVG files). When I try to place them on the map, they all end up positioned around 0, 0. This is easily observable with this code snippet:
<body>
<script src="matter-0.8.0.js"></script>
<script>
var engine = Matter.Engine.create(document.body);
var object = Matter.Body.create(
{
vertices: [ { x: 300, y: 300 }, { x: 200, y: 300 }, { x: 350, y: 200 } ],
isStatic: true
});
Matter.World.add(engine.world, [object]);
Matter.Engine.run(engine);
</script>
</body>
Or just look at this jsfiddle: https://jsfiddle.net/vr213z4u/
How is this positioning supposed to work? Do I need to normalize the absolute position data into relative positions and use the position attribute of the options sent to Matter.Object.create? I tried that, but all objects got a little displaced, probably due to my lack of understanding.
Update/clarification:
My goal is that the vertices of the created object end up on exactly on the coordinates they have in the source data.
At body creation the vertices are re-orientated about their center of mass and then translated to the given body.position. As you've not passed a position vector here this just defaults to (0, 0).
Try something like this:
var body = Matter.Body.create({
position: { x: 500, y: 500 },
vertices: [{ x: 300, y: 300 }, { x: 200, y: 300 }, { x: 350, y: 200 }],
isStatic: true
});
If you want the position to be absolute:
var vertices = [{ x: 300, y: 300 }, { x: 200, y: 300 }, { x: 350, y: 200 }];
var body = Matter.Body.create({
position: Matter.Vertices.centre(vertices),
vertices: vertices,
isStatic: true
});

custom point to point path with bezier curve animation

I wanna create a moving car object that animates from point-A to point-B, point-B to point-C, and point-C to point-D. I'm using the "jQuery.Path.js" with bezier curve function but it only has 2 points to setup (start & end). How can I add 2 more points in between?? please help!
here's the original js page: JQUERY PATH BEZIER CURVE GENERATOR
<script type="text/javascript" src="js/jquery.path.js"></script>
<script type="text/javascript">
function animate(){
var path = {
start: {
x: 1200,
y: 445,
},
second: {
x: 890,
y: 520,
},
third: {
x: 650,
y: 600,
angle: 20,
length: 0.2
},
end: {
x: -100,
y: 470,
}
};
$('.car').animate(
{
path : new $.path.bezier(path)
},
20000,
animate
);
}
animate();
</script>

CraftyJS ignores polygon onHit

i've some problems with craftyjs, collision and polygons.
Here is my code: http://jsfiddle.net/haenx/85mhj/
As you can see, the player will hit the enemy each time both boxes are overlapping. But the given polygon has another form and it semms it will be ignored...
I can't figure out, why. The docs from craftys are also not the best.
Crafty.init(300,300, document.querySelector('#Game'));
Crafty.e('2D, Canvas, Color, Mouse')
.attr({
x: 0,
y: 0,
w: 300,
h: 300
})
.color('#333')
.bind('MouseMove', function () {
player.x = Crafty.mousePos.x - (32 / 2);
player.y = Crafty.mousePos.y - (32 / 2);
});
Crafty.c('Enemy', {
init: function () {
this
.addComponent('2D, Canvas, Color')
.attr({
x: 134,
y: 134,
w: 32,
h: 32
})
.color('#ff0');
}
})
var enemy = Crafty.e('Enemy');
var player = Crafty.e('2D, Canvas, Color, Collision, WiredHitBox')
.attr({
x: 134,
y: 134,
w: 32,
h: 32
})
.color('#f00');
player
.collision(new Crafty.polygon([0,0], [0,6], [8, 13], [24,13], [32,6], [32,0]))
.onHit('Enemy',
function() {
enemy.color('#fff');
},
function() {
enemy.color('#ff0');
}
);

Uncaught TypeError: Object #<Object> has no method 'transitionTo'

After upgrading kineticjs from 4.0.5 to 4.5.1, I get
Uncaught TypeError: Object #<Object> has no method 'transitionTo'
Following code works with the previous version:
gameLayer.transitionTo({
duration: '0.5',
opacity: 1,
callback: function() {
intervalId = setInterval(reDraw, 33);
isInteractive = true;
}
});
Whats the the alternative function for transitionTo in 4.5.1
UPDATE
I opened an issue over Github, according to the guy transitionTo has been removed and it is replaced by Tween
Regards,
You can use the onFinish attribute as below :
var tween = new Kinetic.Tween({
node: rect,
duration: 1,
x: 400,
y: 30,
rotation: Math.PI * 2,
opacity: 1,
strokeWidth: 6,
scaleX: 1.5,
onFinish: function() {console.log('onFinish');}
});
The alternative is TweenLite. It has much more functionalities than the classic Kinetic transitions so they have been deprecated and TweenLite is fully adapted to KineticJS shapes.
Here is a tutorial that shows us how to use these transitions.
http://www.html5canvastutorials.com/kineticjs/html5-canvas-linear-transition-tutorial-with-kineticjs/
var stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 200
});
var layer = new Kinetic.Layer();
var rect = new Kinetic.Rect({
x: 100,
y: 100,
width: 100,
height: 50,
fill: 'green',
stroke: 'black',
strokeWidth: 2,
opacity: 0.2
});
layer.add(rect);
stage.add(layer);
var tween = new Kinetic.Tween({
node: rect,
duration: 1,
x: 400,
y: 30,
rotation: Math.PI * 2,
opacity: 1,
strokeWidth: 6,
scaleX: 1.5
});
// start tween after 20 seconds
setTimeout(function() {
tween.play();
}, 2000);

Categories