此页面由社区从英文翻译而来。了解更多并加入 MDN Web Docs 社区。

View in English Always switch to English

构建砖块区域

这是使用 Phaser 创建打砖块游戏教程 12 个步骤中的第 7 步。让我们探索如何创建一组砖块,通过循环将它们绘制到屏幕上,并在球击中它们时将其移除。构建砖块区域比向屏幕添加单个对象稍微复杂一些,不过使用 Phaser 实现可能比使用纯 JavaScript 更简单。

新属性

首先,在之前的属性定义下方添加新的 bricks 属性:

js
class ExampleScene extends Phaser.Scene {
  // ……之前的属性定义……
  bricks;
  // ……类的其余部分……
}

bricks 属性将用于创建一组砖块,让我们可以同时管理多个砖块。

渲染砖块图像

接下来,加载砖块图像——在其他 load.image() 调用下方添加以下调用:

js
class ExampleScene extends Phaser.Scene {
  // ...
  preload() {
    // ...
    this.load.image("brick", "img/brick.png");
  }
  // ...
}

你还需要下载砖块图像,并将其保存在你的 /img 目录中。

绘制砖块

我们会将绘制砖块的所有代码放在 initBricks 方法中,使其与其余代码分离。在 create() 方法末尾添加对 initBricks 的调用:

js
class ExampleScene extends Phaser.Scene {
  // ...
  create() {
    // ...
    this.initBricks();
  }
  // ...
}

现在来编写方法本身。在 ExampleScene 类的末尾、右花括号 } 之前添加 initBricks 方法,如下所示。首先,添加 bricksLayout 对象,它很快就会派上用场:

js
class ExampleScene extends Phaser.Scene {
  // ...
  initBricks() {
    const bricksLayout = {
      width: 50,
      height: 20,
      count: {
        row: 3,
        col: 7,
      },
      offset: {
        top: 50,
        left: 60,
      },
      padding: 10,
    };
  }
}

bricksLayout 保存了我们需要的所有信息:单个砖块的宽度和高度、屏幕上砖块的行数和列数、顶部和左侧的偏移量(开始绘制砖块的画布位置),以及各行和各列砖块之间的间距。

现在,让我们开始创建砖块本身——首先,在 initBricks() 方法底部添加以下行,创建一个空组来容纳砖块:

js
this.bricks = this.add.group();

我们可以循环遍历行和列,在每次迭代中创建一个新砖块——在上一行代码下方添加以下嵌套循环:

js
for (let c = 0; c < bricksLayout.count.col; c++) {
  for (let r = 0; r < bricksLayout.count.row; r++) {
    // 创建新砖块并将其添加到组中
  }
}

这样,我们就能创建所需数量的砖块,并将它们全部放在一个组中。现在,需要在嵌套循环结构中添加一些代码来绘制每个砖块。按如下所示填入内容:

js
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);
  }
}

这里,我们循环遍历行和列,创建新砖块并将其放在屏幕上。为新创建的砖块启用 Arcade 物理引擎,将其物理体设置为不可移动(这样它就不会在被球击中时移动),然后将其添加到组中。

目前的问题是,所有砖块都绘制在同一个位置,即坐标 (0, 0) 处。我们需要将每个砖块绘制在各自的 x 和 y 位置。按如下所示更新 brickX 和 brickY 所在的行:

js
const brickX =
  c * (bricksLayout.width + bricksLayout.padding) + bricksLayout.offset.left;
const brickY =
  r * (bricksLayout.height + bricksLayout.padding) + bricksLayout.offset.top;

每个 brickX 位置的计算方式是:将 bricksLayout.width 与 bricksLayout.padding 相加,乘以列号 c,再加上 bricksLayout.offset.left。brickY 的计算逻辑相同,只是使用行号 r、bricksLayout.height 和 bricksLayout.offset.top。现在,每个砖块都能放在正确的位置,砖块之间留有间距,整个砖块区域与画布左侧和顶部边缘之间也留有偏移量。

此时重新加载 index.html,你应该能看到砖块已绘制到屏幕上,彼此之间的间距均匀。

砖块与球的碰撞检测

接下来是另一个挑战——检测球与砖块之间的碰撞。幸运的是,我们不仅可以使用物理引擎检查两个单独对象(例如球和球板)之间的碰撞,还可以检查对象与组之间的碰撞。

首先,在 update() 方法中添加一行代码,检测球与砖块之间的碰撞,如下所示:

js
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;
    // ...
  }
  // ...
}

这里会将球的位置与组中所有砖块的位置进行比较。第三个参数是可选的,用于指定发生碰撞时执行的函数。Phaser 调用此函数时会传入两个参数:第一个是球,也就是我们显式传给 collide 方法的对象;第二个是砖块组中与球发生碰撞的那个砖块。这里,我们在名为 hitBrick() 的方法中实现相应行为。在 ExampleScene 类的末尾、右花括号 } 之前创建这个新方法,如下所示:

js
class ExampleScene extends Phaser.Scene {
  // ...
  hitBrick(ball, brick) {
    brick.destroy();
  }
}

这样就完成了!重新加载代码,你应该能看到新的碰撞检测按预期工作。

比较你的代码

以下是你目前应该得到的结果,可以实时运行。若要查看其源代码,请点击“运行”按钮。

下一步

现在我们已经可以击中并移除砖块,这让游戏玩法更加丰富。如果能记录分数并在所有砖块被摧毁时获胜,游戏就会更有趣。