Welcome To Junix's Blog


  • 首页

  • 归档

基础界面组件

发表于 2018-03-14

基础界面组件

Eui中一个界面的创建,预加载资源和皮肤后,首先会在舞台上创建这个界面,随后再给这个界面绑定皮肤。这个步骤需要严格监听相对应的事件,顺序不能颠倒,故每写一个界面文件时,都需要处理监听这些事件,操作统一且固定,造成冗余。故现在抽离出一个基础界面类BasicUI,进行一些统一的操作,只要界面统一继承自这个BasicUI即可。

代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
export class BasicUI extends eui.Component {
public adata;//protected adata;
public constructor(cd ?: any) {
super();
if(cd){
this.adata = cd;
}

let c_class = this["__class__"] as string;
let c_arr = c_class.split(".");
this.name = c_arr[1];//每个界面独有name 此方法可获取

this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);//添加到舞台
this.addEventListener(egret.Event.REMOVED_FROM_STAGE, this.remove_self, this);//从舞台上移除
this.addEventListener(eui.UIEvent.COMPLETE, this.mx_created, this);//绑定批复后,所有元素加载完成
}

private onAddToStage(event:egret.Event) {
this.removeEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
//舞台上创建完毕后,绑定对应皮肤文件
this.set_skinname();
}

private mx_created(event:eui.UIEvent):void{
this.removeEventListener(eui.UIEvent.COMPLETE, this.mx_created, this);
//所有元素加载完成 后续操作自定义实现
this.pre_init();
}

protected remove_self() : void{
let view = this;
view.removeEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
view.removeEventListener(egret.Event.REMOVED_FROM_STAGE, this.remove_self, this);
view.removeEventListener(eui.UIEvent.COMPLETE, this.mx_created, this);
view.on_remove();
view.removeChildren();
}

//通用的绑定皮肤方法,子类可覆盖
protected set_skinname() : void{
let cname = this.name + "Skin";
let skins = window && window["skins"];
if(skins && skins[cname]){
this.skinName = skins[cname];
}else{
this.skinName = RES.getRes(cname + "_exml");//默认使用立刻获取的方式
}
}
//子类初始化操作
protected pre_init() : void{}
protected on_remove() : void{}//仅处理特殊逻辑操作

public set_scale(s: number): void {
this.scaleX = this.scaleY = s;
}
}

egret.Event.ADDED_TO_STAGE和eui.UIEvent.COMPLETE这两个事件需要在所有文件界面中处理,故提取到基类处理,减少代码量,也不用暴露给开发。

Egret 4.1引擎以上第三方库文件制作、导入以及使用(二)

发表于 2017-12-13

Egret 中使用puremvc

在上篇中介绍了如何将puremvc第三方库导入到项目中,在本文中将介绍如何在项目中启动puremvc。

首先定义好统一消息管理文件,方便修改和维护

1
2
3
4
5
6
module Jun {
export const NOTICE = {
"STARTUP" : Symbol(),//启动MVC
"CLIENT_INITED" : Symbol(),//客户端初始化完成
}
}

首先需要用ts自定义实现ApplicationFacade的单例,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* @author Junix
* @desc 引擎面门,用于启动整个项目
**/
module Jun {
export class ApplicationFacade extends puremvc.Facade implements puremvc.IFacade{

public constructor(){
super();
}

public static getInstance():ApplicationFacade{
if (this.instance == null) {
this.instance = new ApplicationFacade();
}
return <ApplicationFacade><any> (this.instance);
}

public initializeController():void{
super.initializeController();
this.registerCommand(NOTICE.STARTUP, StartupCommand );
}

/**
* 启动PureMVC,在应用程序中调用此方法,并传递应用程序本身的引用
* @param rootView:PureMVC应用程序的根视图root,包含其它所有的View Componet
*/
public startUp(rootView:egret.DisplayObjectContainer):void{
this.sendNotification(NOTICE.STARTUP, stage);
this.removeCommand(NOTICE.STARTUP);
}
}
}

StartupCommand的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author Junix
* @desc StartupCommand 启动命令,项目启动后用于初始化M,V,C 命令
**/
module mx {
export class StartupCommand extends puremvc.MacroCommand{
//携带的消息会依次传递给子消息
public initializeMacroCommand():void{
this.addSubCommand(ModelPrepCommand);//model管理器
this.addSubCommand(ViewPrepCommand);//view管理器
this.addSubCommand(ControllerPrepCommand);//controller管理器
}
}
}

