SkillAgentSearch skills...

2048egret

egret+puremvc

Install / Use

/learn @f111fei/2048egret
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

写在前面

本游戏使用Egret的GUI进行开发,不涉及Egret基础部分内容。本实例强烈建议结合GUIExample和深入浅出EGRET GUI系列教程来学习。

要了解Egret基础部分的内容请参考下面:

Egret GUI 相关教程:


2048是最近很火的一个小游戏,原版 就是用JavaScript写的。恰巧最近Egret PublicBeta,观望和学习了一阵后,发现egret正好适合开发这类游戏。Egret使用TypeScript作为开发语言,最终编译为JavaScript,正好和原始版本PK一下。

游戏预览:

2048egret

PieceOfPie

1.准备开始

在开始之前,我们需要学习一下TypeScript和阅读官方的教程从Egret开发环境的部署到创建,编译,发布项目,以及Egret相关工具。在安装好开发环境后,在工作空间目录下使用命令行,创建2048egret新项目

egret create 2048egret

2.准备素材

每一个游戏都离不开美术资源,我们需要做的就是把美术资源打包,然后加载进来使用。这一方面egret有一套完整的工作流。

①资源打包

这里我们用到的资源主要有按钮,背景,文字以及数字这些图片。我们选择把这些图片都打包在一起合成一张大图就像 这样这样 这样做可以减少URL请求数,还能减少资源的体积,把一些具有相同特征的图片放在一起便于管理。在egret里面这种类型的资源就是sheet。只有图片是不够的,还需要一个json描述文件来说明这张图每一张小图片的位置和大小。目前已经有成熟的工具来生成sheet和json。这里我用到的是 ShoeBox 配合这个 插件 来生成egret能识别的json。安装好插件后, 将每一张图片命名,然后将这些图片选中拖入Sprites Sheet中然后配置好生成的文件名点击save就能得到一张大图和一个json了,将图片和json放入"resource/assets/"文件夹下以备使用。此外ShoeBox还能读取swf将MovieClip导出为这种大图,按每一帧自动命名,这里的number.png就是这样导出的,下面有原始素材下载地址。

②资源加载

接下来我们需要生成一个资源描述文件resource.json,在游戏开始之前读取这个json来加载对应的文件。egret的资源加载机制可以参考 这里 现在已经有 工具 能自动生成这个resource.json了。按照下图配置。注意:虽然我们的资源有图片,但是对应的json文件已经记录了图片的位置,所以在这个工具中我们不需要添加对应的图片只添加json文件就行了。

<p align="center"><image src="http://xzper.qiniudn.com/2014/06/ResTool.png" /></p>

在项目初始化时,使用RES加载资源,简单明了。

private onAddToStage(event:egret.Event){
    ........
    ........
    //初始化Resource资源加载库
    RES.addEventListener(RES.ResourceEvent.CONFIG_COMPLETE,this.onConfigComplete,this);
    RES.loadConfig("resource/resource.json","resource/");
}

/**
 *配置文件加载完成,开始预加载preload资源组。
 */
private onConfigComplete(event:RES.ResourceEvent):void{
    RES.removeEventListener(RES.ResourceEvent.CONFIG_COMPLETE,this.onConfigComplete,this);
    RES.addEventListener(RES.ResourceEvent.GROUP_COMPLETE,this.onResourceLoadComplete,this);
    RES.addEventListener(RES.ResourceEvent.GROUP_PROGRESS,this.onResourceProgress,this);
    RES.loadGroup("preload");
}

③资源使用

在项目中我们可以使用RES来使用资源,参照对应的API。对于没有写进配置文件的资源使用RES.getResByUrl方法来异步获取。开发人员能使用极其少量的代码来完成各类资源的加载。

3.更改模板生成的代码

①修改细节

默认的文档类是GameApp。我觉得还是叫Main比较亲切,修改类名称,然后修改项目目录下的egretProperties.json文件,将document_class的值改为Main

{
    "document_class" : "Main",
    "native": {
        "path_ignore": [
            "libs"
        ]
    }
}

默认生成的html的背景是黑色的,这里全部改成白色。将index.html里面的背景替换成#ffffff。

默认尺寸是480x800的尺寸。由于我们使用的部分图片宽度大于500,以及部分PC的分辨率太小为了不出现垂直滚动条影响体验,将尺寸换成520x650。这个不影响移动设备上的尺寸,移动设备默认是自适应宽度的。

index.html中

<div style="display:inline-block;width:100%; height:100%;margin: 0 auto; background: #ffffff; position:relative;" id="gameDiv">
    <canvas id="gameCanvas" width="520" height="650" style="background-color: #ffffff"></canvas>
