How to add a "Play Again" button - javascript

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.

Related

Randomly spawn an object when enemies die Phaser 3

I would like to randomly spawn a sprite when an enemy dies.
Example: There is a 1 in 5 chance that when an enemy dies, it drops an object (sprites that increase your HP).
Any idea how this can be done?
I did some research, but I didn't find much.
For randomness in a Phaser application, I would use the Phaser's Math helper function Between (here is the link to the documentation).
It creates a random number (whole number) from the first number to the last one (including the last number, perfect for dice).
So for 1 in 5, you just need to select one number from the interval like 5and compare it with a call to the Between function. And only if it matches, you drop/create the sprite.
Just like this:
if(Phaser.Math.Between(1, 5) === 5){
// .. drop "loot" / health-object
}
Here a small Demo:
(In this demo something could be dropped or not, depending on your luck. 20% is pretty low)
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536,
height: 183,
scene: {
create
},
banner: false
};
function create () {
this.add.text(10, 10, 'Click to red Boxes')
let graphics = this.make.graphics({x: 0, y: 0, add: false});
graphics.fillStyle(0xFF0000);
graphics.fillRect(0, 0, 20, 20);
graphics.generateTexture('enemy', 20, 20)
let enemiesGroup = this.add.group({
defaultKey: 'enemy',
maxSize: 10
});
let maxEnemiesToShow = 10
for(let idx = 0; idx < maxEnemiesToShow; idx++){
// here the function is used to spawn enemies randomly on screen
const x = Phaser.Math.Between(20, config.width - 20);
const y = Phaser.Math.Between(40, config.height /2 );
let enemy = enemiesGroup.get(x, y);
enemy.setInteractive()
.on('pointerdown', () => {
// 1 in 5
if(Phaser.Math.Between(1, 5) === 5){
// Drop object
this.add.rectangle(enemy.x, enemy.y, 10, 10, 0xFFFF00);
}
enemy.destroy();
})
}
}
new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Bonus (because I find this Phaser function especially useful):
If you want to select different loot/outcome in phaser you, could even let phaser select from a selected Array, with the function Phaser.Math.RNG.pick(...) (link to documentation)
Bonus Demo:
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536,
height: 183,
scene: {
create
},
banner: false
};
function create () {
this.add.text(10, 10, 'Click to red Boxes')
let graphics = this.make.graphics({x: 0, y: 0, add: false});
graphics.fillStyle(0xFF0000);
graphics.fillRect(0, 0, 20, 20);
graphics.generateTexture('enemy', 20, 20)
let enemiesGroup = this.add.group({
defaultKey: 'enemy',
maxSize: 10
});
let maxEnemiesToShow = 10
for(let idx = 0; idx < maxEnemiesToShow; idx++){
const x = Phaser.Math.Between(20, config.width - 20);
const y = Phaser.Math.Between(40, config.height /2 );
let enemy = enemiesGroup.get(x, y);
let loot = [0x00ff00, 0xffff00, 0x0000ff, 0x0, 0x0];
enemy
.setInteractive()
.on('pointerdown', () => {
// Select Colro from an Array of possibilities
let color = Phaser.Math.RND.pick(loot);
// only drop item if color is not black
if(color > 0){
this.add.rectangle(enemy.x, enemy.y, 10, 10, color);
}
enemy.destroy();
})
}
}
new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Phaser Random functions, have the added bonus that you can create your own RandomDataGenerator with a specific seed if you want, that the random numbers, that are created, are generated in the same sequence. Great for testing and so.
For a 1/5 chance, you can use JavaScript's Math.random.
Math.random() will return a float between 0 and 1.
To not hard code this, you can use a function like the following which will return true or false given an odds (in your case 1/5)
function rollRandom(odds) {
return Math.random() < odds;
}
console.log(rollRandom(1/5))

How to direct sound in p5.js - Binaural sound