对应的 ModelPrepCommand、ViewPrepCommand、ControllerPrepCommand的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* @author Junix
* @desc 数据层命令,注册所有的model
**/
module Jun {
export class ModelPrepCommand extends puremvc.SimpleCommand implements puremvc.ICommand{
public execute(notification : puremvc.INotification) : void{
this.facade.registerProxy(new GameProxy());
}
}
}
/*
* @author Junix
* @desc 显示层命令,V层只注册跟容器的mediator 具体场景的mediator可去对应的view下注册

**/
module Jun {
export class ViewPrepCommand extends puremvc.SimpleCommand implements puremvc.ICommand{
public execute(notification:puremvc.INotification):void{
let main = notification.getBody();
this.facade.registerMediator(new MainMediator(main));
}
}
}

/**
* @author Junix
* @desc 控制层命令, 注册所有command
**/
module Jun {
export class ControllerPrepCommand extends puremvc.SimpleCommand implements puremvc.ICommand{
public execute(notification:puremvc.INotification):void{
(new GameCommand()).register();
}
}
}

准备工作完成,为了验证是否正确启动了puremvc,我们单独注册了GameCommand来监听CLIENT_INITED这一则消息,并由gameproxy做出对应处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//GameCommand 监听CLIENT_INITED
module Jun{
export class GameCommand extends puremvc.SimpleCommand implements puremvc.ICommand{
public register() : void {
this.facade.registerCommand(NOTICE.CLIENT_INITED,GameCommand);
}

public execute(notification:puremvc.INotification):void{
let data = notification.getBody();
let name = notification.getName();
let proxy = <GameProxy><any> this.facade.retrieveProxy(GameProxy.NAME);
switch(name){
case NOTICE.CLIENT_INITED:
proxy.init_game();
break;
}
}
}
}

//GameProxy 相关数据处理
module Jun{
export class GameProxy extends puremvc.Proxy implements puremvc.IProxy{
public static NAME : string = 'GameProxy';

public constructor(){
super(GameProxy.NAME);
}

//对应处理
public init_game() : void{
console.log('初始成功');
}
}
}

在Main.ts中初始化,如果你有另写过游戏里自定义容器的话,则将该容器作为根视图传入startUp()中

1
2
3
4
5
//Main.ts创建主界面时
//初始化MVC结构
Jun.ApplicationFacade.getInstance().startUp(this);
//发送CLIENT_INITED消息
Jun.ApplicationFacade.getInstance().sendNotification(jun.NOTICE.CLIENT_INITED);

最后运行项目,在输出控制台里有输出说明puremvc框架成功启动并使用
举个栗子

Egret 4.1引擎以上第三方库文件制作、导入以及使用(一)

发表于 2017-12-09

Egret 引入puremvc

现在的TS开发大多启用puremvc框架,那么在Egret项目里想要引入puremvc就需要自己手动导入。导入的时候发现,官方文档上的说明早就不适合当前最新5.0的引擎,于是自己翻阅文档,研究了一下。

准备库文件:

首先,下载puremvc js库文件 puremvc.js和声明文件puremvc.d.ts。点我下载
得到文件后 重命名下成puremvc.js和puremvc.d.ts。

制作第三方模块:

得到两个文件后,去你的游戏项目外,cmd下输入 egret create_lib puremvc 来创建第三方库文件。
第三方库项目与 Egret 项目不能嵌套。请不要在Egret 项目目录下面创建第三方库项目。
输入后你会得到两个json文件,如下

1
2
3
4
|-- xxxxx //你的项目主文件
|-- puremvc //创建的第三方库文件
|-- tsconfig.json
|-- package.json

  • 自己要另建两个文件夹bin src
