-1

I am trying to make a simple football penalty game using HTML5/JS Canvas. The aim is to make a game where you control the goal keeper and you have three attempts to save the ball.

I have most of the functionality done, I have a score system and collision detection.

Currently I have a delay on the first attempt. I am however finding difficulty in adding a delay before the ball is shot into the goal in the second and third attempt.

I am using the method requestAnimationFrame() to paint my shapes on my canvas. If there is still attempts available, the ball is positioned to its original location but then there is no delay and fires the ball immediately.

Any advice ? Thanks!

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Football</title>
    <style>
        * { padding: 0; margin: 0; }
        canvas { background: #a5bd7b; display: block; margin: 0 auto; }
    </style>
</head>
<body>

<canvas id="myCanvas" width="300" height="250"></canvas>

<script>

var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext("2d");

//Sets the original position of the ball
var x = canvas.width/2;
var y = 50;

// Defines  values that will be added to the position of x and y values
// List of possible values for the x position
var x_options = [3.5, 3, 2.5, 2, 1.5, 1, 0.5, 0, -0.5, -1, -1.5, -2, -2.5, -3, -3.5];

// Gets a random value from the x_options array
var dx = x_options[Math.floor(Math.random() * x_options.length)];
var dy = 5;

var ballRadius = 10;

// Defines the height and width of the goal
var goal_height = 40;
var goal_width = 200


// Defines the height, width and position of goalie
var goalieHeight = 20;
var goalieWidth = 40;
var goalieX = (canvas.width-goalieWidth)/2;
var goalieY = (canvas.height - goal_height) - 30;

// Set to false by default
var rightPressed = false;
var leftPressed = false;

var goalkeeper_blocked = 0;
var goalkeeper_missed = 0;
var attempts_left = 3;

var attempt1 = true;
var attempt2 = false;
var attempt3 = false;



var footBall = {

    shapes : {
        ball: function (){
            ctx.beginPath();
            ctx.arc(x, y, ballRadius, 0, Math.PI*2, false);
            ctx.fillStyle = "red";
            ctx.fill();
            ctx.closePath();

        },

        goal : function (){
            ctx.beginPath();
            ctx.rect((canvas.width - goal_width) / 2 , canvas.height - goal_height, goal_width, goal_height);
            ctx.strokeStyle = "#000000";
            ctx.stroke();
            ctx.closePath();
        },

        goalie : function(){
            ctx.beginPath();
            ctx.rect(goalieX, goalieY, goalieWidth, goalieHeight);
            ctx.fillStyle = "#666666";
            ctx.fill();
            ctx.closePath();
        },

        score : function(){
            ctx.font = "16px Arial";
            ctx.fillStyle = "#ffffff";
            ctx.fillText("Score: "+goalkeeper_blocked, 8, 20);
        },

        missed : function(){
            ctx.font = "16px Arial";
            ctx.fillStyle = "#ffffff";
            ctx.fillText("Missed: "+goalkeeper_missed, 8, 40);
        },

        attempts : function(){
            ctx.font = "16px Arial";
            ctx.fillStyle = "#ffffff";
            ctx.fillText("Attempts left: "+attempts_left, canvas.width-110, 20);
        }


    },

    controls : {
        keyDownHandler : function (e){
            if(e.keyCode == 39) {
                rightPressed = true;
            }
            else if(e.keyCode == 37) {
                leftPressed = true;
            }

        },

        keyUpHandler : function(e){
            if(e.keyCode == 39) {
                rightPressed = false;
            }
            else if(e.keyCode == 37) {
                leftPressed = false;
            }

        }


    },

    calculateScore : function(){
        if(goalkeeper_missed > goalkeeper_blocked){
            alert("GAME OVER! YOU HAVE LOST!");
            document.location.reload();

        } else {
            alert("GAME OVER! YOU HAVE WON!");
            document.location.reload();
        }
    },

    animateBall : function (){
        // Sets a delay of 3 second before it shoots
        setTimeout(function(){ 
            x += dx;
            y += dy;

        }, 3000);

    },

    resetShapePositions : function(){
        //Sets the original position of the ball
        x = canvas.width/2;
        y = 50;

        // Sets a new shooting path
        dx = x_options[Math.floor(Math.random() * x_options.length)];
        dy = 5;

        // Resets the goalie to the middle
        goalieX = (canvas.width-goalieWidth)/2;

    },

    draw : function(){

        // This ensures that the ball doesn't leave a trail
        // Clears the canvas of this shape each frame
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // Draws shapes on the canvas
        footBall.shapes.ball();
        footBall.shapes.goal();
        footBall.shapes.goalie();
        footBall.shapes.score();
        footBall.shapes.missed();
        footBall.shapes.attempts();

        // adds values to the balls  x and y position every frame
        footBall.animateBall();


        // Ball hits the goal 
        if(y + dy > canvas.height - goal_height) {

            attempts_left--;
            goalkeeper_missed++;
            if (!attempts_left){
                footBall.calculateScore();
            } 
            else {
                footBall.resetShapePositions();

            }

        } // Ball saved by goalie
        else if (x  > goalieX && x  < goalieX + goalieWidth && y + dy > goalieY - ballRadius){

            attempts_left--;
            goalkeeper_blocked++;

            if (!attempts_left){
                footBall.calculateScore();
            } 
            else {
                footBall.resetShapePositions();

            }


        } 


        //   makes paddle move left and right and only within the canvas
        if(rightPressed && goalieX < canvas.width-goalieWidth) {
            goalieX += 7;
        }
        else if(leftPressed && goalieX > 0) {
          goalieX -= 7;
        }
        requestAnimationFrame(footBall.draw);


    }


}

footBall.draw();


// Defines what functions are fired when keydown or keyup event triggers
document.addEventListener("keydown", footBall.controls.keyDownHandler, false);
document.addEventListener("keyup", footBall.controls.keyUpHandler, false);

</script>

</body>
</html>
geraldjtorres
  • 121
  • 1
  • 1
  • 8
  • If you want to make the `requestAnimationFrame` slow while the ball is firyng, check the accepted answer in this question: http://stackoverflow.com/questions/19764018/controlling-fps-with-requestanimationframe make the game FPS slow while it's firyng and then make the FPS normal later. –  Jun 19 '16 at 00:39

1 Answers1

1

Add some properties to football that control if/when a shot is occuring:

// is a shot in progress?
isShooting:false,

// the time when next shot will start
nextShotTime:0,

// delay between shots
delayUntilNextShot:3000,

Then in the animation loop, use these properties to appropriately delay the next shot:

  • If isShooting, process the shot,
  • If not isShooting, see if the required delay has elapsed between shots. If yes, set isShooting=true,
  • When the goalie blocks or misses the shot, set isShooting=false and set nextShotTime=currentTime+delayUntilNextShot,

Example code and a Demo:

var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext("2d");

//Sets the original position of the ball
var x = canvas.width/2;
var y = 50;

// Defines  values that will be added to the position of x and y values
// List of possible values for the x position
var x_options = [3.5, 3, 2.5, 2, 1.5, 1, 0.5, 0, -0.5, -1, -1.5, -2, -2.5, -3, -3.5];

// Gets a random value from the x_options array
var dx = x_options[Math.floor(Math.random() * x_options.length)];
var dy = 5;

var ballRadius = 10;

// Defines the height and width of the goal
var goal_height = 40;
var goal_width = 200

// Defines the height, width and position of goalie
var goalieHeight = 20;
var goalieWidth = 40;
var goalieX = (canvas.width-goalieWidth)/2;
var goalieY = (canvas.height - goal_height) - 30;

// Set to false by default
var rightPressed = false;
var leftPressed = false;

var goalkeeper_blocked = 0;
var goalkeeper_missed = 0;
var attempts_left = 3;

var attempt1 = true;
var attempt2 = false;
var attempt3 = false;

var footBall = {

    // is a shot in progress
    isShooting:false,
    
    // time when next shot will run
    nextShotTime:0,
    
    // delay until next shot will run
    delayUntilNextShot:3000,
    
    shapes : {
        ball: function (){
            ctx.beginPath();
            ctx.arc(x, y, ballRadius, 0, Math.PI*2, false);
            ctx.fillStyle = "red";
            ctx.fill();
            ctx.closePath();
        },

        goal : function (){
            ctx.beginPath();
            ctx.rect((canvas.width - goal_width) / 2 , canvas.height - goal_height, goal_width, goal_height);
            ctx.strokeStyle = "#000000";
            ctx.stroke();
            ctx.closePath();
        },

        goalie : function(){
            ctx.beginPath();
            ctx.rect(goalieX, goalieY, goalieWidth, goalieHeight);
            ctx.fillStyle = "#666666";
            ctx.fill();
            ctx.closePath();
        },

        score : function(){
            ctx.font = "16px Arial";
            ctx.fillStyle = "#ffffff";
            ctx.fillText("Score: "+goalkeeper_blocked, 8, 20);
        },

        missed : function(){
            ctx.font = "16px Arial";
            ctx.fillStyle = "#ffffff";
            ctx.fillText("Missed: "+goalkeeper_missed, 8, 40);
        },

        attempts : function(){
            ctx.font = "16px Arial";
            ctx.fillStyle = "#ffffff";
            ctx.fillText("Attempts left: "+attempts_left, canvas.width-110, 20);
        }


    },

    controls : {
        keyDownHandler : function (e){
            if(e.keyCode == 39) {
                rightPressed = true;
            }
            else if(e.keyCode == 37) {
                leftPressed = true;
            }

        },

        keyUpHandler : function(e){
            if(e.keyCode == 39) {
                rightPressed = false;
            }
            else if(e.keyCode == 37) {
                leftPressed = false;
            }

        }

    },

    calculateScore : function(){
        if(goalkeeper_missed > goalkeeper_blocked){
            alert("GAME OVER! YOU HAVE LOST!");
            document.location.reload();

        } else {
            alert("GAME OVER! YOU HAVE WON!");
            document.location.reload();
        }
    },

    resetShapePositions : function(){
        //Sets the original position of the ball
        x = canvas.width/2;
        y = 50;

        // Sets a new shooting path
        dx = x_options[Math.floor(Math.random() * x_options.length)];
        dy = 5;

        // Resets the goalie to the middle
        goalieX = (canvas.width-goalieWidth)/2;

    },

    drawField: function(){
        // This ensures that the ball doesn't leave a trail
        // Clears the canvas of this shape each frame
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // Draws shapes on the canvas
        footBall.shapes.ball();
        footBall.shapes.goal();
        footBall.shapes.goalie();
        footBall.shapes.score();
        footBall.shapes.missed();
        footBall.shapes.attempts();
    },

    draw : function(currentTime){

        //   makes paddle move left and right and only within the canvas
        if(rightPressed && goalieX < canvas.width-goalieWidth) {
            goalieX += 7;
        }
        else if(leftPressed && goalieX > 0) {
          goalieX -= 7;
        }

        // draw the scene
        footBall.drawField();

        // delay until next shot time is due 
        if(!footBall.isShooting){
            // time has elapsed, let's shoot again
            if(currentTime>footBall.nextShotTime){
                footBall.isShooting=true;
            }else{
                // time has not elapsed, just request another loop
                requestAnimationFrame(footBall.draw);
                return;
            }
        }

        // adds values to the balls  x and y position every frame
        x += dx;
        y += dy;

        // Ball hits the goal 
        if(y + dy > canvas.height - goal_height) {

            // end the shot
            footBall.isShooting=false;
            // delay the next shot
            footBall.nextShotTime=currentTime+footBall.delayUntilNextShot;

            attempts_left--;
            goalkeeper_missed++;
            if (!attempts_left){
                footBall.calculateScore();
            } 
            else {
                footBall.resetShapePositions();
            }

        } // Ball saved by goalie
        else if (x  > goalieX && x  < goalieX + goalieWidth && y + dy > goalieY - ballRadius){

            // end the shot
            footBall.isShooting=false;
            // delay the next shot
            footBall.nextShotTime=currentTime+footBall.delayUntilNextShot;

            attempts_left--;
            goalkeeper_blocked++;

            if (!attempts_left){
                footBall.calculateScore();
            } 
            else {
                footBall.resetShapePositions();

            }

        } 

        requestAnimationFrame(footBall.draw);
    }

}

footBall.drawField();
footBall.nextShotTime=footBall.delayUntilNextShot;
requestAnimationFrame(footBall.draw);

// Defines what functions are fired when keydown or keyup event triggers
document.addEventListener("keydown", footBall.controls.keyDownHandler, false);
document.addEventListener("keyup", footBall.controls.keyUpHandler, false);
* { padding: 0; margin: 0; }
canvas { background: #a5bd7b; display: block; margin: 0 auto; }
<canvas id="myCanvas" width="300" height="250"></canvas>
markE
  • 102,905
  • 11
  • 164
  • 176
  • Thanks! works like a charm. Im reading your solution and I can't figure out where currentTime is being declared, it doesn't look like a host or native method unless im mistaken? – geraldjtorres Jun 19 '16 at 11:28
  • You're welcome! `requestAnimationFrame` automatically sends in a timestamp argument with each call to `draw`. In this case I've labeled it `currentTime`, but you can call it anything you want. The timestamp is (basically) the elapsed time since the current browser page started to load. Cheers! – markE Jun 19 '16 at 16:17
  • Ahh thank you for clearing that up. The game is getting there. So far I have removed the alerts every time the ball is block or missed and replaced it with a simple text appearing on screen using ctx.fillText. I am however findind that it will only appear on screen for a split second before the canvas resets itself for the next attempt. I've been trying to use your delay logic but no luck, is there any way to reuse the solution you've given me to apply a delay after the ball is saved or block so the text can stay on screen for a bit longer? – geraldjtorres Jun 19 '16 at 16:57
  • Create a `footBall.lastResult` property that holds the result of the last attempt ("Goal" or "Blocked"). Set `football.lastResult=''` before the first attempt to indicate there are no previous results. Add a `footBall.shapes.showLastResult` method that displays `football.lastResult` only when `football.isShooting` is false. That will show the user the last result until the next attempt starts. Cheers! – markE Jun 19 '16 at 18:12