diff --git a/.vscode/dictionaries/code-entities.txt b/.vscode/dictionaries/code-entities.txt index eb68f5cb7dc70e1..510ab57da50316b 100644 --- a/.vscode/dictionaries/code-entities.txt +++ b/.vscode/dictionaries/code-entities.txt @@ -38,6 +38,7 @@ ALTGR amssymb android-safetynet animalfound +anims anonid apacheconf apachectl diff --git a/files/en-us/games/tutorials/2d_breakout_game_phaser/animations_and_tweens/index.md b/files/en-us/games/tutorials/2d_breakout_game_phaser/animations_and_tweens/index.md index 2ff9cb55805249e..9713ff8c8e313d8 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_phaser/animations_and_tweens/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_phaser/animations_and_tweens/index.md @@ -5,103 +5,330 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Extra_lives", "Games/Workflows/2D_Breakout_game_Phaser/Buttons")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Extra_lives", "Games/Tutorials/2D_breakout_game_Phaser/Buttons")}} -This is the **14th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). You can find the source code as it should look after completing this lesson at [Gamedev-Phaser-Content-Kit/demos/lesson14.html](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/lesson14.html). - -To make the game look more juicy and alive we can use animations and tweens. This will result in a better, more entertaining experience. Let's explore how to implement Phaser animations and tweens in our game. +This is the **14th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). We'll explore how to implement Phaser animations and tweens in our game, to make the game look more juicy and alive. This will result in a better, more entertaining experience. ## Animations -In Phaser, animations, involve taking a spritesheet from an external source and displaying the sprites sequentially. As an example, we will make the ball wobble when it hits something. +In Phaser, animations involve taking a spritesheet from an external source and displaying the sprites sequentially. As an example, we will make the ball wobble when it hits something. -First of all, [grab the spritesheet from GitHub](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/img/wobble.png) and save it in your `/img` directory. +First of all, [grab the spritesheet](https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser/wobble.png) and save it in your `/img` directory. -Next, we will load the spritesheet — put the following line at the bottom of your `preload()` function: +Next, we will load the spritesheet—put the following line at the bottom of your `preload()` method: ```js -game.load.spritesheet("ball", "img/wobble.png", 20, 20); +this.load.spritesheet("wobble", "img/wobble.png", { + frameWidth: 20, + frameHeight: 20, +}); ``` -Instead of loading a single image of the ball we can load the whole spritesheet — a collection of different images. We will show the sprites sequentially to create the illusion of animation. The `spritesheet()` method's two extra parameters determine the width and height of each single frame in the given spritesheet file, indicating to the program how to chop it up to get the individual frames. +Instead of loading a single image of the ball, we can load the whole spritesheet—a collection of different images. We will show the sprites sequentially to create the illusion of animation. The `spritesheet()` method's extra parameter determines the width and height of each single frame in the given spritesheet file, indicating to the program how to chop it up to get the individual frames. ## Loading the animation -Next up, go into your create() function, find the line that loads the ball sprite, and below it put the call to `animations.add()` seen below: +Next up, go into your `create()` method, find the code block that loads and configures the ball sprite, and below it, put the call to `anims.create` seen below: ```js -ball = game.add.sprite(50, 250, "ball"); -ball.animations.add("wobble", [0, 1, 0, 2, 0, 1, 0, 2, 0], 24); +this.ball = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 25, + "ball", +); +// ... +this.ball.anims.create({ + key: "wobble", + frameRate: 24, + frames: this.anims.generateFrameNumbers("wobble", { + frames: [0, 1, 0, 2, 0, 1, 0, 2, 0], + }), +}); ``` -To add an animation to the object we use the `animations.add()` method, which contains the following parameters +To add an animation to the object, we use the `anims.create()` method, which receives the parameter with the following properties: -- The name we chose for the animation -- An array defining the order in which to display the frames during the animation. If you look again at the `wobble.png` image, you'll see there are three frames. Phaser extracts these and stores references to them in an array — positions 0, 1, and 2. The above array says that we are displaying frame 0, then 1, then 0, etc. -- The frame rate, in fps. Since we are running the animation at 24fps and there are 9 frames, the animation will display just under three times per second. +- `key`: The name we chose for the animation. +- `frameRate`: The frame rate in fps. Since we are running the animation at 24fps and there are 9 frames, the animation will display just under three times per second. +- `frames`: An array defining the order in which to display the frames during the animation. If you look again at the `wobble.png` image, you'll see there are three frames. Phaser extracts these and stores references to them in an array—positions 0, 1, and 2. The above array says that we are displaying frame 0, then 1, then 0, etc. ## Applying the animation when the ball hits the paddle -In the `arcade.collide()` method call that handles the collision between the ball and the paddle (the first line inside `update()`, see below) we can add an extra parameter that specifies a function to be executed every time the collision happens, in the same fashion as the `ballHitBrick()` function. Update the first line inside `update()` as shown below: +In the `physics.collide()` method call that handles the collision between the ball and the paddle (the first line inside `update()`, see below), we can add an extra parameter that specifies a function to be executed every time the collision happens, in the same fashion as the `hitBrick()` method. Update the first line inside `update()` as shown below: ```js -function update() { - game.physics.arcade.collide(ball, paddle, ballHitPaddle); - game.physics.arcade.collide(ball, bricks, ballHitBrick); - paddle.x = game.input.x || game.world.width * 0.5; +class ExampleScene extends Phaser.Scene { + // ... + update() { + this.physics.collide(this.ball, this.paddle, (ball, paddle) => + this.hitPaddle(ball, paddle), + ); + this.physics.collide(this.ball, this.bricks, (ball, brick) => + this.hitBrick(ball, brick), + ); + this.paddle.x = this.input.x || this.scale.width * 0.5; + // ... + } + // ... } ``` -Then we can create the `ballHitPaddle()` function (having `ball` and `paddle` as default parameters), playing the wobble animation when it is called. Add the following function just before your closing `` tag: +Then, we can create the `hitPaddle()` method (having `ball` and `paddle` as parameters), playing the wobble animation when it is called. Add the following method, above the `hitBrick()` method: ```js -function ballHitPaddle(ball, paddle) { - ball.animations.play("wobble"); +class ExampleScene extends Phaser.Scene { + // ... + hitPaddle(ball, paddle) { + this.ball.anims.play("wobble"); + } + // ... } ``` -The animation is played every time the ball hits the paddle. You can add the `animations.play()` call inside the `ballHitBrick()` function too, if you feel it would make the game look better. +The animation is played every time the ball hits the paddle. You can add the `anims.play()` call inside the `hitBrick()` method too, if you feel it would make the game look better. ## Tweens Whereas animations play external sprites sequentially, tweens smoothly animate properties of an object in the gameworld, such as width or opacity. -Let's add a tween to our game to make the bricks smoothly disappear when they are hit by the ball. Go to your `ballHitBrick()` function, find your `brick.kill();` line, and replace it with the following: +Let's add a tween to our game to make the bricks smoothly disappear when they are hit by the ball. Go to your `hitBrick()` method, find your `brick.destroy();` line, and replace it with the following: ```js -const killTween = game.add.tween(brick.scale); -killTween.to({ x: 0, y: 0 }, 200, Phaser.Easing.Linear.None); -killTween.onComplete.addOnce(() => { - brick.kill(); -}, this); -killTween.start(); +const destroyTween = this.tweens.add({ + targets: brick, + ease: "Linear", + repeat: 0, + duration: 200, + props: { + scaleX: 0, + scaleY: 0, + }, + onComplete: () => { + brick.destroy(); + }, +}); +destroyTween.play(); ``` Let's walk through this so you can see what's happening here: -1. When defining a new tween you have to specify which property will be tweened — in our case, instead of hiding the bricks instantly when hit by the ball, we will make their width and height scale to zero, so they will nicely disappear. To the end, we use the `add.tween()` method, specifying `brick.scale` as the argument as this is what we want to tween. -2. The `to()` method defines the state of the object at the end of the tween. It takes an object containing the chosen parameter's desired ending values (scale takes a scale value, 1 being 100% of size, 0 being 0% of size, etc.), the time of the tween in milliseconds and the type of easing to use for the tween. +1. When defining a new tween, you have to specify which property of the `targets` will be tweened—in our case, instead of hiding the bricks instantly when hit by the ball, we will make their width and height scale to zero, so they will nicely disappear. To the end, we use the `tweens.add()` method, specifying `brick` as the `targets` and the `scaleX` and `scaleY` properties to tween in the `props` object. +2. Other properties we can set are `ease`, which defines the easing function to use (in this case, `Linear`), `repeat`, which defines how many times the tween should repeat (0 means it will not repeat), and `duration`, which is the time in milliseconds that the tween will take to complete. 3. We will also add the optional `onComplete` event handler, which defines a function to be executed when the tween finishes. -4. The last thing do to is to start the tween right away using `start()`. +4. The last thing to do is to start the tween right away using the `play()` method. -That's the expanded version of the tween definition, but we can also use the shorthand syntax: +## Compare your code -```js -game.add - .tween(brick.scale) - .to({ x: 2, y: 2 }, 500, Phaser.Easing.Elastic.Out, true, 100); +Here's what you should have so far, running live. To view its source code, click the "Play" button. + +```html hidden + ``` -This tween will double the brick's scale in half a second using Elastic easing, will start automatically, and have a delay of 100 milliseconds. +```css hidden +* { + padding: 0; + margin: 0; +} +``` -## Compare your code +```js hidden +class ExampleScene extends Phaser.Scene { + ball; + paddle; + bricks; + + scoreText; + score = 0; + + lives = 3; + livesText; + lifeLostText; + + preload() { + this.load.setBaseURL( + "https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser", + ); + + this.load.image("ball", "ball.png"); + this.load.image("paddle", "paddle.png"); + this.load.image("brick", "brick.png"); + this.load.spritesheet("wobble", "wobble.png", { + frameWidth: 20, + frameHeight: 20, + }); + } + create() { + this.physics.world.checkCollision.down = false; + + this.ball = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 25, + "ball", + ); + this.physics.add.existing(this.ball); + this.ball.body.setVelocity(150, -150); + this.ball.body.setCollideWorldBounds(true, 1, 1); + this.ball.body.setBounce(1); + this.ball.anims.create({ + key: "wobble", + frameRate: 24, + frames: this.anims.generateFrameNumbers("wobble", { + frames: [0, 1, 0, 2, 0, 1, 0, 2, 0], + }), + }); + + this.paddle = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 5, + "paddle", + ); + this.paddle.setOrigin(0.5, 1); + this.physics.add.existing(this.paddle); + this.paddle.body.setImmovable(true); + + this.initBricks(); + + const textStyle = { font: "18px Arial", fill: "#0095dd" }; + this.scoreText = this.add.text(5, 5, "Points: 0", textStyle); + + this.livesText = this.add.text( + this.scale.width - 5, + 5, + `Lives: ${this.lives}`, + textStyle, + ); + this.livesText.setOrigin(1, 0); + this.lifeLostText = this.add.text( + this.scale.width * 0.5, + this.scale.height * 0.5, + "Life lost, click to continue", + textStyle, + ); + this.lifeLostText.setOrigin(0.5, 0.5); + this.lifeLostText.visible = false; + } + update() { + this.physics.collide(this.ball, this.paddle, (ball, paddle) => + this.hitPaddle(ball, paddle), + ); + this.physics.collide(this.ball, this.bricks, (ball, brick) => + this.hitBrick(ball, brick), + ); + + this.paddle.x = this.input.x || this.scale.width * 0.5; + const ballIsOutOfBounds = !Phaser.Geom.Rectangle.Overlaps( + this.physics.world.bounds, + this.ball.getBounds(), + ); + if (ballIsOutOfBounds) { + this.ballLeaveScreen(); + } + if (this.bricks.countActive() === 0) { + alert("You won the game, congratulations!"); + location.reload(); + } + } + + initBricks() { + const bricksLayout = { + width: 50, + height: 20, + count: { + row: 3, + col: 7, + }, + offset: { + top: 50, + left: 60, + }, + padding: 10, + }; + + this.bricks = this.add.group(); + for (let c = 0; c < bricksLayout.count.col; c++) { + for (let r = 0; r < bricksLayout.count.row; r++) { + const brickX = + c * (bricksLayout.width + bricksLayout.padding) + + bricksLayout.offset.left; + const brickY = + r * (bricksLayout.height + bricksLayout.padding) + + bricksLayout.offset.top; + + const newBrick = this.add.sprite(brickX, brickY, "brick"); + this.physics.add.existing(newBrick); + newBrick.body.setImmovable(true); + this.bricks.add(newBrick); + } + } + } + + hitPaddle(ball, paddle) { + this.ball.anims.play("wobble"); + } + + hitBrick(ball, brick) { + const destroyTween = this.tweens.add({ + targets: brick, + ease: "Linear", + repeat: 0, + duration: 200, + props: { + scaleX: 0, + scaleY: 0, + }, + onComplete: () => { + brick.destroy(); + }, + }); + destroyTween.play(); + this.score += 10; + this.scoreText.setText(`Points: ${this.score}`); + } + + ballLeaveScreen() { + this.lives--; + if (this.lives > 0) { + this.livesText.setText(`Lives: ${this.lives}`); + this.lifeLostText.visible = true; + this.ball.body.reset(this.scale.width * 0.5, this.scale.height - 25); + this.input.once( + "pointerdown", + () => { + this.lifeLostText.visible = false; + this.ball.body.setVelocity(150, -150); + }, + this, + ); + } else { + // Game over logic + location.reload(); + } + } +} -You can check the finished code for this lesson in the live demo below, and play with it to understand better how it works: +const config = { + type: Phaser.CANVAS, + width: 480, + height: 320, + scene: ExampleScene, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, + backgroundColor: "#eeeeee", + physics: { + default: "arcade", + }, +}; + +const game = new Phaser.Game(config); +``` -{{JSFiddleEmbed("https://jsfiddle.net/end3r/9o4pakrb/","","400")}} +{{EmbedLiveSample("compare your code", "", 480, , , , , "allow-modals")}} ## Next steps -Animations and tweens look very nice, but we can add even more to our game — in the next section we'll look at handling [button](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Buttons) inputs. +Animations and tweens look very nice, but we can add even more to our game—in the next section we'll look at handling [button](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Buttons) inputs. -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Extra_lives", "Games/Workflows/2D_Breakout_game_Phaser/Buttons")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Extra_lives", "Games/Tutorials/2D_breakout_game_Phaser/Buttons")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_phaser/bounce_off_the_walls/index.md b/files/en-us/games/tutorials/2d_breakout_game_phaser/bounce_off_the_walls/index.md index 0ce70d78852e4f8..da48adca5ce9b76 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_phaser/bounce_off_the_walls/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_phaser/bounce_off_the_walls/index.md @@ -5,36 +5,77 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Physics", "Games/Workflows/2D_Breakout_game_Phaser/Player_paddle_and_controls")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Physics", "Games/Tutorials/2D_breakout_game_Phaser/Player_paddle_and_controls")}} -This is the **6th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). You can find the source code as it should look after completing this lesson at [Gamedev-Phaser-Content-Kit/demos/lesson06.html](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/lesson06.html). - -Now that physics have been introduced, we can start implementing collision detection into the game — first we'll look at the walls. +This is the **6th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). Now that physics have been introduced, we can start implementing collision detection into the game—first we'll look at the walls. ## Bouncing off the world boundaries -The easiest way to get our ball bouncing off the walls is to tell the framework that we want to treat the boundaries of the {{htmlelement("canvas")}} element as walls and not let the ball move past them. In Phaser this can be easily accomplished using the `collideWorldsBound` property. Add this line right after the existing `game.physics.enable()` method call: +The easiest way to get our ball bouncing off the walls is to tell the framework that we want to treat the boundaries of the {{htmlelement("canvas")}} element as walls and not let the ball move past them. In Phaser, this can be easily accomplished using the `setCollideWorldBounds()` method. Add this line right after the existing `this.ball.body.setVelocity()` method call: ```js -ball.body.collideWorldBounds = true; +this.ball.body.setCollideWorldBounds(true, 1, 1); ``` -Now the ball will stop at the edge of the screen instead of disappearing, but it doesn't bounce. To make this occur we have to set its bounciness. Add the following line below the previous one: +The `true` tells Phaser to enable collision detection with the world bounds, while the two `1`s are the bounce factor on the x and y axes, respectively. This means that when the ball hits a wall, it will bounce back with the same speed it had before hitting the wall. Try reloading index.html again—now you should see the ball bouncing off all the walls and moving inside the canvas area. -```js -ball.body.bounce.set(1); +## Compare your code + +Here's what you should have so far, running live. To view its source code, click the "Play" button. + +```html hidden + ``` -Try reloading index.html again — now you should see the ball bouncing off all the walls and moving inside the canvas area. +```css hidden +* { + padding: 0; + margin: 0; +} +``` -## Compare your code +```js hidden +class ExampleScene extends Phaser.Scene { + ball; + + preload() { + this.load.setBaseURL( + "https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser", + ); + + this.load.image("ball", "ball.png"); + } + create() { + this.ball = this.add.sprite(50, 50, "ball"); + this.physics.add.existing(this.ball); + this.ball.body.setVelocity(150, 150); + this.ball.body.setCollideWorldBounds(true, 1, 1); + } + update() {} +} -You can check the finished code for this lesson in the live demo below, and play with it to understand better how it works: +const config = { + type: Phaser.CANVAS, + width: 480, + height: 320, + scene: ExampleScene, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, + backgroundColor: "#eeeeee", + physics: { + default: "arcade", + }, +}; + +const game = new Phaser.Game(config); +``` -{{JSFiddleEmbed("https://jsfiddle.net/end3r/dcw36opz/","","400")}} +{{EmbedLiveSample("compare your code", "", 480, , , , , "allow-modals")}} ## Next steps -This is starting to look more like a game now, but we can't control it in any way — it's high time we introduced the [player paddle and controls](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Player_paddle_and_controls). +This is starting to look more like a game now, but we can't control it in any way—it's high time we introduced the [player paddle and controls](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Player_paddle_and_controls). -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Physics", "Games/Workflows/2D_Breakout_game_Phaser/Player_paddle_and_controls")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Physics", "Games/Tutorials/2D_breakout_game_Phaser/Player_paddle_and_controls")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_phaser/build_the_brick_field/index.md b/files/en-us/games/tutorials/2d_breakout_game_phaser/build_the_brick_field/index.md index 3f6dd22c5adef58..5a89df9d19eeaa3 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_phaser/build_the_brick_field/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_phaser/build_the_brick_field/index.md @@ -5,161 +5,251 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Game_over", "Games/Workflows/2D_Breakout_game_Phaser/Collision_detection")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Game_over", "Games/Tutorials/2D_breakout_game_Phaser/Collision_detection")}} -This is the **9th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). You can find the source code as it should look after completing this lesson at [Gamedev-Phaser-Content-Kit/demos/lesson09.html](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/lesson09.html). +This is the **9th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). Let's explore how to create a group of bricks and print them on the screen using a loop. Building the brick field is a little bit more complicated than adding a single object to the screen, although it's still easier with Phaser than in pure JavaScript. -Building the brick field is a little bit more complicated than adding a single object to the screen, although It's still easier with Phaser than in pure JavaScript. Let's explore how to create a group of bricks and print them on screen using a loop. +## New properties -## Defining new variables - -First, let's define the needed variables — add the following below your previous variable definitions: +First, add the new `bricks` property below your previous property definitions: ```js -let bricks; -let newBrick; -let brickInfo; +class ExampleScene extends Phaser.Scene { + // ... previous property definitions ... + bricks; + // ... rest of the class ... +} ``` -The `bricks` variable will be used to create a group, `newBrick` will be a new object added to the group on every iteration of the loop, and `brickInfo` will store all the data we need. +The `bricks` property will be used to create a group of bricks, which will make it easy to manage multiple bricks at once. ## Rendering the brick image -Next, let's load the image of the brick — add the following `load.image()` call just below the others: +Next, let's load the image of the brick—add the following `load.image()` call just below the others: ```js -function preload() { - // … - game.load.image("brick", "img/brick.png"); +class ExampleScene extends Phaser.Scene { + // ... + preload() { + // ... + this.load.image("brick", "img/brick.png"); + } + // ... } ``` -You also need to [grab the brick image from GitHub](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/img/brick.png) and save it in your `/img` directory. +You also need to [grab the brick image](https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser/brick.png) and save it in your `/img` directory. ## Drawing the bricks -We will place all the code for drawing the bricks inside an `initBricks` function to keep it separated from the rest of the code. Add a call to `initBricks` at the end of the `create()` function: +We will place all the code for drawing the bricks inside an `initBricks` method to keep it separated from the rest of the code. Add a call to `initBricks` at the end of the `create()` method: ```js -function create() { - // … - initBricks(); +class ExampleScene extends Phaser.Scene { + // ... + create() { + // ... + this.initBricks(); + } + // ... } ``` -Now onto the function itself. Add the `initBricks()` function at the end of our games code, just before the closing \ tag, as shown below. To begin with we've included the `brickInfo` object, as this will come in handy very soon: +Now on to the method itself. Add the `initBricks` method at the end of the `ExampleScene` class, just before the closing brace `}`, as shown below. To begin with, we add the `bricksLayout` object, as this will come in handy very soon: ```js -function initBricks() { - brickInfo = { - width: 50, - height: 20, - count: { - row: 3, - col: 7, - }, - offset: { - top: 50, - left: 60, - }, - padding: 10, - }; +class ExampleScene extends Phaser.Scene { + // ... + initBricks() { + const bricksLayout = { + width: 50, + height: 20, + count: { + row: 3, + col: 7, + }, + offset: { + top: 50, + left: 60, + }, + padding: 10, + }; + } } ``` -This `brickInfo` object will hold all the information we need: the width and height of a single brick, the number of rows and columns of bricks we will see on screen, the top and left offset (the location on the canvas where we shall start to draw the bricks) and the padding between each row and column of bricks. +This `bricksLayout` holds all the information we need: the width and height of a single brick, the number of rows and columns of bricks we will see on the screen, the top and left offset (the location on the canvas where we start to draw the bricks), and the padding between each row and column of bricks. -Now, let's start creating the bricks themselves — add an empty group first to contain the bricks, by adding the following line at the bottom of the `initBricks()` function: +Now, let's start creating the bricks themselves—first, add an empty group to contain the bricks, by adding the following line at the bottom of the `initBricks()` method: ```js -bricks = game.add.group(); +this.bricks = this.add.group(); ``` -We can loop through the rows and columns to create new brick on each iteration — add the following nested loop below the previous line of code: +We can loop through the rows and columns to create a new brick on each iteration—add the following nested loop below the previous line of code: ```js -for (let c = 0; c < brickInfo.count.col; c++) { - for (let r = 0; r < brickInfo.count.row; r++) { +for (let c = 0; c < bricksLayout.count.col; c++) { + for (let r = 0; r < bricksLayout.count.row; r++) { // create new brick and add it to the group } } ``` -This way we will create the exact number of bricks we need and have them all contained in a group. Now we need to add some code inside the nested loop structure to draw each brick. Fill in the contents as shown below: +This way, we will create the exact number of bricks we need and have them all contained in a group. Now we need to add some code inside the nested loop structure to draw each brick. Fill in the contents as shown below: ```js -for (let c = 0; c < brickInfo.count.col; c++) { - for (let r = 0; r < brickInfo.count.row; r++) { - let brickX = 0; - let brickY = 0; - newBrick = game.add.sprite(brickX, brickY, "brick"); - game.physics.enable(newBrick, Phaser.Physics.ARCADE); - newBrick.body.immovable = true; - newBrick.anchor.set(0.5); - bricks.add(newBrick); +for (let c = 0; c < bricksLayout.count.col; c++) { + for (let r = 0; r < bricksLayout.count.row; r++) { + const brickX = 0; + const brickY = 0; + + const newBrick = this.add.sprite(brickX, brickY, "brick"); + this.physics.add.existing(newBrick); + newBrick.body.setImmovable(true); + this.bricks.add(newBrick); } } ``` -Here we're looping through the rows and columns to create the new bricks and place them on the screen. The newly created brick is enabled for the Arcade physics engine, its body is set to be immovable (so it won't move when hit by the ball), and we're also setting the anchor to be in the middle and adding the brick to the group. +Here we're looping through the rows and columns to create the new bricks and place them on the screen. The newly created brick is enabled for the Arcade physics engine, its body is set to be immovable (so it won't move when hit by the ball), and then it's added to the group. The problem currently is that we're painting all the bricks in one place, at coordinates (0,0). What we need to do is draw each brick at its own x and y position. Update the `brickX` and `brickY` lines as follows: ```js const brickX = - c * (brickInfo.width + brickInfo.padding) + brickInfo.offset.left; + c * (bricksLayout.width + bricksLayout.padding) + bricksLayout.offset.left; const brickY = - r * (brickInfo.height + brickInfo.padding) + brickInfo.offset.top; + r * (bricksLayout.height + bricksLayout.padding) + bricksLayout.offset.top; ``` -Each `brickX` position is worked out as `brickInfo.width` plus `brickInfo.padding`, multiplied by the column number, `c`, plus the `brickInfo.offset.left`; the logic for the `brickY` is identical except that it uses the values for row number, `r`, `brickInfo.height`, and `brickInfo.offset.top`. Now every single brick can be placed in its correct place, with padding between each brick, and drawn at an offset from the left and top Canvas edges. +Each `brickX` position is worked out as `bricksLayout.width` plus `bricksLayout.padding`, multiplied by the column number, `c`, plus the `bricksLayout.offset.left`; the logic for the `brickY` is identical except that it uses the values for row number, `r`, `bricksLayout.height`, and `bricksLayout.offset.top`. Now every single brick can be placed in its correct place, with padding between each brick, and drawn at an offset from the left and top Canvas edges. -## Checking the initBricks() code +If you reload `index.html` at this point, you should see the bricks printed on screen, at an even distance from one another. -Here is the complete code for the `initBricks()` function: +## Compare your code -```js -function initBricks() { - brickInfo = { - width: 50, - height: 20, - count: { - row: 3, - col: 7, - }, - offset: { - top: 50, - left: 60, - }, - padding: 10, - }; - bricks = game.add.group(); - for (let c = 0; c < brickInfo.count.col; c++) { - for (let r = 0; r < brickInfo.count.row; r++) { - const brickX = - c * (brickInfo.width + brickInfo.padding) + brickInfo.offset.left; - const brickY = - r * (brickInfo.height + brickInfo.padding) + brickInfo.offset.top; - newBrick = game.add.sprite(brickX, brickY, "brick"); - game.physics.enable(newBrick, Phaser.Physics.ARCADE); - newBrick.body.immovable = true; - newBrick.anchor.set(0.5); - bricks.add(newBrick); - } - } +Here's what you should have so far, running live. To view its source code, click the "Play" button. + +```html hidden + +``` + +```css hidden +* { + padding: 0; + margin: 0; } ``` -If you reload `index.html` at this point, you should see the bricks printed on screen, at an even distance from one another. +```js hidden +class ExampleScene extends Phaser.Scene { + ball; + paddle; + bricks; -## Compare your code + preload() { + this.load.setBaseURL( + "https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser", + ); + + this.load.image("ball", "ball.png"); + this.load.image("paddle", "paddle.png"); + this.load.image("brick", "brick.png"); + } + create() { + this.physics.world.checkCollision.down = false; + + this.ball = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 25, + "ball", + ); + this.physics.add.existing(this.ball); + this.ball.body.setVelocity(150, -150); + this.ball.body.setCollideWorldBounds(true, 1, 1); + this.ball.body.setBounce(1); + + this.paddle = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 5, + "paddle", + ); + this.paddle.setOrigin(0.5, 1); + this.physics.add.existing(this.paddle); + this.paddle.body.setImmovable(true); + + this.initBricks(); + } + update() { + this.physics.collide(this.ball, this.paddle); + this.paddle.x = this.input.x || this.scale.width * 0.5; + const ballIsOutOfBounds = !Phaser.Geom.Rectangle.Overlaps( + this.physics.world.bounds, + this.ball.getBounds(), + ); + if (ballIsOutOfBounds) { + // Game over logic + location.reload(); + } + } + + initBricks() { + const bricksLayout = { + width: 50, + height: 20, + count: { + row: 3, + col: 7, + }, + offset: { + top: 50, + left: 60, + }, + padding: 10, + }; + + this.bricks = this.add.group(); + for (let c = 0; c < bricksLayout.count.col; c++) { + for (let r = 0; r < bricksLayout.count.row; r++) { + const brickX = + c * (bricksLayout.width + bricksLayout.padding) + + bricksLayout.offset.left; + const brickY = + r * (bricksLayout.height + bricksLayout.padding) + + bricksLayout.offset.top; + + const newBrick = this.add.sprite(brickX, brickY, "brick"); + this.physics.add.existing(newBrick); + newBrick.body.setImmovable(true); + this.bricks.add(newBrick); + } + } + } +} -You can check the finished code for this lesson in the live demo below, and play with it to understand better how it works: +const config = { + type: Phaser.CANVAS, + width: 480, + height: 320, + scene: ExampleScene, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, + backgroundColor: "#eeeeee", + physics: { + default: "arcade", + }, +}; + +const game = new Phaser.Game(config); +``` -{{JSFiddleEmbed("https://jsfiddle.net/end3r/cck2b9e8/","","400")}} +{{EmbedLiveSample("compare your code", "", 480, , , , , "allow-modals")}} ## Next steps -Something is missing though. The ball goes through the bricks without stopping — we need proper [collision detection](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Collision_detection). +Something is missing though. The ball goes through the bricks without stopping—we need proper [collision detection](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Collision_detection). -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Game_over", "Games/Workflows/2D_Breakout_game_Phaser/Collision_detection")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Game_over", "Games/Tutorials/2D_breakout_game_Phaser/Collision_detection")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_phaser/buttons/index.md b/files/en-us/games/tutorials/2d_breakout_game_phaser/buttons/index.md index 6c10307c1eff218..dc0ad5072c95fa0 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_phaser/buttons/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_phaser/buttons/index.md @@ -5,87 +5,126 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Animations_and_tweens", "Games/Workflows/2D_Breakout_game_Phaser/Randomizing_gameplay")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Animations_and_tweens", "Games/Tutorials/2D_breakout_game_Phaser/Randomizing_gameplay")}} -This is the **15th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). You can find the source code as it should look after completing this lesson at [Gamedev-Phaser-Content-Kit/demos/lesson15.html](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/lesson15.html). +This is the **15th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). Instead of starting the game right away, we can leave that decision to the player by adding a Start button they can press. Let's investigate how to do that. -Instead of starting the game right away we can leave that decision to the player by adding a Start button they can press. Let's investigate how to do that. +## New properties -## New variables - -We will need a variable to store a boolean value representing whether the game is currently being played or not, and another one to represent our button. Add these lines below your other variable definitions: +We will need a property to store a boolean value representing whether the game is currently being played or not, and another one to represent our button. Add these lines below your other properties definitions: ```js -let playing = false; -let startButton; +class ExampleScene extends Phaser.Scene { + // ... previous property definitions ... + playing = false; + startButton; + // ... rest of the class ... +} ``` ## Loading the button spritesheet -We can load the button spritesheet the same way we loaded the ball's wobble animation. Add the following to the bottom of the `preload()` function: +We can load the button spritesheet the same way we loaded the ball's wobble animation. Add the following to the bottom of the `preload()` method: ```js -game.load.spritesheet("button", "img/button.png", 120, 40); +this.load.spritesheet("button", "img/button.png", { + frameWidth: 120, + frameHeight: 40, +}); ``` A single button frame is 120 pixels wide and 40 pixels high. -You also need to [grab the button spritesheet from GitHub](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/img/button.png), and save it in your `/img` directory. +You also need to [grab the button spritesheet](https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser/button.png), and save it in your `/img` directory. ## Adding the button to the game -Adding the new button to the game is done by using the `add.button` method. Add the following lines to the bottom of your `create()` function: +Adding the new button to the game is done by using the `add.sprite` method. Add the following lines to the bottom of your `create()` method: ```js -startButton = game.add.button( - game.world.width * 0.5, - game.world.height * 0.5, +this.startButton = this.add.sprite( + this.scale.width * 0.5, + this.scale.height * 0.5, "button", - startGame, - this, - 1, 0, - 2, ); -startButton.anchor.set(0.5); ``` -The `button()` method's parameters are as follows: +In addition to the parameters we passed to the other `add.sprite` calls (such as when we added the ball and paddle), this time we also pass the frame number, which is `0` in this case. This means that the first frame of the spritesheet will be used for the button's initial appearance. + +To make the button respond to various inputs such as mouse clicks, we need to add the following lines right after the previous `add.sprite` call: -- The button's x and y coordinates -- The name of the graphic asset to be displayed for the button -- A callback function that will be executed when the button is pressed -- A reference to `this` to specify the execution context -- The frames that will be used for the _over_, _out_ and _down_ events. +```js +this.startButton.setInteractive(); +this.startButton.on( + "pointerover", + () => { + this.startButton.setFrame(1); + }, + this, +); +this.startButton.on( + "pointerdown", + () => { + this.startButton.setFrame(2); + }, + this, +); +this.startButton.on( + "pointerout", + () => { + this.startButton.setFrame(0); + }, + this, +); +this.startButton.on( + "pointerup", + () => { + this.startGame(); + }, + this, +); +``` -> [!NOTE] -> The over event is the same as hover, out is when the pointer moves out of the button and down is when the button is pressed. +First, we call `setInteractive` on the button to make it respond to pointer events. Then we add the four event listeners to the button: -Now we need to define the `startGame()` function referenced in the code above: +- `pointerover`—when the pointer is over the button, we change the button's frame to `1`, the second frame of the spritesheet. +- `pointerdown`—when the button is pressed, we change the button's frame to `2`, the third frame of the spritesheet. +- `pointerout`—when the pointer moves out of the button, we change the button's frame back to `0`, the first frame of the spritesheet. +- `pointerup`—when the button is released, we call the `startGame` method to start the game. + +Now, we need to define the `startGame()` method referenced in the code above: ```js -function startGame() { - startButton.destroy(); - ball.body.velocity.set(150, -150); - playing = true; +class ExampleScene extends Phaser.Scene { + // ... + startGame() { + this.startButton.destroy(); + this.ball.body.setVelocity(150, -150); + this.playing = true; + } } ``` -When the button is pressed, we remove the button, sets the ball's initial velocity and set the `playing` variable to `true`. +When the button is pressed, we remove the button, set the ball's initial velocity, and set the `playing` property to `true`. -Finally for this section, go back into your `create()` function, find the `ball.body.velocity.set(150, -150);` line, and remove it. You only want the ball to move when the button is pressed, not before! +Finally for this section, go back into your `create` method, find the `this.ball.body.setVelocity(150, -150);` line, and remove it. You only want the ball to move when the button is pressed, not before! ## Keeping the paddle still before the game starts -It works as expected, but we can still move the paddle when the game hasn't started yet, which looks a bit silly. To stop this, we can take advantage of the `playing` variable and make the paddle movable only when the game has started. To do that, adjust the `update()` function like so: +It works as expected, but we can still move the paddle when the game hasn't started yet, which looks a bit silly. To stop this, we can take advantage of the `playing` property and make the paddle movable only when the game has started. To do that, adjust the `update()` method like so: ```js -function update() { - game.physics.arcade.collide(ball, paddle, ballHitPaddle); - game.physics.arcade.collide(ball, bricks, ballHitBrick); - if (playing) { - paddle.x = game.input.x || game.world.width * 0.5; +class ExampleScene extends Phaser.Scene { + // ... + update() { + // ... + if (this.playing) { + this.paddle.x = this.input.x || this.scale.width * 0.5; + } + // ... } + // ... } ``` @@ -93,12 +132,264 @@ That way the paddle is immovable after everything is loaded and prepared, but be ## Compare your code -You can check the finished code for this lesson in the live demo below, and play with it to understand better how it works: +Here's what you should have so far, running live. To view its source code, click the "Play" button. + +```html hidden + +``` + +```css hidden +* { + padding: 0; + margin: 0; +} +``` + +```js hidden +class ExampleScene extends Phaser.Scene { + ball; + paddle; + bricks; + + scoreText; + score = 0; + + lives = 3; + livesText; + lifeLostText; + + preload() { + this.load.setBaseURL( + "https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser", + ); + + this.load.image("ball", "ball.png"); + this.load.image("paddle", "paddle.png"); + this.load.image("brick", "brick.png"); + this.load.spritesheet("wobble", "wobble.png", { + frameWidth: 20, + frameHeight: 20, + }); + this.load.spritesheet("button", "button.png", { + frameWidth: 120, + frameHeight: 40, + }); + } + create() { + this.physics.world.checkCollision.down = false; + + this.ball = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 25, + "ball", + ); + this.physics.add.existing(this.ball); + this.ball.body.setCollideWorldBounds(true, 1, 1); + this.ball.body.setBounce(1); + this.ball.anims.create({ + key: "wobble", + frameRate: 24, + frames: this.anims.generateFrameNumbers("wobble", { + frames: [0, 1, 0, 2, 0, 1, 0, 2, 0], + }), + }); + + this.paddle = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 5, + "paddle", + ); + this.paddle.setOrigin(0.5, 1); + this.physics.add.existing(this.paddle); + this.paddle.body.setImmovable(true); + + this.initBricks(); + + const textStyle = { font: "18px Arial", fill: "#0095dd" }; + this.scoreText = this.add.text(5, 5, "Points: 0", textStyle); + + this.livesText = this.add.text( + this.scale.width - 5, + 5, + `Lives: ${this.lives}`, + textStyle, + ); + this.livesText.setOrigin(1, 0); + this.lifeLostText = this.add.text( + this.scale.width * 0.5, + this.scale.height * 0.5, + "Life lost, click to continue", + textStyle, + ); + this.lifeLostText.setOrigin(0.5, 0.5); + this.lifeLostText.visible = false; + + this.startButton = this.add.sprite( + this.scale.width * 0.5, + this.scale.height * 0.5, + "button", + 0, + ); + this.startButton.setInteractive(); + this.startButton.on( + "pointerover", + () => { + this.startButton.setFrame(1); + }, + this, + ); + this.startButton.on( + "pointerdown", + () => { + this.startButton.setFrame(2); + }, + this, + ); + this.startButton.on( + "pointerout", + () => { + this.startButton.setFrame(0); + }, + this, + ); + this.startButton.on( + "pointerup", + () => { + this.startGame(); + }, + this, + ); + } + update() { + this.physics.collide(this.ball, this.paddle, (ball, paddle) => + this.hitPaddle(ball, paddle), + ); + this.physics.collide(this.ball, this.bricks, (ball, brick) => + this.hitBrick(ball, brick), + ); + + if (this.playing) { + this.paddle.x = this.input.x || this.scale.width * 0.5; + } + + const ballIsOutOfBounds = !Phaser.Geom.Rectangle.Overlaps( + this.physics.world.bounds, + this.ball.getBounds(), + ); + if (ballIsOutOfBounds) { + this.ballLeaveScreen(); + } + if (this.bricks.countActive() === 0) { + alert("You won the game, congratulations!"); + location.reload(); + } + } + + startGame() { + this.startButton.destroy(); + this.ball.body.setVelocity(150, -150); + this.playing = true; + } + + initBricks() { + const bricksLayout = { + width: 50, + height: 20, + count: { + row: 3, + col: 7, + }, + offset: { + top: 50, + left: 60, + }, + padding: 10, + }; + + this.bricks = this.add.group(); + for (let c = 0; c < bricksLayout.count.col; c++) { + for (let r = 0; r < bricksLayout.count.row; r++) { + const brickX = + c * (bricksLayout.width + bricksLayout.padding) + + bricksLayout.offset.left; + const brickY = + r * (bricksLayout.height + bricksLayout.padding) + + bricksLayout.offset.top; + + const newBrick = this.add.sprite(brickX, brickY, "brick"); + this.physics.add.existing(newBrick); + newBrick.body.setImmovable(true); + this.bricks.add(newBrick); + } + } + } + + hitPaddle(ball, paddle) { + this.ball.anims.play("wobble"); + } + + hitBrick(ball, brick) { + const destroyTween = this.tweens.add({ + targets: brick, + ease: "Linear", + repeat: 0, + duration: 200, + props: { + scaleX: 0, + scaleY: 0, + }, + onComplete: () => { + brick.destroy(); + }, + }); + destroyTween.play(); + this.score += 10; + this.scoreText.setText(`Points: ${this.score}`); + } + + ballLeaveScreen() { + this.lives--; + if (this.lives > 0) { + this.livesText.setText(`Lives: ${this.lives}`); + this.lifeLostText.visible = true; + this.ball.body.reset(this.scale.width * 0.5, this.scale.height - 25); + this.input.once( + "pointerdown", + () => { + this.lifeLostText.visible = false; + this.ball.body.setVelocity(150, -150); + }, + this, + ); + } else { + // Game over logic + location.reload(); + } + } +} + +const config = { + type: Phaser.CANVAS, + width: 480, + height: 320, + scene: ExampleScene, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, + backgroundColor: "#eeeeee", + physics: { + default: "arcade", + }, +}; + +const game = new Phaser.Game(config); +``` -{{JSFiddleEmbed("https://jsfiddle.net/end3r/1rpj71k4/","","400")}} +{{EmbedLiveSample("compare your code", "", 480, , , , , "allow-modals")}} ## Next steps The last thing we will do in this article series is make the gameplay even more interesting by adding some [randomization](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Randomizing_gameplay) to the way the ball bounces off the paddle. -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Animations_and_tweens", "Games/Workflows/2D_Breakout_game_Phaser/Randomizing_gameplay")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Animations_and_tweens", "Games/Tutorials/2D_breakout_game_Phaser/Randomizing_gameplay")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_phaser/collision_detection/index.md b/files/en-us/games/tutorials/2d_breakout_game_phaser/collision_detection/index.md index 47bbd5a4826ff48..2bbaf7967e711b6 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_phaser/collision_detection/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_phaser/collision_detection/index.md @@ -5,46 +5,175 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Build_the_brick_field", "Games/Workflows/2D_Breakout_game_Phaser/The_score")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Build_the_brick_field", "Games/Tutorials/2D_breakout_game_Phaser/The_score")}} -This is the **10th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). You can find the source code as it should look after completing this lesson at [Gamedev-Phaser-Content-Kit/demos/lesson10.html](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/lesson10.html). - -Now onto the next challenge — the collision detection between the ball and the bricks. Luckily enough we can use the physics engine to check collisions not only between single objects (like the ball and the paddle), but also between an object and the group. +This is the **10th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). Now onto the next challenge—the collision detection between the ball and the bricks. Luckily enough, we can use the physics engine to check collisions not only between single objects (like the ball and the paddle), but also between an object and the group. ## Brick/Ball collision detection -The physics engine makes everything a lot easier — we just need to add two simple pieces of code. First, add a new line inside your `update()` function that checks for collision detection between ball and bricks, as shown below: +The physics engine makes everything a lot easier—we just need to add two simple pieces of code. First, add a new line inside your `update()` method that detects a collision between the ball and bricks, as shown below: ```js -function update() { - game.physics.arcade.collide(ball, paddle); - game.physics.arcade.collide(ball, bricks, ballHitBrick); - paddle.x = game.input.x || game.world.width * 0.5; +class ExampleScene extends Phaser.Scene { + // ... + update() { + this.physics.collide(this.ball, this.paddle); + this.physics.collide(this.ball, this.bricks, (ball, brick) => + this.hitBrick(ball, brick), + ); + this.paddle.x = this.input.x || this.scale.width * 0.5; + // ... + } + // ... } ``` -The ball's position is calculated against the positions of all the bricks in the group. The third, optional parameter is the function executed when a collision occurs — `ballHitBrick()`. Create this new function as the bottom of your code, just before the closing `` tag, as follows: +The ball's position is calculated against the positions of all the bricks in the group. The third, optional parameter is the function executed when a collision occurs. This function is called by Phaser with two arguments—the first one is the ball, which we explicitly passed to the collide method, and the second one is the single brick from the bricks group that the ball is colliding with. Here we implement the behavior in a method called `hitBrick()`. Create this new method at the end of the `ExampleScene` class, just before the closing brace `}`, as follows: ```js -function ballHitBrick(ball, brick) { - brick.kill(); +class ExampleScene extends Phaser.Scene { + // ... + hitBrick(ball, brick) { + brick.destroy(); + } } ``` -And that's it! Reload your code and you should see the new collision detection working just as required. - -Thanks to Phaser there are two parameters passed to the function — the first one is the ball, which we explicitly defined in the collide method, and the second one is the single brick from the bricks group that the ball is colliding with. Inside the function we remove the brick in question from the screen by running the `kill()` method on it. +And that's it! Reload your code, and you should see the new collision detection working just as required. -You would expect to have to write a lot more calculations of your own to implement collision detection when using [pure JavaScript](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Collision_detection). That's the beauty of using the framework — you can leave a lot of boring code to Phaser, and focus on the most fun and interesting parts of making a game. +You would expect to have to write a lot more calculations of your own to implement collision detection when using [pure JavaScript](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Collision_detection). That's the beauty of using the framework—you can leave a lot of boring code to Phaser, and focus on the most fun and interesting parts of making a game. ## Compare your code -You can check the finished code for this lesson in the live demo below, and play with it to understand better how it works: +Here's what you should have so far, running live. To view its source code, click the "Play" button. + +```html hidden + +``` + +```css hidden +* { + padding: 0; + margin: 0; +} +``` + +```js hidden +class ExampleScene extends Phaser.Scene { + ball; + paddle; + bricks; + + preload() { + this.load.setBaseURL( + "https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser", + ); + + this.load.image("ball", "ball.png"); + this.load.image("paddle", "paddle.png"); + this.load.image("brick", "brick.png"); + } + create() { + this.physics.world.checkCollision.down = false; + + this.ball = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 25, + "ball", + ); + this.physics.add.existing(this.ball); + this.ball.body.setVelocity(150, -150); + this.ball.body.setCollideWorldBounds(true, 1, 1); + this.ball.body.setBounce(1); + + this.paddle = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 5, + "paddle", + ); + this.paddle.setOrigin(0.5, 1); + this.physics.add.existing(this.paddle); + this.paddle.body.setImmovable(true); + + this.initBricks(); + } + update() { + this.physics.collide(this.ball, this.paddle); + this.physics.collide(this.ball, this.bricks, (ball, brick) => + this.hitBrick(ball, brick), + ); + + this.paddle.x = this.input.x || this.scale.width * 0.5; + const ballIsOutOfBounds = !Phaser.Geom.Rectangle.Overlaps( + this.physics.world.bounds, + this.ball.getBounds(), + ); + if (ballIsOutOfBounds) { + // Game over logic + location.reload(); + } + } + + initBricks() { + const bricksLayout = { + width: 50, + height: 20, + count: { + row: 3, + col: 7, + }, + offset: { + top: 50, + left: 60, + }, + padding: 10, + }; + + this.bricks = this.add.group(); + for (let c = 0; c < bricksLayout.count.col; c++) { + for (let r = 0; r < bricksLayout.count.row; r++) { + const brickX = + c * (bricksLayout.width + bricksLayout.padding) + + bricksLayout.offset.left; + const brickY = + r * (bricksLayout.height + bricksLayout.padding) + + bricksLayout.offset.top; + + const newBrick = this.add.sprite(brickX, brickY, "brick"); + this.physics.add.existing(newBrick); + newBrick.body.setImmovable(true); + this.bricks.add(newBrick); + } + } + } + + hitBrick(ball, brick) { + brick.destroy(); + } +} + +const config = { + type: Phaser.CANVAS, + width: 480, + height: 320, + scene: ExampleScene, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, + backgroundColor: "#eeeeee", + physics: { + default: "arcade", + }, +}; + +const game = new Phaser.Game(config); +``` -{{JSFiddleEmbed("https://jsfiddle.net/end3r/wwneakwf/","","400")}} +{{EmbedLiveSample("compare your code", "", 480, , , , , "allow-modals")}} ## Next steps -We can hit the bricks and remove them, which is a nice addition to the gameplay already. It would be even better to count the destroyed bricks increment [the score](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/The_score) as a result. +We can hit the bricks and remove them, which is a nice addition to the gameplay already. It would be even better to count the destroyed bricks and increment [the score](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/The_score) as a result. -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Build_the_brick_field", "Games/Workflows/2D_Breakout_game_Phaser/The_score")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Build_the_brick_field", "Games/Tutorials/2D_breakout_game_Phaser/The_score")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_phaser/extra_lives/index.md b/files/en-us/games/tutorials/2d_breakout_game_phaser/extra_lives/index.md index b10fb69c79cb255..c4426414996ae33 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_phaser/extra_lives/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_phaser/extra_lives/index.md @@ -5,133 +5,320 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Win_the_game", "Games/Workflows/2D_Breakout_game_Phaser/Animations_and_tweens")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Win_the_game", "Games/Tutorials/2D_breakout_game_Phaser/Animations_and_tweens")}} -This is the **13th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). You can find the source code as it should look after completing this lesson at [Gamedev-Phaser-Content-Kit/demos/lesson13.html](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/lesson13.html). +This is the **13th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). In this article, we'll implement a lives system, so that the player can continue playing until they have lost three lives, not just one, which makes the game enjoyable for longer. -We can make the game enjoyable for longer by adding lives. In this article we'll implement a lives system, so that the player can continue playing until they have lost three lives, not just one. +## New properties -## New variables - -Add the following new variables below the existing ones in your code: +Add the following new properties below the existing ones in your code: ```js -let lives = 3; -let livesText; -let lifeLostText; +class ExampleScene extends Phaser.Scene { + // ... previous property definitions ... + lives = 3; + livesText; + lifeLostText; + // ... rest of the class ... +} ``` -These respectively will store the number of lives, the text label that displays the number of lives that remain, and a text label that will be shown on screen when the player loses one of their lives. +These respectively will store the number of lives, the text label that displays the number of remaining lives, and a text label that will be shown on the screen when the player loses one of their lives. ## Defining the new text labels -Defining the texts look like something we already did in [the score](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/The_score) lesson. Add the following lines below the existing `scoreText` definition inside your `create()` function: +Defining the texts looks like something we already did in [the score](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/The_score) lesson. Add the following lines below the existing `scoreText` definition inside your `create()` method: ```js -livesText = game.add.text(game.world.width - 5, 5, `Lives: ${lives}`, { - font: "18px Arial", - fill: "#0095DD", -}); -livesText.anchor.set(1, 0); -lifeLostText = game.add.text( - game.world.width * 0.5, - game.world.height * 0.5, +this.livesText = this.add.text( + this.scale.width - 5, + 5, + `Lives: ${this.lives}`, + { font: "18px Arial", fill: "#0095dd" }, +); +this.livesText.setOrigin(1, 0); +this.lifeLostText = this.add.text( + this.scale.width * 0.5, + this.scale.height * 0.5, "Life lost, click to continue", - { font: "18px Arial", fill: "#0095DD" }, + { font: "18px Arial", fill: "#0095dd" }, ); -lifeLostText.anchor.set(0.5); -lifeLostText.visible = false; +this.lifeLostText.setOrigin(0.5, 0.5); +this.lifeLostText.visible = false; ``` -The `livesText` and `lifeLostText` objects look very similar to the `scoreText` one — they define a position on the screen, the actual text to display, and the font styling. The former is anchored on its top right edge to align properly with the screen, and the latter is centered, both using `anchor.set()`. +The `this.livesText` and `this.lifeLostText` objects look very similar to the `this.scoreText` one—they define a position on the screen, the actual text to display, and the font styling. The former is anchored on its top right edge to align properly with the screen, and the latter is centered, both using `setOrigin`. The `lifeLostText` will be shown only when the life is lost, so its visibility is initially set to `false`. ### Making our text styling DRY -As you probably noticed we're using the same styling for all three texts: `scoreText`, `livesText` and `lifeLostText`. If we ever want to change the font size or color we will have to do it in multiple places. To make it easier for us to maintain in the future we can create a separate variable that will hold our styling, let's call it `textStyle` and place it before the text definitions: +As you probably noticed, we're using the same styling for all three texts: `scoreText`, `livesText`, and `lifeLostText`. If we ever want to change the font size or color, we will have to do it in multiple places. To make it easier for us to maintain in the future, we can create a separate variable that will hold our styling, let's call it `textStyle` and place it before the text definitions: ```js -textStyle = { font: "18px Arial", fill: "#0095DD" }; +const textStyle = { font: "18px Arial", fill: "#0095dd" }; ``` -We can now use this variable when styling our text labels — update your code so that the multiple instances of the text styling are replaced with the variable: +We can now use this variable when styling our text labels—update your code so that the multiple instances of the text styling are replaced with the variable: ```js -scoreText = game.add.text(5, 5, "Points: 0", textStyle); -livesText = game.add.text( - game.world.width - 5, +this.scoreText = this.add.text(5, 5, "Points: 0", textStyle); + +this.livesText = this.add.text( + this.scale.width - 5, 5, - `Lives: ${lives}`, + `Lives: ${this.lives}`, textStyle, ); -livesText.anchor.set(1, 0); -lifeLostText = game.add.text( - game.world.width * 0.5, - game.world.height * 0.5, +this.livesText.setOrigin(1, 0); +this.lifeLostText = this.add.text( + this.scale.width * 0.5, + this.scale.height * 0.5, "Life lost, click to continue", textStyle, ); -lifeLostText.anchor.set(0.5); -lifeLostText.visible = false; +this.lifeLostText.setOrigin(0.5, 0.5); +this.lifeLostText.visible = false; ``` -This way changing the font in one variable will apply the changes to every place it is used. +This way, changing the font in one variable will apply the changes to every place it is used. ## The lives handling code -To implement lives in our game, let's first change the ball's function bound to the `onOutOfBounds` event. Instead of executing an anonymous function and showing the alert right away: +To implement lives in our game, let's first change the behavior when the ball gets out of bounds. Instead of restarting right away: ```js -ball.events.onOutOfBounds.add(() => { - alert("Game over!"); +if (ballIsOutOfBounds) { + // Game over logic location.reload(); -}, this); +} ``` -We will assign a new function called `ballLeaveScreen`; delete the previous event handler (shown above) and replace it with the following line: +We will call a new method called `ballLeaveScreen()`; delete the previous lines (shown above) and replace it with the following line: ```js -ball.events.onOutOfBounds.add(ballLeaveScreen, this); +if (ballIsOutOfBounds) { + this.ballLeaveScreen(); +} ``` -We want to decrease the number of lives every time the ball leaves the canvas. Add the `ballLeaveScreen()` function definition at the end of our code: +We want to decrease the number of lives every time the ball leaves the canvas. Add the `ballLeaveScreen()` method definition at the end of the `ExampleScene` class: ```js -function ballLeaveScreen() { - lives--; - if (lives) { - livesText.setText(`Lives: ${lives}`); - lifeLostText.visible = true; - ball.reset(game.world.width * 0.5, game.world.height - 25); - paddle.reset(game.world.width * 0.5, game.world.height - 5); - game.input.onDown.addOnce(() => { - lifeLostText.visible = false; - ball.body.velocity.set(150, -150); - }, this); - } else { - alert("You lost, game over!"); - location.reload(); +class ExampleScene extends Phaser.Scene { + // ... + ballLeaveScreen() { + this.lives--; + if (this.lives > 0) { + this.livesText.setText(`Lives: ${this.lives}`); + this.lifeLostText.visible = true; + this.ball.body.reset(this.scale.width * 0.5, this.scale.height - 25); + this.input.once( + "pointerdown", + () => { + this.lifeLostText.visible = false; + this.ball.body.setVelocity(150, -150); + }, + this, + ); + } else { + // Game over logic + location.reload(); + } } } ``` -Instead of instantly printing out the alert when you lose a life, we first subtract one life from the current number and check if it's a non-zero value. If yes, then the player still has some lives left and can continue to play — they will see the life lost message, the ball and paddle positions will be reset on screen and on the next input (click or touch) the message will be hidden and the ball will start to move again. +Instead of instantly printing out the alert when you lose a life, we first subtract one life from the current number and check if it's a non-zero value. If yes, then the player still has some lives left and can continue to play—they will see the life lost message, the ball and paddle positions will be reset on the screen, and on the next input (click or touch) the message will be hidden and the ball will start to move again. -When the number of available lives reaches zero, the game is over and the game over alert message will be shown. +When the number of available lives reaches zero, the game is over, and the game over alert message will be shown. ## Events -You have probably noticed the `add()` and `addOnce()` method calls in the above two code blocks and wondered how they differ. The difference is that the `add()` method binds the given function and causes it to be executed every time the event occurs, while `addOnce()` is useful when you want to have the bound function executed only once and then unbound so it is not executed again. In our case, on every `outOfBounds` event the `ballLeaveScreen` will be executed, but when the ball leaves the screen we only want to remove the message from the screen once. +You have probably noticed the `once` method call in the above code block and wondered what it is. The `once()` method is a Phaser event listener that listens for the next occurrence of the specified event (in this case, a pointer down event) and then removes itself after being triggered. This means that the code inside the callback will only run once after calling `once`, which is exactly what we want here—we want to hide the life lost message and start the ball movement again only once, after the player clicks or touches the screen. ## Compare your code -You can check the finished code for this lesson in the live demo below, and play with it to understand better how it works: +Here's what you should have so far, running live. To view its source code, click the "Play" button. + +```html hidden + +``` + +```css hidden +* { + padding: 0; + margin: 0; +} +``` + +```js hidden +class ExampleScene extends Phaser.Scene { + ball; + paddle; + bricks; + + scoreText; + score = 0; + + lives = 3; + livesText; + lifeLostText; + + preload() { + this.load.setBaseURL( + "https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser", + ); + + this.load.image("ball", "ball.png"); + this.load.image("paddle", "paddle.png"); + this.load.image("brick", "brick.png"); + } + create() { + this.physics.world.checkCollision.down = false; + + this.ball = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 25, + "ball", + ); + this.physics.add.existing(this.ball); + this.ball.body.setVelocity(150, -150); + this.ball.body.setCollideWorldBounds(true, 1, 1); + this.ball.body.setBounce(1); + + this.paddle = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 5, + "paddle", + ); + this.paddle.setOrigin(0.5, 1); + this.physics.add.existing(this.paddle); + this.paddle.body.setImmovable(true); + + this.initBricks(); + + const textStyle = { font: "18px Arial", fill: "#0095dd" }; + this.scoreText = this.add.text(5, 5, "Points: 0", textStyle); + + this.livesText = this.add.text( + this.scale.width - 5, + 5, + `Lives: ${this.lives}`, + textStyle, + ); + this.livesText.setOrigin(1, 0); + this.lifeLostText = this.add.text( + this.scale.width * 0.5, + this.scale.height * 0.5, + "Life lost, click to continue", + textStyle, + ); + this.lifeLostText.setOrigin(0.5, 0.5); + this.lifeLostText.visible = false; + } + update() { + this.physics.collide(this.ball, this.paddle); + this.physics.collide(this.ball, this.bricks, (ball, brick) => + this.hitBrick(ball, brick), + ); + + this.paddle.x = this.input.x || this.scale.width * 0.5; + const ballIsOutOfBounds = !Phaser.Geom.Rectangle.Overlaps( + this.physics.world.bounds, + this.ball.getBounds(), + ); + if (ballIsOutOfBounds) { + this.ballLeaveScreen(); + } + if (this.bricks.countActive() === 0) { + alert("You won the game, congratulations!"); + location.reload(); + } + } + + initBricks() { + const bricksLayout = { + width: 50, + height: 20, + count: { + row: 3, + col: 7, + }, + offset: { + top: 50, + left: 60, + }, + padding: 10, + }; + + this.bricks = this.add.group(); + for (let c = 0; c < bricksLayout.count.col; c++) { + for (let r = 0; r < bricksLayout.count.row; r++) { + const brickX = + c * (bricksLayout.width + bricksLayout.padding) + + bricksLayout.offset.left; + const brickY = + r * (bricksLayout.height + bricksLayout.padding) + + bricksLayout.offset.top; + + const newBrick = this.add.sprite(brickX, brickY, "brick"); + this.physics.add.existing(newBrick); + newBrick.body.setImmovable(true); + this.bricks.add(newBrick); + } + } + } + + hitBrick(ball, brick) { + brick.destroy(); + this.score += 10; + this.scoreText.setText(`Points: ${this.score}`); + } + + ballLeaveScreen() { + this.lives--; + if (this.lives > 0) { + this.livesText.setText(`Lives: ${this.lives}`); + this.lifeLostText.visible = true; + this.ball.body.reset(this.scale.width * 0.5, this.scale.height - 25); + this.input.once( + "pointerdown", + () => { + this.lifeLostText.visible = false; + this.ball.body.setVelocity(150, -150); + }, + this, + ); + } else { + // Game over logic + location.reload(); + } + } +} + +const config = { + type: Phaser.CANVAS, + width: 480, + height: 320, + scene: ExampleScene, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, + backgroundColor: "#eeeeee", + physics: { + default: "arcade", + }, +}; + +const game = new Phaser.Game(config); +``` -{{JSFiddleEmbed("https://jsfiddle.net/end3r/yk1c5n0b/","","400")}} +{{EmbedLiveSample("compare your code", "", 480, , , , , "allow-modals")}} ## Next steps -Lives made the game more forgiving — if you lose one life, you still have two more left and can continue to play. Now let's expand the look and feel of the game by adding [animations and tweens](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Animations_and_tweens). +Lives made the game more forgiving—if you lose one life, you still have two more left and can continue to play. Now let's expand the look and feel of the game by adding [animations and tweens](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Animations_and_tweens). -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Win_the_game", "Games/Workflows/2D_Breakout_game_Phaser/Animations_and_tweens")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Win_the_game", "Games/Tutorials/2D_breakout_game_Phaser/Animations_and_tweens")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_phaser/game_over/index.md b/files/en-us/games/tutorials/2d_breakout_game_phaser/game_over/index.md index 63f30b0336d2e28..ba1b8e108058079 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_phaser/game_over/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_phaser/game_over/index.md @@ -5,40 +5,125 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Player_paddle_and_controls", "Games/Workflows/2D_Breakout_game_Phaser/Build_the_brick_field")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Player_paddle_and_controls", "Games/Tutorials/2D_breakout_game_Phaser/Build_the_brick_field")}} -This is the **8th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). You can find the source code as it should look after completing this lesson at [Gamedev-Phaser-Content-Kit/demos/lesson08.html](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/lesson08.html). - -To make the game more interesting we can introduce the ability to lose — if you don't hit the ball before it reaches the bottom edge of the screen it will be game over. +This is the **8th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). To make the game more interesting, we can introduce the ability to lose—if you don't hit the ball before it reaches the bottom edge of the screen, it will be game over. ## How to lose -To provide the ability to lose, we will disable the ball's collision with the bottom edge of the screen. Add the code below inside the `create()` function; just after you define the ball's attributes is fine: +To provide the ability to lose, we will disable the ball's collision with the bottom edge of the screen. Add the code below inside the `create()` method, at the very top: ```js -game.physics.arcade.checkCollision.down = false; +this.physics.world.checkCollision.down = false; ``` -This will make the three walls (top, left and right) bounce the ball back, but the fourth (bottom) will disappear, letting the ball fall off the screen if the paddle misses it. We need a way to detect this and act accordingly. Add the following lines just below the previous new one: +This will make the three walls (top, left, and right) bounce the ball back, but the fourth (bottom) will disappear, letting the ball fall off the screen if the paddle misses it. We need a way to detect this and act accordingly. Add the following lines at the bottom of the `update()` method: ```js -ball.checkWorldBounds = true; -ball.events.onOutOfBounds.add(() => { +const ballIsOutOfBounds = !Phaser.Geom.Rectangle.Overlaps( + this.physics.world.bounds, + this.ball.getBounds(), +); +if (ballIsOutOfBounds) { + // Game over logic alert("Game over!"); location.reload(); -}, this); +} ``` -Adding those lines will make the ball check the world (in our case canvas) bounds and execute the function bound to the `onOutOfBounds` event. When you click on the resulting alert, the page will be reloaded so you can play again. +Adding those lines will check if the ball sticks out of the world (in our case, the canvas) bounds and then show an alert. When you click on the resulting alert, the page will reload, allowing you to play again. + +> [!NOTE] +> The UX here is quite sketchy because [`alert()`](/en-US/docs/Web/API/Window/alert) shows a system dialog and blocks the game. In a real game, you would probably want to design your own modal dialog using {{HTMLElement("dialog")}}. +> +> Also, we will later add a ["Start" button](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Buttons), but here our game starts immediately when the page loads, so you may "lose" before you even start playing. To prevent the annoying dialog, we will remove the `alert()` call from now on. ## Compare your code -You can check the finished code for this lesson in the live demo below, and play with it to understand better how it works: +Here's what you should have so far, running live. To view its source code, click the "Play" button. + +```html hidden + +``` + +```css hidden +* { + padding: 0; + margin: 0; +} +``` + +```js hidden +class ExampleScene extends Phaser.Scene { + ball; + paddle; + + preload() { + this.load.setBaseURL( + "https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser", + ); + + this.load.image("ball", "ball.png"); + this.load.image("paddle", "paddle.png"); + } + create() { + this.physics.world.checkCollision.down = false; + + this.ball = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 25, + "ball", + ); + this.physics.add.existing(this.ball); + this.ball.body.setVelocity(150, -150); + this.ball.body.setCollideWorldBounds(true, 1, 1); + this.ball.body.setBounce(1); + + this.paddle = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 5, + "paddle", + ); + this.paddle.setOrigin(0.5, 1); + this.physics.add.existing(this.paddle); + this.paddle.body.setImmovable(true); + } + update() { + this.physics.collide(this.ball, this.paddle); + this.paddle.x = this.input.x || this.scale.width * 0.5; + const ballIsOutOfBounds = !Phaser.Geom.Rectangle.Overlaps( + this.physics.world.bounds, + this.ball.getBounds(), + ); + if (ballIsOutOfBounds) { + // Game over logic + location.reload(); + } + } +} + +const config = { + type: Phaser.CANVAS, + width: 480, + height: 320, + scene: ExampleScene, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, + backgroundColor: "#eeeeee", + physics: { + default: "arcade", + }, +}; + +const game = new Phaser.Game(config); +``` -{{JSFiddleEmbed("https://jsfiddle.net/end3r/436bckb7/","","400")}} +{{EmbedLiveSample("compare your code", "", 480, , , , , "allow-modals")}} ## Next steps -Now the basic gameplay is in place let's make it more interesting by introducing bricks to smash — it's time to [build the brick field](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Build_the_brick_field). +Now the basic gameplay is in place, let's make it more interesting by introducing bricks to smash—it's time to [build the brick field](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Build_the_brick_field). -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Player_paddle_and_controls", "Games/Workflows/2D_Breakout_game_Phaser/Build_the_brick_field")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Player_paddle_and_controls", "Games/Tutorials/2D_breakout_game_Phaser/Build_the_brick_field")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_phaser/index.md b/files/en-us/games/tutorials/2d_breakout_game_phaser/index.md index 53608e2d1bba890..74a8f8e4e94c35d 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_phaser/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_phaser/index.md @@ -5,19 +5,19 @@ page-type: guide sidebar: games --- -{{Next("Games/Workflows/2D_Breakout_game_Phaser/Initialize_the_framework")}} +{{Next("Games/Tutorials/2D_breakout_game_Phaser/Initialize_the_framework")}} In this step-by-step tutorial, we create a simple mobile **MDN Breakout** game written in JavaScript, using the [Phaser](https://phaser.io/) framework. Every step has editable, live samples available to play with, so you can see what the intermediate stages should look like. You will learn the basics of using the Phaser framework to implement fundamental game mechanics like rendering and moving images, collision detection, control mechanisms, framework-specific helper functions, animations and tweens, and winning and losing states. -To get the most out of this series of articles you should already have basic to intermediate [JavaScript](/en-US/docs/Learn_web_development/Getting_started/Your_first_website/Adding_interactivity) knowledge. After working through this tutorial, you should be able to build your own simple Web games with Phaser. +To get the most out of this series of articles, you should already have basic to intermediate [JavaScript](/en-US/docs/Learn_web_development/Getting_started/Your_first_website/Adding_interactivity) knowledge. After working through this tutorial, you should be able to build your own simple Web games with Phaser. ![Gameplay screen from the game MDN Breakout created with Phaser where you can use your paddle to bounce the ball and destroy the brick field, with keeping the points and lives.](mdn-breakout-phaser.png) ## Lesson details -All the lessons — and the different versions of the [MDN Breakout game](https://end3r.github.io/Gamedev-Phaser-Content-Kit/demos/lesson16.html) we are building together — are [available on GitHub](https://end3r.github.io/Gamedev-Phaser-Content-Kit/demos/): +All the lessons—and the different versions of the [MDN Breakout game](https://end3r.github.io/Gamedev-Phaser-Content-Kit/demos/lesson16.html) we are building together—are [available on GitHub](https://end3r.github.io/Gamedev-Phaser-Content-Kit/demos/): 1. [Initialize the framework](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Initialize_the_framework) 2. [Scaling](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Scaling) @@ -36,15 +36,12 @@ All the lessons — and the different versions of the [MDN Breakout game](https: 15. [Buttons](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Buttons) 16. [Randomizing gameplay](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Randomizing_gameplay) -As a note on learning paths — starting with pure JavaScript is the best way to get a solid knowledge of web game development. If you are not already familiar with pure JavaScript game development, we would suggest that you first work through this series' counterpart, [2D breakout game using pure JavaScript](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript). +As a note on learning paths—starting with pure JavaScript is the best way to get a solid knowledge of web game development. If you are not already familiar with pure JavaScript game development, we would suggest that you first work through this series' counterpart, [2D breakout game using pure JavaScript](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript). -After that, you can pick any framework you like and use it for your projects; we have chosen Phaser as it is a good solid framework, with a good support and community available, and a good set of plugins. Frameworks speed up development time and help take care of the boring parts, allowing you to concentrate on the fun stuff. However, frameworks are not always perfect, so if something unexpected happens or you want to write some functionality that the framework does not provide, you will need some pure JavaScript knowledge. - -> [!NOTE] -> This series of articles can be used as material for hands-on game development workshops. You can also make use of the [Gamedev Phaser Content Kit](https://github.com/end3r/Gamedev-Phaser-Content-Kit) based on this tutorial if you want to give a talk about game development with Phaser. +After that, you can pick any framework you like and use it for your projects; we have chosen Phaser as it has good browser support, an active community, and a good set of plugins. Frameworks speed up development time and help take care of the boring parts, allowing you to concentrate on the fun stuff. However, frameworks are not always perfect, so if something unexpected happens or you want to write some functionality that the framework does not provide, you will need some pure JavaScript knowledge. ## Next steps -Ok, let us get started! Head to the first part of the series — [Initialize the framework](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Initialize_the_framework). +Ok, let us get started! Head to the first part of the series—[Initialize the framework](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Initialize_the_framework). -{{Next("Games/Workflows/2D_Breakout_game_Phaser/Initialize_the_framework")}} +{{Next("Games/Tutorials/2D_breakout_game_Phaser/Initialize_the_framework")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_phaser/initialize_the_framework/index.md b/files/en-us/games/tutorials/2d_breakout_game_phaser/initialize_the_framework/index.md index dda2f0b185acc75..87da8a1b5752517 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_phaser/initialize_the_framework/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_phaser/initialize_the_framework/index.md @@ -5,11 +5,9 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser", "Games/Workflows/2D_Breakout_game_Phaser/Scaling")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser", "Games/Tutorials/2D_breakout_game_Phaser/Scaling")}} -This is the first of 16 tutorials to learn how to use [Gamedev Phaser](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). After completing this tutorial you can find the source code for this section at [Gamedev-Phaser-Content-Kit/demos/lesson01.html](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/lesson01.html). - -Before we can start writing the game's functionality, we need to create a basic structure to render the game inside. This can be done using HTML — the Phaser framework will generate the required {{htmlelement("canvas")}} element. +This is the first of 16 tutorials to learn how to use [Gamedev Phaser](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). Before we can start writing the game's functionality, we need to create a basic structure to render the game inside. This can be done using HTML—the Phaser framework will generate the required {{htmlelement("canvas")}} element. ## The game's HTML @@ -28,53 +26,100 @@ The HTML document structure is quite simple, as the game will be rendered entire } + - - - + ``` +And create a new `js` directory in the same location as your `index.html` file, and create a new file called `script.js` inside it. This is where we will write the JavaScript code that controls the game. Initially it should contain the following: + +```js +class ExampleScene extends Phaser.Scene { + preload() {} + create() {} + update() {} +} + +const config = { + type: Phaser.CANVAS, + width: 480, + height: 320, + scene: ExampleScene, +}; + +const game = new Phaser.Game(config); +``` + ## Downloading the Phaser code -Next, we need to go through the process of downloading the Phaser source code and applying it to our HTML document. This tutorial uses Phaser V2 — it won't work with the current version on Phaser (V3). The V2 library is still available on the Phaser download page, below the links for the V3 download. +Next, we need to go through the process of downloading the Phaser source code and applying it to our HTML document. This tutorial uses Phaser v3 (v3.90.0 as of the time of writing, though newer minor versions should work the same). 1. Go to the [Phaser download page](https://phaser.io/download/stable). -2. Choose an option that suits you best — we recommend the _min.js_ option as it keeps the source code smaller, and you are unlikely to go through the source code anyway. **Please make sure to use Phaser version 2 as that's what this tutorial was written for.** -3. Save the Phaser code inside a `/js` directory in the same location as your `index.html` file. -4. Update the `src` value of the first {{htmlelement("script")}} element as shown above. +2. Choose an option that suits you best—we recommend the _phaser.min.js_ option as it keeps the source code smaller, and you are unlikely to go through the source code anyway. +3. Save the Phaser code in the `js` directory. If you use another file name, make sure to update the `src` value of the first {{htmlelement("script")}} element in the HTML accordingly. ## Walking through what we have so far -At this point we have a `charset` defined, {{htmlelement("title")}} and some basic CSS in the header to reset the default `margin` and `padding`. We also have a {{htmlelement("script")}} element to apply the Phaser source code to the page. The body contains a second {{htmlelement("script")}} element, where we will write the JavaScript code to render the game and control it. +At this point, we have a `charset` defined, {{htmlelement("title")}}, and some basic CSS in the header to reset the default `margin` and `padding`. We also have a {{htmlelement("script")}} element to apply the Phaser source code to the page. The body contains a second {{htmlelement("script")}} element, where we will write the JavaScript code to render the game and control it. -The {{htmlelement("canvas")}} element is generated automatically by the framework. We are initializing it by creating a new `Phaser.Game` object and assigning it to the game variable. The parameters are: +The {{htmlelement("canvas")}} element is generated automatically by the framework. We are initializing it by creating a new `Phaser.Game` object and assigning it to the `game` variable. The parameters are: +- The rendering method. The available options are `AUTO`, `CANVAS`, `WEBGL`, `HEADLESS`. We can set either `CANVAS` or `WEBGL` explicitly or use `AUTO` to let Phaser decide which one to use. It usually uses WebGL if available in the browser, falling back to Canvas 2D if not. The last option, `HEADLESS`, is used for server-side rendering or testing, which is not relevant for this tutorial. - The width and height to set the {{htmlelement("canvas")}} to. -- The rendering method. The three options are `AUTO`, `CANVAS` and `WEBGL`. We can set one of the latter two explicitly or use `AUTO` to let Phaser decide which one to use. It usually uses WebGL if available in the browser, falling back to Canvas 2D if not. -- The `id` of the {{htmlelement("canvas")}} to use for rendering if one already exists on the page (we've specified null because we want Phaser to create its own.) -- The names to use for Phaser's three key functions that load and start the game, and update the game loop on every frame; we will use the same names to keep it clean. +- The scene to add to the game. In this case, we are creating a new class called `ExampleScene` that extends `Phaser.Scene`. This class implements the methods that Phaser calls at different stages of the game lifecycle. We'll fill in these methods among them later: - `preload` takes care of preloading the assets - `create` is executed once when everything is loaded and ready - `update` is executed on every frame. +## Running the application + +To run the app, you cannot directly open the `index.html` file, because later we will be loading external assets, which will be blocked by the browser's [same-origin policy](/en-US/docs/Web/Security/Same-origin_policy). + +To fix the problem, you need to run a local web server to serve the HTML files and the image files. [As the official document of Phaser suggests](https://docs.phaser.io/phaser/getting-started/set-up-dev-environment#installing-a-web-server), we have a lot of options to run a local web server. We also have our own [tutorials for setting up a local server](/en-US/docs/Learn_web_development/Howto/Tools_and_setup/set_up_a_local_testing_server)—use any option you prefer. For example, if you choose to use the Python HTTP server, then open a terminal, navigate to the directory where your `index.html` file is located, and run the following command: + +```bash +python3 -m http.server +``` + +This will start a simple HTTP server on port 8000. Then, open your web browser and navigate to `http://localhost:8000/index.html`. + ## Compare your code -Here's the full source code of the first lesson, running live in a JSFiddle: +Here's what you should have so far, running live. To view its source code, click the "Play" button. + +```html hidden + +``` + +```css hidden +* { + padding: 0; + margin: 0; +} +``` + +```js hidden +class ExampleScene extends Phaser.Scene { + preload() {} + create() {} + update() {} +} + +const config = { + type: Phaser.CANVAS, + width: 480, + height: 320, + scene: ExampleScene, +}; + +const game = new Phaser.Game(config); +``` -{{JSFiddleEmbed("https://jsfiddle.net/end3r/h6cwzv2b/","","400")}} +{{EmbedLiveSample("compare your code", "", 480, , , , , "allow-modals")}} ## Next steps Now we've set up the basic HTML and learned a bit about Phaser initialization, let's continue to the second lesson and learn about [scaling](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Scaling). -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser", "Games/Workflows/2D_Breakout_game_Phaser/Scaling")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser", "Games/Tutorials/2D_breakout_game_Phaser/Scaling")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_phaser/load_the_assets_and_print_them_on_screen/index.md b/files/en-us/games/tutorials/2d_breakout_game_phaser/load_the_assets_and_print_them_on_screen/index.md index 7cf288c8e62109d..93ede0ba8f05668 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_phaser/load_the_assets_and_print_them_on_screen/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_phaser/load_the_assets_and_print_them_on_screen/index.md @@ -5,56 +5,102 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Scaling", "Games/Workflows/2D_Breakout_game_Phaser/Move_the_ball")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Scaling", "Games/Tutorials/2D_breakout_game_Phaser/Move_the_ball")}} -This is the **3rd step** out of 16 in the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). You can find the source code as it should look after completing this lesson at [Gamedev-Phaser-Content-Kit/demos/lesson03.html](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/lesson03.html). - -Our game will feature a ball rolling around the screen, bouncing off a paddle, and destroying bricks to earn points — familiar, huh? In this article we'll look at how to add sprites into our gameworld. +This is the **3rd step** out of 16 in the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). In this article, we'll look at how to add sprites into our gameworld. Our game will feature a ball rolling around the screen, bouncing off a paddle, and destroying bricks to earn points—familiar, huh? ## Having a ball -Let's start by creating a JavaScript variable to represent our ball. Add the following line between the game initialization code (our `const game` block) and the `preload()` function: +Let's start by adding a property representing our ball to the `ExampleScene` class. Add the following line just after the opening line, inside the class body: ```js -let ball; +class ExampleScene extends Phaser.Scene { + ball; + // ... +} ``` -> [!NOTE] -> For the sake of this tutorial, we will use global variables. The purpose of the tutorial is to teach Phaser-specific approaches to game development rather than dwelling on subjective best approaches. - ## Loading the ball sprite -Loading images and printing them on our canvas is a lot easier using Phaser than using pure JavaScript. To load the asset, we will use the `game` object created by Phaser, executing its `load.image()` method. Add the following new line just inside the `preload()` function, at the bottom: +Loading images and printing them on our canvas is a lot easier using Phaser than using pure JavaScript. To load the asset, we will use the `Phaser.Scene`'s `load.image()` method, available as `this.load.image`. Add the following new line inside the `preload()` method: ```js -function preload() { - // … - game.load.image("ball", "img/ball.png"); +class ExampleScene extends Phaser.Scene { + // ... + preload() { + this.load.image("ball", "img/ball.png"); + } } ``` -The first parameter we want to give the asset is the name that will be used across our game code — for example, in our `ball` variable name — so we need to make sure it is the same. The second parameter is the relative path to the graphic asset. In our case, we will load the image for our ball. (Note that the file name does not also have to be the same, but we'd recommend it, as it makes everything easier to follow.) +The first parameter gives the asset its name that will be used across our game code. For consistency, use the same name as the backing property, which is `ball`. The second parameter is the relative path to the graphic asset. In our case, we will load the image for our ball. (Note that the file name does not need to be called `ball`, but we'd recommend it, as it makes everything easier to follow.) -Of course, to load the image, it must be available in our code directory. [Grab the ball image from GitHub](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/img/ball.png), and save it inside an `/img` directory in the same place as your `index.html` file. +Of course, to load the image, it must be available in our code directory. [Grab the ball image from our assets website](https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser/ball.png), and save it inside an `/img` directory in the same place as your `index.html` file. -Now, to show it on the screen we will use another Phaser method called `add.sprite()`; add the following new code line inside the `create()` function as shown: +Now, to show it on the screen, we will use another `Phaser.Scene`'s method called `add.sprite()`; add the following new line inside the `create()` method: ```js -function create() { - ball = game.add.sprite(50, 50, "ball"); +class ExampleScene extends Phaser.Scene { + // ... + create() { + this.ball = this.add.sprite(50, 50, "ball"); + } + // ... } ``` -This will add the ball to the game and render it on the screen. The first two parameters are the x and y coordinates of the canvas where you want it added, and the third one is the name of the asset we defined earlier. That's it — if you load your `index.html` file you will see the image already loaded and rendered on the canvas! +This will add the ball to the game and render it on the screen. The first two parameters are the x and y coordinates of the canvas where you want it added, and the third one is the name of the asset we defined earlier. That's it—if you load your `index.html` file, you will see the image already loaded and rendered on the canvas! ## Compare your code -You can check the finished code for this lesson for yourself in the live demo below, and play with it to better understand how it works: +Here's what you should have so far, running live. To view its source code, click the "Play" button. + +```html hidden + +``` + +```css hidden +* { + padding: 0; + margin: 0; +} +``` + +```js hidden +class ExampleScene extends Phaser.Scene { + ball; + preload() { + this.load.setBaseURL( + "https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser", + ); + + this.load.image("ball", "ball.png"); + } + create() { + this.ball = this.add.sprite(50, 50, "ball"); + } + update() {} +} + +const config = { + type: Phaser.CANVAS, + width: 480, + height: 320, + scene: ExampleScene, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, + backgroundColor: "#eeeeee", +}; + +const game = new Phaser.Game(config); +``` -{{JSFiddleEmbed("https://jsfiddle.net/end3r/98xrv9x5/","","400")}} +{{EmbedLiveSample("compare your code", "", 480, , , , , "allow-modals")}} ## Next steps -Printing out the ball was easy; next, we'll try [moving the ball](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Move_the_ball) on screen. +Printing out the ball was easy; next, we'll try [moving the ball](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Move_the_ball) on the screen. -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Scaling", "Games/Workflows/2D_Breakout_game_Phaser/Move_the_ball")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Scaling", "Games/Tutorials/2D_breakout_game_Phaser/Move_the_ball")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_phaser/move_the_ball/index.md b/files/en-us/games/tutorials/2d_breakout_game_phaser/move_the_ball/index.md index 92e844cf794ce66..bcd5b615c3d86fc 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_phaser/move_the_ball/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_phaser/move_the_ball/index.md @@ -5,20 +5,21 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Load_the_assets_and_print_them_on_screen", "Games/Workflows/2D_Breakout_game_Phaser/Physics")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Load_the_assets_and_print_them_on_screen", "Games/Tutorials/2D_breakout_game_Phaser/Physics")}} -This is the **4th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). You can find the source code as it should look after completing this lesson at [Gamedev-Phaser-Content-Kit/demos/lesson04.html](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/lesson04.html). - -We have our blue ball printed on screen, but it's doing nothing — It would be cool to make it move somehow. This article covers how to do just that. +This is the **4th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). We have our blue ball printed on screen, but it's doing nothing—it would be cool to make it move somehow. This article covers how to do just that. ## Updating the ball's position on each frame -Remember the `update()` function and its definition? The code inside it is executed on every frame, so it's a perfect place to put the code that will update the ball's position on screen. Add the following new lines of the code inside `update()`, as shown: +Remember the `update()` method and its definition? The code inside it is executed on every frame, so it's a perfect place to put the code that will update the ball's position on screen. Add the following new lines inside `update()`, as shown: ```js -function update() { - ball.x += 1; - ball.y += 1; +class ExampleScene extends Phaser.Scene { + // ... + update() { + this.ball.x += 1; + this.ball.y += 1; + } } ``` @@ -26,14 +27,60 @@ The code above adds 1 to the `x` and `y` properties representing the ball coordi ## Compare your code -You can check the finished code for this lesson in the live demo below, and play with it to understand better how it works: +Here's what you should have so far, running live. To view its source code, click the "Play" button. + +```html hidden + +``` + +```css hidden +* { + padding: 0; + margin: 0; +} +``` + +```js hidden +class ExampleScene extends Phaser.Scene { + ball; + + preload() { + this.load.setBaseURL( + "https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser", + ); + + this.load.image("ball", "ball.png"); + } + create() { + this.ball = this.add.sprite(50, 50, "ball"); + } + update() { + this.ball.x += 1; + this.ball.y += 1; + } +} + +const config = { + type: Phaser.CANVAS, + width: 480, + height: 320, + scene: ExampleScene, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, + backgroundColor: "#eeeeee", +}; + +const game = new Phaser.Game(config); +``` -{{JSFiddleEmbed("https://jsfiddle.net/end3r/g1cfp0vv/","","400")}} +{{EmbedLiveSample("compare your code", "", 480, , , , , "allow-modals")}} ## Next steps -The next step is to add some basic collision detection, so our ball can bounce off the walls. This would take several lines of code — a significantly more complex step than we have seen so far, especially if we want to add paddle and brick collisions too — but fortunately Phaser allows us to do this much more easily than if we wanted to use pure JavaScript. +The next step is to add some basic collision detection, so our ball can bounce off the walls. This would take several lines of code—a significantly more complex step than we have seen so far, especially if we want to add paddle and brick collisions too—but fortunately, Phaser allows us to do this much more easily than if we wanted to use pure JavaScript. -In any case, before we do all that we will first introduce Phaser's [physics](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Physics) engines, and do some setup work. +In any case, before we do all that, we will first introduce Phaser's [physics](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Physics) engines and do some setup work. -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Load_the_assets_and_print_them_on_screen", "Games/Workflows/2D_Breakout_game_Phaser/Physics")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Load_the_assets_and_print_them_on_screen", "Games/Tutorials/2D_breakout_game_Phaser/Physics")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_phaser/physics/index.md b/files/en-us/games/tutorials/2d_breakout_game_phaser/physics/index.md index 03e0d0345e1de77..c39732960693424 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_phaser/physics/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_phaser/physics/index.md @@ -5,88 +5,114 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Move_the_ball", "Games/Workflows/2D_Breakout_game_Phaser/Bounce_off_the_walls")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Move_the_ball", "Games/Tutorials/2D_breakout_game_Phaser/Bounce_off_the_walls")}} -This is the **5th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). You can find the source code as it should look after completing this lesson at [Gamedev-Phaser-Content-Kit/demos/lesson05.html](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/lesson05.html). - -For proper collision detection between objects in our game we will need to have physics; this article introduces you to what's available in Phaser, as well as demonstrating a typical simple setup. +This is the **5th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). For proper collision detection between objects in our game, we will need to have physics; this article introduces you to what's available in Phaser, as well as demonstrating a typical simple setup. ## Adding physics -Phaser is bundled with three different physics engines — Arcade Physics, P2 and Ninja Physics — with a fourth option, Box2D, being available as a commercial plugin. For simple games like ours, we can use the Arcade Physics engine. We don't need any heavy geometry calculations — after all it's just a ball bouncing off walls and bricks. +Phaser is bundled with three different physics engines—Arcade Physics, Impact Physics, and Matter.js Physics—with the fourth option, Box2D, available as a commercial plugin. For simple games like ours, we can use the Arcade Physics engine. We don't need any heavy geometry calculations—after all, it's just a ball bouncing off walls and bricks. -First, let's initialize the Arcade Physics engine in our game. Add the `physics.startSystem()` method at the beginning of the `create` function (make it the first line inside the function), as shown below: +First, let's configure the Arcade Physics engine in our game. Add the `physics` property to the `config` object, as shown below: ```js -game.physics.startSystem(Phaser.Physics.ARCADE); +const config = { + // ... + physics: { + default: "arcade", + }, +}; ``` -Next, we need to enable our ball for the physics system — Phaser object physics is not enabled by default. Add the following line at the bottom of the `create()` function: +Next, we need to enable our ball for the physics system—Phaser object physics is not enabled by default. Add the following line at the bottom of the `create()` method: ```js -game.physics.enable(ball, Phaser.Physics.ARCADE); +this.physics.add.existing(this.ball); ``` Next, if we want to move our ball on the screen, we can set `velocity` on its `body`. Add the following line, again at the bottom of `create()`: ```js -ball.body.velocity.set(150, 150); +this.ball.body.setVelocity(150, 150); ``` ## Removing our previous update instructions -Remember to remove our old method of adding values to `x` and `y` from the `update()` function: +Remember to remove our old method of adding values to `x` and `y` from the `update()` method, i.e., restore it to the empty state: ```js -function update() { - ball.x += 1; - ball.y += 1; +class ExampleScene extends Phaser.Scene { + // ... + update() {} } ``` -we are now handling this properly, with a physics engine. +We are now handling this properly, with a physics engine. -## Final code check +Try reloading `index.html` again. At the moment, the physics engine has no gravity or friction, so the ball will go in the given direction at a constant speed. -The latest code should look like this: +## Fun with physics -```js -let ball; - -function preload() { - game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL; - game.scale.pageAlignHorizontally = true; - game.scale.pageAlignVertically = true; - game.stage.backgroundColor = "#eee"; - game.load.image("ball", "img/ball.png"); -} +You can do much more with physics, for example by adding `this.ball.body.gravity.y = 500;` inside `create()`, you will set the vertical gravity of the ball. Try changing the velocity to `this.ball.body.setVelocity(150, -150);`, and you will see the ball launched upwards, but then fall due to the effects of gravity pulling it down. -function create() { - game.physics.startSystem(Phaser.Physics.ARCADE); - ball = game.add.sprite(50, 50, "ball"); - game.physics.enable(ball, Phaser.Physics.ARCADE); - ball.body.velocity.set(150, 150); -} +This kind of functionality is just the tip of the iceberg—there are various functions and variables that can help you manipulate the physics objects. Check out the official [physics documentation](https://docs.phaser.io/phaser/concepts/physics/arcade) and see the [huge collection of examples](https://phaser.io/examples/v3.85.0/physics) using the Arcade and Matter.js physics systems. -function update() {} -``` +## Compare your code -Try reloading `index.html` again — The ball should now be moving constantly in the given direction. At the moment, the physics engine has gravity and friction set to zero. Adding gravity would result in the ball falling down while friction would eventually stop the ball. +Here's what you should have so far, running live. To view its source code, click the "Play" button. -## Fun with physics - -You can do much more with physics, for example by adding `ball.body.gravity.y = 100;` you will set the vertical gravity of the ball. As a result it will be launched upwards, but then fall due to the effects of gravity pulling it down. +```html hidden + +``` -This kind of functionality is just the tip of the iceberg — there are various functions and variables that can help you manipulate the physics objects. Check out the official [physics documentation](https://phaser.io/docs/#physics) and see the [huge collection of examples](https://samme.github.io/phaser-examples-mirror/) using the Arcade and P2 physics systems. +```css hidden +* { + padding: 0; + margin: 0; +} +``` -## Compare your code +```js hidden +class ExampleScene extends Phaser.Scene { + ball; + + preload() { + this.load.setBaseURL( + "https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser", + ); + + this.load.image("ball", "ball.png"); + } + create() { + this.ball = this.add.sprite(50, 50, "ball"); + this.physics.add.existing(this.ball); + this.ball.body.setVelocity(150, 150); + } + update() {} +} -You can check the finished code for this lesson in the live demo below, and play with it to understand better how it works: +const config = { + type: Phaser.CANVAS, + width: 480, + height: 320, + scene: ExampleScene, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, + backgroundColor: "#eeeeee", + physics: { + default: "arcade", + }, +}; + +const game = new Phaser.Game(config); +``` -{{JSFiddleEmbed("https://jsfiddle.net/end3r/bjto9nj8/","","400")}} +{{EmbedLiveSample("compare your code", "", 480, , , , , "allow-modals")}} ## Next steps Now we can move to the next lesson and see how to make the ball [bounce off the walls](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Bounce_off_the_walls). -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Move_the_ball", "Games/Workflows/2D_Breakout_game_Phaser/Bounce_off_the_walls")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Move_the_ball", "Games/Tutorials/2D_breakout_game_Phaser/Bounce_off_the_walls")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_phaser/player_paddle_and_controls/index.md b/files/en-us/games/tutorials/2d_breakout_game_phaser/player_paddle_and_controls/index.md index 5aa746dab9a4234..ddd406e6a5ca75e 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_phaser/player_paddle_and_controls/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_phaser/player_paddle_and_controls/index.md @@ -5,124 +5,204 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Bounce_off_the_walls", "Games/Workflows/2D_Breakout_game_Phaser/Game_over")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Bounce_off_the_walls", "Games/Tutorials/2D_breakout_game_Phaser/Game_over")}} -This is the **7th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). You can find the source code as it should look after completing this lesson at [Gamedev-Phaser-Content-Kit/demos/lesson07.html](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/lesson07.html). - -We have the ball moving and bouncing off the walls, but it quickly gets boring — there's no interactivity! We need a way to introduce gameplay, so in this article we'll create a paddle to move around and hit the ball with. +This is the **7th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). We have the ball moving and bouncing off the walls, but it quickly gets boring—there's no interactivity! We need a way to introduce gameplay, so in this article, we'll create a paddle to move around and hit the ball with. ## Rendering the paddle -From the framework point of view the paddle is very similar to the ball — we need to add a variable to represent it, load the relevant image asset, and then do the magic. +From the framework point of view, the paddle is very similar to the ball—we need to add a property to represent it, load the relevant image asset, and then do the magic. ### Loading the paddle -First, add the `paddle` variable we will be using in our game, right after the `ball` variable: +First, add the `paddle` property we will be using in our game, right after the `ball` property: ```js -let paddle; +class ExampleScene extends Phaser.Scene { + ball; + paddle; + // ... +} ``` -Then, in the `preload` function, load the `paddle` image by adding the following new `load.image()` call: +Then, in the `preload` method, load the `paddle` image by adding the following new `load.image()` call: ```js -function preload() { - // … - game.load.image("ball", "img/ball.png"); - game.load.image("paddle", "img/paddle.png"); +class ExampleScene extends Phaser.Scene { + // ... + preload() { + this.load.image("ball", "img/ball.png"); + this.load.image("paddle", "img/paddle.png"); + } + // ... } ``` -### Adding the paddle graphic - -Just so we don't forget, at this point you should grab the [paddle graphic](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/img/paddle.png) from GitHub, and save it in your `/img` folder. +Just so we don't forget, at this point, you should grab the [paddle graphic](https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser/paddle.png) and save it in your `/img` folder. ### Rendering the paddle, with physics -Next up, we will initialize our paddle by adding the following `add.sprite()` call inside the `create()` function — add it right at the bottom: +Next up, we will initialize our paddle by adding the following `add.sprite()` call inside the `create()` method—add it right at the bottom: ```js -paddle = game.add.sprite( - game.world.width * 0.5, - game.world.height - 5, +this.paddle = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 5, "paddle", ); ``` -We can use the `world.width` and `world.height` values to position the paddle exactly where we want it: `game.world.width*0.5` will be right in the middle of the screen. In our case the world is the same as the Canvas, but for other types of games, like side-scrollers for example, the world will be bigger, and you can tinker with it to create interesting effects. +We can use the `scale.width` and `scale.height` values to position the paddle exactly where we want it: `this.scale.width * 0.5` will be right in the middle of the screen. In our case, the world is the same as the canvas, but for other types of games, like side-scrollers, the world will be bigger, and you can tinker with it to create interesting effects. -As you'll notice if you reload your `index.html` at this point, the paddle is currently not exactly in the middle. Why? Because the anchor from which the position is calculated always starts from the top left edge of the object. We can change that to have the anchor in the middle of the paddle's width and at the bottom of its height, so it's easier to position it against the bottom edge. Add the following line below the previous new one: +As you'll notice, if you reload your `index.html` at this point, the paddle is currently at the complete bottom of the screen, too low for the paddle. Why? Because the origin from which the position is calculated starts from the center of the object. We can change that to have the origin in the middle of the paddle's width and at the bottom of its height, so it's easier to position it against the bottom edge. Add the following line below the previous new one: ```js -paddle.anchor.set(0.5, 1); +this.paddle.setOrigin(0.5, 1); ``` -The paddle is now positioned right where we want it to be. Now, to make it collide with the ball we have to enable physics for the paddle. Continue by adding the following new line, again at the bottom of the `create()` function: +The paddle is now positioned right where we want it to be. Now, to make it collide with the ball, we have to enable physics for the paddle. Continue by adding the following new line, again at the bottom of the `create()` method: ```js -game.physics.enable(paddle, Phaser.Physics.ARCADE); +this.physics.add.existing(this.paddle); ``` -Now the magic can start to happen — the framework can take care of checking the collision detection on every frame. To enable collision detection between the paddle and ball, add the `collide()` method to the `update()` function as shown: +Now the magic can start to happen—the framework can take care of checking the collision detection on every frame. To enable collision detection between the paddle and ball, add the `collide()` method to the `update()` method as shown: ```js -function update() { - game.physics.arcade.collide(ball, paddle); +class ExampleScene extends Phaser.Scene { + // ... + update() { + this.physics.collide(this.ball, this.paddle); + } } ``` -The first parameter is one of the objects we are interested in — the ball — and the second is the other one, the paddle. This works, but not quite as we expected it to — when the ball hits the paddle, the paddle falls off the screen! All we want is the ball bouncing off the paddle and the paddle staying in the same place. We can set the `body` of the paddle to be `immovable`, so it won't move when the ball hits it. To do this, add the below line at the bottom of the `create()` function: +The first parameter is one of the objects we are interested in—the ball—and the second is the other one, the paddle. This works, but not quite as we expected it to—when the ball hits the paddle, the paddle falls off the screen! All we want is the ball bouncing off the paddle and the paddle staying in the same place. We can set the `body` of the paddle to be immovable, so it won't move when the ball hits it. To do this, add the below line at the bottom of the `create()` method: ```js -paddle.body.immovable = true; +this.paddle.body.setImmovable(true); +``` + +A second thing you will notice is that after the ball hits the paddle, it starts moving horizontally instead of reflecting back. To fix this, we need to set the bounce factor on the ball itself as well (we already did that for the wall collisions with `setCollideWorldBounds()`). Add the following line to the `create()` method, right after the `ball.body.setCollideWorldBounds(true, 1, 1)` line (we want to keep all configurations for the ball together): + +```js +this.ball.body.setBounce(1); ``` Now it works as expected. ## Controlling the paddle -The next problem is that we can't move the paddle. To do that we can use the system's default input (mouse or touch, depending on platform) and set the paddle position to where the `input` position is. Add the following new line to the `update()` function, as shown: +The next problem is that we can't move the paddle. To fix that, we can use the system's default input (mouse or touch, depending on the platform) and set the paddle position to where the `input` position is. Add the following new line to the `update()` method, as shown: ```js -function update() { - game.physics.arcade.collide(ball, paddle); - paddle.x = game.input.x; -} +this.paddle.x = this.input.x; ``` -Now on every new frame the paddle's `x` position will adjust accordingly to the input's `x` position, however when we start the game, the position of the paddle is not in the middle. It's because the input position is not yet defined. To fix that we can set the default position (if an input position is not yet defined) to be the middle of the screen. Update the previous line as follows: +Now on every new frame, the paddle's `x` position will adjust accordingly to the input's `x` position. However, when we start the game, the position of the paddle is not in the middle. It's because the input position is not yet defined. To fix that, we can set the default position (if an input position is not yet defined) to be the middle of the screen. Update the previous line as follows: ```js -paddle.x = game.input.x || game.world.width * 0.5; +this.paddle.x = this.input.x || this.scale.width * 0.5; ``` If you haven't already done so, reload your `index.html` and try it out! ## Position the ball -We have the paddle working as expected, so let's position the ball on it. It's very similar to positioning the paddle — we need to have it placed in the middle of the screen horizontally and at the bottom vertically with a little offset from the bottom. To place it exactly as we want it we will set the anchor to the exact middle of the ball. Find the existing `ball = game.add.sprite()` line, and replace it with the following two lines: +We have the paddle working as expected, so let's position the ball on it. It's very similar to positioning the paddle—we need to have it placed in the middle of the screen horizontally and at the bottom vertically with a little offset from the bottom. To place it exactly as we want it, we will set the origin to the exact middle of the ball. Find the existing `this.ball = this.add.sprite(...)` line, and replace it with the following lines: ```js -ball = game.add.sprite(game.world.width * 0.5, game.world.height - 25, "ball"); -ball.anchor.set(0.5); +this.ball = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 25, + "ball", +); ``` -The velocity stays almost the same — we're just changing the second parameter's value from 150 to -150, so the ball will start the game by moving up instead of down. Find the existing `ball.body.velocity.set()` line and update it to the following: +The velocity stays almost the same—we're just changing the second parameter's value from 150 to -150, so the ball will start the game by moving up instead of down. Find the existing `this.ball.body.setVelocity()` line and update it to the following: ```js -ball.body.velocity.set(150, -150); +this.ball.body.setVelocity(150, -150); ``` Now the ball will start right from the middle of the paddle. ## Compare your code -You can check the finished code for this lesson in the live demo below, and play with it to understand better how it works: +Here's what you should have so far, running live. To view its source code, click the "Play" button. + +```html hidden + +``` + +```css hidden +* { + padding: 0; + margin: 0; +} +``` + +```js hidden +class ExampleScene extends Phaser.Scene { + ball; + paddle; + + preload() { + this.load.setBaseURL( + "https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser", + ); + + this.load.image("ball", "ball.png"); + this.load.image("paddle", "paddle.png"); + } + create() { + this.ball = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 25, + "ball", + ); + this.physics.add.existing(this.ball); + this.ball.body.setVelocity(150, -150); + this.ball.body.setCollideWorldBounds(true, 1, 1); + this.ball.body.setBounce(1); + + this.paddle = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 5, + "paddle", + ); + this.paddle.setOrigin(0.5, 1); + this.physics.add.existing(this.paddle); + this.paddle.body.setImmovable(true); + } + update() { + this.physics.collide(this.ball, this.paddle); + this.paddle.x = this.input.x || this.scale.width * 0.5; + } +} + +const config = { + type: Phaser.CANVAS, + width: 480, + height: 320, + scene: ExampleScene, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, + backgroundColor: "#eeeeee", + physics: { + default: "arcade", + }, +}; + +const game = new Phaser.Game(config); +``` -{{JSFiddleEmbed("https://jsfiddle.net/end3r/ogqza0ye/","","400")}} +{{EmbedLiveSample("compare your code", "", 480, , , , , "allow-modals")}} ## Next steps -We can move the paddle and bounce the ball off it, but what's the point if the ball is bouncing off the bottom edge of the screen anyway? Let's introduce the possibility of losing — also known as [game over](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Game_over) logic. +We can move the paddle and bounce the ball off it, but what's the point if the ball is bouncing off the bottom edge of the screen anyway? Let's introduce the possibility of losing—also known as [game over](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Game_over) logic. -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Bounce_off_the_walls", "Games/Workflows/2D_Breakout_game_Phaser/Game_over")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Bounce_off_the_walls", "Games/Tutorials/2D_breakout_game_Phaser/Game_over")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_phaser/randomizing_gameplay/index.md b/files/en-us/games/tutorials/2d_breakout_game_phaser/randomizing_gameplay/index.md index ac9ee97192e060a..6feea9dde3ec274 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_phaser/randomizing_gameplay/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_phaser/randomizing_gameplay/index.md @@ -5,47 +5,302 @@ page-type: guide sidebar: games --- -{{Previous("Games/Workflows/2D_Breakout_game_Phaser/Buttons")}} +{{Previous("Games/Tutorials/2D_breakout_game_Phaser/Buttons")}} -This is the **16th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). You can find the source code as it should look after completing this lesson at [Gamedev-Phaser-Content-Kit/demos/lesson16.html](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/lesson16.html). - -Our game appears to be completed, but if you look close enough you'll notice that the ball is bouncing off the paddle at the same angle throughout the whole game. This means that every game is quite similar. To fix this and improve playability we should make the rebound angles more random, and in this article we'll look at how. +This is the **16th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). Our game appears to be completed, but if you look close enough, you'll notice that the ball is bouncing off the paddle at the same angle throughout the whole game. This means that every game is quite similar. To fix this and improve playability, we should make the rebound angles more random, and in this article we'll look at how. ## Making rebounds more random -We can change the ball's velocity depending on the exact spot it hits the paddle, by modifying the `x` velocity each time the `ballHitPaddle()` function is run using a line along the lines of the below. Add this new line to your code now, and try it out. +We can change the ball's velocity depending on the exact spot it hits the paddle, by modifying the `x` velocity each time the `hitPaddle()` method is run using a line along the lines of the below. Add this new line to your code now, and try it out. ```js -function ballHitPaddle(ball, paddle) { - ball.animations.play("wobble"); - ball.body.velocity.x = -5 * (paddle.x - ball.x); +class ExampleScene extends Phaser.Scene { + // ... + hitPaddle(ball, paddle) { + this.ball.anims.play("wobble"); + ball.body.velocity.x = -5 * (paddle.x - ball.x); + } + // ... } ``` -It's a little bit of magic — the new velocity is higher, the larger the distance between the center of the paddle and the place where the ball hits it. Also, the direction (left or right) is determined by that value — if the ball hits the left side of the paddle it will bounce left, whereas hitting the right side will bounce it to the right. It ended up that way because of a little bit of experimentation with the given values, you can do your own experimentation and see what happens. It's not completely random of course, but it does make the gameplay a bit more unpredictable and therefore more interesting. +It's a little bit of magic—the new velocity is higher, the larger the distance between the center of the paddle and the place where the ball hits it. Also, the direction (left or right) is determined by that value—if the ball hits the left side of the paddle, it will bounce left, whereas hitting the right side will bounce it to the right. It ended up that way because of a little bit of experimentation with the given values; you can do your own experimentation and see what happens. It's not completely random of course, but it does make the gameplay a bit more unpredictable and therefore more interesting. ## Compare your code -You can check the finished code for this lesson in the live demo below, and play with it to understand better how it works: +Here's what you should have so far, running live. To view its source code, click the "Play" button. + +```html hidden + +``` + +```css hidden +* { + padding: 0; + margin: 0; +} +``` + +```js hidden +class ExampleScene extends Phaser.Scene { + ball; + paddle; + bricks; + + scoreText; + score = 0; + + lives = 3; + livesText; + lifeLostText; + + preload() { + this.load.setBaseURL( + "https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser", + ); + + this.load.image("ball", "ball.png"); + this.load.image("paddle", "paddle.png"); + this.load.image("brick", "brick.png"); + this.load.spritesheet("wobble", "wobble.png", { + frameWidth: 20, + frameHeight: 20, + }); + this.load.spritesheet("button", "button.png", { + frameWidth: 120, + frameHeight: 40, + }); + } + create() { + this.physics.world.checkCollision.down = false; + + this.ball = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 25, + "ball", + ); + this.physics.add.existing(this.ball); + this.ball.body.setCollideWorldBounds(true, 1, 1); + this.ball.body.setBounce(1); + this.ball.anims.create({ + key: "wobble", + frameRate: 24, + frames: this.anims.generateFrameNumbers("wobble", { + frames: [0, 1, 0, 2, 0, 1, 0, 2, 0], + }), + }); + + this.paddle = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 5, + "paddle", + ); + this.paddle.setOrigin(0.5, 1); + this.physics.add.existing(this.paddle); + this.paddle.body.setImmovable(true); + + this.initBricks(); + + const textStyle = { font: "18px Arial", fill: "#0095dd" }; + this.scoreText = this.add.text(5, 5, "Points: 0", textStyle); + + this.livesText = this.add.text( + this.scale.width - 5, + 5, + `Lives: ${this.lives}`, + textStyle, + ); + this.livesText.setOrigin(1, 0); + this.lifeLostText = this.add.text( + this.scale.width * 0.5, + this.scale.height * 0.5, + "Life lost, click to continue", + textStyle, + ); + this.lifeLostText.setOrigin(0.5, 0.5); + this.lifeLostText.visible = false; + + this.startButton = this.add.sprite( + this.scale.width * 0.5, + this.scale.height * 0.5, + "button", + 0, + ); + this.startButton.setInteractive(); + this.startButton.on( + "pointerover", + () => { + this.startButton.setFrame(1); + }, + this, + ); + this.startButton.on( + "pointerdown", + () => { + this.startButton.setFrame(2); + }, + this, + ); + this.startButton.on( + "pointerout", + () => { + this.startButton.setFrame(0); + }, + this, + ); + this.startButton.on( + "pointerup", + () => { + this.startGame(); + }, + this, + ); + } + update() { + this.physics.collide(this.ball, this.paddle, (ball, paddle) => + this.hitPaddle(ball, paddle), + ); + this.physics.collide(this.ball, this.bricks, (ball, brick) => + this.hitBrick(ball, brick), + ); + + if (this.playing) { + this.paddle.x = this.input.x || this.scale.width * 0.5; + } + + const ballIsOutOfBounds = !Phaser.Geom.Rectangle.Overlaps( + this.physics.world.bounds, + this.ball.getBounds(), + ); + if (ballIsOutOfBounds) { + this.ballLeaveScreen(); + } + if (this.bricks.countActive() === 0) { + alert("You won the game, congratulations!"); + location.reload(); + } + } + + startGame() { + this.startButton.destroy(); + this.ball.body.setVelocity(150, -150); + this.playing = true; + } + + initBricks() { + const bricksLayout = { + width: 50, + height: 20, + count: { + row: 3, + col: 7, + }, + offset: { + top: 50, + left: 60, + }, + padding: 10, + }; + + this.bricks = this.add.group(); + for (let c = 0; c < bricksLayout.count.col; c++) { + for (let r = 0; r < bricksLayout.count.row; r++) { + const brickX = + c * (bricksLayout.width + bricksLayout.padding) + + bricksLayout.offset.left; + const brickY = + r * (bricksLayout.height + bricksLayout.padding) + + bricksLayout.offset.top; + + const newBrick = this.add.sprite(brickX, brickY, "brick"); + this.physics.add.existing(newBrick); + newBrick.body.setImmovable(true); + this.bricks.add(newBrick); + } + } + } + + hitPaddle(ball, paddle) { + this.ball.anims.play("wobble"); + ball.body.velocity.x = -5 * (paddle.x - ball.x); + } + + hitBrick(ball, brick) { + const destroyTween = this.tweens.add({ + targets: brick, + ease: "Linear", + repeat: 0, + duration: 200, + props: { + scaleX: 0, + scaleY: 0, + }, + onComplete: () => { + brick.destroy(); + }, + }); + destroyTween.play(); + this.score += 10; + this.scoreText.setText(`Points: ${this.score}`); + } + + ballLeaveScreen() { + this.lives--; + if (this.lives > 0) { + this.livesText.setText(`Lives: ${this.lives}`); + this.lifeLostText.visible = true; + this.ball.body.reset(this.scale.width * 0.5, this.scale.height - 25); + this.input.once( + "pointerdown", + () => { + this.lifeLostText.visible = false; + this.ball.body.setVelocity(150, -150); + }, + this, + ); + } else { + // Game over logic + location.reload(); + } + } +} + +const config = { + type: Phaser.CANVAS, + width: 480, + height: 320, + scene: ExampleScene, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, + backgroundColor: "#eeeeee", + physics: { + default: "arcade", + }, +}; + +const game = new Phaser.Game(config); +``` -{{JSFiddleEmbed("https://jsfiddle.net/end3r/3yds5ege/","","400")}} +{{EmbedLiveSample("compare your code", "", 480, , , , , "allow-modals")}} ## Summary -You've finished all the lessons — congratulations! By this point you would have learnt the basics of Phaser and the logic behind simple 2D games. +You've finished all the lessons—congratulations! By this point you would have learnt the basics of Phaser and the logic behind simple 2D games. ### Exercises to follow -You can do a lot more in the game — add whatever you feel would be best to make it more fun and interesting. It's a basic intro scratching the surface of the countless helpful methods that Phaser provides. Below are some suggestions as to how you could expand our little game, to get you started: +You can do a lot more in the game—add whatever you feel would be best to make it more fun and interesting. It's a basic intro that scratches the surface of the countless helpful methods that Phaser provides. Below are some suggestions as to how you could expand our little game, to get you started: - Add a second ball or paddle. - Change the color of the background on every hit. - Change the images and use your own. -- Grant extra bonus points if bricks are destroyed rapidly, several-in-a-row (or other bonuses of your choosing.) +- Grant extra bonus points if bricks are destroyed rapidly, several-in-a-row (or other bonuses of your choosing). - Create levels with different brick layouts. Be sure to check the ever-growing list of [examples](https://labs.phaser.io/) and the [official documentation](https://phaser.io/docs/), and visit the [Phaser Discourse forum](https://phaser.discourse.group/) if you ever need any help. You could also go back to [this tutorial series' index page](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). -{{Previous("Games/Workflows/2D_Breakout_game_Phaser/Buttons")}} +{{Previous("Games/Tutorials/2D_breakout_game_Phaser/Buttons")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_phaser/scaling/index.md b/files/en-us/games/tutorials/2d_breakout_game_phaser/scaling/index.md index f098d7a994f76ad..c167a112a4e9694 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_phaser/scaling/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_phaser/scaling/index.md @@ -5,50 +5,88 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Initialize_the_framework", "Games/Workflows/2D_Breakout_game_Phaser/Load_the_assets_and_print_them_on_screen")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Initialize_the_framework", "Games/Tutorials/2D_breakout_game_Phaser/Load_the_assets_and_print_them_on_screen")}} -This is the **2nd step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). You can find the source code as it should look after completing this lesson at [Gamedev-Phaser-Content-Kit/demos/lesson02.html](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/lesson02.html). - -Scaling refers to how the game canvas will scale on different screen sizes. We can make the game scale to fit on any screen size automatically during the preload stage, so we don't have to worry about it later. +This is the **2nd step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). We'll be working on scaling, which refers to how the game canvas will scale on different screen sizes. We can make the game scale to fit on any screen size by configuring `scale` during initialization, so we don't have to worry about it later. ## The Phaser scale object -There's a special `scale` object available in Phaser with a few handy methods and properties available. Update your existing `preload()` function as follows: +The `scale` property of the `config` object allows us to set how the game canvas will be scaled. Update your `config` object as follows: ```js -function preload() { - game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL; - game.scale.pageAlignHorizontally = true; - game.scale.pageAlignVertically = true; -} +const config = { + // ... + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, +}; ``` -`scaleMode` has a few different options available for how the Canvas can be scaled: +The `mode` property in `scale` has a few different options available for how the canvas can be scaled: -- `NO_SCALE` — nothing is scaled. -- `EXACT_FIT` — scale the canvas to fill all the available space both vertically and horizontally, without preserving the aspect ratio. -- `SHOW_ALL` — scales the canvas, but keeps the aspect ratio untouched, so images won't be skewed like in the previous mode. There might be black stripes visible on the edges of the screen, but we can live with that. -- `RESIZE` — creates the canvas with the same size as the available width and height, so you have to place the objects inside your game dynamically; this is more of an advanced mode. -- `USER_SCALE` — allows you to have custom dynamic scaling, calculating the size, scale and ratio on your own; again, this is more of an advanced mode +- `NO_SCALE`—nothing is scaled (the default value). +- `ENVELOP`—adjusts the width and height automatically to cover the entire target area while keeping the aspect ratio. It may extend further out than the target size. +- `FIT`—scales the canvas to fit the available space while keeping the aspect ratio untouched. Depending on the aspect ratio, it may not cover the entire space. +- `HEIGHT_CONTROLS_WIDTH`—adjusts the width of the canvas based on the height. +- `WIDTH_CONTROLS_HEIGHT`—adjusts the height of the canvas based on the width. +- `RESIZE`—resizes the visible area of the canvas to fit all available _parent_ space, regardless of aspect ratio. +- `EXPAND`—resizes the visible area of the canvas to fit all available _parent_ space like the RESIZE mode, and scale the canvas size to fit inside the visible area like the FIT mode. -The other two lines of code in the `preload()` function are responsible for aligning the canvas element horizontally and vertically, so it is always centered on screen regardless of size. +The other property, `autoCenter`, is responsible for aligning the canvas element horizontally and vertically, so it always centers the canvas on the screen regardless of size. ## Adding a custom canvas background color -We can also add a custom background color to our canvas, so it won't stay black. The `stage` object has a `backgroundColor` property for this purpose, which we can set using CSS color definition syntax. Add the following line below the other three you added earlier: +We can also add a custom background color to our canvas, so it won't stay black. The configuration object has a `backgroundColor` property for this purpose, which we can set using CSS color definition syntax. Add the following line to your `config` object: ```js -game.stage.backgroundColor = "#eee"; +const config = { + // ... + backgroundColor: "#eeeeee", +}; ``` ## Compare your code -You can check the finished code for this lesson in the live demo below, and play with it to understand better how it works: +Here's what you should have so far, running live. To view its source code, click the "Play" button. + +```html hidden + +``` + +```css hidden +* { + padding: 0; + margin: 0; +} +``` + +```js hidden +class ExampleScene extends Phaser.Scene { + preload() {} + create() {} + update() {} +} + +const config = { + type: Phaser.CANVAS, + width: 480, + height: 320, + scene: ExampleScene, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, + backgroundColor: "#eeeeee", +}; + +const game = new Phaser.Game(config); +``` -{{JSFiddleEmbed("https://jsfiddle.net/end3r/6a64vecL/","","400")}} +{{EmbedLiveSample("compare your code", "", 480, , , , , "allow-modals")}} ## Next steps Now we've set up the scaling for our game, let's continue to the third lesson and work out how to [load the assets and print them on screen](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Load_the_assets_and_print_them_on_screen). -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Initialize_the_framework", "Games/Workflows/2D_Breakout_game_Phaser/Load_the_assets_and_print_them_on_screen")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Initialize_the_framework", "Games/Tutorials/2D_breakout_game_Phaser/Load_the_assets_and_print_them_on_screen")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_phaser/the_score/index.md b/files/en-us/games/tutorials/2d_breakout_game_phaser/the_score/index.md index a76f8c94220adce..edf25b91d76761a 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_phaser/the_score/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_phaser/the_score/index.md @@ -5,32 +5,33 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Collision_detection", "Games/Workflows/2D_Breakout_game_Phaser/Win_the_game")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Collision_detection", "Games/Tutorials/2D_breakout_game_Phaser/Win_the_game")}} -This is the **11th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). You can find the source code as it should look after completing this lesson at [Gamedev-Phaser-Content-Kit/demos/lesson11.html](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/lesson11.html). +This is the **11th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). In this article, we'll add a scoring system to our game. Having a score can also make the game more interesting—you can try to beat your own high score, or your friend's. -Having a score can also make the game more interesting — you can try to beat your own high score, or your friend's. In this article we'll add a scoring system to our game. +We will use a separate property for storing the score and Phaser's `text()` method to print it out onto the screen. -We will use a separate variable for storing the score and Phaser's `text()` method to print it out onto the screen. +## New properties -## New variables - -Add two new variables right after the previously defined ones: +Add two new properties right after the previously defined ones: ```js -// … -let scoreText; -let score = 0; +class ExampleScene extends Phaser.Scene { + // ... previous property definitions ... + scoreText; + score = 0; + // ... rest of the class ... +} ``` ## Adding score text to the game display -Now add this line at the end of the `create()` function: +Now add this line at the end of the `create()` method: ```js -scoreText = game.add.text(5, 5, "Points: 0", { +this.scoreText = this.add.text(5, 5, "Points: 0", { font: "18px Arial", - fill: "#0095DD", + color: "#0095dd", }); ``` @@ -40,30 +41,165 @@ The `text()` method can take four parameters: - The actual text that will be rendered. - The font style to render the text with. -The last parameter looks very similar to CSS styling. In our case the score text will be blue, sized at 18 pixels, and use the Arial font. +The last parameter looks very similar to CSS styling. In our case, the score text will be blue, sized at 18 pixels, and use the Arial font. ## Updating the score when bricks are destroyed -We will increase the number of points every time the ball hits a brick and update the `scoreText` to display the current score. This can be done using the `setText()` method — add the two new lines seen below to the `ballHitBrick()` function: +We will increase the number of points every time the ball hits a brick and update the `scoreText` to display the current score. This can be done using the `setText()` method—add the two new lines seen below to the `hitBrick()` method: ```js -function ballHitBrick(ball, brick) { - brick.kill(); - score += 10; - scoreText.setText(`Points: ${score}`); +class ExampleScene extends Phaser.Scene { + // ... + hitBrick(ball, brick) { + brick.destroy(); + this.score += 10; + this.scoreText.setText(`Points: ${this.score}`); + } } ``` -That's it for now — reload your `index.html` and check that the score updates on every brick hit. +That's it for now—reload your `index.html` and check that the score updates on every brick hit. ## Compare your code -You can check the finished code for this lesson in the live demo below, and play with it to understand better how it works: +Here's what you should have so far, running live. To view its source code, click the "Play" button. + +```html hidden + +``` + +```css hidden +* { + padding: 0; + margin: 0; +} +``` + +```js hidden +class ExampleScene extends Phaser.Scene { + ball; + paddle; + bricks; + scoreText; + score = 0; + + preload() { + this.load.setBaseURL( + "https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser", + ); + + this.load.image("ball", "ball.png"); + this.load.image("paddle", "paddle.png"); + this.load.image("brick", "brick.png"); + } + create() { + this.physics.world.checkCollision.down = false; + + this.ball = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 25, + "ball", + ); + this.physics.add.existing(this.ball); + this.ball.body.setVelocity(150, -150); + this.ball.body.setCollideWorldBounds(true, 1, 1); + this.ball.body.setBounce(1); + + this.paddle = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 5, + "paddle", + ); + this.paddle.setOrigin(0.5, 1); + this.physics.add.existing(this.paddle); + this.paddle.body.setImmovable(true); + + this.initBricks(); + + this.scoreText = this.add.text(5, 5, "Points: 0", { + font: "18px Arial", + color: "#0095dd", + }); + } + update() { + this.physics.collide(this.ball, this.paddle); + this.physics.collide(this.ball, this.bricks, (ball, brick) => + this.hitBrick(ball, brick), + ); + + this.paddle.x = this.input.x || this.scale.width * 0.5; + const ballIsOutOfBounds = !Phaser.Geom.Rectangle.Overlaps( + this.physics.world.bounds, + this.ball.getBounds(), + ); + if (ballIsOutOfBounds) { + // Game over logic + location.reload(); + } + } + + initBricks() { + const bricksLayout = { + width: 50, + height: 20, + count: { + row: 3, + col: 7, + }, + offset: { + top: 50, + left: 60, + }, + padding: 10, + }; + + this.bricks = this.add.group(); + for (let c = 0; c < bricksLayout.count.col; c++) { + for (let r = 0; r < bricksLayout.count.row; r++) { + const brickX = + c * (bricksLayout.width + bricksLayout.padding) + + bricksLayout.offset.left; + const brickY = + r * (bricksLayout.height + bricksLayout.padding) + + bricksLayout.offset.top; + + const newBrick = this.add.sprite(brickX, brickY, "brick"); + this.physics.add.existing(newBrick); + newBrick.body.setImmovable(true); + this.bricks.add(newBrick); + } + } + } + + hitBrick(ball, brick) { + brick.destroy(); + this.score += 10; + this.scoreText.setText(`Points: ${this.score}`); + } +} + +const config = { + type: Phaser.CANVAS, + width: 480, + height: 320, + scene: ExampleScene, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, + backgroundColor: "#eeeeee", + physics: { + default: "arcade", + }, +}; + +const game = new Phaser.Game(config); +``` -{{JSFiddleEmbed("https://jsfiddle.net/end3r/n8o6rhrf/","","400")}} +{{EmbedLiveSample("compare your code", "", 480, , , , , "allow-modals")}} ## Next steps We now have a scoring system, but what's the point of playing and keeping score if you can't win? Let's see how we can add a victory state, allowing us to [win the game](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Win_the_game). -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/Collision_detection", "Games/Workflows/2D_Breakout_game_Phaser/Win_the_game")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/Collision_detection", "Games/Tutorials/2D_breakout_game_Phaser/Win_the_game")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_phaser/win_the_game/index.md b/files/en-us/games/tutorials/2d_breakout_game_phaser/win_the_game/index.md index 30e9de11af606c9..d288c296a0c6a19 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_phaser/win_the_game/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_phaser/win_the_game/index.md @@ -5,40 +5,174 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/The_score", "Games/Workflows/2D_Breakout_game_Phaser/Extra_lives")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/The_score", "Games/Tutorials/2D_breakout_game_Phaser/Extra_lives")}} -This is the **12th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). You can find the source code as it should look after completing this lesson at [Gamedev-Phaser-Content-Kit/demos/lesson12.html](https://github.com/end3r/Gamedev-Phaser-Content-Kit/blob/gh-pages/demos/lesson12.html). - -Implementing winning in our game is quite easy: if you happen to destroy all the bricks, then you win. +This is the **12th step** out of 16 of the [Gamedev Phaser tutorial](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser). Implementing winning in our game is quite easy: if you happen to destroy all the bricks, then you win. ## How to win? -Add the following new code into your `ballHitBrick()` function: +Add the following new code into your `update()` method: ```js -function ballHitBrick(ball, brick) { - brick.kill(); - score += 10; - scoreText.setText(`Points: ${score}`); - - const countAlive = bricks.children.filter((b) => b.alive).length; - if (countAlive === 0) { - alert("You won the game, congratulations!"); - location.reload(); +class ExampleScene extends Phaser.Scene { + // ... + update() { + // ... + if (this.bricks.countActive() === 0) { + alert("You won the game, congratulations!"); + location.reload(); + } } + // ... } ``` -We loop through the bricks in the group using `bricks.children`, checking for the aliveness of each with each brick's `.alive` property. If there are no more bricks left alive, then we show a winning message, restarting the game once the alert is dismissed. +We count the number of bricks that are still alive, using the `countAlive()` method on `this.bricks`. If there are no more bricks left alive, then we display the winning message, restarting the game once the alert is dismissed. ## Compare your code -You can check the finished code for this lesson in the live demo below, and play with it to understand better how it works: +Here's what you should have so far, running live. To view its source code, click the "Play" button. + +```html hidden + +``` + +```css hidden +* { + padding: 0; + margin: 0; +} +``` + +```js hidden +class ExampleScene extends Phaser.Scene { + ball; + paddle; + bricks; + scoreText; + score = 0; + + preload() { + this.load.setBaseURL( + "https://mdn.github.io/shared-assets/images/examples/2D_breakout_game_Phaser", + ); + + this.load.image("ball", "ball.png"); + this.load.image("paddle", "paddle.png"); + this.load.image("brick", "brick.png"); + } + create() { + this.physics.world.checkCollision.down = false; + + this.ball = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 25, + "ball", + ); + this.physics.add.existing(this.ball); + this.ball.body.setVelocity(150, -150); + this.ball.body.setCollideWorldBounds(true, 1, 1); + this.ball.body.setBounce(1); + + this.paddle = this.add.sprite( + this.scale.width * 0.5, + this.scale.height - 5, + "paddle", + ); + this.paddle.setOrigin(0.5, 1); + this.physics.add.existing(this.paddle); + this.paddle.body.setImmovable(true); + + this.initBricks(); + + this.scoreText = this.add.text(5, 5, "Points: 0", { + font: "18px Arial", + color: "#0095dd", + }); + } + update() { + this.physics.collide(this.ball, this.paddle); + this.physics.collide(this.ball, this.bricks, (ball, brick) => + this.hitBrick(ball, brick), + ); + + this.paddle.x = this.input.x || this.scale.width * 0.5; + const ballIsOutOfBounds = !Phaser.Geom.Rectangle.Overlaps( + this.physics.world.bounds, + this.ball.getBounds(), + ); + if (ballIsOutOfBounds) { + // Game over logic + location.reload(); + } + if (this.bricks.countActive() === 0) { + alert("You won the game, congratulations!"); + location.reload(); + } + } + + initBricks() { + const bricksLayout = { + width: 50, + height: 20, + count: { + row: 3, + col: 7, + }, + offset: { + top: 50, + left: 60, + }, + padding: 10, + }; + + this.bricks = this.add.group(); + for (let c = 0; c < bricksLayout.count.col; c++) { + for (let r = 0; r < bricksLayout.count.row; r++) { + const brickX = + c * (bricksLayout.width + bricksLayout.padding) + + bricksLayout.offset.left; + const brickY = + r * (bricksLayout.height + bricksLayout.padding) + + bricksLayout.offset.top; + + const newBrick = this.add.sprite(brickX, brickY, "brick"); + this.physics.add.existing(newBrick); + newBrick.body.setImmovable(true); + this.bricks.add(newBrick); + } + } + } + + hitBrick(ball, brick) { + brick.destroy(); + this.score += 10; + this.scoreText.setText(`Points: ${this.score}`); + } +} + +const config = { + type: Phaser.CANVAS, + width: 480, + height: 320, + scene: ExampleScene, + scale: { + mode: Phaser.Scale.FIT, + autoCenter: Phaser.Scale.CENTER_BOTH, + }, + backgroundColor: "#eeeeee", + physics: { + default: "arcade", + }, +}; + +const game = new Phaser.Game(config); +``` -{{JSFiddleEmbed("https://jsfiddle.net/u8waa4Lx/1/","","400")}} +{{EmbedLiveSample("compare your code", "", 480, , , , , "allow-modals")}} ## Next steps -Both losing and winning are implemented, so the core gameplay of our game is finished. Now let's add something extra — we'll give the player three [lives](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Extra_lives) instead of one. +Both losing and winning are implemented, so the core gameplay of our game is finished. Now let's add something extra—we'll give the player three [lives](/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser/Extra_lives) instead of one. -{{PreviousNext("Games/Workflows/2D_Breakout_game_Phaser/The_score", "Games/Workflows/2D_Breakout_game_Phaser/Extra_lives")}} +{{PreviousNext("Games/Tutorials/2D_breakout_game_Phaser/The_score", "Games/Tutorials/2D_breakout_game_Phaser/Extra_lives")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/bounce_off_the_walls/index.md b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/bounce_off_the_walls/index.md index afa8a17ca3c4a73..e87a4e9d3981c8e 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/bounce_off_the_walls/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/bounce_off_the_walls/index.md @@ -5,7 +5,7 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript/Move_the_ball", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Paddle_and_keyboard_controls")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Move_the_ball", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Paddle_and_keyboard_controls")}} This is the **3rd step** out of 10 of the [Gamedev Canvas tutorial](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript). You can find the source code as it should look after completing this lesson at [Gamedev-Canvas-workshop/lesson3.html](https://github.com/end3r/Gamedev-Canvas-workshop/blob/gh-pages/lesson03.html). @@ -164,4 +164,4 @@ runButton.addEventListener("click", () => { We've now got to the stage where our ball is both moving and staying on the game board. In the fourth chapter we'll look at implementing a controllable paddle — see [Paddle and keyboard controls](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Paddle_and_keyboard_controls). -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript/Move_the_ball", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Paddle_and_keyboard_controls")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Move_the_ball", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Paddle_and_keyboard_controls")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/build_the_brick_field/index.md b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/build_the_brick_field/index.md index a8cf5cf7d4effcf..ae323ee99441719 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/build_the_brick_field/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/build_the_brick_field/index.md @@ -5,7 +5,7 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript/Game_over", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Collision_detection")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Game_over", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Collision_detection")}} This is the **6th step** out of 10 of the [Gamedev Canvas tutorial](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript). You can find the source code as it would look after completing this lesson at [Gamedev-Canvas-workshop/lesson6.html](https://github.com/end3r/Gamedev-Canvas-workshop/blob/gh-pages/lesson06.html). @@ -254,4 +254,4 @@ runButton.addEventListener("click", () => { So now we have bricks! But the ball isn't interacting with them at all — we'll change that as we continue to the seventh chapter: [Collision detection](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Collision_detection). -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript/Game_over", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Collision_detection")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Game_over", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Collision_detection")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/collision_detection/index.md b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/collision_detection/index.md index 9559d7e199ed91b..fff79085aa2a018 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/collision_detection/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/collision_detection/index.md @@ -5,7 +5,7 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript/Build_the_brick_field", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Track_the_score_and_win")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Build_the_brick_field", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Track_the_score_and_win")}} This is the **7th step** out of 10 of the [Gamedev Canvas tutorial](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript). You can find the source code as it should look after completing this lesson at [Gamedev-Canvas-workshop/lesson7.html](https://github.com/end3r/Gamedev-Canvas-workshop/blob/gh-pages/lesson07.html). @@ -294,4 +294,4 @@ runButton.addEventListener("click", () => { We are definitely getting there now; let's move on! In the eighth chapter we will be looking at how to [Track the score and win](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Track_the_score_and_win). -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript/Build_the_brick_field", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Track_the_score_and_win")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Build_the_brick_field", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Track_the_score_and_win")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/create_the_canvas_and_draw_on_it/index.md b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/create_the_canvas_and_draw_on_it/index.md index 52dc17d57b76e5c..3929bf3deac691e 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/create_the_canvas_and_draw_on_it/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/create_the_canvas_and_draw_on_it/index.md @@ -5,7 +5,7 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Move_the_ball")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Move_the_ball")}} This is the **1st step** out of 10 of the [Gamedev Canvas tutorial](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript). You can find the source code as it should look after completing this lesson at [Gamedev-Canvas-workshop/lesson1.html](https://github.com/end3r/Gamedev-Canvas-workshop/blob/gh-pages/lesson01.html). @@ -145,4 +145,4 @@ ctx.closePath(); Now we've set up the basic HTML and learned a bit about canvas, lets continue to the second chapter and work out how to [Move the ball in our game](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Move_the_ball). -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Move_the_ball")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Move_the_ball")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/finishing_up/index.md b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/finishing_up/index.md index 8cb9eb4c42275f3..30447f801f07956 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/finishing_up/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/finishing_up/index.md @@ -5,7 +5,7 @@ page-type: guide sidebar: games --- -{{Previous("Games/Workflows/2D_Breakout_game_pure_JavaScript/Mouse_controls")}} +{{Previous("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Mouse_controls")}} This is the **10th and final step** of the [Gamedev Canvas tutorial](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript). You can find the source code as it should look after completing this lesson at [Gamedev-Canvas-workshop/lesson10.html](https://github.com/end3r/Gamedev-Canvas-workshop/blob/gh-pages/lesson10.html). @@ -300,4 +300,4 @@ You've finished all the lessons - congratulations! By this point, you should now You could also go back to [this tutorial series' index page](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript). Have fun coding! -{{Previous("Games/Workflows/2D_Breakout_game_pure_JavaScript/Mouse_controls")}} +{{Previous("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Mouse_controls")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/game_over/index.md b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/game_over/index.md index deac21fb54138c0..4760ee624994273 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/game_over/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/game_over/index.md @@ -5,7 +5,7 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript/Paddle_and_keyboard_controls", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Build_the_brick_field")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Paddle_and_keyboard_controls", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Build_the_brick_field")}} This is the **5th step** out of 10 of the [Gamedev Canvas tutorial](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript). You can find the source code as it should look after completing this lesson at [Gamedev-Canvas-workshop/lesson5.html](https://github.com/end3r/Gamedev-Canvas-workshop/blob/gh-pages/lesson05.html). @@ -198,4 +198,4 @@ runButton.addEventListener("click", () => { We're doing quite well so far and our game is starting to feel a lot more worth playing now that you can lose! But it is still missing something. Let's move on to the sixth chapter — [Build the brick field](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Build_the_brick_field) — and create some bricks for the ball to destroy. -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript/Paddle_and_keyboard_controls", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Build_the_brick_field")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Paddle_and_keyboard_controls", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Build_the_brick_field")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/index.md b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/index.md index 5c40731a730aa3f..f89da7bc304d9e6 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/index.md @@ -5,7 +5,7 @@ page-type: guide sidebar: games --- -{{Next("Games/Workflows/2D_Breakout_game_pure_JavaScript/Create_the_Canvas_and_draw_on_it")}} +{{Next("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Create_the_Canvas_and_draw_on_it")}} In this step-by-step tutorial we create an **MDN Breakout** game written entirely in pure JavaScript and rendered on HTML {{htmlelement("canvas")}}. @@ -41,4 +41,4 @@ Starting with pure JavaScript is the best way to get a solid knowledge of web ga Ok, let's get started! Head to the first chapter [Create the Canvas and draw on it](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Create_the_Canvas_and_draw_on_it). -{{Next("Games/Workflows/2D_Breakout_game_pure_JavaScript/Create_the_Canvas_and_draw_on_it")}} +{{Next("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Create_the_Canvas_and_draw_on_it")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/mouse_controls/index.md b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/mouse_controls/index.md index 58f7edd3983e563..19e96f87a2394ac 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/mouse_controls/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/mouse_controls/index.md @@ -5,7 +5,7 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript/Track_the_score_and_win", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Finishing_up")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Track_the_score_and_win", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Finishing_up")}} This is the **9th step** out of 10 of the [Gamedev Canvas tutorial](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript). You can find the source code as it should look after completing this lesson at [Gamedev-Canvas-workshop/lesson9.html](https://github.com/end3r/Gamedev-Canvas-workshop/blob/gh-pages/lesson09.html). @@ -232,4 +232,4 @@ runButton.addEventListener("click", () => { Now we've got a complete game we'll finish our series of lessons with some more small tweaks — [Finishing up](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Finishing_up). -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript/Track_the_score_and_win", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Finishing_up")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Track_the_score_and_win", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Finishing_up")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/move_the_ball/index.md b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/move_the_ball/index.md index 3bd0113e2d40ffa..4a14780accc3638 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/move_the_ball/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/move_the_ball/index.md @@ -5,7 +5,7 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript/Create_the_Canvas_and_draw_on_it", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Bounce_off_the_walls")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Create_the_Canvas_and_draw_on_it", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Bounce_off_the_walls")}} This is the **2nd step** out of 10 of the [Gamedev Canvas tutorial](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript). You can find the source code as it should look after completing this lesson at [Gamedev-Canvas-workshop/lesson2.html](https://github.com/end3r/Gamedev-Canvas-workshop/blob/gh-pages/lesson02.html). @@ -194,4 +194,4 @@ runButton.addEventListener("click", () => { We've drawn our ball and gotten it moving, but it keeps disappearing off the edge of the canvas. In the third chapter we'll explore how to make it [bounce off the walls](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Bounce_off_the_walls). -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript/Create_the_Canvas_and_draw_on_it", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Bounce_off_the_walls")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Create_the_Canvas_and_draw_on_it", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Bounce_off_the_walls")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/paddle_and_keyboard_controls/index.md b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/paddle_and_keyboard_controls/index.md index 83c63153bcff1ee..c646429b6d1217f 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/paddle_and_keyboard_controls/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/paddle_and_keyboard_controls/index.md @@ -5,7 +5,7 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript/Bounce_off_the_walls", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Game_over")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Bounce_off_the_walls", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Game_over")}} This is the **4th step** out of 10 of the [Gamedev Canvas tutorial](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript). You can find the source code as it should look after completing this lesson at [Gamedev-Canvas-workshop/lesson4.html](https://github.com/end3r/Gamedev-Canvas-workshop/blob/gh-pages/lesson04.html). @@ -226,4 +226,4 @@ runButton.addEventListener("click", () => { Now we have something resembling a game. The only trouble now is that you can just continue hitting the ball with the paddle and there's no winning or losing implemented. This will all change in the fifth chapter, [Game over](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Game_over), when we start to add in an endgame state for our game. -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript/Bounce_off_the_walls", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Game_over")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Bounce_off_the_walls", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Game_over")}} diff --git a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/track_the_score_and_win/index.md b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/track_the_score_and_win/index.md index b2ea2f2d7d8e5df..0ea1204daf0b6d5 100644 --- a/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/track_the_score_and_win/index.md +++ b/files/en-us/games/tutorials/2d_breakout_game_pure_javascript/track_the_score_and_win/index.md @@ -5,7 +5,7 @@ page-type: guide sidebar: games --- -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript/Collision_detection", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Mouse_controls")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Collision_detection", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Mouse_controls")}} This is the **8th step** out of 10 of the [Gamedev Canvas tutorial](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript). You can find the source code as it should look after completing this lesson at [Gamedev-Canvas-workshop/lesson8.html](https://github.com/end3r/Gamedev-Canvas-workshop/blob/gh-pages/lesson08.html). @@ -276,4 +276,4 @@ runButton.addEventListener("click", () => { The game looks pretty good at this point. In the next lesson you will broaden the game's appeal by adding [Mouse controls](/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Mouse_controls). -{{PreviousNext("Games/Workflows/2D_Breakout_game_pure_JavaScript/Collision_detection", "Games/Workflows/2D_Breakout_game_pure_JavaScript/Mouse_controls")}} +{{PreviousNext("Games/Tutorials/2D_Breakout_game_pure_JavaScript/Collision_detection", "Games/Tutorials/2D_Breakout_game_pure_JavaScript/Mouse_controls")}}