1
2
3
4
5
6
|-- xxxxx //你的项目主文件
|-- puremvc //创建的第三方库文件
|-- bin
|-- src
|-- tsconfig.json
|-- package.json
  • 删除 package.json中的 modoules 字段
  • 修改 tsconfig.json文件,根据 TypeScript / JavaScript 不同类型的类库,进行不同的项目配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// JavaScript 类库
{
"compilerOptions": {
"target": "es5",
"outFile": "bin/libtest1/libtest.js",
"allowJs": true
},
"files": [
"src/a.js",
]
}
// TypeScript 类库
{
"compilerOptions": {
"target": "es5",
"outFile": "bin/libtest1/libtest.js",
"declaration": true
},
"files": [
"src/a.ts",
]
}
  • 因为我们使用的是javascript类库puremvc.js,所以我们应该这样写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // JavaScript 类库
    {
    "compilerOptions": {
    "target": "es5",
    "noImplicitAny": false,
    "sourceMap": false,
    "outFile": "bin/puremvc.js",
    "allowJs": true
    },
    "include": [
    "src"
    ],
    "files": [
    "src/puremvc.js"
    ]
    }
  • 如果项目是 JavaScript 类库,需要在 package.json中配置一个 typings字段,并设置为一个自定义的 .d.ts 文件
    package.json配置如下:

    1
    2
    3
    4
    {
    "name": "puremvc",
    "typings": "typings/puremvc.d.ts"
    }
  • 我们把puremvc.d.ts声明文件放在这里就行了

    1
    2
    3
    4
    5
    6
    7
    8
    |-- xxxxx //你的项目主文件
    |-- puremvc //创建的第三方库文件
    |-- bin
    |-- src
    |-- typings
    |-- puremvc.d.ts
    |-- tsconfig.json
    |-- package.json
  • 完成上述操作后,执行 egret build,会根据 tsconfig.json中的 outFile字段生成库文件,压缩文件以及 .d.ts

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    |-- xxxxx //你的项目主文件
    |-- puremvc //创建的第三方库文件
    |-- bin //生成文件
    |-- puremvc.d.ts
    |-- puremvc.js
    |-- puremvc.min.ts
    |-- src
    |-- typings
    |-- puremvc.d.ts
    |-- tsconfig.json
    |-- package.json

使用第三方模块:

大功告成,现在再去项目主目录下的egretProperties.json中 module字段下添加

1
2
3
4
5
6
7
"modules": [
//...省略其他模块
{
"name": "puremvc",
"path": "../puremvc"
}
]

最后运行下egret build -e ,引擎会把自定义的第三方库引用进来。
这样,在这个项目代码中就可以使用puremvc框架了,如何启动请见下一篇。

游戏优化相关浅谈

发表于 2017-11-11

游戏加载优化

由于微信及H5版本的加载压力,以及游戏自身品质性能的提高,对游戏进行加载优化,优化方向为:合并api请求,合并json文件,整理资源组,合图组。删除废弃的加载项。

https请求:

1.部分功能在独带版已经废弃,或者暂不开启。可将这部分请求删除。
2.部分请求可以一次游戏仅请求一次,或者一段间隔后再次请求,原来支持这种设定,现在与6666合并冲突,调整下重新支持。
3.部分请求合并后,由于多个请求之间参数可能相同,所以将携带参数的请求没有合并到6666中,与服务端协调,合并到请求包中。

静态表及JSON:

1.部分功能在独带版已经废弃,或者暂不开启。可将这部分请求删除。比如敏感字校验
2.合并部分json进入config中,注意静态表本身有缓存机制,不要将频繁修改的静态表合到config中。避免单文件过大,也可以合并几个文件为一个configX,注意不要造成单场景加载量过大。config中现有的静态表也排查一遍,去掉无用和频繁修改的表。
3.删除静态表的废弃字段,及废弃数据。比如签到数据可以仅保留近2个月的数据,每次大更新修改一次。
4.场景移除时,释放该场景使用的静态表,下次使用时重新从缓存中获取(风险较高,暂不处理)。

图片,皮肤文件:

