Why am I getting undefined on ctx.fillRect()? - javascript

When I'm trying to run ctx.fillRect() from class method im getting undefined when passing class object properties.
``javascript
export default classs Reel{
constructor(gameWidth,gameHeight){
this.width = 50;
this.height = 50;
this.position = {
x: gameWIdth / 2 - this.width / 2;
y: gameHeight / 2 - this.height / 2;
}
}
draw(ctx){
ctx.fillRect(this.position.x, this.position.y, this.width, this.height);
}
}
``
It should draw a rectangle on canvas.

Related

Collision detection in html5 canvas between two squares

class Snake {
constructor() {
this.x = 400;
this.y = 400;
this.width = 25;
this.height = 25;
}
draw() {
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
let snake = new Snake();
class Food {
constructor() {
this.x = Math.floor(Math.random() * (canvasWidth - 0) + 0)
this.y = Math.floor(Math.random() * (canvasHeight - 0) + 0)
this.width = 50;
this.height = 50;
/* this.image = new Image()
this.image.src = 'img/apple.png' */
}
update() {
}
draw() {
/* ctx.drawImage(this.image, this.x, this.y, this.width, this.height); */
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
let food = new Food()
function collision(){
if(snake.x < food.x + food.width &&
snake.x + snake.width > food.x &&
snake.y < food.y + food.height &&
snake.height + snake.y > food.y){
alert("hit")
}
}
collision()
I have two JS classes one portrays a snake the other portrays food, I tried this collision algorithm I found but it's not working. where is my mistake or how can I make the collision happen?
Just had to call collision() in the animate function
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
snake.draw();
snake.update();
food.draw()
collision()
requestAnimationFrame(animate);
}
animate();

how to make JavaScript class portable for any project

I have this code and I always have to creat the const ctx at begin of document with same name used in the class, and I wonder if have way make this class more portable for others project
const canvas = document.getElementById('canvas1');
const ctx = canvas.getContext('2d');
class layer{
constructor(image, speedModifier){
this.x = 0;
this.y = 0;
this.width = 2400;
this.height = 700;
//this.x2 = this.width;
this.image = image;
this.speedModifier = speedModifier;
this.speed = gameSpeed * this.speedModifier
}
update(){
this.speed = gameSpeed * this.speedModifier;
this.x = gameFrame * this.speed % this.width;
}
draw(){
ctx.drawImage(this.image, this.x, this.y, this.width, this.height);
ctx.drawImage(this.image, this.x + this.width, this.y, this.width, this.height);
}
}
Just export your class and make your class take in a context in the constructor.
export class Layer {
constructor(image, speedModifier, context) {
this.context = context;
this.x = 0;
this.y = 0;
this.width = 2400;
this.height = 700;
this.image = image;
this.speedModifier = speedModifier;
this.speed = gameSpeed * this.speedModifier
}
draw(){
this.context.drawImage(this.image, this.x, this.y, this.width, this.height);
this.context.drawImage(this.image, this.x + this.width, this.y, this.width, this.height);
}
}
Now anyone who uses your class can import it:
import { Layer } from './layer.js';
And the user of the class has to get the canvas context. However they can then get that once and pass it in to as many layers as they want:
const context = document.querySelector('canvas').getContext('2d');
const layer1 = new Layer(someImage, 1, context);
const layer2 = new Layer(anotherImage, 1, context);
const layer3 = new Layer(thirdImage, 2, context);
Nothing more you can really do beyond that.

Running a separate canvas animation to the game loop animation

I'm caught in a logic knot with this game :(. I simply want to remove the explosions from the screen after say 1 second of each doing its loop. As you can see below they run at the frame rate of the game loop. This is the only way I could animate the explosion - by setting a sprite to move at the speed of the game loop (frame rate).
I don't understand how to connect a separate animation going at a different speed to this same canvas context that is being essentially cleared every frame.. I can't even figure out how to stop the explosion sprite loop.
I've tried creating a separate method drawExplosion() in the Explosion class and using setInterval in the Explosion constructor but it never likes the context I connect it to and throws this error:
Cannot read property 'drawImage' of undefined (i.e. the context is undefined)
If someone could just stop each explosion loop after 1 second I would understand where I went off course
The outline of the code is this:
class Entity
class Ball extends Entity
class Explosion extends Entity
class Enemy extends Entity
class Paddle extends Entity
class InputsManager
class mouseMoveHandler
class Game
const canvas = document.querySelector('canvas')
const game = new Game(canvas)
game.start()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
background-color: rgb(214, 238, 149);
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
margin: 0;
padding: 0;
}
canvas {
background: url("https://picsum.photos/200");
width: 100%;
background-size: cover;
}
</style>
</head>
<body>
<canvas height="459"></canvas>
</body>
<script>
class Entity {
constructor(x, y) {
this.dead = false;
this.collision = 'none'
this.x = x
this.y = y
}
update() { console.warn(`${this.constructor.name} needs an update() function`) }
draw() { console.warn(`${this.constructor.name} needs a draw() function`) }
isDead() { console.warn(`${this.constructor.name} needs an isDead() function`) }
static testCollision(a, b) {
if (a.collision === 'none') {
console.warn(`${a.constructor.name} needs a collision type`)
return undefined
}
if (b.collision === 'none') {
d
console.warn(`${b.constructor.name} needs a collision type`)
return undefined
}
if (a.collision === 'circle' && b.collision === 'circle') {
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2) < a.radius + b.radius
}
if (a.collision === 'circle' && b.collision === 'rect' || a.collision === 'rect' && b.collision === 'circle') {
let circle = a.collision === 'circle' ? a : b
let rect = a.collision === 'rect' ? a : b
// this is a waaaaaay simplified collision that just works in this case (circle always comes from the bottom)
const topOfBallIsAboveBottomOfRect = circle.y - circle.radius <= rect.y + rect.height
const bottomOfBallIsBelowTopOfRect = circle.y + circle.radius >= rect.y - rect.height
const ballIsRightOfRectLeftSide = circle.x + circle.radius >= rect.x - rect.width / 4
const ballIsLeftOfRectRightSide = circle.x - circle.radius <= rect.x + rect.width
return topOfBallIsAboveBottomOfRect && bottomOfBallIsBelowTopOfRect && ballIsRightOfRectLeftSide && ballIsLeftOfRectRightSide
}
console.warn(`there is no collision function defined for a ${a.collision} and a ${b.collision}`)
return undefined
}
static testBallCollision(ball) {
const topOfBallIsAboveBottomOfRect = ball.y - ball.radius <= this.y + this.height / 2
const bottomOfBallIsBelowTopOfRect = ball.y + ball.radius >= this.y - this.height / 2
const ballIsRightOfRectLeftSide = ball.x - ball.radius >= this.x - this.width / 2
const ballIsLeftOfRectRightSide = ball.x + ball.radius <= this.x + this.width / 2
return topOfBallIsAboveBottomOfRect && bottomOfBallIsBelowTopOfRect && ballIsRightOfRectLeftSide && ballIsLeftOfRectRightSide
}
}
class Ball extends Entity {
constructor(x, y) {
super(x, y)
this.dead = false;
this.collision = 'circle'
this.speed = 300 // px per second
this.radius = 2.5 // radius in px
this.color = '#fff'
this.ballsDistanceY = 12
}
update({ deltaTime }) {
// Ball still only needs deltaTime to calculate its update
this.y -= this.speed * deltaTime / 1000 // deltaTime is ms so we divide by 1000
}
/** #param {CanvasRenderingContext2D} context */
draw(context) {
context.beginPath()
context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI)
context.fillStyle = this.color;
context.fill()
context.beginPath()
context.arc(this.x, this.y + this.ballsDistanceY, this.radius, 0, 2 * Math.PI)
context.fillStyle = this.color;
context.fill()
context.beginPath()
context.arc(this.x, this.y - this.ballsDistanceY, this.radius, 0, 2 * Math.PI)
context.fillStyle = this.color;
context.fill()
}
isDead(enemy) {
const outOfBounds = this.y < 0 - this.radius
const collidesWithEnemy = Entity.testCollision(enemy, this)
if (outOfBounds) {
return true
}
if (collidesWithEnemy) {
//console.log('dead')
this.dead = true;
game.hitEnemy();
return true
}
}
}
class Explosion extends Entity {
constructor(x, y, contextFromGameObject){
super(x, y)
this.contextFromGameObject = contextFromGameObject
this.imgExplosion = new Image();
this.imgExplosion.src = "https://i.ibb.co/9Ggfzxr/explosion.png";
this.totalNumberOfFrames = 12 // ten images in the image (see the url above)
this.spriteFrameNumber = 0 // This is changed to make the sprite animate
this.widthOfSprite = 1200 // this.imgExplosion.width; // find the width of the image
this.heightOfSprite = 100 // this.imgExplosion.height; // find the height of the image
this.widthOfSingleImage = this.widthOfSprite / this.totalNumberOfFrames; // The width of each image in the spirite
//this.timerId = setInterval(this.explode.bind(this), 100)
this.scaleExplosion = 0.5
//this.timerId = setInterval(this.drawExplosion, 100);
}
// drawExplosion(){
// console.log(this.spriteFrameNumber)
// //ctx.clearRect(0, 0, 500, 500)
// this.spriteFrameNumber += 1; // changes the sprite we look at
// this.spriteFrameNumber = this.spriteFrameNumber % this.totalNumberOfFrames; // Change this from 0 to 1 to 2 ... upto 9 and back to 0 again, then 1...
// this.contextFromGameObject.drawImage(this.imgExplosion,
// this.spriteFrameNumber * this.widthOfSingleImage, 0, // x and y - where in the sprite
// this.widthOfSingleImage, this.heightOfSprite, // width and height
// this.x - 25, this.y - 25, // x and y - where on the screen
// this.widthOfSingleImage, this.heightOfSprite // width and height
// );
// if (this.spriteFrameNumber > 9) {
// clearInterval(this.timerId)
// };
// }
/** #param {CanvasRenderingContext2D} context */
draw(context, frameNumber) {
console.log(frameNumber)
//ctx.clearRect(0, 0, 500, 500)
this.spriteFrameNumber += 1; // changes the sprite we look at
this.spriteFrameNumber = this.spriteFrameNumber % this.totalNumberOfFrames; // Change this from 0 to 1 to 2 ... upto 9 and back to 0 again, then 1...
context.drawImage(this.imgExplosion,
this.spriteFrameNumber * this.widthOfSingleImage, 0, // x and y - where in the sprite
this.widthOfSingleImage, this.heightOfSprite, // width and height
this.x - 25, this.y - 25, // x and y - where on the screen
this.widthOfSingleImage, this.heightOfSprite // width and height
);
}
update() {
}
isDead(ball, isDead) {
if(isDead == 'true'){
clearTimeout(this.timerId);
return true
}
return false
}
}
class Enemy extends Entity {
constructor(x, y) {
super(x, y)
this.collision = 'rect'
this.height = 50;
this.width = 50;
this.speedVar = 4;
this.speed = this.speedVar;
this.color = '#EC3AC8';
this.color2 = '#000000';
this.y = y;
this.imgEnemy = new Image();
this.imgEnemy.src = "https://i.ibb.co/kgXsr66/question.png";
this.runCount = 1;
this.timerId = setInterval(this.movePosish.bind(this), 1000);
}
movePosish() {
//console.log(this.runCount)
// x 10 -> 240
// y 10 -> 300
switch (this.runCount) {
case 0:
this.x = 20; this.y = 200;
break
case 1:
this.x = 200; this.y = 300;
break
case 2:
this.x = 30; this.y = 20;
break
case 3:
this.x = 230; this.y = 150;
break
case 4:
this.x = 200; this.y = 20;
break
case 5:
this.x = 30; this.y = 90;
break
case 6:
this.x = 240; this.y = 20;
break
case 7:
this.x = 30; this.y = 150;
break
case 8:
this.x = 180; this.y = 170;
break
case 9:
this.x = 30; this.y = 50;
break
case 10:
this.x = 130; this.y = 170;
break
}
//if 10th image remove image and clear timer
this.runCount += 1;
if (this.runCount > 10) {
//clearInterval(this.timerId)
this.runCount = 0;
console.log('ya missed 10 of em')
};
}
update() {
// //Moving left/right
// this.x += this.speed;
// if (this.x > canvas.width - this.width) {
// this.speed -= this.speedVar;
// }
// if (this.x === 0) {
// this.speed += this.speedVar;
// }
}
/** #param {CanvasRenderingContext2D} context */
draw(context) {
// context.beginPath();
// context.rect(this.x, this.y, this.width, this.height);
// context.fillStyle = this.color2;
// context.fill();
// context.closePath();
context.drawImage(this.imgEnemy, this.x, this.y);
}
isDead(enemy) {
//// collision detection
// const collidesWithEnemy = Entity.testCollision(enemy, ball)
// if (collidesWithEnemy){
// console.log('enemy dead')
// game.hitEnemy();
// return true
// }
// if (ball.dead){
// console.log('enemy dead')
// game.hitEnemy();
// return true
// }
return false
}
}
class Paddle extends Entity {
constructor(x, width) {
super(x, width)
this.collision = 'rect'
this.speed = 200
this.height = 25
this.width = 30
this.color = "#74FCEF"
}
update({ deltaTime, inputs, mouse }) {
// Paddle needs to read both deltaTime and inputs
// if mouse inside canvas AND not on mobile
if (mouse.insideCanvas) {
this.x = mouse.paddleX
} else {
this.x += this.speed * deltaTime / 1000 * inputs.direction
// stop from going off screen
if (this.x < this.width / 2) {
this.x = this.width / 2;
} else if (this.x > canvas.width - this.width / 2) {
this.x = canvas.width - this.width / 2
}
}
}
/** #param {CanvasRenderingContext2D} context */
draw(context) {
context.beginPath();
context.rect(this.x - this.width / 2, this.y - this.height / 2, this.width, this.height);
context.fillStyle = this.color;
context.fill();
context.closePath();
context.beginPath();
context.rect(this.x - this.width / 12, this.y - this.height / 1.1, this.width / 6, this.height);
context.fillStyle = this.color;
context.fill();
context.closePath();
}
isDead() { return false }
}
class InputsManager {
constructor() {
this.direction = 0 // this is the value we actually need in out Game object
window.addEventListener('keydown', this.onKeydown.bind(this))
window.addEventListener('keyup', this.onKeyup.bind(this))
}
onKeydown(event) {
switch (event.key) {
case 'ArrowLeft':
this.direction = -1
break
case 'ArrowRight':
this.direction = 1
break
}
}
onKeyup(event) {
switch (event.key) {
case 'ArrowLeft':
if (this.direction === -1) // make sure the direction was set by this key before resetting it
this.direction = 0
break
case 'ArrowRight':
this.direction = 1
if (this.direction === 1) // make sure the direction was set by this key before resetting it
this.direction = 0
break
}
}
}
class mouseMoveHandler {
constructor() {
// this.paddleWidth = paddleWidth;
this.x = 0;
this.paddleX = 0;
//this.canvas = canvas;
document.addEventListener("mousemove", this.onMouseMove.bind(this), false);
}
//'relative to canvas width' mouse position snippet
getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect(), // abs. size of element
scaleX = canvas.width / rect.width, // relationship bitmap vs. element for X
scaleY = canvas.height / rect.height; // relationship bitmap vs. element for Y
//console.log('canvas width = ' + canvas.width)
return {
x: (evt.clientX - rect.left) * scaleX, // scale mouse coordinates after they have
y: (evt.clientY - rect.top) * scaleY // been adjusted to be relative to element
}
}
onMouseMove(e) {
//console.log('moving')
//this.x = 100;
this.x = this.getMousePos(canvas, e).x; //relative x on canvas
this.y = this.getMousePos(canvas, e).y; //relative x on canvas
this.insideCanvas = false;
if (this.x > 0 && this.x < canvas.width) {
if (this.y > 0 && this.y < canvas.height) {
//console.log('inside')
this.insideCanvas = true;
} else {
this.insideCanvas = false;
}
}
if (this.x - 20 > 0 && this.x < canvas.width - 20) {
this.paddleX = this.x;
}
}
}
class Game {
/** #param {HTMLCanvasElement} canvas */
constructor(canvas) {
this.entities = [] // contains all game entities (Balls, Paddles, ...)
this.context = canvas.getContext('2d')
this.newBallInterval = 900 // ms between each ball
this.lastBallCreated = -Infinity // timestamp of last time a ball was launched
this.paddleWidth = 50
this.isMobile = false
this.frameNumber = 0;
}
endGame() {
//clear all elements, remove h-hidden class from next frame, then remove h-hidden class from the cta content
console.log('endgame')
const endGame = true;
game.loop(endGame)
}
hitEnemy() {
//this.timerId = setTimeout(endExplosion(), 500);
this.explosion = new Explosion(this.enemy.x, this.enemy.y, this.context)
this.entities.push(this.explosion)
}
start() {
this.lastUpdate = performance.now()
this.enemy = new Enemy(140, 220)
this.entities.push(this.enemy)
// we store the new Paddle in this.player so we can read from it later
this.player = new Paddle(150, 400)
// but we still add it to the entities list so it gets updated like every other Entity
this.entities.push(this.player)
//start watching inputs
this.inputsManager = new InputsManager()
//start watching mousemovement
this.mouseMoveHandler = new mouseMoveHandler()
//start game loop
this.loop()
}
update() {
// calculate time elapsed
const newTime = performance.now()
const deltaTime = newTime - this.lastUpdate
this.isMobile = window.matchMedia('(max-width: 1199px)');
// we now pass more data to the update method so that entities that need to can also read from our InputsManager
const frameData = {
deltaTime,
inputs: this.inputsManager,
mouse: this.mouseMoveHandler,
context: this.context
}
// update every entity
this.entities.forEach(entity => entity.update(frameData))
// other update logic (here, create new entities)
if (this.lastBallCreated + this.newBallInterval < newTime) {
// this is quick and dirty, you should put some more thought into `x` and `y` here
this.ball = new Ball(this.player.x, 360)
this.entities.push(this.ball)
this.lastBallCreated = newTime
}
// remember current time for next update
this.lastUpdate = newTime
}
cleanup() {
//console.log(this.entities[0])//Enemy
//console.log(this.entities[1])//Paddle
//console.log(this.entities[2])//Ball
//to prevent memory leak, don't forget to cleanup dead entities
this.entities.forEach(entity => {
if (entity.isDead(this.enemy)) {
const index = this.entities.indexOf(entity)
this.entities.splice(index, 1)
}
})
}
draw() {
//draw entities
this.entities.forEach(entity => entity.draw(this.context, this.frameNumber))
}
//main game loop
loop(endGame) {
this.myLoop = requestAnimationFrame(() => {
this.frameNumber += 1;
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height)
if (endGame) {
cancelAnimationFrame(this.myLoop);
this.endGame()
return;
}
this.update()
this.draw()
this.cleanup()
this.loop()
})
}
}
const canvas = document.querySelector('canvas')
const game = new Game(canvas)
game.start()
</script>
</html>
What you might want to do is to use a boolean that stays true as long as your animation should be running, it will call the function that draws your explosion.
class Entity {
constructor(x, y) {
this.dead = false;
this.collision = 'none'
this.x = x
this.y = y
}
update() { console.warn(`${this.constructor.name} needs an update() function`) }
draw() { console.warn(`${this.constructor.name} needs a draw() function`) }
isDead() { console.warn(`${this.constructor.name} needs an isDead() function`) }
static testCollision(a, b) {
if (a.collision === 'none') {
console.warn(`${a.constructor.name} needs a collision type`)
return undefined
}
if (b.collision === 'none') {
d
console.warn(`${b.constructor.name} needs a collision type`)
return undefined
}
if (a.collision === 'circle' && b.collision === 'circle') {
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2) < a.radius + b.radius
}
if (a.collision === 'circle' && b.collision === 'rect' || a.collision === 'rect' && b.collision === 'circle') {
let circle = a.collision === 'circle' ? a : b
let rect = a.collision === 'rect' ? a : b
// this is a waaaaaay simplified collision that just works in this case (circle always comes from the bottom)
const topOfBallIsAboveBottomOfRect = circle.y - circle.radius <= rect.y + rect.height
const bottomOfBallIsBelowTopOfRect = circle.y + circle.radius >= rect.y - rect.height
const ballIsRightOfRectLeftSide = circle.x + circle.radius >= rect.x - rect.width / 4
const ballIsLeftOfRectRightSide = circle.x - circle.radius <= rect.x + rect.width
return topOfBallIsAboveBottomOfRect && bottomOfBallIsBelowTopOfRect && ballIsRightOfRectLeftSide && ballIsLeftOfRectRightSide
}
console.warn(`there is no collision function defined for a ${a.collision} and a ${b.collision}`)
return undefined
}
static testBallCollision(ball) {
const topOfBallIsAboveBottomOfRect = ball.y - ball.radius <= this.y + this.height / 2
const bottomOfBallIsBelowTopOfRect = ball.y + ball.radius >= this.y - this.height / 2
const ballIsRightOfRectLeftSide = ball.x - ball.radius >= this.x - this.width / 2
const ballIsLeftOfRectRightSide = ball.x + ball.radius <= this.x + this.width / 2
return topOfBallIsAboveBottomOfRect && bottomOfBallIsBelowTopOfRect && ballIsRightOfRectLeftSide && ballIsLeftOfRectRightSide
}
}
class Ball extends Entity {
constructor(x, y) {
super(x, y)
this.dead = false;
this.collision = 'circle'
this.speed = 300 // px per second
this.radius = 2.5 // radius in px
this.color = '#fff'
this.ballsDistanceY = 12
}
update({ deltaTime }) {
// Ball still only needs deltaTime to calculate its update
this.y -= this.speed * deltaTime / 1000 // deltaTime is ms so we divide by 1000
}
/** #param {CanvasRenderingContext2D} context */
draw(context) {
context.beginPath()
context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI)
context.fillStyle = this.color;
context.fill()
context.beginPath()
context.arc(this.x, this.y + this.ballsDistanceY, this.radius, 0, 2 * Math.PI)
context.fillStyle = this.color;
context.fill()
context.beginPath()
context.arc(this.x, this.y - this.ballsDistanceY, this.radius, 0, 2 * Math.PI)
context.fillStyle = this.color;
context.fill()
}
isDead(enemy) {
const outOfBounds = this.y < 0 - this.radius
const collidesWithEnemy = Entity.testCollision(enemy, this)
if (outOfBounds) {
return true
}
if (collidesWithEnemy) {
//console.log('dead')
this.dead = true;
game.hitEnemy();
return true
}
}
}
class Explosion extends Entity {
constructor(x, y, contextFromGameObject){
super(x, y)
this.contextFromGameObject = contextFromGameObject
this.imgExplosion = new Image();
this.imgExplosion.src = "https://i.ibb.co/9Ggfzxr/explosion.png";
this.totalNumberOfFrames = 12 // ten images in the image (see the url above)
this.spriteFrameNumber = 0 // This is changed to make the sprite animate
this.widthOfSprite = 1200 // this.imgExplosion.width; // find the width of the image
this.heightOfSprite = 100 // this.imgExplosion.height; // find the height of the image
this.widthOfSingleImage = this.widthOfSprite / this.totalNumberOfFrames; // The width of each image in the spirite
//this.timerId = setInterval(this.explode.bind(this), 100)
this.scaleExplosion = 0.5
this.explosionHappened = 0;
//this.timerId = setInterval(this.drawExplosion, 100);
}
// drawExplosion(){
// console.log(this.spriteFrameNumber)
// //ctx.clearRect(0, 0, 500, 500)
// this.spriteFrameNumber += 1; // changes the sprite we look at
// this.spriteFrameNumber = this.spriteFrameNumber % this.totalNumberOfFrames; // Change this from 0 to 1 to 2 ... upto 9 and back to 0 again, then 1...
// this.contextFromGameObject.drawImage(this.imgExplosion,
// this.spriteFrameNumber * this.widthOfSingleImage, 0, // x and y - where in the sprite
// this.widthOfSingleImage, this.heightOfSprite, // width and height
// this.x - 25, this.y - 25, // x and y - where on the screen
// this.widthOfSingleImage, this.heightOfSprite // width and height
// );
// if (this.spriteFrameNumber > 9) {
// clearInterval(this.timerId)
// };
// }
/** #param {CanvasRenderingContext2D} context */
draw(context, frameNumber) {
//console.log(frameNumber)
if(this.explosionHappened)
{
//ctx.clearRect(0, 0, 500, 500)
this.spriteFrameNumber += 1; // changes the sprite we look at
this.spriteFrameNumber = this.spriteFrameNumber % this.totalNumberOfFrames; // Change this from 0 to 1 to 2 ... upto 9 and back to 0 again, then 1...
context.drawImage(this.imgExplosion,
this.spriteFrameNumber * this.widthOfSingleImage, 0, // x and y - where in the sprite
this.widthOfSingleImage, this.heightOfSprite, // width and height
this.x - 25, this.y - 25, // x and y - where on the screen
this.widthOfSingleImage, this.heightOfSprite // width and height
);
this.explosionHappened=this.spriteFrameNumber;
}
}
update() {
}
isDead(ball, isDead) {
if(isDead == 'true'){
clearTimeout(this.timerId);
return true
}
return false
}
}
class Enemy extends Entity {
constructor(x, y) {
super(x, y)
this.collision = 'rect'
this.height = 50;
this.width = 50;
this.speedVar = 4;
this.speed = this.speedVar;
this.color = '#EC3AC8';
this.color2 = '#000000';
this.y = y;
this.imgEnemy = new Image();
this.imgEnemy.src = "https://i.ibb.co/kgXsr66/question.png";
this.runCount = 1;
this.timerId = setInterval(this.movePosish.bind(this), 1000);
}
movePosish() {
//console.log(this.runCount)
// x 10 -> 240
// y 10 -> 300
switch (this.runCount) {
case 0:
this.x = 20; this.y = 200;
break
case 1:
this.x = 200; this.y = 300;
break
case 2:
this.x = 30; this.y = 20;
break
case 3:
this.x = 230; this.y = 150;
break
case 4:
this.x = 200; this.y = 20;
break
case 5:
this.x = 30; this.y = 90;
break
case 6:
this.x = 240; this.y = 20;
break
case 7:
this.x = 30; this.y = 150;
break
case 8:
this.x = 180; this.y = 170;
break
case 9:
this.x = 30; this.y = 50;
break
case 10:
this.x = 130; this.y = 170;
break
}
//if 10th image remove image and clear timer
this.runCount += 1;
if (this.runCount > 10) {
//clearInterval(this.timerId)
this.runCount = 0;
console.log('ya missed 10 of em')
};
}
update() {
// //Moving left/right
// this.x += this.speed;
// if (this.x > canvas.width - this.width) {
// this.speed -= this.speedVar;
// }
// if (this.x === 0) {
// this.speed += this.speedVar;
// }
}
/** #param {CanvasRenderingContext2D} context */
draw(context) {
// context.beginPath();
// context.rect(this.x, this.y, this.width, this.height);
// context.fillStyle = this.color2;
// context.fill();
// context.closePath();
context.drawImage(this.imgEnemy, this.x, this.y);
}
isDead(enemy) {
//// collision detection
// const collidesWithEnemy = Entity.testCollision(enemy, ball)
// if (collidesWithEnemy){
// console.log('enemy dead')
// game.hitEnemy();
// return true
// }
// if (ball.dead){
// console.log('enemy dead')
// game.hitEnemy();
// return true
// }
return false
}
}
class Paddle extends Entity {
constructor(x, width) {
super(x, width)
this.collision = 'rect'
this.speed = 200
this.height = 25
this.width = 30
this.color = "#74FCEF"
}
update({ deltaTime, inputs, mouse }) {
// Paddle needs to read both deltaTime and inputs
// if mouse inside canvas AND not on mobile
if (mouse.insideCanvas) {
this.x = mouse.paddleX
} else {
this.x += this.speed * deltaTime / 1000 * inputs.direction
// stop from going off screen
if (this.x < this.width / 2) {
this.x = this.width / 2;
} else if (this.x > canvas.width - this.width / 2) {
this.x = canvas.width - this.width / 2
}
}
}
/** #param {CanvasRenderingContext2D} context */
draw(context) {
context.beginPath();
context.rect(this.x - this.width / 2, this.y - this.height / 2, this.width, this.height);
context.fillStyle = this.color;
context.fill();
context.closePath();
context.beginPath();
context.rect(this.x - this.width / 12, this.y - this.height / 1.1, this.width / 6, this.height);
context.fillStyle = this.color;
context.fill();
context.closePath();
}
isDead() { return false }
}
class InputsManager {
constructor() {
this.direction = 0 // this is the value we actually need in out Game object
window.addEventListener('keydown', this.onKeydown.bind(this))
window.addEventListener('keyup', this.onKeyup.bind(this))
}
onKeydown(event) {
switch (event.key) {
case 'ArrowLeft':
this.direction = -1
break
case 'ArrowRight':
this.direction = 1
break
}
}
onKeyup(event) {
switch (event.key) {
case 'ArrowLeft':
if (this.direction === -1) // make sure the direction was set by this key before resetting it
this.direction = 0
break
case 'ArrowRight':
this.direction = 1
if (this.direction === 1) // make sure the direction was set by this key before resetting it
this.direction = 0
break
}
}
}
class mouseMoveHandler {
constructor() {
// this.paddleWidth = paddleWidth;
this.x = 0;
this.paddleX = 0;
//this.canvas = canvas;
document.addEventListener("mousemove", this.onMouseMove.bind(this), false);
}
//'relative to canvas width' mouse position snippet
getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect(), // abs. size of element
scaleX = canvas.width / rect.width, // relationship bitmap vs. element for X
scaleY = canvas.height / rect.height; // relationship bitmap vs. element for Y
//console.log('canvas width = ' + canvas.width)
return {
x: (evt.clientX - rect.left) * scaleX, // scale mouse coordinates after they have
y: (evt.clientY - rect.top) * scaleY // been adjusted to be relative to element
}
}
onMouseMove(e) {
//console.log('moving')
//this.x = 100;
this.x = this.getMousePos(canvas, e).x; //relative x on canvas
this.y = this.getMousePos(canvas, e).y; //relative x on canvas
this.insideCanvas = false;
if (this.x > 0 && this.x < canvas.width) {
if (this.y > 0 && this.y < canvas.height) {
//console.log('inside')
this.insideCanvas = true;
} else {
this.insideCanvas = false;
}
}
if (this.x - 20 > 0 && this.x < canvas.width - 20) {
this.paddleX = this.x;
}
}
}
class Game {
/** #param {HTMLCanvasElement} canvas */
constructor(canvas) {
this.entities = [] // contains all game entities (Balls, Paddles, ...)
this.context = canvas.getContext('2d')
this.newBallInterval = 900 // ms between each ball
this.lastBallCreated = -Infinity // timestamp of last time a ball was launched
this.paddleWidth = 50
this.isMobile = false
this.frameNumber = 0;
}
endGame() {
//clear all elements, remove h-hidden class from next frame, then remove h-hidden class from the cta content
console.log('endgame')
const endGame = true;
game.loop(endGame)
}
hitEnemy() {
//this.timerId = setTimeout(endExplosion(), 500);
this.explosion = new Explosion(this.enemy.x, this.enemy.y, this.context)
this.explosion.explosionHappened=1;
this.entities.push(this.explosion)
}
start() {
this.lastUpdate = performance.now()
this.enemy = new Enemy(140, 220)
this.entities.push(this.enemy)
// we store the new Paddle in this.player so we can read from it later
this.player = new Paddle(150, 400)
// but we still add it to the entities list so it gets updated like every other Entity
this.entities.push(this.player)
//start watching inputs
this.inputsManager = new InputsManager()
//start watching mousemovement
this.mouseMoveHandler = new mouseMoveHandler()
//start game loop
this.loop()
}
update() {
// calculate time elapsed
const newTime = performance.now()
const deltaTime = newTime - this.lastUpdate
this.isMobile = window.matchMedia('(max-width: 1199px)');
// we now pass more data to the update method so that entities that need to can also read from our InputsManager
const frameData = {
deltaTime,
inputs: this.inputsManager,
mouse: this.mouseMoveHandler,
context: this.context
}
// update every entity
this.entities.forEach(entity => entity.update(frameData))
// other update logic (here, create new entities)
if (this.lastBallCreated + this.newBallInterval < newTime) {
// this is quick and dirty, you should put some more thought into `x` and `y` here
this.ball = new Ball(this.player.x, 360)
this.entities.push(this.ball)
this.lastBallCreated = newTime
}
// remember current time for next update
this.lastUpdate = newTime
}
cleanup() {
//console.log(this.entities[0])//Enemy
//console.log(this.entities[1])//Paddle
//console.log(this.entities[2])//Ball
//to prevent memory leak, don't forget to cleanup dead entities
this.entities.forEach(entity => {
if (entity.isDead(this.enemy)) {
const index = this.entities.indexOf(entity)
this.entities.splice(index, 1)
}
})
}
draw() {
//draw entities
this.entities.forEach(entity => entity.draw(this.context, this.frameNumber))
}
//main game loop
loop(endGame) {
this.myLoop = requestAnimationFrame(() => {
this.frameNumber += 1;
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height)
if (endGame) {
cancelAnimationFrame(this.myLoop);
this.endGame()
return;
}
this.update()
this.draw()
this.cleanup()
this.loop()
})
}
}
const canvas = document.querySelector('canvas')
const game = new Game(canvas)
game.start()
body {
background-color: rgb(214, 238, 149);
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
margin: 0;
padding: 0;
}
canvas {
background: url("https://picsum.photos/200");
width: 100%;
background-size: cover;
}
<canvas height="459"></canvas>
just for an additionnal information, i myself am a fan of ecs architecture and in order to achieve an explosion, or any other game mechanism cleanly you can use systems, there is an(experimental) firefox project that allows you to use entity component system architecture called ecsy
https://ecsy.io

