2.3 显示对象和显示容器

本节开始讲解组成 Egret 项目的基本元素:显示对象( DisplayObject )和显示容器(DisplayObjectContainer)。就像任何客户端编程技术一样,Egret 也采用这种节点和节点容器的方式,从而形成一种树形结构来显示界面。如果了解设计模式的话,就知道这个设计使用了组合设计模式。这种模式使得添加节点、查找节点和删除节点非常方便,而且会产生非常复杂多变的结构,足以满足客户端开发的需求。

2.3.1 舞台

麦克康奈尔在《代码大全 2》中指出,编程就是打比方。笔者认同这个观点,而且笔者一直以面向对象的思维方式工作。在Egret中,该引擎也打了一个比方,这个比方就是舞台。游戏玩家就像观看话剧的观众,他们只会看到舞台上展现的演员和道具。

在 Egret 里也是这样,玩家只能看到放到舞台上的显示对象。舞台(Stage)、显示对象(DisplayObject)、显示容器(DisplayObjectContainer)的继承关系如图2-20所示:

图2-20 Stage类的继承关系

可以把Stage对象看成是树形结构的根节点。在2.2.4一节中提到了data-entry-class字段可以指定入口类,而且这个类是DisplayObjectContainer的子类。由此可以猜测到,Egret项目在运行的时候会生成入口类的一个对象,然后将这个对象放到Stage对象的子节点上,然后所有作为入口类对象的子节点都会显示出来。

常见的显示对象有图形(Shape)、文字、视频和图片等,它们都是DisplayObject的子类。表2-1介绍Egret中自带的8个显示相关的核心类:

表2-1 Egret自带的8个显示相关的核心类

2.3.2 坐标系统以及基本属性

Egret使用一套坐标系统,从而去给显示对象定位,如图2-21所示:

图2-21 舞台坐标系

左上角是舞台坐标的原点,该点和屏幕的左上角重合。横轴是 X 轴,数值向右递增。纵轴是 Y轴,数值向下递增。这就是 Egret 引擎系统的全局坐标

图中灰色矩形的左上角点表示锚点,该锚点的坐标就是该矩形的坐标。通过显示对象的x和y属性就可以访问和修改该对象的坐标位置。示例代码如下:

Shape类是DisplayObject类的子类。

除了可以通过x、y属性来修改显示对象的状态,还可以修改以下的几个基本属性:

alpha:透明度。

width:宽度。

height:高度。

rotation:旋转角度。

scaleX:横向缩放。

scaleY:纵向缩放。

skewX:横向斜切。

skewY:纵向斜切。

visible:是否可见。

anchorOffsetX:对象绝对锚点X。

anchorOffsetY:对象绝对锚点Y。

局部坐标是显示对象以及显示容器内部建立的坐标系统,跟系统和全局坐标系统类似,只不过原点在显示对象或显示容器的锚点上。任何显示对象和显示容器的坐标值,都是相对父显示对象的局部坐标而言的。

2.3.3 添加与删除显示对象

打开Egret启动器,选择“项目”标签,然后单击“创建项目”按钮,如图2-22所示:

图2-22 在Egret启动器项目模块里创建项目

这时会弹出创建项目对话框,如图2-23所示:

图2-23 创建项目对话框

在“项目名称”栏里输入“AddingAndRemovingDisplayObject”,“项目路径”栏里输入路径,然后单击“创建”按钮。这样,就创建了一个默认的Egret项目。

然后删除项目文件夹内src文件夹内的所有文件,这些文件是默认项目的代码,对于目前这个项目而言是没有用处的。

然后在EgretWing里右键单击src文件夹,在弹出的右键菜单里选择:创建模板文件->新建TypeScript类,这时会弹出如图2-24所示的对话框:

图2-24 新建TypeScript类对话框

在类名栏内输入“Main”,然后单击“确定”按钮,这样就在src文件夹内创建了一个称为Main.ts的文件,这个文件就是这个项目的入口类。它里面的内容如下:

在“2.2.4入口文件说明”一节中介绍过,入口类要继承于DisplayObjectContainer类,除此之外,还要添加额外的代码,代码如下所示:

黑体字表示需要添加的代码。跟目前现代的大多数流行的图形用户界面编程一样,Egret采用了事件驱动的方式(关于 Egret 的事件驱动机制,将在2.8节对其进行介绍)。那么DisplayObject及其子类就可以添加事件的响应方式,DisplayObject. addEventListener就是添加事件处理方法的接口。那么上面代码的意图就是,当Main类的对象被添加到舞台上之后(触发了egret.Event.ADDED_TO_STAGE事件),会调用Main类的onAddToStage方法。这就意味着在onAddToStage里添加的代码,都会在项目运行的时候执行。

然后在onAddToStage方法里添加如下的代码:

先运行一下这个项目,单击EgretWing的调试标签,然后再单击标签下方的三角按钮,这样就会启动调试播放器,如果执行调试播放器的菜单命令:横竖屏->垂直视图,显示效果会更准确一些。会看到在窗口的左上角有一个绿色的正方形,如图2-25所示:

图2-25 程序运行结果(绘制正方形)

结合上一个代码清单。从代码的第2行到第5行,创建了一个称为sprite的egret.Sprite对象,在“2.3.1 舞台”一节了解到Sprite类是显示容器的子类,通过它可以绘制矢量图形。sprite有一个称为graphics的子对象,该子对象具有绘制矢量图形的全部功能。

在绘制项目中的绿色矩形之前,需要执行该子对象的beginFill方法(代码第3行),从而告诉系统要开始绘制了,而且也要告诉系统要绘制的图形的颜色,这个是通过该方法的第一个参数来指定的。然后就可以绘制矩形了(代码第4行)。drawRect方法的原型如下:

它有四个参数,这四个参数的作用如下所示:

x:相对父显示对象x坐标的水平距离。

y:相对父显示对象y坐标的垂直距离。

width:矩形的宽度。

height:矩形的高度。

然后,还要告诉系统已经绘制完毕了,这个是通过endFill方法完成的(代码第5行)。

这样就将绘制的矩形添加到sprite对象的子节点里了。如果想把这个sprite对象显示出来,还需要把它放到舞台上。Main类对象已经放到舞台上了,那么只要把这个sprite对象放到Main类对象的子节点下就可以了。DisplayObjectContainer类对象的addChild方法(代码第7行)就可以把一个显示对象添加到子节点里,所以它可以把sprite对象放到Main类对象的子节点下,因为Main类也继承了该方法。addChild方法的原型如下:

返回值是参数中传递的DisplayObject实例。

关于颜色值

在上面的例子里,在 beginFill 里指定了一个颜色值——0x00ff00。这个值是一个十六进制的数字,其中前两个数字表示红色的值,中间两个数字表示绿色的值,最后两个数字表示蓝色的值。

然后在上面的基础上添加一行代码:

然后运行一下调试播放器,如图2-26所示:

图2-26 程序运行结果(删除正方形)

可以看到刚才创建的绿色矩形消失了。removeChild方法(代码第9行)就是用来删除子节点的。这个sprite对象虽然已经被删除了,但是这只能表明它从舞台上移除了,它仍旧在内存中。

课后作业:在已有的代码基础上,创建第二个矩形,把它指定为红色,坐标不再是原点。改变它的x、y坐标,或者其他属性,看看有什么变化。然后再删除它。

2.3.4 深度管理

一个显示容器会有多个子节点,那么子节点的绘制顺序是怎么确定的呢?(思考一下,如果没有绘制顺序,开发者怎么指定叠放效果呢?)

跟其他流行的绘图引擎一样,Egret为每个子节点赋予一个深度值就解决了上述问题。

显示容器里的深度值是从0 开始的,当第一个显示对象被添加到容器中时,它的深度值是0。当添加第二个显示对象的时候,它的深度值是1,并且在第一个显示对象的上方。

在Egret启动器里创建一个新项目,项目名称为“DepthOrder”,将src文件夹内的文件全部删除,再在里面创建一个Main.ts类文件(如上一节所示)。然后添加如下的代码(该代码来源于 Egret 官方文档),参见二维码2-8:

二维码2-8

然后运行调试播放器,会看到如图2-27所示的效果:

图2-27 程序运行结果(叠放效果)

代码里先后在舞台上添加了两个矩形——一个红色,一个绿色。就像之前说的,后添加的绿色矩形会覆盖先添加的红色矩形,红色矩形的深度值是0,绿色矩形的深度值是1。那如果想让红色矩形覆盖绿色矩形,那应该怎么做呢?

在onAddToStage方法里做出如下修改,参见二维码2-9:

二维码2-9

运行调试播放器,会发现红色矩形确实覆盖了绿色矩形。

这里的代码把addChild方法换成了addChildAt方法,该方法的原型如下:

该方法的第一个参数和返回值的含义和addChild是一样的。对于第二个参数index,代码文档里是这样解释的:

@param index 添加该子项的索引位置。如果指定当前占用的索引位置,则该位置以及所有更高位置上的子对象会在子级列表中上移一个位置。

这个index值实际上就是深度值。将红色矩形的深度值设为1,将绿色矩形的深度值设为0,自然就会让红色矩形覆盖绿色矩形。如果想知道当前显示对象的深度值,可以访问该对象的zIndex属性。

课后作业:如果把上面的代码清单做出如下修改,参见二维码2-10:

二维码2-10

运行调试播放器看看运行效果,尝试解释一下为什么会出现这种结果。