1.所有图片进行压缩,尤其是技术导出的位图文本,MC,合图文件。一定要压缩。
2.皮肤中不要有文本赋值,不要给组件属性赋默认值,非特殊情况不要指定字体。避免无意义的Group。(此项针对小游戏皮肤压缩,详细规则可参照小游戏皮肤优化建议)。
3.去掉图片同一次场景打开时,不会使用的资源赋值,如后宫背景的白天夜晚资源(代码动态指定),选秀的两人|三人模式(多状态控制)。这种情况在皮肤中不要给source赋值,在代码中动态指定即可。
4.将常用的样式封装成组件,多处复用。(有利于小游戏皮肤压缩,代码压缩)。
5.避免通用组件复杂化,应合理均衡多样化和拆分。(较为复杂,统一处理)。
6.纯色背景的九宫拉伸,减少合图体积。(需要美工少许协助)。
7.背景通用化,将背景固定为几个格式,技术和美工使用统一命名。避免资源重复,显示效果不统一。(需要美工大量协助)
8.数字位图文本通用化,预设多个数字文本,统一命名,指定使用,(由于异步加载,不建议合图,且需美工协助,实际意义不大,可延后处理)。
9.同一个图片,如果仅存在尺寸差异,可考虑使用一张大图进行放缩,但不建议放缩比例过大会影响效果和单场景的内存占用,常用的货币图标不建议进行放缩。
10.可明显划分区域的复杂背景,建议使用九宫拉伸,或者文件拆分。(需要美工协助,工作量大,延后处理)
11.SSbtton优化,分为底层背景+上层位图文本+右上角红点提示。点击可同步缩放。背景和位图文本均分样式进行预设,美工技术采用统一命名进行指定。(需要美工及大协助,工作量巨大,延后处理)
12.样式背景,可进行适当拆分,例如任务条目的背景。(需要美工的大量协助,且需要考虑美术效果,延后处理)
13.会同时使用的MC可以合并多个为同一个文件,减少请求数量。

资源组,合图组:

1.资源一定要配置预加载,便于使用和释放。
2.发布时一定要合图,合图后一定要压缩。MC,位图文本,龙骨资源不要进行合图。
3.public\preload中资源不要放置在任何资源组与合图组中,因此对public中的资源要严格校验是否通用。
4.alert中为次常用资源,请配置到需要的资源组中,以便于内存释放,但不要放入任何合图组中,以免造成过量加载。
5.通常一个目录下的所有资源应在同一个预加载资源组中,同一个合图组中。如不在同一个资源组中,请使用划分物理目录的形式进行区分。
6.由于合图应小于10241024,一个资源组 = m 合图组 + n 组内零散资源 + k alert资源。
7.由于合图是按照2次幂进行尺寸匹配,应避免合图后出现大量空白。
8.零散使用的资源不建议进行合图,避免占用过多内存,这样的资源应放置在base包中。
9.将稳定的零散资源,稳定的界面放置在base包中,利用缓存机制,减少加载数,和更新后由于回源造成的加载延时。

动态加载:

目前技术支持动态配置静态表,动态配置网络请求,动态配置资源组。开发根据情况在preloadtool和systemproxy中进行配置。
优化:
1.将动态配置方法均转移至systemproxy中,使加载逻辑与动态配置逻辑相分离。
2.暂不支持同一个场景的预加载中,相互依赖的网络请求进行动态配置。如活动数据。

如何识别是否需要优化:

0.可通过浏览器查看加载下来的文件的源目录,api请求的参数和返回数据。
1.检查加载下来的图片是否有重复,仅存在尺寸差异。
2.检查加载下来的资源是否都有使用。
3.检查是都加载了多个合图,通常一个场景仅有一个合图,多余的合图是否大部分资源都不在本场景中使用。
4.检查打开界面后,是否有图片延迟显示。
5.检查加载下来的静态表是否需要。(需要对项目和代码有一定的了解)。
6.检查反复进入一个场景请求的api是否有变化。部分请求是不需要每次进入游戏都进行发送的。(需要对项目和代码有一定的了解)。
7.检查反复打开一个界面,是否有内存占用的显著增长。(需要对内存监管有了解)

动态加载界面所需资源

发表于 2017-07-11

资源的动态加载

一场战斗开始前务必要加载好相应的资源,让战斗在展示播放的过程中保持流畅和不卡顿,将极大改善游戏体验。当然了,如果要加载所有英雄资源组肯定会造成极大的开销和浪费,务必需要自己实实一套独立的动态加载机制,不仅运用于战斗,也运用于所有需要的场景下。以战斗为例,涉及到的资源是由双方英雄决定的,那么在进入战斗画面开始前,在已经知晓了双方出站英雄的索引id后,通过索引id去找出所有参战英雄的相关资源,如:技能特效、技能buff特效、人物动画、人物立绘等等。将这些涉及到的资源放入一个临时资源组中,在界面创建时候动态加载这个临时资源组,离开界面时销毁这个资源组即可。

