phaser3 微信游戏_如何使用Phaser 3和TypeScript在浏览器中构建简单的游戏

phaser3 微信游戏

I’m a developer advocate and a backend developer, and my frontend development expertise is relatively weak. A while ago I wanted to have some fun and make a game in a browser; I chose Phaser 3 as a framework (it looks quite popular these days) and TypeScript as a language (because I prefer static typing over dynamic). It turned out that you need to do some boring stuff to make it all work, so I wrote this tutorial to help the other people like me get started faster.

我是开发人员拥护者和后端开发人员,而我的前端开发专业知识相对较弱。 不久前,我想找点乐子,用浏览器玩游戏。 我选择了Phaser 3作为框架(目前看来很流行),而选择TypeScript作为一种语言(因为我更喜欢静态类型而不是动态类型)。 原来,您需要做一些无聊的工作才能使其全部正常工作,因此我编写了本教程来帮助像我这样的其他人更快地入门。

准备环境 (Preparing the environment)

集成开发环境 (IDE)

Choose your development environment. You can always use plain old Notepad if you wish, but I would suggest using something more helpful. As for me, I prefer developing pet projects in Emacs, therefore I have installed tide and followed the instructions to set it up.

选择您的开发环境。 如果愿意,您可以始终使用普通的旧记事本,但我建议您使用更有用的东西。 对于我来说,我更喜欢在Emacs中开发宠物项目,因此我已经安装了tide并按照说明进行了设置。

节点 (Node)

If we were developing on JavaScript, we would be perfectly fine to start coding without all these preparation steps. However, as we want to use TypeScript, we have to set up the infrastructure to make the future development as fast as possible. Thus we need to install node and npm.

如果我们使用JavaScript进行开发,那么无需所有这些准备步骤就可以开始编码。 但是,由于要使用TypeScript,我们必须设置基础结构以使将来的开发尽可能快。 因此,我们需要安装node和npm。

As I write this tutorial, I use node 10.13.0 and npm 6.4.1. Please note that the versions in the frontend world update extremely fast, so you simply take the latest stable versions. I strongly recommend using nvm instead of installing node and npm manually; it will save you a lot of time and nerves.

在编写本教程时,我使用的是节点10.13.0npm 6.4.1 。 请注意,前端世界中的版本更新速度非常快,因此您只需使用最新的稳定版本。 我强烈建议您使用nvm而不是手动安装node和npm; 它将为您节省大量时间和精力。

设置项目 (Setting up the project)

项目结构 (Project structure)

We will use npm for building the project, so to start the project go to an empty folder and run npm init. npm will ask you several questions about your project properties and then create a package.json file. It will look something like this:

我们将使用npm来构建项目,因此要启动项目,请转到一个空文件夹并运行npm init 。 npm将询问您有关项目属性的几个问题,然后创建一个package.json文件。 它看起来像这样:

{
  "name": "Starfall",
  "version": "0.1.0",
  "description": "Starfall game (Phaser 3 + TypeScript)",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Mariya Davydova",
  "license": "MIT"
}
配套 (Packages)

Install the packages we need with the following command:

使用以下命令安装我们需要的软件包:

npm install -D typescript webpack webpack-cli ts-loader phaser live-server

npm install -D typescript webpack webpack-cli ts-loader phaser live-server

-D option (a.k.a. --save-dev) makes npm add these packages to the list of dependencies in package.json automatically:

-D选项(aka --save-dev )使npm自动将这些软件包添加到package.json中的依赖项列表中:

"devDependencies": {
   "live-server": "^1.2.1",
   "phaser": "^3.15.1",
   "ts-loader": "^5.3.0",
   "typescript": "^3.1.6",
   "webpack": "^4.26.0",
   "webpack-cli": "^3.1.2"
 }
Webpack (Webpack)

Webpack will run the TypeScript compiler and collect the bunch of resulting JS files as well as libraries into one minified JS so that we can include it in our page.

Webpack将运行TypeScript编译器并将一堆生成的JS文件和库收集到一个缩小的JS中,以便我们可以将其包含在页面中。

Add webpack.config.js near your project.json:

在您的project.json附近添加webpack.config.js

const path = require('path');
module.exports = {
  entry: './src/app.ts',
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/
      }
    ]
  },
  resolve: {
    extensions: [ '.ts', '.tsx', '.js' ]
  },
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, 'dist')
  },
  mode: 'development'
};

Here we see that webpack has to get the sources starting from src/app.ts(which we’ll add very soon) and collect everything in dist/app.js file.

