Gravity implementation causing Obj to continue through object - javascript

I am having an issue with my implementation of gravity and collision on many objects on a canvas...I understand where I have gone wrong I just cannot for the life of me think of a solution to fix it.
I am checking if dropped objects reach a y position on the canvas and then reverse the velocity * friction to bounce the object...This works fine with one object or many objects. However, because of my conditional when the objects stack on top of each other the velocity never decreases as it is always being increased by the gravity, meaning the objects slowly pass through each other after they have finished bouncing around.
I have tried checking if "this" object is colliding however this will not work as the objects should be able to bounce until friction causes the velocity to decrease or increase to zero(or very close in this case).
Here is the function for gravity.
gravity() {
//If the objects y + the height and delta are greater than the canvases height reverse the velocity * friction also apply
//same friction to x to stop the objects from sliding
if (this.y + this.height + this.delta_y >= this.canvas.height) {
this.delta_y = -this.delta_y * this.FRICTION;
this.delta_x = this.delta_x * this.FRICTION;
//If the delta_y does not push the objects y over the canvas height increase the delta by the gravitational pull
} else {
this.delta_y += this.GRAVITY;
}
//Apply reversed velocity over x - works because no constant gravity being added
if (this.x + this.width >= this.canvas.width || this.x - this.width <= 0) {
this.delta_x = -this.delta_x * this.FRICTION;
}
//update the x and y coordinates with the new delta value
this.x += this.delta_x;
this.y += this.delta_y;
//set the new centre for collision.
this.center = {
"x": this.x + this.width / 2,
"y": this.y + this.height / 2
};
}
I also thought I maybe able to run a timer on the y variable for a few milliseconds and if it has not changed outside of a bound then I could stop movement. However, this is not ideal and im not sure if a timeout would be good as javascript is single threaded? I would be at home in python but I am struggling here.
I have enclosed the calling function here
gameLoop(timeStamp) {
// Calculate how much time has passed // was thinking I could use this to check y after some time
//but stopped and decided to post to stack
let secondsPassed = (timeStamp - this.oldTimeStamp) / 1000;
this.oldTimeStamp = timeStamp;
if (this.gameObjects.length > 0) {
//update all objects and pass all objects to update to check for collision
for (let i = 0; i < this.gameObjects.length; i++) {
this.gameObjects[i].update(this.gameObjects);
}
//clear the canvas ready for next draw
this.clearCanvas();
//draw the next iteration of the gameobjects
for (let i = 0; i < this.gameObjects.length; i++) {
this.gameObjects[i].draw();
}
}
window.requestAnimationFrame((timeStamp) => this.gameLoop(timeStamp));
}
Finally here is the update function for completeness
update(gameObjects) {
//impose gravity on this object
this.gravity();
//loop through objects and check for collision
for (let i = 0; i < gameObjects.length; i++) {
// if this object is the same as the iterated object skip it
if (this === gameObjects[i]){continue;}
//test variable to check collision on this object
this.colliding = this.collisionDetection(gameObjects[i]);
//if colliding resolve the collision
if (this.colliding) {
this.resolveCollision(this, gameObjects[i]);
}
}
}
Thank you, vanilla JS only please.

I think the problem is that, when the object is out of bounds, you reduce its speed, so it doesn't have enough speed to come back in 100%.
Maybe you can apply your "friction" in the next update, not the one with the collision.

For anyone that gets stuck on this I was able to get it to working by passing the game objects to the gravity method and then adding a check in the conditional to see if a collision has occured. Its not perfect and there are a few other things to sort but for now its progress.
code
gravity(objects) {
//initialize collision to false
let thisCollide = false;
//loop through all objects
for(let i = 0; i < objects.length; i++){
//If the iteration is on this object then skip
if(this === objects[i]) continue;
//if there is a collision && that collision is on the top of the current object
if(this.collisionDetection(objects[i]) && (this.y - objects[i].y <= 1)){
//set the collision to true and break
thisCollide = true;
}
}
//If the objects y + the height and delta are greater than the canvases height or the object has collide on the top
//reverse the velocity * friction also apply same friction to x to stop the objects from sliding
if (this.y + this.height + this.delta_y >= this.canvas.height || thisCollide) {
this.delta_y = -this.delta_y * this.FRICTION;
this.delta_x = this.delta_x * this.FRICTION;
//If the delta_y does not push the objects y over the canvas height increase the delta by the gravitational pull
} else {
this.delta_y += this.GRAVITY;
}
//Apply reversed velocity over x - works because no constant gravity being added
if (this.x + this.width >= this.canvas.width || this.x - this.width <= 0) {
this.delta_x = -this.delta_x * this.FRICTION;
}
//update the x and y coordinates with the new delta value
this.x += this.delta_x;
this.y += this.delta_y;
//set the new centre for collision.
this.center = {
"x": this.x + this.width / 2,
"y": this.y + this.height / 2
};
}

