I have made a simple sketch where 4 circles are moving at a given speed. I want to change the fill color when any of the circles touches another circle.
I think the problem is that I am initializing the x and y values for ellipse in my constructor object which I need to update as the circle moves, but I am not sure how to do that.
// Declare objects
let bubble1;
let bubble2;
let bubble3;
let bubble4;
var centDistance;
function setup() {
createCanvas(windowWidth, windowHeight);
ellipseMode(CENTER);
// Create objects
bubble1 = new Bubble(50, 0, 2);
bubble2 = new Bubble(50, 2, 0);
bubble3 = new Bubble(50, -2, 0);
bubble4 = new Bubble(50, 0, -2);
}
function draw() {
background(0);
bubble1.display();
bubble2.display();
bubble1.move();
bubble2.move();
bubble3.display();
bubble3.move();
bubble4.display();
bubble4.move();
var d = dist(bubble1.x, bubble1.y, bubble2.x, bubble2.y);
if (d < bubble1.r + bubble2.r) {
bubble1.changeCol();
bubble2.changeCol();
}
}
class Bubble {
constructor(r, xSpeed, ySpeed) {
this.x = width / 2;
this.y = height / 2;
this.r = r;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
this.fillColor = color(255);
this.move = function() {
if (this.x > width / 2 + 200 || this.x < width / 2 - 200) {
this.xSpeed = -this.xSpeed;
}
if (this.y > height / 2 + 200 || this.y < height / 2 - 200) {
this.ySpeed = -this.ySpeed;
}
this.x += this.xSpeed;
this.y += this.ySpeed;
}
this.display = function() {
fill(this.fillColor);
stroke(255);
ellipse(this.x, this.y, 2 * this.r);
}
this.changeCol = function() {
this.fillColor = color(0);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.js"></script>
The problem is that when the animation starts both circles are colliding, bubble1 and bubble2 in this example.
then you change the color to black inside your changeCol function and they both turn black and then stay that way indefinitely.
Your code is working the problem is that this.fillColor will not be re-initialized every re-render since you've instanciated your objects inside the setup function, so you have to re-paint the circles when they're not touching one another.
if (d < bubble1.r+bubble2.r) {
bubble1.changeCol(255);
bubble2.changeCol(255);
} else {
bubble1.changeCol(0);
bubble2.changeCol(0);
}
this.changeCol = function (col) {
this.fillColor = color(col);
}
Related
Can somebody fix it script to make it works properly?
What I expects:
Run script
Click at the canvas to set target (circle)
Object (triangle) starts to rotate and move towards to target (circle)
Change target at any time
How it works:
Sometimes object rotates correctly, sometimes isn't
Looks like one half sphere works well, another isn't
Thanks!
// prepare 2d context
const c = window.document.body.appendChild(window.document.createElement('canvas'))
.getContext('2d');
c.canvas.addEventListener('click', e => tgt = { x: e.offsetX, y: e.offsetY });
rate = 75 // updates delay
w = c.canvas.width;
h = c.canvas.height;
pi2 = Math.PI * 2;
// object that moves towards the target
obj = {
x: 20,
y: 20,
a: 0, // angle
};
// target
tgt = undefined;
// main loop
setInterval(() => {
c.fillStyle = 'black';
c.fillRect(0, 0, w, h);
// update object state
if (tgt) {
// draw target
c.beginPath();
c.arc(tgt.x, tgt.y, 2, 0, pi2);
c.closePath();
c.strokeStyle = 'red';
c.stroke();
// update object position
// vector from obj to tgt
dx = tgt.x - obj.x;
dy = tgt.y - obj.y;
// normalize
l = Math.sqrt(dx*dx + dy*dy);
dnx = (dx / l);// * 0.2;
dny = (dy / l);// * 0.2;
// update object position
obj.x += dnx;
obj.y += dny;
// angle between +x and tgt
a = Math.atan2(0 * dx - 1 * dy, 1 * dx + 0 * dy);
// update object angle
obj.a += -a * 0.04;
}
// draw object
c.translate(obj.x, obj.y);
c.rotate(obj.a);
c.beginPath();
c.moveTo(5, 0);
c.lineTo(-5, 4);
c.lineTo(-5, -4);
//c.lineTo(3, 0);
c.closePath();
c.strokeStyle = 'red';
c.stroke();
c.rotate(-obj.a);
c.translate(-obj.x, -obj.y);
}, rate);
This turned out to be a bit more challenging than I first thought and I ended up just re-writing the code.
The challenges:
Ensure the ship only rotated to the exact point of target. This required me to compare the two angle from the ship current position to where we want it to go.
Ensure the target did not rotate past the target and the ship did not translate past the target. This required some buffer space for each because when animating having this.x === this.x when an object is moving is very rare to happen so we need some room for the logic to work.
Ensure the ship turned in the shortest direction to the target.
I have added notes in the code to better explain. Hopefully you can implement this into yours or use it as is. Oh and you can change the movement speed and rotation speed as you see fit.
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 400;
let mouse = { x: 20, y: 20 };
let canvasBounds = canvas.getBoundingClientRect();
let target;
canvas.addEventListener("mousedown", (e) => {
mouse.x = e.x - canvasBounds.x;
mouse.y = e.y - canvasBounds.y;
target = new Target();
});
class Ship {
constructor() {
this.x = 20;
this.y = 20;
this.ptA = { x: 15, y: 0 };
this.ptB = { x: -15, y: 10 };
this.ptC = { x: -15, y: -10 };
this.color = "red";
this.angle1 = 0;
this.angle2 = 0;
this.dir = 1;
}
draw() {
ctx.save();
//use translate to move the ship
ctx.translate(this.x, this.y);
//angle1 is the angle from the ship to the target point
//angle2 is the ships current rotation angle. Once they equal each other then the rotation stops. When you click somewhere else they are no longer equal and the ship will rotate again.
if (!this.direction(this.angle1, this.angle2)) {
//see direction() method for more info on this
if (this.dir == 1) {
this.angle2 += 0.05; //change rotation speed here
} else if (this.dir == 0) {
this.angle2 -= 0.05; //change rotation speed here
}
} else {
this.angle2 = this.angle1;
}
ctx.rotate(this.angle2);
ctx.beginPath();
ctx.strokeStyle = this.color;
ctx.moveTo(this.ptA.x, this.ptA.y);
ctx.lineTo(this.ptB.x, this.ptB.y);
ctx.lineTo(this.ptC.x, this.ptC.y);
ctx.closePath();
ctx.stroke();
ctx.restore();
}
driveToTarget() {
//get angle to mouse click
this.angle1 = Math.atan2(mouse.y - this.y, mouse.x - this.x);
//normalize vector
let vecX = mouse.x - this.x;
let vecY = mouse.y - this.y;
let dist = Math.hypot(vecX, vecY);
vecX /= dist;
vecY /= dist;
//Prevent continuous x and y increment by checking if either vec == 0
if (vecX != 0 || vecY != 0) {
//then also give the ship a little buffer incase it passes the given point it doesn't turn back around. This allows time for it to stop if you increase the speed.
if (
this.x >= mouse.x + 3 ||
this.x <= mouse.x - 3 ||
this.y >= mouse.y + 3 ||
this.y <= mouse.y - 3
) {
this.x += vecX; //multiple VecX by n to increase speed (vecX*2)
this.y += vecY; //multiple VecY by n to increase speed (vecY*2)
}
}
}
direction(ang1, ang2) {
//converts rads to degrees and ensures we get numbers from 0-359
let a1 = ang1 * (180 / Math.PI);
if (a1 < 0) {
a1 += 360;
}
let a2 = ang2 * (180 / Math.PI);
if (a2 < 0) {
a2 += 360;
}
//checks whether the target is on the right or left side of the ship.
//We use then to ensure it turns in the shortest direction
if ((360 + a1 - a2) % 360 > 180) {
this.dir = 0;
} else {
this.dir = 1;
}
//Because of animation timeframes there is a chance the ship could turn past the target if rotating too fast. This gives the ship a 1 degree buffer to either side of the target to determine if it is pointed in the right direction.
//We then correct it to the exact degrees in the draw() method above once the if statment defaults to 'else'
if (
Math.trunc(a2) <= Math.trunc(a1) + 1 &&
Math.trunc(a2) >= Math.trunc(a1) - 1
) {
return true;
}
return false;
}
}
let ship = new Ship();
class Target {
constructor() {
this.x = mouse.x;
this.y = mouse.y;
this.r = 3;
this.color = "red";
}
draw() {
ctx.strokeStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2, false);
ctx.stroke();
}
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(0, 0, canvas.width, canvas.height);
ship.draw();
ship.driveToTarget();
if (target) {
target.draw();
}
requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>
I'm attempting to write a JavaScript program where the ball ignores hitting a green obstacle but ends the game when a red obstacle indicates game over. However in my code the game ends when the ball hits an obstacle of any color.
My definition of the obstacle is:
function Obstacle(x, size, horizon, color) {
this.x = x;
this.y = horizon - size;
this.size = size;
this.color = color;
this.onScreen = true;
}
/**
* handle x and onScreen values
*/
Obstacle.prototype.update = function(speed) {
/* check if offscreen */
this.onScreen = (this.x > -this.size);
/* movement */
this.x -= speed;
};
Obstacle.prototype.draw = function() {
fill(this.color);
stroke(255);
strokeWeight(2);
rect(this.x, this.y, this.size, this.size);
};
Obstacle.prototype.hits = function(ball) {
var halfSize = this.size / 2;
var minimumDistance = halfSize + (ball.radius); // closest before collision
/* find center coordinates */
var xCenter = this.x + halfSize;
var yCenter = this.y + halfSize;
var distance = dist(xCenter, yCenter, ball.x, ball.y); // calculate distance from centers
return (distance < minimumDistance); // return result
};
This is the code for the game:
function setup() {
createCanvas(600, 200);
textAlign(CENTER);
horizon = height - 40;
score = 0;
obstacleSpeed = 6;
var size = 20;
ball = new bol(size * 2, height - horizon, size);
textSize(20);
}
function draw() {
background(51);
drawHUD();
handleLevel(frameCount);
ball.update(horizon);
handleObstacles();
//handlepowerups();
}
/**
* draws horizon & score
*/
function drawHUD() {
/* draw horizon */
stroke(255);
strokeWeight(2);
line(0, horizon, width, horizon);
/* draw score */
noStroke();
text("Score: " + score, width / 2, 30);
ball.draw();
}
/**
* updates, draws, and cleans out the obstacles
*/
function handleObstacles() {
for (var i = obstacles.length - 1; i >= 0; i--) {
obstacles[i].update(obstacleSpeed);
obstacles[i].draw();
if (obstacles[i].hits(ball)) // if there's a collision
{
if (obstacles[i].color = color(225, 0, 0))
endGame();
else
obstacles[i].color = color(0, 0, 225)
}
if (!obstacles[i].onScreen) // if it's no longer showing
obstacles.splice(i, 1); // delete from array
}
}
/**
* speeds game up, pushes new obstacles, & handles score
*/
function handleLevel(n) {
if (n % 100 === 0) { // every 0.5 seconds
var n = noise(n);
if (n > 0.5)
newObstacle(n); // push new obstacle
if (n % 120 === 0) // every 2 seconds
obstacleSpeed *= 1.05; // speed up
}
score++;
}
/**
* pushes random obstacle
*/
function newObstacle(n) {
var col1 = color(225, 0, 0);
var col2 = color(0, 225, 0);
var size = random(25) + 20;
var obs = new Obstacle(width + size, size, horizon, col2);
var obs2 = new Obstacle(width + size, size, horizon, col1);
var cases = random(1);
if (cases < 0.5)
obstacles.push(obs);
else
obstacles.push(obs2)
}
function keyPressed() {
if ((keyCode === UP_ARROW || keyCode === 32) && ball.onGround) // jump if possible
ball.jump();
boingaudio.play()
}
function endGame() {
noLoop();
noStroke();
textSize(40);
text("GAME OVER", width / 2, height / 2);
if (highscore !== null) {
if (score > highscore) {
localStorage.setItem("highscore", score);
}
} else {
localStorage.setItem("highscore", score);
}
textSize(20);
text("Highscore: " + highscore, width / 2, height / 2 + 20);
gameoveraudio.play()
}
I don't understand why the ball keeps ending game for no matter what colored ball appears. Can you help me figure out what's wrong?
You seem to be assigning a value to obstacles[i].color rather than testing it:
if (obstacles[i].color = color(225, 0, 0))
endGame();
else
obstacles[i].color = color(0, 0, 225)
This should probably read:
if (obstacles[i].color == color(225, 0, 0))
endGame();
Without your else, which seemed to serve no purpose.
When a user clicks on any particle I want it to expand and fade and upon collision with any other particle and that particle will also expand and fade. Now my problem is that I want to know if there is a way in which I can get those particles (made with constructor in this case) to effect each other when they get collide. Link to Codepen
var bubbles = [];
function setup() {
frameRate(25);
// Creates Canvas
createCanvas(windowWidth, windowHeight);
//Genrates 100 Particles with random a & y
for (var i = 0; i < 80; i++) {
var x = random(width);
var y = random(height);
bubbles[i] = new Bubble(x, y);
}
}
function mousePressed() {
for (var i = 0; i < bubbles.length; i++) {
bubbles[i].clicked();
}
}
function draw() {
clear();
//Adds color and motion
for (var bubble of bubbles) {
fill(bubble.color.red, bubble.color.green, bubble.color.blue);
bubble.move();
bubble.display();
}
}
function Bubble(x, y) {
this.x = x;
this.y = y;
this.wh = 15;
this.speedX = random(1, 5);
this.speedY = random(1, 5);
//Individual Particle Creation
this.display = function() {
noStroke();
ellipse(this.x, this.y, this.wh, this.wh);
};
//Interactivity
this.clicked = function() {
var d = dist(this.x, this.y, mouseX, mouseY);
if (d < 8) {
this.wh = 100;
}
};
//Randomizes colors
this.color = {
red: random(255),
green: random(255),
blue: random(255)
};
//Particle Motion
this.move = function() {
//Motion in X direction
this.x += this.speedX;
//Bouncing back on X-axis
if (this.x > windowWidth || this.x < 0) {
this.speedX = -this.speedX;
}
//Motion in Y Direction
this.y += this.speedY;
//Bouncing back on Y-axis
if (this.y > windowHeight || this.y < 0) {
this.speedY = -this.speedY;
}
};
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
::-webkit-scrollbar{
display:none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.10/p5.js"></script>
Use a nested for loop.
Step 1: Loop over the bubbles. Do this with a for loop.
Step 2: For each bubble, loop over the rest of the bubbles (if you're on bubble 4, start with bubble 5). Do this with another for loop inside the first one.
Step 3: Now that you have two bubbles, do the collision between them.
If you're having trouble getting that working, then please start smaller. Start with a simpler program that just shows two hard-coded bubbles and does collision detection between them.
I created a collision detection between Snake and BasicEnemy. I created a for loop to make five different enemies but the collision detection doesn't get called on any of the enemies that were created from the for loop. The collision only works with the one BasicEnemy object. Why isn't collision function being called for all of the enemies inside the array? Thank you.
Sketch.js
var snake;
var food;
var basicEnemy;
var scl = 20;
var enemies = [];
function setup() {
createCanvas(600, 500);
snake = new Snake();
basicEnemy = new BasicEnemy();
//** CREATE FIVE ENEMIES **
for (var i = 0; i < 5; i++) {
enemies[i] = new BasicEnemy();
}
}
// **FUNCTION WHEN SNAKE HITS ENEMY**
function collision() {
console.log("hit!");
}
function draw() {
background(51);
//Draw snake
snake.update();
snake.show();
//Draw basicEnemy
basicEnemy.update();
basicEnemy.show();
//** LOOP THROUGH ENEMIES AND UPDATE AND SHOW **
for (var i = 0; i < enemies.length; i++) {
enemies[i].show();
enemies[i].update();
if (enemies[i].hits(snake)) {
collision();
}
}
}
function keyPressed() {
if (keyCode === UP_ARROW){
snake.dir(0, -1);
} else if (keyCode === DOWN_ARROW) {
snake.dir(0, 1);
} else if (keyCode === LEFT_ARROW) {
snake.dir(-1 , 0);
} else if (keyCode === RIGHT_ARROW) {
snake.dir(1 , 0);
}
}
BasicEnemy.js
function BasicEnemy() {
this.x = random(700);
this.y = random(700);
this.velX = 15;
this.velY = 15;
}
//** FUNCTION TO CHECK IF ENEMY AND SNAKE ARE IN THE SAME LOCATION **
this.hits = function (pos) {
var = d = dist(this.x, this.y, pos.x, pos.y);
if(d < 1) {
return true;
} else {
return false;
}
}
this.show = function () {
fill(255, 0, 100);
rect(this.x, this.y, scl, scl);
}
Snake.js
function Snake() {
this.x = 0;
this.y = 0;
this.xspeed = 1;
this.yspeed = 0;
this.update = function() {
this.x = this.x + this.xspeed * scl;
this.y = this.y + this.yspeed * scl;
this.x = constrain(this.x, 0, width - scl);
this.y = constrain(this.y, 0, height - scl);
}
this.show = function() {
fill(255);
rect(this.x, this.y, scl, scl);
}
this.dir = function (x , y) {
this.xspeed = x;
this.yspeed = y;
}
}
Because you're essentially checking for the distance between the top left corners of the snake and the enemy, this'll only return true, if they completely overlap.
Use an AABB collision detection instead:
return this.x + scl >= pos.x && this.x <= pos.x + scl && this.y + scl >= pos.y && this.y <= pos.y + scl;
This returns true, if the first rectangle contains the second rectangle.
MDN says:
One of the simpler forms of collision detection is between two rectangles that are axis aligned — meaning no rotation. The algorithm works by ensuring there is no gap between any of the 4 sides of the rectangles. Any gap means a collision does not exist.
This question already has an answer here:
Comparing x/y of two positions on a canvas
(1 answer)
how to make a canvas element follow another canvas element smoothly at the same speed [duplicate]
Closed 6 years ago.
I'm wondering today how to make a canvas element follow another canvas element smoothly. For example, I'm trying to make a game where a canvas element continually follows the player (which can be moved using W, A, S, & D) smoothly. I had an idea to use the Pythagorean theorem to check for the closest & fastest way to move from Point A (the canvas element) to Point B (the player). However, I have no physical way to do this. Does anyone have any ideas or answers of how I could make a canvas element constantly follow a player as smoothly as possible so it reaches the player the fastest?
Here I have an example of a very bad way of following:
<!DOCTYPE html>
<html>
<head>
<title>Target Following Test</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-2.1.0.js"></script>
<center>
<canvas id="canvas" width="800" height="500"></canvas>
</center>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var width = canvas.width;
var height = canvas.height;
var circle = function(x, y, radius, fillCircle, color) {
ctx.beginPath();
ctx.fillStyle = color;
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
if (fillCircle) {
ctx.fill();
} else {
ctx.stroke();
}
};
var drawRect = function(x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, 20, 20)
}
//Moving Obstacle
var Obstacle = function(x, y) {
this.x = x;
this.y = y;
this.vSpeed = 0;
this.hSpeed = 0;
}
Obstacle.prototype.drawOb = function(color) {
drawRect(this.x, this.y, "Red")
}
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;
}
}
Obstacle.prototype.checkCollision = function(direction) {
return (ball.x - ball.radius < this.x + 20) &&
(ball.x + ball.radius > this.x) &&
(ball.y - ball.radius < this.y + 20) &&
(ball.y + ball.radius > this.y);
}
// The Ball constructor
var Ball = function() {
this.x = 20
this.y = 20
this.xSpeed = 0;
this.ySpeed = 0;
this.radius = 10;
};
// Draw the ball at its current position
Ball.prototype.draw = function() {
circle(this.x, this.y, 10, true, "Black");
};
Ball.prototype.reposition = function(reX, reY) {
this.x = reX;
this.y = reY;
}
// Update the ball's position based on its speed
Ball.prototype.move = function() {
this.x += this.xSpeed;
this.y += this.ySpeed;
if (this.x < 11) {
this.x = 11;
} else if (this.x > width - 11) {
this.x = width - 11;
} else if (this.y < 11) {
this.y = 11;
} else if (this.y > height - 11) {
this.y = height - 11;
}
};
// Set the ball's direction based on a string
Ball.prototype.setDirection = function(direction) {
if (direction === "up") {
this.xSpeed = 0;
this.ySpeed = -2;
} else if (direction === "down") {
this.xSpeed = 0;
this.ySpeed = 2;
} else if (direction === "left") {
this.xSpeed = -2;
this.ySpeed = 0;
} else if (direction === "right") {
this.xSpeed = 2;
this.ySpeed = 0;
} else if (direction === "stop") {
this.xSpeed = 0;
this.ySpeed = 0;
}
};
function simulate() {
var prev_ball_x = ball.x;
var prev_ball_y = ball.y;
var prev_fol_x = follower.x;
var prev_fol_y = follower.y;
ball.move();
follower.follow()
if (follower.checkCollision()) {
ball.setDirection('stop');
follower.vSpeed = 0;
follower.hSpeed = 0;
follower.x = prev_fol_x;
follower.y = prev_fol_y;
ball.x = prev_ball_x;
ball.y = prev_ball_y;
}
}
function draw() {
ctx.clearRect(0, 0, width, height);
ball.draw();
follower.drawOb();
ctx.strokeRect(0, 0, width, height);
}
// An object to convert keycodes into action names
var keyActions = {
37: "left",
38: "up",
39: "right",
40: "down"
};
// The keydown handler that will be called for every keypress
$("body").keydown(function(event) {
var direction = keyActions[event.keyCode];
ball.setDirection(direction);
});
$("body").keyup(function(event) {
ball.setDirection('stop');
})
setInterval(function() {
// separate drawing and simulating phases
simulate();
draw();
}, 10);
// Create all the Objects!
var ball = new Ball();
var follower = new Obstacle(400, 100);
</script>
</body>
</html>
Note: i haven't really inspected your code...
But hopefully I understand your question correctly. And if I do, the solution could be pretty simple.
The most simple and fast way is to move the canvas element in a straight line to the player, without the help of mr Pythagoras. For that you need to know the player's position (x, y), which you do.
I took an easing function from an AS3 question, but it's the same for JS: AS 3 simple ease
On every update, ease the follower to the position of the player:
follower.x += (player.x - follower.x) / delay;
follower.y += (player.y - follower.y) / delay;
Example: Fiddle
It isn't a drop-in fix for your script, but hopefully it's helpful