CraftyJS ignores polygon onHit - javascript

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');
}
);

Related

Save mouse position in a state with react

I need to save the mouse position in an array for an application, but the positions doesn't save in the array, it just replace it and I don't know why, someone help please.
Here is the code:
const mouseXY = async e =>{
setCoords([...coords,{
x:e.clientX,
y:e.clientY
}])
}
useEffect(()=>{
let interval = null;
interval = setInterval(() => {
setTime((prevTime) => prevTime + 0.5);
}, 1000);
window.addEventListener('mousemove', mouseXY);
}
When I print I get this:
[{x: 379, y: 106}]
A single element in an Array.
What I want to get would be something like this:
[{x: 379, y: 106},
{x: 379, y: 106},
{x: 379, y: 106},
{x: 379, y: 106},
{x: 379, y: 106},
{x: 379, y: 106},
{x: 379, y: 106}]
with the position of the mouse around the page.
It is because of staleState. coords was an empty array at the time that mouseXy was defined and handed to addEventListener, and it will always be an empty array.
Instead, update your setCoords call to use the callback pattern.
const mouseXY = (e) => {
setCoords((prevState) => [...prevState, { x: e.clientX, y: e.clientY }]);
};

Bug report: Wrong position of the UIElement / ChartMarkerXY when first rendering a chart with animation disabled

I've tried to place an UIElement / ChartMarkerXY on a chart where the animations are disabled.
Unfortunately, the UIElement / ChartMarkerXY was not in the right position on the first render.
However, when we do an another render (eg: when you put the mouse on the chart for show the AutoCursor), it has been moved to the right position.
I noticed that when the animations are enabled, the first frame of the animation put the UIElement / ChartMarkerXY at the same position.
Currently, I have a workaround for prevent this: chart.engine.renderFrame(0,0)
Demonstration of the bug
You can find in the snippet an example that reproduce the bug:
const {
AxisPosition,
AxisTickStrategies,
emptyLine,
lightningChart,
Point,
UIElementBuilders,
emptyFill
} = lcjs;
const dataSeries = [
{ x: -5, y: 0 },
{ x: 2, y: -5 },
{ x: 5, y: 6 },
{ x: 8, y: 12 },
{ x: 12, y: -12 }
];
const chart = lightningChart().ChartXY({
container: 'chart',
});
chart.setAnimationsEnabled(false);
chart.addLineSeries().add(dataSeries);
chart.addUIElement(UIElementBuilders.TextBox, {
x: chart.getDefaultAxisX(),
y: chart.getDefaultAxisY()
})
.setText('My Text')
.setPosition({ x: 0, y: 0 });
.lcCanvas {
width: 500px;
height: 300px;
}
<script src="https://unpkg.com/#arction/lcjs#3.1.0/dist/lcjs.iife.js"></script>
<h2>Example of the bug</h2>
<div id="chart" class="lcCanvas"></div>
Bug reports should be sent to support#lightningchart.com instead of posting here.
-- Snekw
Bug report sent.
Currently, the workaround I found is chart.engine.renderFrame(0,0).
This will force the re-render of the chart, and put the marker at the right position.
Here, a snippet of how implement the workaround.
const {
AxisPosition,
AxisTickStrategies,
emptyLine,
lightningChart,
Point,
UIElementBuilders,
emptyFill
} = lcjs;
const dataSeries = [
{ x: -5, y: 0 },
{ x: 2, y: -5 },
{ x: 5, y: 6 },
{ x: 8, y: 12 },
{ x: 12, y: -12 }
];
const chart = lightningChart().ChartXY({
container: 'chart',
});
chart.setAnimationsEnabled(false);
chart.addLineSeries().add(dataSeries);
chart.addUIElement(UIElementBuilders.TextBox, {
x: chart.getDefaultAxisX(),
y: chart.getDefaultAxisY()
})
.setText('My Text')
.setPosition({ x: 0, y: 0 });
chart.engine.renderFrame(0, 0); // Workaround: Add this for force the re-render
.lcCanvas {
width: 500px;
height: 300px;
}
<script src="https://unpkg.com/#arction/lcjs#3.1.0/dist/lcjs.iife.js"></script>
<h2>Workaround</h2>
<div id="chart" class="lcCanvas"></div>

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

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>

prevent looping in kineticjs sprite animation

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

Categories