在学习了一段时间egret时间后,今天来分享一个egret的实战项目,用egret框架来做一款很流行的黑白块游戏。
开发环境的搭建在这里就不在详细述说,以下项目默认你已经搭好开发环境和了解egret的API,如果还有不会的同学请移步白鹭官网

本项目使用的是白鹭的工具Egret wing 3 和 5.1.1的引擎版本

开启Egret项目之旅~

1.新建项目

新建一个Egret游戏项目 项目名称叫做black_white_block_chapter,因为这时候创建的项目是一个白鹭的模板项目,可以直接在这个项目上开发自己的项目。接下来打开src文件夹下面的Main.ts文件,把创建游戏场景的createGameScene这个方法里面自带的内容删掉,之后就可以直接在这里做自己想做的事情–游戏的业务逻辑。
每个程序员都是一个好的架构师(偷笑一秒钟),为了更好的管理项目,下面只在createGameScene这个方法里面作入口处理,其他的业务逻辑独立到其他的文件。

2.创建文件

在src下新建一个文件夹App把该项目的所有业务代码放在这里面,现在新建一个GameMain.ts文件,代码里面会有一些注释,里面的具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//GameMain.ts
//自定义的文档类继承自egret的容器类
class GameMain extends egret.DisplayObjectContainer{
//构造函数
public constructor(){
//因为此自定义的文档类是来自继承的,所以强制要求要写上super();
super();

//将所有的东西添加到舞台
this.addEventListener(egret.Event.ADDED_TO_STAGE, this.addToStage, this);
}
public addToStage(){
//添加成功之后移除掉,只添加一次
this.removeEventListener(egret.Event.ADDED_TO_STAGE, this.addToStage, this);

//TODO
console.log('TODO HERE');
}
}

3.回到Main.ts在创建场景的时候新建这个类并添加到舞台,具体代码如下:
1
2
3
4
5
6
7
8
//Main.ts
/**
* 创建游戏场景
* Create a game scene
*/
private createGameScene() {
this.addChild(new GameMain());
}
4.在App下新建一个Rect.ts文件

既然是面向对象,而项目中方块就是主角色,所以下面就是封装一个有业务逻辑的方块类,具体代码如下:

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
//Rect.ts
//继承自Sprite类
class Rect extends egret.Sprite{
public constructor(){
super();
//打开点击事件
this.touchEnabled = true;
this.draw();
}
//4种颜色<黑、白、红、蓝>,不同状态对应不同的颜色
private _colors:Array<number> = [0x000000, 0xffffff, 0xff0000, 0x0000ff];
//当前的颜色值下表
private _currentColor:number = 1;
//执行绘图
private draw(){
this.width = Data.getRectWidth();
this.height = Data.getRectWidth();
this.graphics.lineStyle(2, 0x000000);
this.graphics.beginFill(this._colors[ this._currentColor ]);
this.graphics.drawRect(0, 0, Data.getRectWidth(), Data.getRectWidth());
this.graphics.endFill();
}
//默认不可点击
private _type:string = RectType.NONCLICKABLE;
//get属性
public get type():string{
return this._type;
}
//set属性
public set type(val:string){
// if(val != this._type){
this._type = val;
//根据类型不同改变颜色
if(this._type == RectType.CLICKABLE){
this._currentColor = 0;
}else{
this._currentColor = 1;
}
this.draw();
// }
}
//处理点击业务逻辑
public onRectClick(){
//根据类型不同改变颜色
if(this._type == RectType.CLICKABLE){
this._currentColor = 3;
}else{
this._currentColor = 2;
}
this.draw();
}
}

5.在App下新建一个RectType.ts文件

定义方块的类型,两个静态变量,具体代码如下:

1
2
3
4
5
class RectType{
//外部可以访问的静态变量
public static CLICKABLE:string = 'clickable';//可点击
public static NONCLICKABLE:string = 'nonclickable';//不可点击
}

6.在App下新建一个Data.ts文件

每个项目中都会有一些固定的配置信息,那么在这个项目中,将一些公用的数据在Data.ts中封装起来,具体代码如下:

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
//Data.ts
class Data{
//小方块的宽
private static _rectWidth:number = 0;
//获取小方块的宽度
public static getRectWidth():number{
if(Data._rectWidth == 0){
//小方块的宽度等于舞台的宽度/4
Data._rectWidth = egret.MainContext.instance.stage.stageWidth/4;
}
return Data._rectWidth;
}
//分数
public static score:number = 0;
//行数
public static _rectRow:number = 0;
//获取行数
public static getRectRow():number{
if(Data._rectRow == 0){
//每屏的行数等于舞台的高度/小方块的宽度 +1是为了不出现断层
Data._rectRow = Math.ceil(egret.MainContext.instance.stage.stageHeight/Data.getRectWidth()) + 1;
}
return Data._rectRow;
}
//获取舞台的的高度
public static getStageHeight():number{
return egret.MainContext.instance.stage.stageHeight;
}
}

