Egret社区

[教程文档] Lakeshore 插件开发 - 3. 组件运行层部分的开发

2016-11-2 17:44
39482
本帖最后由 flep 于 2016-11-2 17:50 编辑

组件运行层部分的开发
运行层插件是Lakeshore插件系统的核心,官方提供了Lakeshore插件开发的基础核心库:
  • Ls.js
  • Ls.min.js
ls核心库位于modules文件夹下,如图:
3、组件插件运行层101.png
基于ls核心库我们就可以很方便的开发lakeshore运行层方面的插件。lakeshore运行层插件与普通的插件开发方式不太一样,它甚至不需要关注lakeshore运行层核心库是如何运作的,只需要关注插件本身自身的逻辑即可。甚至极端情况下,你可以将一个游戏所有的逻辑都内置到插件系统中,只对外部提供几个简单的接口,即可很方便的利用lakeshore来实现你需要的功能。
上一节我们曾讲过显示对象插件需要继承自AIDisplayObject,非显示对象需要继承自AIObject。
以官方提供的button插件为例,查看其runtime.ts文件,会发现它继承自AIDisplayObject。
表示button插件是一个显示对象。
而查看插件touch,打开runtime.ts文件,会发现其入口类AITouch继承自AIObject。
下面以图示表示一下类的继承关系。
3、组件插件运行层489.png
对于自定义显示对象插件类,基础结构如下:
module ls{
export class AICustomDisplayObject extends AIDisplayObject{
constructor(){
//constructor code
}
}
}
对于自定义非显示对象插件类,基础结构如下:
module ls{
export class AICustomObject extends AIObject{
constructor(){
//constructor code
}
}
}
分析入口插件类AICustomDisplayObject与AICustomObject,其结构一样,只不过不同点在于继承类不一样。另外,自定义的插件类都需要基于模块ls,即必须放在module  ls中。必须以关键字export 置于class之前。这属于typescript的语法,如果对于typescript语法不太清楚的,可以查看typescript官方说明,这里便不再详说。
上一节我们讲过,组件分为场景组件与非场景组件。场景组件在使用之前需要先在场景中创建一个实例,而非场景组件并不需要(可以认为是单例,系统默认创建)。对于不知道如何定义场景组件与非场景组件的同学可以查看上一节的详细说明。
对于场景组件来讲,我们经常需要做的操作是复制,动态创建等,而非场景组件则不需要,因此,场景组件一般需要重写一个clone方法,这个clone方法是在AIObject已经默认定义好的。结构如下:
module ls{
export class AICustomDisplayObject extends AIDisplayObject{
publicProperty:any;
constructor(){
//constructor code
}
clone():AICustomDisplayObject{
var cl:AICustomDisplayObject = <AICustomDisplayObject>super.clone();
cl.publicProperty=this.cl.publicProperty;
cl.initialize();
return cl;
}
}
}
1. 条件
我们在上一节详细讲解了插件在IDE方面的条件与动作的编写方法。而strings.json文件中定义的条件和动作需要与runtime.ts文件中的条件与动作一一对应起来。下面我们便详细讲解一下在runtime.ts文件中定义条件的方法。为了更清晰的讲解条件的定义,我们以movieclip为例,打开插件movieclip下面的runtime.ts文件,查找下面一段条件函数代码,如图:
3、组件插件运行层1654.png
条件定义分三部分:
1) 条件函数
条件函数与strings.json里定义的条件callName名称一致,如图所示:
3、组件插件运行层1713.png
若为帧循环触发条件,那么,条件函数将会每帧执行一次用于检测条件是否成立。
若为主动式触发条件,那么,条件函数受某个因素影响而进行检测,比如按钮按下等。
2) 条件参数
条件参数类名与strings.json里定义的type名称一致,如图所示:
3、组件插件运行层1834.png
条件参数需要传递一个继承自BaseEvent的类实例,例如条件“onPlayComplete”的参数实例“OnPlayCompleteEvent”继承自BaseEvent,如下:
export class OnPlayCompleteEvent extends BaseEvent{
    constructor(){super();}
}
上面的条件参数不带任何属性,即在IDE里不需要属性选择。
但多数情况下,我们需要传递至少1个属性作为比较。
比如,movieclip插件下的runtime.ts文件下的比较当前帧,就需要传递两个参数,一个为比较运算符,一个为帧索引。在strings.json文件中条件表示为:
{
                        "name": "比较当前帧",
                        "type": "CompareFrameEvent",
                        "callName": "compareFrame",
                        "isTrigger": "false",
                        "toogle": "true",
                        "icon": "icons/conditions/CompareFrameEvent.png",
                        "description": "对动画播放的当前帧进行比较。",
                        "detailformat": "如果动画当前帧 {0} {1}",
                        "properties": [
                            {
                                "name": "operationType",
                                "label": "运算符",
                                "value": "equalTo",
                                "valueType": "any",
                                "restrict": "",
                                "description": "选择比较运算符。",
                                "prepare": [
                                    {
                                        "label": "等于",
                                        "value": "equalTo"
                                    },
                                    {
                                        "label": "不等于",
                                        "value": "notEqualTo"
                                    },
                                    {
                                        "label": "小于",
                                        "value": "lessThan"
                                    },
                                    {
                                        "label": "小于或等于",
                                        "value": "lessOrEqual"
                                    },
                                    {
                                        "label": "大于",
                                        "value": "greaterThan"
                                    },
                                    {
                                        "label": "大于或等于",
                                        "value": "greaterOrEqual"
                                    }
                            },
                            {
                                "name": "frame",
                                "label": "比较值",
                                "value": "1",
                                "valueType": "any",
                                "restrict": "",
                                "description": "与当前帧比较的帧数。",
                                "prepare": []
                            }
                    }
在runtime.ts中的条件条件参数类写法如下:
export class CompareFrameEvent extends BaseEvent{
   operationType: string;
   frame: any;
   constructor(){super();}
}
比较当前帧的执行函数表示如下:
compareFrame($event: CompareFrameEvent): IConditionData {
   return { instances: [this], status: compare(this._currentFrame, $event.operationType, $event.frame) };
}
注意:strings.json文件中的条件属性名称与runtime.ts文件中的条件参数属性名称必须得一致。
3) 条件函数返回值
条件函数返回值类型是固定的,即所有的条件必须返回IConditionData类型数据。IConditionData包含条件的一些状态以及条件所带的一些数据。它的结构如下:
interface IConditionData {
        instances: any[];
        status: boolean;
        data?: any;
        selectSingle?: boolean;
    }
a. Instances
当前参与条件过滤计算的实例列表
一般情况下,instances列表都是自身,即值为[this]。比如此处的“单次播放完毕”条件,参与判断的只是此动画本身是否播放完毕,因此,此处只需要传this即可,由于默认为数组类型,所以用[]来括起来。最终为:[this]。
但某些复杂情况下,instances列表除了自身实例之外,还可能包含别的实例,比如,精灵的一个条件“碰撞”,实际上我们除了需要传自身实例之外,还需要传递与之碰撞的实例。即:[this,collisionTarget]。
有些人可能不太明白这里为什么要传递参与计算的目标呢,实际上是为了进行条件判断时目标过滤。举一个例子,假定场景中有6个叫ball的小球水平排列,场景中还有一个叫bullet的子弹。我们创建一个条件,当bullet与ball碰撞时,ball销毁。实际上,如果不告诉是bullet与哪个ball碰撞了,是不知道哪个ball会被销毁的。因此,传递有效的目标可以有效的将所需要的目标过滤出来。由于条件过滤机制太复杂,有太多特殊的情况,我们将会在接下来的章节中专门介绍Lakeshore的条件过滤机制问题,这里就不再详细说明了。
b. status
状态
这里的状态是指每次执行条件时需要告诉底层运行时当前所检测的状态是真是假。这里的真假决定了当前事件中的动作是否可以被执行,如果状态为false,那么,实际上动作肯定不执行,如果状态为true,还得要检测与之相关的条件是真是假,这里的检测机制同样会在Lakeshore条件过滤机制一节中详细讲解,在这里,我们只需要关注自身状态是真还是假。
另外,之前我们讨论过触发条件与非触发条件。如果是触发条件,那么status值一定为true,否则,需要根据当前运行状态判断。
c. data
条件传递的数据。
有时候,我们可能需要在检测条件的时候进行数据传递。因此,这里可以自行定义,一般情况下不需要传递数据。
d. selectSingle
是否只选择其中一个实例。
某些特殊情况下,同一个名称可能存在多个实例,实际上,有时候我们又希望强制性的只想对其中一个实例进行过滤,这里就可以通过真假来设置了。
4) 触发条件:
onPlayComplete($onPlayComplete: OnPlayCompleteEvent): IConditionData {
    return { instances: [this], status: true };
}
通过上面的讲解,我们可以知道status值为true,在strings.json文件中为的isTrigger值为true,如图:
3、组件插件运行层6413.png
这里我们解释一下触发条件为什么一定为真。因为,触发条件是主动式触发,并非由系统自发式触发,即并非在帧循环系统里进行触发。所谓帧循环,是每一帧(1/60秒)都会对系统所有事件的所有条件进行检测,用来判断是否为条件是否为真。如果为真,那么执行动作,否则不执行后面的动作。我们举一个帧循环的例子,创建一个条件:
【条件】如果小球ball的水平方向坐标(x)大于100。
【动作】让小球销毁。
实际上,对于小球ball的坐标什么时候会大于100,我们如果不每帧判断的话,是不清楚的,这种情况下就属于帧循环检测。
还有一个是主动式触发,例如:
【条件】当点击按纽ball的时候。
【动作】让按纽ball销毁。
ball被点击是由用户输入的,这种情况下,我们称之为主动式触发。例如还有,比如图片加载完毕等。
在Lakeshore运行层中,如果条件为主动式触发,那么没有被触发,条件是不会被执行的。即上面的条件”onPlayComplete”函数是不会被调用的。因此,这里直接将status值设置为true,而在帧循环触发中,因为需要每帧进行判断,而每次判断的时候不一定当前判断的状态为真,所以需要根据当前状态来定义,即,我们在条件”isPlayingCheck”函数中是这么写的:
isPlayingCheck($isPlaying: IsPlayingEvent): IConditionData {
    return { instances: [this], status: this._isPlaying };
}
2. 动作
动作的创建与条件的创建很类似,也需要由以下三部分组成:
1) 动作函数
同样的道理,动作函数名必须与strings.json中定义的动作名一致,如在movieclip插件中rumtime.ts中的动作”gotoAndPlay”,定义如下:
3、组件插件运行层7193.png
而与之对应的strings.json文件中定义如下:
3、组件插件运行层7222.png
其中strings.json中的type与runtime.ts中的动作函数名一致。这样,当条件满足时,就会根据配置表的动作名来查找目标类中的函数名了。
2) 动作参数列表
runtime.ts中的动作“gotoAndPlay”带两个参数:
l frame
l Loop
而在strings.json文件中动作“gotoAndPlay”则有两个属性,这两个属性列表即是runtime.ts中动作“gotoAndPlay”的两个参数,其两方的值类型必须一致,都为any。
有人可能会问,为什么strings.json中动作“gotoAndPlay”中的两个属性名称(name)的值分别为2与3,而在runtime.ts中动作“gotoAndPlay”两个参数值分别为frame和loop?其实很简单,传进gotoAndPlay函数中的只是形参,因此,可以不要求同名,当然,同名会更好,因为识别起来更方便,只需要保证参数的顺序一致性即可。
3) 动作函数返回值
通常情况下,动作函数返回void,因为动作执行完毕之后,一个逻辑就算完毕了,也不需要通知做什么事。然而有些情况下,我们还需要做一些特殊的事,比如在一个事件中,同时有二个动作,我希望上一个动作的结果能够传递给下一个动作。那么,显然,在当前动作执行完毕之后,我们需要告诉下一个动作当前的结果,这里的结果一般指实例本身。我们举一个很特殊的例子,如图:
3、组件插件运行层7815.png
场景中有两个对象,一个是fly的飞机,一个是bullet的子弹,默认给bullet对象添加“子弹”行为,并设置其默认运行角度为-90度,很明显,子弹会向上运动,然后,在事件表中添加一条件事件,如图:
3、组件插件运行层7917.png
最终运行效果如下:
3、组件插件运行层7929.png
很多时候,我们希望改变子弹的运行角度,如图,我们添加一个动作,设置子弹的运行角度为-110度,如图:
3、组件插件运行层7982.png
最终运行,效果如下:
3、组件插件运行层7995.png
很明显,fly创建bullet子弹之后,接着就改变了创建的bullet的运行角度。这里有人就会问了,这里叫bullet的子弹可能存在多个实例,系统怎么知道改变哪个bullet的角度呢,或者系统会不会改变所有子弹的运行角度呢,如果改变所有子弹的运行角度,那么,很明显,子弹还是向垂直向上运动,而不会朝-110度运动。如果只改变新创建的bullet的角度,那么,就可实现如上图的效果了,在runtime.ts中,为了告诉第二个动作“设置子弹运行角度”,在上一个动作“制造”后需要有一个返回值,这里的返回值就是制造的子弹,制造动作的runtime.ts文件没有开放,下面截一下图:
3、组件插件运行层8285.png
这里返回的是一个数组,当然,如果你希望将上一个动作执行的若干目标传递给下一个动作,那么,数组列表中就列出需要传递的目标就可以了。
需要注意一点的是,如果动作函数返回的是void,那么,就不会传递任何值给下一个动作了,因此,我们可以认为传递只会进行一层,因为到下一层就会被下一个动作破坏了。
通过这个功能,我们很轻松的制作散弹,事件表如下:
3、组件插件运行层8457.png
事件表中唯一不同的是制造之后,重新设置了角度,运行效果如下:
3、组件插件运行层8490.png
利用for循环进行散弹制作更简单:
事件表如下:
3、组件插件运行层8517.png
效果如下:
3、组件插件运行层8525.png

分享到 :
0 人收藏

2 个回复

倒序浏览
yjtx  官方团队 | 2016-11-3 15:41:02
koyonuji  圆转纯熟 | 2017-6-26 16:00:31
留个名,每次都要找!好东西
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|京网文[2014]0791-191号|京ICP证150115号|Egret社区 ( 京ICP备14025619号 )

Powered by Discuz! X3.4 © 2001-2019 Comsenz Inc.

返回顶部