Part 3
This part is interesting, is my favorite, and probably the most fun to do of the whole project, which is to give life to the rival bot player. The rival is going to steal the ball from us and is going to score goals if we don't defend our gate.
You can check the GitHub repository by clicking here
You can play the last version of the game by clicking here
What do I want in Part 3? I want the game to be more complex.
- Giving the players ability to shoot and run
- Improve the draw of the field
- Make the goalkeepers cover their position.
- Add a home goal gate and the possibility for the rival to go after the ball, and score a goal.
Giving the players ability to shoot and run
I'll give the user the possibility to shoot if the player is close enough to the ball. The player will also have the chance to run, which means the speed will be increased.
Also, I'll add a restart button option, when the user presses the R key, the match would restart the scores and the positions.
I'll start by adding this three global variables:
var shoot = false;
var run = false;
var restart = false;
//Also I need to add this to the reset function
I need to add the document.onkeyup
function this three options:
if (e.keyCode === 88) {
shoot = false;
}
if (e.keyCode === 90) {
run = false;
}
if (e.keyCode === 82) {
restart = false;
}
And I also need to add the document.onkeydown
function this three options:
if (e.keyCode === 88) {
shoot = true;
}
if (e.keyCode === 90) {
run = true;
}
if (e.keyCode === 82) {
restart = true;
}
Now, what I need to do to achieve this is to update the keyboardMoves
function:
First I added these two vars and a playerShoot
function.
var dx = (players[0].x - ball.x) / players[0].size;
var dy = (players[0].y - ball.y) / players[0].size;
function playerShoot() {
ball.xVel = 3 * -dx;
ball.yVel = 3 * -dy;
players[0].xVel = dx;
players[0].yVel = dy;
console.log("Shoots");
}
I added a var called isAble
this variable will store a number, using the getDistance
function is going to tell me how close the player is from the ball. So If the player is close enough then can make a shoot. If the player presses the button, then will shoot.
On the other hand, I wrote that if the Z key is pressed down then the player speed will increase. if not then it'll be normal.
//this var will allow me to shoot if the player can (if the player is close enough)
var isAble =
getDistance(players[0].x, players[0].y, ball.x, ball.y) -
players[0].size -
ball.size;
if (isAble < 15) {
if (shoot) {
playerShoot();
}
}
if (run) {
players[0].maxSpeed = 3;
} else {
players[0].maxSpeed = 2;
}
And, I need also to update the HTML file
, I'll add this to the body after the score elements.
<p id="yellowteam_score" class="text-1xl"></p>
<p id="greenteam_score" class="text-1xl" ></p>
<p class="text-1xl">
Press Z to run, press X to shoot! <br />
Press R to restart the match
</p>
So here you can check, that if I get close enough, I can console.log(isAble)
, and when I press the 'x key' my player will shoot the ball
Now that my player has the new abilities I'll inform the user by updating the HTML file. Below everything inside the body section I'll add the instructions:
<p class="text-1xl" id="instructions">
Press Z to run, press X to shoot! <br />
Press R to restart the match
</p>
Updating the field
Okay, my player can move and have two new functions. Now let's add a new goal gate for the rival team.
And the function looks like this:
function renderBackground() {
context.save();
// Outer lines
context.beginPath();
context.rect(0, 0, canvas.width, canvas.height);
context.fillStyle = "#a0bb9e";
context.fill();
context.lineWidth = 1;
context.strokeStyle = "#FFF";
context.stroke();
context.closePath();
context.fillStyle = "#FFF";
// Mid line
context.beginPath();
context.moveTo(canvas.width / 2, 0);
context.lineTo(canvas.width / 2, canvas.height);
context.stroke();
context.closePath();
//Mid circle
context.beginPath();
context.arc(canvas.width / 2, canvas.height / 2, 53, 0, 2 * Math.PI, false);
context.stroke();
context.closePath();
//Local penalty box
context.beginPath();
context.rect(0, (canvas.height - 146) / 2, 44, 146);
context.stroke();
context.closePath();
//Local goal
context.beginPath();
context.moveTo(1, canvas.height / 2 - 73);
context.lineTo(1, canvas.height / 2 + 73);
context.lineWidth = 5;
context.stroke();
context.closePath();
context.lineWidth = 1;
//Local penalty point
context.beginPath();
context.arc(88, canvas.height / 2, 1, 0, 2 * Math.PI, true);
context.fill();
context.closePath();
//Rival goal box
context.beginPath();
context.rect(canvas.width - 44, (canvas.height - 146) / 2, 44, 146);
context.stroke();
context.closePath();
//Rival goal
context.beginPath();
context.moveTo(canvas.width - 1, canvas.height / 2 - 73);
context.lineTo(canvas.width - 1, canvas.height / 2 + 73);
context.lineWidth = 5;
context.stroke();
context.closePath();
context.lineWidth = 1;
//Rival penalty point
context.beginPath();
context.arc(canvas.width - 88, canvas.height / 2, 1, 0, 2 * Math.PI, true);
context.fill();
context.closePath();
}
And the renderPlayers
function which replaces the renderPlayer
one, looks like this:
function renderPlayers() {
for (let i = 0; i < 2; i++) {
context.beginPath();
context.fillStyle = "yellow"; //player color
context.arc(players[i].x, players[i].y, players[i].size, 0, Math.PI * 2);
context.fill();
context.closePath();
}
for (let i = 2; i < 4; i++) {
context.beginPath();
context.fillStyle = "green";
context.arc(players[i].x, players[i].y, players[i].size, 0, Math.PI * 2);
context.fill();
context.closePath();
}
context.restore();
}
The background now looks quite better:
Making the CPU move
This is an interesting section of the article. My favorite part. What I want exactly is, the goalkeepers to cover their position, if the ball is near, they should throw it as far as they can The forward player, should try to steal the ball and score a goal.
To start I started a new function called directions
. Which inside has two more functions
coverPosition()
goalkeeperDirections()
forwardDirections()
function directions(){
function coverPosition(){}
function goalkeeperDirections(){}
function forwardDirections(){}
coverPosition()
goalkeeperDirections()
forwardDirections()
}
The code's a little longer, but one can follow with the comments of it.
The coverPosition()
function
// Making goalkeepers cover their position
function coverPosition() {
players[1].y--;
players[3].y++;
var dy_player1 = (players[1].y - (players[1].y - 25)) / players[1].size;
var dy_player3 = (players[3].y - (players[3].y - 25)) / players[3].size;
if (players[1].y < upon_theGoal) {
players[1].yVel = dy_player1;
}
if (players[1].y > down_theGoal) {
players[1].yVel = -dy_player1;
}
if (players[3].y < upon_theGoal) {
players[3].yVel = dy_player3;
}
if (players[3].y > down_theGoal) {
players[3].yVel = -dy_player3;
}
// A limit for the goalkeeper in the x-axis
if (players[3].x < 825) {
var dx = (players[3].x - (players[3].x - 25)) / players[3].size;
players[3].xVel = dx;
}
if (players[1].x > 75) {
var dx = (players[1].x - (players[1].x - 25)) / players[1].size;
players[1].xVel = -dx;
}
}
The goalkeeperDirections()
function:
//making the yellow goalkeeper go after the ball when it's close enough
function goalkeeperDirections() {
for (i = 1; i < 4; i++) {
if (i === 2) {
i++;
}
var dx = (players[i].x - ball.x) / players[i].size;
var dy = (players[i].y - ball.y) / players[i].size;
var ball_distance =
getDistance(players[i].x, players[i].y, ball.x, ball.y) -
players[i].size -
ball.size;
// if the player is close to the ball he will go for it
if (i === 1) {
if (ball_distance < 75) {
console.log("Goalkeeper player is looking for the ball");
players[i].xVel = (1 / 2) * -dx;
players[i].yVel = (1 / 2) * -dy;
if (ball_distance < 10) {
console.log("throws the ball!");
ball.xVel = 5 * -dx;
ball.yVel = 0.15 - dy;
players[i].xVel = 3 * dx;
}
if (players[i].x > 150) {
players[i].xVel = 2 * dx;
}
}
}
if (i === 3) {
if (ball_distance < 75) {
console.log("Goalkeeper player is looking for the ball");
players[i].xVel = (1 / 2) * -dx;
players[i].yVel = (1 / 2) * -dy;
if (ball_distance < 10) {
console.log("throws the ball!");
ball.xVel = 5 * -dx;
ball.yVel = 0.15 - dy;
players[i].xVel = 3 * -dx;
}
if (players[i].x < 750) {
players[i].xVel = 2 * dx;
}
}
}
}
}
And I finished this section with the forwardDirections()
function:
function forwardDirections() {
var dx = (players[2].x - ball.x) / players[2].size;
var dy = (players[2].y - ball.y) / players[2].size;
var ball_distance =
getDistance(players[2].x, players[2].y, ball.x, ball.y) -
players[2].size -
ball.size;
// if the green player is close to the ball he will go for it
if (ball_distance < 100) {
players[2].xVel = (1 / 2) * -dx;
players[2].yVel = (1 / 2) * -dy;
console.log("green player is looking for the ball");
// if the player is in the right position to shoot and score a goal, the he will do it
if (
players[2].y < down_theGoal &&
players[2].y > upon_theGoal &&
players[2].x < 100
) {
players[2].x = players[2].x++;
ball.xVel = (7 / 2) * -dx;
ball.yVel = (7 / 2) * -dy;
players[2].xVel = 3 * dx;
players[2].yVel = 3 * dy;
}
// check if the player is going in the right direction, if it's not then fix it
// Here I check if the player goes in the right direction related to axis x
if (players[2].x > 700) {
console.log("nohe right direction axis-x");
players[2].xVel = -dx;
players[2].yVel = -dy;
ball.x--;
ball.y--;
}
if (players[2].x < upon_theGoal) {
players[2].x = players[2].x++;
ball.x = ball.x - 3;
if (players[2].y < upon_theGoal) {
ball.y = ball.y++;
} else if (players[2].y > upon_theGoal) {
ball.y = ball.y--;
}
}
// Here I check if the player goes in the right direction related to axis-y
if (players[2].y < upon_theGoal || players[2].y > down_theGoal) {
console.log("not in the right direction axis-y");
if (players[2].y < upon_theGoal) {
ball.y++;
} else if (players[2].y > down_theGoal) {
ball.y--;
players[2].x--;
}
}
}
}
When I update the reset function, ends like this:
function reset() {
players = [
//yellow
new Player(canvas.width / 3, canvas.height / 2),
new Player(30, 300),
//green
new Player((canvas.width / 3) * 2, canvas.height - canvas.height / 2),
new Player(1170, 300),
];
ball = new Ball(canvas.width / 2, canvas.height / 2);
up = false;
down = false;
left = false;
right = false;
shoot = false;
run = false;
restart = false;
}
Finally, I need to update the start
function to add the restart match possibility. It'll look like this:
function start() {
$(document).ready(
function()
{
document.getElementById("greenteam_score").innerHTML = "Green Team Score: " + greenteam;
document.getElementById("yellowteam_score").innerHTML = "Yellow Team Score: " + yellowteam;
}
);
if (restart) {
reset();
}
//Render functions
clear();
renderBackground();
renderPlayers();
renderBall();
//Moves functions
movePlayers();
moveBall();
keyboardMoves();
directions();
//Bounce functions
players_Ball_Collision();
playersCollision();
playersBounds();
ballBounds();
requestAnimationFrame(start);
};
Here is a playable result of part 3:
(codepen.io/gonzalo-simon/full/ExgwwBq)]
Alright, now I have a game, and the CPU seems to play, at least it's trying. Now the main idea is done, the game is working, as a final step I want the game the look better, the objectives for the next part are:
- I want a better-looking score counter.
- I want to counter that will set up the full time of the match.
- I want a Main Menu with the following options:
- Quick Game Mode
- Settings
- About (credits)
- Sound (Crowd, and a piece of music, for the Main Menu)
- To draw a player for the local team.
- To draw a player for the rival team.
- To draw a ball.
End of the third part.