I'm currently creating a little game to learn to code in p5.js.
The bug I'm facing is when I try to shoot missils[array of object] the Keyboard Arrow at comets that fly around [also an array of objects]. In order to understand if the missils would shoot the comet, I created a class method that compare their distance and if their distance is less than for ex 20, both the missil and the comets are getting spliced from their array.
It works randomly, sometimes it works for 20 comets and sometimes it lags at the first encounter. I suspect that this has to do with the nested for loops that I'm using.
First of all I'm creating 2 objects MOVER every 5 seconds in the Setup function and store them into an array.
for(let i=0; i<2; i++) {
{
setInterval(
function(){
movertest = new Mover(random(400, width), random(400, height));
mover.push(movertest);
},
5000)
};
}
I basically created then a method called "destroy" in my object Mover, which indicates whether or not a missil is less then 20 pixels away.
destroy(px, py) {
if( dist(this.position.x, this.position.y, px, py) <= 20) {
return true
} else {
return false;
}
}
and then when I summon those functions in the draw functions of P5.js, it is lagging.
for (i=0; i < missil.length; i++) {
missil[i].show();
missil[i].update();
for (p=0; p < planets.length; p++) {
missil[i].bounce(planets[p].position.x, planets[p].position.y, planets[p].lrect/2)
};
//if missil is in the canvas then check for collision
if (missil[i].contains(width/2,height/2)) {
for (let u = 0; u < mover.length; u++) {
if (mover[u].destroy(missil[i].position.x, missil[i].position.y)) {
mover.splice(u,1);
missil.splice(i,1);
console.log(u);
console.log(i)
};
}
} else {
missil.splice(i,1)
}
} // if missil is out of the canvas it get erased from the array.
If you would like to help me that would be super appreciated! If you want to play the game to understand the error, you can move green rectangle with your mouse, and avoid the comets and shoot them with the Keyboard arrow! thanks a lot ! :)
Here is the entire code:
let mover = [];
let x = 0;
let collision = 0; //counter to know when to end the game
let collu = 0;
let lrect = 10; //size of the ship.
let accslider, velslider;
let shoot = false; //to know if to shoot a missil.
let started = true;
let movertest = [];
//number of missil.
let missil = [];
let outofcanvas;
let a = 0;
let b = 0;
let munitions = 100;
let planets = [];
let cron;
function windowalert() {
if (confirm("Votre score est de rejouer?")) {
location.reload()
} else {
location.close();
}
}
function setup() {
background(0, 1);
cnv = createCanvas(900, 600);
for (let i = 0; i < 2; i++) {
setInterval(function() {
movertest = new Mover(random(400, width), random(400, height));
mover.push(movertest);
}, 5000)
}
for (p = 0; p < 4; p++) {
planets[p] = new Planet(100, 100, 20 + p * 20, 20 + p * 20)
}
ship = new Ship(300, 300);
accslider = createSlider(0, 255, 100);
accslider.position(width + 20, 20);
// noLoop(); // putted here since loop is when pressed the button start => function
}
function draw() {
text(munitions, 80, 20);
if (started) {
background(0, 50);
for (let i = 0; i < mover.length; i++) {
mover[i].show();
mover[i].update(x);
mover[i].edge();
// if rollover/ contains is true => then change collision +1 => collsison arrives at 255=> you are dead.
if (mover[i].contains(mouseX, mouseY)) {
collision += 1;
lrect += 0.04;
}
}
if (collision >= 255) {
clearInterval(cron);
started = false;
windowalert()
}
ship.move(mouseX, mouseY);
//ship.impact(mover[i].position.x,mover[i].position.y)
ship.show();
ship.edge();
noCursor();
x += 0.00005;
for (p = 0; p < planets.length; p++) {
planets[p].show(50 + p * 10, 180 + p * 40, 120 + p * 10);
planets[p].move();
}
for (i = 0; i < missil.length; i++) {
missil[i].show();
missil[i].update();
for (p = 0; p < planets.length; p++) {
missil[i].bounce(planets[p].position.x, planets[p].position.y, planets[p].lrect / 2)
}
//if missil is in the canvas then check for collision
if (missil[i].contains(width / 2, height / 2)) {
for (let u = 0; u < mover.length; u++) {
if (mover[u].destroy(missil[i].position.x, missil[i].position.y)) {
mover.splice(u, 1);
missil.splice(i, 1);
console.log(u);
console.log(i)
};
}
} else {
missil.splice(i, 1)
}
}
}
} // if missil is out of the canvas it get erased from the array.
setInterval(function() {
if (munitions < 100) {
munitions += 1
}
}, 1000);
function keyPressed() {
if (munitions > 0) {
if (keyIsDown(LEFT_ARROW))
{
a = -1;
munitions += -1
} // a is in the class munitions and represent the vector x. I did it like that so when we click both arrow it goes in diagonal.
if (keyIsDown(RIGHT_ARROW))
{
a = 1;
munitions += -1
}
if (keyIsDown(UP_ARROW)) {
b = -1;
munitions += -1
}
if (keyIsDown(DOWN_ARROW))
{
b = 1;
munitions += -1
}
for (let u = 0; u < 1; u++) {
let mi = new Missil(mouseX, mouseY, a, b);
missil.push(mi);
}
}
}
function keyReleased() {
if (keyCode == LEFT_ARROW) {
a = 0
} // this was implemented to reput the missil vector at the default value when released the key.
if (keyCode == RIGHT_ARROW) {
a = 0
}
if (keyCode == UP_ARROW) {
b = 0
}
if (keyCode == DOWN_ARROW) {
b = 0
}
}
class Missil {
constructor(x, y, a, b) //partira de mx,my.
{
this.position = createVector(x, y);
this.vel = createVector(a, b);
this.vel.mult(random(2, 4))
}
update() {
this.position = this.position.add(this.vel);
}
show() {
stroke(255);
noStroke();
fill(255, 0, 0, 100);
ellipse(this.position.x, this.position.y, 5);
}
contains(px, py) {
if (dist(this.position.x, this.position.y, px, py) < width / 2) {
return true
} else {
return false
}
}
bounce(px, py, dista) {
{
if (dist(this.position.x, this.position.y, px, py) < dista) {
let v = createVector(this.position.x - px, this.position.y - py);
this.vel = this.vel.mult(1.5).reflect(v)
}
}
}
}
class Planet {
constructor(x, y, lrect, lrect2) {
this.position = createVector(x, y)
this.vel = p5.Vector.random2D();
this.lrect = lrect;
this.lrect2 = lrect2;
}
show(r, g, b) {
fill(r, g, b);
noStroke();
ellipseMode(CENTER);
ellipse(this.position.x, this.position.y, this.lrect, this.lrect2)
stroke(255);
}
move() {
let center = createVector(width / 2, height / 2)
this.gravityacc = p5.Vector.sub(center, this.position);
this.gravityacc.setMag(0.004);
this.vel = this.vel.add(this.gravityacc);
this.position = this.position.add(this.vel);
this.vel.limit(1.3);
}
}
class Mover { //those are the comets
constructor(x, y) {
this.position = createVector(x, y);
this.vel = p5.Vector.random2D();
this.vel.mult(random(3));
//this.acc=p5.Vector.random2D();// acceleeration is a a random vector here.
// this.acc.setMag(0.01); // magnitude of acceleration is slow. Acceleration is a vector.
// this.vel.limit(3); // shrinks size of vector to 5, but if it smaller then 5 then it doent equal to 5 like in setMag().
}
update(speed)
{
setInterval(function() {
this.speed += 0.01
}, 3000);
let mouse = createVector(mouseX, mouseY);
this.acc = p5.Vector.sub(mouse, this.position);
this.acc.setMag(0.04)
this.acc.limit(0.1)
this.vel.add(this.acc); /// add acceleration to velocitiy.
this.position.add(this.vel);
this.vel.limit(5);
}
show() {
stroke(255, 10);
strokeWeight(0);
fill(map(this.position.x, 0, width, 0, 255), map(this.position.y, 0, height, 0, 255), 255, 255);
ellipse(this.position.x, this.position.y, 5)
}
edge() {
if (this.position.x >= width) {
let n = createVector(-1, 0);
this.vel = this.vel.reflect(n)
}
if (this.position.x <= 0) {
let n = createVector(1, 0);
this.vel = this.vel.reflect(n)
}
if (this.position.y >= height) {
let n = createVector(0, -1);
this.vel = this.vel.reflect(n)
}
if (this.position.y <= 0) {
let n = createVector(0, 1);
this.vel = this.vel.reflect(n)
}
}
contains(px, py) {
if (dist(this.position.x, this.position.y, px, py) < 5 + lrect) {
return true
} else {
return false
}
}
destroy(px, py) {
if (dist(this.position.x, this.position.y, px, py) <= 20) {
return true
} else {
return false;
}
}
}
class Ship {
constructor(x, y) {
this.position = createVector(x, y);
this.vel = createVector();
this.acc = createVector();
}
move(px, py) {
this.position.x = px;
this.position.y = py
} //if key pressed.
edge() {
if (this.position.x >= width) {
this.position.x = width
}
if (this.position.y >= height - 50) {
this.position.y = height - 50
}
}
show() {
stroke(255);
strokeWeight(0);
fill(collision * 1, 255 - collision * 2, 0, 100);
rect(this.position.x, this.position.y, lrect, lrect)
}
}
/* Without the HTML this isn't functional
"use strict";
document.form_main.start.onclick = () => start();
document.form_main.pause.onclick = () => pause();
document.form_main.reset.onclick = () => reset();
function start() {
pause();
cron = setInterval(() => {
timer();
}, 10);
started = true; // to indicate to start draw
loop(); // noLoop in fucntion setup.
}
function pause() {
clearInterval(cron);
started = false;
}
function reset() {
location.reload();
}
function timer() {
if ((millisecond += 10) == 1000) {
millisecond = 0;
second++;
}
if (second == 60) {
second = 0;
minute++;
}
if (minute == 60) {
minute = 0;
hour++;
}
document.getElementById('hour').innerText = returnData(hour);
document.getElementById('minute').innerText = returnData(minute);
document.getElementById('second').innerText = returnData(second);
document.getElementById('millisecond').innerText = returnData(millisecond);
}
function returnData(input) {
return input > 10 ? input : `0${input}`
} */
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.js"></script>
The problem is in the the missil/mover collision detection loop:
You are looping through all of the "missils"
For each missil you loop through all the movers
In the event of a collision you remove both the mover and the missil
But you continue looping with the current value of i, which may now be past the end of the array!
As a result you are getting an error sometimes because on a subsequent pass through the mover loop missil[i] is undefined. Any time you update an array you are currently looping over you need to be careful to update your indices and re-check your length before continuing.
// 1. You are looping through all of the "missils"
for (i = 0; i < missil.length; i++) {
// ...
// 2. For each missil you loop through all the movers
for (let u = 0; u < mover.length; u++) {
if (mover[u].destroy(missil[i].position.x, missil[i].position.y)) {
// 3. In the event of a collision you remove both the mover and the missil
mover.splice(u, 1);
missil.splice(i, 1);
console.log(u);
console.log(i)
}
// 4. But you continue looping with the current value of i, which may now be past the end of the array!
}
Here is a fixed version of your sketch:
let mover = [];
let x = 0;
let collision = 0; //counter to know when to end the game
let collu = 0;
let lrect = 10; //size of the ship.
let accslider, velslider;
let shoot = false; //to know if to shoot a missil.
let started = true;
let movertest = [];
//number of missil.
let missil = [];
let outofcanvas;
let a = 0;
let b = 0;
let munitions = 100;
let planets = [];
let cron;
function windowalert() {
if (confirm("Votre score est de rejouer?")) {
location.reload()
} else {
location.close();
}
}
function setup() {
background(0, 1);
cnv = createCanvas(900, 600);
for (let i = 0; i < 2; i++) {
setInterval(function() {
movertest = new Mover(random(400, width), random(400, height));
mover.push(movertest);
}, 5000)
}
for (p = 0; p < 4; p++) {
planets[p] = new Planet(100, 100, 20 + p * 20, 20 + p * 20)
}
ship = new Ship(300, 300);
accslider = createSlider(0, 255, 100);
accslider.position(width + 20, 20);
// noLoop(); // putted here since loop is when pressed the button start => function
}
function draw() {
text(munitions, 80, 20);
if (started) {
background(0, 50);
for (let i = 0; i < mover.length; i++) {
mover[i].show();
mover[i].update(x);
mover[i].edge();
// if rollover/ contains is true => then change collision +1 => collsison arrives at 255=> you are dead.
if (mover[i].contains(mouseX, mouseY)) {
collision += 1;
lrect += 0.04;
}
}
if (collision >= 255) {
clearInterval(cron);
started = false;
windowalert()
}
ship.move(mouseX, mouseY);
//ship.impact(mover[i].position.x,mover[i].position.y)
ship.show();
ship.edge();
noCursor();
x += 0.00005;
for (p = 0; p < planets.length; p++) {
planets[p].show(50 + p * 10, 180 + p * 40, 120 + p * 10);
planets[p].move();
}
for (i = 0; i < missil.length; i++) {
missil[i].show();
missil[i].update();
for (p = 0; p < planets.length; p++) {
missil[i].bounce(planets[p].position.x, planets[p].position.y, planets[p].lrect / 2)
}
//if missil is in the canvas then check for collision
if (missil[i].contains(width / 2, height / 2)) {
let colission = false;
for (let u = 0; u < mover.length; u++) {
if (mover[u].destroy(missil[i].position.x, missil[i].position.y)) {
mover.splice(u, 1);
missil.splice(i, 1);
// Exit the mover loop immediately
colission = true;
break;
}
}
if (colission) {
// because we've deleted the item at i, the item that was at
// i + 1 is now at i, so in order not to skip that item we
// need to decrement i before continuing
i--;
}
} else {
missil.splice(i, 1);
i--;
}
}
}
} // if missil is out of the canvas it get erased from the array.
setInterval(function() {
if (munitions < 100) {
munitions += 1
}
}, 1000);
function keyPressed() {
if (munitions > 0) {
if (keyIsDown(LEFT_ARROW))
{
a = -1;
munitions += -1
} // a is in the class munitions and represent the vector x. I did it like that so when we click both arrow it goes in diagonal.
if (keyIsDown(RIGHT_ARROW))
{
a = 1;
munitions += -1
}
if (keyIsDown(UP_ARROW)) {
b = -1;
munitions += -1
}
if (keyIsDown(DOWN_ARROW))
{
b = 1;
munitions += -1
}
for (let u = 0; u < 1; u++) {
let mi = new Missil(mouseX, mouseY, a, b);
missil.push(mi);
}
}
}
function keyReleased() {
if (keyCode == LEFT_ARROW) {
a = 0
} // this was implemented to reput the missil vector at the default value when released the key.
if (keyCode == RIGHT_ARROW) {
a = 0
}
if (keyCode == UP_ARROW) {
b = 0
}
if (keyCode == DOWN_ARROW) {
b = 0
}
}
class Missil {
constructor(x, y, a, b) //partira de mx,my.
{
this.position = createVector(x, y);
this.vel = createVector(a, b);
this.vel.mult(random(2, 4))
}
update() {
this.position = this.position.add(this.vel);
}
show() {
stroke(255);
noStroke();
fill(255, 0, 0, 100);
ellipse(this.position.x, this.position.y, 5);
}
contains(px, py) {
if (dist(this.position.x, this.position.y, px, py) < width / 2) {
return true
} else {
return false
}
}
bounce(px, py, dista) {
{
if (dist(this.position.x, this.position.y, px, py) < dista) {
let v = createVector(this.position.x - px, this.position.y - py);
this.vel = this.vel.mult(1.5).reflect(v)
}
}
}
}
class Planet {
constructor(x, y, lrect, lrect2) {
this.position = createVector(x, y)
this.vel = p5.Vector.random2D();
this.lrect = lrect;
this.lrect2 = lrect2;
}
show(r, g, b) {
fill(r, g, b);
noStroke();
ellipseMode(CENTER);
ellipse(this.position.x, this.position.y, this.lrect, this.lrect2)
stroke(255);
}
move() {
let center = createVector(width / 2, height / 2)
this.gravityacc = p5.Vector.sub(center, this.position);
this.gravityacc.setMag(0.004);
this.vel = this.vel.add(this.gravityacc);
this.position = this.position.add(this.vel);
this.vel.limit(1.3);
}
}
class Mover { //those are the comets
constructor(x, y) {
this.position = createVector(x, y);
this.vel = p5.Vector.random2D();
this.vel.mult(random(3));
//this.acc=p5.Vector.random2D();// acceleeration is a a random vector here.
// this.acc.setMag(0.01); // magnitude of acceleration is slow. Acceleration is a vector.
// this.vel.limit(3); // shrinks size of vector to 5, but if it smaller then 5 then it doent equal to 5 like in setMag().
}
update(speed)
{
setInterval(function() {
this.speed += 0.01
}, 3000);
let mouse = createVector(mouseX, mouseY);
this.acc = p5.Vector.sub(mouse, this.position);
this.acc.setMag(0.04)
this.acc.limit(0.1)
this.vel.add(this.acc); /// add acceleration to velocitiy.
this.position.add(this.vel);
this.vel.limit(5);
}
show() {
stroke(255, 10);
strokeWeight(0);
fill(map(this.position.x, 0, width, 0, 255), map(this.position.y, 0, height, 0, 255), 255, 255);
ellipse(this.position.x, this.position.y, 5)
}
edge() {
if (this.position.x >= width) {
let n = createVector(-1, 0);
this.vel = this.vel.reflect(n)
}
if (this.position.x <= 0) {
let n = createVector(1, 0);
this.vel = this.vel.reflect(n)
}
if (this.position.y >= height) {
let n = createVector(0, -1);
this.vel = this.vel.reflect(n)
}
if (this.position.y <= 0) {
let n = createVector(0, 1);
this.vel = this.vel.reflect(n)
}
}
contains(px, py) {
if (dist(this.position.x, this.position.y, px, py) < 5 + lrect) {
return true
} else {
return false
}
}
destroy(px, py) {
if (dist(this.position.x, this.position.y, px, py) <= 20) {
return true
} else {
return false;
}
}
}
class Ship {
constructor(x, y) {
this.position = createVector(x, y);
this.vel = createVector();
this.acc = createVector();
}
move(px, py) {
this.position.x = px;
this.position.y = py
} //if key pressed.
edge() {
if (this.position.x >= width) {
this.position.x = width
}
if (this.position.y >= height - 50) {
this.position.y = height - 50
}
}
show() {
stroke(255);
strokeWeight(0);
fill(collision * 1, 255 - collision * 2, 0, 100);
rect(this.position.x, this.position.y, lrect, lrect)
}
}
/* Without the HTML this isn't functional
"use strict";
document.form_main.start.onclick = () => start();
document.form_main.pause.onclick = () => pause();
document.form_main.reset.onclick = () => reset();
function start() {
pause();
cron = setInterval(() => {
timer();
}, 10);
started = true; // to indicate to start draw
loop(); // noLoop in fucntion setup.
}
function pause() {
clearInterval(cron);
started = false;
}
function reset() {
location.reload();
}
function timer() {
if ((millisecond += 10) == 1000) {
millisecond = 0;
second++;
}
if (second == 60) {
second = 0;
minute++;
}
if (minute == 60) {
minute = 0;
hour++;
}
document.getElementById('hour').innerText = returnData(hour);
document.getElementById('minute').innerText = returnData(minute);
document.getElementById('second').innerText = returnData(second);
document.getElementById('millisecond').innerText = returnData(millisecond);
}
function returnData(input) {
return input > 10 ? input : `0${input}`
} */
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.js"></script>
Related
I am trying to take Dan Shiffman's prime spiral program and make it object oriented.
I am putting all variables into the constructor and making the functions into methods to encapsulate them.
But the OOP version of the code does not draw more than 1 triangle to the screen. I can't see why there should be a problem, as I have all the variables and functions included in the scope of the primeSpiral class.
Working code without classes
let x, y;
let step = 1;
let stepSize = 20;
let numSteps = 1;
let state = 0;
let turnCounter = 1;
let offset = 0;
function setup() {
createCanvas(900, 900);
background(0);
const cols = width / stepSize;
const rows = height / stepSize;
x = width / 2;
y = height / 2;
for (let i = 0; i < 500; i++) {
noStroke();
primeSpiral(20, 1)
primeSpiral(30, 200)
incrementStep();
}
}
function incrementStep() {
switch (state) {
case 0:
x += stepSize;
break;
case 1:
y -= stepSize;
break;
case 2:
x -= stepSize;
break;
case 3:
y += stepSize;
break;
}
if (step % numSteps == 0) {
state = (state + 1) % 4;
turnCounter++;
if (turnCounter % 2 == 0) {
numSteps++;
}
}
step++;
}
function primeSpiral(offset, color) {
if (!isPrime(step + offset)) {
//might put something here
} else {
let r = stepSize * 0.5;
fill(color, 99, 164);
push();
translate(x, y);
rotate(-PI / 4);
triangle(-r, +r, 0, -r, +r, +r);
pop();
}
}
function isPrime(value) {
if (value == 1) return false;
for (let i = 2; i <= sqrt(value); i++) {
if (value % i == 0) {
return false;
}
}
return true;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.js"></script>
With classes:
let stepSize = 20;
function setup() {
createCanvas(900, 900);
background(0);
const cols = width / stepSize;
const rows = height / stepSize;
x = width / 2;
y = height / 2;
background(0);
prime = new PrimeSpiral(0, 0, 2, stepSize, 1, 0) //: x,y,offset,color
}
function draw() {
prime.walk();
}
class PrimeSpiral {
constructor(x, y, number) {
this.x = x;
this.y = y;
this.number = number;
this.stepSize = 20;
this.numSteps = 1;
this.state = 0;
this.turnCounter = 1;
}
walk() {
if (!this.isPrime(this.number)) {
console.log(this.succes);
} else {
let r = stepSize * 0.5;
fill(color, 99, 164);
push();
translate(x, y);
rotate(-PI / 4);
triangle(-r, +r, 0, -r, +r, +r);
pop();
this.incrementStep()
}
}
isPrime(value) {
if (value == 1) return false;
for (let i = 2; i <= Math.sqrt(value); i++) {
if (value % i == 0) {
return false;
}
}
return true;
}
incrementStep() {
switch (this.state) {
case 0:
this.x += this.stepSize;
break;
case 1:
this.y -= this.stepSize;
break;
case 2:
this.x -= this.stepSize;
break;
case 3:
this.y += this.stepSize;
break;
}
if (this.step % this.numSteps == 0) {
this.state = (this.state + 1) % 4;
this.turnCounter++;
if (this.turnCounter % 2 == 0) {
this.numSteps++;
}
}
this.step++;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.js"></script>
Sometimes you need to slow down to go faster :)
My hunch is you might attempted to write the class all in one go, without slowing down and testing one function at a time to ensure no errors snuck through.
Here are some of the main errors:
you forgot to add the step property to the class (this.step = 1;) in the constructor. In incrementStep() this.step which is undefined gets incremented which results in NaN throwing the rest off (e.g. state, etc.)
you left stepSize mostly as global variable, but are also using this.stepSize: try to avoid ambiguity and use one or the other (I recommend using this.stepSize since it will allow multiple instances to have independent step sizes)
In walk you're still using translate(x, y); when you probably meant translate(this.x, this.y);
Here's a version with the above notes applied (and optionally no longer incrementing or drawing triangles if x,y go outside the screen bounds):
let spiral1;
let spiral2;
function setup() {
createCanvas(900, 900);
background(0);
noStroke();
// instantiate spirals here so width, height set
spiral1 = new PrimeSpiral(20, 1);
spiral2 = new PrimeSpiral(30, 200);
}
function draw(){
spiral1.walk();
spiral2.walk();
}
class PrimeSpiral{
constructor(offset, color){
this.x = width / 2;
this.y = height / 2;
this.step = 1;
this.stepSize = 20;
this.numSteps = 1;
this.state = 0;
this.turnCounter = 1;
this.offset = offset;
this.color = color;
}
incrementStep() {
switch (this.state) {
case 0:
this.x += this.stepSize;
break;
case 1:
this.y -= this.stepSize;
break;
case 2:
this.x -= this.stepSize;
break;
case 3:
this.y += this.stepSize;
break;
}
if (this.step % this.numSteps == 0) {
this.state = (this.state + 1) % 4;
this.turnCounter++;
if (this.turnCounter % 2 == 0) {
this.numSteps++;
}
}
this.step++;
}
walk(){
// optional, early exit function if we're offscreen
if(this.x < 0 || this.x > width) return;
if(this.y < 0 || this.y > height) return;
if (!isPrime(this.step + this.offset)) {
//console.log('not prime:', step + offset);
} else {
let r = this.stepSize * 0.5;
fill(this.color, 99, 164);
push();
translate(this.x, this.y);
rotate(-PI / 4);
triangle(-r, +r, 0, -r, +r, +r);
pop();
}
this.incrementStep();
}
}
function isPrime(value) {
if (value == 1) return false;
for (let i = 2; i <= sqrt(value); i++) {
if (value % i == 0) {
return false;
}
}
return true;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.min.js"></script>
I am trying the simple circle filling and it works. But when I try to fill the first circle with more circles, it hangs the program immediately. Here's my draw and and class code which fills the circles inside circles:
let krr=[];
function draw() {
background(0);
print(cir.length,krr.length)
if (cir.length<=100){
let temp=new c();
cir.push(temp);
}
else if (krr.length<100){
print(1)
fr=cir[0]
if (fr.r>50){
let re=new cirgain(fr.x,fr.y,(fr.r)/2)
krr.push(re);
}
}
for (let h of krr){
h.show();
}
for (let g of cir){
g.show();
g.grow();
}
}
class cirgain{
constructor(x,y,r){
this.smr=floor(r/3);
if (krr.length==0){
while (true){
this.x=random(x-r+1,x+r-1);
this.y=random(y-r+1,y+r-1)
if (this.x*this.x+this.y*this.y<r*r-2){
break
}
}
}
else{
let flag1=1
let count1=0
while (flag){
if (count1>=500){
count1=0;
this.smr--;
}
while (true){
this.x=random(x-r+1,x+r-1);
this.y=random(y-r+1,y+r-1);
if (this.x*this.x+this.y*this.y<r*r-2)
break
}
for (let i=0;i<krr.length;i++){
if (dist(krr[i].x,krr[i].y,this.x,this.y)<r+this.smr){
flag1=1
count1++;
break;
}
flag1=0;
}
}
}
this.ccc=createVector(random(255),random(100,255),random(100,255))
}
show(){
stroke(0);
noFill();
strokeWeight(3)
stroke(this.ccc.x,this.ccc.y,this.ccc.z);
circle(this.x,this.y,this.smr)
}
}
If the whole code (including setup and class c (which at first fills the space with circles)) is needed, let me know, I will edit to include it.
Edit: Okay, here is the whole code:
let cir = [];
let maxR;
let krr = [];
function setup() {
createCanvas(windowWidth, windowHeight);
maxR = width / 4;
if (height > width)
maxR = height / 4
colorMode(HSB);
angleMode(DEGREES);
}
function draw() {
background(0);
print(cir.length, krr.length)
if (cir.length <= 100) {
let temp = new c();
cir.push(temp);
} else if (krr.length < 100) {
print(1)
fr = cir[0]
if (fr.r > 50) {
let re = new cirgain(fr.x, fr.y, (fr.r) / 2)
krr.push(re);
}
}
for (let h of krr) {
h.show();
}
for (let g of cir) {
g.show();
g.grow();
}
}
class c {
constructor() {
this.tempr = 1
if (cir.length == 0) {
this.x = random(maxR + 1, width - maxR - 1);
this.y = random(maxR + 1, height - maxR - 1)
this.r = maxR;
} else {
let flag = 1
let count = 0
while (flag) {
if (count >= 500) {
count = 0;
maxR--;
}
this.x = random(maxR / 2 + 1, width - maxR / 2 - 1);
this.y = random(maxR / 2 + 1, height - maxR / 2 - 1);
this.r = maxR;
for (let i = 0; i < cir.length; i++) {
if (dist(cir[i].x, cir[i].y, this.x, this.y) < cir[i].r / 2 + this.r / 2 + 3) {
flag = 1
count++;
break;
}
flag = 0;
}
}
}
this.cc = createVector(random(255), random(100, 255), random(100, 255))
}
show() {
stroke(0);
noFill();
strokeWeight(3)
stroke(this.cc.x, this.cc.y, this.cc.z);
circle(this.x, this.y, this.tempr)
rectMode(CENTER);
}
grow() {
if (this.tempr <= this.r)
// this.tempr+=.5
this.tempr += this.r / 100
}
}
class cirgain {
constructor(x, y, r) {
this.smr = floor(r / 3);
if (krr.length == 0) {
while (true) {
this.x = random(x - r + 1, x + r - 1);
this.y = random(y - r + 1, y + r - 1)
if (this.x * this.x + this.y * this.y < r * r - 2) {
break
}
}
} else {
let flag1 = 1
let count1 = 0
while (flag) {
if (count1 >= 500) {
count1 = 0;
this.smr--;
}
while (true) {
this.x = random(x - r + 1, x + r - 1);
this.y = random(y - r + 1, y + r - 1);
if (this.x * this.x + this.y * this.y < r * r - 2)
break
}
for (let i = 0; i < krr.length; i++) {
if (dist(krr[i].x, krr[i].y, this.x, this.y) < r + this.smr) {
flag1 = 1
count1++;
break;
}
flag1 = 0;
}
}
}
this.ccc = createVector(random(255), random(100, 255), random(100, 255))
}
show() {
stroke(0);
noFill();
strokeWeight(3)
stroke(this.ccc.x, this.ccc.y, this.ccc.z);
circle(this.x, this.y, this.smr)
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
Your logic for finding viable x/y values randomly in the cirgain constructor seems broken. Several of your while loops seem like they never exit.
let cir = [];
let krr = [];
let maxR;
function setup() {
createCanvas(windowWidth, windowHeight);
maxR = width / 4;
if (height > width) {
maxR = height / 4;
}
colorMode(HSB);
angleMode(DEGREES);
}
function draw() {
background(0);
print(cir.length, krr.length);
if (cir.length <= 100) {
let temp = new c();
cir.push(temp);
} else if (krr.length < 100) {
print(1)
fr = cir[0]
if (fr.r > 50) {
let re = new cirgain(fr.x, fr.y, (fr.r) / 2)
krr.push(re);
}
}
for (let h of krr) {
h.show();
}
for (let g of cir) {
g.show();
g.grow();
}
}
class c {
constructor() {
this.tempr = 1
if (cir.length == 0) {
this.x = random(maxR + 1, width - maxR - 1);
this.y = random(maxR + 1, height - maxR - 1)
this.r = maxR;
} else {
let flag = 1
let count = 0
while (flag) {
if (count >= 500) {
count = 0;
maxR--;
}
this.x = random(maxR / 2 + 1, width - maxR / 2 - 1);
this.y = random(maxR / 2 + 1, height - maxR / 2 - 1);
this.r = maxR;
for (let i = 0; i < cir.length; i++) {
if (dist(cir[i].x, cir[i].y, this.x, this.y) < cir[i].r / 2 + this.r / 2 + 3) {
flag = 1
count++;
break;
}
flag = 0;
}
}
}
this.cc = createVector(random(255), random(100, 255), random(100, 255))
}
show() {
stroke(0);
noFill();
strokeWeight(3)
stroke(this.cc.x, this.cc.y, this.cc.z);
circle(this.x, this.y, this.tempr)
rectMode(CENTER);
}
grow() {
if (this.tempr <= this.r)
// this.tempr+=.5
this.tempr += this.r / 100
}
}
class cirgain {
constructor(x, y, r) {
this.smr = floor(r / 3);
if (krr.length == 0) {
let n = 0;
while (++n < 1000) {
this.x = random(x - r + 1, x + r - 1);
this.y = random(y - r + 1, y + r - 1);
if (this.x * this.x + this.y * this.y < r * r - 2) {
break;
}
}
if (n >= 1000) {
console.warn('1. Unable to find an x/y value that satisfied the constraint');
debugger;
}
} else {
let flag1 = 1;
let count1 = 0;
let n = 0;
while (flag1 && ++n < 10000) {
if (count1 >= 500) {
count1 = 0;
this.smr--;
}
let n2 = 0;
while (++n2 < 1000) {
this.x = random(x - r + 1, x + r - 1);
this.y = random(y - r + 1, y + r - 1);
if (this.x * this.x + this.y * this.y < r * r - 2) {
break;
}
}
if (n2 >= 1000) {
console.warn('2. Unable to find an x/y value that satisfied the constraint');
debugger;
}
for (let i = 0; i < krr.length; i++) {
if (dist(krr[i].x, krr[i].y, this.x, this.y) < r + this.smr) {
flag1 = 1
count1++;
break;
}
flag1 = 0;
}
}
if (n >= 10000) {
console.warn('3. Flag never set to false');
debugger;
}
}
this.ccc = createVector(random(255), random(100, 255), random(100, 255))
}
show() {
stroke(0);
noFill();
strokeWeight(3)
stroke(this.ccc.x, this.ccc.y, this.ccc.z);
circle(this.x, this.y, this.smr);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
Update
You seem to have an error in your logic checking if the randomly generated x/y values are acceptable:
// Instead of this, which checks if the square of the distance from the top left (0, 0) to the randomly generated center is less than the radius squared
if (this.x * this.x + this.y * this.y < r * r - 2) {
break;
}
// You probably meant to check if the distance from the center of the circle to the randomly generated position is less than the radius:
if (dist(x, y, this.x, this.y) < r - 2) {
break;
}
Additionally I cannot make heads or tails of this logic:
for (let i = 0; i < krr.length; i++) {
if (dist(krr[i].x, krr[i].y, this.x, this.y) < r + this.smr) {
flag1 = 1
count1++;
break;
}
flag1 = 0;
}
For each cirgain that you create you are check that for every existing cirgain the new one has a distance from the existing that is less than the containing circle's radius plus the current circle's diameter*, and if that is the case (which it almost always is!) you try to find a different random position, and only after 500 attempts you decrease the current circle's radius. I can hardly tell what your objective is, but there has to be a better way.
* You're using the default ellipse mode, which means the third argument to circle() is the diameter, which makes this code especially confusing
I have written this code to demonstrate a basic visual p5js project. In here there are 10 balls of varying sizes and colors that spawn at random positions, move around in the canvas and might collide with each other. I am not looking for elastic collision or "realistic" collision physics for that matter. I just want the balls to change to a different direction (can be random as long as it works) and work accordingly.
Here is my code :
class Ball {
//create new ball using given arguments
constructor(pos, vel, radius, color) {
this.pos = pos;
this.vel = vel;
this.radius = radius;
this.color = color;
}
//collision detection
collide(check) {
if (check == this) {
return;
}
let relative = p5.Vector.sub(check.pos, this.pos);
let dist = relative.mag() - (this.radius + check.radius);
if (dist < 0) { //HELP HERE! <--
this.vel.mult(-1);
check.vel.mult(-1);
}
}
//give life to the ball
move() {
this.pos.add(this.vel);
if (this.pos.x < this.radius) {
this.pos.x = this.radius;
this.vel.x = -this.vel.x;
}
if (this.pos.x > width - this.radius) {
this.pos.x = width - this.radius;
this.vel.x = -this.vel.x;
}
if (this.pos.y < this.radius) {
this.pos.y = this.radius;
this.vel.y = -this.vel.y;
}
if (this.pos.y > height - this.radius) {
this.pos.y = height - this.radius;
this.vel.y = -this.vel.y;
}
}
//show the ball on the canvas
render() {
fill(this.color);
noStroke();
ellipse(this.pos.x, this.pos.y, this.radius * 2);
}
}
let balls = []; //stores all the balls
function setup() {
createCanvas(window.windowWidth, window.windowHeight);
let n = 10;
//loop to create n balls
for (i = 0; i < n; i++) {
balls.push(
new Ball(
createVector(random(width), random(height)),
p5.Vector.random2D().mult(random(5)),
random(20, 50),
color(random(255), random(255), random(255))
)
);
}
}
function draw() {
background(0);
//loop to detect collision at all instances
for (let i = 0; i < balls.length; i++) {
for (let j = 0; j < i; j++) {
balls[i].collide(balls[j]);
}
}
//loop to render and move all balls
for (let i = 0; i < balls.length; i++) {
balls[i].move();
balls[i].render();
}
}
Here is a link to the project : https://editor.p5js.org/AdilBub/sketches/TNn2OREsN
All I need is the collision to change the direction of the ball to a random direction and not get stuck. Any help would be appreciated. I am teaching kids this program so I just want basic collision, doesnot have to be "realistic".
Any help is appreciated. Thank you.
The issues you are currently encountering with balls being stuck has to do with randomly generating balls that overlap such that after one iteration of movement they still overlap. When this happens both balls will simply oscillate in place repeatedly colliding with each other. You can prevent this simply by checking for collisions before adding new balls:
class Ball {
//create new ball using given arguments
constructor(pos, vel, radius, color) {
this.pos = pos;
this.vel = vel;
this.radius = radius;
this.color = color;
}
isColliding(check) {
if (check == this) {
return;
}
let relative = p5.Vector.sub(check.pos, this.pos);
let dist = relative.mag() - (this.radius + check.radius);
return dist < 0;
}
//collision detection
collide(check) {
if (this.isColliding(check)) {
this.vel.x *= -1;
this.vel.y *= -1;
check.vel.x *= -1;
check.vel.y *= -1;
}
}
//give life to the ball
move() {
this.pos.add(this.vel);
if (this.pos.x < this.radius) {
this.pos.x = this.radius;
this.vel.x = -this.vel.x;
}
if (this.pos.x > width - this.radius) {
this.pos.x = width - this.radius;
this.vel.x = -this.vel.x;
}
if (this.pos.y < this.radius) {
this.pos.y = this.radius;
this.vel.y = -this.vel.y;
}
if (this.pos.y > height - this.radius) {
this.pos.y = height - this.radius;
this.vel.y = -this.vel.y;
}
}
//show the ball on the canvas
render() {
fill(this.color);
noStroke();
ellipse(this.pos.x, this.pos.y, this.radius * 2);
}
}
let balls = []; //stores all the balls
function setup() {
createCanvas(500, 500);
let n = 10;
//loop to create n balls
for (i = 0; i < n; i++) {
let newBall =
new Ball(
createVector(random(width), random(height)),
p5.Vector.random2D().mult(random(5)),
random(20, 40),
color(random(255), random(255), random(255))
);
let isOk = true;
// check for collisions with existing balls
for (let j = 0; j < balls.length; j++) {
if (newBall.isColliding(balls[j])) {
isOk = false;
break;
}
}
if (isOk) {
balls.push(newBall);
} else {
// try again
i--;
}
}
}
function draw() {
background(0);
//loop to detect collision at all instances
for (let i = 0; i < balls.length; i++) {
for (let j = 0; j < i; j++) {
balls[i].collide(balls[j]);
}
}
//loop to render and move all balls
for (let i = 0; i < balls.length; i++) {
balls[i].move();
balls[i].render();
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
That said, fully elastic collisions (which means collisions are instantaneous and involve no loss of energy due to deformation and resulting heat emission) are actually quite simple to simulate. Here's a tutorial I made on OpenProcessing demonstrating the necessary concepts using p5.js: Elastic Ball Collision Tutorial.
Here's the final version of the code from that tutorial:
const radius = 30;
const speed = 100;
let time;
let balls = []
let boundary = [];
let obstacles = [];
let paused = false;
function setup() {
createCanvas(400, 400);
angleMode(DEGREES);
ellipseMode(RADIUS);
boundary.push(createVector(60, 4));
boundary.push(createVector(width - 4, 60));
boundary.push(createVector(width - 60, height - 4));
boundary.push(createVector(4, height - 60));
obstacles.push(createVector(width / 2, height / 2));
balls.push({
pos: createVector(width * 0.25, height * 0.25),
vel: createVector(speed, 0).rotate(random(0, 360))
});
balls.push({
pos: createVector(width * 0.75, height * 0.75),
vel: createVector(speed, 0).rotate(random(0, 360))
});
balls.push({
pos: createVector(width * 0.25, height * 0.75),
vel: createVector(speed, 0).rotate(random(0, 360))
});
time = millis();
}
function keyPressed() {
if (key === "p") {
paused = !paused;
time = millis();
}
}
function draw() {
if (paused) {
return;
}
deltaT = millis() - time;
time = millis();
background('dimgray');
push();
fill('lightgray');
stroke('black');
strokeWeight(2);
beginShape();
for (let v of boundary) {
vertex(v.x, v.y);
}
endShape(CLOSE);
pop();
push();
fill('dimgray');
for (let obstacle of obstacles) {
circle(obstacle.x, obstacle.y, radius);
}
pop();
for (let i = 0; i < balls.length; i++) {
let ball = balls[i];
// update position
ball.pos = createVector(
min(max(0, ball.pos.x + ball.vel.x * (deltaT / 1000)), width),
min(max(0, ball.pos.y + ball.vel.y * (deltaT / 1000)), height)
);
// check for collisions
for (let i = 0; i < boundary.length; i++) {
checkCollision(ball, boundary[i], boundary[(i + 1) % boundary.length]);
}
for (let obstacle of obstacles) {
// Find the tangent plane that is perpendicular to a line from the obstacle to
// the moving circle
// A vector pointing in the direction of the moving object
let dirVector = p5.Vector.sub(ball.pos, obstacle).normalize().mult(radius);
// The point on the perimiter of the obstacle that is in the direction of the
// moving object
let p1 = p5.Vector.add(obstacle, dirVector);
checkCollision(ball, p1, p5.Vector.add(p1, p5.Vector.rotate(dirVector, -90)));
}
// Check for collisions with other balls
for (let j = 0; j < i; j++) {
let other = balls[j];
let distance = dist(ball.pos.x, ball.pos.y, other.pos.x, other.pos.y);
if (distance / 2 < radius) {
push();
let midPoint = p5.Vector.add(ball.pos, other.pos).div(2);
let boundaryVector = p5.Vector.sub(other.pos, ball.pos).rotate(-90);
let v1Parallel = project(ball.vel, boundaryVector);
let v2Parallel = project(other.vel, boundaryVector);
let v1Perpendicular = p5.Vector.sub(ball.vel, v1Parallel);
let v2Perpendicular = p5.Vector.sub(other.vel, v2Parallel);
ball.vel = p5.Vector.add(v1Parallel, v2Perpendicular);
other.vel = p5.Vector.add(v2Parallel, v1Perpendicular);
let bounce = min(radius, 2 * radius - distance);
ball.pos.add(p5.Vector.rotate(boundaryVector, -90).normalize().mult(bounce));
other.pos.add(p5.Vector.rotate(boundaryVector, 90).normalize().mult(bounce));
pop();
}
}
}
// Only draw balls after all position updates are complete
for (let ball of balls) {
circle(ball.pos.x, ball.pos.y, radius);
}
}
function drawLine(origin, offset) {
line(origin.x, origin.y, origin.x + offset.x, origin.y + offset.y);
}
// Handles collision with a plane given two points on the plane.
// It is assumed that given a vector from p1 to p2, roating that vector
// clockwise 90 degrees will give a vector pointing to the in-bounds side of the
// plane (i.e. a "normal").
function checkCollision(ball, p1, p2) {
let boundaryVector = p5.Vector.sub(p2, p1);
let objVector = p5.Vector.sub(ball.pos, p1);
let angle = boundaryVector.angleBetween(objVector);
let distance = objVector.mag() * sin(angle);
if (distance <= radius) {
// Collision
let vParallel = project(ball.vel, boundaryVector);
let vPerpendicular = p5.Vector.sub(ball.vel, vParallel);
ball.vel = p5.Vector.add(vParallel, p5.Vector.mult(vPerpendicular, -1));
let bounce = min(radius, (radius - distance) * 2);
// If the ball has crossed over beyond the plane we want to offset it to be on
// the in-bounds side of the plane.
let bounceOffset = p5.Vector.rotate(boundaryVector, 90).normalize().mult(bounce);
ball.pos.add(bounceOffset);
}
}
// p5.Vector helpers
function project(vect1, vect2) {
vect2 = p5.Vector.normalize(vect2);
return p5.Vector.mult(vect2, p5.Vector.dot(vect1, vect2));
}
function reject(vect1, vect2) {
return p5.Vector.sub(vect1, project(vect1, vect2));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
I have a game in which there si a rocket. When rockets health is 0 the game over screen should appear and when any key is pressed the game should restart.
I tried using redraw and loop keywords in p5.js
let x = 240;
let x1 = 258;
let score = 0;
let health = 5;
let opelsins = [];
let ecllipseOpelsin = [];
let monsterimg;
let meteorimg;
let inGameSound;
let opelsinimg;
let gameOverSound;
let backgroundsun;
let gameoverScreen;
function collideRectangleCircle(rx, ry, rw, rh, cx, cy, cr)
{
return rx+rw > cx-cr && cx+cr > rx && ry+rh > cy-cr && cy+cr > ry;
}
function Opelsin() {
this.x = random(40,560);
this.y = random(-200,-190);
this.speed = random(3,10);
this.fall = function() {
this.y = this.y + this.speed;
if (this.y > height) {
this.y = random(-200,-100);
this.x = random(40,560);
this.speed = random(3, 10);
}
};
this.show = function() {
fill(255)
ellipse(this.x + 15,this.y + 34, 10, 10)
image(opelsinimg,this.x,this.y, 40, 40)
};
}
function opelsinmodel() {
this.x = random(0,600);
this.y = random(-300,-310);
this.speed = random(3,10);
this.show = function() { ellipse(this.x,this.y, 20, 20) };
}
function setup() {
createCanvas(600, 400);
frameRate(300)
inGameSound.setVolume(1);
inGameSound.loop();
interval = setInterval(scoreCount, 500);
}
function gameOver() {
textSize(20);
text("GAME OVER", 250, 200);
text("SCORE: " + score, 270, 220);
fill(255);
}
function newGame() {
if (keyIsPressed === true) {
//redraw();
loop();
}
}
function preload() {
soundFormats('m4a')
monsterimg = loadImage('assets/monster.png');
opelsinimg = loadImage('assets/opelsin.png');
inGameSound = loadSound('assets/spaceBotInGameMusic.m4a');
gameOverSound = loadSound('assets/gameOverInGameSound.m4a');
backgroundsun = loadImage('assets/backgroundsun.jpg');
gameoverScreen = loadImage('assets/gameoverScreen.png');
}
function scoreCount() {
score++;
}
function draw() {
// background(11, 72, 170);
image(backgroundsun, 0, 0, 700, 400);
if (score == 0) {
for (let i = 0; i < 5; i ++) {
opelsins[i] = new Opelsin();
ecllipseOpelsin[i] = new opelsinmodel();
}
} if (score == 50) {
for (let i = 0; i < 10; i ++) {
opelsins[i] = new Opelsin();
ecllipseOpelsin[i] = new opelsinmodel();
}
}
if (score == 100) {
for (let i = 0; i < 15; i ++) {
opelsins[i] = new Opelsin();
ecllipseOpelsin[i] = new opelsinmodel();
}
}
if (score == 150) {
for (let i = 0; i < 20; i ++) {
opelsins[i] = new Opelsin();
ecllipseOpelsin[i] = new opelsinmodel();
}
}
if (score == 200) {
for (let i = 0; i < 25; i ++) {
opelsins[i] = new Opelsin();
ecllipseOpelsin[i] = new opelsinmodel();
}
}
if (keyIsDown(LEFT_ARROW) && x > -14) {
x -= 5;
}
if (keyIsDown(RIGHT_ARROW) && x < 550) {
x += 5;
}
if (keyIsDown(LEFT_ARROW) && x1 > 9) {
x1 -= 5;
}
if (keyIsDown(RIGHT_ARROW) && x1 < 565) {
x1 += 5;
}
fill(255, 255, 0)
rect(x1, 345, 20, 20)
image(monsterimg,x,310,60,60)
for (let opelsin of opelsins) {
opelsin.fall();
opelsin.show();
}
textSize(20);
text("Health: " + health, 10, 20);
fill(255);
textSize(20);
text("Score: " + score, 10, 40);
fill(255);
for (let opelsin of opelsins) {
hit = collideRectCircle(x1, 335, 20, 30, opelsin.x, opelsin.y, 40);
if(hit == true) {
health -= 1;
opelsin.y = height+1;
if (health == 0) {
inGameSound.stop();
gameOverSound.setVolume(1);
gameOverSound.loop();
image(gameoverScreen, 0, 0, 600, 400);
gameOver();
noLoop();
newGame();
}
}
}
}
What I expect is when the game over screen appears and then when the random key is pressed the game should restart.
The issue is that the game loop is stopped by noLoop() when the game is over. The loop is never restarted.
Set a state (e.g waitRestart) when the game is over (instead of newGame()):
let waitRestart = false;
function draw() {
// [...]
for (let opelsin of opelsins) {
hit = collideRectCircle(x1, 335, 20, 30, opelsin.x, opelsin.y, 40);
if(hit == true) {
health -= 1;
opelsin.y = height+1;
if (health == 0) {
inGameSound.stop();
gameOverSound.setVolume(1);
gameOverSound.loop();
image(gameoverScreen, 0, 0, 600, 400);
gameOver();
noLoop();
// newGame(); <----- delete
waitRestart = true; // <----- wait for restart
}
}
}
}
Add a keyPressed() callback instead of the newGame() function, which restarts the game and set all initial states like health and score:
function keyPressed() {
if (waitRestart) {
waitRestart = false;
score = 0;
health = 5;
loop();
}
}
I have been following The Coding Train's coding challenge where he creates a flocking algorithm using cohesion, separation, and alignment. I challenged myself to see if I could rotate each triangle object so they are looking where they are going, however, I have struggled to implement this into my code.
My Boid class is where all the algorithms are located
const flock = [];
let alignSlider, cohesionSlider, separationSlider;
function setup() {
createCanvas(900, 600);
for (i = 0; i < 50; i++) {
flock.push(new Boid());
}
alignSlider = createSlider(0, 5, 1, 0.1);
cohesionSlider = createSlider(0, 5, 1, 0.1);
separationSlider = createSlider(0, 5, 2, 0.1);
}
function draw() {
background(51);
for (let boid of flock) {
boid.show();
boid.edges();
boid.flock(flock);
boid.update();
}
}
class Boid {
constructor() {
this.position = createVector(random(width), random(height));
this.velocity = p5.Vector.random2D();
this.velocity.setMag(random(4, 6));
this.acceleration = createVector();
this.maxForce = 0.2;
this.maxSpeed = 4;
}
edges() {
if (this.position.x > width) {
this.position.x = 0;
}
if (this.position.x < 0) {
this.position.x = width;
}
if (this.position.y > height) {
this.position.y = 0;
}
if (this.position.y < 0) {
this.position.y = height;
}
}
align(boids) {
let perceptionRadius = 100;
let steering = createVector();
let total = 0;
for (let other of boids) {
let d = dist(
this.position.x,
this.position.y,
other.position.x,
other.position.y
);
if (other != this && d < perceptionRadius) {
steering.add(other.velocity);
total++;
}
}
if (total > 0) {
steering.div(total);
steering.setMag(this.maxSpeed);
steering.sub(this.velocity);
steering.limit(this.maxForce);
}
return steering;
}
cohesion(boids) {
let perceptionRadius = 100;
let steering = createVector();
let total = 0;
for (let other of boids) {
let d = dist(
this.position.x,
this.position.y,
other.position.x,
other.position.y
);
if (other != this && d < perceptionRadius) {
steering.add(other.position);
total++;
}
}
if (total > 0) {
steering.div(total);
steering.sub(this.position);
steering.setMag(this.maxSpeed);
steering.sub(this.velocity);
steering.limit(this.maxForce);
}
return steering;
}
separation(boids) {
let perceptionRadius = 50;
let steering = createVector();
let total = 0;
for (let other of boids) {
let d = dist(
this.position.x,
this.position.y,
other.position.x,
other.position.y
);
if (other != this && d < perceptionRadius) {
let diff = p5.Vector.sub(this.position, other.position);
diff.div(d);
steering.add(diff);
total++;
}
}
if (total > 0) {
steering.div(total);
steering.setMag(this.maxSpeed);
steering.sub(this.velocity);
steering.limit(this.maxForce);
}
return steering;
}
flock(boids) {
let alignment = this.align(boids);
let cohesion = this.cohesion(boids);
let separation = this.separation(boids);
alignment.mult(alignSlider.value());
cohesion.mult(cohesionSlider.value());
separation.mult(separationSlider.value());
this.acceleration.add(alignment);
this.acceleration.add(cohesion);
this.acceleration.add(separation);
}
update() {
this.position.add(this.velocity);
this.velocity.add(this.acceleration);
this.velocity.limit(this.maxSpeed);
this.acceleration.mult(0);
}
show() {
//this is where i create my triangle and this is where i would like to be able to rotate it
let triangleSize = 12;
let x = this.position.x;
let y = this.position.y;
fill(51);
stroke(255);
// ellipseMode(CENTER);
// rotate(this.velocity.heading());
triangle(
x,
y,
x + triangleSize,
y,
x + triangleSize / 2,
y + triangleSize * 1.2
);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/addons/p5.dom.min.js"></script>
I modified your code so that the boids will point in the direction that they are moving by using push, translate, rotate and pop.
The change is all within Boid.show()
push();
translate(x, y);
rotate(this.velocity.heading() - radians(90));
triangle(0,0,triangleSize,0,triangleSize / 2,triangleSize * 1.2);
pop()
Push allows us to just apply the translate and rotation to one boid at a time as pop will undo the changes. I translate to x, y and then position the triangle at 0, 0 so the rotation will only rotate the boid triangle.
I also had to subtract 90 degrees or 1/2 pi from the velocity heading to get the boid to point in the direction of movement.
const flock = [];
let alignSlider, cohesionSlider, separationSlider;
var theta = 0.1;
function setup() {
createCanvas(900, 600);
for (i = 0; i < 50; i++) {
flock.push(new Boid());
}
alignSlider = createSlider(0, 5, 1, 0.1);
cohesionSlider = createSlider(0, 5, 1, 0.1);
separationSlider = createSlider(0, 5, 2, 0.1);
}
function draw() {
theta += .01;
background(51);
for (let boid of flock) {
boid.show();
boid.edges();
boid.flock(flock);
boid.update();
}
}
class Boid {
constructor() {
this.position = createVector(random(width), random(height));
this.velocity = p5.Vector.random2D();
this.velocity.setMag(random(4, 6));
this.acceleration = createVector();
this.maxForce = 0.2;
this.maxSpeed = 4;
}
edges() {
if (this.position.x > width) {
this.position.x = 0;
}
if (this.position.x < 0) {
this.position.x = width;
}
if (this.position.y > height) {
this.position.y = 0;
}
if (this.position.y < 0) {
this.position.y = height;
}
}
align(boids) {
let perceptionRadius = 100;
let steering = createVector();
let total = 0;
for (let other of boids) {
let d = dist(
this.position.x,
this.position.y,
other.position.x,
other.position.y
);
if (other != this && d < perceptionRadius) {
steering.add(other.velocity);
total++;
}
}
if (total > 0) {
steering.div(total);
steering.setMag(this.maxSpeed);
steering.sub(this.velocity);
steering.limit(this.maxForce);
}
return steering;
}
cohesion(boids) {
let perceptionRadius = 100;
let steering = createVector();
let total = 0;
for (let other of boids) {
let d = dist(
this.position.x,
this.position.y,
other.position.x,
other.position.y
);
if (other != this && d < perceptionRadius) {
steering.add(other.position);
total++;
}
}
if (total > 0) {
steering.div(total);
steering.sub(this.position);
steering.setMag(this.maxSpeed);
steering.sub(this.velocity);
steering.limit(this.maxForce);
}
return steering;
}
separation(boids) {
let perceptionRadius = 50;
let steering = createVector();
let total = 0;
for (let other of boids) {
let d = dist(
this.position.x,
this.position.y,
other.position.x,
other.position.y
);
if (other != this && d < perceptionRadius) {
let diff = p5.Vector.sub(this.position, other.position);
diff.div(d);
steering.add(diff);
total++;
}
}
if (total > 0) {
steering.div(total);
steering.setMag(this.maxSpeed);
steering.sub(this.velocity);
steering.limit(this.maxForce);
}
return steering;
}
flock(boids) {
let alignment = this.align(boids);
let cohesion = this.cohesion(boids);
let separation = this.separation(boids);
alignment.mult(alignSlider.value());
cohesion.mult(cohesionSlider.value());
separation.mult(separationSlider.value());
this.acceleration.add(alignment);
this.acceleration.add(cohesion);
this.acceleration.add(separation);
}
update() {
this.position.add(this.velocity);
this.velocity.add(this.acceleration);
this.velocity.limit(this.maxSpeed);
this.acceleration.mult(0);
}
show() {
//this is where i create my triangle and this is where i would like to be able to rotate it
let triangleSize = 12;
let x = this.position.x;
let y = this.position.y;
fill(51);
stroke(255);
push();
translate(x, y);
rotate(this.velocity.heading() - radians(90));
triangle(
0,
0,
triangleSize,
0,
triangleSize / 2,
triangleSize * 1.2
);
pop();
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/addons/p5.dom.min.js"></script>