How to kill bullet after a certain of distance? - javascript

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.

Related

event.movement returning odd values

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

How to lengthen the update interval or reduce the number of steps of jQuery's .animate() method?

Animating with jQuery is straightforward. Here's an example of animating a number from 0 to 1000 over 2 seconds:
var $h1 = $('h1'),
startValue = parseInt($h1.text(), 10),
endValue = parseInt($h1.data('end-value'), 10);
$({ int: startValue })
.animate({
int: endValue
}, {
duration: 2 * 1000,
step: function () {
$h1.text(Math.ceil(this.int));
}
});
Working example: http://codepen.io/troywarr/pen/NpjyJE?editors=1010#0
This animation looks nice, but makes it difficult to read the number at any point during the animation, as the next number replaces it within milliseconds. Ideally, I'd like to animate the number over the same length of time but using fewer steps (increasing the length of the update interval), so that it's easier to read the number at a glance as it animates.
It doesn't appear that jQuery offers direct control over the number of steps in the animation, nor the update interval; it only seems to accept a step function that receives an interpolated value.
Is there a way to adjust the number of steps that jQuery uses to interpolate the animation over its duration?
Here is an approach that I used to simulate this, which probably works well enough for everyday purposes. This isn't ideal from a performance standpoint, though, because jQuery still calls the step function the same number of times as it would have originally.
var $h1 = $('h1'),
startValue = parseInt($h1.text(), 10),
endValue = parseInt($h1.data('end-value'), 10),
stepCounter = 0,
showNthStep = 8;
$({ int: startValue })
.animate({
int: endValue
}, {
duration: 2 * 1000,
step: function () {
if (stepCounter++ === showNthStep) {
$h1.text(Math.ceil(this.int));
stepCounter = 0;
}
},
complete: function () {
$h1.text(Math.ceil(this.int));
}
});
Working example: http://codepen.io/troywarr/pen/NpjYWo?editors=1010#0
This uses the variable stepCounter to count the calls to the step function and only update the number every 8th time (per showNthStep).
Note that the complete function is necessary to update the number one final time if the step function isn't called a multiple of 8 times.
Just use in front of your code
jQuery.fx.interval = 1000;
Default is 13 millisecounds
But the disadvantage is a global setting for all JQuery animations.

Smoothly snap a rotatable object to degree

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.

Javascript wait for one animation to finish before the next one begins

