laya2.0的场景Scene和脚本Script

一、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文件

image.png

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场景。


image.png

3.设置启动场景为Start.scene,现在运行后就能看到场景切换效果了。


image.png

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对象

image.png
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脚本参数说明
image.png

可以在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;
image.png

一个完整的标签主要由下面几个部分:

  • type IDE属性类型,此类型是指IDE属性类型,非真正的属性类型,不过大多情况下是一样的
  • name IDE内显示的属性名称
  • tips IDE内鼠标经过属性名称上后,显示的鼠标提示,如果没有则使用name(可选)
  • default 输入框显示的默认值(可选)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,042评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,996评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,674评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,340评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,404评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,749评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,902评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,662评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,110评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,577评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,258评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,848评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,726评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,952评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,271评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,452评论 2 348

推荐阅读更多精彩内容

  • 我们都是芸芸众生中微小的一粒沙子,把自己放在宇宙中来观察,自己的存在简直微不足道,没有人会注意到你的存在,生命在这...
    袁生斌阅读 195评论 0 0
  • 回忆, 是倒后镜里的公路。 坐在人生的驾驶室里, 我们要全神贯注地望着未来, 时不时瞥一下过去, 从过去, 可以看...
    溟夜星辰阅读 229评论 0 1
  • 鹏程万里 心宽天地阔 志远任鹏跃 抖擞精气神 胸中有乾坤
    国胜阅读 281评论 0 0
  • 以前都是在讲时间管理,但是现在越来越多的人已经在注重精力管理了。为什么呢?因为时间对于每个人来说都是二十四小时,不...
    sunny视界阅读 566评论 0 3
  • 背景 最近公司新购置了好几台 Linux 服务器然后配置一些服务的时候很不习惯,估计是我平时自己的 zsh + o...
    小虚大魔王阅读 282评论 0 0