使用Phaser来开发我的炉石传说(一)

作为一位骨灰级的炉石玩家,本人在上个月最终还是脱坑了。因为各个版本的改动,卡组的各种退环境,导致这个游戏的平衡性已经一塌糊涂。所以,这里我们自己来试着开发一款属于自己的炉石游戏,缅怀下当年初入坑时的心情。

一.关于引擎的选择

市面上的游戏引擎现在是数不胜数。不管你是做网页游戏,还是说做桌面游戏,还是做手机游戏,总能找到与之对应的各种引擎。作为一名jser,我们这里还是优先选择web上能用的游戏引擎,也就是做html5的炉石小游戏。

引擎方面就如我们标题所说的,选择国外的比较不错的phaser,国内目前比较好的中文站点是phaser小站,站内有不少的实例demo,只是中文api还不是很齐全,作为初步的学习研究还是足够的。

这里也说明下,笔者也是最近才开始接触这款引擎,对其有所了解之后,觉得引擎提供的功能还是相当不错的,相较于市面上的其他引擎,整个开发的理念更为合理一点。更便于我们的理解。

二.搭建环境

这里我们使用gulp作为自动化开发工具,所有的源码都放在了github上,大家可以自行下载安装相应的模块。

然后建立如下的目录结构:

image

/dist 运行文件的发布目录
/modules 游戏的各个模块
/resource 游戏所需要的资源
外层的index.html和main.js是整个游戏的入口。整个目录搭建完成之后,我们就可以开始我们的开发了。

首先在文件根目录下使用

1
npm install

安装所有的依赖。然后打开index.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

<!DOCTYPE html>
<html>

<head>
<title>Phaser HearthStone</title>
<style>
#mainCanvas {
width: 100%;
height: 100%;
}

#mainCanvas canvas {
margin: 0 auto;
}
</style>
</head>

<body>
<div id="mainCanvas"></div>
<script src="resource/phaser.js"></script>
<script src="./dist/main.js"></script>
</body>

</html>

注意我们最后加载的两个script,一个是phaser游戏的依赖库,另外一个是我们生成在dist目录下的mian.js文件。以后我们在modules和入口文件mian.js中所写的js代码,都将通过gulp打包生成为dist目录下的main.js文件,index.html完成之后将不再需要我们的修改了。

现在我们打开main.js。在这里我们要开始写我们的第一行phaser代码了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'mainCanvas', { preload:preload,create: create,update:update });

function preload(){
console.log("preload")
}

function create() {
console.log("create");
}

function update(){
console.log("update");
}

这里我们使用

1
gulp default

命令启动整个服务(gulp的配置我在gulpfile.js中都已经配置好了,如果不懂gulp可以自行百度一下,挺简单的)。然后在浏览器中打开控制台,看看都执行了一些什么。

这里有一点要注意下,phaser的运行环境必须在服务器环境下,有很多人喜欢在本地直接通过双击文件来打开html文件,这个时候phaser是无法对浏览器进行渲染的,而且会有相应的报错。

浏览器会自动打开http://127.0.0.1:8000 打开控制台,可以看到相应的输出:
preload create 以及正在无限重复的upadate。这个就是phaser中一个游戏场景的生命周期。preload是场景中首先执行的,然后是create最后是无限开始循环的upadate。更多的场景周期的用法我们可以看这里。我们目前只需要使用最基础的这三个就可以了。

三.游戏场景的设置

我们经常在玩一款游戏时,登陆游戏是一个界面,登陆后进入了游戏的主界面,在游戏结束之后,又会有一个结算的界面。从以前的fc魂斗罗,马里奥,到现在的lol等等,差不多都是沿袭了这一界面跳转的风格。这里的界面,我们就可以用phaser中的场景来理解。当需要进入不同的游戏环境时,我们就用不同的游戏场景来对其进行渲染展示。

打开我们的modules目录,在里面新建一个叫scenes的文件夹,用来专门存放我们写好的场景文件。