I would recommend this order:
Apply forces
Update Velocity
Update Position
In your case:
//apply acceleration
this.delta_y += this.GRAVITY;
//apply friction
this.delta_x -= this.delta_x * this.FRICTION;
this.delta_y -= this.delta_y * this.FRICTION;
//update position
this.x += this.delta_x;
this.y += this.delta_y;
//collision
if (this.x + this.width >= this.canvas.width || this.x - this.width <= 0) {
this.delta_x = -this.delta_x;
}
if (this.y + this.height >= this.canvas.height) {
this.delta_y = -this.delta_y;
}

Related

How can I change an object's speed in p5.js if the mouse is pressed in OOP

Hi guys i'm very new to p5/js and not quite good at OOP programming.
I want to drop a ball by changing its speed once the mouse is clicked.
After trying to write and edit the code many times, the p5 editor didn't show any error massages but there aren't anything shown up on the display. So, I need a hand and some advices to fix this kind of problem.
Thank you in advance (:
let ball1;
let circleX = 50;
let circleY = 50;
let xspeed = 0; // Speed of the shape
let yspeed = 0; // Speed of the shape
let xdirection = 1; // Left or Right
let ydirection = 1; // Top to Bottom
let rad =50;
function setup() {
createCanvas(400, 400);
ball1 = new Ball();
}
function draw() {
background(220);
ball1.x =20;
ball1.y = 50;
ball1.c = color(25,0,100)
ball1.body();
ball1.move();
ball1.bounce();
}
class Ball {
constructor(){
this.x = width/2;
this.y = height;
this.w = 30;
this.h = 30;
this.c = color(0,255,0);
this.xspeed = 0;
this.yspeed = 0;
}
body(){
noStroke();
fill(this.c);
ellipse(this.x, this.y, this.w, this.h);
}
move() {
//this.xpos = width / 2;
//this.ypos = height / 2;
this.x = this.x + this.xspeed * this.xdirection;
this.y = this.y + this.yspeed * this.ydirection;
}
bounce() {
if (this.x > width - rad || this.x < rad) {
this.xdirection *= -1;
}
if (this.y > height - rad || this.y < rad) {
this.ydirection *= -1;
}
}
if(mouseIsPressed) { // if the mouse is pressed
//set the new speed;
this.fill(0, 0, 0);
this.xspeed =1.5;
this.yspeed=1.5;
}
}
You've got some good beginnings to your code. I modified it just a bit to assist toward OOP programming. See the code at the end of my answer for the full source.
Solution
The main question you are asking involves setting the ball's speed when the mouse button is pressed. For that, you should declare a mousePressed() function in your code as such (borrowing from your if block):
function mousePressed() {
// if the mouse is pressed
// set the new speed;
ball.xspeed = 1.5;
ball.yspeed = 1.5;
}
That should set the speed of the ball and that should solve your problem.
Other thoughts
Some other steps I took to adjust your code to more working conditions include:
I created a reset() function to be used in the setup() function and potentially able to reset the animation whenever you would like.
I changed the rad variable declaration to const rad = 50 since it doesn't change
I changed the Ball constructor to include initial (x, y) coordinates when creating the ball (could be helpful if creating multiple balls)
If you want to create an arbitrary number of balls, create an array and use array.push(new Ball(x, y)) for each ball and loop through the array to move()/bounce()/body() each ball.
I'm assuming rad is meant to be the radius of the balls, so I set the this.w and this.h to rad * 2 since with and height would equate to the diameter.
let ball;
const rad = 50;
// let circleX = 50;
// let circleY = 50;
//let xspeed = 0; // Speed of the shape
//let yspeed = 0; // Speed of the shape
function setup() {
createCanvas(400, 400);
reset();
}
function draw() {
background(220);
ball.body();
ball.move();
ball.bounce();
}
function mousePressed() {
// if the mouse is pressed
//set the new speed;
ball.xspeed = 1.5;
ball.yspeed = 1.5;
}
function reset() {
ball = new Ball(width / 2, height / 2);
}
class Ball {
constructor(x, y) {
this.x = x;
this.y = y;
this.w = rad * 2;
this.h = rad * 2;
this.c = color(25, 0, 100);
this.xspeed = 0;
this.yspeed = 0;
this.xdirection = 1;
this.ydirection = 1;
}
body() {
noStroke();
fill(this.c);
ellipse(this.x, this.y, this.w, this.h);
}
move() {
this.x = this.x + this.xspeed * this.xdirection;
this.y = this.y + this.yspeed * this.ydirection;
}
bounce() {
if (this.x > width - rad || this.x < rad) {
this.xdirection *= -1;
}
if (this.y > height - rad || this.y < rad) {
this.ydirection *= -1;
}
}
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.0/lib/p5.min.js"></script>
Numbers before code are line numbers, they might be wrong, but should be close enough...
You need to move the 64 if(mouseIsPressed){} into a function like 47 move().
Also you haven't declared and initialized 39 this.xdirection
in the constructor(){}.
I think 67 this.fill(0, 0, 0) should be fill(0).
Maybe 24 ball1.body(); should be after the other methods (24 -> 26 line).
I think the if() statements in bounce(){} are wrong, since they'd need to have AND or && instead of OR or ||.
Also you might want to make the if(mouseIsPressed) into function mousePressed(){} mouseIsPressed is a variable that is true if mouse button is down; mousePressed is a function that gets called once when user presses the mouse button.
function mousePressed(){
ball1.xSpeed = 1.5
ball1.ySpeed = 1.5
}
There might very well be other things i haven't noticed.

Collision detection/player movement physics

I have an issue understanding and fixing an issue I am having. I have a collision map and if an element is a 1 it should trigger my collision detection function, which it does. I am pretty sure my problem exist with how I am controlling my characters movement but I can't figure out what to do to fix it. If I hit a wall I can still move through it even though I set player.vx to 0. So then I added player.x = cell.x - cell.w but doing that for all sides causes the character to be flung around depending on which side gets called first in my routing table.
I have also tried many many variations of adding velocity to my player to prevent the unwanted penetration.
Here's my players code
let friction = 0.9;
let gravity = 2;
let size = 32;
//player
class Player {
constructor() {
this.x = 256;
this.y = 96;
this.w = 32;
this.h = 32;
this.vx = 0;
this.vy = 0;
this.oldX = this.x;
this.oldY = this.y;
this.jumping = false;
}
draw() {
ctx.fillStyle = 'green';
ctx.fillRect(this.x, this.y, this.w, this.h)
}
update() {
this.oldX = this.x;
this.oldY = this.y;
if (controller.right) {this.vx += 1}
if (controller.left) {this.vx -= 1}
if (controller.up && !this.jumping) {this.vy -= 10; this.player = true}
if (controller.down) {this.vy += 1}
this.x += this.vx;
this.y += this.vy;
this.vx *= friction;
this.vy *= friction;
this.vy += gravity;
this.draw();
}
}
let player = new Player();
and a Codepen to make it easier to help https://codepen.io/jfirestorm44/pen/GRrjXGE?editors=0010
thanks in advance
EDIT: If anyone finds this I left a new CodePen in the comments below with a working example completely re-written.
Alright I think I got it
First off, it looks like leftCollision() and rightCollision() functions have the names mixed up.
The if conditional statements in both functions look correct to me, so I decided to rejected the new x value by assigning the oldX value to it:
function rightCollision(obj, cell) {
if (obj.x + obj.w >= cell.x && obj.oldX < obj.x) {
obj.vx = 0;
obj.x = obj.oldX; // <-- like this
}
};
How I approached debugging this
Focused on a single direction. I chose moving right
Noticed that it's detecting the left collision only after hitting the left arrow key
Printed the player and cell coordinates inside RightCollision() and noticed that the x and oldX 'seemed' correct to me
Rejected the new x value by assigning the oldX value to it.

p5.js repetition of the same function

I am learning p5.js and I don't quite understand how to repeat my function on the y-axis so that the lines appeared on top of the other. I understand that I would need to make a class object but all that I succeeded to do was to freeze the editor XD. Could you help me figure out how to make my function repeat itself with different Y starting point?
let walkers = []; // creation of an array
this.xoff = 0; //changed to go outside of the walker class
this.yoff = 0; //changed to go outside of the walker class
this.x = 0;
y = 200;
function setup() {
createCanvas(600, 600);
background(250);
for (let i = 0; i < 10; i++) { //mix array and class
walkers[i] = new walker(y);
}
}
function draw() {
for (i = 0; i < walkers.length; i++) { // consider the array lenght
walker[i].acceleration(); // call the class and it's function
walker[i].velocity();
walker[i].update();
walker[i].display();
}
}
class walker {
constructor(y) { //divide the class in multiple function
this.y = y
}
acceleration() {
this.accX = 0.1;
this.accY = 0.1;
this.px = this.x;
this.py = this.y;
}
velocity() {
this.velocityY = random(-20, 20);
this.velocityX = 5;
}
update() {
this.x = this.x + this.accX + this.velocityX * noise(this.xoff);
this.y = this.y + this.accY + this.velocityY * noise(this.yoff);
}
display() {
for (this.y < 200; this.y > 400; this.y + 20) {
line(this.x, this.y, this.px, this.py);
}
this.xoff = this.xoff + 1;
this.yoff = this.yoff + 100;
this.px = this.x;
this.py = this.y;
}
}
There are quite a few things wrong with how your code behaves.
Here are a few issues:
walkers it the array used,: e.g.walkers[i].acceleration();, not walker[i].acceleration(); (same hold true for the rest of the calls)
initialize variables if you plan to use them (otherwise using math operators update() will end up with NaN: e.g. this.x, this.xoff, this.yoff, etc.
it's unclear what motion behaviour you're after with position, velocity, acceleration, perlin noise, etc. (which btw are updated with strange increments ( this.yoff = this.yoff + 100;))
The code mostly freezes because of this:
for (this.y < 200; this.y > 400; this.y + 20)
It's unclear what you're trying to do there: this.y < 200; this.y > 400 makes me think you were going for an if condition to only draw lines between 200-400 px on Y axis, however this.y + 20 makes me think you want to increment y for some reason ?
It's also unclear why x,xoff,yoff moved out the walker class ?
There may be some understanding around classes, instances and the this keyword.
As per JS naming convention, class names should be title case.
I can tell you've put a bit of effort trying to build a more complex sketch, however it will won't be helpful for you to add complexity unless you understand all the parts of your code. This is where mastering the fundamentals pays off.
I recommend:
going back to pen/paper sketching to work out your idea until it's clearer
thinking of how you will achieve that by breaking complex tasks into smaller steps
writing basic test sketches for each step/component
Finally, bring one component at a time into a main program, testing again as components interact with another. Be sure to check out Kevin Workman's How to Program guide.
FWIW, here's a modified version of your code, with a tweak to set the initial y position of each Walker on top of each other:
let walkers = []; // creation of an array
y = 200;
function setup() {
createCanvas(600, 600);
background(250);
for (let i = 0; i < 10; i++) { //mix array and class
// incrementally add 10 pixels to y so the initially lines start on top of each other
walkers[i] = new Walker(y + (i * 10));
}
}
function draw() {
for (let i = 0; i < walkers.length; i++) { // consider the array length
// walkers, not walker
walkers[i].acceleration(); // call the class and it's function
walkers[i].velocity();
walkers[i].update();
walkers[i].display();
}
}
class Walker {
constructor(y) { //divide the class in multiple function
this.y = y;
// remember to init all variables you plan to use
this.xoff = 0; //changed to go back inside of the walker class
this.yoff = 0; //changed to go back inside of the walker class
this.x = 0;
}
acceleration() {
this.accX = 0.1;
this.accY = 0.1;
this.px = this.x;
this.py = this.y;
}
velocity() {
this.velocityY = random(-20, 20);
this.velocityX = 5;
}
update() {
this.x = this.x + this.accX + this.velocityX * noise(this.xoff);
this.y = this.y + this.accY + this.velocityY * noise(this.yoff);
}
display() {
// what were you trying ?
//for (this.y < 200; this.y > 400; this.y + 20) {
line(this.x, this.y, this.px, this.py);
//}
this.xoff = this.xoff + 1;
this.yoff = this.yoff + 1;
this.px = this.x;
this.py = this.y;
// reset x, y to 0
if(this.x > width){
this.x = 0;
}
if(this.y > height){
this.y = 0;
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>
It will definitely be easier to do it using a class. Make different functions inside the class which are responsible for update, movement, etc. You can make a display function too in which you can set the y co-ordinate using a For loop. In that way, it will become very easy to keep changing the y co-ordinate.
If you want to display multiple lines at once, do all of the above and also use an array to store the y co-ordinates and then display them in the For loop.
Do let me know if you need help with the actual code.

Smoothest following animation in canvas

I want to make a coordinate (coord1) follow another coordinate (coord2) as smoothly as possible with these following conditions:
coord2 is able to move.
coord1 follows coord2 in the smoothest way possible (arc fashion).
coord1 follows at a constant speed.
I have an example here but it only succeeds condition 1 and 3 not 2. In the example, you can move the ball with your arrow keys.
Click here to go to the example
Here is my code for following:
Obstacle.prototype.follow = function () {
this.y += this.vSpeed
this.x += this.hSpeed
if (this.x < ball.x - 9) {
this.hSpeed = 1;
}
if (this.x > ball.x - 10) {
this.hSpeed = -1;
}
if (this.y > ball.y - 10) {
this.vSpeed = -1;
}
if (this.y < ball.y - 9) {
this.vSpeed = 1;
}
}
Anyone have a solution that succeeds all three conditions?
To follow an object create a vector from tone object to the other. A vector has direction and length. The length is the speed and the direction is where it is going.
I have forked your fiddle to show this working. The only change is in the follow function. https://jsfiddle.net/blindman67/ksu518cg/2/
// obj one
var x1 = 100;
var y1 = 100;
// object to follow
var x2 = 300;
var y2 = 200;
Every animation frame the distance
var dist = Math.sqrt(Math.pow(x2-x1,2) + Math.pow(y2-y1,2)); // distance
Create a vector with length 1 pixel
var dx = (x2-x1)/dist;
var dy = (y2-y1)/dist;
Multiply by the speed you want to move
dx *= speed;
dy *= speed;
THen add to the objects position
x2 += dx;
y2 += dy;

top of paddle collision detection pong

This is for pong.
I can get collision of the ball and front paddle to fire but not the ball and top of paddle, but this code seems correct for it. This code is inside the ball object. The ball is a square image with correct width and height attributes.
// Paddle collision detection
// Hits front of paddle
if ((this.x) == (playerPaddle.x + playerPaddle.width) && (this.y > playerPaddle.y) && this.y < (playerPaddle.y + playerPaddle.height)) {
console.log("front connect");
this.vx = -this.vx;
}
// Hits top of paddle
if ((this.x + this.width) >= playerPaddle.x && this.x <= (playerPaddle.x + playerPaddle.width) && (this.y + this.height) == playerPaddle.y) {
console.log("top connect");
this.vy = -this.vy;
}
It fires if i change && (this.y + this.height) == playerPaddle.y) to && (this.y + this.height) > playerPaddle.y) but obviously this is wrong, causing it to fire whenever the ball is way below the paddle. It seems like a bug in the browser as it looks correct to me. I'm using chrome which seems to always work well.
Since a 2d ball is circular, you'll need to use trig functions to properly determine a connect with the edge. I suggest you check the general area first with a "quick check" to prevent slowing down your app.
// Paddle Collision Detection
// Start by a global check to see if the ball is behind the paddle's face
if (this.x <= playerPaddle.x + playerPaddle.width) {
// Get the ball's radius
var rad = this.width/2;
// Give yourself a 3px 'padding' area into the front of the paddle
// For the detection fo collisions to prevent collision issues caused
// By a ball moving > 1px in a given frame. You may want to adjust this number
var padding = 3;
// Detect if ball hits front of paddle
// y collision should be from 1/2 the ball width above the paddle's edge
// to 1/2 the ball width below the paddle's edge
if (this.x + padding >= playerPaddle.x + playerPaddle.width
&& this.y - rad >= playerPaddle.y
&& this.y + rad <= playerPaddle.y + playerPaddle.height) {
console.log("front connect");
this.vx = -this.vx;
// Next, do a "quick check" to see if the ball is in the correct
// general area for an edge collision prior to doing expensive trig math
} else if (this.y - this.height >= playerPaddle.y
&& this.y <= playerPaddle.y
&& this.x - rad >= playerPaddle.x) {
// Get the position of the center of the ball
var x = this.x + rad;
var y = this.y + rad;
// Get the position of the corner of the paddle
var px = playerPaddle.x + playerPaddle.width;
var py = playerPaddle.y;
if (this.y + this.height > playerPaddle.y) {
// if the ball is below the top edge, use the bottom corner
// of the paddle - else use the top corner of the paddle
py += playerPaddle.height;
}
// Do some trig to determine if the ball surface touched the paddle edge
// Calc the distance (C = sqrt(A^2 + B^2))
var dist = Math.pow(Math.pow(x - px, 2) + Math.pow(y - py, 2), 0.5);
// Check if the distance is within the padding zone
if (dist <= rad && dist >= rad - padding) {
// Get the angle of contact as Arc-sin of dx/dy
var angle = Math.asin(x - px / y - py);
// Adjust the velocity accordingly
this.vy = (-this.vy * Math.cos(angle)) + (-this.vx * Math.sin(angle));
this.vx = (-this.vx * Math.cos(angle)) + (-this.vy * Math.sin(angle));
}
}
}

Categories