在这里,我们看到webpack必须从src/app.ts (我们将很快添加)开始获取源并将所有内容收集在dist/app.js文件中。

打字稿 (TypeScript)

We also need a small configuration file for the TypeScript compiler (tsconfig.json) where we explain which JS version we want the sources to be compiled to and where to find those sources:

我们还需要一个用于TypeScript编译器的小型配置文件( tsconfig.json ),在其中解释我们希望将源编译到哪个JS版本以及在哪里找到这些源:

{
  "compilerOptions": {
    "target": "es5"
  },
  "include": [
    "src/*"
  ]
}
TypeScript定义 (TypeScript definitions)

TypeScript is a statically typed language. Therefore, it requires type definitions for the compilation. At the time of writing this tutorial, the definitions for Phaser 3 were not yet available as the npm package, so you may need to download them from the official repository and put the file in the src subdirectory of your project.

TypeScript是一种静态类型的语言。 因此,它需要用于编译的类型定义。 在撰写本教程时,相位器3的定义尚未作为npm软件包提供,因此您可能需要从官方存储库下载它们并将其放置在项目的src子目录中。

剧本 (Scripts)

We have almost finished the project set up. At this moment you should have created package.json, webpack.config.js, and tsconfig.json, and added src/phaser.d.ts. The last thing we need to do before starting to write code is to explain what exactly npm has to do with the project. We update the scripts section of the package.json as follows:

我们几乎完成了项目的设置。 此时,您应该已经创建了package.jsonwebpack.config.jstsconfig.json ,并添加了src/phaser.d.ts 。 在开始编写代码之前,我们需要做的最后一件事是解释npm与项目到底有什么关系。 我们如下更新package.jsonscripts部分:

"scripts": {
  "build": "webpack",
  "start": "webpack --watch & live-server --port=8085"
}

When you execute npm build, the app.js file will be built according to the webpack configuration. And when you run npm start, you won’t have to bother about the build process: as soon as you save any source, webpack will rebuild the app and the live-server will reload it in your default browser. The app will be hosted at http://127.0.0.1:8085/.

当您执行npm build ,将根据webpack配置来构建app.js文件。 并且当您运行npm start ,您不必费心构建过程:保存任何源代码后,webpack都会重新构建该应用程序,而实时服务器将在您的默认浏览器中重新加载它。 该应用程序将托管在http://127.0.0.1:8085/

入门 (Getting started)

Now that we have set up the infrastructure (the part I personally hate when starting a project), we can finally start coding. In this step we’ll do a straightforward thing: draw a dark blue rectangle in our browser window. Using a big game development framework for this is a little bit of… hmmm… overkill. Still, we’ll need it on the next steps.

既然我们已经设置了基础架构(启动项目时我个人讨厌的部分),我们终于可以开始编码了。 在这一步中,我们将做一个简单的事情:在浏览器窗口中绘制一个深蓝色的矩形。 为此使用大型游戏开发框架有点…嗯…太过分了。 不过,我们将在后续步骤中使用它。

Let me briefly explain the main concepts of Phaser 3. The game is an instance of the Phaser.Game class (or its descendant). Each game contains one or more instances of Phaser.Scene descendants. Each scene contains several objects, either static or dynamic, and represents a logical part of the game. For example, our trivial game will have three scenes: the welcome screen, the game itself, and the score screen.

让我简要解释一下Phaser 3的主要概念。游戏是Phaser.Game类(或其后代)的实例。 每个游戏都包含一个或多个Phaser.Scene后代实例。 每个场景都包含多个静态或动态对象,并代表游戏的逻辑部分。 例如,我们的琐碎游戏将具有三个场景:欢迎屏幕,游戏本身和得分屏幕。

Let’s start coding.

让我们开始编码。

First, create a minimalistic HTML container for the game. Make an index.html file, which contains the following code:

首先,为游戏创建一个简约HTML容器。 制作一个index.html文件,其中包含以下代码:

<!DOCTYPE html>
<html>
  <head>
    <title>Starfall</title>
    <script src="dist/app.js"></script>
  </head>
  <body>
    <div id="game"></div>
  </body>
</html>

There are only two essential parts here: the first one is a script entry which says that we are going to use our built file here, and the second one is a div entry which will be the game container.

这里只有两个基本部分:第一个是script条目,它表示我们将在这里使用构建的文件,第二个是div条目,它将是游戏容器。

Now create a file src/app.ts with the following code:

现在,使用以下代码创建文件src/app.ts

