The following code works, but I want to be able to have multiple canvasses with different background colors and sizes etc. - how do I modify the code to turn it in to a p5 instance?- I can't get the instance to recognize the external class 'ellipse' and the drawCircles, and setInterval functions
let circles = [];
let circlesStatic = [];
let counter = 0;
function setup() {
createCanvas(400, 400);
setInterval(expandCircle, 1000);
}
function draw() {
background(0);
drawCircles();
}
/////////////////////////////////////////////////////////////////////////////
function expandCircle() {
let e = new Ellipse(random(100), random(100), counter);
circles.push(e);
//console.log("circlesStatic ", circlesStatic);
//console.log(counter);
counter++;
}
///////////////////////////////////////////////////////////////////////////////
class Ellipse {
// constructor
constructor(x, y, counter) {
this.x = x; // copy x argument value to the instance (this) x property
this.y = y; // copy x argument value to the instance (this) x property
// current size - continuously updated
this.size = 10;
// minimum size
this.minSize = 10;
// maximum size
this.maxSize = 30;
// change speed for size (how much will the size increase/decrease each frame)
this.sizeSpeed = 3;
// internal frameCount replacement
this.tick = 0;
this.counter = counter;
// this.fill=(255,0,0);
}
isStatic() {
return this.sizeSpeed === 0;
}
render() {
// if the size is either too small, or too big, flip the size speed sign (if it was positive (growing) - make it negative (shrink) - and vice versa)
if (this.size < this.minSize || this.size > this.maxSize) {
this.sizeSpeed *= -1;
}
// increment the size with the size speed (be it positive or negative)
this.size += this.sizeSpeed;
//console.log(this.sizeSpeed);
if (this.size <= this.minSize) {
circlesStatic[this.counter] = [];
circlesStatic[this.counter][0] = this.x;
circlesStatic[this.counter][1] = this.y;
this.sizeSpeed = 0;
}
ellipse(this.x, this.y, this.size, this.size);
}
}
////////////////////////////////////////////////////////////////////////////////
function drawCircles() {
for (let i = 0; i < circles.length; i++) {
circles[i].render();
if (circles[i].isStatic()) {
circles.splice(i, 1);
i--;
}
}
for (let i = 0; i < circlesStatic.length; i++) {
strokeWeight(0);
var popColour = (255, 255, 200);
fill(popColour);
circle(circlesStatic[i][0], circlesStatic[i][1], 1);
}
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.1/lib/p5.js"></script>
Related
I am trying to make one of those sorting algorithms and want to have the bars get randomly generated with height and x position. However, when I run the code, it doesn't draw any bars. I'm also not getting any errors sent so I have no idea what's going wrong.
class Bar {
constructor(x, height) {
this.height = height;
this.size = 10;
this.x = x;
}
draw() {
fill("blue");
rect(this.x * this.size, height - (this.height * this.size), this.size, this.height * this.size);
}
}
let bars = [];
function setup() {
createCanvas(400, 400);
generateBars();
}
function generateBars() {
for (let i = 0; i < 39; i++) {
randomX = random(0, 39);
randomHeight = random(0, 40)
for (let j = 0; j < bars.length; j++) {
if (bars[j].x == randomX) {
randomX = random(0, 39);
} else {
for (let h = 0; h < bars.length; h++) {
if (bars[j].height == randomHeight) {
randomHeight = random(0, 40);
} else {
bars[i] = new Bar(randomX, randomHeight);
}
}
}
}
}
}
function draw() {
background(220);
for (let k = 0; k < bars.length; k++) {
bars[k].draw();
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.min.js"></script>
The logic in generateBars seems convoluted and the intention is unclear.
As far as I see Bar's x property acts more of a value property storing the number to be sorted later because it is multiplied by size in draw(). (e.g. x is not the final x position the bar should be rendered at, as it's name implies).
I would simply intanstiate Bar like so:
class Bar{
constructor(x, height){
this.height = height;
this.size = 10;
this.x = x;
}
draw(){
fill("blue");
rect(this.x * this.size, height - (this.height * this.size), this.size, this.height * this.size);
}
}
let bars = [];
function setup() {
createCanvas(400, 400);
generateBars();
}
function generateBars(){
for(let i = 0; i < 40; i++){
randomHeight = random(0, 40)
bars[i] = new Bar(i, randomHeight);
}
}
function draw() {
background(220);
for(let k = 0; k < bars.length; k++){
bars[k].draw();
}
}
function mouseClicked(){
setup();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.min.js"></script>
(I've added a hacky mouseClicked() handler so it's easy to regenerate the bars for testing/debugging purposes. You may not need it in your final code)
Update Based on your comment, it's the bar height that is tightly coupled to the value to be sorted later.
One idea is to simply pre-generate the list of numbers to sort and shuffle() them first:
class Bar{
constructor(x, height){
this.height = height;
this.size = 10;
this.x = x;
}
draw(){
fill("blue");
rect(this.x * this.size, height - (this.height * this.size), this.size, this.height * this.size);
}
}
let bars = [];
function setup() {
createCanvas(400, 400);
generateBars();
}
function generateBars(){
// array to be sorted
let barValues = [];
for(let i = 0; i < 40; i++){
// values are sorted first
barValues[i] = i;
}
// then we shuffle them: second argument = shuffle in place (replacing the old array)
shuffle(barValues, true);
// assign shuffled values to Bar instances
for(let i = 0; i < 40; i++){
bars[i] = new Bar(i, barValues[i]);
}
}
function draw() {
background(220);
for(let k = 0; k < bars.length; k++){
bars[k].draw();
}
}
function mouseClicked(){
setup();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.min.js"></script>
This is the code
let x = 10;
let y = 0;
let bottomy = 100;
let Speed = 1
function setup() {
createCanvas(windowWidth,600);
}
function draw() {
background(0)
strokeWeight(3)
stroke(255)
for (i = 0; i < width; i += 20) {
water()
line(i,y,i,bottomy)
}
bottomy = bottomy + Speed;
if (bottomy > height) {
bottomy = 100
}
frameRate(1)
}
function water(){
bottomy = random(0,600)
//noLoop()
}
I want to randomise each y2 line coordinate in the for loop. But then have the y2 line coordinate to increment by 1. To create a rain effect.
I can't put the random variable in setup and then call it in the for loop because it won't affect each line in the for loop and I can't put the for loop in setup because I need the line to be drawn.
I've also tried creating a function that loops once and then calling it in draw but it ends up stopping all the code in the draw function.
I've seen examples where they generate like an infinite amount of random lines. But I would like to keep the x position of each line the same if possible. If it's not possible to do this with a for loop and I have to draw each line individually that's fine I was just wondering if this is possible to efficiently do this with a for loop.
I think what you are looking for is individual variables for each line.
Probably a classic:
from-several-variables-to-arrays
from-several-arrays-to-classes
situation. (those were made in Java's processing, but the concept can be easily adapted)
Anyway, i think this is what you tried to make with your code, but it does not work as intended, since you only have one bottomY var for all lines.
let x = 10;
let y = 0;
let bottomY = 100;
let spd = 1; //by convention capitalized names are for classes
function setup() {
createCanvas(windowWidth, 600);
}
function draw() {
background(0);
strokeWeight(3);
stroke(255);
for (i = 0; i < width; i += 20) {
if (bottomY > height) {
bottomY = random(600);
}
line(i, y, i, bottomY);
}
bottomY += spd;
}
What you want is several lines that each has individual x and bottomY
So you could use two arrays for that:
let x = [];
let y = 0;
let bottomY = [];
//why not have individual speeds as well...
let spd = [];
function setup() {
createCanvas(windowWidth, 600);
for (i = 0; i < width; i += 20) {
x.push(i);
bottomY.push(random(height));
spd.push(random(0.6, 2));
}
strokeWeight(3);
stroke(255);
}
function draw() {
background(0);
for (let i = 0; i < x.length; i++) {
line(x[i], y, x[i], bottomY[i]);
if (bottomY[i] < height) {
bottomY[i] += spd[i];
} else {
bottomY[i] = random(height);
}
}
}
And finally a simple class implementation:
let rain = [];
function setup() {
createCanvas(windowWidth, 600);
for (i = 0; i < width; i += 20) {
rain.push(new Drop(i, 0));
}
}
function draw() {
background(0);
for (const d of rain) {
d.run();
}
}
class Drop {
constructor(x, y) {
this.x = x;
this.y = y;
this.btY = random(height);
this.spd = random(0.6, 2);
}
run() {
strokeWeight(3);
stroke(255);
line(this.x, this.y, this.x, this.btY);
if (this.btY < height) {
this.btY += this.spd;
} else {
this.btY = random(height);
}
}
} //class
Did it make sense?
For my space invader like game I want to make bullets that get shot by the enemies but the problem is that the bullets dont appear in this loop that I made. I tried several things like making only one bullet appear while that works it is not the result that I want. My idea behind the code is that a new bullet gets shot from the position of the enemies when the enemybullet.position == 1 and if it exceeds the height of the canvas I want the bullet to return to the enemies and be shot again.
The code that I used for this result is here:
sketch.js
var enemies = [];
var enemybullet = [];
function setup() {
createCanvas(800, 600);
for (var i = 0; i < 2; i++) {
enemies[i] = new Enemy(i*300+300, 100);
}
// I tried enemybullet = new Enemybullet(120, 200); and that drew the bullet but it wasnt assigned to the enemy
rectMode(CENTER);
}
function draw() {
background(0);
// enemybullet.show(); this is wat I used to draw the single projectile
// enemybullet.move();
var edge = false;
for (var i = 0; i < enemies.length; i++) {
enemies[i].show();
enemies[i].move();
if(enemies[i].x > width || enemies[i].x < 0) {
edge = true;
}
}
if (edge) {
for (var i = 0; i < enemies.length; i++) {
enemies[i].shiftDown();
}
}
for (var i = 0; i < enemybullet.length; i++) {
enemybullet[i].show();
enemybullet[i].move();
for (var j = 0; j < enemies.length; j++) {
if(enemybullet[i].position == 1){
var enemybullets = new Enemybullet(enemies[j].x(), enemies[j].y());
enemybullet.push(enemybullets);
enemybullet[i].x = enemybullet[i].x;
enemybullet[i].y = enemybullet[i].y + enemybullet[i].speed;
if(enemybullet[i].y >= height){
enemybullet[i].position = 2;
}
}
else{
enemybullet[i].y = enemies[i].y;
enemybullet[i].x = enemies[i].x;
}
if(enemybullet[i].position == 2 ){
enemybullet[i].y = enemies[i].y;
enemybullet[i].x = enemies[i].x;
enemybullet[i].position = 1;
}
}
}
enemybullet.js
function Enemybullet(x, y) {
this.x = x;
this.y = y;
this.width = 10;
this.height = 20;
this.position = 1;
this.speed = 2;
this.show = function() {
fill('#ADD8E6');
rect(this.x, this.y, this.width, this.height);
}
this.move = function() {
this.x = this.x;
this.y = this.y + this.speed;
}
}
enemy.js
function Enemy(x, y) {
this.x = x;
this.y = y;
this.r = 100;
this.xdir = 1;
this.shot = function() {
this.r = this.r * 0;
this.xdir = this.xdir * 0;
}
this.shiftDown = function() {
this.xdir *= -1;
this.y += this.r/2;
}
this.show = function() {
fill('#0000FF');
rect(this.x, this.y, this.r, this.r);
}
this.move = function() {
this.x = this.x + this.xdir;
}
}
Rather than having the bullets be in their own global array, try having the bullets be a variable in the Enemy class.
this.bullet = new Enemybullet(this.x, this.y);
Then you can give the enemies functions such as the following to update the bullets.
this.updateBullet = function() {
this.bullet.move();
this.bullet.show();
}
this.resetBullet = function() {
this.bullet = new Enemybullet(this.x, this.y);
}
Where you were looping through each bullet before, you can instead call enemies[i].updateBullet(); when you move and show the enemies.
And when you want the enemies to shoot another shot, call enemies[i].resetBullet();
You can see my implementation here.
class Target {
constructor(_x, _y, _toul, _ardh, _color, _health) {
this.x = _x;
this.y = _y;
this.toul = _toul;
this.ardh = _ardh;
this.col = _color;
this.health = _health;
}
destroy() {
if (this.health === 0) {
this.x = 900;
this.y = 900;
}
}
//colide w change color
show() {
fill(this.col);
rect(this.x, this.y, this.toul, this.ardh);
}
}
function setup() {
createCanvas(1000, 1000);
var target = [];
for (var i = 0; i < 31; i++) {
if (i < 11) {
var x = 10 + 10 * i;
var y = 20;
var toul = 10;
var aredh = 10;
var col = color(255, 0, 0);
var health = 3;
}
if (i < 21) {
x = 10 + 10 * i - 110;
y = 40;
toul = 10;
ardh = 10;
health = 2;
col = color(0, 255, 0);
}
if (i < 31) {
x = 10 + 10 * i - 210;
y = 50;
toul = 10;
ardh = 10;
health = 1;
col = color(0, 0, 255);
}
target[i] = new Target(x, y, toul, ardh, col, health);
}
}
function draw() {
for (var i = 0; i < target.length; i++) {
target[i].show();
}
}
Can anyone tell me the problem and how to fix it please, this is my first attempt at object-oriented programming.
I'm attempting to create a game: 3 lines of rectangles each with its own color and health etc
but i get this error :
3: Uncaught SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode
"%cDid you just try to use p5.js's str() function? If so, you may want to move it into your sketch's setup() function.\n\nFor more details, see: github.com/processing/p5.js/wiki/Frequently-Asked-Questions#why-cant-i-assign-variables-using-p5-functions-and-variables-before-setup"
You are declaring your array of targets locally in setup() so when you try to access it in draw() it does not exist. Declare it globally above setup() and that should fix it:
var target = [];
function setup() {
createCanvas(1000, 1000);
...
I have this code that stacks canvas particles in a lane and then resets the whole canvas when one lane is full.
It does this by setting "particles = []"
I want to modify this behavior by only resetting the canvas for that particular lane. Is it possible?
$(document).ready(function () {
"use strict";
var c = document.getElementById("c"),
ctx = c.getContext("2d"),
WIDTH = c.width = window.innerWidth,
HEIGHT = c.height = window.innerHeight;
var particles = [],
particle = null,
particleCount = 3,
radius = 0,
numParticles = [0, 0, 0, 0, 0, 0],
colors = ["#00FF6D", "E8D90C", "#FF5900", "#C00CE8", "#0D90FF"];
var Vector = function (x, y) {
this.x = x || 0;
this.y = y || 0;
};
Vector.prototype = {
constructor: Vector,
add: function (v) {
this.x += v.x;
this.y += v.y;
},
sub: function (v) {
this.x -= v.x;
this.y -= v.y;
},
mul: function (v) {
this.x *= v.x;
this.y *= v.y;
}
};
var Particle = function (position, velocity, radius, lane, color) {
this.position = position;
this.velocity = velocity;
this.radius = radius;
this.baseRadius = radius;
this.angle = 3;
this.lane = lane;
this.color = color;
};
Particle.prototype = {
constructor: Particle,
update: function (lane) {
this.radius = 3;
this.angle += 10;
this.position.add(this.velocity);
// CHECKS FIRST IF THERE'S ALREADY A PARTICLE IN THE LANE AND THEN SHORTENS THE STOP LENGTH
if (this.position.x > WIDTH - (numParticles[this.lane] + 1) * 120) {
// IF THERE IS ALREADY A PARTICLE ON A LANE THE NUMBER OF PARTICLES PER IS INCREASED
if (this.velocity.x > 0) {
console.log(numParticles[this.lane] + " particles in this lane")
numParticles[this.lane]++;
if (numParticles[this.lane] > 8) {
numParticles[this.lane] = 0;
particles = [];
}
// STOPS THE PARTICLE
this.velocity.x = 0;
}
}
},
render: function (ctx) {
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 5; j++) {
ctx.beginPath()
ctx.fillStyle = this.color;
ctx.arc(this.position.x - i * 12, (this.position.y - 30) + j * 12, this.radius + 0.5, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
}
}
};
function addParticle(lane) {
radius = 3;
particle = new Particle(
new Vector(-radius, lane * (HEIGHT) / 5),
new Vector(5),
radius,
lane,
colors[Math.round(Math.random() * 4)]
);
particles.push(particle);
}
requestAnimationFrame(function loop() {
requestAnimationFrame(loop);
ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
ctx.fillRect(0, 0, WIDTH, HEIGHT);
for (var i = 0, len = particles.length; i < len; i++) {
particle = particles[i];
particle.update();
particle.render(ctx);
}
});
//
// ─── SOCKETIO CONNECTION TO NODE WEBSERVER RUNNING OPENCV ───────────────────────
//
var socket = io.connect('http://localhost:8000');
// SEND "CONNECTED" MESSAGE ONCE SUCCSEFULLY CONNECTED
socket.on('connect', function () {
console.log('connected');
});
socket.on('message', function (msg) {
console.log(msg);
// IF NUMBER OF CARS DETECTED IS MORE THAN 0, A PARTICLE WILL BE ADDED RANDOMLY TO ONE OF THE 4 LANES
if (msg >= 0) {
addParticle(Math.round((Math.random() * 3) + 1));
}
});});
Filtering arrays
If you have an array of particles and want to remove some items that satisfy some condition, there are a few ways to do it.
Array.filter
Create a new array using Array.filter
var particles = []; // fill with particles
// remove particles in lane 3 using arrow function
particles = particles.filter(particle => particle.lane !== 3);
// or using via function
particles = particles.filter(function(particle){ return particle.lane !== 3});
This method does create a new array so incurs some penalties due to GC (Garbage collection) and allocation overheads. Also if you have more than one referance to the array you need to up date all references.
var particles = [];
const obj = {
particles : particles, // referance to the array particles.
}
// remove particles in lane 3
particles = particles.filter(particle => particle.lane !== 3)
// the array referenced by obj.particles still has the unfiltered array
obj.particles = particles; // update reference
Array.splice to filter
When there is need to performance you can use Array.splice to remove items from the array. It also has the advantage of not creating a new referance that you would have to propagate.
for(var i = 0; i <particles.length; i++){ // must use particles.length
if(particles[i].lane === 3){
particles.splice(i--,1); // decrease i so you do not skip an item
// as slice will move the index of all
// items above i down one
}
}
// do not use a variable to store the length in the for loop
var len = particles.length
for(var i = 0; i < len; i++){ // will cause unexpected behaviors because
// the array length will change
Flag active
For the best performance you would assign a flag to all particles that determines if the particle is updated and rendered.
for(var i = 0; i <particles.length; i++){
particles[i].active = ! particles[i].lane === 3;
}
Before you update and render you would check if active is true
// in render and update loops
for(var i = 0; i <particles.length; i++){
if(particles[i].active){
particles[i].update(); // or render()
}
}
There is of course the fact you have a growing array. To stop the uncontroled growth of the array you change the way you set the particle properties adding a method to the particle object.
function Particle(){}; // constructor
Particle.prototype.set = function(pos,vel,foo,bar){
this.active = true;
this.velocity = vel;
//... and the other properties
}
The when you add a particle you first check if there is an inactive particle in the particles array and use that by setting its properties with the set function. If there are no inactive particles then you just create a new particle and add it to the particles array setting its properties with set.
function addParticle(pos,vel,foo,bar){
var i;
for(i = 0; i < particles.length; i++){
if(! particles[i].active){
particles[i].set(pos,vel,foo,bar);
return;
}
}
// no inactive particles create a new one
particles.push(
new Particle().set(pos,vel,foo,bar);
);
}