What am I trying to do:
I'm trying to encode Flappy Bird with the p5.js library.
Problem: The function does not recognize the function I defined.
function Game() {
this.pipes = generatePipes();
setInterval(this.gameLoop, 1000 / 60);
generatePipes = () => {
const firstPipe = new Pipe(null, space);
const secondPipeHeight = winHeight - firstPipe.height - space;
const secondPipe = new Pipe(secondPipeHeight, space);
return [firstPipe, secondPipe]
}
gameLoop = () => {
this.update();
this.draw();
}
update = () => {
if (frameCount % 30 == 0) {
this.pipes = this.generatePipes();
this.pipes.push(...pipes);
}
this.pipes.forEach(pipe => pipe.x = pipe.x - 1);
}
draw = () => {
this.pipes.forEach(pipe => pipe.draw());
}
}
class Pipe {
constructor(height, space) {
this.x = 100;
this.y = height ? winHeight - height : 0; // borunun y eksenine göre konumunu belirler
this.width = pipeWidth;
this.height = height || minPipeHeight + Math.floor(Math.random() * (winHeight - space - minPipeHeight * 2));
}
draw() {
fill(124);
noStroke();
rect(this.x, this.y, this.width, this.height);
}
}
error:
Uncaught TypeError: this.generatePipes is not a function
function Game() {
generatePipes = () => {
const firstPipe = new Pipe(null, space);
const secondPipeHeight = winHeight - firstPipe.height - space;
const secondPipe = new Pipe(secondPipeHeight, space);
return [firstPipe, secondPipe]
}
gameLoop = () => {
this.update();
this.draw();
}
this.pipes = generatePipes();
setInterval(this.gameLoop, 1000 / 60);
update = () => {
if (frameCount % 30 == 0) {
this.pipes = this.generatePipes();
this.pipes.push(...pipes);
}
this.pipes.forEach(pipe => pipe.x = pipe.x - 1);
}
draw = () => {
this.pipes.forEach(pipe => pipe.draw());
}
}
This updated code should work.
In your code as you have invoked the generatePipes() before your function expression it wont work. Function expressions load only when the interpreter reaches the that line of code where you have your function expression first defined.
The way you wrote it: you assigned a function to the variable generatePipes, which means you can only access it once the variable is instanciated.
You have two options: instanciate the generatePipes variable before using it, or declare it as a subfunction.
function Game() {
generatePipes = () => {
...
return x;
}
this.pipes = generatePipes();
}
OR
function Game() {
this.pipes = generatePipes();
function generatePipes() {
...
return x;
}
}
Just assign your functions to this:
this.generatePipes = () => {...}
this.gameLoop = () => {...}
this.update = () => {...}
this.draw = () => {...}
Related
I've been trying to implement Roco C. Buljan's JS Spinning Wheel in React and I'm completely stuck at this particular point. The intended behavior is that the wheel spins once and after stopping displays the pointed sector.
Here's what my code looks like:
import React, { useEffect, useRef } from "react";
import "./SpinWheel.css";
function SpinWheel() {
const sectors = [
{ color: "#f82", label: "Stack" },
{ color: "#0bf", label: "10" },
{ color: "#fb0", label: "200" },
{ color: "#0fb", label: "50" },
{ color: "#b0f", label: "100" },
{ color: "#f0b", label: "5" },
];
// Generate random float in range min-max:
const rand = (m, M) => Math.random() * (M - m) + m;
const tot = sectors.length;
const wheel = useRef(null);
const spin = useRef(null);
useEffect(() => {
const ctx = wheel.current.getContext("2d");
const elSpin = spin.current;
spinWheel(ctx, elSpin);
}, []);
function spinWheel(ctx, elSpin) {
const dia = ctx.canvas.width;
const rad = dia / 2;
const PI = Math.PI;
const TAU = 2 * PI;
const arc = TAU / sectors.length;
const friction = 0.991; // 0.995=soft, 0.99=mid, 0.98=hard
const angVelMin = 0.002; // Below that number will be treated as a stop
let angVelMax = 0; // Random ang.vel. to acceletare to
let angVel = 0; // Current angular velocity
let ang = 0; // Angle rotation in radians
let isSpinning = false;
let isAccelerating = false;
//* Get index of current sector */
const getIndex = () => Math.floor(tot - (ang / TAU) * tot) % tot;
//* Draw sectors and prizes texts to canvas */
const drawSector = (sector, i) => {
const ang = arc * i;
ctx.save();
// COLOR
ctx.beginPath();
ctx.fillStyle = sector.color;
ctx.moveTo(rad, rad);
ctx.arc(rad, rad, rad, ang, ang + arc);
ctx.lineTo(rad, rad);
ctx.fill();
// TEXT
ctx.translate(rad, rad);
ctx.rotate(ang + arc / 2);
ctx.textAlign = "right";
ctx.fillStyle = "#fff";
ctx.font = "bold 30px sans-serif";
ctx.fillText(sector.label, rad - 10, 10);
//
ctx.restore();
};
//* CSS rotate CANVAS Element */
const rotate = () => {
const sector = sectors[getIndex()];
ctx.canvas.style.transform = `rotate(${ang - PI / 2}rad)`;
elSpin.textContent = !angVel ? sector.label : "SPIN";
elSpin.style.background = sector.color;
};
const frame = () => {
if (!isSpinning) return;
if (angVel >= angVelMax) isAccelerating = false;
// Accelerate
if (isAccelerating) {
angVel ||= angVelMin; // Initial velocity kick
angVel *= 1.06; // Accelerate
}
// Decelerate
else {
isAccelerating = false;
angVel *= friction; // Decelerate by friction
// SPIN END:
if (angVel < angVelMin) {
isSpinning = false;
angVel = 0;
}
}
ang += angVel; // Update angle
ang %= TAU; // Normalize angle
rotate(); // CSS rotate!
};
const engine = () => {
frame();
requestAnimationFrame(engine);
};
elSpin.addEventListener("click", () => {
if (isSpinning) return;
isSpinning = true;
isAccelerating = true;
angVelMax = rand(0.25, 0.4);
});
// INIT!
sectors.forEach(drawSector);
rotate(); // Initial rotation
engine(); // Start engine!
}
return (
<div id="wheelOfFortune">
<canvas id="wheel" ref={wheel} width="300" height="300"></canvas>
<div id="spin" ref={spin}>
SPIN asd asd asd as dasd as dasd asd asd as d
</div>
</div>
);
}
export default SpinWheel;
And here's the sandbox link
The issue is that if I console.log the useEffect block, I can see that it executes twice at the start which is not what I intend. I tried putting an empty array as the dependency array but it still doesn't work for me. The second issue is that the wheel supposedly stops and then spins a small amount yet again, resulting in two final sector outputs. Again, not what I intend. I can only guess that it is rendering more than once but I'm not sure. Any ideas what I'm doing wrong?
There are a couple things you need to do to get this working in a react context:
The spinWheel method should return start/stop/cleanup methods for controlling the wheel. The cleanup method should clean up any artifacts such as event handlers and whatnot.
const startSpin = () => {
if (isSpinning) return;
isSpinning = true;
isAccelerating = true;
angVelMax = rand(0.25, 0.4);
};
const stopSpin = () => {
isSpinning = false;
isAccelerating = false;
}
elSpin.addEventListener("click", startSpin);
const cleanup = () => {
elSpin.removeEventListener("click", startSpin);
};
...
return { startSpin, stopSpin, cleanup };
As mentioned in a comment, you need to make sure your effect has a "cleanup" function which stops the wheel (see this link). This is necessary so that the setup → cleanup → setup sequence works in development as described on that page.
useEffect(() => {
const ctx = wheel.current.getContext("2d");
const elSpin = spin.current;
const { startSpin, stopSpin, cleanup } = spinWheel(ctx, elSpin);
startSpin();
return () => {
stopSpin();
cleanup();
}
}, []);
Here's a link to a working sandbox: https://codesandbox.io/s/spin-wheel-forked-tb42zf?file=/src/components/SpinWheel/SpinWheel.js:546-826
I did a Drawing class:
export class Drawing {
constructor(texture) {
const material = new MeshBasicMaterial({
color: 0xffffff,
map: texture
});
this.mesh = new Mesh(new PlaneGeometry(texture.image.naturalWidth / 20, texture.image.naturalHeight / 20), material);
}
setPosition(x, y, z) {
this.mesh.position.x = x;
this.mesh.position.y = y;
this.mesh.position.z = z;
}
}
I would like to access the texture.image properties in order to set the PlaneGeometry. So, before invoking a Drawing object, I do some async/await calls to load the textures (the Drawing invokations are made in the World constructor):
let world;
let raycasterDown;
let prevTime = performance.now();
const direction = new Vector3();
const globalInputs = new GlobalInputs();
const textureLoader = new TextureLoader();
const promiseTextureBack = (pathName) => {
return new Promise((resolve) => {
resolve(textureLoader.load(pathName));
});
}
const allTexturesPromises = [];
drawingPaths.map(pathName => { //drawingPaths is an array of string
allTexturesPromises.push(promiseTextureBack(pathName));
});
const loadingWorld = async () => {
const allTextures = await Promise.all(allTexturesPromises);
console.log(allTextures[0]);
world = new World(allTextures);
document.body.appendChild(world.renderer.domElement);
world.instructions.addEventListener('click', function () {
world.controls.lock();
});
world.controls.addEventListener('lock', function () {
world.instructions.style.display = 'none';
world.blocker.style.display = 'none';
});
world.controls.addEventListener('unlock', function () {
world.blocker.style.display = 'block';
world.instructions.style.display = '';
});
}
init();
function init() {
loadingWorld();
raycasterDown = new Raycaster(new Vector3(), new Vector3(0, -1, 0), 0, 10);
document.addEventListener('keydown', (event) => {
globalInputs.onKeyDown(event);
});
document.addEventListener('keyup', (event) => {
globalInputs.onKeyUp(event);
});
window.addEventListener('resize', onWindowResize);
animate();
}
Nevertheless,
console.log(allTextures[0])
in the loadingWorld returns:
And the image is still undefined... I'm quite sure the issue comes from:
textureLoader.load(pathName)
I'm open to any suggestions !
The load method takes a callback. It doesn't return anything that you can call resolve with. Instead of all this promiseTextureBack code, just use the loadAsync method which returns a promise:
const allTexturesPromises = drawingPaths.map(pathName => {
return textureLoader.load(pathName);
});
Im trying to make simple game in canvas. I made animation for hero using setTimeout() function. I check pressed keys with function moove(e):
Everything works pretty fine when i press leftarrow or rightarrow for the first time, but then hero doesnt moove. Any recomendations to the code is appreciated.
var cns = document.getElementById("can");
cns.height = 600;
cns.width = 300;
var ctx = cns.getContext("2d");
var hero = new Image();
hero.src = "images/hero.png";
hero.onload = function() {
ctx.drawImage(hero, 120, 570);
hero.xx = 120;
hero.yy = 570;
};
var intervalL, intervalR, intervalLL, intervalRR;
var keys = [];
function moove(e) {
keys[e.keyCode] = (e.type == "keydown");
if (keys[37]) {
clearTimeout(intervalR);
clearTimeout(intervalRR);
goLeft(hero);
} else {
clearTimeout(intervalL);
clearTimeout(intervalLL);
}
if (keys[39]) {
clearTimeout(intervalL);
clearTimeout(intervalLL);
goRight(hero);
} else {
clearTimeout(intervalR);
clearTimeout(intervalRR);
}
}
function goLeft(img) {
var x = img.xx,
y = img.yy;
function f() {
ctx.clearRect(img.xx, img.yy, img.width, img.height);
ctx.drawImage(img, x, y);
img.xx = x;
img.yy = y;
x -= 1.2;
if (x < -35) {
x = cns.width;
}
}
if (!intervalL) {
intervalL = setTimeout(function run() {
f();
intervalLL = setTimeout(run, 5);
}, 5);
}
}
Function goRight is similiar to goLeft.
Function moove is called in tag body onkeydown='moove(event)' onkeyup='moove(event)'.
You can check the project here: https://github.com/Fabulotus/Fabu/tree/master/Canvas%20game%20-%20dodge%20and%20jump
The reason it doesn't work the first time is because the first time through you are setting the position to its previous position (x = image.xx) then updating x after you draw. You should update the x value x -= 1.2 before calling drawImage
Here is a "working" version of your code:
var cns = document.getElementById("can");
cns.height = 170;
cns.width = 600;
var ctx = cns.getContext("2d");
var hero = new Image();
hero.src = "http://swagger-net-test.azurewebsites.net/api/Image";
hero.onload = function() {
ctx.drawImage(hero, cns.width-10, cns.height/2);
hero.xx = cns.width-10;
hero.yy = cns.height/2;
};
var intervalL, intervalR, intervalLL, intervalRR;
var keys = [];
function goLeft(img) {
function f() {
ctx.beginPath()
ctx.clearRect(0, 0, cns.width, cns.height);
ctx.drawImage(img, img.xx, img.yy);
img.xx--;
if (img.xx < -img.width) {
img.xx = cns.width;
}
}
if (!intervalL) {
intervalL = setTimeout(function run() {
f();
intervalLL = setTimeout(run, 5);
}, 5);
}
}
goLeft(hero)
<canvas id="can">
As you can see the function goLeft has been significantly simplified.
One recommendation: avoid the many setTimeout and clearTimeout instead use one setInterval to call a draw function that takes care of drawing everything on your game, all the other function should just update the position of your gameObjects.
There is a class with a handler that calculates the direction - InputManager.
How do I change the values of variables in the main Game class each time process an event in the InputManager?
On the codepen or:
class Game {
constructor() {
this.size = 4;
this.score = 0;
this.inputManager = new InputManager;
}
}
class InputManager {
constructor() {
this.mouseDown_position = {};
this.events = {};
this.listen();
}
listen() {
document.addEventListener("mousedown", () => {
this.mouseDown_position = {
x : event.clientX,
y : event.clientY
};
});
document.addEventListener("mouseup", () => {
let mouseUp_position = {
x : event.clientX,
y : event.clientY
};
let deltaX = this.mouseDown_position.x - mouseUp_position.x,
deltaY = this.mouseDown_position.y - mouseUp_position.y;
// Move directions:
if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX > 0) {
console.log('left');
}
if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX < 0) {
console.log('right');
}
});
}
}
My advice is that you pass the Game context to the InputManager, and from there you can manipulate the variable.
This is not encapsulation done right.
In theory Game should have a function to update the score, and InputManager should call that function to update the score (or use a similar approach for other variables).
class Game {
constructor() {
this.size = 4;
this.score = 10;
this.inputManager = new InputManager(this);
this.direction = this.inputManager.direction;
console.log(this.direction);
}
}
class InputManager {
constructor(gC) {
console.log(gC.score);
this.gC = gC;
this.mouseDown_position = {};
this.events = {};
this.direction = null;
this.listen();
}
listen() {
document.addEventListener("mousedown", () => {
this.mouseDown_position = {
x : event.clientX,
y : event.clientY
};
});
document.addEventListener("mouseup", () => {
this.gC.score++;
console.log(this.gC.score);
let mouseUp_position = {
x : event.clientX,
y : event.clientY
};
let deltaX = this.mouseDown_position.x - mouseUp_position.x,
deltaY = this.mouseDown_position.y - mouseUp_position.y;
// MOVE DIRECTION:
// LEFT
if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX > 0) {
console.log('left');
this.direction = 'left';
}
// RIGHT
if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX < 0) {
console.log('right');
this.direction = 'right';
}
// UP
if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY > 0) {
console.log('up');
this.direction = 'up';
}
// DOWN
if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY < 0) {
console.log('down');
this.direction = 'down';
}
});
}
}
var game = new Game();
class Game {
constructor() {
this.size = 4;
this.score = 0;
this.changesize = (size) => {
this.size = size;
}
this.inputManager = new InputManager(changesize);
}
class InputManager(){
constructor(changesize){
[...]
this.changeSize = changesize;
this.changeSize(300);
[...]
}
}
One option could be to supply a reference to InputManager which refers back to the Game instance. For example, suppose InputManager required this reference on its constructor:
class InputManager {
constructor(game) {
this.game = game; // <-- store it locally here
this.mouseDown_position = {};
this.events = {};
this.listen();
}
//...
Then when creating an instance of InputManager, your Game class could pass a reference to itself:
this.inputManager = new InputManager(this);
With that reference, anywhere in InputManager you can modify the values on that instance. Something as simple as:
this.game.score++;
From here you can further refactor to invoke operations in the objects instead of modifying values directly, you can pass closures to wrap access to the values, etc. But the main point is that you'd simply need some reference back to what you want to modify.
I am trying to create a class which creates a Crafty entity with specific properties. So far, the functions within the class do not run because 'this' refers to the window object
$(document).ready(function () {
Crafty.init(window.innerWidth, window.innerHeight);
var player = new controller(37,38,39,40);
player.d.color("red").attr({
w: 50,
h: 50,
x: 0,
y: 0
});
// Jump Height = velocity ^ 2 / gravity * 2
// Terminal Velocity = push * (1 / viscosity)
var gravity = 1;
var viscosity = 0.5;
var frame = (1 / 20);
var distanceMultiplier = 10; //pixels per meter
var timeMultiplier = 20; //relative to actual time
var keystart = [];
var keyboard = [];
function controller (controls) {
this.d = Crafty.e();
this.d.addComponent("2D, Canvas, Color, Collision");
this.d.collision();
this.d.mass = 1;
this.d.a = {
extradistance : 0,
velocity : 0,
acceleration : 0,
force : 0,
resistance : 0
};
this.d.a.push = 0;
this.d.v = {
extradistance : 0,
velocity : 0,
acceleration : 0,
force : 0
};
this.d.jumping = true;
this.d.onHit("Collision", function () {
var a = this.d.hit("Collision");
if (a) {
for (var b in a) {
this.d.x = this.d.x - a[b].normal.x * a[b].overlap;
this.d.y = this.d.y - a[b].normal.y * a[b].overlap;
if (a[b].normal.y < -0.5) {
this.d.jumping = false;
}
if (Math.abs(a[b].normal.x) < 0.2) {
this.d.v.velocity = this.d.v.velocity * a[b].normal.y * 0.2;
}
if (Math.abs(a[b].normal.y) < 0.2) {
this.d.a.velocity = this.d.a.velocity * a[b].normal.x * 0.2;
}
}
return;
}
});
this.d.physics = function () {
if (keyboard[arguments[1]] && !this.jumping) {
this.v.velocity = 5;
this.jumping = true;
}
if (keyboard[arguments[1]] && this.jumping) {
var now = new Date();
if (now.getTime() - keystart[arguments[1]].getTime() < 500) {
this.v.velocity = 5;
}
}
if (keyboard[arguments[0]] && keyboard[arguments[2]]) {
this.a.velocity = 0;
} else {
if (keyboard[arguments[0]]) {
this.a.velocity = -3;
}
if (keyboard[arguments[2]]) {
this.a.velocity = 3;
}
}
if (keyboard[arguments[3]]) {
this.v.velocity = -5;
}
this.a.force = this.a.push - this.a.resistance;
this.a.acceleration = this.a.force / this.mass;
this.a.velocity = this.a.velocity + (this.a.acceleration * frame);
this.a.extradistance = (this.a.velocity * frame);
this.a.resistance = this.a.velocity * viscosity;
this.attr({
x: (this.x + (this.a.extradistance * distanceMultiplier))
});
this.v.force = gravity * this.mass;
this.v.acceleration = this.v.force / this.mass;
this.v.velocity = this.v.velocity - (this.v.acceleration * frame);
this.v.extradistance = (this.v.velocity * frame);
this.attr({
y: (this.y - (this.v.extradistance * distanceMultiplier))
});
setTimeout(this.physics, (frame * 1000) / timeMultiplier);
};
this.d.listen = function(){ document.body.addEventListener("keydown", function (code) {
var then = new Date();
if (!keyboard[code.keyCode] && !this.jumping && code.keyCode == arguments[1]) { //only if not yet pressed it will ignore everything until keyup
keyboard[code.keyCode] = true; //start movement
keystart[code.keyCode] = then; //set time
}
if (!keyboard[code.keyCode] && code.keyCode != arguments[1]) { //only if not yet pressed it will ignore everything until keyup
keyboard[code.keyCode] = true; //start movement
keystart[code.keyCode] = then; //set time
}
});
};
}
player.d.physics();
player.d.listen();
document.body.addEventListener("keyup", function (code) {
keyboard[code.keyCode] = false;
});
});
In trying to put the functions as prototypes of the class, I run into a problem.
Crafty.init(500,500);
function block () {
block.d = Crafty.e("2D, Color, Canvas");
block.d.color("red");
block.d.attr({x:0,y:0,h:50,w:50});
}
block.d.prototype.green = function() {
this.color("green");
}
var block1 = new block();
block1.d.color();
If an object is defined in the constructor, I cannot use it to add a prototype to.
Generally in Crafty, we favor composition. That is, you extend an entity by adding more components to it. You can have kind of a hierarchy by having one component automatically add others during init.
I haven't looked through all of your example code, because there's a lot! But consider the second block:
function block () {
block.d = Crafty.e("2D, Color, Canvas");
block.d.color("red");
block.d.attr({x:0,y:0,h:50,w:50});
}
block.d.prototype.green = function() {
this.color("green");
}
var block1 = new block();
block1.d.color();
You're trying to combine Crafty's way of doing things (an entity component system) with classes in a way that's not very idiomatic. Better to do this:
// Define a new component with Crafty.c(), rather than creating a class
Crafty.c("Block", {
// On init, add the correct components and setup the color and dimensions
init: function() {
this.requires("2D, Color, Canvas")
.color("red")
.attr({x:0,y:0,h:50,w:50});
},
// method for changing color
green: function() {
this.color("green");
}
});
// Create an entity with Crafty.e()
block1 = Crafty.e("Block");
// It's not easy being green!
block1.green();