This is my code:
var drawGlob = function () {
function keyPressed() {
if (keyTyped === 32) {
var size = (random(100, 150));
fill(random(0, 255), 0, 0);
ellipse(mouseX, mouseY, size, size);
return false;
}
}
};
var draw = function () {
noStroke();
drawGlob();
};
After some testing, I discovered that the problem lies within:
var draw = function () {
noStroke();
drawGlob();
};
Not sure where and how you are using it, more information would be useful. Anyway, in drawGlob you are declaring a function in a function, and where is it taking the variable keyTyped since it is not passed as arguments?
Are you referring to keyTyped the function? maybe take a look to this example
Maybe you meant keyCode here another example
Edit: try this:
function keyPressed() {
if (keyCode === 32) {
var size = (random(100, 150));
fill(random(0, 255), 0, 0);
ellipse(mouseX, mouseY, size, size);
return false;
}
};
var draw = function() {
noStroke();
};
So, when you press a Spacebar(32) it will trigger a random circle where the mouse is pointed.
Related
My 9 year old son is learning Javascript. I'm not able to easily help him. He's working on a small project, and can't seem to get past an error:
Uncaught ReferenceError: mainLoop is not defined.
This is a great learning opportunity for him. We appreciate any clues as to what's going on in his code that's causing the error. Thanks!
Here's what he's got:
var CANVAS_WIDTH = 800;
var CANVAS_HEIGHT = 400;
var LEFT_ARROW_KEYCODE = 37;
var RIGHT_ARROW_KEYCODE = 39;
//SETUP
var canvas = document.createElement('canvas');
var c = canvas.getContext('2d');
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
document.body.appendChild(canvas);
window.requestAnimationFrame(mainLoop);
var shapeInfo = {
squares: {
square1: {
x: 10,
y: 10,
w: 30,
h: 30,
color: 'orange'
}
}
};
window.addEventListener('keydown', onKeyDown);
window.addEventListener('keyup', onKeyUp);
var leftArrowKeyIsPressed = false;
var rightArrowKeyIsPressed = false;
var touchingRightEdge = false;
// SENSORS
function sense() {
if (shapeInfo.squares.square1.x <= CANVAS_WIDTH - 30) {
touchingRightEdge = true;
}
// PLAYER CONTROLS
function onKeyDown(event) {
if (event.keyCode === RIGHT_ARROW_KEYCODE) {
rightArrowKeyIsPressed = true;
}
}
function onKeyUp(event) {
if (event.keyCode === RIGHT_ARROW_KEYCODE) {
rightArrowKeyIsPressed = false;
}
}
//MAIN LOOP
function mainLoop() {
window.requestAnimationFrame(mainLoop);
draw();
}
//DRAW
function draw() {
c.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Draw the frame
c.strokeStyle = 'black';
c.strokeRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Draw square1
c.fillStyle = shapeInfo.squares.square1.color;
c.fillRect(shapeInfo.squares.square1.x, shapeInfo.squares.square1.y, shapeInfo.squares.square1.w, shapeInfo.squares.square1.h);
if (rightArrowKeyIsPressed) {
if (!touchingRightEdge) {
shapeInfo.squares.square1.x++;
}
}
if (leftArrowKeyIsPressed) {
shapeInfo.squares.square1.x--;
}
// end
}
}
Great to hear that your son is learning something as cool as JavaScript. Now as #Pointy pointed out (no pun intended) you are calling window.requestAnimationFrame(mainLoop); outside the sense function which causes the error. The mainLoop function does not exist outside sense.
The solution to this would to be define your functions globally, in this case meaning:
not inside another function.
So prevent doing:
function foo() {
// Do something
function bar() {
// Do something else
}
}
foo() // Do someting
bar() // Uncaught ReferenceError: bar is not defined.
Now bar only exists within foo. Instead do this:
function foo() {
// Do something
}
function bar() {
// Do something else
}
foo() // Do something
bar() // Do something else
Both functions can now be called from the same scope (remember this word).
Also in your mainLoop function you got to switch some things around. Try to call the draw function first before you start the mainLoop again. JavaScript works from top to bottom. So in the example below it will first draw and then start the loop again.
function mainLoop() {
draw();
window.requestAnimationFrame(mainLoop);
}
You're doing great, kid! Keep it up and come back whenever you want. We'll help you out!
I'm working on a Snake game in JavaScript and I want the snake to move only vertically or horizontally but it keeps moving diagonally. For example, if I press up, it moves up, but then if I press right, it'll move diagonally rather than only to the right.
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const length_width = 15;
let snakeCoord = [
{x:300,y:150},
{x:315,y:150},
{x:330,y:150},
{x:345,y:150},
{x:360,y:150},
{x:375,y:150}
];
function drawSnakePart(snakePart) {
ctx.beginPath();
ctx.fillRect(snakePart.x, snakePart.y, length_width, length_width);
ctx.strokeRect(snakePart.x, snakePart.y, length_width, length_width);
ctx.closePath();
}
function drawSnake() {
snakeCoord.forEach(drawSnakePart);
}
function moveSnake(dx, dy) {
const head = {
x: snakeCoord[0].x + dx,
y: snakeCoord[0].y + dy
};
snakeCoord.unshift(head);
snakeCoord.pop();
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawSnake();
setTimeout(function() {
moveSnake(dx, dy)
}, 100);
}
function keyPress(e) {
let key = e.key;
if (key == "ArrowUp") {
if (snakeCoord[0].y - length_width !== snakeCoord[1].y) {
moveSnake(0, -length_width);
}
} else if (key == "ArrowDown") {
if (snakeCoord[0].y + length_width !== snakeCoord[1].y) {
moveSnake(0, length_width);
}
} else if (key == "ArrowLeft") {
if (snakeCoord[0].x - length_width !== snakeCoord[1].x) {
moveSnake(-length_width, 0);
}
} else if (key == "ArrowRight") {
if (snakeCoord[0].x + length_width !== snakeCoord[1].x) {
moveSnake(length_width, 0);
}
}
}
drawSnake();
document.addEventListener("keyup", keyPress);
<canvas width="500" height="500"></canvas>
On every keypress and then recursively you are setting new timeout setTimeout(function(){ moveSnake(dx,dy) }, 100);. You end up with growing number of controdicting moveSnake calls.
You should save timeout to a variable and clear it with clearTimeout() on keypress before calling moveSnake().
Rather than have the keyboard handler call a move method that starts its own timer loop, you should have a single update routine that updates everything for one frame of animation. You should also drive rendering as fast as possible using requestAnimationFrame and have each render request the next animation frame. (See example at the link provided.) If you want slower animation then you can ratchet a step-by-step update of the scene with a separate timer. (Trust me, some day you'll want high frame-rate animation, even in your step-by-step game.)
I was bored so I decided to implement some changes to your code.
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const length_width = 15;
let snakeCoord = [
{x:300,y:150},
{x:315,y:150},
{x:330,y:150},
{x:345,y:150},
{x:360,y:150},
{x:375,y:150}
];
let snake = {
dir: {dx: -1, dy: 0},
nextDir: [], // buffered direction changes
speed: 5, // steps per second
ratchet: 0
};
function drawSnakePart(snakePart) {
ctx.beginPath();
ctx.fillRect(snakePart.x, snakePart.y, length_width, length_width);
ctx.strokeRect(snakePart.x, snakePart.y, length_width, length_width);
ctx.closePath();
}
function drawSnake() {
snakeCoord.forEach(drawSnakePart);
}
function moveSnake() {
if (snake.nextDir[0]) {
// only change directions if it doesn't result in doubling back on yourself
if (snakeCoord[0].x + snake.nextDir[0].dx * length_width !== snakeCoord[1].x
&& snakeCoord[0].y + snake.nextDir[0].dy * length_width !== snakeCoord[1].y) {
snake.dir = snake.nextDir[0];
}
snake.nextDir.shift(1);
}
const head = {
x: snakeCoord[0].x + snake.dir.dx * length_width,
y: snakeCoord[0].y + snake.dir.dy * length_width
};
snakeCoord.unshift(head);
snakeCoord.pop();
}
function keyPress(e) {
let key = e.key;
if (key == "ArrowUp") {
setDirection(0,-1);
} else if (key == "ArrowDown") {
setDirection(0, 1);
} else if (key == "ArrowLeft") {
setDirection(-1, 0);
} else if (key == "ArrowRight") {
setDirection(1, 0);
}
e.preventDefault();
}
drawSnake();
let lastTime = new Date();
window.requestAnimationFrame(render);
function setDirection(dx, dy) {
snake.nextDir.push({dx, dy}); // overwrite any pending direction changes.
}
function update() {
let now = Date.now();
let elapsed = (now - lastTime) / 1000;
snake.ratchet += elapsed * snake.speed;
while (snake.ratchet >= 1) {
moveSnake();
snake.ratchet -= 1;
}
lastTime = now;
}
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
update();
drawSnake();
window.requestAnimationFrame(render);
}
document.addEventListener("keydown", keyPress);
* {
overflow: hidden
}
<canvas width="500" height="500"></canvas>
Made this a high frame rate render loop. Employed a ratchet mechanism to move the snake discretely every so often at some rate (see snake.speed). Added a property of the snake which is its direction. (see snake.dir). Buffered keystrokes of requested direction changes (see snake.nextDir) Simplified the logic of preventing the snake from doubling back on itself. Eat up one direction change per move step.
You still need to do the snake self-collision. (Assuming this is what you're up to, with a traditional snake game.)
Anyway, I hope this helps you, or someone else.
I am working with Processing.js (version 1.4.8).
I have 5 white points, which coordinates I chose specifically. The black dot marks the center of the sketch! I want to be able to translate and scale my sketch. ALSO, I want it to occupy the whole window.
var mapWidth, mapHeight, canvas, pjs, centerX, centerY;
var points = [[100, 100], [300, 100], [100, 300], [300, 300], [200, 200]];
var setSize = function() {
mapWidth = $(window).outerWidth();
mapHeight = $(window).outerHeight();
if (pjs) {
pjs.size(mapWidth, mapHeight);
}
};
var clear = function() {
pjs.background(200);
};
var drawPoint = function(coordinates) {
var radius = 30;
pjs.ellipse(coordinates[0], coordinates[1], radius, radius);
};
var drawPoints = function() {
pjs.fill(255);
points.map(function(point) {
drawPoint(point);
});
};
var calculateCenter = function() {
centerX = Math.floor(mapWidth / 2);
centerY = Math.floor(mapHeight / 2);
};
var drawCenter = function() {
calculateCenter();
var radius = 10;
pjs.fill(0);
pjs.ellipse(centerX, centerY, radius, radius);
console.log("center", centerX, centerY);
};
var move = function() {
pjs.translate(200, 300);
redraw();
};
var zoomIn = function() {
pjs.scale(2, 2);
redraw();
};
var draw = function() {
clear();
drawPoints();
drawCenter();
};
var redraw = function() {
clear();
draw();
};
var addEvent = function(object, type, callback) {
if (object == null || typeof object == "undefined") return;
if (object.addEventListener) {
object.addEventListener(type, callback, false);
} else if (object.attachEvent) {
object.attachEvent("on" + type, callback);
} else {
object["on" + type] = callback;
}
};
$(function() {
canvas = document.getElementById("map");
setSize();
var pjsRun = function(processingjs) {
pjs = processingjs;
pjs.setup = function() {
pjs.size(mapWidth, mapHeight);
draw();
};
};
var p = new Processing(canvas, pjsRun);
addEvent(window, "resize", function(event) {
setSize();
redraw();
});
});
Until here, everything is fine, as you can see in this CodePen.
I want to be able to resize the window AND keep the transformations (translations, scales, ...) that I had already performed.
Please, open the CodePen and try to reproduce this weird behaviour:
1) Perform one (or two) transformation(s) using the top-right buttons
The map is translated by 200 to the right and 300 downwards.
Everything OK by now...
But the problem arises now.
2) Resize the window
The five points are again where they were before the "translate" operation.
So... Again... Is there a way to resize without losing all the transformations that had been performed?
Thanks
Like you've discovered, it appears as though calling the size() function resets the transformation matrix. The short answer to your question is that you need to keep track of the transformations, and then apply them whenever you draw something.
The longer answer to your question is that you're using Processing.js a little bit differently than people typically use it. You've left out the draw() function (note that your draw() function is not the draw() function that's automatically called 60 times per second) and are trying to code event handlers yourself. This disconnect is why you're having issues.
If I were you, I'd start with a more basic sketch that starts out using Processing's built-in draw() function. Write code that draws the scene every frame. Make sure you set the translation and scale every frame. Here's an example:
var draw = function() {
scale(scaleX, scaleY);
translate(translateX, translateY);
background(200);
fill(255);
points.map(function(point) {
ellipse(coordinates[0], coordinates[1], 30, 30);
});
fill(0);
ellipse(width/2, height/2, 10, 10);
};
Then setup event listeners that change the values of scaleX and scaleY as well as translateX and translateY. Let Processing handle the rest.
I am really new to p5.js, and I'm trying to find a solution to this for a few days with no luck.
I have a button and an ellipse. When the button is clicked a rectangle appears on the canvas and when the ellipse is clicked its color changes.The problem is that I can't have both at the same time and the reason is this part of the code:
stroke(rgb);
strokeWeight(2);
If I have it on my code, when I click the button the rectangle appears, but when I click the ellipse nothing happens. If I leave it out of my code, I can change the ellipse's color, but when I click the button nothing happens.
I have no idea why these two lines of code make it impossible to interact with both the button and the shape, and I'd really like to find out. Am I missing something important?
My code is the following:
function setup(){
createCanvas(800, 600);
bubble = new new_ellipse(500,300);
}
function draw() {
background(51);
button = createButton("clickme");
button.position(100, 65);
button.mousePressed(show_rect);
bubble.display();
stroke(rgb);
strokeWeight(2);
}
function mousePressed(){
bubble.clicked();
}
function new_ellipse(x,y){
this.x = x;
this.y = y;
this.col = color(255,100);
this.display = function(){
stroke(255);
fill(this.col);
ellipse(this.x, this.y, 100, 100);
}
this.clicked = function(){
var d = dist(mouseX, mouseY, this.x, this.y);
if(d < 50){
this.col = color(233,11,75);
}
}
}
function show_rect(){
fill(255,24,23);
rect(200,200,100,100);
}
Two related issues:
Don't call createButton inside draw. draw is called about 60 times a second, so this spams buttons. draw is your animation loop. Use setup to set things up.
Given that draw() runs 60 times a second, any calls to show_rect triggered by a button press will be almost instantly wiped clean by background(51) a split second later.
One option is to introduce a boolean to keep track of whether the button has been pressed yet, then re-draw it every frame if it's supposed to be visible.
Another approach is to drop the draw() function and re-paint only when state changes. This prevents background(51) from blasting everything and is efficient, but also isn't suitable if you have animations or a good deal of dynamism in your app.
Here's a sample of the first approach, using a boolean:
var bubble, button;
var shouldShowRect = false;
function setup() {
createCanvas(800, 600);
bubble = new Ellipse(500, 300);
button = createButton("clickme");
button.mousePressed(function () {
// or set to true if you don't want to toggle
shouldShowRect = !shouldShowRect;
});
button.position(100, 65);
}
function draw() {
background(51);
bubble.display();
if (shouldShowRect) {
showRect();
}
strokeWeight(2);
}
function mousePressed() {
bubble.clicked();
}
function Ellipse(x, y) {
this.x = x;
this.y = y;
this.col = color(255, 100);
this.display = function() {
stroke(255);
fill(this.col);
ellipse(this.x, this.y, 100, 100);
}
this.clicked = function() {
var d = dist(mouseX, mouseY, this.x, this.y);
if (d < 50) {
this.col = color(233, 11, 75);
}
}
}
function showRect() {
fill(255, 24, 23);
rect(200, 200, 100, 100);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js"></script>
I am writing a game in progress and I ran into a little problem. I am using the keyPressed function and when I am moving the the left, then I suddenly and quickly start moving to the right, my rectangle just stops (vice versa). There will be dodging in my game, so it is important to be able to switch direction as fast as possible. What should I do?
//main file, sketch.js:
var person;
function setup() {
createCanvas(380, 720);
person = new Person();
}
function draw() {
background(64, 64, 64);
person.show();
person.move();
}
function keyReleased() {
if (keyCode === LEFT_ARROW || keyCode === RIGHT_ARROW) {
person.setDirX(0);
}
}
function keyPressed() {
if (keyCode === RIGHT_ARROW) {
person.setDirX(1)
}
else if (keyCode === LEFT_ARROW) {
person.setDirX(-1);
}
}
//person(rectangle) file, person.js:
function Person() {
this.x = width/2;
this.y = height - 20;
this.xdir = 0;
this.ydir = -0.25;
this.show = function() {
noStroke();
fill(250);
rectMode(CENTER);
rect(this.x, this.y, 25, 25);
}
this.setDirX = function(dir) {
this.xdir = dir;
}
this.move = function(dir) {
this.x += this.xdir * 5;
this.y += this.ydir;
}
}
Try to think about what keys you're pressing and releasing when you quickly go from holding in left to holding in right. This is what you're doing:
First you hold down left.
Then you hold down right. So for a split second you're holding down both keys.
Then you let go of left, but continue holding down right.
Your code detects that you released the left key and sets the movement speed to 0, which is why you stop moving.
To fix this, you're going to want to keep track of which keys are currently pressed. You do this by keeping track of a boolean variable for each key you care about, setting those booleans in your keyPressed() and keyReleased() function, and checking those booleans in the draw() function.
Shameless self-promotion: I wrote a tutorial on using this approach here. See the section titled "Handling Multiple Keys". This tutorial is for Processing, but the same idea applies to P5.js.