这里我们新建第一个场景,在scenes目录下建立StartScene.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

// 游戏开始场景
function StartScene(game) {
this.preload = function () {
console.log("preload");
}

this.create = function () {
console.log("create");
}

this.update = function(){
console.log("update");
}
}

module.exports = StartScene;

然后我们打开最外层的mian.js文件,我们做一些修改,引入这个模块,并把这个开始的模块作为场景放进我们的游戏世界中去。

1
2
3
4
5
6

var StartScene = require("./modules/scenes/StartScene"); // 导入我们的开始场景

var game = new Phaser.Game(800, 600, Phaser.AUTO,'mainCanvas', {},true);
game.state.add("StartScene",StartScene); // 游戏开始场景
game.state.start("StartScene");

我们再打开浏览器,看看控制台中输出的字符串是不是和之前一摸一样的。

这里得要说下phaser的场景管理了。phaser中有一个state对象,这个state对象可以是一个object,也可以是一个function(所以说,将StartScene.js中的

1
function GameScene(){}

换成

1
var GameScene = {}

是完全可行的),每一个state对象至少要包含我们前面所说的create,preload,update中三个方法的一个。 不然phaser会提出相应的警告。

场景制作完了之后,我们还需再游戏的入口去添加这个场景到game对象中去,game.state.add()方法就是做这个事情,前一个参数是这个场景的key,后一个参数就是这个场景对象。

最后通过game.state.start(场景别名)来进入你所需要的场景。

说完这里我们对整个游戏的场景有了相关的认识了。接下来我们就要去这个StartScene.js中加点东西,让他跑起来了。

打开StartScene.js,找到create函数,往里面添加:

1
2
3
4
5
6
7
8

var style = {
fill: "#000",
fontSize: "32pt"
}
var text = game.add.text(game.world.centerX, game.world.centerY, "欢迎来到我的炉石传说", style);

text.anchor.set(0.5);

打开浏览器,会出现相应的输出。

这里我们定义了一个text,使用game.add.text()方法来初始化它,我们在参数中定义它的初始位置,初始字符串,已经相应的显示样式(这里的style对象,有点像css的风格)。最后我们设定了它的锚点位置(关于什么是锚点,可以参看官方api)。

phaser通过Phaser.Text类来展示文字对象。其中还有Phaser.Image来显示图片对象,还有更高级的例如Sprite,Button,Spine对象等。

ok,我们继续。继续编辑create函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

function StartScene(game) {
this.preload = function () {
}

this.create = function () {
var style = {
fill: "#000",
fontSize: "32pt"
}
var text = game.add.text(game.world.centerX, game.world.centerY, "欢迎来到我的炉石传说", style);

text.anchor.set(0.5);

var startButton = game.add.text(game.world.centerX, game.world.centerY + 70, "开始游戏", { fill: "#333", fontSize: "24pt" });

startButton.anchor.set(0.5);

startButton.inputEnabled = true;
startButton.events.onInputDown.add(function () {
game.state.start("GameScene");
},this);
}
}

module.exports = StartScene;

我们在这里实例化了一个新的text对象,而且给它设置了点击事件,使其可以进行场景间的跳转。所以我们需要在scenes中去创建一个新的场景,并在入口main.js中进行场景注册。

OK,进入modules/scenes文件夹,然后建立一个新的场景文件:GameScene.js:

1
2
3
4
5
6
7
8
9
10
11

function GameScene(game) {
this.preload = function () {

}
this.create = function () {
game.add.text(100,100,"这是游戏场景",{});
}
}

module.exports = GameScene;

同样的方法,不过我们只显示相关的提示,来区别下这两个不同的场景。

最后打开main.js,加入相应的场景引入就可以了。

1
2
3
4
5
6
7
8
9

var StartScene = require("./modules/scenes/StartScene");
var GameScene = require("./modules/scenes/GameScene");

var game = new Phaser.Game(800, 600, Phaser.AUTO,'mainCanvas', {},true);