目前技术支持动态配置静态表,动态配置网络请求,动态配置资源组。开发根据情况在preloadtool和systemproxy中进行配置。
优化:
1.最好将动态配置方法均转移至其他工具类中,使加载逻辑与动态配置逻辑相分离。
2.暂不支持同一个场景的预加载中,相互依赖的网络请求进行动态配置。如活动数据。

打开一个界面时,在依次检测加载这个界面所需的静态表、通信端口后,在最后一步对动态资源进行加载,需要自定义实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
……场景加载逻辑
**/
if (this.loadIndex < this.loadMax) {//检查动态资源组
this.check_dynamic_group();
}

private DYN_GNUM = {}
private check_dynamic_group(): void {//最后检查动态资源组
let arr = this.get_dyn_arr(this.obj);//obj是创建界面时候传入的参数
this.DYN_GNUM[this.loadScreen] = this.DYN_GNUM[this.loadScreen] || 1;
if (arr.length) {//存在动态资源组
let gname = this.loadScreen + "1";
if (this.DYN_GNUM[this.loadScreen] == 1 || arr.length > 1) {//第一次加载或者有多个资源
gname = this.loadScreen + this.DYN_GNUM[this.loadScreen];
RES.createGroup(gname, arr);
this.DYN_GNUM[this.loadScreen]++;
}
this.loadAssets.push("assets." + gname);
//继续加载下一项
this.load_next();
} else {
//没有动态资源 直接预加载
this.pre_complete();
}
}

private get_dyn_arr(data){
let arr = [];
let c_d = data.param;
let cname = data.name;
switch (cname) {
case FightView.S_NAME:
arr = function(){}//方法自实现,按英雄id找出所有英雄技能和buff,将涉及到的特效、动画、立绘、资源图等存放到arr数组里
break;
/*...后续有其他场景需要,则继续添加场景名和对应实现方式即可*/
}
let skins = window && window["skins"];
if (!(skins && skins[cname + "Skin"]) && RES.hasRes(cname + "Skin_exml")) {
arr.push(cname + "Skin_exml");
}
return arr;
}

Tween缓动动画实现复杂运动

发表于 2017-05-13

Tween缓动动画

当我们搞事的产品经理要求做一个抛物线运动的效果,参考网上的资料与实际需要,就自己写了一套通用实现。垂直和水平的移动不用多做介绍,重点是曲线运动。有个著名的曲线叫贝塞尔曲线,运用相关数学公式,可以写出比较满意的效果。除此以外,后期做了运动对象的兼容,让文字、位图文本、图片和逐帧动画都可以作相应的运动。
预先设定好相关结构体ITween_D

1
2
3
4
5
6
7
8
9
10
11
12
export interface ITween_D {//自定义运动参数接口
"o_type": string;//当前运动对象类型,label,blabel,image,mc
"o_param": any;//当前运动对象携带参数,如文本,图片资源名,mc的id。
"t_type": string;//运动类型,bse,zx(拼音首字母)
"t_param": {//运动参数。
"ex": number;//结束点x坐标
"ey"?: number;//结束点y坐标
"ax"?: number;//运动锚点x坐标
"ay"?: number;//运动锚点y坐标
};
"c_time"?: number;//运动时间,默认为500(逻辑帧整数倍)。
}

按照结构的数据格式传入即可

