I decided to start playing around with dart again today and for some reason I can't get my viewport to clear between frames. I've tried to use clearRect and fillRect to draw a white rectangle over the entire canvas element. Here is my code.
import 'dart:html';
void main() {
var canvasFun = new CanvasFun(query("#Game"));
}
class CanvasFun{
CanvasElement canvas;
num _width;
num _height;
Square square;
CanvasFun(this.canvas){
this.square = new Square(10,10, new Point(50,50));
requestRedraw();
}
void draw(num _){
var ctx = canvas.context2d;
//clear the viewport
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillStyle = "white";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
//draw the square
this.square.draw(ctx);
requestRedraw();
}
void requestRedraw() {
window.requestAnimationFrame(draw);
}
}
class Point {
num x, y;
Point(this.x, this.y);
}
class Square{
num width;
num height;
num vectorX;
num vectorY;
Point location;
Square(this.width, this.height, this.location){
vectorX = 1;
vectorY = 1;
}
void draw(CanvasRenderingContext2D context){
context.save(); //I thought this might fix the blue viewport problem
this.update(context);
context.rect(this.location.x, this.location.y, this.width, this.height);
context.fillStyle = "blue";
context.fill();
}
void update(CanvasRenderingContext2D context)
{
num xBound = context.canvas.width;
num yBound = context.canvas.height;
if((this.location.x + this.width) > xBound){
this.vectorX = -1;
}
else if(this.location.x < 0){
this.vectorX = 1;
}
if((this.location.y + this.height) > yBound){
this.vectorY = -1;
}
else if(this.location.y < 0){
this.vectorY = 1;
}
this.location.x += (this.vectorX * 10);
this.location.y += (this.vectorY * 20);
}
}
The resulting animation draws a rectangle at the correct location but as it moves about the canvas the previous instance of the rectangle is still drawn. I'd like the rectangle to only appear once on the canvas and to look like it is moving about the screen.
Here is a screenshot of the output (notice the blue square is never cleared):
I simplified your code to get it working:
In CanvasFun:
void draw(num _){
var ctx = canvas.context2d;
//clear the viewport
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
//draw the square
this.square.draw(ctx);
requestRedraw();
}
In Square:
void draw(CanvasRenderingContext2D context){
this.update(context);
context.fillStyle = "blue";
context.fillRect(this.location.x, this.location.y, this.width, this.height);
}
I'm not sure what the exact problem was with the original version though. :)
Related
I'm new to p5js and I'm trying to plot incoming x y data by drawing circles which initially expand and then shrink becoming 'fixed' , but the speed at which the circles expand slows down over time - and I can't work out why. I have one array which stores the animated expanding / shrinking circle and another which stores the final state of the fixed circle.
let circles = [];
let circlesStatic = [];
function setup() {
createCanvas(640, 480);
frameRate(60);
smooth();
// e1 = new Ellipse(320,240);
// e2 = new Ellipse(20,20);
background(0);
}
function mouseClicked() {
let e = new Ellipse(mouseX, mouseY);
circles.push(e);
}
function draw() {
for (let i = 0; i < circles.length; i++) {
circles[i].render();
}
for (let i = 0; i < circlesStatic.length; i += 2) {
// noFill();
fill('rgba(50,50,50, 0.1)');
strokeWeight(1);
stroke('rgba(100,100,100, 0.25)');
circle(circlesStatic[i], circlesStatic[i + 1], 20);
}
}
class Ellipse {
// constructor
constructor(x, y) {
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 = random(30, 35);
// change speed for size (how much will the size increase/decrease each frame)
this.sizeSpeed = 3;
// internal frameCount replacement
this.tick = 0;
// this.fill=(255,0,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) {
this.sizeSpeed = 0;
circlesStatic.push(this.x);
circlesStatic.push(this.y);
}
background(0);
// noStroke();
fill(200, 50, 0);
ellipse(this.x, this.y, this.size, this.size);
// this.tick++;
// this.size = map(sin(this.tick * this.sizeSpeed),-1.0,1.0,this.minSize,this.maxSize);
// fill(random(255), random(255), random(255));
// ellipse(this.x,this.y, this.size,this.size);
}
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.1/lib/p5.js"></script>
The problem is that every time you render a circle it checks if it should become "static," and if it should it adds new values to circlesStatic. However, there is nothing to stop the animating circle to continue being rendered on subsequent frames. This results in massive numbers of entries being added to circlesStatic which means longer and longer render times. I've implemented one possible fix below. I also made it so that if you hold the shift key down you will see the previous behavior (watch the length of circlesStatic skyrocket after a few clicks). As soon as you release the shift key the fix will run and the number of entries in circlesStatic will plateau.
let circles = [];
let circlesStatic = [];
function setup() {
createCanvas(640, 480);
frameRate(60);
smooth();
// e1 = new Ellipse(320,240);
// e2 = new Ellipse(20,20);
textSize(16);
}
function mouseClicked() {
let e = new Ellipse(mouseX, mouseY);
circles.push(e);
}
function draw() {
background(0);
push();
noStroke();
fill('red')
text(`circles: ${circles.length}; circlesStatic: ${circlesStatic.length}`, 20, 20);
pop();
for (let i = 0; i < circles.length; i++) {
circles[i].render();
if (!keyIsDown(SHIFT)) {
if (circles[i].isStatic()) {
circles.splice(i, 1);
i--;
}
}
}
for (let i = 0; i < circlesStatic.length; i += 2) {
// noFill();
fill('rgba(50,50,50, 1)');
strokeWeight(1);
stroke('rgba(100,100,100, 1)');
circle(circlesStatic[i], circlesStatic[i + 1], 20);
}
}
class Ellipse {
// constructor
constructor(x, y) {
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 = random(30, 35);
// change speed for size (how much will the size increase/decrease each frame)
this.sizeSpeed = 3;
// internal frameCount replacement
this.tick = 0;
// 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) {
this.sizeSpeed = 0;
circlesStatic.push(this.x);
circlesStatic.push(this.y);
}
// background(0);
// noStroke();
fill(200, 50, 0);
ellipse(this.x, this.y, this.size, this.size);
// this.tick++;
// this.size = map(sin(this.tick * this.sizeSpeed),-1.0,1.0,this.minSize,this.maxSize);
// fill(random(255), random(255), random(255));
// ellipse(this.x,this.y, this.size,this.size);
}
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.1/lib/p5.js"></script>
Can anyone help me?
So, I'm supposed to have a ball that is moving horizontally, such that every time I press the mouse, a ball would get shoot vertically, then slows down due to friction. The vertical ball would stay in the old position but the player would reset.
How do I go about doing that without using classes?
Here my code so far:
boolean circleupdatetostop = true;
float x = 100;
float yshot = 880;
float speedshot = random(4,10);
float speedx = 6;
void setup() {
size(1280,960);
}
void draw() {
background(255);
stroke(0);
fill(0);
circle(x,880,80);
if (x > width || x < 0 ) {
speedx = speedx * -1;
}
if (circleupdatetostop) {
x = x + speedx;
}
if (circleupdatetostop == false) {
float locationx = x;
stroke(0);
fill(255,0,255);
circle(locationx,yshot,30);
yshot = yshot - speedshot;
}
}
void mousePressed () {
circleupdatetostop = !circleupdatetostop;
}
I'm not entirely sure if this is what you meant, but you could achieve shooting multiple balls by using ArrayList as well as processing's PVector to better handle the x and y coordinate pairs. If you wanted to look at classes, see this post.
import java.util.*;
// Whether the ball is moving or not
boolean circleupdatetostop = true;
// Information about the main_ball
PVector position = new PVector(100, 880);
PVector speed = new PVector(6, 0);
float diameter = 80;
// Information about the sot balls
ArrayList<PVector> balls_loc = new ArrayList<PVector>();
ArrayList<PVector> balls_speed = new ArrayList<PVector>();
float diameter_shot = 30;
float friction = 0.994;
void setup() {
size(1280, 960);
}
void draw() {
background(255);
stroke(0);
fill(0);
circle(position.x, position.y, diameter);
// Remember to consider the radius of the ball when bouncing off the edges
if (position.x + diameter/2 > width || position.x - diameter/2 < 0 ) {
speed.mult(-1);
}
if (circleupdatetostop) {
position.add(speed);
}
// Cycle through the list updating their speed and draw each ball
for (int i = 0; i<balls_loc.size(); i++) {
balls_speed.get(i).mult(friction+random(-0.05, 0.05));
balls_loc.get(i).add(balls_speed.get(i));
stroke(0);
fill(255, 0, 255);
circle(balls_loc.get(i).x, balls_loc.get(i).y, diameter_shot);
}
}
void mousePressed(){
// Add a new ball to be drawn
if(circleupdatetostop){
balls_loc.add(new PVector(position.x, position.y));
balls_speed.add(new PVector(0, random(-4, -10)));
}
circleupdatetostop = !circleupdatetostop;
}
I have a simple code that traces the Liss cruve with a small ellipse. I was wondering how to add a fading trail to this shape so it represents the cruve more clearly. I only know a bit about adding trails that follows the mouse but I'm not sure how to do this one.
Any help is appreciated, here is the code:
var t = 0;
function setup() {
createCanvas(500, 500);
fill(255);
}
function draw() {
background(0);
for (i = 0; i < 1; i++) {
y = 160*sin(3*t+PI/2);
x = 160*sin(1*t);
fill(255);
ellipse(width/2+x, height/2+y, 5, 5);
t += .01;
}
}
Try changing background(0) to background(0, 0, 0, 4) :)
Here is a working example:
https://editor.p5js.org/chen-ni/sketches/I-FbLFDXi
Edit:
Here is another solution that doesn't use the background trick:
https://editor.p5js.org/chen-ni/sketches/HiT4Ycd5U
Basically, it keeps track of each point's position and redraws them in every frame with updated alpha to create the "fading out" effect.
var t = 0;
var particleArray = [];
function setup() {
createCanvas(500, 500);
}
function draw() {
background(0);
y = width / 2 + 160 * sin(3 * t + PI / 2);
x = height / 2 + 160 * sin(1 * t);
particleArray.push(new Particle(x, y, t));
for (i=0; i<particleArray.length; i++) {
particleArray[i].show(t);
}
//keep the array short, otherwise it runs very slow
if (particleArray.length > 800) {
particleArray.shift();
}
t += .01;
}
function Particle(x, y, t) {
this.x = x;
this.y = y;
this.t = t;
this.show = function(currentT) {
var _ratio = t / currentT;
_alpha = map(_ratio, 0, 1, 0, 255); //points will fade out as time elaps
fill(255, 255, 255, _alpha);
ellipse(x, y, 5, 5);
}
}
I would like to do the following in p5.js. Say I have a canvas roughly like this:
Let's say I have given those red squares some interactivity using the function mouseClicked() and their respective coordinates in the canvas (as in, if I click on a square, change its color).
Now I'd like to use that blue "i"-button to display some sort of info box, and it should look approximately like this:
I want that "info dialog" to go away if the user clicks on that "OK-button" (whcih is not really a button, but also just a square in a p5 canvas).
Question: Is there an elegant way of deactivating the "square interactivity" and activating that "OK button interactivity" and to have the interactivity the other way around whenever the info box is not being displayed?
The only way I can think of to achieve this goes like this:
function mouseClicked(){
if(infoBoxIsBeingDisplayed){
do something
}else{
do something else
}
}
However, this seems a little convoluted.
I'd appreciate any suggestions on how to do this better.
Your solution seems fine, and it also seems much less "convoluted" than any of the other options. Keep it simple.
You might be able to clean up your code by splitting up your logic into smaller utility functions. Something like this:
function mouseClicked(){
if(infoBoxIsBeingDisplayed){
mouseClickedInfoBox();
}else{
mouseClickedSquaresCanvas();
}
}
Then your logic would be in those utility functions, specific to each screen. You could further split it up to generalize your bounds-checking, but the idea is the same.
I've run into a similar problem to this before, the best way I came up with to solve this (which is a fairly patchy way to do it) is to move the squares outside of the canvas. sorry my code is probably really messy but look at the function game and you will see what i did
If you run the game make it full screen.
example:(to play the game use A and D or use the arrow keys but i suggest A and D because the way the code snippet is set up the game doesn't fit on the page and the page moves around when you press the arrow keys)
var bs = [];
var speed;
var ship1;
var num = 40;
var d;
var gscore = 0;
var highscore = 0;
var cap = 0;
function setup() {
createCanvas(windowWidth,windowHeight- 4);
for(var i = 0; i < num; i++) {
bs[i] = new Box(random(0, width), random(-600,-30));
}
ship1 = new ship();
button1 = new button();
}
function draw() {
background(0);
if(cap == 0){
gscore = gscore + 0.1;
}
if(highscore < gscore){
highscore = gscore;
}
speed = map(gscore,4,100,4,5);
ship1.show();
ship1.update();
for(var i = 0; i < num; i++) {
bs[i].update();
bs[i].show();
if(bs[i].y >= height){
bs[i].x = random(0, width);
bs[i].y = random(-600,-30);
}
for(var j = 0; j < num; j++) {
if(bs[i].touch(bs[j])){
if(bs[i] != bs[j]){
bs[j].x = random(0, width);
bs[j].y = random(-600,-30);
}
}
}
if(bs[i].touch(ship1)){
game();
}
}
push();
fill(255,0,0);
textSize(36);
text("score: "+ floor(gscore),0,36);
text("highscore: "+floor(highscore),0,72);
pop();
}
function Box(x, y) {
this.x = x;
this.y = y;
this.show = function() {
fill(255);
noStroke();
rect(this.x, this.y, 30, 30);
}
this.update = function() {
this.y = this.y + speed;
}
this.touch = function(other){
d = dist(this.x, this.y, other.x, other.y);
if(d < 15/*half of the squares*/+15/*the total area of the ship*/){
return true;
}else {
return false;
}
}
}
function game(){//look here, game is the end game screen
for(var i = 0; i < num; i++) {
bs[i].x = -200;//making all squares x value -200
bs[i].y = -200;//making all squares y value -200
}
ship1.x = -200;//making ship x value -200
ship1.y = -200;//making ship y value -200
cap = 1;//cap is a variable made to stop the score from increasing when the end game screen is shown its "capping" the score
push();
fill(255,0,0);
textAlign(CENTER);
textSize(64);
text("You lose", width/2, height/2);
fill(255);
text("Try again?", width/2,height/2+64);
button1.touch();//touch checks if the mouse is over the button(the button sucks ass btw the hitbox for it is a square not a rectangle)
button1.show();//showing the button
button1.update();//updates the text and button color when highlighted
fill(texthover);
textSize(48);
text("Yes",width/2, height/2+145);
pop();
}
function button(){
this.x = width/2;
this.y = height/2+128;
this.d;
this.update = function() {
this.x = width/2;
this.y = height/2+128;
}
this.show = function(){
push();
rectMode(CENTER);
fill(hover);
rect(this.x, this.y, 128, 64, 50);
pop();
}
this.touch = function(){
this.d = dist(this.x, this.y, mouseX, mouseY);
if(this.d <32){
hover = 51;
texthover = 255;
if(mouseIsPressed){
for(var i = 0; i < num; i++) {
bs[i].x = random(0, width);
bs[i].y = random(-600,-30);
}
ship1.x = width/2;
ship1.y = 450;
gscore = 0;
cap = 0;
}
}else {
hover = 200;
texthover = 0;
}
}
}
function ship() {
this.x = width/2;
this.y = 450;
this.update = function() {
if(keyIsDown(LEFT_ARROW) || keyIsDown(65)) {
if(this.x>14){
this.x = this.x - map(gscore,2,100,2,3);
}
}
if(keyIsDown(RIGHT_ARROW) || keyIsDown(68)) {
if(this.x<width- 15){
this.x = this.x + map(gscore,2,100,2,3);
}
}
}
this.show = function() {
push();
rectMode(CENTER);
fill(200,200,0);
rect(this.x+15, this.y+5, 5, 30);
fill(150,100,200);
rect(this.x+15, this.y + 15,30, 15)
pop();
}
}
function windowResized() {
createCanvas(windowWidth,windowHeight- 4);
button1.update();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.14/addons/p5.dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.14/p5.js"></script>
i was able to make particles go around the ellipse I created which was my previous question. Now I have another one, flow of the particles are not as smooth as i want, there is this diagonal looking shape they follow and when you move the mouse (the ellipse) you can see my lines of my "force" variable. Again I want particles to move like water floating around a rock in a river.
Link for the previous question I asked about same project
int NUM_PARTICLES = 9000;
ParticleSystem p;
Rock r;
void setup()
{
smooth();
size(700,700,P2D);
p = new ParticleSystem();
r = new Rock();
}
void draw()
{
background(0);
p.update();
p.render();
r.rock();
}
float speed = 2;
float rad = 100;
class Particle
{
PVector position, velocity;
float initialPosY;
Particle()
{
position = new PVector(random(width), random(height));
initialPosY = position.y;
velocity = new PVector();
}
void update()
{
velocity.x = speed;
velocity.y = 0;
float d = dist (position.x, position.y, mouseX, mouseY);
if (d < rad) {
float force = map(d, 0, rad, speed, 0);
if (position.x < mouseX) {
if (position.y < mouseY) {
velocity.y = -force;
} else {
velocity.y = force;
}
} else {
if (position.y < mouseY) {
velocity.y = force;
} else {
velocity.y = -force;
}
}
position.add(velocity);
} else {
position = new PVector(position.x+speed, initialPosY);
}
if (position.x<0)position.x+=width;
if (position.x>width)position.x-=width;
if (position.y<0)position.y+=height;
if (position.y>height)position.y-=height;
}
void render()
{
stroke(255, 255, 255, 80);
point(position.x, position.y);
}
}
class ParticleSystem
{
Particle[] particles;
ParticleSystem()
{
particles = new Particle[NUM_PARTICLES];
for (int i = 0; i < NUM_PARTICLES; i++)
{
particles[i]= new Particle();
}
}
void update()
{
for (int i = 0; i < NUM_PARTICLES; i++)
{
particles[i].update();
}
}
void render()
{
for (int i = 0; i < NUM_PARTICLES; i++)
{
particles[i].render();
}
}
}
class Rock{
void rock()
{
noFill();
stroke(255);
strokeWeight(4);
ellipse(mouseX,mouseY,50,50);
}
}
It'll be a lot easier if you try to narrow your problem down to a smaller MCVE, like I did in the answer to your first question:
PVector position;
PVector speed;
void setup() {
size(500, 500);
position = new PVector(250, 0);
speed = new PVector(0, 1);
}
void draw() {
background(0);
if (dist(position.x, position.y, mouseX, mouseY) < 100) {
fill(255, 0, 0);
if (position.x < mouseX) {
position.x--;
} else {
position.x++;
}
} else {
fill(0, 255, 0);
}
ellipse(mouseX, mouseY, 100, 100);
fill(0, 0, 255);
ellipse(position.x, position.y, 20, 20);
position.add(speed);
if (position.y > height) {
position.y = 0;
}
if (position.x < 0) {
position.x = width;
} else if (position.x > width) {
position.x = 0;
}
}
Now that we have that, we can talk about how we might improve it.
Right now, our logic for having the particles avoid our obstacle is here:
if (dist(position.x, position.y, mouseX, mouseY) < 100) {
if (position.x < mouseX) {
position.x--;
} else {
position.x++;
}
}
Notice that we're always moving the particle by 1 pixel, which is why it looks blocky. What we need to do is smooth our transition out by moving the pixel only a little bit at first, and then moving it more as it gets closer to the obstacle.
You might user the lerp() or map() function for this, but for this simple example, we can simply use the dist() function.
Here is a super simple approach you might take:
float distance = dist(position.x, position.y, mouseX, mouseY);
if (position.x < mouseX) {
position.x -= 1000/(distance*distance);
} else {
position.x += 1000/(distance*distance);
}
Notice that by squaring the distance, I'm setting up a polynomical interpolation. In other words, the particle moves faster the closer it gets to the center of the boundary.
Again, you're going to have to play with this to get the exact effect you're looking for, but the basic idea is there: what you're looking for is an interpolation (how fast the particle moves) that scales with the distance from the boundary. You can use squaring to exaggerate the effect.
You could also use basic trig to make the particle follow a circular path.