game.state.add("StartScene",StartScene); // 游戏开始场景
game.state.add("GameScene",GameScene); // 游戏场景
game.state.start("StartScene");

至此,我们关于游戏场景的基本设置就做完了。我们可以在开始游戏的场景通过点击按钮来跳转到下一个游戏场景。接下来我们要做的事情就是资源的加载与游戏的主逻辑开发。

四.资源的加载

这里事先声明,本示例中所有使用的资源请不要用于商业运用。虽然很多都是出自本人的手笔,但版权都是归属暴雪。如果大家喜欢,请支持正版的炉石传说,这里仅做学习phaser游戏开发而使用。

那么接下来我们把所有的文件导入到项目之中。本文中所有使用的图片资源都可以在本人的github上找到,欢迎star,欢迎fork。

好的,现在回归正题。

phaser的资源加载很大一部分都是在场景中的preload函数中进行的。只有在资源完全加载成果之后才会开始执行下一步,也就是开始执行create函数。我们也采用这样的方式。但是,如果在游戏一打开就咬加载大量的资源,大家难免会觉得难以等待,尤其是在web端。可能等待时间稍长,玩家就已经失去了耐心。所以我们在这里选择在GameScene中加载文件资源,而不是在游戏最先载入的StartScene中加载。

打开GameScene.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

function GameScene(game) {
this.preload = function () {
// 加载提示声明
var loadText = game.add.text(game.world.centerX,game.world.centerY,"Loading ... ",{fill:"#333","fontSize":"28pt"});

// 锚点设置
loadText.anchor.set(0.5);

game.load.image("background","../../resource/background.png");
game.load.image("card_back","../../resource/card_back.png");
game.load.image("dead_wing","../../resource/dead_wing.png");
game.load.image("dead_wing_fight","../../resource/dead_wing_fight.png");
game.load.image("enemy_turn_button","../../resource/enemy_turn_button.png");
game.load.image("fee","../../resource/fee.png");
game.load.image("fighter_hero","../../resource/fighter_hero.png");
game.load.image("fishman_baby","../../resource/fishman_baby.png");
game.load.image("fishman_baby_fight","../../resource/fishman_baby_fight.png");
game.load.image("freshwater_crocodile","../../resource/freshwater_crocodile.png");
game.load.image("freshwater_crocodile_fight","../../resource/freshwater_crocodile_fight.png");
game.load.image("hero_turn_button","../../resource/hero_turn_button.png");
game.load.image("hp_background","../../resource/hp_background.png");
game.load.image("ogre","../../resource/ogre.png");
game.load.image("ogre_fight","../../resource/ogre_fight.png");
game.load.image("attack_icon","../../resource/attack_icon.png");

// 单个文件加载完的回调
game.load.onFileComplete.add(function(){
loadText.setText("Loading ... "+arguments[0]);
});

// 所有文件加载完成回调
game.load.onLoadComplete.add(function(){
loadText.destroy();
});

}
this.create = function () {
game.add.text(100,100,"这是游戏场景",{});
}
}

module.exports = GameScene;

我们首先设置了一个提示文本,用来显示当前加载的进度。然后使用game.load.onFileComplete.add()这个方法来做好每个资源加载后的回调,也就是更新我们的加载进度条,最后在整个资源加载完成之后用game.load.onLoadComplete.add()方法来销毁我们的提示字符串。

现在资源的加载模块我们已经做完了。我们需要一个背景类来管理我们的背景,还需要一个UI类来管理我们的其他各种ui组件。在class文件夹下面我们先创建一个BackGround.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

/**
* 背景类
*/

function BackGround(game){
this.picObj = null;
this.init(game);
}

BackGround.prototype.init = function(game){
this.picObj = this.setPic(game);

}

BackGround.prototype.setPic = function(game){
var background = game.add.image(0,0,"background");
return background;
}

module.exports = BackGround;

背景类搞定之后,我们要在ui中将其实例化。我们继续在class中建立UIManager.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

