网站建设
网站备案流程
本机IIS服务器的创建
Win7下配置本机IIS服务器
建网站需要学习的内容
使用表格布局网页
定义网页头文件元素
制作弹出网页
制作网页宣传窗
Div+CSS布局网页
CSS的常见选择器
CSS设置文本样式
CSS设置背景颜色与图像
CSS设置表格样式
HTML中使用CSS的方法
CSS3新增的部分属性1
CSS3新增的部分属性2
CSS3动画与渐变
网页显示MySQL数据库中汉字的乱码问题
HTML5的新特性
HTML5的API
HTML5音视频API
HTML5表单
HTML5表单API
HTML5画布canvas
HTML5拖放API
HTML5地理位置API
HTML5离线应用API
HTML5 Workers多线程
HTML5跨源通信
HTML5 WebSocket通信
HTML5的Web存储API
HTML5本地数据库
HTML5其他一些API
Node.js功能和使用
常用Web前端工具
WebGL编程
GLSL ES语言
使用ThreeJs库3D编程
XML可扩展标记语言
Egret是专注于移动设备上开发HTML5游戏的引擎,而且提供了Android和iOS Support,使得HTML游戏可以通过简单的步骤生成原生游戏。Egret提供多种免费可视化工具体系,包括IDE、资源管理工具、图片打包、粒子特效编辑器等,还提供了开放平台以便接入分发渠道对游戏推广运营。
Egret Wing是Egret附带的IDE工具,可通过下载的Egret软件中进行安装使用,包括了自动创建项目、定制皮肤、适配布局、交互设计、编辑调试代码等功能,其中还支持JavaScript、TypeScript、微信小程序等的开发设计,可作为通用的开发IDE使用。
还有其他一些工具,其中ResDepot是可视化资源管理工具,可以用于管理大量的游戏素材和配置文件,能快速生成Egret游戏需要的资源配置文件;Texture Merger为资源打包工具,可将零散的小图合并为大图纹理集,也能将GIF或SWF动画转换为Egret支持的动画格式,还提供个性位图文字效果输出。
一、Egret项目及基本结构:
1. 创建一个Egret项目:
用Wing创建一个项目,项目自动生成默认的资源和代码,并可以运行并输出一个默认界面。
项目一般自动创建了.wing、bin-debug、libs、resource、scripts、src等7个文件夹,还有index.html、manifest.json、tsconfig.json、egretProperties.json、wingProperties.json、favicon.ico等文件,
egretProperties.json是Egret的项目配置文件,其中包含了项目编译运行时所需要配置的参数或选项。其中包含engineVersion、compilerVersion项,表示项目版本和编译版本;modules为模块配置,其中列出所需要的各个模块,一般都有egret模块,这是核心模块;target为模板的运行环境,一般为web。
src目录中为项目的源代码,包括Main.ts、LoadingUI.ts、Platform.ts、AssetAdapter.ts、ThemeAdapter.ts等5个文件,Main.ts为项目的入口类,LoadingUI.ts是加载过程中的辅助显示组件,一般用来显示加载进度。
libs目录中包含各模块所对应的所有类库,在对egretProperties.json进行修改后需要通过编译来重新生成项目类库。
bin-debug目录为项目编译目录,源代码中的文件将会编译到此目录中。Egret开发是使用TypeScript语言,如果设置target为web,bin-debug目录中的文件都成为js及js.map后缀的文件。
resource目录用来存放项目的资源文件,包含图片和配置文件,图片或声音等文件的默认存储目录为resource/assets,配置数据文件的默认存储目录为resource/config。
项目运行只包含运行库文件和项目编译文件,项目最终发布时将会对所有项目编译文件和运行库文件进行整合压缩,合并为一个文件。发布生成的文件都会存储在项目的release目录中,并按发布类型存放,通常位于release/html5目录中,并使用一个时间戳命名子目录,以便在频繁发布时保留不同版本。
2. Egret代码风格:
对于比较大型的项目,使用module关键字将类分包,类似Java中的package。TypeScript的语言风格是包名用小写、类名用大写开头,变量第一个字母为小写,其后的单词起首字母大写,常量则全部使用大写字母,单词之间使用下划线分隔。如果一个方法或属性以下划线开头,一般为内部使用的方法或属性。
3. Egret库结构:
Egret提供的各个核心库使用模块方式组成,通过配置文件来加载或不加载,以减少最终代码的体积。
Egret引擎的核心库有8个模块,包括egret、dragonbones、eui、game、gui、res、socket、tween,其中egret是最核心的模块,所有Egret的项目都要包含这个模块,res模块则涉及资源载入。
4. 使用第三方库:
第三方库可以是在网上下载的js库,或者自己写的js库。由于js与ts语法的差异,ts不能直接调用js库,TypeScript提供了虚构声明语法,可以把现有的代码API用头文件形式描述,称为ts类型定义,扩展名为d.ts。
第三方模块是作为一个项目来对待的,首先创建一个Egret库项目,创建位置要处于其他Egret项目外部。创建项目时,在指定目录下运行终端命令:
egret create_lib <lib_name>
此命令创建以库名命名的目录,目录中包含bin、src、libs等3个目录,还有一个package.json配置文件。然后将准备好js和对应的d.ts文件复制到此项目的src目录中。如果该库项目src中的所有文件需要引用其他库的代码(依赖库),要把这些库的TypeScript描述文件(d.ts)放到libs目录下。
还需要编辑Egret库项目中的配置文件package.json,将放入src目录下的文件名依次作为file_list数组中的元素列出。示例:
{"name":"egret","version":"2.5.0","modules":[
{"name":"physics","description":"physics","files":["p2.min.js","physics.d.ts"],"root":"src"}]}
其中,modules表示包含的第三方库模块,每一个模块作为modules的一个元素;模块中的name为此模块名称,description为此模块的描述,files为模块的代码文件,需要js和d.ts文件,root为模块中代码的路径,通常默认为src。
最后使用终端命令编译:egret build <lib_name>
还要在Egret项目的egretProperties.json中添加代码:
mudules:[{"name":"physics","path":"path/to/egret/library/project/root/"}]
其中,path是Egret库项目的路径,可以使用绝对路径或相对路径,路径要在当前Egret项目路径目录外。添加上述代码后,执行egret build -e就会把配置到项目的第三方库编译到项目中。执行成功后,项目代码中就可以使用第三方库了。
编译第三方库后,在项目的index.html文件中应该包含对应模块名称physics.js的script外部脚本引用行,编译第三方库后libs/modules中将会出现一个名为physics的目录。
4. 命令行模式:
Egret Wing是IDE工具,可以使用菜单命令进行项目创建、编译、运行、发布等操作。而且,还可以在控制台直接执行命令来进行操作。
1)创建项目命令:
egret create <project_name>
打开命令行,定位到一个目录,可以用此命令创建一个项目。项目处于当前目录下,创建一个以项目名命名的子目录。
2)编译项目:
egret build <project_name>
开发过程中对代码修改后,都需要使用此命令编译并运行来进行验证。编译命令会检查语法错误,如果发现错误就会在控制台输出,并精确指出错误所在的文件及行列数,还会有简要描述,便于修正。如果此命令在项目目录中执行,可以省略项目名称参数。
编译命令还有几个类似的命令:
①egret build <project_name> -e
编译引擎命令,就是将项目运行库中的内容更新为与当前引擎一致,通常在引擎升级或者修改了项目配置模块后进行。
②egret quickbuild
增量编译,是默认的编译方式,只编译修改或新增的ts类源代码。但此功能在新版本中已经不可用了。
3)运行项目:
egret startserver <project_name>
执行此命令后,会自动弹出窗口界面,并显示运行结果。
4)发布项目:
egret publish <project_name>
项目发布会对所有的项目编译文件和运行库文件整合压缩,合并成一个文件,并存储到release对应target的有时间戳的子目录下。
5)帮助命令:
egret help
通过此命令可以查看所有Egret支持的命令,一般包括:
·create:创建新项目
·create_lib:创建第三方库项目
·create_app:从H5生成app
·build:构建指定项目,编译指定项目的TypeScript文件
·publish:发布项目,使用GoogleClosureCompiler压缩代码
·startserver:启动HttpServer,在默认浏览器中打开指定项目
·update:升级项目代码
·apitest:版本升级后检测api是否已经替换完成
·info:获得Egret信息
命令的执行格式为:egret <command> [-v]
可以使用egret help <command>了解各个command的细节。如果在命令行只输入egret命令,会显示目前的Egret版本。
二、Egret的基础知识:
1. Egret的显示对象:
Egret引擎架构中,参与显示的对象称为显示对象,其中每一个显示对象都有唯一的作用。
显示对象之间有层次关系:
其中,DisplayObject是所有显示对象的基类,封装了显示对象所必须的属性和行为,但其本身不会产生任何图像。DisplayObjectContainer类继承自DisplayObject,并带有容器属性,而其下的子类Sprite、Stage和ScrollView都有容器属性。其他显示对象则是普通的显示对象。
常用的显示对象类型有:
类 |
说明 |
DisplayObject |
显示对象类型,所有显示对象继承自该类 |
Sprite |
精灵,即可绘制矢量图形,也是容器 |
Bitmap |
用于显示位图 |
Shape |
用于绘制2D矢量图形 |
MovieClip |
逐帧动画 |
Stage |
舞台类,是游戏的主场景 |
ScrollView |
滑动拖放,可以让一个显示对象在一定范围内滚动 |
DisplayObjectContainer |
显示容器基类,所有显示容器都继承自此类 |
TextField |
文本类,用于显示文本或者输入文本 |
BitmapText |
位图文本 |
2. 显示对象的基本属性:
所有显示对象都继承自基类DisplayObject,其包含以下基本属性:
属性 |
描述 |
x |
z轴坐标 |
y |
y轴坐标 |
width |
宽度 |
height |
高度 |
scaleX |
横向拉伸比例 |
scaleY |
纵向拉伸比例 |
rotation |
旋转角度 |
skewX |
x方向斜切角度 |
skewY |
y方向斜切角度 |
alpha |
透明度 |
为了让一个显示对象显示出来,使用this.addChild()方法将其加入显示列表中,然后渲染时就会显示出来。Egret中,使用的坐标系与canvas中的坐标系一致,及Y轴向下为正方向,(0,0)点在左上角。
3. Shape矢量图:
Egret中,可以直接使用代码绘制简单的图形,这些图形是在运行时实时绘图,使用的是Graphics类。Graphics类提供了多种绘图方法,可以绘制矩形、圆形、直线、曲线、圆弧等。
1)绘制矩形:
示例代码:
var shp:egret.Shape=new egret.Shape();
shp.graphics.beginFill(0x00ff00,1);
shp.graphics.drawRect(0,0,100,200);
shp.graphics.endFill();
this.addChild(shp);
其中主要的绘图语句为:
·调用beginFill()方法设置填充颜色,使用16进制数值表示颜色值,并设alpha为1,不透明。
·调用drawRect()方法设置矩形,代码中绘制了100x200的矩形。
·调用endFill()方法结束当前绘制。
如果需要为矩形添加描边,可以通过lineStyle方法设置线条样式:
shp.graphics.lineStyle(10,0x00ff00)
其中,第1个参数为线宽,第2个参数为描边颜色。
2)绘制圆形:
shp.graphics.drawCircle(0,0,50);
上述代码绘制了半径为50的圆形,其中三个参数分别为x轴坐标、y轴坐标、圆形半径。其中的坐标是相对于Shape对象的锚点的。
3)绘制直线:
画直线使用moveTo()方法指定起点,使用lineTo()方法绘制到终点,一般需要先指定线条样式:
shp.graphics.lineStyle(2,0x00ff00);
shp.graphics.moveTo(10,10);
shp.graphics.lineTo(100,20);
shp.graphics.endFill();
可以连续使用lineTo()绘制首尾相连的很多直线组成的折线。
4)绘制曲线:
Egret中绘制曲线使用二次贝塞尔曲线,首先使用moveTo()方法指定曲线起点,然后使用curveTo()方法绘制到终点,其中还需要指定控制点:
shp.graphics.lineStyle(2,0x00ff00);
shp.graphics.moveTo(50,50);
shp.graphics.cureTo(100,100,200,50);
shp.graphics.endFill();
5)绘制圆弧:
新版本的Egret中提供了绘制圆弧的方法drawArc:
shp.graphics.drawArc(x,y,radius,startAngle,endAngle,anticlockwise);
其中,x、y表示圆形坐标,radius表示圆半径;startAngle和endAngle表示起始和终止点的弧度值,是以x轴方向开始计算的;anticlockwise是布尔值,逆时针为true,顺时针为false。
6)连续绘制多个图形:
一个物体由多个几何形状组成时,需要连续绘制多个形状,其中每个绘制都是相互独立的,都需要以endFill()结束,然后开始下一个绘制。但这些图形可以赋给一个Shape对象,并一次使用addChild()方法添加到显示列表中。
7)清空绘图:
如果要清空界面中全部图形,使用graphics对象的clear()方法:
shp.graphics.clear();
4. 容器类:
Egret中的DisplayObjectContainer类封装了一些显示列表中常用的操作,如添加及删除子对象、访问子对象、检测子对象、叠放子对象等。
1)创建DisplayObjectContainer类实例:
如果游戏中仅仅使用容器相关功能,可以直接创建DisplayObjectContainer类的实例,也就是自定义一个容器;如果要实现Graphics绘图功能,也可以继承Sprite类。示例:
class Main extends egret.DisplayObjectContainer{}
class GridSprite extends egret,Sprite{}
其中都需要包含有构造方法constructor(),直接继承DisplayObjectContainer类时一般还需要有addEventListener事件处理,并实现处理方法;而继承Sprite时一般要实现一个绘制方法。
2)添加及删除显示对象:
Egret中,建立显示对象后会存在内存中,但并不会直接参与渲染,只有把显示对象放入显示列表中才会渲染。如果不想再渲染某个对象,可以将其从显示列表中删除。
把对象放入显示列表中使用addChild()方法,而从显示列表中删除则使用removeChild()方法。
注意,一个对象不在显示列表中,或已经在显示列表中被删除,这个对象仍然在内存中,其相应属性,如坐标值、旋转等仍然存在。显示对象的坐标是相对坐标,即相对于其父对象的坐标值,如果父对象坐标改变了,其位置也会随之发生改变,只是与父对象的相对位置不变。一个对象多次被添加到显示列表中只会被绘制一次。
如果删除的显示对象并不在显示列表中就会出错,所以删除前需要判断其是否有父级对象:
if(spr.parent){
spr.parent.removeChild(spr);
}
5. 遮罩与碰撞检测:
1)遮罩:
遮罩功能来自于DisplayObject,所有显示对象都具备此功能。所谓遮罩,实际上是指定一个显示对象的可见区域,Egret渲染时会根据设置的区域进行裁剪,最终得到原始画面的一部分。示例:
var rect:egret.Rectangle=new egret.Rectangle(100,50,200,100);
bmp.mask=rect;
如果要取消遮罩,可以将mask属性设置为null:
bmp.mask=null;
2)非精确碰撞检测:
当需要判断一个点与一个显示对象是否碰撞时,可以使用hitTestPoint()方法。该方法用于计算显示对象,以确定它是否与x和y参数指定的点重叠或相交。
console.log(shp.hitTextPoint(30,30);
3)精确碰撞检测:
非精确碰撞检测是使用包围盒的,还有一种基于像素的碰撞检测方法,开启精确碰撞检测需要加一个参数:
console.log(shp.hitTextPoint(30,30,true);
将hitTestPoint()方法的第3个参数设置为true,就开启精确碰撞检测,但这会消耗更多的计算机资源。
4)包围盒碰撞:
如果对两个显示对象进行碰撞检测,可以使用它们的包围盒的intersects()方法:
var rect1:egret.Rectangle=new egret.Rectangle(shp1.x,shp1.y,shp1.width,shp1.height);
var rect2:egret.Rectangle=new egret.Rectangle(shp2.x,shp2.y,shp2.width,shp2.height);
concole.log(rect1.intersects(rect2));
如果要判断两个显示对象shp1和shp2是否相交或者是否发生了碰撞,可以通过它们的x、y和width、height属性创建两个egret.Rectangle对象,也即包围盒,然后通过egret.Rectangle的intersects方法来确定两个包围盒的区域是否相交。
6. 混合模式:
Egret提供了4种混合模式,以便制作一些颜色的效果。DisplayObject类定义了blendMode属性,设置显示对象的混合模式。
1)NORMAL模式:
默认使用的混合模式为NORMAL,显示对象出现在背景前面,显示对象的像素值会覆盖背景的像素值。在显示对象为透明的区域,背景是可见的。
2)ADD模式:
ADD模式是将显示对象的原色值添加到它的背景颜色中,也就是3个颜色独立相加,上限值为0xFF,此设置通常用于使两个对象间的加亮溶解产生动画效果。
3)ERASE模式:
此模式会根据显示对象的alpha值擦除背景,alpha值不为0的区域将被擦除。
一般情况下,图片资源会放置在resource/assets目录下,将两个图片文件加入此目录,并编辑resource.json文件:
{"groups":[{"keys":"b_png,g_png","name":"preload"}],
"resources"[{"name":"b_png","type":"image","url":"assets/b.png"},
{"name":"g_png","type":"image","url":"assets/g.png"}]}
json文件中将两张图片形成一个group并命名为preload,还分别指明两张图片的类型和路径。这个json文件一般使用ResDepot资源管理工具产生,将所用图片拖入这个窗口,然后设置资源组名字,指定输出文件路径,自动产生。如果输出不正确,就需要按上述格式检查。
三、事件与用户交互:
Egret提供了与JavaScript一样的事件处理系统,并在此基础上提供了一个事件处理对象,通过事件处理机制可以方便地响应用户交互输入与系统事件。
1. 事件处理机制:
Egret的每个事件都由一个事件对象来表示,事件对象是egret.Event类或其子类的实例。事件对象不仅存储特定事件的信息,还包含便于操作事件对象的方法。例如,当Egret检测到用户轻触,就会创建一个egret.TouchEvent类的实例,。创建事件对象后,Egret即调度该事件对象。
如果事件目标在显示列表中,事件对象将在显示列表层次结构中向下传递,直到到达事件目标;在某些情况下,事件对象会沿着列表层次结构向上冒泡。处理事件可以使用事件监听器,编写代码用来响应特定事件,还必须将事件监听器添加到事件目标,或添加到作为事件对象的显示列表对象中。示例:
class Main extends DisplayObjectContainer {
public constructor(){
super();
this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
}
private onAddToStage(evt:egret.Event){
this.shape=new egret.Shape();
this.addChild(this.shape);
}
}
示例中为Stage被加入场景中时触发的事件机制,程序监听到有显示对象加入Stage,就派发egret.Event.ADDED_TO_STAGE事件。事件监听器中定义了一个私有方法.onAddToStage(),用于执行事件触发后的相关动作,其中第3个参数为传入调用对象自身。
当程序触发任何一个事件,Egret就会创建一个事件实例,同时会广播给所有可接收的事件目标。如果事件目标不在显示列表中,Egret就将事件直接调度到事件目标;如果事件目标在显示列表中,Egret将事件对象调度到显示列表,并在显示列表中沿层次结构传递,直到到达事件目标。
事件流就是事件实例在显示列表结构中传递的过程。显示列表结构顶部是舞台Stage,这是一种特殊的显示对象容器,用作显示列表的root。舞台由egret.Stage类表示,且只能通过显示对象访问,每个显示对象都有一个stage属性,用来表示应用程序的舞台,只有显示对象加入显示列表时生效。
当Egret为显示列表相关事件进行调度时,事件对象将进行一次从Stage到目标节点的往返过程。事件流分为捕获、目标和冒泡三个阶段,捕获阶段是从Stage到目标节点的父节点的所有节点,目标阶段仅包括目标节点,冒泡阶段包括从目标节点的父节点返回到Stage过程中遇到的节点。只要在事件流的路径上有相应的事件监听器,就可以监听到这个事件。
2. 事件类:
事件类是承载事件信息以及方法,每个事件都有关联的事件类型,类型以字符串值的形式存储在egret.Event.type属性中,通过事件的类型就可以区分不同类型的事件。常用的事件对象有触摸事件egret.TouchEvent、声音事件egret.SoundEvent、定时器事件egret.TimerEvent,还有文本事件egret.TextEvent、网络加载状态事件egret.HTTPStatusEvent、I/O错误事件egret.IOErrorEvent等。
Egret定义了一个egret.Event类,作为所有事件类的基类,此类定义了基本的属性和方法:
属性 |
说明 |
bubbles:boolean |
Event对象是否参与事件流冒泡阶段,只读属性,默认false |
currentTarget:any |
当前正在使用某个事件监听器处理Event的对象,只读属性 |
eventPhase:number |
事件流中的当前阶段,只读属性 |
target:any |
事件目标,包含目标节点,只读属性 |
data:any |
事件Event的数据或附近参数对象,可读写 |
type:string |
事件的类型,区分大小写,只读属性 |
常用方法有:
方法 |
说明 |
Event(type:string,bubbles:boolean=false,cancelable:boolean=false) |
创建一个作为参数传递给事件监听器的Event对象 |
stopImmediatePropagation() |
防止对事件流中当前节点和所有后续节点中的事件监听器进行处理 |
stopPropagation:void |
防止对事件流中当前节点的后续节点中的所有事件监听器进行处理 |
1)bubbles属性:
如果事件对象参与冒泡阶段,bubbles属性为true;如果为false则不参与冒泡阶段。
2)currentTarget属性:
包含对当前正在处理事件的对象的引用。因为可以将事件流中任何显示对象添加监听对象,也可以将监听器放在任何位置,并能将相同的监听器添加到不同的显示对象,在项目较大较复杂时这个属性是很有用的。
3)eventPhase属性:
可以通过调查事件对象的eventPhase属性确定事件的阶段,此属性是一个数值,1~3,代表3个事件流阶段。一般更常使用代表不同阶段的常量egret.EventPhase.CAPTURING_PHASE、egret.EventPhase.AT_TARGET、egret.EventPhase.BUBBLING_PHASE。示例:
if(event.eventPhase==egret.EventPhase.AT_TARGET){myFunction();}
4)target属性:
包含对事件目标对象的引用。如果用户在有重叠的显示列表对象的某一点进行触摸点击,Egret会选择距离Stage层次最深的对象作为事件目标。
对于嵌套复杂的显示对象,如按钮嵌套,target属性通常指向按钮的子对象,这时常将事件监听器添加到按钮并使用currentTarget属性。
5)data属性:
此属性包含事件传递过程中附加对象或参数。有时期望目标能识别事件触发方式,比如是用户触发还是程序触发,就可以设置data属性进行区分。
6)stopPropagation()和stopImmediatePropagation()方法:
这两种方法用来阻止在事件流中继续执行事件对象,差别在于egret.Event.stopPropagation()方法阻止事件对象移到下一个节点,但只有在允许执行当前节点上的任何事件监听器后才起作用;egret.Event.stopImmediatePropagation()方法不允许执行当前节点上的任何事件监听器。
7)type属性:
type属性是string类型常量,用来标识事件类型。常用的属性常量有:
属性值 |
说明 |
ENTER_FRAME:string |
定义enterFrame事件对象的type属性,表示主循环进入新的一帧 |
COMPLETE:string |
定义complete事件对象的type值,表示动作完成 |
CHANGE:string |
定义change事件对象的type属性,表示状态发生改变 |
RESIZE:string |
定义resize事件对象的type属性,表示Stage大小发生改变 |
ACTIVITY:string |
定义activity事件对象的type属性,表示游戏激活 |
DEACTIVITY:string |
定义deactivity事件对象的type属性,表示取消激活 |
ADDED_TO_STAGE:string |
定义added_to_stage事件对象的type属性,表示显示对象添加到Stage |
ADDED:string |
定义added事件对象的type属性,表示显示对象被添加到显示列表中 |
REMOVED_FROM_STAGE:string |
定义removed_form_stage事件对象的type属性,表示显示对象被移出Stage |
REMOVED:string |
定义removed事件对象的type属性,表示显示对象被移出显示列表 |
Egret提供的诸多事件类型,一般使用公共属性已经足够,但也有一些事件类型需要一些附加属性和事件类型来描述,比如与触摸相关的事件就有独特属性。egret.TouchEvent类就添加了9个属性,包含触摸的位置及同时按下的特定键等,还包括了TOUCH_TAP、TOUCH_MOVE、TOUCH_BEGIN、TOUCH_END等事件类型,分别表示轻触、移动、触摸开始、触摸结束事件。
用户为了一些特殊需要,还可以自定义事件:
class ProgressEvent extends egret.Event{
public static PROGRESS:string="progress";
public bytesLoaded:number=0;
public bytesTotal:number=0;
public constructor(type:string,bubbles:boolean=false,cancelable:boolean=false,
bytesLoadedLnumber=0,bytesTotal:number=0){
super(type,bubbles,cancelable);
this.bytesLoaded=bytesLoaded;
this.bytesTotal=bytesTotal;
}
}
自定义事件类需要继承egret.Event类,并定义其中的属性和方法。
3. 监听器:
事件监听器也称为事件处理函数。添加事件监听器分为两步,首先要创建一个为响应事件而执行的函数或类的方法,然后使用addEventListener方法在事件的目标或适当的事件流上任何显示列表中注册监听器函数。
1)创建监听器:
具有egret.IEventDispatcher接口的类都有监听事件的能力。egret.IEventDispatcher接口提供了5个与事件监听有关的方法:
事件监听方法 |
说明 |
addEventListener(type:string,listener:Function,thisObject:any,useCapture?:boolean,priority?number) |
使用egret.EventDispatcher对象注册事件监听对象,以使用监听器能够接收事件通知 |
removeEventListener(type:string,listener:Function,thisObject:any,useCapture?:boolean) |
从egret.EventDispatcher对象中删除监听器 |
hasEventListener(type:string):boolean |
检查egret.EventDispatcher对象是否为特定事件类型注册了任何监听器 |
dispatchEvent(event:Event):boolean |
将事件调度到事件流中 |
willTrigger(type:string):boolean |
检查是否用此egret.EventDispatcher对象或任何始祖为指定事件注册了事件监听器 |
要想使一个对象监听某个事件,要使用addEventListener方法注册事件监听器。在写一个具体的事件监听器代码时,由于egret.Shape是egret.EventDispatcher的子类,可以直接进行监听注册:
2)注册监听器和移除监听器:
addEventListener方法是egret.IEventDispatcher接口的主函数,用来注册监听器函数,其中必要的参数是type、liastener和thisObject。type参数用来指定事件类型,listener参数用于指定发生事件时要执行的函数,thisObject绑定this对象。指定listener参数时不要使用括号。
removeEventListener方法用来删除不再需要的事件监听器,必须的参数有eventName、listener和thisObject,这些参数与对应的addEventListener方法中的参数相同。
监听器方法addEventListener和removeEventListener中的thisObject比较特殊,一般填写解调函数所属的对象。
2. 事件的优先级:
addEventListener方法有两个可选的参数来设置事件流阶段和监听器优先级。其中的useCapture参数用于确定监听器运行的阶段,如果将useCapture设置为true,则监听器只在捕获阶段处理事件,而在模板阶段和冒泡阶段不处理事件;如果设为false,监听器只在目标阶段和冒泡阶段处理事件。如果要在3个阶段都监听事件,要调用两次addEventListener,一次将useCapture设置为true,第2次再将useCapture设置为false。
通过使用addEventListener方法的priority参数设置该事件监听器的优先级,参数是一个整数,默认为0,数值越大优先级越高,如果两个或更多个监听器共享相同的优先级,会按照添加顺序进行处理。这个参数并不是DOM Level 3事件模型的正式部分,是Egret组织事件监听器提供的灵活性。
3. 自定义事件发送器:
Egret中定义事件有多种方法,本质来自于EventDispatcher。
1)继承EventDispatcher:
class ExampleEventDispatcher extends egret.EventDispatcher{
public constructor(){
super();
}
public dispatchEventWith(eventName:string){
super.dispatchEvent(new egret.Event(eventName));
}
}
当ExampleEventDispatcher继承了EventDispatcher就具有派发事件能力。继承往往是为了扩展,然后就可以使用增加新的派发事件方法dispatchEventWith:
class Main extends egret.DisplayObjectContainer {
public constructor(){
super();
var example:ExampleEventDispatcher=new ExampleEventDispatcher();
example.dispatchEventWith("complete");
}
}
2)复合EventDispatcher方法:
class ExampleEventDispatcher{
private _dispatcher:egret.EventDispatcher;
public constructor(){
this._dispatcher=new egret.EventDispatcher();
}
public getEventDispatcher():egret.EventDispatcher{
return this._dispatcher;
}
}
复合对象是包含了其他对象的对象,例如一幅图由基本的线、圆、矩形和文本等对象组成,图就是复合对象。上例中ExampleEventDispatcher没有继承对象,而是通过创建_dispatcher实例使其内部的getEventDispatcher具有派发事件能力。使用的方法为:
class Main extends egret.DisplayObjectContainer {
public constructor(){
super();
var example:ExampleEventDispatcher=new ExampleEventDispatcher();
example.getEventDispatcher().dispatchEvent(new egret.Event("complete"));
}
}
3)实现IEventDispatcher接口:
class ExampleEventDispatcher extends egret.HashObject implements egret.IEventDispatcher{
private _dispatcher:egret.EventDispatcher;
public constructor(){
super();
this._dispatcher=new egret.EventDispatcher();
}
public addEventListener(type:string,listener:Function,thisObject:any,
useCapture:boolean=false,priority:number=0):void{
this._dispatcher.addEventListener(type,listener,thisObject,useCapture,priority);
}
public dispatchEvent(event:egret.Event):boolean{
return this._dispatcher.dispatchEvent(event);
}
public hasEventListener(type:string):boolean{
return this._dispatcher.hasEventListener(type);
}
public removeListener(type:string,listener:Function,thisObject:any,
useCapture:boolean=false):void{
this._dispatcher.removeEventListener(type,listener,useCapture);
}
public willTrigger(type:string):boolean{
return this._dispatcher.willTrigger(type);
}
}
ExampleEventDispatcher类中实现了egret.IEventDispatcher接口,需要实现其中指定的所有方法、属性,这种方法更加规范。使用方法为:
class Main extends egret.DisplayObjectContainer {
public constructor(){
super();
var example:ExampleEventDispatcher=new ExampleEventDispatcher();
example.dispatchEvent(new egret.Event("complete"));
}
}
使用方法与原生的egret.EventDispatcher一致。
4. 触摸事件:
移动终端上,触摸是最常用的事件类型,可以监听由egret.TouchEvent类中的定义的一系列触摸事件。
1)触摸事件类型:
常用触摸类型有:
事件类型 |
说明 |
TOUCH_TAP |
轻触事件,与鼠标点击事件类似 |
TOUCH_MOVE |
移动事件,与鼠标左键按下并移动类似 |
TOUCH_BEGIN |
开始触摸 |
TOUCH_END |
在同一对象上结束触摸 |
TOUCH_RELEASE_OUTSIDE |
在对象外部结束触摸 |
Egret为了提高事件执行效率,默认关闭显示对象的触摸事件。如果需要进行显示对象对触摸的监听,需要打开触摸属性,可以提高touchEnable=true来设置显示对象接收触摸/鼠标事件。示例:
var t:egret.Texture=obj;
this.bitmap=new egret.Bitmap(t);
this.bitmap.touchEnabled=true;
this.addChild(this.bitmap);
this.bitmap.addEventListener(egret.TouchEvent.TOUCH_MOVE,this.onTouchMove,this);
2)触摸事件中的特有属性:
egret.TouchEvent对egret.Event事件做了扩展,新增的属性有:
属性 |
说明 |
stageX |
事件发生点在全局Stage坐标中的水平坐标 |
stageY |
事件发生点在全局Stage坐标中的垂直坐标 |
localX |
事件发生点相对于currentTarget的水平坐标 |
localY |
事件发生点相对于currentTarget的垂直坐标 |
touchPointID |
分配给触摸点的唯一标识号 |
touchDown |
表示触摸已按下还是未按下,值分别为true和false |
四、游戏资源管理:
游戏开发中会使用很多素材,如图片、音频等。Egret中,所有资源都存储在服务器端,当用户打开游戏时会将资源下载到用户本地计算机中,然后转载到内存。
1. RES资源加载模块:
RES是Egret为开发者准备的资源加载机制。当开发者编写游戏时,只需指定加载的资源,并在对应的逻辑位置中添加相应的执行加载代码。
RES是一个可选模块,该模块与Egret核心库完全分离。把资源加载的任务都交给RES模块进行管理,包括载入资源加载配置、根据配置载入资源、载入完好后调度相应事件等。
配合RES模块,还有ResDepot资源管理工具,能管理游戏素材和配置文件资源,快速制作生成Egret所需的资源配置文件,定制分组加载规则。
使用ResDepot时,找到项目所在目录,打开resource目录中的default.res.json文件。需要添加新的资源时,只需要将资源放入resource目录中任意位置,拖动资源到工具界面,便可生成游戏加载的配置文件。需要注意,只有资源拖入具体分组,游戏资源才会被加入,可以根据需要分组加载。
2. 资源配置文件:
Egret中使用JSON作为RES资源配置文件格式,比如default.res.json代码为:
{"resource":[{"name":"bgImage","type":"image","url":"assets/pg.jpg"),
{"name":"egretIcon","type":"image","url":"assets/egret_icon.png"},
{"name":"description","type":"json","url":"config/description.json"}],
"groups":[{"name":"preload","keys":"bgImage,egretIcon"}]}
配置文件中的resource为资源库,当前项目中使用的资源都放在其中,其中每一项配置信息都包括3个属性,name为资源标识符,type为资源类型,url为资源路径。groups是资源组的配置,每一项是一个资源组,其中每项包括两个属性,name为资源组名,keys列出资源组包含的资源,使用逗号分隔每一个资源标识符字符串,与resource下的资源name对应。
浏览器有缓存机制,一般是在url中加入版本号,格式为"url":"assets/bg.jpg?v=1.0"。
Egret支持7种资源类型,包括bin、image、json、sheet、font、text、sound,资源必须填写类型。一般并不手动编写default.res.json,而是使用ResDepot来生成,以便减少可能的错误。
3. 加载资源配置文件:
常规游戏开发中,需要先加载资源文件进行解析,然后加载特定资源。RES模块对资源加载有两种读取方式。
1)外部文件方式:
新建项目后,在resource目录中会有一个default.res.json文件。一般在程序入口函数或Stage载入后的egret.Event.ADDED_TO_STAGE事件函数中放入加载监听代码:
RES.addEventListener(RES.ResourceEvent.CONFIG_COMPLETE, this.onConfigComplete,this);
RES.addEventListener(RES.ResourceEvent.CONFIG_LOAD_ERROR, this.onConfigLoadErr,this);
RES.loadConfig("resource/default.res.json","resource/");
RES.loadConfig()函数执行动作,初始化RES资源加载模块,函数包含两个参数,前面一个是文件完整路径,后面一个是每个资源的相对路径基址。如果要在初始化完成后再做一些处理,监听RES.ResourceEvent.CONFIG_COMPLETE事件即可。载入配置难以保证不出错,所以要监听RES.ResourceEvent.CONFIG_LOAD_ERROR事件。RES.loadConfig()只执行一次。
2)直接读取:
直接将资源加载配置内容以参数方式给出,资源相对路径的基址为resource/:
class Main extends egret.DisplayObjectContainer{
public constructor(){
super();
this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
}
private onAddStage(event:egret.Event){
RES.getResByUrl('/resource/assets/bg.jpg', this.onComplete, this,
RES.ResourceItem.TYPE_IMAGE);
}
private onComplete(event:any):void{
var img:egret.Texture=<egret.Texture>event;
var bitmap:egret.Bitmap=new egret.Bitmap(img);
this.addChild(bitmap);
}
}
直接读取方式不需要建立单独的配置文件,也不用监听RES.ResourceEvent.CONFIG_ COMPLETE事件。
两种方法是等效的,常规项目中两种方式混用,外部文件用于加载固定资源,直接读取方式用于加载变化资源。Egret Wing创建项目时会默认带有RES加载模块。
4. 预加载资源组:
在配置文件加载完成后,通过调用RES.loadGroup(name:string, priority:number=0)开始预加载配置中的一组资源,该函数有两个参数,name参数为要加载资源组的组名,priority为优先级,可以为负数,默认为0。低优先级组必须等待高优先级组完全加载结束才能开始,同一优先级的组会同时加载。
预加载可以在游戏启动时,也可以是某个面板被打开前或场景切换时,调用时机由具体项目确定,代码为:
RES.addEventListener ( RES.ResourceEvent.GROUP_COMPLETE, this.onResourceLoadComplete, this);
RES.addEventListener ( RES.ResourceEvent.GROUP_PROGRESS, this.onResourceProgress, this);
RES.addEventListener ( RES.ResourceEvent.GROUP_LOAD_ERROR, this.onResourceLoadErr, this);
其中,RES.ResourceEvent.GROUP_COMPLETE为组资源加载完成事件;RES.ResourceEvent. GROUP_PROGRESS为组资源加载进度事件,单个加载完成会触发此事件;RES.ResourceEvent. GROUP_LOAD_ERROR是组资源加载失败事件。在组加载回调函数中,需要使用event. groupName判断事件是哪个资源组的,因为可能会有多个资源组同时在加载。示例代码为:
private onResourcePrigress(event:RES.ResourceEvent):void{
if(event.groupName=="preload"){
this.loadingView.setProgress(event.itemLoaded, event.itemTotal);
}
}
由于网络延迟等原因,可能造成资源加载失败,这种情况下将会派发RES.ResourceEvent. GROUP_LOAD_ERROR事件,可以在事件处理中重新加载资源:
private onResourceLoadErr(event:RES.ResourceEvent):void{
RES.loadGroup(event.groupName);
}
在复杂网络环境中,可能会出现多次加载失败,这时需要在一定的失败次数后停止加载。需要对加载次数进行计数,修改代码为:
private onResourceLoadErr(event:RES.ResourceEvent):void{
if(++this.countGroupError<3){
RES.loadGroup(event.groupName);
}else{
//弹出网络失去连接提示等
}
}
对于多个资源同时加载的情况,countGroupError可以使用一个以groupName为键的哈希数组来记录每个资源组的加载失败次数。
若同时启动多个资源组一起加载,比如在加载preload前,希望先加载一个更小的loading资源组,以提供显示preload组加载进度的素材,可以使用RES.loadGroup()的第2个参数priority,为loading组传入一个优先级更高的数字,来迫使loading组在preload前加载完成,代码为:
RES.loadGroup("loading",1);
RES.loadGroup("preload",0);
5. 动态创建资源组:
若资源组无法预先在文件中配置,需要运行时动态确定,可以通过调用方法:
RES.createGroup(groupName:string, keys:Array<string>, override:boolean):boolean
该方法动态创建一个资源组,其中参数groupName为要创建的资源组名;参数keys为要包含的键名列表,key对应配置文件中的name属性或subKeys属性的一项或一个资源组名;参数override指示是否覆盖已经存在的同名资源名,默认false。
动态创建资源组后,调用loadGroup()方法加载。
6. 读取资源文件:
RES资源管理器模块有3种资源获取方式:
1)同步获取资源:
RES.getRes(name:string):any
同步方式只能获取已经缓存过的资源,例如之前调用过loadGroup()被预加载的资源。
2)异步获取资源:
RES.getResAsync(name:string, comFunc:Function, thisObject:any):void
异步方式可以获取配置中含有的所有资源项。如果缓存中存在,直接调用回调函数返回;若不存在,就启动网络加载文件并解析后回调。
3)通过url获取不在配置中的资源:
RES.getResByUrl(url:string, compFunc:Function, thisObject:any, type:string=""):void
通常不建议使用这个接口,只有那些不适合填写在配置中的资源才采用这种方式。
可以看到,RES.getRes()可直接获取资源文件,而RES.getResAsync()和RES.getResByUrl()需要回调函数,在回调函数中获取资源。
前两种获取方式的name参数都对应配置文件中资源项的name属性。如果name对应的文件是SpriteSheet等含有多个子资源的类型,可以使用“.”语法直接获取子资源。比如,配置中有一个name为icons的SpriteSheet文件,它里面含有一个activity_10的子位图,要获取这个子位图,可以使用代码:
var spriteSheet:egret.SpriteSheet=RES.getRes("icons");
var texture=SpriteSheet.getTexture("activity_10");
等同于:
var texture:egret.Texture=RES.getRes("activity_10");
因为在不同的SpriteSheet中可能有重复的纹理,最好使用资源ID全路径方式:
var texture:egret.Texture=RES.getRes("icons.activity_10");
上述两种方法是等效的。
7. 资源的缓存机制:
default.res.json中resource节点下配置的每个资源加载项,在第一次加载成功后,会用name属性作为key在内存缓存下来。再次请求时,直接从内存缓存中取。如果两个组都含有一个资源,第2个组再加载这个资源时,也会直接从缓存中读取,不会重复发起加载请求。
在移动设备中,文件缓存机制还不是很完善,再次进入游戏时资源会从服务器重新加载。
配置文件加载项时,常见的控制缓存机制方法是在url后加入特定字符,如:
assets/bg.png?v=20180101
每次修改v=xxxx就是一个新地址,保持地址不重复就不会缓存,这样可以合理安排更新特定资源。
8. 释放资源:
RES在第一次加载资源后,会将这个资源缓存下来,可以使用RES.destroyRes释放资源。
通过传入资源文件的name即可清理对应的缓存,传入资源组名可清理整个资源组对应的缓存,如果要清理RES.getResByUrl(url)加载的资源,传入url就可以清理。
9. 内置文件类型解析器:
RES内置支持的文件类型如表:
类型 |
说明 |
RES.ResourceItem.TYPE_BIN |
解析为原始的二进制文件 |
RES.ResourceItem.TYPE_IMAGE |
解析为egret.Texture对象 |
RES.ResourceItem.TYPE_TEXT |
解析为string变量 |
RES.ResourceItem.TYPE_JSON |
解析为JSON对象 |
RES.ResourceItem.TYPE_SHEE |
解析为egret.SpriteSheet对象 |
RES.ResourceItem.TYPE_FONT |
解析为egret.BitmapFont对象 |
RES.ResourceItem.TYPE_SOUND |
解析为egret.Sound对象 |
RES.ResourceItem.TYPE_XML |
解析为egret.XML对象 |
1)配置九宫格参数:
九宫格切图是一种用于拉伸位图并使其保持不失真的解决方案。在ResDepot中使用右键菜单中九宫格子菜单,可在弹出界面开启九宫格编辑功能。
可以通过default.res.json文件设置scale9grid属性:
{"name":"button","scale9grid":"22,0,10,60","type":"image","url":"assets/button.png"}
其中,scale9grid属性的4个值分别表示九宫格矩形的x、y、width、height。
2)读取解析二进制文件:
图像文件可以作为二进制文件读取,通过default.res.json文件设置为:
{"name":"bg","type":"bin","url":"assets/wall.png"}
资源载入后,读取前两个字节的内容,并输出为16进制文件。解析二进制数据代码:
var baImage:egret.ByteArray=new egret.ByteArray(RES.getRes("bg"));
console.log("JPEG SOL:",baImage.readUnsignedShort().toString(16));
这种方式读取bin,得到的数据是ArrayBuffer格式,需要作为ByteArray。
3)扩展资源文件类型解析器:
可以根据资源文件中type类型自定义需要的解析器:
RES.registerAnalyzer(type:string, analyzerClass:any)
其中,参数type为注入的类型,参数analyzerClass为注入类型需要解析的类。其中解析类是自定义的类,当RES加载完文件后,根据其类型就使用对应的解析类来解析。这种方式会替换默认的内置解析器,默认内置解析器可以在RES.ResourceItem中找到。
五、位图操作:
位图是最基本的游戏资源,几乎所以游戏都依赖位图呈现背景、角色及界面等。Egret支持JPEG和PNG位图图像文件,其中PNG支持256级透明度。Egret只能使用准备好的图片,而不能动态创建。
1. 加载并显示位图:
Egret中使用位图需要将准备好的JPG或PNG图片文件作为资源加载进来。加载位图使用加载资源文件的方法,不论JPG还是PNG格式,统一设置为image类型。
下列代码放在位图资源加载完成后的位置:
var img:egret.Bitmap=new egret.Bitmap();
img.texture=RES.getRes("penguin");
this.addChild(img);
在Egret中,位图使用egret.Bitmap类创建,使用RES.getRes()获取位图内容数据并设置为位图的纹理texture,然后加入显示容器的显示列表就可以显示了。
2. 操作纹理集:
虽然可以使用RES.getRes()方法根据资源名直接获取位图纹理,但在实际项目中往往需要大批位图资源,一般使用精灵表Sprite Sheet进行管理。
Egret的Texture Merger工具可生成纹理集,其中包括Sprite Sheet功能。可以使用添加纹理按钮打开若干图片文件,或者拖曳图片到精灵表界面,也可以将包含图片的整个目录拖到工作区。纹理加载完成后会显示在界面中。当所有图片资源都添加进来后,可以导出到一个目录,其中包括一个JSON文件和一个PNG文件。
其中JSON文件中内容的示例代码为:
{"file":"swords.png", "frames":{"image1":{"x":1, "y":128, "w":101, "h":125, "offX":0, "offY":0, "sourceW":101, "sourceH":125}, "image2":{"x":1, "y":383, "w":80, "h":124, "offX":0, "offY":0, "sourceW":80, "sourceH":124}}}
其中,file指定对应的PNG文件路径,frames指定每一个图元在纹理中的信息。代码中可以用精灵表名与图元名组合来获取位图的纹理,如swords.image1。创建位图代码为:
var sword1:egret.Bitmap=new egret.Bitmap(RES.getRes("swords.image1"));
也可以使用一种简写方法:
var sword1:egret.Bitmap=new egret.Bitmap(RES.getRes("image1"));
不过,如果有多个精灵表且含有重复图元名时可能会出错。
3. 纹理填充方式:
当设置的位图的宽高与其纹理的宽高不一致时,就要使用不同的纹理填充方法。
1)位图拉伸以填充区域:
默认时,会将整个位图进行缩放,以匹配所设置的宽高值。代码为:
bone.fillMode=egret.BitmapFillMode.SCALE;
直接缩放会使原图出现变形。
2)重复位图以填充区域:
Egret提供了类似电脑屏幕平铺效果,填充模式设置为REPEAT:
bone.fillMode=egret.BitmapFillMode.REPEAT;
4. 位图的九宫格:
为了让一幅图适应不同界面,不至于因为拉伸压缩变形过大,把一幅图按九宫格方式分割,可以在代码中通过矩形构造函数传入:
bgDialog.scale9Grid=new egret.Rectangle(70,70,180,180);
也可以使用ResDepot工具的九宫格编辑按钮打开编辑界面,使用鼠标拖动调整位置,然后保存到项目json文件中。经过九宫格编辑的图片有了scale9grid属性,示例:
{"name":"bgDialog","scale9grid":"66,66,203,200","type":"image","url":"assets/bgDialog.png"}
资源清单中已经包含了九宫格信息,程序中加载图片时就不再需要设置这个属性,可直接进行宽度和高度调整。
5. 滤镜:
滤镜用来对位图使用某种算法实现特殊效果。Egret内核提供了几种常用的滤镜,不过一些版本并不支持。
滤镜仅限于在Runtime中可用,推荐用WebGL方式来调试项目,这样可以看到滤镜效果。切换到WebGL渲染方式,需要打开项目的launcher/egret_loader.js文件,其中代码为:
var rendererType=0;
将变量rendererType的值修改为1,即切换到WebGL渲染模式。但因为在移动设备上支持WebGL的浏览器有限,建议只在测试阶段使用WebGL。
可以将滤镜应用于任何显示对象,如MovieClip、TextField、Bitmap等,要对显示对象应用滤镜要使用filter属性,设置这个属性并不会修改对象,清除filter属性就可以删除相应滤镜。
1)发光滤镜GlowFilter:
使用GlowFilter类可以对显示对象应用发光效果,其中有多个选项,包括内侧发光、外侧发光、挖空模式等。示例代码:
applyGlowFilter(disp:egret.DisplayObject):void{
var color:number=0x33ccff; //光晕颜色
var alpha:bumber=0.8; //光晕透明度
var blurX:number=35; //水平模糊量,有效值0~255.0浮点
var blurY:number=35; //垂直模糊量,有效值0~255.0浮点
var strength:number=2; //压印的强度,值越大压印的颜色越深,0~255
var quality:number=egret.BitmapFilterQuality.HIGH; //应用滤镜次数
var inner:boolean=false; //指定发光是否为内侧发光
var knockout:boolean=false; //指定是否具有挖空效果
var glowFilter:egret.GlowFilter=new egret.GlowFilter(color, alpha, biurX, blurY, strength, quality, inner, knockout);
disp.filters=[glowFilter];
}
上述代码结束处添加一行代码可以实现对一个位图文件应用发光滤镜:
this.applyGlowFilter(sword1Filter);
2)投影滤镜DropShadowFilter:
可使用DropShadowFilter类向显示对象添加投影。阴影算法基于模糊滤镜。投影样式有多个选项,包括内侧投影、外侧投影和挖空模式。创建一个函数用来对传入的显示对象用给定的参数进行投影滤镜处理:
applyDropShadowFilter(disp:egret.DisplayObject):void{
var distance:number=6; //阴影偏移距离,以像素为单位
var angle:bumber=45; //阴影角度,0~360
var color:number=0x000000; //阴影颜色,不包含透明度
var alpha:number=0.7; //颜色透明度,对color参数的透明度设定
var blurX:number=16; //水平模糊量,有效值0~255浮点
var blurY:number=16; //垂直模糊量,有效值0~255浮点
var strength:number=0.65; //压印的强度,值越大压印的颜色越深
var quality:number=egret.BitmapFilterQuality.LOW; //应用滤镜次数
var inner:boolean=false; //指定发光是否为内侧发光
var knockout:boolean=false; //指定是否具有挖空效果
var dropShadowFilter:egret.DropShadowFilter=new egret.DropShadowFilter(distance, angle, color, alpha, biurX, blurY, strength, quality, inner, knockout);
disp.filters=[dropShadowFilter];
}
上述代码结束处添加一行代码可以实现对一个位图文件应用发光滤镜:
this.applyDropShadowFilter(sword1Filter);
投影滤镜比发光滤镜多出两个参数distance和angle,当这两个参数设为0时,两个滤镜效果很相似。
3)颜色矩阵滤镜ColorMatrixFilter:
使用ColorMatrixFilter类可以将4x5矩阵转换应用于输入图像上的每个像素的RGB颜色和alpha值,以生成具有一组新的RGB颜色和alpha值的结果,包括饱和度更改、色相旋转、亮度为alpha等效果。由20个元素的数组用于4x5转换矩阵,matrix属性不能通过直接修改它的值来更改,必须先获取对数组的引用,然后对引用进行更改,然后重置该值。
颜色矩阵滤镜将每个像素分离成RGB和alpha,分别以srcR、srcG、srcB、srcA表示。要计算4个通道中每个通道的结果,可将图像中每个像素的值乘以转换矩阵中的值,并可以添加一个介于-255~255的偏移量。滤镜将各颜色分量重新组合为一像素。颜色矩阵的计算方法:
redResult=(a[0]*srcR)+(a[1]*srcG)+(a[2]*srcB)+(a[3]*srcA)+a[4];
greenResult=(a[5]*srcR)+(a[6]*srcG)+(a[7]*srcB)+(a[8]*srcA)+a[9];
blueResult=(a[10]*srcR)+(a[11]*srcG)+(a[12]*srcB)+(a[13]*srcA)+a[14];
alphaResult=(a[15]*srcR)+(a[16]*srcG)+(a[17]*srcB)+(a[18]*srcA)+a[19];
其中,a[0]~a[19]为矩阵中的20个元素,相应值已经传递到matrix属性中。matrix数组中的值取值范围0~1,需要根据场景需要取适当的值以达到特定效果。
对位图进行红色矩阵变换的方法为:
private applyRed(disp:egret.DisplayObject):void{
var matrxi:any[]=[];
matrix=matrix.concat([1,0,0,0,0]); //red
matrix=matrix.concat([0,0,0,0,0]); //green
matrix=matrix.concat([0,0,0,0,0]); //blue
matrix=matrix.concat([0,0,0,1,0]); //alpha
var redColorMatrixFilter:egret.ColorMatrixFilter=new egret.ColorMatrixFilter(matrix);
disp.filters=[redColorMatrixFilter];
}
在以上代码结束处添加一行代码对位图应用红色转换矩阵滤镜:
this.applyRed(swordFilter);
类似地,对位图进行绿色矩阵变换方法为:
private applyGreen(disp:egret.DisplayObject):void{
var matrxi:any[]=[];
matrix=matrix.concat([0,0,0,0,0]); //red
matrix=matrix.concat([0,1,0,0,0]); //green
matrix=matrix.concat([0,0,0,0,0]); //blue
matrix=matrix.concat([0,0,0,1,0]); //alpha
var greenColorMatrixFilter:egret.ColorMatrixFilter=new egret.ColorMatrixFilter(matrix);
disp.filters=[greenColorMatrixFilter];
}
而对位图进行蓝色矩阵变换的方法为:
private applyBlue(disp:egret.DisplayObject):void{
var matrxi:any[]=[];
matrix=matrix.concat([0,0,0,0,0]); //red
matrix=matrix.concat([0,0,0,0,0]); //green
matrix=matrix.concat([0,0,1,0,0]); //blue
matrix=matrix.concat([0,0,0,1,0]); //alpha
var blueColorMatrixFilter:egret.ColorMatrixFilter=new egret.ColorMatrixFilter(matrix);
disp.filters=[blueColorMatrixFilter];
}
4)模糊矩阵BlurFilter:
可使用BlurFilter类将模糊视觉效果应用于显示对象。模糊效果可以柔化图像的细节,当此滤镜的quality属性设置为低时结果为柔化,当设置为高时接近高斯模糊滤镜。示例:
private applyBlur(disp:egret.DisplayObject):void{
var blurX:number=0.7; //水平模糊量,值范围0~266浮点
var blurY:number=0.7; //垂直模糊量,值范围0~266浮点
var blurFilter:egret.BlurFilter=new egret.BlurFilter(blurX, blurY);
disp.filters=[blurFilter];
}
建议使用0~1之间的值,大于1后相对原图会出现较大变化。
5)设置滤镜品质:
BitmapFilterQuality提供用于设定各种滤镜的品质常量,实际上是对位图应用某种滤镜效果的次数。为了应用不同的品质,可以在构建滤镜函数时增加一个参数quality,用来传递需要的品质参数。
对显示对象应用滤镜,建议将显示对象的cacheAsBitmap属性设置为true,以避免每次进行帧刷新时重新计算滤镜。
六、文本:
Egret中,所有文本的显示都是通过egret.TextField类,GUI库中的egret.gui.TextArea与egret.gui.TextInput等文本组件也是使用egret.TextField类显示文本。egret.TextField的使用有3种类型,分别为普通文本、输入文本和密码文本:
·普通文本:正常显示各种文本,内容可被程序设置,是最常见的类型
·输入文本:能被用户输入的文本,常用于登录输入框或游戏中的聊天窗口
·密码文本:可被用户输入的文本,输入内容会被特殊字符替换,常用于游戏登录窗口
egret.TextField采用浏览器/设备的API进行渲染,在不同平台上会有差异,避免差异可以使用egret.BitmapText位图字体,其使用了egret.Bitmap+SpriteSheet方式渲染,但体积较大。
1. 普通文本:
1)创建普通文本:
可以直接创建一个egret.TextField,并设置相关属性:
class Main extend egret.DisplayObjectContainer{
public constructor(){
super();
this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
}
private onAddToStage(event:egret.Event){
var label:egret.TextField=new egret.TextField();
label.y=40;
label.text="这是一个文本";
this.addChild(label);
}
}
示例中,当创建一个egret.TextField文本对象后,就有了一个普通文本对象。其显示内容来自text属性,也需要将其加入到显示列表中才能显示。当一个TextField被赋予一个文本时,egret.TextField会自动计算出适合的大小,得到一个width和height属性值。
实际应用中,可以手动设置文本的宽高值:
label.width=70;
label.height=70;
默认情况下,单个汉字大约占30像素,如果文字超出设定宽度会自动换行,超出设定区域之外的文字会被剪切。
2)设置文本样式:
egret.TextField包含很多文本样式属性,通过这些样式的设置可以调整文字或容器的外观。
⑴文本边框:
文本的border属性设置为true可加边框,并可以设置borderColor属性指定边框颜色。
label.border=true;
label.borderColor=0xffffff;
默认边框颜色是黑色的,borderColor设为0xffffff为文字加白色边框。将border属性设为false会关闭边框。
⑵文本描边:
将文本strokeColor属性设为需要的颜色值可创建文本描边,还可以设置stroke属性改变描边宽度。示例:
label.strokeColor=0x0000ff;
label.stroke=2;
代码中设置描边粗细为2像素,描边颜色为0x0000ff,即蓝色。在不同浏览器中设置不同字体时对描边有影响。
⑶文本粗体与斜体:
将文本bold属性设为true可以设为粗体文字,将italic属性设为true可以改变字体为斜体,两种属性可同时设置为true。
⑷字体:
通过设置文本的fontFamily属性,可以改变文本字体。但字体能否使用,依赖用户使用的系统是否存在,所以尽可能选择所有计算机中均存在的字体。
label.fontFamily="Arial";
3)多样式混合文本:
实际应用中,往往需要文字有丰富的样式变化,首先要按照样式差异把文本分段,分别设定每一段文本元素的样式。建立多种样式混合文本的基本结构为ITextElement:
interface ITextElement{
text:string;
style:ITextStyle;
}
其中,ITextStyle是所需要定义的各种样式属性的集合,以Object样式给出,其中每个元素就是一种样式属性的键值定义,如{"textColor:0xff0000}。
在style属性中,可以包含多个样式组合定义,示例:
var label:egret.TextField=new egret.TextField;
label.textFlow=<Array<egret.ITextElement>>[{text:"Egret",
style:{"textColor":0xff0000, "size":"30"}}];
this.addChild(label);
代码中把文字Egret设为红色0xff0000,并设置字体大小为30。
Egret也支持HTML的CSS设置方式,示例:
class Main extend egret.DisplayObjectContainer{
public constructor(){
super();
this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
}
private onAddToStage(event:egret.Event){
var label:egret.TextField=new egret.TextField();
label.textFlow=(new egret.HtmlTextParser).parser(
'没有格式的文本,\n'+'<font color="#0000ff" size="30"
fontFamily="Verdana">Verdana blue large</font>\n'+'<font color="#ff7f50"
size="10">珊瑚色<b>局部加粗</b>小体字</font>'+'<i>斜体</i>');
label.y=40;
this.addChild(label);
}
}
但Egret并不是支持所有HTML标记,<p></p>和<br>标记就不支持,换行需要使用\n。
4)文本超链接:
egret.TextField可以响应触摸事件,但只是针对整个egret.TextField。如果需要在一段文字中加入一个可以响应触摸事件的热区,可以通过设置href超链接方式:
class Main extend egret.DisplayObjectContainer{
public constructor(){
super();
this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
}
private onAddToStage(event:egret.Event){
var label:egret.TextField=new egret.TextField();
label.textFlow=(new egret.HtmlTextParser).parser(
'点击---<a href="event:text event link">点击触发事件</a>---点击');
label.y=40;
label.touchEnable=true;
label.addEventListener(egret.TextEvent.LINK, function(evt:egret.TextEvent){
console.log(evt.text);},this);
this.addChild(label);
}
}
使用时,首先要建立textFlow属性,其中包括href标记,href中的内容以event:开头,后面跟随一个字符串,用于输出相应的文字或用于识别包含该连接的文字段。然后监听TextEvent.LINK事件,在事件处理函数中通过事件对象的text属性来获取该文字字段所设置的字符串。为了使事件生效,还必须设置touchEnable属性为true。
也可以使用egret.ITextElement方式进行设置:
label.textFlow=new Array<egret.ITextElement>(
{text:"点击---",style:{}},{text:"点击触发事件",style:{"href":"event:text event triggered"}});
目前,href仅支持这种链接到事件处理函数的格式,不支持超链接打开url。
2. 输入文本:
1)创建可输入文本:
要让用户可以输入文本,需把文本属性type设置为egret.TextFieldYpe.INPUT:
label.type=egret.TextFieldType.INPUT;
输入文本通常会有边框和背景,一般会支持输入框,文本覆盖到文本框上,代码为:
label.border=true;
label.background=true;
如果需要将当前的文本输入修改为动态的:
label.type=egret.TextFieldType.DYNAMIC;
这样,文本输入会变为不可输入状态。
2)设置输入文本样式:
当用户输入密码时,通常不希望密码被旁边的人看到,惯例是显示星号。创建以星号屏蔽输入内容的文本需要把文本属性displayAsPassword设为true:
userpwd.displayAsPassword=true;
密码文本只会在文本失去焦点后触发,当获得焦点,会继续显示当前输入的内容。
3. 位图文本:
1)创建位图文本字体:
Texture Merger是一款纹理集打包和动画转换工具,能将零散的小图合并为大图纹理集,还可以将GIF和SWF动画转换为Egret支持的动画格式。Texture Merger还有Bitmap font位图字体编辑功能,打开界面后选择Bitmap font,添加字符或用“更多字符”创建纹理字符集图片。选择所需字体以及字体大小、颜色等参数,输入文字,确认并导出纹理图。导出文件在项目目录的resource/assets/目录下,会有fnt和png两个文件,其中fnt是一种未知MIME格式,经常会遇到加载不到文件等错误,需要在服务器端添加扩展格式。
fnt文件格式示例:
{"file":"font.png","frames":{"0":{"x":1,"y":1,"w":22,"h":23,"offX":2,"offY":14},
"1":{"x":99,"y":1,"w":9,"h":23,"offX":8,"offY":14}}}
其中包含了纹理集图片的名称和路径。
2)位图文本的使用:
在ResDepot工具中导入fnt文件,将此fnt文件加入到项目资源分组,然后就可以使用了。
完成RES资源组加载后,创建位图字体bitmapText并设置属性font:
bitmapText.font=RES.getRes("font_fnt");
完成font属性设定,可以使用text进行输入操作,但输入的范围必须是纹理中预先设定好的字符:
bitmapText.text="123";
七、动画与粒子特效:
Egret中内置了多种动画处理方式,包括逐帧动画,Egret中还封装了一个用户缓动动画的Tween库。
1. 逐帧动画:
逐帧动画是一种常见的动画,就是在时间轴的每帧上绘制不同的内容,使其连续播放而形成动画。逐帧动画制作时需要生成很多图片,也就时资源体积变大。
1)动画素材制作:
动画素材可以从GIF动画中获取,或SWF动画中获取,使用plugin插件可以从Flash中获取动画数据。这种处理可以在Texture Merger工具的MovieClip中实现,导出后一般会产生两个文件,一个是纹理png图,一个是描述动画数据的json文件。
2)创建逐帧动画:
var data=RES.getRes("mcdemo_json");
var tex=RES.getRes("mcdemo_png");
var mcf:egret.MovieClipDataFactory=new egret.MovieClipDataFactory(data, tex);
var mc:egret.MovieClip=new egret.MovieClip(mcf.generateMovieClipData("mcdemo"));
this.addChild(mc);
mc.play();
Egret将数据与逐帧动画进行了分离,所以有MovieClipData和MovieClipDataFactory两个类,MovieClipData用来存储逐帧动画中的数据,MovieClipDataFactory用于存储MovieClipData和Texture。
3)播放和暂停动画:
创建一个MovieClip对象后,可以调用play方法来播放动画,还可以传递一个参数指派循环播放次数。参数默认值为0,表示只能播放一次;如果参数值大于等于1,就播放相应的次数;如果小于0,则无限循环播放。
如果想暂停动画播放,调用MovieClip对象的stop方法。
4)跳转动画:
MovieClip可以跳转到某个帧并继续播放,有4个方法:
·gotoAndPlay:该方法需要2个参数,一个指定帧编号或帧标签,另一个为对应播放次数
·gotoAndStop:在参数中传入要跳转到的帧,然后暂停播放
·nextFrame:跳转到下一帧,并停止动画播放
·preFrame:跳转到上一帧,并停止动画播放
5)动态切换动画数据:
MovieClip类包含movieClipData属性,该属性可以在程序运行时实时替换其值,以达到切换动画数据的效果。示例:
private textContainer:egret.Sprite
private mc:egret.MovieClip;
private mcf:egret.MovieClipDataFactory;
private createGameScene():void{
var data=RES.getRes("mcdemo_json");
var tex=RES.getRes("mcdemo_png");
this.mcf=new egret.MovieClipDataFactory(data, tex);
this.mc=new egret.MovieClip(this.mcf.generateMovieClipData("mc1"));
this.addChild(this.mc);
this.mc.play(1);
this.mc.addEventListener(egret.Event.COMPLETE, this.completemc, this);
}
private completemc(evt:egret.Event):void{
this.mc.movieClipData=this.mcf.generateMovieClipData("mc2");
this.mc.play(1);
}
6)动画缓存机制:
MovieClipFactory有enableCache属性,可以设为true实现缓存。
当一个json数据传递到MovieClipFactory中,并不会立刻解析,只有当调用generateMovieClip Data方法时,MovieClipFactory才会在json数据中寻找指定的数据。如果一个数据中存在多个mc,那么每次调用generateMovieClipData都会进行一次查找,当数据量大时会比较慢。为此,MovieClipFactory存在一个缓存,当进行一次查找后会将结果放在缓存中,下次使用时可直接取出。如果关闭了缓存,每一次调用generateMovieClipData都会重新执行一次全局查找。
但开启缓存后,内存会出现增长,开发游戏时要根据实际情况选择性开关此功能。一般在游戏运行中,如果某组逐帧动画数据已经废弃不用了,可以使用clearCache方法将缓存数据全部清除。
7)动画数据格式:
MovieClip使用的json文件中包含了动画数据。在mc段中,mcName代表一个MovieClip名,可以自定义修改,一个json中可以包含多个MovieClip;frameRate为帧率,可选属性,默认24;labels为帧标签列表,可选属性,没有帧标签时可不加;events为帧事件标签列表,可选属性,没有帧标签时可不加;frames为关键帧数据列表。
在res段中是资源列表,列表中每个属性都代表一个资源名,其中的属性有x、y、w、h,分别表示资源在纹理集位置的x和y坐标及资源的宽高。
2. 缓动动画:
通常情况下,游戏中都会带有一些缓动动画,如界面弹出、道具飞入等。制作缓动动画时,是用简单的方法实现这种移动或者变形缩放效果。
1)Tween缓动动画:
Egret存在近20种缓动效果,这些效果可以任意组合,形成动画特效。大部分情况下使用缓动动画不用new关键字创建对象,而是直接使用工厂方法来生成Tween对象。
2)缓动的基本用法:
如果要使用缓动动画,需要使用Tween类,其中封装了最常用的缓动动画,包括动画时间设定、缓动动画控制、缓动效果控制等。示例代码:
class Main extend egret.DisplayObjectContainer{
public constructor(){
super();
this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
}
private onAddToStage(event:egret.Event){
this.removeEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
var shp:egret.Shape=new egret.Shape();
shp.graphics.beginFill(0x00ff00);
shp.guaphics.drawRect(0,0,100,100);
shp.graphics.endFill();
shp.x=50;
this.addChild(shp);
egret.Tween.get(shp).to({x:150},1000);
}
}
3)缓动对象的基本控制参数:
定义缓动时,可以传入一些属性参数来进行定制。Tween.get的第2个参数是可选的,是一个对象参数,支持loop和useTicks两个属性。
·loop:布尔值,用于指定是否循环该缓动定义,true为循环,false为不循环,默认false
·useTicks:布尔值,用于指定是否使用帧同步,默认false
缓动在计算缓动属性的插值有两种方法,一种是每帧都考虑运行时间,每帧的时间长度对缓动造成的影响会通过自动计算时间差来计算当前帧的插值;另一种是假设每帧的时间长度是恒定的,当每帧的执行时间变化较大时,就会造成动画过程不稳定。该值默认false。
示例:var tw=egret.Tween.get(shp, {loop:true});
4)缓动对象的缓动变化事件:
在Tween执行过程中,逻辑需要实时做一些变化,跟踪这个过程同样可以通过Tween.get的第2个参数。示例:
var obj={x:0};
var funcChange=function():void{console.log(this.x);}
egret.Tween.get(obj, {onChange:funcChange, onChangeObj:obj})
to({x:600}, 1000, egret.Ease.backInOut);
代码中,在Tween变化过程中随时计算obj坐标,并根据得到的坐标修正缓动位置。
5)缓动过程参数设定:
缓动过程的to()方法还有第3个参数,用来指定缓动函数,即动画过程中的缓动方式,可以使用Ease提供的常量来设定。egret.Ease.backInOut表示缓动过程的开始和结束阶段都有一个短暂的反方向运动过程。
6)缓动的其他方法:
缓动控制还有其他一些方法:
·call:在某个缓动过程结束时产生一个回调,将回调函数作为参数传给call方法
·wait:用于多个缓动连续设定中设置中间的等待时间,以毫秒为单位
使用缓动,可以通过连续调用缓动过程的方法连续进行多次缓动,以完成复杂的动画。如:
var shp:egret.Shape=new egret.Shape();
shp.graphics.beginFill(0x00ff00);
shp.guaphics.drawRect(0,0,100,100);
shp.graphics.endFill();
shp.x=50;
shp.y=50;
this.addChild(shp);
var tw=egret.Tween.get(shp,{loop:true});
tw.to({x:250},500).call(function(){console.log("右上角")}).wait(100)
to({y:250},500).call(function(){console.log("右下角")}).wait(100);
3. 粒子特效:
Egret提供了粒子特效的支持,但在使用时注意性能消耗问题》
粒子库是基于Egret引擎的扩展,其中所有粒子使用相同的纹理,通过设置不同属性实现各种运动效果。粒子库遵循第三方库规则,需要使用时要在egretProperties.json中配置particle模块,并将path属性设置为粒子库路径,由于整个粒子系统使用相同纹理,在使用WebGL或OpenGL渲染时可以通过批处理提高性能,但在canvas渲染模式下尽量小于200个粒子。
粒子系统包括Particle、ParticleSystem、GravityParticle、GravityParticleSystem类。
1)粒子系统的创建和使用:
通过RES模块获得GravityParticleSystem所需要的纹理及配置,然后创建particle.Gravity ParticleSystem对象。
var textture=RES.getRes("texture");
var config=RES.getRes("texture_json");
this.system=new particle.GravityParticleSystem(texture, config);
this.system.start();
粒子系统使用的纹理的配置需要被加载过才能使用。然后通过start方法启动粒子系统,通过stop方法停止粒子系统:
this.system.start();
this.system.stop();
通过changeTexture方法更换粒子纹理:
var newTexture=RES.getRes("newTexture");
this.system.changeTexture(newTexture);
2)粒子系统配置文件:
GravityParticleSystem构造函数传入的配置是一个Object,包含了GravityParticleSystem所需的各个必需的参数。配置文件可以为json格式,通过RES模块加载后直接使用;也可以是从服务器端请求的数据对象,XML或其他格式配置文件,通过自定义解析生成数据对象。
{"emotter":{"x":240,"y":600},"emitterVariance":{"x":104,"y":0},"gravity":{"x":0,"y":0},
"maxParticles":500,"speed":90,"speedVariance":30,"lifespan":2000,"lifespanVariance":1900,
"emitAngle":270,"emitAngleVariance":15,"startSize":70,"startSizeVariance":50,"endSize":10,
"endSizeVariance":0,"startRotation":0,"startRotationVariance":0,"endRotation":0,
"endRotationVariance":0,"radialAcceleration":0,"radialAccelerationVariance":0,
"tangentialAcceleration":0,"tangentialAccelerationVariance":0}
3)自定义粒子特效:
ParticleSystem支持自定义扩展。创建自定义粒子类要继承Particle类,并定义扩展粒子库的粒子属性;创建自定义粒子库,要继承ParticleSystem类,重写initParticle和advanceParticle方法,其中的initParticle方法用于初始化粒子属性,advanceParticle方法用于每个时间间隔粒子的运动。
public initParticle(particle:particle.Particle):void
public advanceParticle(particle:particle.Particle, dt:number):void
八、音效:
游戏中需要使用声音和音乐配合,egret.Sound用来控制音效,还往往需要使用egret.Sound Channel和egret.URLLoader类。因为不同平台支持的音频格式不同,Egret在Android系统使用基于HTML5的Audio,在iOS系统则使用基于WebAudio的。
1. 声音类egret.Sound:
使用音效,首先需要使用ResDepot工具或URLLoader类预先载入需要的音频文件。使用egret.Sound类加载音效要等到音频文件完全载入才可以播放,当音频没有加载完成时不能使用。
1)直接载入音频文件:
假定resource/assets中存在sound.mp3文件,加载的代码示例为:
class Main extend egret.DisplayObjectContainer{
public constructor(){
super();
this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
}
private onAddToStage(event:egret.Event){
var loader:egret.URLLoader=new egret.URLLoader();
loader.dataFormat=egret.URLLoaderDataFormat.SOUND;
loader.addEventListener(egret.Event.COMPLETE, this.onLoadComplete, this);
var url:string="resource/assets/sound.mp3";
var request:egret.URLRequest=new egret.URLRequest(url);
loader.load(request);
}
private onLoadComplete(event:egret.Event){
var loader:egret.URLLoader=<egret.URLLoader>event.target;
var sound:egret.Sound=<egret.Sound>loader.data;
sound.play();
}
}
代码中通过监听loader中的egret.Event.COMPLETE事件方法onLoadComplete来获得需要的资源文件。在onLoadComplete中创建egret.Sound类。
2)使用ResDepot加入资源分组:
比较快的声音创建方式是预先使用ResDepot加入音频文件,并放入preload分组:
class Main extend egret.DisplayObjectContainer{
public constructor(){
super();
this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
}
private onAddToStage(event:egret.Event){
RES.addEventListener ( RES.ResourceEvent.GROUP_COMPLETE,
this.onResourceLoadComplete, this);
RES.loadConfig("resource/resource.json", "resource/");
RES.loadGroup("preload");
}
private onResourceLoadComplete():void{
var sound:egret.Sound=RES.gerRes("sound_mp3");
sound.play();
}
}
2. 音频控制类:
egret.SoundChannel类用于执行精细的声音控制,每个声音分配给一个声道,应用程序可以混合多个声道。
1)播放:
var sound:egret.Sound=RES.getRes("background_mp3");
sound.play(0,1);
在iOS和部分Android设备上,声音在每次播放前必须由触摸事件触发,声音播放必须写在TouchEvent监听函数中。play方法格式:
play(startTime:number, loops:number):egret.SoundChannel
参数startTime指定播放的初始位置,以秒为单位,默认为0,;loops指定播放次数,大于0的值为播放次数,小于等于0表示循环播放,默认为0。
2)音量:
通过egret.SoundChannel中的volume属性进行音量调节,0为禁音,1 为最大音量:
var sound:egret.Sound=RES.getRes("background_mp3");
var channel:egret.SoundChannel=sound.play(0,1);
channel.volume=0;
var tween:egret.Tween=egret.Tween.get(channel);
tween.to({volume:1.0}, 3000);
3)暂停:
通过egret.SoundChannel中的stop方法可以暂停音效,使用通过egret.Sound中的play方法可以继续播放。
3. 声音事件:
与声音相关的事件:
事件类型 |
说明 |
Egret.Event.SOUND_COMPLETE |
声音完成播放后调度 |
Egret.IOErrorEvent.IO_ERROR |
声音加载错误后调度 |
4. 音效类型设置:
Egret针对原生及Runtime添加了egret.Sound.MUSIC和egret.Sound.EFFECT类型,两种类型不支持HTML5。
·egret.Sound.MUSIC:适合比较长且对时间要求不高的情况
·egret.Sound.EFFECT:适合短促且对反应速度要求比较高的情况
如果音频时间不是很长,使用egret.Sound.EFFECT,例如按键声等经常重复且非常短暂的音效,这种类型的egret.Sound可以被创建多个。如果声音文件比较大,使用egret.Sound.MUSIC,如背景音乐,这种类型只能创建一个。
编程中容易遗漏的是加载的音频格式:
urlload.dataFormat=egret.URLLoaderDataFormat.SOUND
对于egret.Sound.MUSIC,对应的是Android MediaPlayer类;egret.Sound.EFFECT对应的是SoundPool类。
九、数据操作:
一个游戏中,从背景呈现、角色及对手设定、角色及对手的动作还有位置等等都是数据。在Egret中,最常用的组织数据的格式是JSON,数据量大时常用二进制数据。
1. JSON数据操作:
JSON是一种轻量级的数据交换格式,以字符为基础,多种编程语言都给予支持。Egret的RES模块在加载JSON后自动进行了解析,可以直接使用其中的数据了。
2. 二进制数据操作:
Egret中常用的位图、声音等资源都可以视为二进制文件,也可以把需要传输的数据按某种规范组织起来存储为二进制文件。
1)写入字节流:
读写二进制文件使用ByteArray类,包括几种方法。使用writeBytes()、writeInt()、writeFloat()、writeObject()、writeUTFBytes()等方法可将特定数据类型的变量内容直接写入二进制字节流。
var groceries:Array<any>=["milk",4.50,"soup",1.79,"egges",3.19,"bread",2.35];
var bytes:egret.ByteArray=new egret.ByteArray();
for(var i:number=0;i<groceries.length;i++){
bytes.writeUTF(groceries[i++]);
bytes.writeFloat(groceries[i]);
}
groceries数组中,字符串元素和浮点数元素交替出现,可以每两个一组分组循环方式来遍历,写入字符串使用writeUTF,写入浮点数使用writeFloat。writeUTF在写入字符串时会自动将字符串的长度放在一个占用2字节的整数字节流中,读取时只需要使用readUTF方法即可读取整个字符串。写入字符串还可以使用writeUTFBytes方法,写入时依次写入字符串中的字符,不带任何附加信息,读取时需要使用readUTFBytes方法,并提供一个长度参数,适用于读取字符串的一部分时。
2)读取字节流:
ByteArray类的另外一组方法用于读取字节流,包括readBytes()、readInt()、readFloat()、readObject()、readUTFBytes()等,读取后的数据存入相应数据类型的变量中。示例:
var reader:Array<any>=[];
bytes.position=0;
for(var i:number=0;i<groceries.length;i+=2){
reader.push(bytes.readUTF());
reader.push(Number(bytes.readFloat().toFixed(2)));
}
为了抵消计算机系统本身对浮点数的存储误差,对其进行了四舍五入修正。
3)大端模式与小端模式:
在存储多字节数字时,不同计算机可能有差异。有些计算机首先将数字中的最高有效字节存储在最低的内存地址,有的则首先存储最低有效字节。一般称最高有效字节位于最前的为big endian模式,即大端模式;而将最低有效字节位于最前的称为little endian,即小端模式。
十、网络通信:
Egret封装了传统的HTTP通信,也封装了WebSocket通信功能。
1. HTTP网络请求:
Egret提供了两套HTTP网络通信库,一套在Egret Core库中,另一套位于Egret Game扩展包中。HTTP网络请求是最基本的通信方式,这与浏览器中打开网页时的请求方式相同。HTTP网络请求是无状态的请求,每一次与服务器通信后都会得到服务器返回的信息,通信完成后就与服务器没有关系了。
1)构建网络请求:
class Main extend egret.DisplayObjectContainer{
public constructor(){
super();
this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
}
private loader:egret.URLLoader;
private onAddToStage(event:egret.Event){
this.loader=new egret.URLLoader();
var req:egret.URLRequest=new egret.URLRequest();
req.url="http://httpbin.org/get";
req.data=new egret.URLVariables("text=ok");
this.loader.dataFormat=egret.URLLoaderDataFormat.TEXT;
this.loader.addEventListener ( egret.Event.COMPLETE, this.complete, this);
this.loader.load(req);
}
private complete(evt:egret.Event){
console.log(evt.target.data);
}
}
Egret中默认使用GET请求,也可以使用POST请求与服务器通信。HTTP定义了与服务器交互的方式,最基本的有GET、POST、PUT、DELETE等,最常用的是GET和POST。
代码中,设置了服务器地址后,添加GET通信参数,所有参数都需要封装到 egret.URLVariables()对象中,然后设置返回的数据格式。代码中最后使用load方法进行网络通信。Egret支持5中网络传输数据格式为:
静态常量 |
格式描述 |
egret.URLLoaderDataFormat.BINARY |
二进制数据 |
egret.URLLoaderDataFormat.SOUND |
音频文件 |
egret.URLLoaderDataFormat.TEXT |
文本格式,默认 |
egret.URLLoaderDataFormat.TEXTURE |
位图纹理 |
egret.URLLoaderDataFormat.VARIABLES |
URL编码变量 |
2)POST方法:
上述代码使用默认的GET方式进行通信,如果使用POST 方法,需要增加一行代码:
req.method=egret.URLRequestMethod.POST;
使用POST方法时,需要使用可以接收POST请求的url地址。
3)检测网络请求状态:
egret.URLLoader()访问网络请求过程中可以监控到两个状态,当访问请求完成后可派发egret.Event.COMPLETE事件,如果遇到网络错误可派发egret.IOErrorEvent.IO_ERROR事件。为了项目能够稳定运行,应在egret.URLLoader()中始终监听egret.IOErrorEvent.IO_ERROR事件。
2. WebSocket通信:
HTTP只能实现单向通信,HTML5定义了WebSocket协议,可实现节省带宽的实时通信。
1)创建WebSocket连接发送数据:
WebSocket是以扩展模块形式支持的,需要修改egretProperties.json中的modules内容,加入WebSocket模块支持:{"name":"socket"}。
然后在项目所在目录执行一次引擎编译:egret build -e,完成后就可以在项目中使用WebSocket功能了。
class Main extend egret.DisplayObjectContainer{
public constructor(){
super();
this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
}
private socket:egret.WebSocket;
private onAddToStage(event:egret.Event){
this.socket=new egret.WebSocket();
this.socket.addEventListener ( egret.Event.CONNECT, this.onSocketOpen, this);
this.socket.connect("echo.websocket.org", 80);
}
private onSocketOpen(evt:egret.Event){
this.socket.writeUTF("要发送的数据,为UTF8编码");
this.socket.flush();
}
}
代码中先创建一个WebSocket对象,然后链接指定的服务器,执行connect方法后WebSocket开始向服务器发起链接请求。WebSocket会要求提供端口号,connect方法的第2个参数就是端口号。通常情况下,WebSocket发送和接收的是二进制数据。
代码中设定了 egret.Event.CONNECT事件监听器,在事件处理函数中使用writeUTF方法向缓冲区内添加发送的字符数据,flush直接刷新数据。
2)读取数据:
可以通过egret.Event.SOCKET_DATA事件监听是否接收到服务器发送过来的数据。
private onReceiveMessage(evt:egret.ProgressEvent){
var msg:string=this.socket.readUTF();
console.log(msg);
}
WebSocket提供了两个读取数据的方法,readUTF和readBytes。
3)WebSocket网络状态:
WebSocket网络状态有两个,连接中和断开连接。访问状态可以通过cinnected属性直接读取,应在每一次数据发送前都检测当前网络状态,以防止Bug出现。WebSocket中的事件:
事件 |
说明 |
egret.Event.CONNECT |
连接成功会派发此事件 |
egret.ProgressEvent.SOCKET_DATA |
收到数据会派发此事件 |
egret.Event.CLOSE |
主动关闭或者服务器关闭连接会派发此事件 |
egret.IOErrorEvent.IO_ERROR |
出现异常会派发此事件 |
4)断开与重连服务器:
移动端在3G/4G网络环境下会经常发生网络抖动,此时网络连接非常不稳定,需要针对WebSocket进行网络检测。
网络连接主动关闭可以使用close方法,如果网络环境断开,可以再次使用connect方法进行重新连接。
十一、计时器与心跳控制器:
Egret提供了Timer用来创建计时器,可以使用该类在一定延迟后执行动作,或按重复间隔执行动作。Egret还提供了Ticker,提供游戏运行过程中最基本的运行节律控制。另外,还有一组专用于一次性延时触发动作控制的setTimeout和clearTimeout方法。
1. 计时器Timer:
1)创建计时器:
计时器类为egret.Timer,创建代码为:
var timer:egret.Timer=new egret.Timer(3000, 5);
计时器创建的构造函数包含两个参数,第1个是计时器间隔时间,表示每隔多长时间生成一次计时器事件。为了精确控制时间,计时器间隔时间单位是ms。第2个参数是计时次数,表示在计时器停止工作前生成多少次事件。上述代码创建每隔3秒产生一个事件的计时器,并在5次计时后停止工作。
2)加入计时器事件监听:
计时器每隔一个时间间隔产生一个计时器事件,事件为TimerEvent.TIMER;计时器停止工作后会产生计时器结束事件,事件为TimerEvent.TIMER_COMPLETE。可以对这两个事件添加事件监听:
timer.addEventListener(egret.TimerEvent.TIMER, function(e:egret.TimerEvent){...},this)
timer.addEventListener(egret.TimerEvent.TIMER_COMPLETE,function(e:egret.TimerEvent){...},this)
3)启动计时器:
前面只是准备计时器,计时器并没有开始工作。计时器需要启动后才开始工作:
timer.start();
4)修改计时器时间间隔:
如果只按照构造函数中给出的参数来控制计时器,事件之间的间隔是固定不变的。计时器提供了计时间隔属性,通过设置delay属性值可以修改计时间隔,这将在下一次计时开始生效。
5)修改计时器:
计时次数也可以修改,先定义一个布尔值来表示计时次数是否已经修改:
bChanged:boolean=false;
然后修改TIMER事件的监听处理函数,对次数进行修改:
timer.addEventListener(egret.TimerEvent.TIMER,
function(e:egret.TimerEvent){
if(timer.delay==3000){
bChanged=true;
timer.repeatCount=15;
}
if(timer.delay>500){
timer.delay=timer.delay-500;
}
console.log(e.type, timer.delay);
},this);
代码会在计时间隔调整为小于3000后对计时次数进行一次修改,由原来的5次修改为15次。
2. Ticker:
Ticker从功能上也是一个计时器,但更接近系统级,是计时间隔古代且会在Egret运行期间一直运行的计时器。Ticker的计时间隔是由Egret内核控制的,按照60fps运行,即1s产生50次Ticker事件,一般称为心跳计时器。
1)开启Ticker监听:
egret.Ticker.getInstance().register(function(delta:number){...}, this);
注册Ticker事件的方法是直接调用Egret中的Ticker所提供的注册方法,与常规事件监听方法不同。Ticker在事件监听处理函数中会提供一个时间偏移量参数,即代码中的delta,是本次调用与上次调用之间的时间间隔。
2)移除Ticker监听:
不再需要监听Ticker时,可以移除。由于移除监听需要通过函数的引用。所以通常应将监听处理函数赋值给一个变量。示例代码:
var tmp:any={times:0};
var func=function(delta){console.log(delta);
if(++tmp.times>120){egret.Ticker.getInstance().unregister(func, null);}
};
egret.Ticker.getInstance().register(func, null);
代码中,在监听处理函数被调用120次后就移除。
3)Ticker的新用法:
Egret的新版本中新增了startTick和stopTick两个API,计时间隔仍然由Egret内核控制。
var tmp:any={times:0};
var func=function(timestamp:number):boolean{
console.log(timestamp);
if(++tmp.times>120){egret.log("egret.stopTick");egret.stopTick(func, null); }
return false;
};
egret.log("egret.startTick");
egret.startTick(func, null);
3. setTimeout与clearTimeout:
setTimeout也是一种计时器,用法简单。功能单一,只能执行一次,达到指定事件会调用给出的回调函数,也称为超时计时器。在其执行过程中,可以根据需要使用clearTimeout来随时停止计时。示例:
var idTimeout:number=egret.setTimeout(function(arg){
console.log("timeout:", arg);
egret.clearTimeout(idTimeout);
}, this, 3000, "egret");
使用setTimeout不需要创建对象,为了在计时过程中能终止计时,需要提供一个超时ID,调用这个方法会返回这个ID。如果在计时过程中需要取消,只需要调用clearTimeout并传入setTimeout所返回的超时ID。
setTimeout的第3个参数为超时时间,单位ms;其实可以有无限制数目的参数,如果有的话,从第4个参数开始的参数都将在超时回调时,将参数逐一传递到回调函数。
4. getTimer:
getTimer方法返回的是从Egret程序启动开始所经历的毫秒数,不需要提供任何参数。
十二、屏幕适配与环境交互:
开发的游戏和应用需要运行在成千上万种不同设备上,这些设备的屏幕尺寸有数十种,还有不同的浏览器及操作系统。
1. 屏幕适配策略:
一个Egret游戏或应用需要运行在不同操作系统以及不同型号设备的不同浏览器上,在实际尺寸上无法一一定制,需要提供适配功能,使得内容在目标设备和环境中以恰当的方式呈现出来。在Egret项目的index.html中用于显示应用的div标记为:
<div id="egret-sample" class="egret-player" style="margin:auto;width:100%;height:100%;"
data-entry-class="Main" data-scale-mode="noBorder" data-frame-rate="30"
data-orientation="auto" data-content-width="480" data-content-height="800"
data-multi-fingered="2" data-show-paint-rect="false" data-show-fps="true"
data-show-fps-style="x:0,y:0,size:24,textColor:0xffffff" data-show-log="true"
data-log-filter="">
</div>
其中,data-show-log设置是否在屏幕中显示日志,data-log-filter设置一个正则表达式以过滤日志;data-show-paint-rect设置是否显示重绘区域,data-show-fps设置是否显示帧频信息;data-content-width和data-content-height属性用于设置内容的宽度和高度,data-scale- mode属性为适配模式。如果目标Egret容器尺寸与<div>设置的相同,会达到最好效果,如果不一致就需要根据适配模式的规则调整容器、Stage的尺寸。
1)exactFit模式:
该模式缩放Egret舞台以填满容器,而不考虑原来的内容宽高比。宽度和高度的缩放系数可能会不同,因此当Stage宽高比变化时,显示内容可能压扁或拉长。在exactFit模式中,stage. stageWidth和stage.stageHeight始终保持不变,当宽高比发生变化时不会响应RESIZE事件。这种模式是最简单最省事的适配模式,目的就是撑满屏幕,不顾及缩放比改变的失真。该模式对应的stage.scaleMode模式常量为egret.StageScaleMode.EXACT_FIT。
2)noScale模式:
该模式不缩放应用程序内容,即严格按照原始尺寸来显示内容,但会使Stage尺寸和容器尺寸匹配一致。该模式中,stage. stageWidth和stage.stageHeight与容器尺寸一致,为了有合理的布局显示,程序需要根据屏幕的宽高来调整UI及游戏的场景布局,达到不同分辨率统一的效果。当宽高比发生变化时,会响应RESIZE事件。该模式适合对画面品质要求严格的场合,但需要开发者用程序实现精确布局,对应的stage.scaleMode模式常量为egret.StageScaleMode.NO_SCALE。
3)showAll模式:
该模式在Stage总是保持设定宽高比下,在尺寸较长的方向撑满容器,另外一个方向按宽高比修正并居中,富裕的地方填充背景色。此模式会根据容器宽高进行等比例缩放,stage. stageWidth和stage.stageHeight保持不变,当宽高比发生变化时不会响应RESIZE事件,对应的stage.scaleMode模式常量为egret.StageScaleMode.SHOW_ALL。
4)fixedWidth模式和fixedHeight模式:
这两种模式所采用的规则是一致的,但适配依据不同,一个根据宽度,一个根据高度。这两种模式,Stage完全撑满容器,Stage显示内容是以某个方向的设计尺寸为准,进行等比例缩放。fixedWidth模式只考虑设计尺寸设定中的宽度,宽度方向缩放撑满容器;fixedHeight模式只考虑设计尺寸设定中的高度,高度方向缩放以撑满屏幕。而另一个方向将根据宽高比进行等比例缩放,并且以左上角为缩放原点。
程序中,fixedWidth模式的stage. stageWidth总是保持预设值,stage.stageHeight根据宽高比获取;fixedHeight模式的stage.stageHeight总是保持预设值,stage. stageWidth根据宽高比来获取。只要设计尺寸宽高比与容器宽高比不一致,当宽高比发生变化时均响应RESIZE事件。
fixedWidth模式对应的stage.scaleMode模式常量为egret.StageScaleMode.FIXED_WIDTH,fixedHeight模式对应的stage.scaleMode模式常量为egret.StageScaleMode.FIXED_HEIGHT。
5)noBorder模式:
该模式保持原始宽高比缩放内容,缩放后显示内容的较窄方向填满容器,另一个方向的两侧可能会超出容器视口而被裁切。这种模式下,stage.stageWidth和stage.stageHeight保持原始尺寸,该模式对应的stage.scaleMode模式常量为egret.StageScaleMode.NO_BORDER。
6)程序内设置缩放模式:
为了灵活控制,引擎提供了程序内设置缩放模式的方法,只需要在任何能访问Stage对象的位置设定其scaleMode属性即可。示例:
this.stage.scaleMode=egret.StageScaleMode.FIXED_WIDTH;
2. 屏幕方向设置:
在index.html用以显示Egret游戏或应用的<div>标记中,data-orientation属性的值设定屏幕方向,auto、portrait、landscape、landscapeFlipped。
1)竖屏模式:
竖屏模式下,设备宽高较长的方向在上下方向,对应的属性值为portrait。设置为该模式,屏幕旋转时显示内容也会跟随旋转。
2)横屏模式:
横屏模式下,设备宽高较长的方向在左右方向,方向属性为landscape。
3)反向横屏模式:
也是设备宽高较长的方向在左右方向,但为翻转的横屏,方向属性为landscapeFlipped。
4)自动模式:
显示内容不根据设备来调整,而是根据浏览器,方向属性为auto。
3. 环境交互:
1)Egret与网页JavaScript交互:
如果希望用HTML5页面的方式显示某一张图片,或者调用HTML5的JavaScript库,就需要Egret跨语言调用JavaScript内容。
HTML中有一个window对象,当前页面中所有已经定义的变量或函数均可以通过这个对象访问,TypeScript也可以访问这个对象,因此可以以此为中转。
window表示一个浏览器窗口或一个框架,客户端JavaScript中window是一个全局变量要引用窗口不需要特殊语法,可以把窗口属性作为全局变量来使用。示例:
window['onEraseFinished']=function(){
//当JavaScript中调用window['onEraseFinished']()方法时会执行的函数
}
代码中定义了一个匿名函数,并且赋值给window对象的onEraseFinished变量,该变量就可以作为一个方法被执行。在JavaScript中执行以上方法的代码为:
JavaScriptwindow['onEraseFinished']();
反过来的情形下,在JavaScript中定义方法,供TypeScript调用,也适合,因为定义和调用,用的都是JavaScript语法。但需要注意时序问题,确保运行时,调用这个方法的代码已经定义完成。
2)读取网页GET参数:
为了向HTML网页快速传递信息,可以在url后面加一个问号,然后跟随一个变量字符串来传递若干变量,称为查询字符串,这在JavaScript中用location.search来获取或设置。通常用到的是获取,也就是通过查询字符串来读取url中的变量信息。
如果一个Egret应用中,显示的内容可以是位图,也可以是文本,显示内容的类型信息在url查询字符串中给出。用字符串bitmap表示显示位图,用字符串text表示显示文本,代码:
var strSearch=location.search.toLocaleLowerCase();
if(strSearch.indexOf("bitmap")!=-1){
Global.iSampleMode=SampleMode.BMP;
}else if(strSearch.indexOf("text")!=-1){
Global.iSampleMode=SampleMode.TEXT;
}
代码中首先将查询字符串保存到临时变量strSearch中,然后扫描该字符串中是否存在bitmap或者text,并根据结果来进入不同的逻辑代码,或者保存到含义更明确的变量中,供后续的逻辑判断直接使用。