Link to code for reference: https://editor.p5js.org/tpant963/sketches/wQy1zfKBW
Hello! So I'm using p5.js to make a Java OOP concepts review game where a question comes up on the screen with bubbles floating around that have word associations on them. If a bubble turns green when you click on it, it means that you've clicked the correct answer/association. If it turns red, then you've clicked the incorrect one.
I wanted to make my game so that if all the correct answers are clicked (meaning all the possible bubbles that can turn green have been clicked), then a "Play Again" button shows up and if you click on it, then the game restarts and you can play again.
How can I do this? This is a sample of the try that got me the closest to my desired final outcome aesthetically, but it's not able to return back to the main menu and actually restart the game when I click "Play again" Also, I can still click the "incorrect" bubbles during the game and have the same outcome, which I don't want.
let menu = 0;
function draw() {
if (menu == 1) {
background(bubblepopperG)
fill(0)
textSize(25)
text(currentQuestion.question, 25, 300);
//Allow the bubbles to move and be displayed on the screen.
bubbles.forEach(bubble => {
bubble.move();
bubble.display();
})
if (answerCount > currentQuestion.correct.length){
background(bubblepopperG)
textSize(30)
text('YOU GOT ALL THE RIGHT BUBBLES!', 25, height / 2)
//Add button to return to main menu.
fill(255, 0, 255);
rect(180, 400, 250, 75);
stroke(100);
strokeWeight(3);
textSize(26);
text('PLAY AGAIN', 230, 450);
}
}
Then I tried the same thing but tried assigning a mousePressed() function to the "Play Again" button at the end of the game. This was the code:
if (answerCount > currentQuestion.correct.length){
menu = 4;
background(bubblepopperG)
textSize(30)
text('YOU GOT ALL THE RIGHT BUBBLES!', 25, height / 2)
//Add button to return to main menu.
fill(255, 0, 255);
rect(180, 400, 250, 75);
stroke(100);
strokeWeight(3);
textSize(26);
text('PLAY AGAIN', 230, 450);
}
}
function mousePressed(){
if (menu == 4) {
if (mouseX < 430 && mouseX > 180) {
if (mouseY < 475 && mouseY > 400) {
menu = 0
}
}
}
}
However, this one doesn't even allow the post-game screen to come up at all. It just takes me straight back to the game menu I made, but I'm unable to click on anything when I get there.
Thanks
So close!
Short answer: you already have a condition to check if a button has been pressed within a bounding box and a condition to check if the play again menu should be displayed (e.g. if(answerCount > currentQuestion.correct.length)).
Remember what state / menu you're in and use the right condition.
Mimicking you're code, you'd have something like:
if(menu == 1){
if (mouseX < 430 && mouseX > 180) {
if (mouseY < 475 && mouseY > 400) {
// reset answer count
answerCount = 0;
// return to start menu
menu = 0;
}
}
}
Update:
Based on your comments you need to count the number of correct bubbles (one way might be filtering the CorrectBubble instances and counting them (e.g. bubbles.filter(bubble => bubble instanceof CorrectBubble)) and that total number of correct bubbles with the number of correct bubbles that have been clicked (again filtering can help: e.g. correctBubbles.filter(bubble => bubble.col === bubble.clickedColor).length))
clicked() could update an internal boolean property which would make the code more readable (e.g. adding this.isClicked = true; in clicked() would make the cliecked bubbles filter read as correctBubbles.filter(bubble => bubble.isClicked).
There's also a minor collision detection bug, you probably meant something like if (d < this.r * 0.5) (and perhaps renaming this.r to this.diameter to reflect how it's used).
Even better, a reset() method could be added to the Bubble class to reset both the boolean flag and the colour:
reset(){
this.isClicked = false;
this.col = '#000000';
}
Bubbles are nicely encapsulated into classes. You could do the same for buttons which minimise repetition.
Here's a modified version of your main sketch with a basic Button class encapsulating the display and clicking within a bounding rectangle functionality:
let menu = 0 //Variable to show the game main menu.
let bubblepopper; //A variable for the image
let bubblepopperM; //A variable for the menu background image
let bubblepopperG; //A variable for the game background image
const bubbles = []; //Array to store the bubbles.
let currentQuestion = null;
const questions = [
{
question: 'What are the 4 types of access modifiers?',
correct: ['public', 'private', 'protected', 'default'],
incorrect: ['method', 'class', 'protection', 'published', 'nested', 'abstract'],
},
{
question: 'Words that go with encapsulation:',
correct: ['class', 'method', 'variable','access modifiers'],
incorrect: ['initial', 'encapsulate', 'complex', 'automatic', 'primitive', 'interface'],
},
{
question: 'Things you can put in a UML OBJECT diagram:',
correct: ['classes', 'variables', 'components', 'methods'],
incorrect: ['packages', 'Java Project', 'client code', 'no booleans', 'no objects', 'concatenation'],
},
{
question: 'Abstract classes...',
correct: ['declare abstract', 'extend subclass', 'no instantiation', 'static & non-static'],
incorrect: ['interface', 'only static', 'slow', 'only public access', 'default methods', 'multiple inheritance'],
},
{
question: 'OOP important words:',
correct: ['encapsulation', 'inheritance', 'polymorphism', 'abstraction'],
incorrect: ['capsuling', 'aggravation', 'adhesion', 'dissociation', 'array', 'submethod'],
},
{
question: 'Which of the following words go together?',
correct: ['private class', 'getter', 'setter', 'protect data'],
incorrect: ['public class', 'char', 'float', 'array', 'settings', 'extends class'],
},
];
class Button{
constructor(x, y, width, height, label, textSize, fillColor){
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.label = label;
this.textSize = textSize;
this.fillColor = fillColor;
}
draw(){
push();
textAlign(CENTER);
fill(this.fillColor);
rect(this.x, this.y, this.width, this.height);
stroke(100);
strokeWeight(3);
textSize(this.textSize);
fill(255);
text(this.label, this.x + (this.width * 0.5), this.y + (this.height * 0.75));
pop();
}
isPressed(){
return ((mouseX >= this.x && mouseX <= this.x + this.width) &&
(mouseY >= this.y && mouseY <= this.y + this.height));
}
}
let playButton;
let exitButton;
let instructionsButton;
function preload() {
bubblepopper = loadImage('Bubble Popper Logo.png');
bubblepopperM = loadImage('Bubble Popper Menu Background.jpg');
bubblepopperG = loadImage('Bubble Popper Game Background.jpg');
}
function setup() {
currentQuestion = questions[Math.floor(Math.random() * questions.length)];
createCanvas(600, 600);
for(let i = 0; i < currentQuestion.correct.length; i++) {
bubbles.push(new CorrectBubble(random(-3, 3), random(-3, 3), currentQuestion.correct[i], random(width), random(height), random(80,100)));
} //The new bubbles will be added to the bubbles array.
for(let i = 0; i < currentQuestion.incorrect.length; i++) {
bubbles.push(new IncorrectBubble(random(-3, 3), random(-3, 3), currentQuestion.incorrect[i], random(width), random(height), random(80,100)));
} //The new bubbles will be added to the bubbles array.
playButton = new Button(50, 100, 200, 75, 'PLAY', 50, color(64,224,208));
exitButton = new Button(50, 400, 200, 75, 'EXIT', 50, color(255, 0, 255));
instructionsButton = new Button(50, 250, 200, 75, 'INSTRUCTIONS', 26, color(64,224,208));
playAgainButton = new Button(180, 400, 250, 75, 'PLAY AGAIN', 26, color(255, 0, 255));
returnToMenuButton = new Button(180, 400, 250, 75, 'RETURN TO MENU', 26, color(255, 0, 255));
}
function mousePressed() {
//For each bubble in the array, click it and allow it to change to it's correct colour.
bubbles.forEach(bubble => bubble.clicked(mouseX, mouseY));
}
function draw() {
//Set up the main menu and the buttons in it.
background(bubblepopperM);
playButton.draw();
exitButton.draw();
instructionsButton.draw();
image(bubblepopper, 275, 150, 300, 300); //Add the Bubble Popper logo.
if (menu == 1) {
background(bubblepopperG)
fill(0)
textSize(25)
text(currentQuestion.question, 25, 300);
//Allow the bubbles to move and be displayed on the screen.
bubbles.forEach(bubble => {
bubble.move();
bubble.display();
})
const correctBubbles = getCorrectBubbles();
if (countClickedBubbles(correctBubbles) === correctBubbles.length){
background(bubblepopperG)
textSize(30)
text('YOU GOT ALL THE RIGHT BUBBLES!', 25, height / 2)
//Add button to return to main menu.
playAgainButton.draw();
}
}
//When clicked this will show the user instructions on how to play the game.
if (menu == 2) {
background(bubblepopperM)
fill(255);
stroke(100);
strokeWeight(3);
textSize(20)
text('1. A question will be displayed to do with object-oriented', 50, 150)
text('programming (OOP) concepts.', 80, 175)
text('2. Click on the bubbles with the correct word associations', 50, 225)
text('to the question. Correct answers will turn green,', 80, 250)
text(' incorrect answers will turn red when clicked on.', 80, 275)
text('3. The round is over when you select all the correct bubbles', 50, 325)
returnToMenuButton.draw();
}
//Will exit out of the program.
if (menu == 3) {
background(bubblepopperM);
fill(255);
textSize(50);
text('THANKS FOR PLAYING!', 25, height / 2);
playAgainButton.draw();
}
}
function getCorrectBubbles(){
return bubbles.filter(bubble => bubble instanceof CorrectBubble);
}
function countClickedBubbles(bubbles){
return bubbles.filter(bubble => bubble.col === bubble.clickedColor).length;
}
//Determine the mouse coordinates so that the user can click on the buttons.
function mouseClicked() {
if (menu == 0) {
if(playButton.isPressed()){
menu = 1;
}
if(instructionsButton.isPressed()){
menu = 2;
}
if(exitButton.isPressed()){
console.log('exit');
menu = 3;
}
}
if(menu == 1){
if (playAgainButton.isPressed()) {
// return to start menu
menu = 0;
}
}
//Allow the user to go back to the main menu if they click instructions.
if (menu == 2) {
if(returnToMenuButton.isPressed()){
menu = 0;
}
}
//Allow the user to go back to the main menu if they click exit.
if (menu == 3) {
if(playAgainButton.isPressed()){
menu = 0;
}
}
}
Additionally, the menu based functionality could also be encapsulated into classes. Here is variant of the above code with a basic state machine illustrating polymorphism:
let bubblepopper; //A variable for the image
let bubblepopperM; //A variable for the menu background image
let bubblepopperG; //A variable for the game background image
const bubbles = []; //Array to store the bubbles.
let currentQuestion = null;
const questions = [
{
question: 'What are the 4 types of access modifiers?',
correct: ['public', 'private', 'protected', 'default'],
incorrect: ['method', 'class', 'protection', 'published', 'nested', 'abstract'],
},
{
question: 'Words that go with encapsulation:',
correct: ['class', 'method', 'variable','access modifiers'],
incorrect: ['initial', 'encapsulate', 'complex', 'automatic', 'primitive', 'interface'],
},
{
question: 'Things you can put in a UML OBJECT diagram:',
correct: ['classes', 'variables', 'components', 'methods'],
incorrect: ['packages', 'Java Project', 'client code', 'no booleans', 'no objects', 'concatenation'],
},
{
question: 'Abstract classes...',
correct: ['declare abstract', 'extend subclass', 'no instantiation', 'static & non-static'],
incorrect: ['interface', 'only static', 'slow', 'only public access', 'default methods', 'multiple inheritance'],
},
{
question: 'OOP important words:',
correct: ['encapsulation', 'inheritance', 'polymorphism', 'abstraction'],
incorrect: ['capsuling', 'aggravation', 'adhesion', 'dissociation', 'array', 'submethod'],
},
{
question: 'Which of the following words go together?',
correct: ['private class', 'getter', 'setter', 'protect data'],
incorrect: ['public class', 'char', 'float', 'array', 'settings', 'extends class'],
},
];
class Button{
constructor(x, y, width, height, label, textSize, fillColor){
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.label = label;
this.textSize = textSize;
this.fillColor = fillColor;
}
draw(){
push();
textAlign(CENTER);
fill(this.fillColor);
rect(this.x, this.y, this.width, this.height);
stroke(100);
strokeWeight(3);
textSize(this.textSize);
fill(255);
text(this.label, this.x + (this.width * 0.5), this.y + (this.height * 0.75));
pop();
}
isPressed(){
return ((mouseX >= this.x && mouseX <= this.x + this.width) &&
(mouseY >= this.y && mouseY <= this.y + this.height));
}
}
class State{
constructor(){
}
mouseClicked(){}
draw(){}
}
class MainMenuState extends State{
draw(){
background(bubblepopperM);
playButton.draw();
exitButton.draw();
instructionsButton.draw();
image(bubblepopper, 275, 150, 300, 300); //Add the Bubble Popper logo.
}
mouseClicked(){
if(playButton.isPressed()){
currentState = playState;
}
if(instructionsButton.isPressed()){
currentState = instructionsState;
}
if(exitButton.isPressed()){
currentState = exitState;
}
}
}
class PlayState extends State{
draw(){
background(bubblepopperG)
fill(0)
textSize(25)
text(currentQuestion.question, 25, 300);
//Allow the bubbles to move and be displayed on the screen.
bubbles.forEach(bubble => {
bubble.move();
bubble.display();
})
}
mouseClicked(){
//For each bubble in the array, click it and allow it to change to it's correct colour.
bubbles.forEach(bubble => bubble.clicked(mouseX, mouseY));
// check if all correct bubbles have been clicked
const correctBubbles = getCorrectBubbles();
if (countClickedBubbles(correctBubbles) === correctBubbles.length){
currentState = winState;
}
}
}
class WinState extends State{
draw(){
background(bubblepopperG);
fill(255);
textSize(30);
text('YOU GOT ALL THE RIGHT BUBBLES!', 25, height / 2);
//Add button to return to main menu.
playAgainButton.draw();
}
mouseClicked(){
if(playAgainButton.isPressed()){
// reset clicked answers first
bubbles.forEach(bubble => bubble.reset());
// then change state
currentState = menuState;
}
}
}
class InstructionsState extends State{
draw(){
background(bubblepopperM)
fill(255);
stroke(100);
strokeWeight(3);
textSize(20);
text('1. A question will be displayed to do with object-oriented', 50, 150);
text('programming (OOP) concepts.', 80, 175);
text('2. Click on the bubbles with the correct word associations', 50, 225);
text('to the question. Correct answers will turn green,', 80, 250);
text(' incorrect answers will turn red when clicked on.', 80, 275);
text('3. The round is over when you select all the correct bubbles', 50, 325);
returnToMenuButton.draw();
}
mouseClicked(){
if(returnToMenuButton.isPressed()){
currentState = menuState;
}
}
}
class ExitState extends State{
draw(){
background(bubblepopperM);
fill(255);
textSize(50);
text('THANKS FOR PLAYING!', 25, height / 2);
playAgainButton.draw();
}
mouseClicked(){
if(returnToMenuButton.isPressed()){
currentState = menuState;
}
}
}
let playButton;
let exitButton;
let instructionsButton;
let menuState = new MainMenuState();
let playState = new PlayState();
let winState = new WinState();
let instructionsState = new InstructionsState();
let exitState = new ExitState();
let currentState = menuState;
function preload() {
bubblepopper = loadImage('Bubble Popper Logo.png');
bubblepopperM = loadImage('Bubble Popper Menu Background.jpg');
bubblepopperG = loadImage('Bubble Popper Game Background.jpg');
}
function setup() {
currentQuestion = questions[Math.floor(Math.random() * questions.length)];
createCanvas(600, 600);
for(let i = 0; i < currentQuestion.correct.length; i++) {
bubbles.push(new CorrectBubble(random(-3, 3), random(-3, 3), currentQuestion.correct[i], random(width), random(height), random(80,100)));
} //The new bubbles will be added to the bubbles array.
for(let i = 0; i < currentQuestion.incorrect.length; i++) {
bubbles.push(new IncorrectBubble(random(-3, 3), random(-3, 3), currentQuestion.incorrect[i], random(width), random(height), random(80,100)));
} //The new bubbles will be added to the bubbles array.
playButton = new Button(50, 100, 200, 75, 'PLAY', 50, color(64,224,208));
exitButton = new Button(50, 400, 200, 75, 'EXIT', 50, color(255, 0, 255));
instructionsButton = new Button(50, 250, 200, 75, 'INSTRUCTIONS', 26, color(64,224,208));
playAgainButton = new Button(180, 400, 250, 75, 'PLAY AGAIN', 26, color(255, 0, 255));
returnToMenuButton = new Button(180, 400, 250, 75, 'RETURN TO MENU', 26, color(255, 0, 255));
}
function draw() {
currentState.draw();
}
function mouseClicked() {
currentState.mouseClicked();
}
function getCorrectBubbles(){
return bubbles.filter(bubble => bubble instanceof CorrectBubble);
}
function countClickedBubbles(bubbles){
return bubbles.filter(bubble => bubble.isClicked).length;
}
Of course the Button class could live in button.js and the state clasess in states.js for example: this would greatly reduce the code in the main sketch.
I`m writing a pong game on js and html using canvas. Besides main body of the game I have the starting menu with "play button"(all the buttons are just images i counted their coordinates and just use ), then next menu where user chooses the background and then eventually game itself and then after reaching 3 points by user or computer the game ends and there's a win or fail window appering where you can press the "back to menu" button and go back to the very first menu with "play button" and start everything again. Program does everything okay when you first launch it but after you for example lose and go back to main menu, choose the background, game starts aaaaaaand when ball reaches 2 points the game get frozen and sometimes after minute or so somehow loads the menu with backgrounds. I really have no idea what's wrong and why it gets frozen and strangely on the second time you play. Please help me ;((
PS. Im just a beginner idk how to insert the images here in stackoverflow
const canvas = document.getElementById("pongping");
const context = canvas.getContext('2d');
/////////////1)function to draw the rect
function drawPryamokutnik(x,y,w,h,color) {
context.fillStyle = color;
context.fillRect(x,y,w,h);
}
//////////////////////2)рисуем круг ///////////////////////////////////////////////////////////////////////////////
function drawKolo(x,y,radius,color) {
context.fillStyle = color;
context.beginPath();
context.arc(x, y, radius, 0, Math.PI * 2, false);
context.closePath();
context.fill();
}
///////////////////////3)drawText/////////////////////////////////////////////
function drawText(text,x,y,color){
context.fillStyle = color;
context.font = "80px Arial";
context.fillText(text,x,y);
}
///////////////////////4)создаем пластинку пльзователя//////////////////////////////////////////////////////////////
const rectleft = {
x:20,
y:canvas.height/2-100,
width: 25,
height: 200,
color: "WHITE",
score:0
};
///////////////////////5)создаем пластинку компьютера//////////////////////////////////////////////////////////////
const rectright = {
x:canvas.width-45,
y:canvas.height/2-100,
width: 25,
height: 200,
color: "WHITE",
score:0
};
///////////////////////6)создаем шайбу/////////////////////////////////////////////////////////////////////////////
const ball = {
x: canvas.width/2,
y: canvas.height/2,
speed: 5,
radius: 20,
velocityX: 5,
velocityY: 5,
colour:"WHITE"
};
//////////////////////8)создаем разделительную сетку///////////////////////////////////////////////////////////////
const net = {
x:canvas.width/2-1,
y:0,
width: 2,
height:10,
color:"WHITE"
};
//////////////////////9)создаем функцию сетки/////////////////////////////////////////////////////////////////////
function drawNet(){
for(let i=0; i<=canvas.height;i+=15)
{
drawPryamokutnik(net.x,net.y+i,net.width,net.height,net.color)
}
}
//drawPryamokutnik(0,0,canvas.width, canvas.height, "BLACK");
//drawText("so you wanna die young?",200,300,"WHITE");
canvas.addEventListener("mousemove",moovePaddle);
function moovePaddle(etv){
let rect=canvas.getBoundingClientRect();
rectleft.y=etv.clientY-rect.top-rectleft.height/2;
}
function update(){
ball.x += ball.velocityX;
ball.y += ball.velocityY;
let computerLevel= 0.1;
rectright.y+=(ball.y-(rectright.y+rectright.height/2))*computerLevel;
if (ball.y + ball.radius > canvas.height || ball.y - ball.radius < 0) {
ball.velocityY = -1 * ball.velocityY;
}
let player = (ball.x>canvas.width/2)? rectright:rectleft;
if(colission(ball,player)){
let collidePoint = ball.y - (player.y + player.height / 2);
collidePoint = collidePoint /(player.height / 2);
let angleRead = Math.PI/4 * collidePoint;
let direction = (ball.x < canvas.width / 2) ? 1 : -1;
ball.velocityX = direction * ball.speed * Math.cos(angleRead);
ball.velocityY = direction * ball.speed * Math.sin(angleRead);
ball.speed += 1;
}
if (ball.x - ball.radius < 0 ){
rectright.score++;
ResetBall();
}
else if(ball.x+ball.radius>canvas.width)
{rectleft.score++;
ResetBall();}
}
function ResetBall() {
ball.x=canvas.width/2;
ball.y=canvas.height/2;
ball.speed=5;
ball.velocityX=-ball.velocityX;
}
function colission(b,p){
b.top=b.y-b.radius;
b.bottom=b.y+b.radius;
b.left=b.x-b.radius;
b.right=b.x+b.radius;
p.top=p.y;
p.bottom=p.y+p.height;
p.left=p.x;
p.right=p.x+p.width;
return b.right>p.left && b.bottom>p.top && b.left<p.right && b.top < p.bottom;
}
function render(){
const back = new Image();
if(backchek===1)
{back.src = "back1.png";}
if(backchek===2)
{back.src = "back2.png";}
if(backchek===3)
{back.src = "back3.png";}
context.drawImage(back, 0, 0);
//drawPryamokutnik(0,0,canvas.width,canvas.height,"BLACK");
drawNet();
drawText(rectleft.score,canvas.width/4,canvas.height/5,"WHITE");
drawText(rectright.score,3*canvas.width/4,canvas.height/5,"WHITE");
drawPryamokutnik(rectleft.x,rectleft.y,rectleft.width,rectleft.height,rectleft.color);
drawPryamokutnik(rectright.x,rectright.y,rectright.width,rectright.height,rectright.color);
drawKolo(ball.x,ball.y,ball.radius,ball.colour);
}
let slide;
function game() {
slide=1;
menu();
if(startcheck===true)
{backchoose();
slide=2;
if(backchek===1||backchek===2||backchek===3)
{render();
update();
if(rectright.score>2)
{
losing();
slide=3;
// startcheck=false;
if(backagain===true)
{slide=1;
game();
}}
if(rectleft.score>2)
{//startcheck=false;
winning();
slide=3;
if(backagain===true)
{slide=1;
game();
}}
}
}
}
let framePerSecond=65;
setInterval(game,1000/framePerSecond);
function menu(){
const menuu = new Image();
menuu.src = "menu.png";
context.drawImage(menuu, 0, 0);
}
function losing(){
const lose = new Image();
lose.src = "lose.png";
context.drawImage(lose, 0, 0);
}
function winning(){
const win = new Image();
win.src = "win.png";
context.drawImage(win, 0, 0);
}
function backchoose(){
const backk = new Image();
backk.src = "back choose .png";
context.drawImage(backk, 0, 0);
}
let backchek;
let backagain=false;
let startcheck=false;
canvas.addEventListener("mousedown", clicked, false);
function clicked(e){
e.preventDefault();
let rectt=canvas.getBoundingClientRect();
const x = e.clientX;
const y = e.clientY - rectt.top;
let xprtr;
let yprtr;
let hipot;
let yby;
let xby;
let yey;
let xex;
if(x===496 && y===471&& slide===1)
{startcheck=true;
//backagain=false;
}
if(x>496){xprtr=x-496;}
if(x<496){xprtr=496-x;}
if(y>471){yprtr=y-471;}
if(y<471){yprtr=471-y;}
hipot=yprtr*yprtr+xprtr*xprtr;
if(hipot<=10609&&slide===1)
{startcheck=true;}
if(slide===2&&(y>217&&y<430&&x>75&&x<404)){backchek=1;}
if(slide===2&&(y>217&&y<430&&x>585&&x<924)){backchek=2;}
if(slide===2&&(y>508&&y<773&&x>344&&x<675)){backchek=3;}
if(slide===3&&y>519&&(y<702&&x>224&&x<768))
{backagain=true;
backchek=0;
startcheck=false;
rectright.score=0;
rectleft.score=0;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Pong20</title>
</head>
<body>
<canvas id="pongping" width="1000" height="800"></canvas>
<script src="pongping.js"></script>
</body>
Does anything pop up in the console? That might give some hint as to what is going wrong.
I noticed that in your game() function you are recursively calling game(). This may cause a recursive overflow.
At the same time, you have setInterval calling game again and again. I recommend you delete the lines:
let framePerSecond=65;
setInterval(game,1000/framePerSecond);
and replace them with
window.requestAnimationFrame(game)
This will still initialize your game properly, but it will also not block the thread, so users can still interact with the page as expected. It may not keep the proper frame rate that you're expecting, but that function passes a timestamp to the function it calls, so you could keep track of the time since last call and update movements based on that. Check out this example.
then inside your game function, replace every call to
game()
with
window.requestAnimationFrame(game)
This will hopefully stop your game from crashing.
https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
I want to draw image sprite using canvas.
The code not working. How to improve my code.
I have some Error.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var Game = {
draw_image: function(img, sourceX, sourceY, sourceW, sourceH, destX, destY, destW, destH){
var img = new Image(); // Create new img element
img.src = 'images/background.png'; // Set source path
img.onload = function(){
canvas.width = 1000;
canvas.height = 500;
ctx.drawImage(img, sourceX, sourceY, sourceW, sourceH, destX, destY, destW, destH);
};
}
var BACKGROUND = {
image_top: { x: 5, y: 5, w: 1280, h: 480 , dx:0 ,dy:0 ,dw:500 ,dh:500 },
image_body: { x: 5, y: 495, w: 1280, h: 480 , dx:0 ,dy:150 ,dw:500 ,dh:350},
image_bottom: { x: 5, y: 985, w: 1280, h: 480 , dx:0 ,dy:300 ,dw:500 ,dh:200 }
};
for(var n = 0 ; n < BACKGROUND.length ; n++) {
draw_image(nameImage, BACKGROUND[n].x,BACKGROUND[n].y, BACKGROUND[n].w, BACKGROUND[n].h, BACKGROUND[n].dx, BACKGROUND[n].dy, BACKGROUND[n].dw, BACKGROUND[n].dh );
}
};
To create a sprite animation it's important to know how it works.
You need your spritesheet make with pixel precision ( 1 pixel can mess up your animation ).
Like here, the character is always in the same size area, make it simple when you make your sprites.
With this you can make an object for each sprite you have like :
function Sprite(_position, _numberFrame, _framesize, _image, _duration){
this.position = _position; //Array like { x : 0, y : 0 }
this.rendersize = _rendersize; //Array like { width : 50, height : 80 }
this.framesize = _framesize; //Array like { width : 50, height : 80 }
this.image = _image; //Image object
this.chrono = new Chrono(_duration); //Explanation below
}
For more animation precision you can add a chrono who will manage the time of your animation :
function Chrono(_duration){
this.currentTime = 0;
this.lastTime = 0;
this.timeElapse = 0;
this.duration = _duration;
}
Chrono.prototype.countTime = function(){
this.currentTime = Date.now();
if(this.lastTime != 0)
this.timeElapse += this.currentTime - this.lastTime;
this.lastTime = Date.now();
if(this.timeElpase >= this.duration && this.lastTime != 0){
this.timeElapse = 0;
return TRUE;
} else {
return FALSE;
}
}
Then the function to animate your sprite may like :
Sprite.prototype.render = function(){
if(this.position.x <= this.image.width && this.chrono.countTime()){
this.position.x += this.framesize.x;
} else {
this.position.x = 0;
}
ctx.drawImage(this.image,
this.position.x, this.position.y,
this.framesize.width, this.framesize.height,
this.rendersize.width, this.rendersize.height
);
}
I hope I was clear and helpful,
Cheers
PS: Comments for question or optimisation ideas
You have to many problems with this code to make this sprite animation works. I wouldn't go to point any of the problems with your code, but I highly recommend to read a little bit about functions and variable scope before try to write this kind of code.
Another simple (and best for newbies) solution can be to use a canvas framework as EaselJS, with this you can do something like this to animate an sprite:
var data = {
images: ["images/background.png"],
frames: {width:50, height:50},
animations: {run:[0,4], jump:[5,8,"run"]}
};
var animation = new createjs.BitmapAnimation(data);
animation.gotoAndPlay("run");