</div>

egret_loader.js中

//设置屏幕适配策略
egret.StageDelegate.getInstance().setDesignSize(520, 650);
context.stage = new egret.Stage();
var scaleMode =  egret.MainContext.deviceType == egret.MainContext.DEVICE_MOBILE ? egret.StageScaleMode.SHOW_ALL : egret.StageScaleMode.NO_SCALE;
context.stage.scaleMode = scaleMode;

③引入第三方库pureMVC

这次我们要使用到一个mvc开发框架-pureMVC,熟悉as3的朋友一定也对这个框架不陌生吧。不熟悉的也没关系,这个框架不是这次的主角。我们从 这里 下载pureMVC的TypeScript版本。得到puremvc-typescript-standard-1.0.d.ts 和 puremvc-typescript-standard-1.0.js这两个文件,其实.d.ts就类似于c++里面的.h头文件,只有空方法和空属性,真正的实现是在js文件或者ts文件里面。在项目里面的src文件夹下建立一个puremvc的文件夹,将这个js文件和d.ts文件放进去。然后在项目根目录下建立一个puremvc.json的文件内容如下

{
    "name": "puremvc",
    "source":"src/puremvc/",
    "file_list": [
        "puremvc-typescript-standard-1.0.js",
        "puremvc-typescript-standard-1.0.d.ts"
    ]
}

这样就表示配置了一个第三方模块。之后在编译器编译时会把相应的模块对应的js文件夹编译进libs文件夹下。项目里面我们还使用了gui模块,这些模块的配置是在egretProperties.json中,部分代码如下

"modules": [
	{
		"name": "core"
	},
	{
		"name": "gui"
	},
    {
        "name": "puremvc","path":"."
    }
],

④注入AssetAdapter和SkinAdapter

我们这次的主角是egret的GUI。找到官方<a href="https://github.com/egret-labs/egret-examples" target="_blank">GUIExample</a>中的这两个ts文件复制到项目的src文件夹下面,由于这个项目没有用到默认皮肤,删除ShinAdapter里面getDefaultSkin方法的默认皮肤。最后不要忘了一点,在引擎初始化的时候注入这两个Adapter。

private onAddToStage(event:egret.Event){
        //注入自定义的素材解析器
        egret.Injector.mapClass("egret.gui.IAssetAdapter",AssetAdapter);
        //注入自定义的皮肤解析器
        egret.Injector.mapClass("egret.gui.ISkinAdapter",SkinAdapter);
        ......
        ......
}

这两个的Adapter的作用至关重要,AssetAdapter负责解释UIAsset的source属性 ,SkinAdapter负责解释SkinnableCompent的skinName属性。这里官方提供了两个默认已经写好了的,当然我们可以自己扩展。 没有他们,UIAsset素材包装器的source属性 和 可设置皮肤的GUI组件的skinName属性毫无作用。而这两种组件是今后使用最多的。不信可以往下看 。

⑤修改createGameScene方法

在生成的模板中,文档类Main在经过一系列的前期准备工作之后,终于轮到GUI组件的老大UIStage上场了。UIStage类似于Flex里面的SystemManager,内置弹出窗口层,工具提示层和鼠标样式层,所有的GUI组件都应该添加到他的下面,并且UIStage全局唯一。  这里我们实现了一个AppContainer继承自UIStage。 同时在这里pureMVC框架正式启动,开始运作。

/**
 * 创建游戏场景
 */
private createGameScene():void {
    var appContainer:game.AppContainer = new game.AppContainer();
    this.addChild(appContainer);
    game.ApplicationFacade.getInstance().startUp(appContainer);
}

4.pureMVC

①Mediator

Mediator(中介器)是连接视图也就是egret的GUI和pureMVC的桥梁。Mediator受到消息时(handleNotification)调用GUI组件的方法和设置属性,来改变视图。或者视图发生改变时通知Mediator由其发送消息到pureMVC(sendNotification)。

ApplicationMediator 监听键盘事件或者手势发送消息到GameCommand通知移动

MainGameMediator 接收消息,调用MainGameUI的方法处理格子的移动,添加,删除,重置,以及接收游戏结果,显示结果面板

MainMenuMediator 接收更新分数的消息,调用MainGameUI的方法更新分数与重置

ResultWindowMediator 发送游戏重置的消息,以及自销毁。

②Command

command属于控制器。负责收发消息和处理简单的事务。在StartupCommand中使用ControllerPrepCommand,ModelPrepCommand,ViewPrepCommand三个子任务。分别注册控制器,数据和视图。