/**
* UI界面管理
*/

var BackGround = require("./BackGround");

function UIManager(game){
this.backgroundObj = null; // 背景图
this.heroHeadObj = null; // 本方英雄图像
this.enemyHeadObj = null; // 敌人英雄头像
this.heroHandCardObj = null; // 本方英雄手牌
this.enemyHandCardObj = null; // 敌人英雄手牌
this.init(game);
}

UIManager.prototype.init = function(game){
this.backgroundObj = this.setBackGround(game);
}

// 设置背景
UIManager.prototype.setBackGround = function(game){
var background = new BackGround(game);
return background;
}

module.exports = UIManager;

在UIManager中,我们还预留了很多的属性,他们都是整个游戏的重要组件,我们在后面也会慢慢的将其完善。

最后在GameScene.js更新下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

// 游戏主场景
var UIPanel = require("../class/UIManager");

function GameScene(game) {
this.preload = function () {
// 加载提示声明
var loadText = game.add.text(game.world.centerX, game.world.centerY, "Loading ... ", { fill: "#333", "fontSize": "28pt" });

// 锚点设置
loadText.anchor.set(0.5);

game.load.image("background", "../../resource/background.png");
game.load.image("card_back", "../../resource/card_back.png");
game.load.image("dead_wing", "../../resource/dead_wing.png");
game.load.image("dead_wing_fight", "../../resource/dead_wing_fight.png");
game.load.image("enemy_turn_button", "../../resource/enemy_turn_button.png");
game.load.image("fee", "../../resource/fee.png");
game.load.image("fighter_hero", "../../resource/fighter_hero.png");
game.load.image("fishman_baby", "../../resource/fishman_baby.png");
game.load.image("fishman_baby_fight", "../../resource/fishman_baby_fight.png");
game.load.image("freshwater_crocodile", "../../resource/freshwater_crocodile.png");
game.load.image("freshwater_crocodile_fight", "../../resource/freshwater_crocodile_fight.png");
game.load.image("hero_turn_button", "../../resource/hero_turn_button.png");
game.load.image("hp_background", "../../resource/hp_background.png");
game.load.image("ogre", "../../resource/ogre.png");
game.load.image("ogre_fight", "../../resource/ogre_fight.png");
game.load.image("attack_icon","../../resource/attack_icon.png");

// 单个文件加载完的回调
game.load.onFileComplete.add(function () {
loadText.setText("Loading ... " + arguments[0]);
});

// 所有文件加载完成回调
game.load.onLoadComplete.add(function () {
loadText.destroy();
});

}
this.create = function () {
// 添加ui界面
var ui = new UIPanel(game);

}
}

module.exports = GameScene;

ok,到这里我们打开浏览器,可以看到屏幕上输出了我们理想中的背景图片了。

image

现在我们需要完成整个UI需要的所有显示逻辑。

接下来我们继续,完成其他的各个相关组件的搭建。

首先是英雄头像组件。

我们在class文件夹下面创建一个Head.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

/**
* 角色头像类
* @param game [obj] 游戏场景对象
* @param textureName [string] 图片key
* @param positionX [number] 初始化的x坐标
* @param positionY [number] 初始化的y坐标
*/

function Head(game, textureName, positionX, positionY) {
this.headObj = null;
this.x = positionX;
this.y = positionY;
this.HPObj = null; // 英雄血量
this.textureName = textureName;
this.init(game);
}

Head.prototype.init = function (game) {
this.headObj = this.setPic(game);
this.HPObj = this.setHP(game);
}

// 设置英雄头像
Head.prototype.setPic = function (game) {
var pic = game.add.image(this.x, this.y, this.textureName);
return pic;
}

// 设置血量
Head.prototype.setHP = function (game) {
var HPbg = game.add.image(10, 170, "hp_background");
var HP = game.add.text(HPbg.width / 2, HPbg.height / 2 + 5, "30", { fill: "#fff", fontSize: "24pt" });
HP.anchor.set(0.5);
HPbg.addChild(HP);

return HP;
}


