There doesn't seem to be a good way to base the <canvas> viewport on the location of the mouse pointer and being able to move around freely. Basically, like every other first-person game on the market.
There is no way to capture the mouse
inside a <canvas> element.
There is no way to set the position
of the mouse pointer.
It is not possible to go full screen
with <canvas>, and even if, once the edge has been reached, functionality will be broken.
For good reasons, too. Imagine what possible scenarios could (and definitely would) be employed by malicious persons.
Perhaps it's too early to be thinking of something that is almost only of any use in a 3D environment, something that there isn't yet a spec for.
What's your take or solution?
You can get the mouse position inside of a canvas.
function getCursorPosition(e) {
var x;
var y;
if (e.pageX != undefined && e.pageY != undefined) {
x = e.pageX;
y = e.pageY;
}
else {
x = e.clientX + document.body.scrollLeft +
document.documentElement.scrollLeft;
y = e.clientY + document.body.scrollTop +
document.documentElement.scrollTop;
}
x -= gCanvasElement.offsetLeft;
y -= gCanvasElement.offsetTop;
var cell = new Cell(Math.floor(y/kPieceHeight),
Math.floor(x/kPieceWidth));
return cell;
}
From Dive Into HTML5: Let's Call it a Draw(ing Surface)
I don't think there is a good solution for this -- at least, not until we get mouse locking. No matter how elegant your solution, if you make a twitchy mouselook driven game, the user is going to twitch outside the canvas area at some point. Even if they don't accidentally click a link, their immersion will be broken when the view stops responding to their mouse.
For slower paced games, you could:
Use click and drag to turn. Once user starts dragging within the canvas, you use the mouse delta from the point where they started dragging to determine how far to turn. Because the user is holding down the button, they won't accidentally click things.
Hover cursor near the edges of the canvas to turn, similar to an RTS. This would turn more slowly, but is probably the most intuitive, and easiest for a user to accidentally discover.
Use the keyboard to look, like pre-mouse FPS games (such as Doom).
It's worth noting that there is an open feature request in Firefox for mouse locking. But, unfortunately, neither this, nor mouse hiding or fullscreen are part of the WebGL spec.
All of these features are supported by Unity, so that may be a path to look at if you really need FPS controls.
Related
I just wanted to know how I could make something on a canvas change clicking ONLY on a specific area of the canvas.
Let's say my canvas is of width 400px and height 400px, and I am drawing a face.
If I click on an eye, I want it to change. Let's say the eye is in the coordinates (drawn with .arc()) (135,70,15,0,Math.PI*2,true).
How can I make it so that when I click anywhere inside said eye, and nowhere else, something happens?
Thanks a lot!!
You need to manually compute if the mouse is in the eye. The best, simple and most precise solution would be to use ctx.isPointInPath. This API will tell you if a point is in the current shape you are drawing. Example :
ctx.arc(135,70,15,0,Math.PI*2,true)
if(ctx.isPointInPath(your_mouse.x, your_mouse.y)) {
doSomething()
}
Else you can do some hit-testing, but that's way more complicated depending on the shape of your object. Example for a rectangle :
if(
mouse.x > rect.x && mouse.y > rect.y &&
mouse.x < (rect.x + rect.width) && mouse.y < (rect.y + rect.height)
) {
doSomething()
}
You need to implement hit testing.
Keep track of your drawn objects in a Scene Graph - a kind of DOM that contains objects and their positions.
When you click somewhere you need to compare the position of the click with the the positions of your objects.
If the click point lies within the area of a drawn object, you consider that a hit.
That or just ahead and use a canvas library that keeps such a Scene Graph for you, such as Paper.js or Fabric.js - both libraries allow for hit-testing as well.
I use Pointer Lock for capturing the cursor in a game I'm developing in JavaScript with three.js. I've done my basic Googling, but I can't seem to find why, on Chrome OS, the cursor doesn't seem to move. If you look at http://scheib.github.io/HTMLMisc/PointerLockAndFullscreen.html Pointer Locking is working just fine and there's even a nice "cursor" to go along with the movement!
JavaScript Console output (The logging is me):
I "fabricate" the movementX/Y with
if (evt.movementX === undefined) {
evt.movementX = evt.screenX - lastx;
evt.movementY = evt.screenY - lasty;
lastx = evt.screenX;
lasty = evt.screenY;
}
In case you're wondering, I'm just setting up basic graphics.
The cursor is not moving because you have "lock" it. But evt.movementX and evt.movementY gives you the direction of the movement.
You have to update the position or direction of your camera and render the result to make it feel like it moves.
Under pointer lock, only the movementX and movementY properties are available. All other "ordinary" position co-ordinates are deliberately set to zero.
In pointer lock mode, it is perceived that there is no cursor, only mouse movement, so there's no (screenX,screenY) for a cursor which does not exist, nor is there an (offsetX,offsetY) for the same reason.
Only (movementX,movementY) by design.
I have figured out a nice effect from application that needs several UI elements but also requires as much screen space as possible.
Idea of the effect is, that the UI buttons almost dissapear as soon as you move the mouse too away from them.
I have made a jsFiddle in case you'd like to see it.
It's quite simple:
window.addEventListener("mousemove", function(e) {
var rect = element.getBoundingClientRect();
//Measuring distance from top-left corner of the div
var top = rect.top+(rect.bottom-rect.top)/2;
var left = rect.left+(rect.right-rect.left)/2;
//Mouse position
var x = e.clientX;
var y = e.clientY;
//Thank pythagoras for this
var distance = Math.sqrt(Math.pow(x-left, 2)+Math.pow(y-top, 2));
//Brightness in interval <1, 0.1>
var brightness = Math.min(1, Math.max(0.1, 100/distance));
element.style.opacity = brightness+"";
});
This jsFiddle was also supposed to demonstrate problem I have - but it runs unexpectedly smoothly.
The problem is, that the browsers seem to buffer the CSS changes if they are too frequent. This is a very smart performance strategy but in my case it quite breaks the effect.
I have also uploaded test script here. In Google Chrome, buffering appeared to be so strong (and unsynchronized), that the buttons sometimes flickered.
Should I implement some frame-skip so that the browser buffer is not initiated by the animation effect?
My buttons have their bottom border cut off. If you'd know why this happens, please let me know in comments
After a sleepness night I discovered something about this question which I think is fundamentally mind boggling, at least to me.
Mouse coordinates ARE NOT PRECISE (I guess at a high speed of processing where the whole canvas has to be recreated when movement occurs) as in my codes above. I have tested this code piece by piece and discovered that the problem is not in my loop, but in the precision of
if ((newMouseX !== mouseX) && (newMouseY !== mouseY)).
If you tested this part of code by slower times (which will allow your eyes to detect the difference in coordinates when 'it stops', then you will realise that newMouseX & mouseX are off by 1-2 pixel 90% of the time, > 2 pixel 9% of the time, and only equal about 1% of the time. (I did not measure the statistics but that is what I picked on several rounds of testing).
I can't get it to work in fiddle but I think you can copy it to your testing ground to see what I mean. If you can get it to work in fiddle it would be great so experts can give it a short :)
This means that the mouse is considered to be 'moving' by my code even when it should have 'stopped', and thus 'stops' several times in between, therefore calling the loop too many times in a second, which is the problem I have been having.
I would be happy to hear comments from other experts, including those who can test this and come up with a statistical precision/advice.
My advice, and solution for the moment, is to consider movement when the difference is more than 10 pixels of either coordinates. Of course this presents a problem, but I can leave with that until some better solution comes up.
so instead of
if ((newMouseX !== mouseX) && (newMouseY !== mouseY))
i have used
if (( Math.abs(newMouseX - mouseX) > 10) || ( Math.abs(newMouseY != mouseY) > 10) )
Another thing to consider is how to deal with the mouse position when it goes off my target canvas area... that looks like an infinite movement at the moment!
The Question:
How can I get the precise mouse coordinates so I can compare mouseX & newMouseX?
Thanks.
Mouse precision is determined by the hardware. A high-precision mouse will produce different results than a built-in mouse pad for instance (not to mention touch devices).
However, this is not the problem with your code and your scenario. You are only listening to the mousemove event. It will by definition only throw an event when you move the mouse - hence the new mouse position can never be at the same position as the previous one. That would be impossible and should be off by 100% (unless you are triggering two+ moves where the last goes back to the fist position before you check).
Normally one would listen to the mousedown and mouseup events as well as they are not dependent on a mouse move to trigger. Detecting start and stop solely based on mouse movement is considered impossible under all possible circumstances.
You can do a compromise and make a definition of what a start is and what a stop is, ie. if the mouse has not moved after x milliseconds it is considered a stop (start would be on first move).
This means you will need to follow this rule every time you need to detect a stop. Imaging doing a drawing and sometimes you draw sometime slow other times fast. Or, how do you move the mouse to a new position without drawing anything... There is a good reason for the mouse button(s) to be invented :-)
The rule will soon prove to be useless (or overly complicated prone to more than one error).
As to mouse positions outside canvas there are several ways to handle this.
You can get the canvas bounds by calling:
var canvasRect = canvas.getBoundingClientRect();
which gives you properties to check when mouse position is inside or outside this rectangle.
Another way is to listen to the mouseleave and mouseenter events on the canvas element.
A third is to actually use the mouse buttons. When mouse button is held down on the canvas element you set a flag so mousemove events are considered.
This will keep listening until the mouse button is released. If you release it outside canvas and is using the canvas mouseup event it won't be detected. Therefor you should listen to the window's mouseup event which will trigger in either case.
This also goes for mousemove events. Using the window event will allow you to record positions outside canvas. If you don't want to do this you can use canvas' mousemove which will clip at the canvas' boundaries.
It boils down to:
Use the mousedown, mousemove and mouseup events in combination and you'll be fine. All these events delivers clientX and clientY for mouse positions.
And if I may - you can also test by going to my easyCanvas project and run the sample:
Sample - mouse event details
This will show you details for mouse down, move and up (the details are extended with other information, but you can at least verify mouse positions - do a click without moving and you see the mouse position is exactly the same).
http://jsfiddle.net/6czap/74/
I hope i helped with this :)
$("div").mousemove(function(e){
var pageCoords = "( " + e.pageX + ", " + e.pageY + " )";
var clientCoords = "( " + e.clientX + ", " + e.clientY + " )";
$("span:first").text("( e.pageX, e.pageY ) : " + pageCoords);
$("span:last").text("( e.clientX, e.clientY ) : " + clientCoords);
});
I implemented a simple draggable world map for a game but the performance differs when using different browsers - which is kinda obvious. I used 256x256 pixle tiles and the script dynamically renders the number to fill the whole window plus borders.
Prototype: http://mt111102.students.fhstp.ac.at/draggable/game.html
Currently I'm doing it simply by setting the top and left style attributes on mousemove. Heres a snippet:
mouseDown : function(e) {
Map.myPreventDefault(e);
dx = map.offsetLeft - e.clientX;
dy = map.offsetTop - e.clientY;
map.addEventListener('mousemove', Map.divMove, false);
},
divMove : function(e) {
Map.myPreventDefault(e);
map.style.position = "absolute";
map.style.cursor = "move";
map.style.left = e.clientX + dx + "px";
map.style.top = e.clientY + dy + "px";
}
Later when dragging to the borders I'm gonna load new tiles with XHR and delete old ones on the other end to retain performance so that the wrapper doesn't get to big.
My question is: Would it be more performant by using CSS translate instead of just setting the top and left attributes? And do you guys have any tips how to make it smoother?
In Firefox the prototype works almost perfectly smooth but in Webkit browsers like Chrome it doesn't look very good - it lags a bit. I just wonder how Google Maps managed it to work in every modern browser with the same smoothness.
A CSS transform, especially with a Z value (which will load the transform into the GPU), will almost always be faster and smoother - especially on devices like iPads, but elsewhere as well.
For more details you can check out this tutorial on GPU acceleration:
http://creativejs.com/2011/12/day-2-gpu-accelerate-your-dom-elements/