I have been playing around with webGL and I have reached a point I can make small three dimensional games with very pitiful graphics (it is more of a proof of concept/functionality as of now). For the three dimensional experience, it is nice to move the mouse in any direction infinitely and seamlessly to rotate the first person camera. Pointerlock allows me to lock and hide the cursor position, which is very helpful, but then I need to find another method of tracking the mouse's movements. In my research, event.movementX and event.movementY seemed to be the standard, but I often get large blips (usually between 500 and 583) of movement in the opposite direction of the mouse's movement. I tested this with numerous mice and trackpads and experience the same phenomenon.
Here are my relavent event listeners:
document.addEventListener("mousemove", function(event) {
xMovement += event.movementX;
yMovement += event.movementY;
console.log(event.movementX)
}, false);
document.addEventListener("pointerlockchange", function(event) {
if(pointerLockEnabled) pointerLockEnabled = false;
else pointerLockEnabled = true;
xMovement = 0; yMovement = 0;
} , false);
And relevant render loop code:
function render() {
if(pointerLockEnabled) {
camera.rotation.y = -xMovement / 1000;
camera.rotation.x = -yMovement / 1000;
if(rightKey && !leftKey) {
camera.position.x += 10 * Math.cos(camera.rotation.y);
camera.position.z -= 10 * Math.sin(camera.rotation.y);
}
else if(leftKey && !rightKey) {
camera.position.x -= 10 * Math.cos(camera.rotation.y);
camera.position.z += 10 * Math.sin(camera.rotation.y);
}
if(upKey&& !downKey) {
camera.position.z -= 10 * Math.cos(camera.rotation.y);
camera.position.x -= 10 * Math.sin(camera.rotation.y);
}
else if(downKey && !upKey) {
camera.position.z += 10 * Math.cos(camera.rotation.y);
camera.position.x += 10 * Math.sin(camera.rotation.y);
}
}
}
But my console has occurrences such as this:
I added conditions to changing xMovement to prevent massive turns in camera angle, but I am still left with very annoying movement. Any ideas to patch or replace to a more seamless interface movement?
It could be helpful if you would throttle your mousemove event in some way. For example the lodash throttle version:
function handleMouseMove(event) {
xMovement += event.movementX;
yMovement += event.movementY;
console.log(event.movementX)
}
var throttledHandleMouseMove = _.throttle(handleMouseMove, 75);
document.addEventListener("mousemove", throttledHandleMouseMove, false);
With this approach handleMouseMove would not be executed more that 1 time per 75ms.
I know I'm super late to the party here, but I have an explanation for the first problem mentioned in the OP, and a work-around for the second problem brought up by #KiranKota.
The first problem is actually a bug in Chromium versions prior to 64. It was dormant until something happened in the Windows 10 Fall Creator's update that ended up exposing it. Even though you would be in pointer-lock, your "cursor", though invisible, would essentially wrap around to the other side of the window, causing spikes in the opposite direction of movement.
The fix for this is to simply ignore the first mouse move event that moves in the opposite direction; that's if you still care about supporting Chromium < 67.
The second problem, where the spikes move in the same direction, is entirely unrelated, and still a problem as of Chromium 94. The issue has to do with mice with high polling rates, as is the case with many gaming mice. Through my experiments, I've discovered that a polling rate of 1000 is quite bad, 500 less so, and 250 appears to make the issue disappear. I've also discovered that the spikes are consistent with the width of the current window. They are always window.innerWidth (or innerHeight) / ~2.3... , plus what I can only assume is the "real" distance of the current mouse movement. Why 2.3-ish...? I have no idea. The factor is the same whether I'm running a rate of 1000 or 500.
I'm going to experiment with this some more and see if I can't reliably filter out these anomalies. In the mean time, perhaps this info will be useful.
UPDATE:
I've settled on solving the second issue by simply ignoring any mouse movements that are greater than window.innerWidth / 3 and window.innerHeight / 3. I've provided a toggle for users to turn this solution on or off, but it doesn't seem to interfere with normal use, regardless of polling rate.
I had the same issue but, for me, the bad values are always in the same direction as movement. I found that if I replace any values above 50 with the last value, I get very good accuracy. The only issues are when the bad values are in the 30-49 range but I don't want to cancel those in case the user is actually moving their mouse that fast or their mouse has a bad polling rate. Some trendline comparison would work for smoothing those, but if you don't need too much precision, this is good:
const movement = {X: 0, Y: 0};
const lastMovement = {X: 0, Y: 0};
function onMouseMove(evt) {
if (checkPointerLock()) {
['X', 'Y'].forEach(axis => {
const checkValue = evt['movement' + axis] || evt['mozMovement' + axis]|| 0;
//ignore >=50. probably erroneous. smooth to last value
if (Math.abs(checkValue) < 50) {
lastMovement[axis] = checkValue;
}
movement[axis] = lastMovement[axis];
});
yaw += movement.X * -0.001 * data.sensitivityX;
pitch += movement.Y * (data.invertY ? .001 : -.001) * data.sensitivityY;
pitch = Math.max(-Math.PI/2, Math.min(Math.PI/2, pitch));
}
}
Related
How to kill bullet after a certain amount of time, or certain amount of distance ? I'm using JavaScript and Phaser 3.
if (keyA.isDown && time > lastFired || isDown && time > lastFired) {
var bullet = bullets.create(player.x , player.y, 'bullet');
bullet.setVelocity( -800, 0);
lastFired = time + 90;
}
If it should be after some time, you could do something like this.
(here some more details from the Docs: from the Docs )
But to be honest I don't know, if this would impact the performance, or not.
...
// the 500 ist the timeout in ms
this.time.delayedCall(500, () => bullet.destroy();
....
(add this line after creating the bullet)
Or you cout use a time event
this.time.addEvent({
delay: 500,
callback: ()=>{
bullet.destroy()
},
loop: false
});
for destroying by distance: depending if it is one bullet or many. you could add the bullet to a group and check the distance in the update function, and it its too far you detroy it/them.
I have a function in class Player called jump(), which should move the player object up by 15, unless it's 20 from the top of the canvas, in which case it drops by 20. Then it calls the function fall(), after a brief wait, which drops the position of the player by 15 - theoretically resulting in the player being put back to where they were after falL() executes. However, the problem is that if you spam the jump button or hold it down enough, when the fall()s come through, it drops the player far below where it started. Is this because of the roof collision check?
function fall() {
player.y += 15
}
(within class Player:)
jump() {
this.y -= 15;
if (this.y <= 20){ // check roof
this.y += 20
}
setTimeout(fall,550);
}
How can I make it so the user can hold the jump button, and not end up with a y lower than their starting y?
Sounds like you might want to just only set the timeout to fall if the player actually jumps in the first place.
jump() {
if (this.y <= 5){
// roof
this.y += 5
} else {
// jump
this.y -= 15;
setTimeout(fall, 550);
}
}
I don't know what your app is like, but depending on the setup, a more elegant approach might be to, during the game loop, check if there's anything under the player (like the floor, or some other platform), and if there isn't, change their y appropriately, so that the falling action is more consistent and not tied to jump.
I have been hacking on this problem for a while and can't seem to find a solution (I am not friends with trigonometry).
I am trying to build an element that the user can "grab" and then rotate. I have it working except for one last feature I can't seem to figure out:
http://codepen.io/megakoresh/pen/WbLLzZ?editors=001
(it's long, but I am only asking about how the snapTo() function should work).
What I want is for the object snap to degrees based on the increments value. This means that if snap==true, the code should calculate the closest estimated targets to the point of release of the mouse and based on the direction of rotation smoothly rotate the object to that target rotation:
Since the object is 'grabbed', I calculate the offset at mousedown to object's current rotation, thats where it comes from, so it doesn't just snap to mouse.
So in this case the user rotates the object clockwise and releases the mouse when the objects rotation is between 90° and 45°. Since the direction (identified by the sign of angle variable) was positive, the target will be after the Current rotation.
The task is to calculate that target and then smoothly rotate the object to it.
The function I have written for it is based on by autoSpin() function (executes when spin==false), which takes a flipped time exponent multiplier delta (calculated from the time elapse since mouse was released). delta will decrease along a flipped exponent as time passes and so the angle slows down.
There is spinTo() function, please don't judge me I have a feeling it is very stupid:
function snapTo() {
var elapsed, delta;
increments = (typeof increments === 'number') ? Math.floor(increments) : 4;
var ia = 360 / increments; //increment angle - snapping points should occur "every ia degrees"
if (Math.abs(angle % ia) > 0) { //if user turned for more than 1 increment
var a = lastRot % ia; //check the distance from
if (angle > 0){ //positive direction
amplitude = 50; //if snapping is on, force amplitude
target = (lastRot - a) + ia;
}
if (angle < 0){ //negative direction
amplitude = -50;
target = lastRot - a;
}
} else { //cancel the rotation
target = rotation;
}
elapsed = Date.now() - timestamp; //time passed since mouse was released
delta = -amplitude * Math.exp(-elapsed / timeConstant); //the greater the time from mouse release, the smaller this value
if (delta > 0.5 || delta < -0.5) { //while delta is decreasing...
rotate(target - delta - offset);
snapFrame = requestAnimationFrame(snapTo); //keep rotation
} else {
rotate(target - offset); //when enough time passes (based on timeConstant), make one final rotation and stop
}
}
What am I doing wrong?
*le sigh
Ok well I had to figure it out on my own. If anyone wants to do this kind of thing, here is one way of doing it:
function snapTo() {
var elapsed, ease, diff;
increments = (typeof increments === 'number') ? Math.floor(increments) : 4;
var ia = 360 / increments; //increment angle - this is how large each increment will be
var a = last % ia; //check the distance from point of mouse release to the previous increment
if (movement>0) {
elapsed = Date.now() - timestamp; //time passed since mouse was released
ease = 1 - Math.exp((-3*elapsed)/timeConstant); //your easing function formula goes here
if(a > ia/2) { //if halfway through the increment...
target = (last - a) + ia; //target next increment
}
else { //else revert to previous increment to the point of mouse release
target = last - a;
}
diff = target - last; //difference between target and rotation at time of mouse release
if(ease < 0.95){ //this depends on your easing formula
rotate(last+diff * ease);
requestAnimationFrame(snapTo); //don't forget to RAF
} else { //and for your final frame...
rotate(last+diff); //make it snappy :P
}
console.log(last); //traces of debugging were found in this derelict question...
}
}
So in here
elapsed is the time passed since the mouse was released
increments is the argument provided by user: how many snapping points will be there
ia is the computed increment in degrees: 360/increments
last is the angle recorded on mouseup event - "Current rotation" in the diagram. It includes the offset (in my code, its just a "snapshot" of rotation at point of release, that is then also reversed in sign because in DOM the coordinate system is y-flipped and I don't like working with that).
a is how much bigger the last is than the previous nearest increment point.
diff is the "target" on the diagram - difference between final rotation and last
ease is just the value that changes according to your easing function, based on which you either continue calling RAF or finish the animation.
timeConstant is time in milliseconds for how long the animation will take. You can have this or not, depends on your easing formula
A good resource to read in general: Understanding Easing Functions
Also Desmos graphing calculator is quite good for developing easing formulas.
Oh and: in case anyone is wondering, it doesn't seem possible to pass any kind of non-time-related arguments to RAF callback functions. Seems to break it if I do. So the only solution is to define the increments value elsewhere in the code.
Codepen of the working function
Anyone got a better solution, I am all eyes.
I have this fiddle http://jsfiddle.net/pp7oby62/. The fiddle consists of a paddle game with rocks being hurdled at a paddle on a 80x10 sensor grid. The function notify_player has two parameters which give me the location of the rocks and the paddle. My code looks like this:
defender.start(
function notify_player(rocks, paddle_y) {
var rocks_x = Math.max(rocks);
var rocks_y = Math.min(rocks);
var paddle_x = Math.max(paddle_y);
var paddle_yp = Math.min(paddle_y);
var deltaY = rocks_y - paddle_yp;
var deltaX = rocks_x - paddle_x;
var angleInRadians = Math.atan2(deltaY, deltaX);
var angleRound=Math.round(angleInRadians);
// random plan
var moves = [];
for (var i = 0; i < 22; i++) {
// -1, 0, 1 to move the fiddle up and down on the grid
//moves.push( Math.floor(Math.random()*3) - 1 );
if (angleInRadians >0) moves.push(-1);
else if (angleInRadians < 0) moves.push(0, 1, -1);
}
return moves;
}
);
The moves array controls the paddle moving up and down. As you can see I am calculating the angle in radians using the the information I get from the two parameters. But when I run it there's no effect on the movement of paddle when I give conditional loops stating that the angle is >0 or <0. But suppose I say:
if(angleInRadians!==0)
The paddle moves.
How could this be possible?
there's no effect on the movement of paddle when I give conditional loops stating that the angle is >0 or <0.
This could simply be when anleInRadians is zero - you've got a blind spot there.
But suppose I say: if(angleInRadians!==0) The paddle moves. How could this be possible?
The variable angleInRadians contains the value NaN. It is neither smaller nor greater than zero, as it's not a number - but it is definitely not zero. Yes, NaNs equality relations are a bit odd (it's not even equal to itself).
The reason why you are getting NaN here is that you're doing invalid mathematical operations. rocks is an array of objects (you really should log and inspect it), which you cannot call Math.min or .max on.
Btw, it doesn't even make sense to call min or max on a single value. And you won't get the paddle_x position by calling Math.max(paddle_y).
I have this function which detects the collision between to objects. It is called within a Ticker (FPS 60). The if statement will run as long as the two objects are together. I think this has something to do with the ticker and it running the if every frame. What would be the best way to fix this so that for example when the two objects collided the person gets one point instead of four or ten.
function collDec(){
var minDistance = 10 + 10;
var xDist = circle.x - arrow.x;
var yDist = circle.y - arrow.y;
var distance = Math.sqrt(xDist*xDist + yDist*yDist);
if (distance < minDistance) {
Lpoints.text = "Points: " + ++pointsAm;
//console.log("HIT");
var dingSound = createjs.Sound.play("sound/ding.mp3");
//reset();
}
} // End of collDec
Have an int on each object, recently collided
If a collision occurs, set both recently collided on both objects to 2
At the start of every frame, decrement recently collided on all objects by 1 to a minimum of 0
If a collision occurs and recently collided is 1 or higher* on both objects, do not add points/play a sound but still increment recently collided.
*I think 'exactly 1' may be fine too. It seems to only matter in 'three balls collide simultaneously or near simultaneously' cases.