In part 4 of this series, we added a game mode and a feature to reset the ball. Now we’ll look at adding some controls to the paddles to make it move and collision detection so the paddles can deflect the ball.
Adding keyboard input
Here we set the paddle movement velocity at a 600 pixels. Next, add the following code to the mainState
object:
39 40 41 42 43 44 45 46 47 48 49 |
var mainState = function(game) { this.backgroundGraphics; this.ballSprite; this.paddleLeftSprite; this.paddleRightSprite; this.paddleLeft_up; this.paddleLeft_down; this.paddleRight_up; this.paddleRight_down; } |
Lines 45-48 will be used to add both the left and right paddles controls with its own up and down input controls. We will add keyboard input to these next by creating a new function called initKeyboard
after the initPhysics
function.
90 91 92 93 94 95 96 97 98 99 100 101 102 |
initPhysics: function () { game.physics.startSystem(Phaser.Physics.ARCADE); game.physics.enable(this.ballSprite, Phaser.Physics.ARCADE); this.ballSprite.checkWorldBounds = true; this.ballSprite.body.collideWorldBounds = true; this.ballSprite.body.immovable = true; this.ballSprite.body.bounce.set(1); }, initKeyboard: function () { }, |
Add the following code in the initKeyboard
function:
100 101 102 103 104 105 106 |
initKeyboard: function () { this.paddleLeft_up = game.input.keyboard.addKey(Phaser.Keyboard.A); this.paddleLeft_down = game.input.keyboard.addKey(Phaser.Keyboard.Z); this.paddleRight_up = game.input.keyboard.addKey(Phaser.Keyboard.UP); this.paddleRight_down = game.input.keyboard.addKey(Phaser.Keyboard.DOWN); }, |
For the left paddle, we will assign the A and Z keys to control the up and down movement. For the right paddle, the UP and DOWN arrow keys will be used instead.
The game.input.keyboard.addKey
function creates a new Key object with a specific keyCode argument to keep track of when that key is pressed. You can find the complete list of keyCodes in the Phaser.Keyboard
class file from lines 579 to 678.
To enable the keyboard when the game starts and disable it during demo mode, add the following code to the enablePaddles
function:
137 138 139 140 141 142 143 144 145 |
enablePaddles: function (enabled) { this.paddleLeftSprite.visible = enabled; this.paddleRightSprite.visible = enabled; this.paddleLeft_up.enabled = enabled; this.paddleLeft_down.enabled = enabled; this.paddleRight_up.enabled = enabled; this.paddleRight_down.enabled = enabled; }, |
Next, we need to call the initKeyboard
function from the create
function to intialise the keyboard controls:
61 62 63 64 65 66 |
create: function () { this.initGraphics(); this.initPhysics(); this.initKeyboard(); this.startDemo(); }, |
Adding the arcade physics body
Going back to the gameProperties
object, we’ll set the constant movement speed for both paddles. Add the following highlighted code:
7 8 9 |
paddleLeft_x: 50, paddleRight_x: 590, paddleVelocity: 600, |
Since both paddles function exactly the same, we’ll create a grouped object to manage both paddles simultaneously. Let’s call it paddleGroup
and add it to the mainState
function:
39 40 41 42 43 44 45 46 47 48 49 50 |
var mainState = function(game) { this.backgroundGraphics; this.ballSprite; this.paddleLeftSprite; this.paddleRightSprite; this.paddleGroup; this.paddleLeft_up; this.paddleLeft_down; this.paddleRight_up; this.paddleRight_down; } |
Now we’ll add the arcade physics to the paddles. Go to the initPhysics
function and add the following code:
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
initPhysics: function () { game.physics.startSystem(Phaser.Physics.ARCADE); game.physics.enable(this.ballSprite, Phaser.Physics.ARCADE); this.ballSprite.checkWorldBounds = true; this.ballSprite.body.collideWorldBounds = true; this.ballSprite.body.immovable = true; this.ballSprite.body.bounce.set(1); this.paddleGroup = game.add.group(); this.paddleGroup.enableBody = true; this.paddleGroup.physicsBodyType = Phaser.Physics.ARCADE; this.paddleGroup.add(this.paddleLeftSprite); this.paddleGroup.add(this.paddleRightSprite); this.paddleGroup.setAll('checkWorldBounds', true); this.paddleGroup.setAll('body.collideWorldBounds', true); this.paddleGroup.setAll('body.immovable', true); }, |
Line 102 creates a new group object. Next, we add some default properties to the group so that any new object added to the group will inherit these properties.
By default, we will want all newly added objects to have its physics body enabled and use the arcade physics system. We do that in lines 103-104.
At lines 106-107, we add our left and right paddle sprites to the group.
Next, we’ll set the same property for all objects in the group by calling the setAll
function:
- Line 109: the
checkWorldBounds
property is set totrue
. This will enable collision checking between the paddles and the game world boundaries. - Line 110: to prevent both paddles from going outside the world boundaries, we set the physics body property
body.collideWorldBounds
totrue
. - Line 111: to prevent the paddles from being pushed away when the ball collides with either paddle, we set the physics body property
body.immovable
property totrue
.
Looking again at the enablePaddles
function, let’s make a minor change to our paddle visibility code and add a new line of code:
154 155 156 157 158 159 160 161 162 |
enablePaddles: function (enabled) { this.paddleGroup.setAll('visible', enabled); this.paddleGroup.setAll('body.enable', enabled); this.paddleLeft_up.enabled = enabled; this.paddleLeft_down.enabled = enabled; this.paddleRight_up.enabled = enabled; this.paddleRight_down.enabled = enabled; }, |
By using the setAll
function, we can easily set the visible
and body.enable
properties of both paddles to use the enabled
parameter.
Moving the paddles
Next, add the following code to the moveLeftPaddle
function:
162 163 164 165 166 167 168 169 170 171 172 173 |
moveLeftPaddle: function () { if (this.paddleLeft_up.isDown) { this.paddleLeftSprite.body.velocity.y = -gameProperties.paddleVelocity; } else if (this.paddleLeft_down.isDown) { this.paddleLeftSprite.body.velocity.y = gameProperties.paddleVelocity; } else { this.paddleLeftSprite.body.velocity.y = 0; } }, |
Add the following code to the moveRightPaddle
function:
175 176 177 178 179 180 181 182 183 184 185 186 |
moveRightPaddle: function () { if (this.paddleRight_up.isDown) { this.paddleRightSprite.body.velocity.y = -gameProperties.paddleVelocity; } else if (this.paddleRight_down.isDown) { this.paddleRightSprite.body.velocity.y = gameProperties.paddleVelocity; } else { this.paddleRightSprite.body.velocity.y = 0; } } |
Notice how both the moveLeftPaddle
and moveRightPaddle
functions are almost identical. Here how it works:
- First, we check if the up key
isDown
. If the up key is currently being pressed, we set the y velocity of the paddle physics body to a negative value. A negative y velocity value moves the sprite upwards. - If the up key is not being pressed, we then check if the down key is being pressed instead. If the down key is being pressed, we set the y velocity to a positive value which moves the sprite downwards.
- If neither up nor down keys are being pressed, the last
else
condition code block will run. This will sets our paddle y velocity to 0 and causes it stop completely.
To get this working, we need to call both the moveLeftPaddle
and moveRightPaddle
functions within the update
function:
69 70 71 72 |
update: function () { this.moveLeftPaddle(); this.moveRightPaddle(); }, |
Click here to download the source codes up to this point. Here is our current work in progress:
Hitting the ball
In part 1 of this series, I mentioned that the original Pong paddles were divided into 8 segments that determine the return angle of the ball when it collided with the paddles.
Let’s add the following code to the gameProperties
object:
7 8 9 10 11 12 |
paddleLeft_x: 50, paddleRight_x: 590, paddleVelocity: 600, paddleSegmentsMax: 4, paddleSegmentHeight: 4, paddleSegmentAngle: 15, |
Here, at line 10, we set the maximum segments to a value of 4. This will be used to divide the top half into 4 segments and bottom half into another 4 segments.
Our current paddle graphic height is 32 pixels. To ensure that each segment is the same height, I have divided the 32 pixels into 8 segments with each segment being 4 pixels.
Here is the illustration used from part 1 to shows each segment and its return angle.
For each segment, there will be an increment of 15 degrees as the ball collides towards to outer edges of the paddles. The centre 2 segments will return the ball at a perfect 0 degree angle while the outer edge will return the ball at a 45 degree angle.
We need another function to perform collision detection between the paddles and balls. Add a new function called collideWithPaddle
after the moveRightPaddle
function:
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
moveRightPaddle: function () { if (this.paddleRight_up.isDown) { this.paddleRightSprite.body.velocity.y = -gameProperties.paddleVelocity; } else if (this.paddleRight_down.isDown) { this.paddleRightSprite.body.velocity.y = gameProperties.paddleVelocity; } else { this.paddleRightSprite.body.velocity.y = 0; } }, collideWithPaddle: function (ball, paddle) { }, |
The collideWithPaddle
requires 2 parameters to work:
- The
ball
parameter which is a reference to the ball sprite. - The
paddle
parameter which is a reference to the paddle sprite.
Now to add in the code to make the collideWithPaddle
function work:
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
collideWithPaddle: function (ball, paddle) { var returnAngle; var segmentHit = Math.floor((ball.y - paddle.y)/gameProperties.paddleSegmentHeight); if (segmentHit >= gameProperties.paddleSegmentsMax) { segmentHit = gameProperties.paddleSegmentsMax - 1; } else if (segmentHit <= -gameProperties.paddleSegmentsMax) { segmentHit = -(gameProperties.paddleSegmentsMax - 1); } if (paddle.x < gameProperties.screenWidth * 0.5) { returnAngle = segmentHit * gameProperties.paddleSegmentAngle; game.physics.arcade.velocityFromAngle(returnAngle, gameProperties.ballVelocity, this.ballSprite.body.velocity); } else { returnAngle = 180 - (segmentHit * gameProperties.paddleSegmentAngle); if (returnAngle > 180) { returnAngle -= 360; } game.physics.arcade.velocityFromAngle(returnAngle, gameProperties.ballVelocity, this.ballSprite.body.velocity); } }, |
Two variables are declared:
- The
returnAngle
variable will be used to calculate the angle the ball will be returned at. - The
segmentHit
variable will be used to determine which segment on the paddle is hit.
The centre two segments will have a segmentHit
value of 0 while the outer most segments will have a value of 3.
As it’s possible to exceed the paddleSegmentsMax
value of 4, lines 194-198 will check when that happens.
The -1 is added at the end of the calculation at lines 195 and 197 to limit the values of segmentHit
to a range of -3 to 3. This final value is used to calculate the returnAngle
of the ball by multiplying it by the the paddleSegmentAngle
value of 15.
The next set of conditions from lines 200-210 checks whether the ball has hit the left or right paddle. The first if condition checks if the paddle is on the left side of the game world while the second condition checks if the paddle is on the right.
If the right paddle is hit, we need to offset the angle by 180 degrees to give us the correct angle in returning the ball sprite.
Here is an illustration of how to return angle is calculated:
After calculating the return angle of the ball, we need to reset the velocity and angle for the ball to bounce off the paddles. In lines 202 and 209, we use the arcade physics velocityFromAngle
function to calculate the new velocity of the ball. Here, we use 3 arguments:
- The
angle
: value is in degrees. - The
speed
: the velocity to move at. - The
point
: the Point object to apply the x and y properties to. In this case we apply it to the ball’s arcade physics body.
One final line of code to complete this step. In the update
function, add this line of code:
72 73 74 75 76 |
update: function () { this.moveLeftPaddle(); this.moveRightPaddle(); game.physics.arcade.overlap(this.ballSprite, this.paddleGroup, this.collideWithPaddle, null, this); }, |
At line 75, we call the arcade physics overlap
function to check if the ball sprite overlaps the paddles in the paddle group. Here, we use 5 arguments:
- The
object1
: The first object or array of objects to check. We use our ball sprite for the first object. - The
object2
: The second object or array of objects to check. Here we use our paddle group that contains both paddles. - The
overlapCallback
: An optional callback function that is called if the objects overlap. The two objects from the first two arguments will be passed to thecollideWithPaddle
function in the same order. - The
processCallback
: A callback function that lets you perform additional checks against the two objects if they overlap. We only use this if we need to perform any additional verification before theoverlapCallback
function is called. As there is no need for this, we set it tonull
. - The
callbackContext
: The context in which to run the callbacks. This will be needed later on to apply any further modifications or calculations in thecollideWithPaddle
function.
Here’s our current work in progress:
Click here to download the source codes.
So far so good. In the next step, we’ll look at adding scoring and resetting the game when a player has won.
Hey
Thanks for the Tuts 🙂 very helpful.
Please update the “collideWithPaddle” from:
game.physics.arcade.velocityFromAngle(returnAngle, this.ballVelocity, this.ballSprite.body.velocity);
to:
game.physics.arcade.velocityFromAngle(returnAngle, gameSettings.ballVelocity, this.ballSprite.body.velocity);
As in your code.
Cheers
also for the left paddle 🙂
Hey nd, thanks for noticing the mistake. I’ve updated my article accordingly =)
Should be gameProperties, not gameSettings
Hey Scott, thanks for noticing that error. Correction made =)