I created a small game using JavaScript canvas. It's a 2D shooting game where the controller is the mouse and enemies spawn randomly within the canvas.
I am trying to get the JavaScript to detect if my cursor is overlaying my "enemy" component
For example. The logic statement is
onclick,
If aimcursor is overlapping enemy
enemy spawns at random location.
My only problem is how to check if my aimcursor is overlapping enemy
The If statement I'm using right now is what I have learnt from a website but it doesn't seem to be working.
document.addEventListener("mousedown", function(){
var gunshot = new Audio('gunshot.mp3');
gunshot.play();
if(aimcursor.x >= enemy.x && enemy.y >= aimcursor.y&&
enemy.y <= aimcursor.y && enemy.y <= aimcursor.y){}
Keep a separate gamestate object which contains x/y positions of all objects. Each time you draw on the canvas you use these coordinates. You might already be doing this. So lets say enemy.x and enemy.y are currently available within scope.
The next thing to do is getting the cursors's x/y position. You can get this by checking the MouseEvent instance provided by the mousedown callback.
https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
The MouseEvent instance contains the clientX and clientY properties. These will reflect the coordinates of te cursor based on the client area, not the canvas, so we need to convert this first. Please see this answer:
How do I get the coordinates of a mouse click on a canvas element?
Once you actually got the coordinates. You need to check whether the mouse x/y coordinate is within a certain boundary around the enemy coordinate. This is because modern screens have high resolutions, and most likely no person is precise enough to click that correct 1 pixel area on the screen.
The boundary which is acceptable as valid input is called a "bounding box" and is typically a square or a rectangle the size of the image used to represent the enemy. If the enemy is a 50x50 px image placed on the center of it's x/y coordinate. the bounding box will start at x - 25px, y - 25px and will stretch to x + 25px, y + 25px.
You need to take the bounding box into account and validate whether the user clicked within that region. This will probably look somewhat like.
if(mouse.x >= enemy.x - 25 && mouse.x <= enemy.x + 25 && mouse.y >= enemy.y - 25 && mouse.y <= enemy.y + 25)`
(from the top of my head).
If the calculated mouse position on click matches that condition can consider that a hit.
Related
I am working on this "simple" problem for hours with no success, although I tried many ways to solve it using all kind of solutions suggested in SO.
My problem is as follows:
I have a point on a canvas, which when I click on it my app does something, after identifying the point by comparing the mouse click coordinates to the stored position of the point.
After zooming into the point, using the mouse wheel, I click on the point again but the mouse coordinates no longer fits the stored position of the point.
I need to either transform the mouse coordinates to it's coordinates before the zoom, so I will be able to compare to the stored position, or to transform the stored position to the new canvas so it can be compare to the coordinates of the mouse. Any of the solution is fine by me.
I know the following data:
The "scale" value,
The size of the canvas (top, left, width, height),
The new origin of the canvas (top, left)
I would like a solution using java script.
Finally figured it out and it is quite simple, once I understood the concept.
Save the new canvas origin after doing the zoom (in JS it is calling ctx.translate() and ctx.scale(), where ctx is the canvas context.
When need to calculate the mouse position in the old coordinate system, one has to add back the moved origin of the canvas, and multiply by the scale factor.
seat.x = (-new_org.x + pos.x) / scale;
seat.y = (-new_org.y + pos.y) / scale;
where pos is the calculated mouse pointer
pos.x = event.clientX - .getBoundingClientRect().left;
pos.y = event.clientY - .getBoundingClientRect().top;
==The Crux==
How do I elegantly define five equal-area clickable regions in terms of (x,y) coordinates for a pentagon?
==The Context==
I have a clickable sprite for a javascript game using Phaser. The sprite is shaped like a pentagon and each of the vertices defines a territory that is one-fifth of the pentagon's area. When the user clicks within the territory, the event handler calls the appropriate function for that territory and returns the (x,y) position clicked within the territory.
I am a novice javascript programmer who would like to learn how (and why) to write elegant code, if possible. I can do a little math. Normally, clickable regions are defined as rectangles, but this is a special case. The five-fold symmetry of the graphic is integral to the theme of the game (Five Elements Gong Fu).
What is a good way to define these clickable regions in terms of the coordinates? I feel as if there is some computer-math-formula-wizardry ( that could be thought of that would rely on the symmetry to somehow define the coordinates in an elegant way, but I haven't been able to determine one.
Additionally, the graphic is centered in a png file that is rectangular. I need to somehow translate the coordinates of the pentagon's neighborhoods to the coordinates of the canvas so that the user can feel they are clicking the appropriate region.
edit
==Crappy Graphic==
Ideally, the pentagon would be drawn symmetrically, but I just roughed this up. Like this:
How do I define the (x,y) coordinates of neighborhood A so that the user can click region A and it calls Function_for_region_A(handler)?
I do not know anything about phaser, so here is only javascrip. You could rotate the whole canvas and reput it to it's normal angle when done. So when rotated, the regions become squares and it makes the code and programming simpler. I think you must rotate the canvas 60° to get squares, but I am not sure. I'm not sure if this is correct, so plz do not jugde me or take away points from me xD, THIS IS REALLY REALLY COMPLICATED FOR ME.Here is the code :`
var ctx = document.getElementById("canvasId").getContext("2d");
//code to draw the the pentagon here
var regionA = { //Because the canvas rotates, the x and y points change, so it must have other destination points.
x : pointToTheLeftInRegionA.x,
y : pointToTheLeftInRegionA.y - pointAtTopInRegionA.y / 2
}
function canvasWhenClicked(event) { //Function called when : "<canvas onclick="canvasWhenClicked(event)" ...></canvas>"
var X = event.clientX; //Finds x coordinates of where you clicked
var Y = event.clientY; //Finds x coordinates of where you clicked
ctx.rotate(60 * Math.PI / 180);
if (X > regionA.x && X < regionA.width + regioA.x) { //Sees if the X where clicked is bigger than regionA's X and smaller than regionA X + width
if (Y > regionA.y && Y < regionA.height + regionA.y) { //Sees if the Y where clicked is bigger than regionA's Y and smaller than regionA X + height
Function_for_region_A(handler); //If clicked in box, call function
}
}
ctx.rotate((360 - 60) * Math.PI / 180); //rotate the canvas back
}
I've built a vertical sliderish thing in my web application with the support of mouse and touch dragging events by Hammer.js. At the end of the drag (when user releases mouse button or takes his finger off the screen, a.k.a dragendevent), if the one above is closer to middle, it gets moved to the middle and vice versa.
The thing is, when dragend event is triggered at 48% when user drags the mouse from down to up (which means the one above will get moved to middle) if the velocity is high enough, the one below should get moved to middle. How to calculate if the velocity is high enough?
I solved it like this:
// y is the drag amount
// visible height
var height = $(this).height()
// difference
var diff = (Math.abs(y) % height) / height
// I checked Hammer.js's source. It defines velocity as the change in 16ms.
// So multiplying it by 62.5 gives you the change in 1s.
// Not sure why I did that, but works pretty well in most situations
var inertia = event.gesture.velocityY * 62.5
if (
(event.gesture.direction == 'up' && diff + (inertia / height) >= 0.5) ||
(event.gesture.direction == 'down' && diff - (inertia / height) >= 0.5)
) {
// show the one below
} else {
// show the one above
}
In Microsoft's WPF (now open-source), the inertial velocity is based on a weighted moving average of the last several position values (with their associated timestamps).
See ManipulationSequence.cs and the OnCompletedManipulation() method (presently starting on line 558). The velocities for translation (i.e. X and Y), expansion, and rotation are calculated based on a rolling history of timestamped values, in the CalculateWeightedMovingAverage() method.
I've searched for the answer to this and have tried many proposed solutions, but none seem to work. I've been struggling with this forever so any insight is greatly appreciated.
I have 3 shapes (vectors I suppose) on a JS canvas, each with an orientation represented as degrees off of 0 and a width. I need to drag one of these shapes "straight out" from its orientation. This is difficult to explain in words so please view the graphic I created:
The middle (diagonal) shape is at 45 degrees. It's origin is the red dot, (x1,y1). The user drags the shape and their mouse lies at the green dot, (x2,y2). Since the shape's origin is in the lower left, I need to position the shape at the position of the lighter blue shape as if the user has dragged straight outward from the shape's origin.
I don't think it matters, but the library I'm using to do this is KineticJS. Here's the code and some information I have available which may help solve the problem. This code positions the shape on top of the mouse, which isn't what I want:
var rotationDeg = this.model.get("DisplayOri"), // rotation in degrees
rotationRadians = rotationDeg * Math.PI / 180, // rotation in rads
unchanged = this.content.getAbsolutePosition(), // {x,y} of the shape before any dragging
dragBoundFunc = function (changed) {
// called on a mouseMove event, so changed is always different and is the x,y of mouse on stage
var delta = {
x: changed.x - unchanged.x,
y: changed.y - unchanged.y
};
return changed; // go to the mouse position
};
[edit] I should mention that the obvious of "return delta" doesn't work.
It sounds like you want to constrain the movement of the object.
Determine the vector representing the constraint axis : that is, we only want motion to occur along this line. It appears from your drawing that this is in the direction of the short line from the red dot out to the left. That vector has a direction of -1/m where m is the slope of the line we are moving.
Constrain the movement. The movement is represented by the mouse move delta - but we only want the portion of that movement in the direction of the constraint axis. This is done with a dot product of the two vectors.
So in pseudo code
m = (line.y2 - line.y1)/(line.x2 - line.x1)
constraintSlope = -1/m
contraintVector = {1, constraintSlope} //unit vector in that direction
userMove = {x2-x1, y2-y1} //vector of mouse move direction
projection = userMove.x * constraintVector.x + userMove.y * constraintVector.y
translation = projection * constraintVector //scaled vector
I'm writing a 2D game in html5 using Canvas which requires mouse click and hover events to be detected. There are 3 problems with this: detections must be pixel-perfect, objects are not rectangular (houses, weird-shaped UI buttons...), and it is required to be fast and responsive. (Obviously brute force is not an option)
So what I want to ask is how do I find out which object the mouse is on, and what are the possible optimizations.
P.S: I did some investigation and found a guy who used QuadTree here.
I have a (dated) tutorial that explains the concept of a ghost canvas which is decent for pixel-perfect hit detection. The tutorial is here. Ignore the warning about a newer tutorial, the newer one does not use the ghost canvas concept.
The idea is to draw the image in question to an in-memory canvas and then use getImageData to get the single pixel of the mouse click. Then you see if that single pixel is fully transparent or not.
If its not fully transparent, well, you've got your target.
If it is fully transparent, draw the next object to the in-memory canvas and repeat.
You only have to clear the in-memory canvas at the end.
getImageData is slow but it is your only option if you want pixel-perfect hit detection and aren't pre-computing anything.
Alternatively you could precompute a path or else an array of pixels with an offset. This would be a lot of work but might be faster. For instance if you have a 40x20 image with some transparency you'd compute an array[40][20] that would have true or false corresponding to transparent or not. Then you'd test that against the mouse position, with some offset, if the image is drawn at (25, 55) you'd want to subtract that from the mouse position and then test if the new position is true when you look at array[posx][posy].
That's my answer to your question. My Suggestion? Forget pixel-perfect detection if this is a game.
Seriously.
Instead make paths (not in canvas, in plain javascript code) that represent the objects but are not pixel perfect, for instance a house might be a square with a triangle on the top that is a very close approximation of the image but is used in its stead when it comes to hit testing. It is comparatively extremely fast to compute if a point is inside a path than it is to do pixel-perfect detection. Look up point in polygon winding number rule detection. That's your best bet, honestly.
The common solution in traditional game development is to build a click mask. You can re-render everything onto a separate off-screen canvas in a solid color (the rendering should be very quick). When you want to figure out what was clicked on, you simply sample the color at the x/y co-ordinate on the off-screen canvas. You end up building a color-->obj hash, akin to:
var map = {
'#000000' : obj1
, '#000001' : obj2
, ...
};
You can also optimize the rendering to the secondary canvas to only happen when the user clicks on something. And using various techniques, you can further optimize it to only draw the part of the canvas that the user has clicked on (for example, you can split you canvas into an NxN grid, e.g. a grid of 20x20 pixel squares, and flag all of the objects in that square -- you'd then only need to re-draw a small number of objects)
HTML5 Canvas is just a drawing plane, where you can set different transforms before calling each drawing API function. Objects cannot be created and there is no display list. So you have to build these features yourself or you can use different libraries available for this.
http://www.kineticjs.com/
http://easeljs.com/
A few months before I got interested in this and even wrote a library for this purpose. You can see it here : http://exsprite.com. Ended up facing a lot of performance issues, but because of lack of time I couldn't optimize it. It was really interesting, so waiting for some time to make it perfect.
I believe the comments should suffice. This is how I determine user intention in my 2d isometric scroller, currently located at http://untitled.servegame.com
var lastUp = 0;
function mouseUp(){
mousedown = false; //one of my program globals.
var timeNow = new Date().getTime();
if(mouseX == xmouse && mouseY == ymouse && timeNow > lastUp + 100){//if it was a centralized click. (mouseX = click down point, xmouse = mouse's most recent x) and is at least 1/10th of a second after the previous click.
lastUp = new Date().getTime();
var elem = document.elementFromPoint(mouseX, mouseY); //get the element under the mouse.
var url = extractUrl($(elem).css('background-image')); // function I found here: http://webdevel.blogspot.com/2009/07/jquery-quick-tip-extract-css-background.html
imgW = $("#hiddenCanvas").width(); //EVERY art file is 88px wide. thus my canvas element is set to 88px wide.
imgH = $(elem).css('height').split('p')[0]; //But they vary in height. (currently up to 200);
hiddenCanvas.clearRect(0, 0, imgW, imgH); //so only clear what is necessary.
var img = new Image();
img.src = url;
img.onload = function(){
//draw this elements image to the canvas at 0,0
hiddenCanvas.drawImage(img,0,0);
///This computes where the mouse is clicking the element.
var left = $(elem).css('left').split('p')[0]; //get this element's css absolute left.
var top = $(elem).css('top').split('p')[0];
offX = left - offsetLeft; //left minus the game rendering element's absolute left. gives us the element's position relative of document 0,0
offY = top - offsetTop;
offX = mouseX - offX; //apply the difference of the click point's x and y
offY = mouseY - offY;
var imgPixel = hiddenCanvas.getImageData(offX, offY, 1, 1); //Grab that pixel. Start at it's relative X and it's relative Y and only grab one pixel.
var opacity = imgPixel.data[3]; //get the opacity value of this pixel.
if(opacity == 0){//if that pixel is fully transparent
$(elem).hide();
var temp = document.elementFromPoint(mouseX, mouseY); //set the element right under this one
$(elem).show();
elem = temp;
}
//draw a circle on our hiddenCanvas so when it's not hidden we can see it working!
hiddenCanvas.beginPath();
hiddenCanvas.arc(offX, offY, 10, 0, Math.PI*2, true);
hiddenCanvas.closePath();
hiddenCanvas.fill();
$(elem).css("top", "+=1"); //apply something to the final element.
}
}
}
In conjunction with this:
<canvas id="hiddenCanvas" width="88" height="200"></canvas>
Set the CSS positioning absolute and x = -(width) to hide;