import "phaser";
const config: GameConfig = {
  title: "Starfall",
  width: 800,
  height: 600,
  parent: "game"
  backgroundColor: "#18216D"
};
export class StarfallGame extends Phaser.Game {
  constructor(config: GameConfig) {
    super(config);
  }
}
window.onload = () => {
  var game = new StarfallGame(config);
};

This code is self-explanatory. GameConfig has a lot of various properties, you can check them out here .

此代码是不言自明的。 GameConfig具有很多各种属性,您可以在此处查看它们。

And now you can finally run npm start. If everything was done correctly on this and previous steps, you should see something as simple as this in your browser:

现在,您终于可以运行npm start 。 如果在此步骤和之前的步骤中所有操作都正确完成,您应该在浏览器中看到以下内容:

使星星坠落 (Making the stars fall)

We have created an elementary application. Now it’s time to add a scene where something will happen. Our game will be simple: the stars will fall to the ground, and the goal will be to catch as many as possible.

我们已经创建了基本应用程序。 现在是时候添加将要发生事情的场景了。 我们的游戏很简单:星星会掉落到地面,目标是尽可能多地捕捉。

To achieve this goal create a new file, gameScene.ts, and add the following code:

为了实现此目标,请创建一个新文件gameScene.ts ,并添加以下代码:

import "phaser";
export class GameScene extends Phaser.Scene {
constructor() {
    super({
      key: "GameScene"
    });
  }
init(params): void {
    // TODO
  }
preload(): void {
    // TODO
  }
  
  create(): void {
    // TODO
  }
update(time): void {
    // TODO
  }
};

Constructor here contains a key under which other scenes may call this scene.

此处的构造方法包含一个键,在该键下其他场景可以称为该场景。

You see here stubs for four methods. Let me briefly explain the difference between then:

您在这里看到四种方法的存根。 让我简要解释一下它们之间的区别:

  • init([params]) is called when the scene starts; this function may accept parameters, which are passed from other scenes or game by calling scene.start(key, [params])

    场景开始时调用init([params]) ; 此函数可以接受通过调用scene.start(key, [params])从其他场景或游戏传递的scene.start(key, [params])

  • preload() is called before the scene objects are created, and it contains loading assets; these assets are cached, so when the scene is restarted, they are not reloaded

    在创建场景对象之前调用preload() ,它包含加载资源; 这些资产已缓存,因此当场景重新启动时,不会重新加载它们

  • create() is called when the assets are loaded and usually contains creation of the main game objects (background, player, obstacles, enemies, etc.)

    当加载资产时调用create()并且通常包含主要游戏对象(背景,玩家,障碍物,敌人等)的创建

  • update([time]) is called every tick and contains the dynamic part of the scene — everything that moves, flashes, etc.

    update([time])被称为每个滴答,并且包含场景的动态部分,包括所有移动,闪烁等。

To be sure that we don’t forget it later, let’s quickly add the following lines in the game.ts:

为确保我们以后不会忘记它,让我们快速在game.ts添加以下几行:

import "phaser";
import { GameScene } from "./gameScene";
const config: GameConfig = {
  title: "Starfall",
  width: 800,
  height: 600,
  parent: "game",
  scene: [GameScene],
  physics: {
    default: "arcade",
    arcade: {
      debug: false
    }
  },
  backgroundColor: "#000033"
};
...

Our game now knows about the game scene. If the game config contains a list of scenes then the first one is started when the game is begun, and all others are created but not started until explicitly called.

我们的游戏现在了解游戏场景。 如果游戏配置包含场景列表,则第一个场景将在游戏开始时启动,所有其他场景都会创建,但直到明确调用后才开始。

We have also added arcade physics here. It is required to make our stars fall.

我们还在这里添加了街机物理。 这是使我们的星星坠落所必需的。

Now we can put flesh on the bones of our game scene.

现在,我们可以在游戏场景的骨骼上放些肉了。

First, we declare some properties and objects we’re gonna need:

首先,我们声明一些我们需要的属性和对象:

