一、laya1.0 UI类结构
1.Sprite常见子类
- Sprite->Node->EventDispatcher
- Text->Sprite
- Stage->Sprite
- AnimationPlayerBase->Sprite
- DialogManager->Sprite
- Component->Sprite
- Component是ui控件类的基类,有很多子类
2.Box常见子类
Box->Component
View->Box
List->Box
Panel->Box
LayoutBox->Box
VBox,HBox->LayoutBox
Dialog->View
View子类,更常见的是在我们自己创建的UI导出的类。
二、laya2.0 UI类结构
- 新增加了一个Scene->Sprite,Scene只有一个子类就是View,Dialog仍然继承View。
- Component也出现了变化:
Component implements ISingletonElement, IDestroy
。现在的Componet变成了一个脚本的基类,它的子类有相对布局插件Widget,Script,CommonScript。之前的Component现在被重命名为UIComponent。 - AnimationPlayerBase被重命名成为AnimationBase
laya1.0创建的UI:xxUI->View->Box->Component->Sprite
laya2.0创建的UI: xxUI->View->Scene->Sprite(创建时也可以选成Scene类型,xxUI->Scene->Sprite)
需要说明一下,现在创建的UI文件后缀已经不是.ui了,统一成.scene。
三、laya2.0的Scene类
public function open(closeOther:Boolean = true, param:* = null):void {
if (closeOther) closeAll();
root.addChild(scene);
onOpened(param);
}
public function close():void {
if (autoDestroyAtClosed) this.destroy();
else removeSelf();
onClosed();
}
override public function destroy(destroyChild:Boolean = true):void {
_idMap = null;
super.destroy(destroyChild);
var list:Array = Scene.unDestroyedScenes;
for (var i:int = 0, n:int = list.length; i < n; i++) {
if (list[i] === this) {
list.splice(i, 1);
return;
}
}
}
/**创建后,还未被销毁的场景列表,方便查看还未被
销毁的场景列表,方便内存管理,本属性只读,请不要直接修改*/
public static var unDestroyedScenes:Array = [];
public function Scene() {
this._setBit(Const.NOT_READY, true);
unDestroyedScenes.push(this);
this._scene = this;
createChildren();
}
public static function open(url:String, closeOther:Boolean = true,
complete:Handler = null, param:* = null):void {
load(url, Handler.create(null, _onSceneLoaded, [closeOther, complete, param]));
}
private static function _onSceneLoaded(closeOther:Boolean,
complete:Handler, param:*, scene:Scene):void {
scene.open(closeOther, param);
if (complete) complete.runWith(scene);
}
public static function close(url:String, name:String = ""):Boolean {
var flag:Boolean = false;
var list:Array = Scene.unDestroyedScenes;
for (var i:int = 0, n:int = list.length; i < n; i++) {
var scene:Scene = list[i];
if (scene.parent && scene.url === url && scene.name == name) {
scene.close();
flag = true;
}
}
return flag;
}
public static function closeAll():void {
var root:Sprite = Scene.root;
for (var i:int = 0, n:int = root.numChildren; i < n; i++) {
var scene:Scene = root.getChildAt(0) as Scene;
if (scene is Scene) scene.close();
}
}
public static function destroy(url:String, name:String = ""):Boolean {
var flag:Boolean = false;
var list:Array = Scene.unDestroyedScenes;
for (var i:int = 0, n:int = list.length; i < n; i++) {
var scene:Scene = list[i];
if (scene.url === url && scene.name == name) {
scene.destroy();
flag = true;
}
}
return flag;
}
public static function get root():Sprite {
if (!_root) {
_root = Laya.stage.addChild(new Sprite()) as Sprite;
_root.name = "root";
Laya.stage.on("resize", null, resize);
function resize():void {
_root.size(Laya.stage.width, Laya.stage.height);
_root.event(Event.RESIZE);
}
resize();
}
return _root;
}
1.autoDestroyAtClosed/**场景被关闭后,是否自动销毁(销毁节点和使用到的资源),默认为false*/
在close方法中,可以看到if (autoDestroyAtClosed) this.destroy();
2.在open方法中,看到root.addChild(scene);
,而在get root中看到_root = Laya.stage.addChild(new Sprite()) as Sprite
,这说明所有打开的Scene都被添加到一个叫root的Sprite容器里,这个容器是直接放在stage上了。
3.参考一下官方例子中如何使用scene的:
static startScene:any="test/Test2View.scene";
Laya.Scene.open(GameConfig.startScene);
这里直接调用static open方法,在里面调用了load方法
public static function load(url:String, complete:Handler = null):void {
Laya.loader.resetProgress();
var loader:SceneLoader = new SceneLoader();
loader.on(Event.COMPLETE, null, create);
loader.load(url);
function create():void {
var obj:Object = Loader.getRes(url);
if (!obj) throw "Can not find scene:" + url;
if (!obj.props) throw "Scene data is error:" + url;
var runtime:String = obj.props.runtime ? obj.props.runtime : obj.type;
var clas:* = ClassUtils.getClass(runtime);
if (obj.props.renderType == "instance") {
var scene:Scene = clas.instance || (clas.instance = new clas());
} else {
scene = new clas();
}
if (scene && scene is Node) {
scene.url = url;
if (!scene._getBit(Const.NOT_READY)) complete.runWith(scene);
else {
scene.on("onViewCreated", null, function():void {
complete && complete.runWith(scene)
})
scene.createView(obj);
}
} else {
throw "Can not find scene:" + runtime;
}
}
}
牵涉到SceneLoader类
public static const LoadableExtensions:Object = {"scene": Loader.JSON
这个映射会把test/Test2View.scene
转化为去加载相应的json文件
4.Dialog
laya1.0创建的Dialog:xxDialogUI->Dialog->View->Box->Component->Sprite
laya2.0创建的Dialog: xxDialogUI->Dialog->View->Scene->Sprite
Dialog会把Scene中默认的open方法覆盖掉,转交给DialogManager处理
//Dialog:
override public function open(closeOther:Boolean = true, param:* = null):void {
_dealDragArea();
_param=param;
manager.open(this, closeOther, isShowEffect);
manager.lock(false);
}
//DialogManager:
public class DialogManager extends Sprite
public function DialogManager() {
this.mouseEnabled = maskLayer.mouseEnabled = true;
this.zOrder = 1000;
Laya.stage.addChild(this);
Laya.stage.on(Event.RESIZE, this, _onResize);
if (UIConfig.closeDialogOnSide) maskLayer.on("click", this, _closeOnSide);
_onResize(null);
}
public function open(dialog:Dialog, closeOther:Boolean = false, showEffect:Boolean=false):void {
if (closeOther) _closeAll();
if (dialog.isPopupCenter) _centerDialog(dialog);
addChild(dialog);
if (dialog.isModal || this._getBit(Const.HAS_ZORDER)) Laya.timer.callLater(this, _checkMask);
if (showEffect && dialog.popupEffect != null) dialog.popupEffect.runWith(dialog);
else doOpen(dialog);
event(Event.OPEN);
}
这个和1.0是一致的,DialogManager作为一个Sprite被添加到stage上,并且它的zOrder=1000。然后所有的Dialog都添加到DialogManager这个容器内。
4.总结:可以把Scene看作是官方提供的一个场景管理器,像closeOther,closeAll,onOpened,onClosed都是很实用的。当然也可以自己去定制。
四、先阅读原文--->官方 场景使用 实例
在2.0项目开发中,无论是创建场景Scene,页面View,对话框Dialog,3d场景scene3d,文件类型和后缀都是scene。LayaAir2.0开发思路为组件化,脚本化,场景管理开发,项目采用scene管理方式,来管理场景,LayaAir 已经对scene做了一系列方案,使得开发者无需考虑场景,关卡,页面的资源,内存管理,只需要单纯的调用接口,管理场景,其他的交给引擎去做,只需专注游戏逻辑开发即可。
1.先新建一个脚本
//Start.ts
export default class Start extends Laya.Script{
onClick(e: laya.events.Event): void{
Laya.Scene.open('box2d.scene');
}
}
2.在Start.scene中放一个按钮,就可以添加组件了,这样运行时,点击按钮,就会触发onClick方法打开box2d.scene场景。
3.设置启动场景为Start.scene,现在运行后就能看到场景切换效果了。
4.在GameConfig的init方法中可以看到
static init(){
var reg: Function = Laya.ClassUtils.regClass;
reg("script/Start.ts",Start);
}
//ClassUtils:
public static function regClass(className:String, classDef:*):void {
_classMap[className] = classDef;
}
5.在bin下面导出的Start.json里是这样的:
{
"type":"Scene",
"props":{"width":1136,"height":640},
"compId":2,
"child":[
{
"type":"Button",
"props":{"y":232,"x":282,"skin":"comp/button.png","label":"label"},
"compId":4,
"child":[{"type":"Script","props":{"runtime":"script/Start.ts"},"compId":5}],
"components":[]
}
],
"loadList":["comp/button.png"],
"loadList3D":[],
"components":[]
}
6.对比看看只有一张图片的box2d.json,可以看到挂一个组件,多出的是"child":[{"type":"Script","props":{"runtime":"script/Start.ts"},"compId":5}]
{"type":"Scene",
"props":{"width":1136,"height":640},
"compId":2,
"child":[
{
"type":"Image",
"props":{"y":51,"x":100,"skin":"comp/image.png"},
"compId":3
}
],
"loadList":["comp/image.png"],
"loadList3D":[],
"components":[]
}
7.创建过程
(1)Scene.open->Scene.load->Scene.createView->SceneUtils.createByData
(2)在SceneUtils.createByData中会使用createComp递归创建节点
/**
* 根据UI数据实例化组件。
* @param uiView UI数据。
* @param comp 组件本体,如果为空,会新创建一个。
* @param view 组件所在的视图实例,用来注册var全局变量,如果值为空则不注册。
* @return 一个 Component 对象。
*/
public static function createComp(uiView:Object, comp:* = null,
view:* = null, dataMap:Array = null, initTool:InitTool = null):* {
参考注释,观察Scene中使用方式是SceneUtils.createByData(this, view);
public static function createByData(root:*, uiView:Object):* {
var tInitTool:InitTool = InitTool.create();
//递归创建节点
root = createComp(uiView, root, root, null, tInitTool);
...
对于"child":[{"type":"Script","props":{"runtime":"script/Start.ts"},"compId":5}],
这种,会在递归中继续创建
tChild = createComp(node, null, view, dataMap, initTool);
这时候就会解析type:Script这段了:
//处理脚本
if (node.type == "Script") {
if (tChild is Component) {
comp._addComponentInstance(tChild);
} else {
//兼容老版本
if ("owner" in tChild) {
tChild["owner"] = comp;
} else if ("target" in tChild) {
tChild["target"] = comp;
}
}
}
(3)comp._addComponentInstance(tChild);
这里comp就是Button对象,而tChild就是Script对象
public static function getCompInstance(json:Object):* {
if (json.type == "UIView") {
if (json.props && json.props.pageData) {
return createByData(null, json.props.pageData);
}
}
var runtime:String = (json.props && json.props.runtime) || json.type;
var compClass:Class = ClassUtils.getClass(runtime);
if (!compClass) throw "Can not find class " + runtime;
if (json.type === "Script" && compClass.prototype._doAwake) {
var comp:* = Pool.createByClass(compClass);
comp._destroyed = false;
return comp;
}
...
这里通过ClassUtils.getClass拿到上面注册的类:
static init(){
var reg: Function = Laya.ClassUtils.regClass;
reg("script/Start.ts",Start);
}
_addComponentInstance在Node当中,添加了一些属性
//Node
public function _addComponentInstance(comp:Component):void {
_components ||= [];
_components.push(comp);
comp.owner = this;
comp._onAdded();
activeInHierarchy && comp._setActive(true);
_scene && comp._setActiveInScene(true);
}
注意comp.owner = this;
,Component类并不是一个显示对象,无法添加到显示列表,它是个组件,挂载到Node里了,Node类是可放在显示列表中的所有对象的基类。该显示列表管理 Laya 运行时中显示的所有对象。使用 Node 类排列显示列表中的显示对象。Node 对象可以有子显示对象。
上面_onAdded、_setActive、_setActiveInScene,会在创建页面时触发,更多细节见下一节。
五、Component类和Node类
在UI方面,Component类有三个子类:Widget,Script,CommonScript
1.comp._onAdded();
/**
* 被添加到节点后调用,可根据需要重写此方法
* @private
*/
public function _onAdded():void {
//override it.
}
2.这里介绍一下Node中的private var _components:Array;
,简单来说这个数组维护了一个Node添加的所有组件,可以在_addComponentInstance中看到_components.push(comp);
3.Node.activeInHierarchy,参考 activeInHierarchy && comp._setActive(true);
,只有Node激活了,挂载的组件才会激活。
/**
* 获取在场景中是否激活。
* @return 在场景中是否激活。
*/
public function get activeInHierarchy():Boolean {
return _getBit(Const.ACTIVE_INHIERARCHY);
}
4.Node 是否激活
public function set active(value:Boolean):void {
value = ! !value;
if (!_getBit(Const.NOT_ACTIVE) !== value) {
_setBit(Const.NOT_ACTIVE, !value);
if (_parent) {
if (_parent.activeInHierarchy) {
if (value) _activeHierarchy();
else _inActiveHierarchy();
}
}
}
}
public function _activeHierarchy():void {
_setBit(Const.ACTIVE_INHIERARCHY, true);
if (_components) {
for (var i:int = 0, n:int = _components.length; i < n; i++)
_components[i]._setActive(true);
}
_onActive();
for (i = 0, n = _children.length; i < n; i++) {
var child:Node = _children[i];
(!child._getBit(Const.NOT_ACTIVE)) && (child._activeHierarchy());
}
if (!_getBit(Const.AWAKED)) {
_setBit(Const.AWAKED, true);
onAwake();
}
onEnable();
}
public function _inActiveHierarchy():void {
_onInActive();
if (_components) {
for (var i:int = 0, n:int = _components.length; i < n; i++)
_components[i]._setActive(false);
}
_setBit(Const.ACTIVE_INHIERARCHY, false);
for (i = 0, n = _children.length; i < n; i++) {
var child:Node = _children[i];
(!child._getBit(Const.NOT_ACTIVE)) && (child._inActiveHierarchy());
}
onDisable();
}
protected function _onActive():void {
//override it.
}
/**
* 组件被激活后执行,此时所有节点和组件均已创建完毕,次方法只执行一次
* 此方法为虚方法,使用时重写覆盖即可
*/
public function onAwake():void {
//this.name && trace("onAwake node ", this.name);
}
/**
* 组件被启用后执行,比如节点被添加到舞台后
* 此方法为虚方法,使用时重写覆盖即可
*/
public function onEnable():void {
//this.name && trace("onEnable node ", this.name);
}
5.Component.as
public function _setActive(value:Boolean):void {
if (_active === value) return;
if (!owner.activeInHierarchy) return;
_active = value;
if (value) {
if (!_awaked) {
_awaked = true;
_onAwake();
}
_enabled && _onEnable();
} else {
_enabled && _onDisable();
}
}
六、官方文档 LayaAir脚本参数说明
可以在Script代码中添加一些标注,然后在UI设计界面上使用这些属性:
export default class Click1 extends Laya.Script {
/** @prop {name:createBoxInterval,tips:"xxx",type:int,default:1000}*/
createBoxInterval: number = 1000;
/** @prop {name:isMy,tips:"test",type:boolean,default:true}*/
isMy: boolean = false;
private pref: Laya.Prefab = null;
一个完整的标签主要由下面几个部分:
- type IDE属性类型,此类型是指IDE属性类型,非真正的属性类型,不过大多情况下是一样的
- name IDE内显示的属性名称
- tips IDE内鼠标经过属性名称上后,显示的鼠标提示,如果没有则使用name(可选)
- default 输入框显示的默认值(可选)