代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/**
* Tween动画
**/
export class mxTweenObj extends egret.DisplayObjectContainer
{
//目標组件类型,label:普通文本,blabel:位图文本,image:图片,mc:动画
private o_type : string;
private o_param : any;//目标携带参数
//Tween类型,bse:贝塞尔曲线,zx:直线
private t_type : string;
private t_param : any;//tween参数
private c_obj : any;//需要進行tween操作的物体。
private c_tween : any;//tween实例
private c_time : any;
private bse_ex : any;//终点
private bse_ey : any;
private bse_ax : any;//参照点
private bse_ay : any;

public constructor(mx : ITween_D){
super();
for(let i in mx){
this[i] = mx[i];
}
this.init_param();
}

private init_param():void{
//运动轨迹
switch(this.t_type){
case "bse"://贝塞尔曲线,需要终点和参照点
this.bse_ex = this.t_param.ex;//>0
this.bse_ey = this.t_param.ey || 0;
this.bse_ax = this.t_param.ax || this.t_param.ex * 0.5;
if(this.t_param.ay){
this.bse_ay = this.t_param.ay
}else{
this.bse_ay = 0 - Math.abs(this.bse_ex);
}
break;
case "vertical":
this.bse_ex = this.t_param.ex;//>0
this.bse_ey = this.t_param.ey || 0;
this.bse_ax = this.t_param.ax || this.t_param.ex * 0.5;
if(this.t_param.ay){
this.bse_ay = this.t_param.ay
}else{
this.bse_ay = 0 - Math.abs(this.bse_ex);
}
break;
case "zx":
this.bse_ex = this.t_param.ex || 0;//>0
this.bse_ey = this.t_param.ey || 0;
break;
default:
break;
}
//运动的对象,可以是文字,位图文本,图像,动画
let c_t, str, font= "";
switch(this.o_type){
case "label":
str = "+" + this.o_param;
font = "add_ybi_fnt";
c_t = new eui.BitmapLabel(str);
c_t.font = font;
break;
case "blabel":
if(this.o_param < 0){
str = "" + this.o_param;
font = "sub_blood_fnt";
}else{
str = "+" + this.o_param;
font = "add_blood_fnt";
}
c_t = new eui.BitmapLabel(str);
c_t.font = font;
break;
case "image":
c_t = new eui.Image(this.o_param + "_png");
break;
case "mc":
c_t = new GeneralEffect(this.o_param);
c_t.play_by_times(-1);
break;
default:
break;
}
c_t.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
this.c_obj = c_t;
this.addChild(c_t);
}
private onAddToStage(event:egret.Event) {
//加载到舞台
this.c_obj.removeEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
this.c_tween = egret.Tween.get(this).to({mx:1}, this.c_time);
this.c_tween.call(this.on_remove, this);
}

public set mx(v:number){
switch(this.t_type){
case "bse"://贝塞尔运动,抛物线
this.c_obj.x = 2 * v * (1 - v) * this.bse_ax + v * v * this.bse_ex;
this.c_obj.y = 2 * v * (1 - v) * this.bse_ay + v * v * this.bse_ey;

let tan = 0 - (this.bse_ay - 2 * this.bse_ay * v + this.bse_ey * v) /
(this.bse_ax - 2 * this.bse_ax * v + this.bse_ex * v);
let c_r = 0 - Math.atan(tan) / Math.PI * 180;
this.c_obj.rotation = c_r;
break;
case "vertical"://垂直
this.c_obj.x = 2 * v * (1 - v) * this.bse_ax + v * v * this.bse_ex;
this.c_obj.y = 4 * v * (1 - v) * this.bse_ay + v * v * this.bse_ey;
break;
case "zx"://水平直线
this.c_obj.x = v * this.bse_ex;
this.c_obj.y = v * this.bse_ey;
break;
default:
break;
}
}

public get mx():number{
return 0;
}
//记得释放
public on_remove():void{
egret.Tween.removeTweens(this);
this.c_tween = null;
if(this.c_obj){
this.c_obj.removeEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
this.c_obj = null;
}
this.removeChildren();
if(this.parent){
this.parent.removeChild(this);
}
}
}

使用的时候如下

1
2
3
4
5
6
7
let mxd = {
/*按需求自行填写参数*/
}
let mxto = new mxTweenObj(mxd);
mxto.x = 0;//自行指定坐标
mxto.y = 0;//自行指定坐标
stage.addChild(mxto);

Egret的EUI自定义布局(弧形,等差排列)

发表于 2017-03-01

EUI容器布局

白鹭引擎中的EUI容器布局分三种 : 水平,垂直,格子。
以上三种基本布局就能满足大部分的需求,然而亲(gao)爱(shi)的产品经理总会有奇奇怪怪的设计。

弧形

因为环形布局,依赖容器尺寸来定义半径,所以需要容器显式的设置width和height,在这种情况下measure方法将失去作用,如果需要根据内部子项计算尺寸,就要重写显示列表的更新函数updateDisplayList()。在这个函数中,将根据起始角度,重定位所有子元件。