mousePressed inside Constructor Function in JavaScript - p5.js

I am currently working on a Constructor Function in JavaScript - p5.js. My problem is that the mousePressed() function doesn't execute the code in it when I am clicking in on the right spot on the canvas. Basically what I want is that a faction of the content in the cup disappears when I click the mouse on the cup. and when I hit the button I want to fill the cup as originally filled.
Here is my file:
var bierglas;
var füllung;
var xPos = 200;
var yPos = 100;
function setup() {
createCanvas(650, 450);
bierglas = new Cup();
füllung = new Content();
}
function draw() {
background(51);
füllung.show();
füllung.button();
füllung.move();
bierglas.square();
bierglas.henkel();
}
//
// This draws my cup
//
function Cup() {
this.x = xPos;
this.y = yPos;
this.width = 150;
this.height = 300;
this.square = function() {
fill(255, 150);
rect(this.x, this.y, this.width, this.height);
};
this.henkel = function() {
arc(this.x + this.width, this.y + this.height / 2, 150, 120, 0, HALF_PI);
arc(this.x + this.width, this.y + this.height / 2, 150, 120, 0, 0);
};
}
//
// This is for the mousePressed function, the content of the cup and the button
//
function Content() {
this.x = xPos;
this.y = yPos;
this.width = 150;
this.height = 300;
this.buttonX = width - width / 4;
this.buttonY = height - height / 6;
this.buttonWidth = width / 8;
this.buttonHeight = height / 6;
this.show = function() {
fill(0, 200, 200);
rect(this.x, this.y * 2, this.width, this.height - this.y);
};
this.button = function() {
fill(255, 0, 0);
rect(this.buttonX, this.buttonY, this.buttonWidth, this.buttonHeight);
};
this.move = function() {
mousePressed();
};
}
function mousePressed() {
if (
mouseX < this.x + this.width &&
mouseX > this.x &&
mouseY < this.y + (this.height - this.y) &&
mouseY > this.y
) {
this.y * 2;
console.log("Auf Bierglas getippt");
}
if (
mouseX > this.buttonX &&
mouseX < this.buttonX + this.buttonWidth &&
mouseY > this.buttonY &&
mouseY < this.buttonY + this.buttonHeight
) {
this.y = yPos;
console.log("Auf Button getippt");
}
}
You should get into the habit of checking the developer console for errors. You'll see that your mousePressed() function is indeed firing, but it's hitting an error.
Let's look at the first few lines of your mousePressed() function:
function mousePressed() {
if (
mouseX < this.x + this.width &&
mouseX > this.x &&
mouseY < this.y + (this.height - this.y) &&
mouseY > this.y
) {
this.y * 2;
You have a few problems here. What do you think this is? I'm guessing that you thought it was an instance of Content, but this is not the case. Therefore, this does not contain variables like x, y, width, or height, which is why you're getting your errors. Also, notice that this.y * 2 doesn't actually do anything with the resulting value.
If I were you, I would break your problem down into smaller steps and take those steps on one at a time. Try to get a very simple sketch that uses mousePressed() working:
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
}
function mousePressed(){
console.log("mouse pressed");
}
Next, try to create a simple sketch that contains one object with a function that you call from the sketch-level mousePressed() function. Here's an example:
var myObject = new MyObject();
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
}
function mousePressed(){
myObject.mousePressed();
}
function MyObject(){
this.mousePressed = function(){
console.log("object mouse pressed");
};
}
Work your way forward in small steps like this, and remember to always check the developer console for errors. Good luck.

