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.  ## 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 } + -
- - +