I am currently taking a course on Javascript at Khan Academy and Im having some trouble with one of the assignments. I have posted the question there, but I have noticed that there is less volunteers willing to help there, so I thought I`d ask here.
I have an Assignment on JS Arrays ( https://www.khanacademy.org/computing/computer-programming/programming/arrays/p/project-make-it-rain ) with the following requirements:
To make an animation of rain, it's best if we use arrays to keep track of the drops and their different properties.
Start with this simple code and build on it to make a cool rain animation. Here are some ideas for what you could do:
Add more drops to the arrays.
Make it so that the drops start back at the top once they've reached the bottom, using a conditional.
Make an array of colors, so that every drop is a different color.
Make other things rain, like snowflakes (using more shape commands) or avatars (using the image commands).
Make it so that when the user clicks, a new drop is added to the array.
Initialize the arrays using a for loop and random() function, at the beginning of the program.
Question 1)
I've got it to use a random colour when the raindrop is called, but it overwrites the previous drop's colour if you call it before the previous drop goes off screen. I've tried moving the fill function outside the loop, and around the loop to no avail. Can anyone give me some insight on this? What am I doing wrong?
Question 2)
I've got a conditional (if/else) to make the raindrop start back at the top, but it drops much slower the second time, and only repeats once. Having trouble figuring out the logic of why this is happening in order to "debug" it.
Current code:
var xPositions = [100];
var yPositions = [0];
var colors = [
color(255, 0, 0),
color(255, 128, 0),
color(255, 255, 0),
color(0, 255, 0),
color(0, 0, 255),
color(128, 0, 255)
];
background(204, 247, 255);
fill(colors[Math.floor(Math.random() * colors.length)]);
// Raindrops (random color)
draw = function() {
background(204, 247, 255);
for (var i = 0; i < xPositions.length; i++) {
if (yPositions[i] < 400) { // if the raindrop hasnt hit the bottom
noStroke();
ellipse(xPositions[i], yPositions[i], 10, 10);
yPositions[i] += 5;
} else { // when it hits the bottom, set the yPositions variable to 0 and restart
ellipse(xPositions[i], yPositions.push(0), 10, 10);
yPositions[i] += 5;
}
}
};
var mouseClicked = function() {
xPositions.push(mouseX);
yPositions.push(mouseY);
fill(colors[Math.floor(Math.random() * colors.length)]);
draw();
};
Question 1)
You need to make a fill call for each raindrop that is drawn, per iteration of the for loop inside draw. For a raindrop to maintain its color as it falls (between draw calls) you need to store its color in an additional array, and initialize the corresponding color when you create new drops.
Question 2)
Simply reset the y value in the y array to make the drop start over. I'm not sure what the ellipse call was doing in your code - see below.
// initial raindrop values
var xPositions = [100];
var yPositions = [0];
var colors = [
color(255, 0, 0),
color(255, 128, 0),
color(255, 255, 0),
color(0, 255, 0),
color(0, 0, 255),
color(128, 0, 255)
];
// initialize the first raindrop to a random color
var dropColors = [colors[Math.floor(Math.random() * colors.length)]];
background(204, 247, 255);
draw = function() {
background(204, 247, 255);
for (var i = 0; i < xPositions.length; i++) {
if (yPositions[i] < 400) { // if the raindrop hasnt hit the bottom
noStroke();
// set the fill color for this drop
fill(dropColors[i]);
ellipse(xPositions[i], yPositions[i], 10, 10);
yPositions[i] += 5;
} else { // when it hits the bottom, set the yPositions variable to 0
yPositions[i] = 5;
}
}
};
var mouseClicked = function() {
xPositions.push(mouseX);
yPositions.push(mouseY);
dropColors.push(colors[Math.floor(Math.random() * colors.length)]);
draw();
};
Related
I am taking a course online and I can't find help if am stuck..... I am using brackets and p5.js
these are the instructions i have:
Edit the spotlight object by creating x and y properties initialised to your location. Also endX and endY properties initialised to one of the Minsky's location.
Assign the other 2 spotlights and create the required properties.
Make the spotlight move perfectly from you towards the Minskys by adjusting the increments of x and y properties.
If you get everything correct then it will stop over the target.
Adjust x and y properties using
"+=" or "+"
"-=" or "-"
*/
(the minsky brothers are the targets i need the spotlight to be on, the "your location" is the start location)
i will copy and paste my code and the message i get when i submit :
// other variables, you don't need to change these
var img, spotlight_image;
var spotlight1;
var spotlight2;
var spotlight3;
function preload()
{
img = loadImage('scene.png');
spotlight_image = loadImage('spotlight.png')
}
function setup()
{
createCanvas(img.width, img.height);
//complete the initialisation of the first spotlight
//with properties x, y, endX and endY
spotlight1 = {
image: spotlight_image
x: 164,
y: 810,
endX: 780,
endY: 640,
}
//Initialize the second and third spotlights
spotlight2 = {
image: spotlight_image
x: 164,
y: 810,
endX: 480,
endY: 474,
}
spotlight3 = {
image: spotlight_image
x: 164,
y: 810,
endX:766,
endY: 290,
}
}
function draw()
{
image(img, 0, 0);
// alter the properties x and y of the objects below to animate the spotlights
spotlight.x += 1;
spotlight.y += 1;
////////// DO NOT CHANGE ANYTHING BELOW /////////////
var spotlights = [spotlight1, spotlight2, spotlight3];
var spotlightSize = 300;
blendMode(BLEND);
background(30);
for (var i = 0; i < spotlights.length; i++)
{
var spotlight = spotlights[i];
//stop the spotlight if it's near enough to endx and endy
if(spotlight)
{
//stop the spotlight if it goes off of the screen
spotlight.x = min(spotlight.x, 960);
spotlight.y = min(spotlight.y, 945);
spotlight.x = max(spotlight.x, 0);
spotlight.y = max(spotlight.y, 0);
if (abs(spotlight.endX - spotlight.x) < 50
&& abs(spotlight.endY - spotlight.y) < 50)
{
spotlight.x = spotlight.endX;
spotlight.y = spotlight.endY;
}
image(spotlight.image, spotlight.x-spotlightSize/2,
spotlight.y-spotlightSize/2, spotlightSize, spotlightSize);
}
}
blendMode(DARKEST);
image(img, 0, 0);
////////// DONOT CHANGE ANYTHING ABOVE /////////////
}
the message i get when submitting:
Error in compile
SyntaxError: Unexpected identifier
Blockquote
This is fairly easy to do. If I understand correctly, you want to move an object towards a point over a certain amount of time. All you have to do is get the range between the two X coordinates and the two Y coordinates, divide them by how many frames you want it to be moving for, and update its position by that amount every frame until it reaches its destination.
I am making a basic drawing application on p5.js.
I have placed my background under the draw function as I have inserted sliders to change the rgb of the background.
Once I do this, I cannot draw however. I have a mousePressed function, which works when I move the background to setup().
Any ideas of why this may be?
let brushSize;
let white;
let redB;
let yellowB;
let blueB;
let blackB;
let greenB;
let pinkB;
let brownB;
let brushColor;
let rSlide, gSlide, bSlide;
let r, g, b;
let imgLego;
let imgBrush;
let fontHead;
let fontMid;
let x;
function preload() {
imgLego = loadImage("images/lego.png");
imgBrush = loadImage("images/paint_brush.png");
fontHead = loadFont("fonts/shizuru.ttf");
fontMid = loadFont("fonts/concertOne.ttf");
}// close preload function
function setup() {
x = 10;
createCanvas(1000, 600);
noStroke();
// create the slider for the brush size
brushSize = createSlider(1, 100, 20);
// create the sliders for red, gree, blue values
rSlide = createSlider(0, 255, 255);
gSlide = createSlider(0, 255, 255);
bSlide = createSlider(0, 255, 255);
// position the sliders
rSlide.position(x, x * 20);
gSlide.position(x, x * 22);
bSlide.position(x, x * 24);
brushSize.position(x, x * 26);
// variables to hold the colour (background)
r = 255;
g = 255;
b = 255;
// color variables for brush
redB = color(255);
whiteB = color(255,0,0);
yellowB = color(246, 236, 54);
blueB = color(0,0,255);
blackB = color(0);
greenB = color(0,170,35);
pinkB = color(255,53,184);
brownB = color(155,103,60);
brushColor = redB;
}
function draw() {
background(r, g, b);
noStroke();
// poisition the text & value of slider
textAlign(LEFT, TOP);
fill(120, 120, 255);
textFont(fontMid);
textSize(15);
text("red : " + rSlide.value(), x*2 + rSlide.width, x*20);
text("green : " + gSlide.value(), x*2 + gSlide.width, x*22);
text("blue : " + bSlide.value(), x*2 + bSlide.width, x*24);
text("brush : ", x*2 + bSlide.width, x*26);
// read the value of the slider and store it in a variable
r = rSlide.value();
g = gSlide.value();
b = bSlide.value();
// create & position the default brush to follow the mouse
//ellipse(mouseX, mouseY, brushSize.value(), brushSize.value());
// customise "background" text
fill(0);
textSize(20);
text("BACKGROUND", 20, 180);
// red "navbar"
fill(255,0,0)
rect(0,0,1000,120,0,0,50,0);
// customse "sketch" text
fill(255)
textFont(fontHead);
textSize(40);
text("SKETCH", 180, 10)
// images
image(imgBrush, 930, 40, 40, 40);
image(imgLego, 20, 15, 160, 90);
//**lego block top right corner**//
stroke(0);
strokeWeight(3)
// yellow block
fill(246, 236, 54);
rect(748,18,164,84,5)
// paint buttons
ellipseMode(CENTER);
// white button
fill(whiteB);
ellipse(770,40,30);
// red button
fill(redB);
ellipse(810,40,30);
// yellow button
fill(yellowB);
ellipse(850,40,30);
// blue button
fill(blueB);
ellipse(890,40,30);
// black button
fill(blackB);
ellipse(770,80,30);
// green button
fill(greenB);
ellipse(810,80,30);
// pink button
fill(pinkB);
ellipse(850,80,30);
// brown button
fill(brownB);
ellipse(890,80,30);
}
// create function to allow the mosue to draw the relevant colours from the lego bloack colour paletter - top right corner
function mouseDragged() {
stroke(brushColor);
strokeWeight(5);
line(mouseX,mouseY,pmouseX,pmouseY);
}
Note that the draw function is continuously executed.
This is what's happening:
mouseDragged draws a line
draw function runs in the next frame and whatever's inside this function gets re-drawn on top
So if there's a background() call inside the draw function, it will re-draw the background on top of whatever was previously drawn. Hence the order of operation is so important in processing.
In your sketch, instead of drawing the line in mouseDragged, you can to draw it inside the draw loop after background() is called. For example:
// Globally accessed variable, declared at the top
// Stores the metadata for all the lines drawn in mouseDragged
let drawnLines = []
function mouseDragged() {
drawnLines.push({
mouseX: mouseX,
mouseY: mouseY,
pmouseX: pmouseX,
pmouseY: pmouseY,
});
}
function draw() {
// Draw the background
background(255, 255, 255);
// Draw the lines drawn by the user
stroke(brushColor);
strokeWeight(5);
drawnLines.forEach(drawnLine => {
line(
drawnLine.mouseX,
drawnLine.mouseY,
drawnLine.pmouseX,
drawnLine.pmouseY);
});
}
I am using phaser3. When I create a group of sprites with values for gravity they are not moving. If the sprites are created individually then they do move.
For example, the code below works:
brick1 = game.add.sprite(game.world.width / 2, 0, 'tile'+randomNumber(1,6));
brick2 = game.add.sprite((game.world.width / 2) + 60, 0, 'tile'+randomNumber(1,6));
game.physics.arcade.enable(brick1);
game.physics.arcade.enable(brick2);
brick1.enableBody=true;
brick2.enableBody=true;
brick1.body.gravity.y = 10;
brick2.body.gravity.y = 10;
I need them to be in a group so if I have the code below, they just don't move. I have checked the attributes on each child item and they have values for gravity.
brick = game.add.group();
brick1 = brick.create(game.world.width / 2, 0, 'tile'+randomNumber(1,6));
brick2 = brick.create((game.world.width / 2)+60, 0, 'tile'+randomNumber(1,6));
game.physics.arcade.enable(brick);
game.physics.arcade.enable(brick1);
game.physics.arcade.enable(brick2);
brick1.enableBody=true;
brick2.enableBody=true;
brick1.body.gravity.y = 10;
brick2.body.gravity.y = 10;
console.log(brick);
UPDATE:
I realise that this is happening when setting the velocity of each child in the group. I want to have the user press the down cursor key and then change the velocity. I have the code below, but this just stops them from moving at all.
if (!cursors.down.isDown) {
brick.children.forEach(child => child.body.setVelocity(300));
} else {
brick.children.forEach(child => child.body.setVelocity(300));
}
Also tried with "brick.children.forEach(child => child.body.velocity = 300);", but no luck.
I tried to make your code working with as few corrections as possible. I guess variable game in your example points to the current scene, so i called it scene:
let brick = scene.add.group();
let brick1 = brick.create(game.world.width / 2, 0, 'tile'+randomNumber(1,6));
let brick2 = brick.create((game.world.width / 2)+60, 0, 'tile'+randomNumber(1,6));
scene.physics.add.existing(brick1);
scene.physics.add.existing(brick2);
brick1.enableBody=true;
brick2.enableBody=true;
brick1.body.gravity.y = 10;
brick2.body.gravity.y = 10;
I want to make green ellipses on left side and red ellipses on right side. I use random function to fill the canvas. I use if statements for my purpose. Maybe switch case would be better for this task?
This syntax only generate pink dots, whats wrong?
var spotPos = {
x:300,
y:200
}
var spotCol = {
r:0,
g:0,
b:0
}
function setup() {
createCanvas(600,400);
background(0);
}
function draw() {
spotPos.x = random(0,width);
spotPos.y = random(0,height);
//spotCol.r = random(60,255);
noStroke()
fill(spotCol.r, spotCol.g, spotCol.b)
ellipse(spotPos.x, spotPos.y, 25, 25);
if(spotPos.x < 300) {
spotCol.b = 255;
} else if(spotPos.x > 300) {
spotCol.r = 255;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.1/p5.js"></script>
There were a few things preventing you from achieving your desired result.
First you drew the ellipse and then afterwards selected a color. That meant that in the next round of the draw loop, the ellipse will be drawn somewhere else but the color will still be based on the previous position.
The second problem was the assignment of RGB values:
if(spotPos.x < 300) {
spotCol.b = 255;
} else if(spotPos.x > 300) {
spotCol.r = 255;
}
You only ever assigned blue and red a value of 255 and never changed it backed. So after a few iterations you have fill(255, 0, 255) e.g. full red, no green, full blue which resulted in the pink color you were seeing.
Think of draw as a set of instructions that you repeat over and over again. You need to consider the order of your instructions and in what state you end/start each iteration of your loop. If you change some global variables how will they affect your program the next time draw is run?
I've included a working example below but feel free to experiment with your own ideas and solutions.
const spotPos = {
x: 300,
y: 200
}
const spotCol = {
r: 0,
g: 0,
b: 0
}
function setup() {
createCanvas(600, 400);
background(0);
}
function draw() {
spotPos.x = random(0, width);
spotPos.y = random(0, height);
if (spotPos.x < 300) {
spotCol.r = 0;
spotCol.g = 255;
} else if (spotPos.x > 300) {
spotCol.r = 255;
spotCol.g = 0;
}
noStroke();
fill(spotCol.r, spotCol.g, spotCol.b)
ellipse(spotPos.x, spotPos.y, 25, 25);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.1/p5.js"></script>
If you draw the circle first before coloring it, it won't detect the new color.
I'm trying to create an array of shapes that overlap. But I'm having difficulty preventing those shapes stacking on top of one-another.
I guess I want them to mesh together, if that makes sense?
Here's the code:
var overlap_canvas = document.getElementById("overlap");
var overlap_context = overlap_canvas.getContext("2d");
var x = 200;
var y = x;
var rectQTY = 4 // Number of rectangles
overlap_context.translate(x,y);
for (j=0;j<rectQTY;j++){ // Repeat for the number of rectangles
// Draw a rectangle
overlap_context.beginPath();
overlap_context.rect(-90, -100, 180, 80);
overlap_context.fillStyle = 'yellow';
overlap_context.fill();
overlap_context.lineWidth = 7;
overlap_context.strokeStyle = 'blue';
overlap_context.stroke();
// Degrees to rotate for next position
overlap_context.rotate((Math.PI/180)*360/rectQTY);
}
And here's my jsFiddle:
http://jsfiddle.net/Q8yjP/
And here's what I'm trying to achieve:
Any help or guidance would be greatly appreciated!
You cannot specify this behavior but you can implement an algorithmic-ish approach that uses composite modes.
As shown in this demo the result will be like this:
Define line width and the rectangles you want to draw (you can fill this array with the loop you already got to calculate the positions/angles - for simplicity I just use hard-coded ones here):
var lw = 4,
rects = [
[20, 15, 200, 75],
[150, 20, 75, 200],
[20, 150, 200, 75],
[15, 20, 75, 200]
], ...
I'll explain the line width below.
/// set line-width to half the size
ctx.lineWidth = lw * 0.5;
In the loop you add one criteria for the first draw which is also where you change composite mode. We also clear the canvas with the last rectangle:
/// loop through the array with rectangles
for(;r = rects[i]; i++) {
ctx.beginPath();
ctx.rect(r[0], r[1], r[2], r[3]);
ctx.fill();
ctx.stroke();
/// if first we do a clear with last rectangle and
/// then change composite mode and line width
if (i === 0) {
r = rects[rects.length - 1];
ctx.clearRect(r[0] - lw * 0.5, r[1] - lw * 0.5, r[2] + lw, r[3] + lw);
ctx.lineWidth = lw;
ctx.globalCompositeOperation = 'destination-over';
}
}
This will draw the rectangles and you have the flexibility to change the sizes without needing to recalculate clipping.
The line-width is set separately as stroke strokes the line from the middle. Therefor, since we later use destination-over mode it means half of the line won't be visible as we first fill which becomes part of destination so that the stroke will only be able to fill outside the stroked area (you could reverse the order of stroke and fill but will always run into an adjustment for the first rectangle).
We also need it to calculate the clipping which must include (half) the line on the outside.
This is also why we initially set it to half as the whole line will be drawn the first time - otherwise the first rectangle will have double as thick borders.
The only way to do it to cut your rectangles and compute which sub rectangle goes over which one. But I think you will have to draw your borders and inner rectangles separately because separating rectangles will add additional borders.
Hope it helped
Sadly, the feature you want of setting z-indexes on part of an element using canvas is not available currently. If you just need it for the four rectangle object you could do something like this which hides part of the rectangle to fake the effect you want, however this is hard coded to only 4 rectangles.
var overlap_canvas = document.getElementById("overlap");
var overlap_context = overlap_canvas.getContext("2d");
var x = 200;
var y = x;
var rectQTY = 4 // Number of rectangles
overlap_context.translate(x, y);
for (j = 0; j < rectQTY; j++) { // Repeat for the number of rectangles
// Draw a rectangle
overlap_context.beginPath();
overlap_context.rect(-90, -100, 180, 80);
overlap_context.fillStyle = 'yellow';
overlap_context.fill();
overlap_context.lineWidth = 7;
overlap_context.strokeStyle = 'blue';
overlap_context.stroke();
if (j === 3) {
overlap_context.beginPath();
overlap_context.rect(24, -86, 72, 80);
overlap_context.fillStyle = 'yellow';
overlap_context.fill();
overlap_context.closePath();
overlap_context.beginPath();
overlap_context.moveTo(20, -89.5);
overlap_context.lineTo(100, -89.5);
overlap_context.stroke();
overlap_context.closePath();
overlap_context.beginPath();
overlap_context.moveTo(20.5, -93.1);
overlap_context.lineTo(20.5, 23);
overlap_context.stroke();
overlap_context.closePath();
}
// Degrees to rotate for next position
overlap_context.rotate((Math.PI / 180) * 360 / rectQTY);
}
Demo here
If you have to make it dynamic, you could cut the shapes like Dark Duck suggested or you could try to create a function that detects when an object is overlapped and redraw it one time per rectangle (hard to do and not sure if it'd work). Perhaps you could come up with some equation for positioning the elements in relation to how I have them hard coded now to always work depending on the rotation angle, this would be your best bet IMO, but I don't know how to make that happen exactly
Overall you can't really do what you're looking for at this point in time
Using pure JavaScript ...
<!DOCTYPE html>
<html>
<head></head>
<body>
<canvas id="mycanvas" width="400px" height="400px"></canvas>
<script>
window.onload = function(){
var canvas = document.getElementById('mycanvas');
var ctx = canvas.getContext('2d');
//cheat - use a hidden canvas
var hidden = document.createElement('canvas');
hidden.width = 400;
hidden.height = 400;
var hiddenCtx = hidden.getContext('2d');
hiddenCtx.strokeStyle = 'blue';
hiddenCtx.fillStyle = 'yellow';
hiddenCtx.lineWidth = 5;
//translate origin to centre of hidden canvas, and draw 3/4 of the image
hiddenCtx.translate(200,200);
for(var i=0; i<3; i++){
hiddenCtx.fillRect(-170, -150, 300, 120);
hiddenCtx.strokeRect(-170, -150, 300, 120);
hiddenCtx.rotate(90*(Math.PI/180));
}
//reset the hidden canvas to original status
hiddenCtx.rotate(90*(Math.PI/180));
hiddenCtx.translate(-200,-200);
//translate to middle of visible canvas
ctx.translate(200, 200);
//repeat trick, this time copying from hidden to visible canvas
ctx.drawImage(hidden, 200, 0, 200, 400, 0, -200, 200, 400);
ctx.rotate(180*(Math.PI/180));
ctx.drawImage(hidden, 200, 0, 200, 400, 0, -200, 200, 400);
};
</script>
</body>
</html>
Demo on jsFiddle