Ball collision not working

I'm working on a JQuery / JavaScript canvas brick break game, so I added the collision detection code if the ball hits the paddle or not, but when I ran the code, the collision detection code didn't work, I was wondering if anyone could help me? Here's the code.
JSFiddle: https://jsfiddle.net/18h7b9fd/
Main.js
$(document).ready(() => {
let fps = 60;
setInterval(init, 1000 / fps);
$(canvas).bind("mousemove", paddleControl);
})
// INIT
let init = () => {
draw(); //draw objects
animate(); //animate objects
collision(); //detect collision
}
// DRAW
let draw = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
paddle.draw();
ball.draw();
}
// ANIMATE
let animate = () => {
ball.animate();
}
// COLLISION
let collision = () => {
ball.collision();
}
// CONTROL
let paddleControl = (e) => {
// get mouse pos
let rect = canvas.getBoundingClientRect();
let root = document.documentElement;
let mouseX = e.pageX - rect.left - root.scrollLeft;
paddle.x = mouseX - paddle.w / 2;
}
Objects.js
class Paddle {
constructor(x, y, w, h, color) {
this.x = canvas.width / 2 - 100 / 2;
this.y = canvas.height - 60;
this.w = 100;
this.h = 10;
this.color = "#fff";
}
draw() {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
}
}
class Ball {
constructor(x, y, r, color, speedX, speedY) {
this.x = canvas.width / 2 - 10 / 2;
this.y = canvas.height / 2 - 10 / 2;
this.r = 10;
this.color = "#fff";
this.speedX = 6;
this.speedY = 6;
}
draw() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
ctx.fill();
}
animate() {
this.x += this.speedX;
this.y += this.speedY;
}
collision() {
// BALL TO PADDLE
let paddleTop = paddle.y - paddle.h;
let paddleBottom = canvas.height - paddleTop;
let paddleLeft = paddle.x;
let paddleRight = paddle.x + paddle.w;
if(this.y >= paddleTop && this.y <= paddleBottom &&
this.x >= paddleLeft && this.x <= paddleRight) {
this.speedY *= -1;
}
// BALL TO WALL
if(this.x >= canvas.width) this.speedX *= -1; //left
if(this.x <= 0) this.speedX *= -1; //right
if(this.y >= canvas.height) this.reset(); //bottom
if(this.y <= 0) this.speedY *= -1; //top
}
reset() {
this.x = canvas.width / 2 - this.r / 2;
this.y = canvas.height / 2 - this.r / 2;
this.animate();
}
}
let paddle = new Paddle(this.x, this.y, this.w, this.h, this.color);
let ball = new Ball(this.x, this.y, this.r, this.color, this.speedX, this.speedY);
console.log(paddle);
console.log(ball);
The problem is the way you are setting the paddle top and bottom. Use this instead:
let paddleTop = paddle.y;
let paddleBottom = paddleTop + paddle.h;
Currently the way you are setting the values makes it impossible for the condition to ever be true (as paddle bottom is always less than paddle top).
I have put together a fiddle here: https://jsfiddle.net/gegbqv5p/
You will also notice that I didn't use jQuery. For what you have so far, it really isn't necessary.

Categories