module.exports = Head;

在Head这个类中,我们设置了英雄的头像和血量。由于英雄的血量是会根据战况经常发生变化的。所以我们需要把这个对象放到类属性中去。我们用HPObj来接受这个血量数值对象,以后我们只要通过实例化后的对象来访问这个属性就能拿到我们需要的东西了。

但是这个类并不是我们直接拿来去实例化后去使用的。我们用这个Head作为基类。到时候我们既要有玩家英雄的头像逻辑,也要有敌人英雄头像逻辑。两者的既有相似又会有不同。所以我们要用两个不同的子类来继承这个基类,分别去实现自己相应的逻辑。我们先创建第一个子类,我们玩家英雄的头像类,HeroHead.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

/**
* 玩家角色头像
*/
var utils = require("../Utils");
var Head = require("./Head");

function HeroHead(game, textureName, positionX, positionY) {
Head.apply(this, arguments);
}

// HeroHead继承自Head类
utils.extend(HeroHead, Head);

// @override 重写setHP方法
HeroHead.prototype.setHP = function (game) {
var HPbg = game.add.image(this.x, this.y-55, "hp_background");
var HP = game.add.text(HPbg.width / 2, HPbg.height / 2 + 5, "30", { fill: "#fff", fontSize: "24pt" });
HP.anchor.set(0.5);
HPbg.addChild(HP);

return HP;
}

module.exports = HeroHead;

这里我们使用了utils.extend()这个方法。这是我们在外部定义的继承方法,由于js的语言特性,我们要继承一个类不能像静态语言那样直接使用自带的继承方法来做,这个继承的方法需要我们自己来写,然后在子类中通过对象冒充的方式来继承父类原型中的方法。也就是我们这里所用的:

1
Head.apply(this, arguments);

对于apply方法的简单理解就是用Head的作用域来顶替了当前作用域中的this对象就可以了。对js还不太熟型的同学可以多多去了解相关的知识。

最后在看看我们的extend函数,在class外部建立一个名为Utils.js的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

/**
* 常用函数
*/

var Utils = {
extend: function (child, parent) {
var p = parent.prototype;
var c = child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}
}

module.exports = Utils;

最后我们在UIManager.js中去实例化这个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

/**
* UI界面管理
*/

var BackGround = require("./BackGround");
var HeroHead = require("./HeroHead");

function UIManager(game) {
this.backgroundObj = null; // 背景图
this.heroHeadObj = null; // 本方英雄图像
this.init(game);
}

UIManager.prototype.init = function (game) {
this.backgroundObj = this.setBackGround(game); // 生成背景图

this.heroHeadObj = new HeroHead(game, "fighter_hero", 0, 0); // 生成玩家英雄头像
}

// 设置背景
UIManager.prototype.setBackGround = function (game) {
var background = new BackGround(game);
return background;
}
module.exports = UIManager;

做到这里,我们英雄的头像就可以正常的显示出来了。

我们用同样的方式将敌人的头像也显示出来。创建一个EnemyHead.js的文件,用EnemyHead这个类来继承Head类,并在UIManager中将其实例化。

后面我们还有很多类似的组件,也都是采用同样的方式将其一一实现。我们需要有一个手牌类,那么我们先声明一个手牌的基类,用玩家手牌类和敌人手牌类来继承这个基类,最后在UIManager中将两者实例化。费用管理组件,战斗场景中的随从组件,都是采用这样的实现方法来做。但是目前我们只是简单的设置了他们的表现形式,具体的游戏逻辑我们还要在后面根据具体的实际情况完成。

那么我们就开始看看这个手牌类长啥样,在class目录下建立一个HandCard.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

/**
* 手牌类
*/

function HandCard(game, x, y) {
this.cardViewList = []; // 手牌视图数组
this.x = x || 140;
this.y = y || 20;
this.init(game);
}

HandCard.prototype.init = function (game) {
this.buildHandCardViewList(game); // 设置卡背
}