GameCommand 处理各类事务。比如 玩家按下了方向键,收到消息调用GridProxy的移动方法改变数据,比如GridProxy移动格子分数改变了,通知GameCommand 调用GameProxy的更新分数方法改变分数,比如处理重置游戏的事务,通知各个数据模块重置数据

③Proxy

处理数据,提供公共方法供Command调用以改变数据。改变数据了然后sendNotification通知Mediator改变视图。

GameProxy 处理游戏数据,比如更新分数,处理游戏结果

GridProxy 这个游戏的核心数据,操作每一个格子的数据,通知视图格子的移动,添加,删除,重置。这里包含2048这个游戏的精髓。有兴趣的可以研究下,源码里面有详细注释,这篇文章不做重点讲解。

5.egret的GUI

①制作菜单-------认识皮肤部件

先来看看菜单长什么样子

我们会发现这个菜单。有些是静态文本,是一直不变的,我偷懒直接用了一张图片代替了,图片可以用egret.gui.UIAsset。

还有当前得分和最高分已经那个向上飘的数字是动态的,可以选用egret.gui.Label这个组件。

一个重试按钮,既然已经说了是按钮了我们就用egret.gui.Button好了。

接下来我们要做到皮肤和组件分离。那几个需要参与逻辑的组件自然就成了皮肤部件了。来看看MainMenuUISkin:

/**
 * 和主机组件匹配的皮肤部件
 */
private static _skinParts:Array&lt;string&gt; = ["addLabel","scoreLabel","highScoreLabel","resetButton"];

public get skinParts():Array&lt;string&gt;{
    return MainMenuUISkin._skinParts;
}

/**
 * 加分文本
 */
public addLabel:egret.gui.Label;

/**
 * 总分文本
 */
public scoreLabel:egret.gui.Label;

/**
 * 最高分文本
 */
public highScoreLabel:egret.gui.Label;

/**
 * 重置按钮
 */
public resetButton:egret.gui.Button;

public createChildren():void
{
    super.createChildren;
    var uiAsset:egret.gui.UIAsset = new egret.gui.UIAsset();
    uiAsset.source = "source.menu";
    this.addElement(uiAsset);

    this.resetButton = new egret.gui.Button();
    this.resetButton.skinName = ResetButtonSkin;
    this.resetButton.right = 10;
    this.resetButton.top = 80;
    this.resetButton.label = "重置游戏";
    this.addElement(this.resetButton);

    this.highScoreLabel = new egret.gui.Label();
    ...省略若干代码
    this.scoreLabel = new egret.gui.Label();
    ...省略若干代码
    this.addLabel = new egret.gui.Label();
    ...省略若干代码
}

篇幅有限,省略了createChildren方法里面的子组件布局。**skin的createChildren方法是在皮肤和主机组件匹配的时候被调用的。皮肤和主机组件匹配是在主机组件被添加到显示列表的时候完成的。所以只要主机组件hostComponent还没有添加到显示舞台,获取hostComponent的皮肤部件都是无效的。**这也是为什么我将Mediator的注册放在GUI组件的createComplete后。以防Mediator访问出现空对象的情况。

再来看看主机组件MainMenuUI是怎么写的。

export class MainMenuUI extends egret.gui.SkinnableComponent{
    public addLabel:egret.gui.Label;
    public scoreLabel:egret.gui.Label;
    public highScoreLabel:egret.gui.Label;
    public resetButton:egret.gui.Button;

    public constructor(){
        super();
        this.skinName = MainMenuUISkin;
        this.addEventListener(egret.gui.UIEvent.CREATION_COMPLETE , this.createCompleteEvent, this);
    }

    public createCompleteEvent(event:egret.gui.UIEvent):void{
        this.removeEventListener(egret.gui.UIEvent.CREATION_COMPLETE , this.createCompleteEvent, this);
        ApplicationFacade.getInstance().registerMediator( new MainMenuMediator(this) );
    }

    public partAdded(partName:string, instance:any):void{
        super.partAdded(partName , instance);
        if(this.addLabel == instance){
            this.addLabel.visible = false;
        }
    }

    private moveEffect_effectEndHandler():void
    {
        this.addLabel.visible = false;
    }

    /**
	* 加分效果
	*/
    public playScoreEffect(addScore:number):void{
        this.addLabel.visible = true;
        this.addLabel.text = "+".concat(addScore.toString());
        egret.Tween.removeTweens(this.addLabel);
        this.addLabel.y = 25;
View on GitHub
GitHub Stars151
CategoryDevelopment
Updated3mo ago
Forks85

Languages

JavaScript

Security Score

72/100

Audited on Dec 3, 2025

No findings