I'm building a game using the canvas element powered by JavaScript. Part of my player class includes an update() method which is called once per tick. In this method, I'm doing some math to update the players velocity based on keyboard input and also to move the player. Here's a chunk of that code:
// Gradually make the players velocity 0
if(this.xv > 0) {
this.xv -= 0.1;
} else if(this.xv < 0) {
this.xv += 0.1;
}
if(this.yv > 0) {
this.yv -= 0.1;
} else if(this.yv < 0) {
this.yv += 0.1;
}
// Update player position based on velocity
this.x += this.xv;
this.y += this.yv;
// Update velocity based on keyboard inputs
if(keyState[87]) {
this.yv -= 0.5;
}
if(keyState[65]) {
this.xv -= 0.5;
}
if(keyState[83]) {
this.yv += 0.5;
}
if(keyState[68]) {
this.xv += 0.5;
}
Now in theory this should all work okay, if the player holds down the W key for 4 ticks their velocity will be 2, and then after 40 more ticks their velocity will be reduced down to 0.
This doesn't work in practice though, as JavaScript doesn't seem to be entirely accurate when it comes to working with floating point numbers. If I console.log() the velocity variables each game tick I get this kind of output:
x: -1.0241807402167069e-14 y: -1.379452108096757e-14
x: 0.09999999999998976 y: 0.09999999999998621
x: -1.0241807402167069e-14 y: -1.379452108096757e-14
x: 0.09999999999998976 y: 0.09999999999998621
So there's two things that look wrong here, one is that JavaScript never calculates the velocity with good precision, and the other is when the velocity is negative it's always at least -1, which is a problem because the player sprite will now move at 1 pixel per tick.
How can I get more accurate calculations for this task?
I recommend using an "exponential decay" approach to smoothing out your velocity transitions. Instead of the conditional block you currently use to reduce the velocity to zero, I'd use the following:
this.xv *= 0.9;
this.yv *= 0.9;
Note that this caps your maximum velocity at ten times your constant acceleration, and that changing the rate of decay will change that multiplier. This might be okay, but if it's not, you can tweak the equations to ease into a target velocity:
var p = 0.9;
var q = 1.0 - p;
this.xv = this.xv * p + this.txv * q;
this.yv = this.yv * p + this.tyv * q;
Disclaimer: This has no real basis in Newtonian physics; it's just something I came up with to help smooth out state transitions in a robot. But it worked great for me, and hopefully will for you, too.
Related
I have a 3D Render of moving cubes, they are different colors, so it's like a rainbow. But I want to know if there is a way to make the squares pulse colors.
https://repl.it/#AlexanderLuna/R-A-I-N-B-O-W#index.html
colorMode(HSB, nums.x * nums.y, 1, 1) Is your answere.
Apply it in the update function and play around with the colors by altering 'nums.x * nums.y' values.
Use a timer or a simple tick (you can simply do tick++ in the update function) as a modifier until it reaches a certain iteration and then reset (or jump the value). You should get the desired effect.
Okey.. apparently I'm procrastinating and spent the last hour playing around with your Repl..
This might not be exactly what you're after, but maybe it'll help some..
class Cube {
constructor(x_, y_, z_, size_, offset_) {
this.x = x_;
this.y = y_;
this.z = z_;
this.size = size_;
this.offset = offset_;
this.angle = 0;
this.tick = 1; // starting point
this.hueSpeed = 2; // tick modifier
}
update(f) {
this.y = map(f(this.angle + this.offset), -1, 1, this.size / 2, height - this.size / 2);
this.angle += 0.05;
colorMode(HSB, this.tick, 1, 1);
/**
* The request is there to simply regulate the frequency of the tick a bit..
* Though we do need to cancel the previous request if hadn't yet fired
* Which I'm apparently to lazy to do atm
*/
window.requestAnimationFrame((e)=>{
this.tick += this.hueSpeed;
(this.tick > 150 || this.tick < 2) && (this.hueSpeed *= -1);
});
}
render() {
push();
stroke(0);
translate(this.x, this.y, this.z);
box(this.size);
pop();
}
}
This is the cube.js script file, the only one altered.
Im am creating a COVID-19 simulator where every circle in the simulation is a person. When two persons hit each other, i want the direction that they "bounce" off each other to be random. Currently i just mirror the current speed, which means the the persons follow a pre defined path, even when bouncing of each other.
This is my "move" function
move() {
if (this.willMove) {
this.xPos += this.xSpeed;
this.yPos += this.ySpeed;
}
}
This where i do my collision detection
collision(other) {
let distance = dist(this.xPos, this.yPos, other.xPos, other.yPos);
if (distance < this.personRadius + other.personRadius) {
this.changeDirection();
return true;
} else {
return false;
}
}
The things handeling the changing of direction:
changeDirection() {
this.mirrorXSpeed();
this.mirrorYSpeed();
}
mirrorXSpeed() {
this.xSpeed = this.xSpeed * -1;
}
mirrorYSpeed() {
this.ySpeed = this.ySpeed * -1;
}
I have tried multiplying the speed by -0.95, but this just decreases the speed.
The full project can be found here: https://github.com/perkynades/Simulation-of-COVID19/tree/part1
This might help, it will make all the people bounce in a random direction when called.
You will have to add this.speed to your person constructor, and make sure angle mode is set to radians.
changeDirection() {
//This next line will change the speed by a random amount,
//delete it if you don't like it,
//or change the range for a different behavior
this.speed += random(1,3) * random([-1,1])
let ang = random(PI * 2)
this.changeXSpeed(ang);
this.changeYSpeed(ang);
}
changeXSpeed(ang) {
this.xSpeed = this.speed * cos(ang);
}
changeYSpeed(ang) {
this.ySpeed = this.speed * sin(ang);
}
}
If you want a more realistic bounce, I would check out Chris courses video on Collision detection it gives a natural fell to circle on circle collisions, and even though it is not all P5.js, it is was very helpful to me and I hope that it will be for you too.
I have simple function that moves a circle in specific direction:
var rad = (a) => Math.PI / 180 * a;
this.x += Math.cos(rad) * this.throttle();
this.y += Math.sin(rad) * this.throttle();
I am also calculating distance to a target:
var distance = (p1, p2) => Math.sqrt( (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) );
this.destination_distance = parseInt(distance( { x: this.x, y: this.y }, { x: x, y: y } ));
I started to work on this.throttle function but i cannot get my head around it.
I wanted to achieve simple thing, when circle starts to move i want to increase speed from min to max by some step and when it is close to destination i want it to start slow down until it reaches min.
This is my current approach:
this.min_speed = 0.1;
this.max_speed = 1.5;
this.current_speed = 0.1;
this.throttle = function() {
if(this.destination_distance > 300) {
this.current_speed += 0.002;
} else {
this.current_speed -= 0.002;
}
if(this.current_speed < this.min_speed) {
this.current_speed = this.min_speed;
}
if(this.current_speed > this.max_speed) {
this.current_speed = this.max_speed;
}
return this.current_speed;
};
This doesnt work, because if the distance is smaller then 300 it doesnt speed up at all its always on min speed, so i suppose it should be somehow related to the distance variable. Maybe someone could help me solve this problem.
You need to calculate the difference between target speed and current speed, then add some specific fraction of that.
this.throttle = function() {
var target_speed = this.destination_distance > 300 ? this.max_speed : this.min_speed;
var diff = target_speed - this.current_speed;
this.current_speed += diff * laziness;
return this.current_speed;
};
laziness is something between 0.0001 and 1; the greater the value the faster the change of velocity.
Your starting speed is your minimum speed, so if the starting distance is <300 the circle will never speed up or slow down. Why not make your starting speed depend on the initial distance? Here's a crude implementation
if (this.destination_distance<300) {
this.current_speed = 1.5; //start fast when in the deceleration zone
} else {
this.current_speed = 0.1; //start slow when in the acceleration zone
}
Why don't you use something like percent of total distance passed? That way you will avoid having hardcoded threshold of 300 and it should work with distances of arbitrary length (with some tweaking of the speeds). Your changed function would look something like this:
this.throttle = function() {
if(this.destination_distance > 0.5 * total_distance) {
this.current_speed += 0.002;
} else {
this.current_speed -= 0.002;
}
if(this.current_speed < this.min_speed) {
this.current_speed = this.min_speed;
}
if(this.current_speed > this.max_speed) {
this.current_speed = this.max_speed;
}
return this.current_speed;
};
Here I consider total_distance to be the distance from the start to the final destination. This way the circle will travel approximately half of the way with increasing speed and the other half with decreasing speed. If you replace 0.5 by let 0.8 then the speed will increase for the first 80% and decrease for the last 20% of the path.
The only thing that I do inside the animation loop is update the x and y coordinates but the circle is still not moving as smoothly as it should. This is the fiddle. I am using CraftyJS to animate the circle. Here is the code that does the animation:
.bind("EnterFrame", function (eventData) {
this.x += this.xDirection;
this.y += this.yDirection;
if (this.x < 0) this.xDirection *= -1;
if (this.y < 0) this.yDirection *= -1;
if (this.x > (0.96*gWidth)) this.xDirection *= -1;
if (this.y > (0.96*gHeight)) this.yDirection *= -1;
});
Rest of the calculations are done just once and I don't think just a bunch of multiplications should make the animation lag. Any help on how to make the animation smooth will be appreciated.
I failed to mention earlier that xDirection is equal to 0.005*gWidthand yDirection is equal to 0.005*gHeight. If gWidth is 600 the ball is still moving just 3px. Is it really that fast? I don't want to specify the width in pixels (gWidth is the screen size) because then the gameplay will be different on different devices. Is there some way to move the circle quickly while still keeping the animation smooth?
Changing from 'fixed' to 'variable' steptype smoothed things out for me.
After Crafty.init, call Crafty.timer.steptype():
const _step = 20;
Crafty.init(gWidth, gHeight, document.getElementById('game'));
Crafty.timer.steptype('variable', _step);
// ...
You may also want to update your EnterFrame to take in to account time elapsed since the last frame:
.bind("EnterFrame", function (eventData) {
let dt = eventData.dt;
this.x += this.xDirection * dt / _step;
this.y += this.yDirection * dt / _step;
// ...
this is my first question after having relied on this site for years!
Anyway, I'd like to accomplish something similar to this effect:
http://www.flashmonkey.co.uk/html5/wave-physics/
But on a circular path, instead of a horizon. Essentially, a floating circle/blob in the center of the screen that would react to mouse interaction. What I'm not looking for is gravity, or for the circle to bounce around the screen - only surface ripples.
If at all possible I'd like to apply a static texture to the shape, is this a possibility? I'm completely new to Canvas!
I've already tried replacing some code from the above example with circular code from the following link, to very limited success:
http://www.html5canvastutorials.com/tutorials/html5-canvas-circles/
If only it were that easy :)
Any ideas?
Thanks in advance!
I tried to figure out how wave simulation works using View Source and JavaScript console. It's working fine but threw some JS errors. Also, it seems physics update is entangled with rendering in the render() method.
Here is what I found about the code:
The mouseMove() method creates disturbances on the wave based on mouse position, creating a peak around the mouse. The target variable is the index of the particle that needs to be updated, it's calculated from mouse pos.
if (particle && mouseY > particle.y) {
var speed = mouseY - storeY;
particles[target - 2].vy = speed / 6;
particles[target - 1].vy = speed / 5;
particles[target].vy = speed / 3;
particles[target + 1].vy = speed / 5;
particles[target + 2].vy = speed / 6;
storeY = mouseY;
}
Then, the particles around target are updated. The problem I found is that it does no bounds checking, i.e. it can potentially particles[-1] when target == 0. If that happens, an exception is thrown, the method call ends, but the code does not stop.
The render() method first updates the particle positions, then renders the wave.
Here is its physics code:
for (var u = particles.length - 1; u >= 0; --u) {
var fExtensionY = 0;
var fForceY = 0;
if (u > 0) {
fExtensionY = particles[u - 1].y - particles[u].y - springs[u - 1].iLengthY;
fForceY += -fK * fExtensionY;
}
if (u < particles.length - 1) {
fExtensionY = particles[u].y - particles[u + 1].y - springs[u].iLengthY;
fForceY += fK * fExtensionY;
}
fExtensionY = particles[u].y - particles[u].origY;
fForceY += fK / 15 * fExtensionY;
particles[u].ay = -fForceY / particles[u].mass;
particles[u].vy += particles[u].ay;
particles[u].ypos += particles[u].vy;
particles[u].vy /= 1.04;
}
Basically, it's Hooke's Law for a chain of particles linked by springs between them. For each particle u, it adds the attraction to the previous and next particles (the if statements check if they are available), to the variable fForceY. I don't fully understand the purpose of the springs array.
In the last four lines, it calculates the acceleration (force / mass), updates the velocity (add acceleration), then position (add velocity), and finally, reduce velocity by 1.04 (friction).
After the physics update, the code renders the wave:
context.clearRect(0, 0, stageWidth, stageHeight);
context.fillStyle = color;
context.beginPath();
for (u = 0; u < particles.length; u++) {
...
}
...
context.closePath();
context.fill();
I'm not explaining that, you need to read a canvas tutorial to understand it.
Here are some ideas to get started, note that I didn't test these code.
To modify the code to draw a circular wave, we need introduce a polar coordinate system, where the particle's x-position is the angle in the circle and y-position the distance from center. We should use theta and r here but it requires a large amount of refactoring. We will talk about transforming later.
mouseMove(): Compute particle index from mouse position on screen to polar coordinates, and make sure the disturbance wrap around:
Define the function (outside mouseMove(), we need this again later)
function wrapAround(i, a) { return (i + a.length) % a.length; }
Then change
particles[target - 2] --> particles[wrapAround(target - 2, particles)]
particles[target - 1] --> particles[wrapAround(target - 1, particles)]
...
The modulo operator does the job but I added particles.length so I don't modulo a negative number.
render(): Make sure the force calculation wrap around, so we need to wrapAround function again. We can strip away the two if statements:
fExtensionY = particles[wrapAround(u - 1, particles)].y - particles[u].y - springs[wrapAround(u - 1, springs)].iLengthY;
fForceY += -fK * fExtensionY;
fExtensionY = particles[u].y - particles[wrapAround(u + 1, particles)].y - springs[warpAround(u, springs)].iLengthY;
fForceY += fK * fExtensionY;
Here is the result so far in jsfiddle: Notice the wave propagate from the other side. http://jsfiddle.net/DM68M/
After that's done, the hardest part is rendering them on a circle. To do that, we need coordinate transform functions that treat particle's (x, y) as (angle in the circle, distance from center), and we also need inverse transforms for mouse interaction in mouseMove().
function particleCoordsToScreenCoords(particleX, particleY) {
return [ radiusFactor * particleY * Math.cos(particleX / angleFactor),
radiusFactor * particleY * Math.sin(particleX / angleFactor) ];
}
function screenCoordsToParticleCoords(screenX, screenY) {
// something involving Math.atan2 and Math.sqrt
}
Where the ...Factor variables needed to be determined separately. The angleFactor is two pi over the highest x-position found among particles array
Then, in the coordinates supplied to the context.lineTo, context.arc, use the particleCoordsToScreenCoords to transform the coordinates.