// 构建手牌数组view
HandCard.prototype.buildHandCardViewList = function (game) {
for (var i = 0; i < 4; i++) {
var card = game.add.image(this.x + i * 70, this.y, "card_back");
card.scale.set(0.5);
this.cardViewList.push(card);
}
}

module.exports = HandCard;

代码上的实现大家应该都能看懂了。我们接下来就得用玩家手牌类和电脑手牌类来分别继承这个父类了,先看HeroHandCard.js,玩家手牌类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

/**
* 英雄的手牌类
*/

var HandCarnd = require("./HandCard");
var utils = require("../Utils");

function HeroHandCard(game,x,y) {
HandCarnd.apply(this, arguments);
}

utils.extend(HeroHandCard, HandCarnd);

module.exports = HeroHandCard;

和之前的继承实现是完全一样的。电脑手牌类我这里就不贴出来了。大家可以自己尝试着自己去继承一下。

继承完了之后最后不要忘记在UIManager中去实例化一下哦。不然是无法看到效果的。

UIManager的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

/**
* UI界面管理
*/

var BackGround = require("./BackGround");
var HeroHead = require("./HeroHead");
var EnemyHead = require("./EnemyHead");
var DataManager = require("./DataManager");
var HeroHandCard = require("./HeroHandCard");
var EnemyHandCard = require("./EnemyHandCard");
var HeroFee = require("./HeroFee");
var EnemyFee = require("./EnemyFee");

function UIManager(game) {
this.backgroundObj = null; // 背景图
this.heroHeadObj = null; // 本方英雄图像
this.enemyHeadObj = null; // 敌人英雄头像
this.heroHandCardObj = null; // 本方英雄手牌
this.enemyHandCardObj = null; // 敌人英雄手牌
this.turnOverButton = null; // 回合结束
this.shotCardButton = null; // 出牌按钮
this.heroFeeObj = null; // 玩家费用管理
this.enemyFeeObj = null;
this.init(game);
}

UIManager.prototype.init = function (game) {
this.backgroundObj = this.setBackGround(game); // 生成背景图

this.heroHeadObj = new HeroHead(game, "fighter_hero", 0, 0); // 生成玩家英雄头像
this.enemyHeadObj = new EnemyHead(game, "fighter_hero", 0, game.world.height - 140); // 生成电脑英雄头像

this.turnOverButton = this.setTurnOverButton(game); // 设置回合结束按钮

this.enemyHandCardObj = new EnemyHandCard(game); // 设置敌人手牌
this.heroHandCardObj = new HeroHandCard(game, null, game.world.height - 120); // 设置玩家手牌

this.shotCardButton = this.setShotCardButton(game); // 设置出牌按钮

this.heroFeeObj = new HeroFee(game, game.world.width - 110, game.world.centerY + 42); // 英雄费用管理
this.enemyFeeObj = new EnemyFee(game, game.world.width - 110, game.world.centerY - 90); // 敌人费用管理
}

// 设置背景
UIManager.prototype.setBackGround = function (game) {
var background = new BackGround(game);
return background;
}

// 回合结束
UIManager.prototype.setTurnOverButton = function (game) {
var button = game.add.image(game.world.width - 150, game.world.centerY - 30, "hero_turn_button");
button.inputEnabled = true;
button.events.onInputDown.add(function () {
if (DataManager.turn == 0) {
button.loadTexture("enemy_turn_button");
DataManager.turn = 1;
}

});

return button;
}

// 出牌按钮
UIManager.prototype.setShotCardButton = function (game) {
var shot = game.add.image(80, game.world.centerY - 10, "shot_card");
shot.anchor.set(0.5);
shot.inputEnabled = true;
shot.events.onInputDown.add(function () {
console.log("我出牌了");
});
return shot;
}
module.exports = UIManager;

其他组件的实现思路基本一致,代码我这里就不一一贴出来了。在将元素一一添加之后,整个游戏的面板现在看起来大概是这个样子:

image