I have a Javascript function (Not jQuery) that animates a box opening or closing. The problem is that I close the box, run some code that changes the content, and then reopen it.
Now the "problem" is that the rest of the code is too fast, and so it never even manages to close, let alone reopen. I could make the animation not be allowed to run again internally unless the last one was finished, but this would limit it if I say, were to want to run it twice on two different objects.
So what's the best method to prevent this? My thought was possibly a timeout that says to wait before running the animation, but that seems hacky, an I wasn't sure if there was a better solution?
Thanks.
function animate(boxID, step, limit, speed){
// Add timeout property to animate function/object if it doesn't exist already
if(animate.timeout == undefined) animate.timeout = 0;
// Clear the current timeout
clearTimeout(animate.timeout);
// Initialize box and current box height
var box = document.getElementById(boxID);
var h = box.clientHeight;
// Check if we want the step needs to be changed to pos/neg based on which direction is wanted to be going
if(h < limit && step < 0 || // Positive
h > limit && step > 0){ // Negative
step *= -1;
}
// If the step is positive, then we need to be below the limit, or if negative, then greater than the limit
if((step > 0 && h <= limit - step) || (step < 0 && h >= limit - step)){
// Set new height
box.style.height = h + step + "px";
// Start new timeout
animate.timeout = setTimeout(function(){ animate(boxID, step, limit, speed, 1); }, speed);
}
else{
box.style.height = limit + "px"; // Set to the exact height
}
}
You could achieve this with a callback. Your animate function gets a plus parameter, a function to call when the animation is ready:
function animate(boxID, step, limit, speed, onReady){
When the animation is done, you call it:
else{
box.style.height = limit + "px"; // Set to the exact height
if (onReady) { onReady(); }
}
You also want to forward the callback to the timeout call:
setTimeout(function(){ animate(boxID, step, limit, speed, 1, onReady); }, speed);
So, you can call the function for multiple boxes like this:
animate(box1_id, close_step, close_limit, close_speed, function () {
// now box1 is closed, put something in. then:
animate(box1_id, open_step, open_limit, open_speed, null);
});
// then the same for box2, etc…
This way box1 and box2 will close simultaneously, and only reopen after the thing have been put inside.
Also, you can't store the timer on the function, because now it's running on multiple boxes. So you may store it on the boxes, or a separate place instead. For example create an object outside of the function and put all the boxes' timers in that:
var timeouts = {};
timeouts[box1_id] = setTimeout(…);

Run a function as far as a variable reaches specific values

I have a canvas game which calls a function incScore every time an action is performed in the game to increase the score.
Inside incScore I have a few if statements to draw a particular image to represent a level number on the canvas.
I also want to have a sound play once per level up. The way I've gone about things the lvlup sound will play every time the score matches the if statement.
Can anyone please help me get this so that the sound will only play once when the level changes and not again until the next level change? I'm also mention I'm using jQuery incase it has anything that could help me.
incScore(); //everytime an action in the game causes the score to increase
function incScore(){
if (scoreTotal < 500){
lvlimg = "L01";
drawLevel(lvlimg);
lvlupSound();
}
else if (scoreTotal > 500 && scoreTotal < 1000){
lvlimg = "L02";
drawLevel(lvlimg);
lvlupSound();
}
else{
lvlimg = "L03";
drawLevel(lvlimg);
lvlupSound();
}
}
You could shorten your function and use a semi static property to save the state. Using that, you can compare the current level to the previous and play a sound if they differ.
function incScore(){
incScore.level = incScore.level || 'L0'; //< initialize property
lvlimg = "L0" + scoreTotal < 500 ? 1 : scoreTotal < 1000 ? 2 : 3;
drawLevel(lvlimg);
if (incScore.level!=='L0' &&
incScore.level !== lvlimg) { lvlupSound(); };
// ^compare local level to current
incScore.level = lvlimg;
// ^ update local level
}
[edit, based on comment] The third line is a so called ternary, or conditional operator. See MDN. You can use more conditions.
To avoid playing a sound before the score has reached a first level, you could use
if (incScore.level!=='L0' && incScore.level !== lvlimg).
I've created a mockup jsFiddle
A simple solution could be comparing the current level to the old one, to detect when the level changed:
function scoreToLevel(score)
if(score < 500){
return 1
}else if (score < 1000){
return 2
}else{
return 3
}
}
function incScore()
var next_level = scoreToLevel(scoreTotal)
if(next_level !== current_level){
lvlimg = "L0" + next_level;
drawLevel(lvlimg)
lvlupSound()
}
}
The easiest solution is to factor the sound out of those if statements. If the levels are nice and regular like that(every 500 points) and the points always increase in a way that you will always land exactly on an even multiple of 500 when you level up, something like this should do:
if(scoreTotal % 500 === 0 && scoreTotal < 1001)
{
lvlupSound();
}
If you won't always land directly on the gate to the next level(maybe the player can earn anywhere between 1 and 15 points at a time) then you should be able to get by using something along the lines of this before you increment the player's score:
if( (scoreTotal % 500) > ((scoreTotal + increment) % 500)
{
lvlupSound();
}
if your level boundries are not regular like that obviously it gets a little bit more complex, but that should get you started.
That is because you have the in every statement for every score (which means from 0 to infinite).
You will need to write inner if statements such as;
if (scoreTotal < 500){
lvlimg = "L01";
drawLevel(lvlimg);
if(scoreTotal x times of each the level) // That means for each level completed
{
lvlupSound();
}
}
If your score increment is only 1, then only play the tone when the score equals the threshold for a new level.
If they can increase their score by more than 1, then you could pass the number of points in and check the score before and after to see if the numbers fall on each side of the threshold.
If that still doesn't work, some more info on the "level" and points would be appreciated.
Try something like this (demo):
var scoreTotal,
lastLevel = 0,
levels = [500, 1000, 2500, 5000, 10000, 25000, 50000, 75000],
currentLevel = 0,
lvlImg;
function incScore() {
while (scoreTotal > levels[currentLevel]) {
currentLevel++;
}
if (lastLevel !== currentLevel) {
lastLevel = currentLevel;
// gives a two digit number with a leading zero
lvlImg = ('0' + currentLevel).slice(-2);
drawLevel("L" + lvlimg);
lvlupSound();
}
}
Then you can easily add additional levels by adding the score cutoff to the levels variable.

Categories