Purpose
I'm making a simple "shoot the word" game, where user needs to click on some moving rectangles with words to "shoot" them.
Problem
So i create some objects and move them using simple kinetic.js tweening.
Word creation
function createWord(value){
//here comes some word object construction
var wordGroup = new Kinetic.Group({
x: 0,
y: 0
});
var padding = 10;
wordGroup.label = new Kinetic.Text({
x: padding,
y: padding,
text: value,
fontFamily: 'Times New Roman',
fontSize: 30,
fill: 'white'
});
wordGroup.tag = new Kinetic.Rect({
x: 0,
y: 0,
width: wordGroup.label.width() + (padding << 1),
height: wordGroup.label.height() + (padding << 1),
fill: 'black',
shadowColor: 'black',
shadowBlur: 10,
shadowOffset: {x:10,y:20},
shadowOpacity: 0.5,
cornerRadius: 10
});
wordGroup.add(wordGroup.tag);
wordGroup.add(wordGroup.label);
wordGroup.shoot = function(){ //shooting mechanism (simple stop from moving and remove from scene)
wordGroup.tween.pause();
wordGroup.clean();
dropNextWord(); //drops fresh blood! (new word instead of shooted)
}
wordGroup.clean = function(){ //remove from scene and set it free to drop again
wordGroup.remove();
wordGroup.isActive = false;
}
wordGroup.move = function(callback){ //animates word
wordLayer.add(wordGroup);
moveToSide(wordGroup, callback); //calls moving function
}
wordGroup.on('click', function(e){
wordGroup.shoot();
});
return wordGroup;
}
Tweening part
//move word to opposite side
function moveToSide(word, callback){
var side = Math.random();
var d = 100;
spawnFromSide(word, side); //set random side word position
tweenPosition = {
x: word.x(),
y: word.y()
}
if(side < 0.25){ //left
tweenPosition.x = - d;
} else if(side > 0.25 && side < 0.5){ //right
tweenPosition.x = defaultStageWidth + d;
} else if(side > 0.5 && side < 0.75){ //up
tweenPosition.y = - d;
} else { //down
tweenPosition.y = defaultStageHeight + d;
}
word.tween = new Kinetic.Tween({
node: word,
duration: 4,
easing: Kinetic.Easings.Linear,
x: tweenPosition.x,
y: tweenPosition.y,
onFinish: function(){
word.clean();
callback();
}
});
word.tween.play();
}
But the problem is that click event doesn't fire on large amount of user clicks. As i think, this caused by delayed drawHit() calls inside tweening mechanism, that draws new object position before updating the hit area, so when we shoot object thinking that we hit its current position we miss because its hit area still have the same old position.
Live example
http://jsfiddle.net/hd6z21de/7/
Take a minute on shooting to see this effect in action
Solved this weird behavior by listening canvas touches and check if pointer collide some target word-rect by myself instead of using their own onclick events.
//i listen to canvas because of my app specific, you could simple listen your own layer or even document
$("canvas").bind('click', function(event){
var x = (event.pageX) / stage.scaleX(); //you don't need to divide by scale if your stage isn't scaled as mine does
var y = (event.pageY) / stage.scaleY();
var wordArray = wordGroup.getChildren();
for(var i = 0; i < wordArray.length; i++){ //go through all words and check if we shoot someone (is mouse position included in some word rect)
if(x > wordArray[i].x() &&
y > wordArray[i].y() &&
x < (wordArray[i].x() + wordArray[i].width()) &&
y < (wordArray[i].y() + wordArray[i].height())){
wordArray[i].shoot(); //shoot that word
break;
}
}
}
Related
I am trying to do something like paint with KineticJS. I am trying to draw the color with circles that originate from the mouse position. However the eventlistener of the mouse position seems too slow and when I move the mouse too fast the circles drawn are far from each other resulting this:
I have seen people filling array with points drawing lines between them, but I thought thats very bad for optimization because after dubbing the screen too much the canvas starts lagging because it has too much lines that it redraws every frame. I decided to cancel the cleaning of the layer and I am adding new circle at the current mouse position and I remove the old one for optimization. However since Im not drawing lines on fast mouse movement it leaves huge gaps. I would be very grateful if anyone can help me with this.
Here is my code:
(function() {
var stage = new Kinetic.Stage({
container: 'main-drawing-window',
width: 920,
height: 750
}),
workplace = document.getElementById('main-drawing-window'),
layer = new Kinetic.Layer({
clearBeforeDraw: false
}),
border = new Kinetic.Rect({
stroke: "black",
strokeWidth: 2,
x: 0,
y: 0,
width: stage.getWidth(),
height: stage.getHeight()
}),
brush = new Kinetic.Circle({
radius: 20,
fill: 'red',
strokeWidth: 2,
x: 100,
y: 300
});
Input = function() {
this.mouseIsDown = false;
this.mouseX = 0;
this.mouseY = 0;
this.offsetX = 0;
this.offsetY = 0;
};
var input = new Input();
document.documentElement.onmousedown = function(ev) {
input.mouseIsDown = true;
};
document.documentElement.onmouseup = function(ev) {
input.mouseIsDown = false;
};
document.documentElement.onmousemove = function(ev) {
ev = ev || window.event;
// input.mouseX = (ev.clientX - workplace.offsetLeft);
// input.mouseY = (ev.clientY - workplace.offsetTop);
input.mouseX = (ev.offsetX);
input.mouseY = (ev.offsetY);
};
function DistanceBetweenPoints(x1, y1, x2, y2) {
return Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)));
}
var canvasDraw = setInterval(function() {
// console.log(input);
if (input.mouseIsDown) {
workplace.style.cursor = "crosshair";
var currentBrushPosition = brush.clone();
currentBrushPosition.setX(input.mouseX);
currentBrushPosition.setY(input.mouseY);
// var distance = DistanceBetweenPoints(brush.getX(), brush.getY(), currentBrushPosition.getX(), currentBrushPosition.getY());
// if (distance > brush.getRadius() * 2) {
// var fillingLine = new Kinetic.Line({
// points: [brush.getX(), brush.getY(), currentBrushPosition.getX(), currentBrushPosition.getY()],
// stroke: 'yellow',
// strokeWidth: brush.getRadius()*2,
// lineJoin: 'round'
// });
// // layer.add(fillingLine);
// }
layer.add(currentBrushPosition);
brush.remove();
brush = currentBrushPosition;
layer.draw();
// if (fillingLine) {
// fillingLine.remove();
// }
}
if (!input.mouseIsDown) {
workplace.style.cursor = 'default';
}
}, 16);
layer.add(border);
stage.add(layer);
})();
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Coloring Game</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/kineticjs/5.2.0/kinetic.min.js"></script>
</head>
<body>
<div id="main-drawing-window"></div>
<script type="text/javascript" src="./JS files/canvas-draw.js"></script>
</body>
</html>
Don't use individual Kinetic.Circles for each mousemove. Every Kinetic object is a "managed" object and that management takes up a lot of resources. KineticJS will slow to a crawl as the number of circles increases with every mousemove.
Instead, use a Kinetic.Shape and draw you circles onto the canvas with
// This is Pseudo-code since I haven't worked with KineticJS in a while
shapeContext.beginPath();
shapeContext.arc(mouseX,mouseY,20,0,Math.PI*2);
shapeContext.fillStrokeShape(this);
This will probably clear your problem, but if the mouse is moved very far in a single mousemove then you might have to draw a lineTo (instead of arc) between the last mouse point and the current far-away mouse point.
I've got this code. What I want the code to do is to make the ball move and when the ball goes over a grey spot (holes) it goes back to the starting point. I've done that by creating a random place for the grey holes. I simply need to find a way to define the position of these holes even though they are randomized.
var startPoint = new Path.Circle(new Point(40, 40), 40);
startPoint.fillColor = "green";
//finishPoint
var finishPoint = new Path.Circle(new Point(1300, 600), 40);
finishPoint.fillColor = "red";
var ball = new Path.Circle(new Point(40, 40), 20);
ball.fillColor = "black";
//holes
var path = new Path(new Point(20, 20), new Point(20, 23));
path.style = {
strokeColor: 'grey',
strokeWidth: 70,
strokeCap: 'round'
};
var holes = new Symbol(path);
for (var i = 0; i < 10; i++) {
var placement = view.size * Point.random();
var placed = holes.place(placement);
}
var vector = new Point(0, 0);
function onFrame(event) {
ball.position += vector / 100;
}
var moves = new Point(100, 1);
function onKeyDown(event) {
if (event.key === "s") {
vector.y += 10;
}
if (event.key === "d") {
vector.x += 10;
}
if (event.key === "a") {
vector.x -= 10;
}
if (event.key === "w") {
vector.y -= 10;
}
var ballPlace = ball.position;
if (ballPlace.isClose(finishPoint.position, 40) == true) {
var text = new PointText(view.center);
text.content = 'Congratulations';
text.style = {
fontFamily: 'Courier New',
fontWeight: 'bold',
fontSize: 100,
fillColor: 'gold',
justification: 'center'
};
ball.remove();
}
if(ballPlace.isClose(placement.position, 40) == true) {
ball = new Point(40, 40);
}
};
and I want the ball to go back to Point(40, 40) when it goes over a grey hole (var holes) but I can't get it to work. Any idea how to fix this?
You want to test the ball's position against the holes to see if the ball goes back to the starting position. The simplest way I can think of to do this is to create a group of the holes then test the position of the ball against that group. In the following code the ball's position is simulated via the onMouseMove function and the holes are flashed red to indicate when the ball would be returned to the the starting position.
var holes = [];
var hole;
for (var i = 0; i < 10; i++) {
hole = new Path.Circle(view.size * Point.random(), 10);
hole.fillColor = 'grey';
holes.push(hole);
}
holes = new Group(holes);
onMouseMove = function(e) {
if (holes.hitTest(e.point)) {
holes.fillColor = 'red';
} else {
holes.fillColor = 'grey';
}
Here's an implementation: sketch. It should be straightforward to replaced onMouseMove with onFrame, move the ball as you currently do, and then test to see if it falls into a hole.
In order to test if the ball is over a hole you can remove on the onMouseMove function and replace it with:
onFrame = function(e) {
ball.position += vector / 100;
if (holes.hitTest(ball.position)) {
// move the ball wherever you want to move it, position text,
// etc. you might have to loop through the array to find which
// hole was hit.
}
}
#Luke Park is right about using an array.
Trial each new point, by ensuring it is a distance from all other existing points. Example below (not scaled to view.size).
p = Point.random();
while ( isTooClose(p, points) ) {
p = Point.random();
}
It's possible for this to loop infinitely, but if you're populating the area sparsely, there should be no problem.
isTooClose tests each point in array p, where distance = sqrt(dxdx + dydy). If you have many points, you can optimise by avoiding sqrt(), by testing whether the raw dx and dy values are smaller than the test radius.
You can also use a similar function on each frame, to test for collision.
I made a simple "animation" with PhysicsJS, where I have this body:
balon = Physics.body('circle', {
x: 50,
y: random(20, 350),
vx: 0.45,
angle: random(0,360),
angularVelocity:-0.005,
radius: 60,
mass: 1,
fixed: true
});
balon.view = new Image();
balon.view.src = 'ballon.png';
All works good but I need to add a shadow for the "ball", this means that I need to use two images the "ballon.png" and the second image (the shadow) need to be fixed over the first image (don't rotate with the body).
Any idea hot to do this ?
Thank you in advance !
If you need one of the images to have a different behavior, you'll need to handle the rendering yourself.
You can add another rendering layer for shadows. If you store the shadow image inside body.shadow, then you can do something like this.
var shd = renderer.addLayer('shadows');
var bodies = [balon];
// draw the provided shadow view
shd.drawShadow = function( body, view ){
var pos = body.state.pos
,v = body.state.vel
,t = renderer._interpolateTime || 0
,x
,y
,aabb
,ctx = shd.ctx;
;
// interpolate positions
x = pos.x + v.x * t;
y = pos.y + + v.y * t;
ctx.save();
ctx.translate( x, y );
ctx.drawImage(view, -view.width/2, -view.height/2, view.width, view.height);
ctx.restore();
}
shd.render = function(){
var body;
for (var i = 0, l = bodies.length; i < l; i++){
body = bodies[ i ];
if ( body.shadow ){
shd.drawShadow( body, body.shadow );
}
}
};
I am new to Kineticjs and not a great javascript coder so I am hoping to get some help with this example.
http://jsfiddle.net/pwM8M/
I am trying to store the x axis on doors so when a redraw unrelated to the doors is done they don't go back to the default position. (multiple types of doors and windows too)
Each form element can have multiple quantities (more than one door) so I need a way to store/retrieve the data currently contained in the alert on jsfiddle.
I did some research but have come up empty. Can anyone help with what I have provided?
function OH(x,y,w,h) {
var oh = new Kinetic.Text({
x:x,
y: y,
width: w*scale,
height: h*scale,
stroke: wtc,
strokeWidth: 1,
fill: 'brown',
fontSize: 6,
fontFamily: 'Calibri',
padding:4,
text: w+' x '+h+' OH\n\n<- DRAG ->',
align: 'center',
textFill: 'white',
draggable: true,
dragConstraint:'horizontal',
dragBounds: {
left:xoffset, right: xoffset+width+length-(w*scale)
}
});
oh.on('dragend', function(e) {
alert(oh.getPosition().x);
});
window.south.add(oh);
}
Thanks,
I have fixed sized 40x40 rectangle here in which i am using dragging function. try this
var box = new Kinetic.Rect({
x: parseFloat(area.size.x),
y: parseFloat(area.size.y),
width: 40, //parseFloat(area.size.width)
height: 40,
fill: area.color,
stroke: 'black',
strokeWidth: 1,
opacity: 0.6,
id : area.id + id,
draggable: true,
dragBoundFunc: function(pos) {
// x
var newX = pos.x < 0 ? 40 : pos.x;
var newX = newX > _self.canvasWidth - area.size.width ? _self.canvasWidth - area.size.width : newX;
// y
var newY = pos.y < 0 ? 40 : pos.y;
var newY = newY > _self.canvasHeight - area.size.height ? _self.canvasHeight - area.size.height : newY;
return {
x: newX,
y: newY
};
Use this function
box.on('dragend', function() {
_self.draw = false;
_self.dragArea(area, box);
});
and try this to play with x y coordinates
dragArea: function(area, box){
if(box){
$$('#' + this.formId + ' [name="areas[' + area.id + '][size][x]"]').first().value = parseInt(box.attrs.x);
$$('#' + this.formId + ' [name="areas[' + area.id + '][size][y]"]').first().value = parseInt(box.attrs.y);
area.size.x = parseInt(box.attrs.x);
area.size.y = parseInt(box.attrs.y);
}
},
Create a new array before the function, like so:
var xArray = new Array();
then inside your function
oh.on('dragend', function(e) {
alert(oh.getPosition().x);
// ADD NEW ITEM TO ARRAY, STORE X POSITION
xArray.push(oh.getPosition().x);
});
and so all the x values get stored in the array.
If you need to clear the array, you can simply create a new one again with the same name.
And you can iterate through it with a loop if needed.
updated:
http://jsfiddle.net/pwM8M/2/
How would I make my object reach the end of the animation paths? I am making a basic pong game, but the ball stops early if it misses the paddle, It should reach the end of the path. Here is my code:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf8">
<title> Rapahel test </title>
<script type="text/javascript" src="raphael.js">
</script>
</head>
<body>
<script type="text/javascript">
//create the canvas, which all the shapes are on
var paper = Raphael(0,0,500,500);
ballpath = '';
ballNum = 0;
//the paths the ball will follow, one at a time, starting from 0. which is the first in an array.
paths = ["M480, 180L35, 180", "M35, 180L480, 325", "M480, 325L35, 110", "M35, 110L480, 50", "M480, 50L35, 340", "M35, 340L480, 340", "M480, 340L35, 200", "M35, 200L35, 200"];
Raphael(function () {
ballpath = paper.path(paths[ballNum]).attr({stroke: "#0000ff", opacity: .3, "stroke-width": 1});
var ball = paper.circle(0, 0, 5).attr("fill", "rgba(255, 0, 0, 1)");
//the length variable (and arribute)
var len = ballpath.getTotalLength();
ball.onAnimation(function () {
var t = this.attr("transform");
});
paper.customAttributes.along = function (v) {
var point = ballpath.getPointAtLength(v * len);
return {
transform: "t" + [point.x, point.y] + "r" + point.alpha
};
};
ball.attr({along: 0});
//the variable rotateAlongThePath is true
var rotateAlongThePath = true;
//th function that actually makes the ball move
function run() {
//the path which the wall will follow
wallpath = paper.path("M480, 180L480, 380, M480, 380L480, 60, M480, 60L480, 340").attr({stroke: "#0000ff", opacity: .3, "stroke-width": 1});
//create a rectangle
var wall2 = paper.rect(0 - 50, 0 - 9, 100, 20).attr("fill", "rgba(0, 0, 255, 1)");
//the length variable (and arribute)
var wallLen = wallpath.getTotalLength();
//not sure aout these commands, they are to do with animation paths
wall2.onAnimation(function () {
var t = this.attr("transform");
});
paper.customAttributes.along2 = function (v2) {
var point2 = wallpath.getPointAtLength(v2 * wallLen);
return {
transform: "t" + [point2.x, point2.y] + "r" + point2.alpha
};
};
//set the wall's movemnt to 0
wall2.attr({along2: 0});
var collision = function(){
//find the paddle's height and width, the ball's center point, and AI Paddle's x position
var paddleheight = Math.round(paddle.getBBox().y + paddle.getBBox().height / 2);
var paddleWidth = Math.round(paddle.getBBox().x + paddle.getBBox().width / 2);
var ballcenterpoint = Math.round(ball.getBBox().y + ball.getBBox().height / 2);
var AIpaddle = Math.round(wall2.getBBox().x + wall2.getBBox().width / 2);
//the height from the center point of the paddle to the edge (heigh variance), finding the height of the top edge of the boundingbox (where the ball will colide with) by adding the centerpoint and height variance. finding the bottom edge by subtracting the centerpoint from the bounding box.
var heightvariance = pHeight;
var heightPlusboundingbox = paddleheight + heightvariance;
var heightMinusboundingbox = paddleheight - heightvariance;
//if the edge of the box is less than or equal to the center of the ball.
var balledge = Math.round(ball.getBBox().x + (ball.getBBox().width / 2) + 22);
//continue ballbounce is used for the if below; it equals: if the ball center is largerthan or equal to the height of the paddle minus its bounding box AND the center point is equal to or less than the height of the paddle plus the bounding box AND the center is equal to or greater than the width (extending the detected edge ofthe box by a few pixels so as to fix some collision problems) the if below will work.
console.log('Ball X: ', balledge);
console.log('AI X: ', AIpaddle);
console.log(balledge);
console.log(AIpaddle);
//the if
if(ballcenterpoint >= heightMinusboundingbox && ballcenterpoint <= heightPlusboundingbox || balledge >= AIpaddle){
//if the ball number is less than or equal to 6 it will increase theball number by one an change the ballpath to one in the array called paths[ballNum], the ball will then begin at the beginning of that path and be animated along it
if(ballNum <= 6){
ballNum++
ballpath = paper.path(paths[ballNum]).attr({stroke: "#0000ff", opacity: .3, "stroke-width": 1});
ball.attr({along: 0});
ball.animate({along: 1}, 2500, onanimationdone);
}
//if the ballNum is greater than 6 it will remove the ball (fixin the error of an infinite loop of theball just sitting the corner acting all boring and doing nothing)
}
else {
// ball.remove(); //- YOU LOSE
};
};
//animates the wall
wall2.animate({along2: 1}, 16200, function () {
});
//loops through all the ball paths
var onanimationdone = function () {
collision();
};
ball.animate({along: 1}, 2500, onanimationdone);
ball.attr({along: 0});
};
//create a rectangles
var pHeight = 100;
var pWidth = 20;
var paddle = paper.rect(10, 130, pWidth, pHeight);
paddle.attr("fill", "rgba(0, 0, 255, 1)");
var start = function () {
// storing original coordinates
this.originalY = this.attr("y");
this.attr({opacity: 0.9});
},
move = function (dx, dy) {
// moves the object up and down, when clicked and dragged
this.attr({y: this.originalY + dy});
},
up = function () {
// restoring state
this.attr({opacity: 1.0});
};
//runs the functions
paddle.drag(move, start, up);
run();
});
</script>
</body
</html>
I am new to JavaScript, I apologise if my code is a little hard to understand (this is my fourth day doing JavaScript).
Using raphael is valid for rendering your graphics, but I would suggest using the paths array for the ball's motion is not. If you are in fact trying to make a game you are best off having the ball store it's current position and velocity, and update these per frame via some some of timer.