代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
* 自定义布局,支持椭圆(圆形),弧形布局
**/
export class MXCircleLayout extends eui.LayoutBase{
private _data : number;//弧度
public constructor(data ?: any){//
super();
this._data = data;
}

public measure():void{
super.measure();
}
/**重写显示列表更新*/
public updateDisplayList(unscaledWidth : number, unscaledHeight : number):void{
super.updateDisplayList(unscaledWidth, unscaledHeight);

if(this.target == null){
return;
}

let cd : any = this._data || {}
let maxX : number = 0;
let maxY : number = 0;
//重定位所有子元件
for (let i = 0, count = this.target.numElements; i < count; i++){
let c_c : eui.UIComponent = <eui.UIComponent> (this.target.getVirtualElementAt(i));
if (!egret.is(c_c, "eui.UIComponent") || !c_c.includeInLayout){
continue;
}
let childX = 0;
let childY = 0;
switch(cd.type){
case "desc"://等差
childX = i % 2 == 0 ? ((i / 2) * (cd.desc + cd.width)) : (1 * (cd.width - 20) + (i / 2 - 0.5) * (cd.width+ 46));
childY = i % 2 == 0 ? 0 : 61;
break;
case "spe"://非相等间距
childX = (i == count - 1 ? (i * (cd.width + cd.desc) - cd.desc) : (i * (cd.width + cd.desc))) + 30;
childY = i == count - 1 ? -5 : 0;
break;
case "hor"://水平
childX = i * (cd.gap + cd.width);
childY = 0;
maxX = cd.width;
maxY = cd.height;
break;
case 'other'://非规则其他类型,按预先位置直接赋值即可
break;
case 'circle'://环形
//中心坐标
let centerX = unscaledWidth / 2;
let centerY = unscaledHeight / 2;
let a = centerX - (cd.ww || 0);//X轴
let b = centerY - (cd.hh || 0);//X轴
let sa = cd.sa || 0;//起始角度。
let rad = cd.rad || 2 * Math.PI;
if(rad < Math.PI){//弧形布局
let r = centerX / Math.sin(rad / 2);
a = b = r - (cd.ww || 0);
sa = sa - rad / 2;
centerY = r;
}
//获得角度的大小
let maxa : number = rad < Math.PI ? count - 1 : count;
let angle : number = sa + rad * i / maxa;
//获得圆周点的X坐标
childX : number = centerX + a * Math.sin(angle) - (cd.ww || 0);
childY : number = centerY - b * Math.cos(angle) - (cd.hh || 0);
break;
}
c_c.setLayoutBoundsPosition(childX, childY);
maxX = Math.max(maxX, childX + (cd.ww || 0));
maxY = Math.max(maxY, childY + (cd.hh || 0));
}
this.target.setContentSize(maxX, maxY);
}
}

抽离出来成类,调用时直接new MXCircleLayout()即可,传入预先设定好的参数

1
2
3
4
5
6
let layout = new MXCircleLayout({
"ww": 40 * 1.5,//按实际情况自行设定
"hh": 50 * 1.5,//按实际情况自行设定
"sa": -30 / 180 * Math.PI,//起始位置,默认为12点方向
});
list.layout = layout;

JavaScript踩过的坑---深复制和浅复制

发表于 2017-02-22

深复制和浅复制

JavaScript中针对Object, Array这样的复杂对象的复制都是浅复制,浅复制是对对象地址的复制,也就是说新对象和原对象都是访问同一块内存地址,修改其中一个对象的属性,则另一个对象的属性也会改变。相反,深复制则是开辟新的内存空间,也就是两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。

代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//object深度复制,规避js原有的引用传递
public object_clone(source: Object): Object {
let data: Object = {};
for (let key in source) {
if (source[key] == null) {
continue;
}
if (this.getType(source[key]) == 'object') {
data[key] = this.object_clone(source[key]);
}
else if (this.getType(source[key]) == "array") {
data[key] = this.arr_clone(source[key]);
}
else {
data[key] = source[key];
}
}
return data;
}

//arr深度复制,对所有复杂arr均有效,规避js原有的引用传递
public arr_clone(source) {
let destination: any = [];
for (let key in source) {
let p = parseInt(key);
if (this.getType(source[p]) == "array") {
destination[p] = [];
arguments.callee(destination[p], source[p]);
}
else if (this.getType(source[p]) == "object") {
destination[p] = {};
destination[p] = this.object_clone(source[p]);
}
else {
destination[p] = source[p];
}
}
return destination;
}

private getType(o) {
let _t;
return ((_t = typeof (o)) == "object" ? o == null && "null" || Object.prototype.toString.call(o).slice(8, -1) : _t).toLowerCase();
}

可将此方法写入工具类中,方便使用时调用

Junix

Junix

很惭愧,一点微小的工作

8 日志
3 标签
© 2018 Junix
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4