7.项目中是以一行为一个小组单元,每组当中由4个小方块组成,然后多组(行)拼成一整个舞台,所以接下来在App下新建一个GroupRect.ts,具体的代码如下:
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
//GroupRect.ts
//继承自Sprite
class GroupRect extends egret.Sprite{
public constructor(){
super();
this.createRects();
}
//Rect类型的数组
private _rects:Array<Rect>;
//当GroupRect被实例化的时候能够同时创建4个首尾相接的小方块,同时添加到显示列表
private createRects(){
//实例化数组
this._rects = [];
//创建小方块
for(var i = 0;i < 4;i++){
var rect:Rect = new Rect();
this._rects.push(rect);
//水平位置
rect.x = rect.width*i;
this.addChild(rect);
//添加点解事件
rect.addEventListener(egret.TouchEvent.TOUCH_TAP, this.onClickRect, this);
}
}
//当前的行数
public _currentRow:number = 0;
//点击事件逻辑处理
private onClickRect(evt:egret.TouchEvent){
//调用当前对象的点击事件
evt.target.onRectClick();
//当前总行数Data.getRectRow()
//倒数第二行 this._currentRow != Data.getRectRow() - 2 这里-2要终点理解!!!
if(evt.target.type == RectType.NONCLICKABLE || this._currentRow != Data.getRectRow() - 2){
//抛出一个游戏结束的事件
this.dispatchEventWith("gameOver");
}else{
//抛出一个点击正确的事件
this.dispatchEventWith("clickRight");
}
}
//定义一组当中,随机其中一个为可点击的
private _currentBlcakRectIndex:number = 0;
public createBlackRect(){
this.init();
var n:number = Math.random();
if(n >= 0 && n < 0.25){
this._currentBlcakRectIndex = 0;
}else if(n >= 0.25 && n < 0.5){
this._currentBlcakRectIndex = 1;
}else if(n >= 0.5 && n < 0.75){
this._currentBlcakRectIndex = 2;
}else if(n >= 0.75 && n <= 1){
this._currentBlcakRectIndex = 3;
}
this._rects[this._currentBlcakRectIndex].type = RectType.CLICKABLE;
}

//为了性能考虑,当某一行往下移动到屏幕外的时候,又回到顶部第一行,这时候需要把该组的类型重置为不可点击状态,并且一定要在每组当中定义随机一个可点击的方块的之前重置
public init(){
for(var i = 0;i < 4;i++){
this._rects[i].type = RectType.NONCLICKABLE;
}
}
//移动的动作
public move(){
//当前行数自增1
this._currentRow += 1;
//当当前行数和总行数相等时
if(this._currentRow == Data.getRectRow()){
//重置当前行数
this._currentRow = 0;
this.createBlackRect();
}
this.y = this._currentRow*Data.getRectWidth();
}
}
8.在App文件夹下新建一个项目的总控Game.ts,具体代码如下:
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
77
78
79
80
81
82
83
84
85
86
87
//Game.ts
//总控类,不继承任何类,为了可以获取最根层的显示列表,需要传入一个容器,这里就是egret.DisplayObjectContainer类型的容器
class Game{
//根容器
private _root : egret.DisplayObjectContainer;
public constructor(root:egret.DisplayObjectContainer){
this._root = root;
this.createGroupsRect();
this.createTimer();
this.startGame();
}
//记录有多少行
private _row:number;
//所有的组的父级,为了当游戏变得复杂的时候,分成多个层级
private _rectRoot:egret.Sprite;
//存放所有GroupRect对象
private _rectGroups:Array<GroupRect>;
//创建组
private createGroupsRect(){
this._rectRoot = new egret.Sprite();
this._root.addChild(this._rectRoot);
this._rectGroups = [];
this._row = Data.getRectRow();

//创建组
var groupRect:GroupRect;
console.log(this._row)
for(var i = 0;i < this._row;i++){
groupRect = new GroupRect();
//监听gameOver和clickRight
groupRect.addEventListener("gameOver", this.gameOver, this);
groupRect.addEventListener("clickRight", this.newRow, this);
this._rectGroups.push(groupRect);
groupRect.y = Data.getRectWidth()*i;
this._rectRoot.addChild(groupRect);
}
this._rectRoot.y = Data.getStageHeight() - this._rectRoot.height;
}
private newRow(){
for(var i =0;i < this._row;i++){
//所有的组移动
this._rectGroups[i].move();
}
//分数+1
Data.score++;
}
//游戏结束面板
private gameOverPanel:GameOverPanel;
//创建游戏结束面板
private gameOver(){
//停止计时器
this._timerPanel.stop();
//弹出结束面板
if(!this.gameOverPanel){
this.gameOverPanel = new GameOverPanel();
this.gameOverPanel.addEventListener('startGame', this.startGame, this);
}
this._root.addChild(this.gameOverPanel);
}
//计时器面板
private _timerPanel:TimePanel;
//创建计时器面板
private createTimer(){
this._timerPanel = new TimePanel();
this._timerPanel.addEventListener('gameOver', this.gameOver, this);
this._root.addChild(this._timerPanel);
}
private startGame(){
//初始化分数归0
Data.score = 0;
//遍历所有组(行)
for(var i = 0;i < this._row;i++){
//重置所有为不可点击
this._rectGroups[i].init();
//Y轴左边
this._rectGroups[i].y = Data.getRectWidth()*i;
//定义当前的行数!!!!!!!!
this._rectGroups[i]._currentRow = i;
//控制最低部一行,相当于是最后一行的时候,4个小方块为白色,不可点击状态
if(i != (this._row - 1)){
this._rectGroups[i].createBlackRect();
}
}
//开始计时
this._timerPanel.start();
}
}
9.在App文件夹下新建一个项目的时间面板TimerPanel.ts,具体代码如下:
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
//TimerPanel.ts
//继承自Sprite精灵类
class TimePanel extends egret.Sprite{
constructor(){
super();
this.draw();
this.createTimer();
}
//时间显示文本
private txt:egret.TextField;
//创建文本
private draw(){
this.txt = new egret.TextField();
this.txt.width = egret.MainContext.instance.stage.stageWidth;
this.txt.y = 200;
this.txt.textColor = 0xff0000;
this.txt.textAlign = egret.HorizontalAlign.CENTER;
this.txt.text = "20'00''";
this.addChild(this.txt);
}
//存放计时器
private _timer:egret.Timer;
//计时(循环)的次数
private _num:number = 20;
//创建计时器
private createTimer(){
this._timer = new egret.Timer(1000, this._num);
//监听一个TIMER时间,没帧执行
this._timer.addEventListener(egret.TimerEvent.TIMER, this.onTimer, this);
//监听计时结束
this._timer.addEventListener(egret.TimerEvent.TIMER_COMPLETE, this.onTimerCom, this);
}
//计时时间
private _timers = 20;
private onTimer(){
this._timers -= 1;
this.txt.text = this._timers + "'00''"
}
private onTimerCom(){
this.txt.text = "00'00''";
this.dispatchEventWith('gameOver');
}
//重置计时器和时间
public start(){
this.txt.text = "20'00''";
this._timers = 20;
this._timer.reset();
this._timer.start();
}
//停止计时器
public stop(){
this._timer.stop();
}
}
10.在App文件夹下新建一个项目的结束GameOverPanel.ts,具体代码如下:
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
//GameOverPanel.ts
//继承自Sprite类
class GameOverPanel extends egret.Sprite{
public constructor(){
super();
//绘图
this.draw();
//监听一个事件,当此面板被添加到舞台的时候触发
this.addEventListener(egret.Event.ADDED, this.showText, this);
}
//显示文本
private txt:egret.TextField;
private draw(){
var w = egret.MainContext.instance.stage.stageWidth;
var h = egret.MainContext.instance.stage.stageHeight;

this.graphics.beginFill(0x111111, 0.5);
this.graphics.drawRect(0, 0, w, h);
this.graphics.endFill();

this.txt = new egret.TextField();
this.txt.width = w;
this.txt.y = 100;
this.txt.textColor = 0xff0000;
this.txt.textAlign = egret.HorizontalAlign.CENTER;
this.addChild(this.txt);

var btn = new egret.Sprite();
btn.graphics.beginFill(0x0000ff);
btn.graphics.drawRect(0, 0, 200, 100);
btn.graphics.endFill();
btn.width = 200;
btn.height = 100;
btn.x = (w - 200)/2;
btn.y = (h - 100)/2;
this.addChild(btn);
btn.touchEnabled = true;
btn.addEventListener(egret.TouchEvent.TOUCH_TAP, this.startGame, this);
}
//更新文本内容
private showText(){
this.txt.text = '我努力点击了'+Data.score+'步';
}
private startGame(){
this.parent.removeChild(this);
this.dispatchEventWith('startGame')
}
}

到这里就写完了整个项目的逻辑了,篇幅比较长,在教程的过程中也没有每个阶段的给出效果图,看起来和学习起来是比较吃力,在这里建议可以跑起完整的项目,然后自己跟着思路一步一步去实现。如果学习过程中,有不正确的欢迎纠正。谢谢大家!
源码地址