I' m try trying to direct the sound,
how to direct the sound where the circle is? Taking into account that the circle will have to change position (I would like to replace the opposite sound with a binaural sound, and I don't know if there is an easier way to do it)
The code only works on mobile.
thanks in andavance.
https://editor.p5js.org/matteomuiafrate1999/sketches/P7XX_n-RS?fbclid=IwAR1D3dGhW_zvqlRuU-_kTAP-mzrg9urKv1bSXu9Qd72WA-Swu6lVHtCwoU8
It's not entirely clear what you are asking, but if you want to adjust how much of a particular sound comes from which channel (Right vs Left) based on spacial arrangement you can use a Panner3D.
let osc, pan;
let playing = false;
let direction = 0;
function setup() {
createCanvas(windowWidth, windowHeight);
angleMode(DEGREES);
osc = new p5.Oscillator('sine');
osc.freq(261.625565);
osc.disconnect();
pan = new p5.Panner3D();
pan.process(osc);
pan.setFalloff(Math.min(width, height) / 2, 1);
// By default the sound source will be omni-directional
pan.panner.coneInnerAngle = 60;
pan.panner.coneOuterAngle = 360;
// You can also adjust how quite the sound outside the
// outer code is with coneOuterGain which defaults to 0
}
function doubleClicked() {
if (!playing) {
playing = true;
osc.start();
osc.amp(0.3, 0.1);
} else {
playing = false;
osc.amp(0, 0.1);
}
}
function mouseMoved() {
if (pan) {
pan.set(mouseX - width / 2, 0, mouseY - height / 2, 0.1);
}
}
function mouseDragged() {
mouseMoved();
}
function draw() {
background(100);
circle(width / 2, height / 2, 20);
arc(mouseX, mouseY, 40, 40, direction - 20, direction + 20);
if (mouseIsPressed) {
direction = (direction + 3) % 360;
pan.orient(cos(direction), 0, sin(direction), 0.1);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/addons/p5.sound.min.js"></script>

Game froze in <canvas> js

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

Javascript - Properties in functions in objects reverting to newest created object

I have a problem. Whenever I try to create an object of the Candy function, all the attributes seem to be created fine. But, whenever I attempt to run the draw function, all of the properties of the newest created object are used, instead of the one I am using. It will always draw the second object that I created twice, but never the first one. I have no idea why. I have tried everything to try to fix this, so if something seems highly inefficient in this code, it was probably one of the many attempts of me trying to fix this. Do you guys know the problem?
Here is the code for the Candy function:
Candy = function(img, location, canvas) {
self = {}
self.image = new Image()
self.image.src = img
self.location = {x: location.x, y: location.y}
self.canvas = canvas
self.draw = function() {
self.canvas.drawImage(self.image, self.location.x, self.location.y, 132.4, 132.4)
}
self.move = function(FPS, seconds, location) {
frames = FPS * seconds
deltaX = (location.x - self.location.x) / frames
deltaY = (location.y - self.location.y) / frames
counter = 0
setInterval(function() {
self.location.x += deltaX
self.location.y += deltaY
counter++
self.draw()
if(counter >= frames)
clearInterval()
}, 1000 / FPS)
}
self.image.onload = function() {
Candy.list.push(self)
Candy.queue.splice(0, 1)
if(Candy.queue.length == 0)
draw()
else
Candy(Candy.queue[0].img, Candy.queue[0].location, Candy.queue[0].canvas)
}
}
Candy.list = []
Candy.queue = []
Here is where I call the Candy function:
gameStarted = true
Candy.queue.push({img: "client/img/candy.png", location: {x: width / 3 - 87.5, y: height / 10}, canvas: canvasContext})
Candy.queue.push({img: "client/img/candy2.png", location: {x: width / 3 - 87.5, y: 3 * (height / 10)}, canvas: canvasContext})
Candy(Candy.queue[0].img, Candy.queue[0].location, Candy.queue[0].canvas)
Finally, here is the draw function:
function draw() {
colorRect(0, 0, canvas.width, canvas.height, 'white');
colorText("Player 1", 0.02, 0.05, "black", "40px Comic Sans");
colorText("Player 2", 0.88, 0.05, "black", "40px Comic Sans");
if(!gameStarted) {
if(player1.ready)
colorText("Ready", 0.02, 0.09, "green", "20px Comic Sans");
else
colorText("Not Ready", 0.02, 0.09, "red", "20px Comic Sans");
if(player2.ready)
colorText("Ready", 0.88, 0.09, "green", "20px Comic Sans");
else
colorText("Not Ready", 0.88, 0.09, "red", "20px Comic Sans");
if(player1.ready && player2.ready)
colorText("Press a button to start the game!", 0.32, 0.5, "black", "40px Comic Sans")
}else{
alert(Candy.list[0].image.src)
alert(Candy.list[0].getImg())
for(var i = 0; i < Candy.list.length; i++) {
Candy.list[i].draw()
}
//TODO
}
}
Alright, I figured it out. In the function, I replaced self with this and it fixed the issue. No need for any replies.

Tweened Kinetic.js shape doesn't fire most of clicks

Purpose
I'm making a simple "shoot the word" game, where user needs to click on some moving rectangles with words to "shoot" them.
Problem
So i create some objects and move them using simple kinetic.js tweening.
Word creation
function createWord(value){
//here comes some word object construction
var wordGroup = new Kinetic.Group({
x: 0,
y: 0
});
var padding = 10;
wordGroup.label = new Kinetic.Text({
x: padding,
y: padding,
text: value,
fontFamily: 'Times New Roman',
fontSize: 30,
fill: 'white'
});
wordGroup.tag = new Kinetic.Rect({
x: 0,
y: 0,
width: wordGroup.label.width() + (padding << 1),
height: wordGroup.label.height() + (padding << 1),
fill: 'black',
shadowColor: 'black',
shadowBlur: 10,
shadowOffset: {x:10,y:20},
shadowOpacity: 0.5,
cornerRadius: 10
});
wordGroup.add(wordGroup.tag);
wordGroup.add(wordGroup.label);
wordGroup.shoot = function(){ //shooting mechanism (simple stop from moving and remove from scene)
wordGroup.tween.pause();
wordGroup.clean();
dropNextWord(); //drops fresh blood! (new word instead of shooted)
}
wordGroup.clean = function(){ //remove from scene and set it free to drop again
wordGroup.remove();
wordGroup.isActive = false;
}
wordGroup.move = function(callback){ //animates word
wordLayer.add(wordGroup);
moveToSide(wordGroup, callback); //calls moving function
}
wordGroup.on('click', function(e){
wordGroup.shoot();
});
return wordGroup;
}
Tweening part
//move word to opposite side
function moveToSide(word, callback){
var side = Math.random();
var d = 100;
spawnFromSide(word, side); //set random side word position
tweenPosition = {
x: word.x(),
y: word.y()
}
if(side < 0.25){ //left
tweenPosition.x = - d;
} else if(side > 0.25 && side < 0.5){ //right
tweenPosition.x = defaultStageWidth + d;
} else if(side > 0.5 && side < 0.75){ //up
tweenPosition.y = - d;
} else { //down
tweenPosition.y = defaultStageHeight + d;
}
word.tween = new Kinetic.Tween({
node: word,
duration: 4,
easing: Kinetic.Easings.Linear,
x: tweenPosition.x,
y: tweenPosition.y,
onFinish: function(){
word.clean();
callback();
}
});
word.tween.play();
}
But the problem is that click event doesn't fire on large amount of user clicks. As i think, this caused by delayed drawHit() calls inside tweening mechanism, that draws new object position before updating the hit area, so when we shoot object thinking that we hit its current position we miss because its hit area still have the same old position.
Live example
http://jsfiddle.net/hd6z21de/7/
Take a minute on shooting to see this effect in action
Solved this weird behavior by listening canvas touches and check if pointer collide some target word-rect by myself instead of using their own onclick events.
//i listen to canvas because of my app specific, you could simple listen your own layer or even document
$("canvas").bind('click', function(event){
var x = (event.pageX) / stage.scaleX(); //you don't need to divide by scale if your stage isn't scaled as mine does
var y = (event.pageY) / stage.scaleY();
var wordArray = wordGroup.getChildren();
for(var i = 0; i < wordArray.length; i++){ //go through all words and check if we shoot someone (is mouse position included in some word rect)
if(x > wordArray[i].x() &&
y > wordArray[i].y() &&
x < (wordArray[i].x() + wordArray[i].width()) &&
y < (wordArray[i].y() + wordArray[i].height())){
wordArray[i].shoot(); //shoot that word
break;
}
}
}

Categories