export class GameScene extends Phaser.Scene {
  delta: number;
  lastStarTime: number;
  starsCaught: number;
  starsFallen: number;
  sand: Phaser.Physics.Arcade.StaticGroup;
  info: Phaser.GameObjects.Text;
...

Then, we initialize numbers:

然后,我们初始化数字:

init(/*params: any*/): void {
    this.delta = 1000;
    this.lastStarTime = 0;
    this.starsCaught = 0;
    this.starsFallen = 0;
  }

Now, we load a couple of images:

现在,我们加载几个图像:

preload(): void {
    this.load.setBaseURL(
      "https://raw.githubusercontent.com/mariyadavydova/" +
      "starfall-phaser3-typescript/master/");
    this.load.image("star", "assets/star.png");
    this.load.image("sand", "assets/sand.jpg");
  }

After that, we can prepare our static components. We will create the ground, where the stars will fall, and the text informing us about the current score:

之后,我们可以准备我们的静态组件。 我们将创建地面,星星将坠落,文本将通知我们当前得分:

create(): void {
    this.sand = this.physics.add.staticGroup({
      key: 'sand',
      frameQuantity: 20
    });
    Phaser.Actions.PlaceOnLine(this.sand.getChildren(),
      new Phaser.Geom.Line(20, 580, 820, 580));
    this.sand.refresh();
this.info = this.add.text(10, 10, '',
      { font: '24px Arial Bold', fill: '#FBFBAC' });
  }

A group in Phaser 3 is a way to create a bunch of the objects you want to control together. There two types of objects: static and dynamic. As you may guess, static objects don’t move (ground, walls, various obstacles), while dynamic ones do the job (Mario, ships, missiles).

Phaser 3中的组是一种创建要一起控制的对象的方法。 有两种类型的对象:静态和动态。 您可能会猜到,静态物体不会移动(地面,墙壁,各种障碍物),而动态物体会移动(马里奥,船舶,导弹)。

We create a static group of the ground pieces. Those pieces are placed along the line. Please note that the line is divided into 20 equal sections (not 19 as you’ve may have expected), and the ground tiles are placed on each section at the left end with the tile center located at that point (I hope this explains those numbers). We also have to call refresh() to update the group bounding box (otherwise, the collisions will be checked against the default location, which is the top left corner of the scene).

我们创建一组静态的地面部分。 这些部分沿线放置。 请注意,这条线分为20个相等的部分(而不是您期望的19个部分),地砖放在左端的每个部分上,而地砖中心位于该点(我希望这可以解释这些数字)。 我们还必须调用refresh()来更新组边界框(否则,将对照默认位置(即场景的左上角)检查冲突。

If you check out your application in the browser now, you should see something like this:

如果您现在在浏览器中签出您的应用程序,应该会看到类似以下内容:

We have finally reached the most dynamic part of this scene — update() function, where the stars fall. This function is called somewhere around once in 60 ms. We want to emit a new falling star every second. We won’t use a dynamic group for this, as the lifecycle of each star will be short: it will be destroyed either by user click or by colliding with the ground. Therefore inside the emitStar() function we create a new star and add the processing of two events: onClick() and onCollision().

我们终于到达了场景中最有活力的部分update()函数,星星落在了那里。 每60毫秒调用一次此功能。 我们想每秒发射一颗新的流星。 我们不会为此使用动态组,因为每颗恒星的生命周期都很短:用户单击或与地面碰撞都将破坏它。 因此,在emitStar()函数内部,我们创建了一个新的星形,并添加了两个事件的处理: onClick()onCollision()

update(time: number): void {
    var diff: number = time - this.lastStarTime;
    if (diff > this.delta) {
      this.lastStarTime = time;
      if (this.delta > 500) {
        this.delta -= 20;
      }
      this.emitStar();
    }
    this.info.text =
      this.starsCaught + " caught - " +
      this.starsFallen + " fallen (max 3)";
  }
private onClick(star: Phaser.Physics.Arcade.Image): () => void {
    return function () {
      star.setTint(0x00ff00);
      star.setVelocity(0, 0);
      this.starsCaught += 1;
      this.time.delayedCall(100, function (star) {
        star.destroy();
      }, [star], this);
    }
  }
private onFall(star: Phaser.Physics.Arcade.Image): () => void {
    return function () {
      star.setTint(0xff0000);
      this.starsFallen += 1;
      this.time.delayedCall(100, function (star) {
        star.destroy();
      }, [star], this);
    }
  }
private emitStar(): void {
    var star: Phaser.Physics.Arcade.Image;
    var x = Phaser.Math.Between(25, 775);
    var y = 26;
    star = this.physics.add.image(x, y, "star");
star.setDisplaySize(50, 50);
    star.setVelocity(0, 200);
    star.setInteractive();
star.on('pointerdown', this.onClick(star), this);
    this.physics.add.collider(star, this.sand, 
      this.onFall(star), null, this);
  }

Finally, we have a game! It doesn’t have a win condition yet. We’ll add it in the last part of our tutorial.

最后,我们有一个游戏! 它还没有获胜条件。 我们将其添加到本教程的最后一部分。

包装全部 (Wrapping it all up)

Usually, a game consists of several scenes. Even if the gameplay is simple, you need an opening scene (containing at the very least the ‘Play!’ button) and a closing one (showing the result of your game session, like the score or the maximum level reached). Let’s add these scenes to our application.

通常,一个游戏包含几个场景。 即使游戏很简单,您也需要一个开场场景(至少包含“播放!”按钮)和一个结束场景(显示游戏过程的结果,例如得分或达到的最高等级)。 让我们将这些场景添加到我们的应用程序中。

In our case, they will be pretty similar, as I don’t want to pay too much attention to the graphic design of the game. After all, this a programming tutorial.

在我们的例子中,它们将非常相似,因为我不想过多关注游戏的图形设计。 毕竟,这是编程指南。

The welcome scene will have the following code in welcomeScene.ts. Note that when a user clicks somewhere on this scene, a game scene will appear.

在欢迎场景中, welcomeScene.ts中将包含以下代码。 请注意,当用户单击此场景上的某个位置时,将出现一个游戏场景。

import "phaser";
export class WelcomeScene extends Phaser.Scene {
  title: Phaser.GameObjects.Text;
  hint: Phaser.GameObjects.Text;
constructor() {
    super({
      key: "WelcomeScene"
    });
  }
create(): void {
    var titleText: string = "Starfall";
    this.title = this.add.text(150, 200, titleText,
      { font: '128px Arial Bold', fill: '#FBFBAC' });
var hintText: string = "Click to start";
    this.hint = this.add.text(300, 350, hintText,
      { font: '24px Arial Bold', fill: '#FBFBAC' });
this.input.on('pointerdown', function (/*pointer*/) {
      this.scene.start("GameScene");
    }, this);
  }
};

The score scene will look almost the same, leading to the welcome scene on click (scoreScene.ts).

得分场景看起来几乎相同,从而导致单击时出现欢迎场景( scoreScene.ts )。

import "phaser";
export class ScoreScene extends Phaser.Scene {
  score: number;
  result: Phaser.GameObjects.Text;
  hint: Phaser.GameObjects.Text;
constructor() {
    super({
      key: "ScoreScene"
    });
  }
init(params: any): void {
    this.score = params.starsCaught;
  }
create(): void {
    var resultText: string = 'Your score is ' + this.score + '!';
    this.result = this.add.text(200, 250, resultText,
      { font: '48px Arial Bold', fill: '#FBFBAC' });
var hintText: string = "Click to restart";
    this.hint = this.add.text(300, 350, hintText,
      { font: '24px Arial Bold', fill: '#FBFBAC' });
this.input.on('pointerdown', function (/*pointer*/) {
      this.scene.start("WelcomeScene");
    }, this);
  }
};

We need to update our main application file now: add these scenes and make the WelcomeScene to be the first in the list:

我们现在需要更新我们的主应用程序文件:添加这些场景并使WelcomeScene成为列表中的第一个:

import "phaser";
import { WelcomeScene } from "./welcomeScene";
import { GameScene } from "./gameScene";
import { ScoreScene } from "./scoreScene";
const config: GameConfig = {
  ...
  scene: [WelcomeScene, GameScene, ScoreScene],
  ...

Have you noticed what is missing? Right, we do not call the ScoreScene from anywhere yet! Let’s call it when the player has missed the third star:

您是否注意到缺少的内容? 是的,我们还没有从任何地方调用ScoreScene ! 当玩家错过第三颗星时,我们称之为:

private onFall(star: Phaser.Physics.Arcade.Image): () => void {
    return function () {
      star.setTint(0xff0000);
      this.starsFallen += 1;
      this.time.delayedCall(100, function (star) {
        star.destroy();
        if (this.starsFallen > 2) {
          this.scene.start("ScoreScene", 
            { starsCaught: this.starsCaught });
        }
      }, [star], this);
    }
  }

Finally, our Starfall game looks like a real game — it starts, ends, and even has a goal to archive (how many stars can you catch?).

最终,我们的Starfall游戏看起来像是一个真实的游戏-它开始,结束,甚至还有存档的目标(您可以捕捉多少颗星?)。

I hope this tutorial is as useful for you as it was for me when I wrote it :) Any feedback is highly appreciated!

希望本教程对您一样有用,对我而言也是如此:)任何反馈都将受到赞赏!

The source code for this tutorial may be found here.

本教程的源代码可以在这里找到。

翻译自: https://www.freecodecamp.org/news/how-to-build-a-simple-game-in-the-browser-with-phaser-3-and-typescript-bdc94719135/

phaser3 微信游戏

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值