├── .gitignore ├── .npmignore ├── LICENSE ├── README-zh_CN.md ├── README.md ├── doc ├── QA-zh_CN.md ├── dragonbones_bin_format_1.0-zh_CN.md ├── dragonbones_json_format_5.5-zh_CN.md ├── dragonbones_json_format_5.5.md └── 如何在 Egret 中使用 Live2D 的动画资源.md ├── out └── resource │ ├── helper.html │ ├── player │ ├── alone.html │ ├── index.html │ └── local.html │ └── viewer │ ├── alone.html │ ├── index.html │ └── local.html ├── package.json ├── src ├── action │ ├── formatFormat.ts │ ├── fromLive2D.ts │ ├── fromSpine.ts │ ├── toBinary.ts │ ├── toFormat.ts │ ├── toNew.ts │ ├── toSpine.ts │ └── toWeb.ts ├── common │ ├── nodeUtils.ts │ ├── object.ts │ ├── server.ts │ ├── types.ts │ └── utils.ts ├── convertFrom.ts ├── convertTo.ts ├── format │ ├── dragonBonesFormat.ts │ ├── dragonBonesFormatV23.ts │ ├── geom.ts │ ├── live2DFormat.ts │ ├── resFormat.ts │ ├── spineFormat.ts │ └── utils.ts ├── helper │ ├── helper.ts │ └── helperRemote.ts ├── remote.ts └── test │ └── testDemos.ts ├── tsconfig.json ├── tslint.json └── typings.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | .vscode -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src 3 | backup 4 | .vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 DragonBones 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-zh_CN.md: -------------------------------------------------------------------------------- 1 | # DragonBones tools 2 | [README in English](./README.md) 3 | ## [常见问题](./doc/QA-zh_CN.md) 4 | 5 | ## JSON 格式文档 6 | * [V 5.5](./doc/dragonbones_json_format_5.5-zh_CN.md) 7 | 8 | ## 如何安装 9 | * 安装 [Node.JS](https://nodejs.org/)。 10 | * 安装支持 html5 的浏览器,并且该浏览器是默认浏览器。 11 | * 命令行执行如下命令: 12 | * $ `npm install dragonbones-tools --global` 13 | 14 | ## 帮助 15 | * 使用 `2db` 命令将其他动画格式文件转换为龙骨 JSON 格式文件,使用 `--help` 命令查看 api 帮助。 16 | 17 | $ `2db --help` 18 | * use `db2` 命令将龙骨 JSON 格式文件转换为其他动画格式文件,使用 `--help` 命令查看 api 帮助。 19 | 20 | $ `db2 --help` 21 | 22 | ## 如何使用 23 | * 将当面目录下所有的 Spine JSON 格式文件转换为龙骨 JSON 格式文件。 24 | 25 | $ `2db -t spine` 26 | * 将当面目录下所有的 Live2d JSON 格式文件转换为龙骨 JSON 格式文件。 27 | 28 | $ `2db -t live2d` 29 | * 将当面目录下所有的龙骨 JSON 格式文件转换为最新的龙骨 JSON 格式文件。 30 | 31 | $ `db2 -t new` 32 | * 将当面目录下所有的龙骨 JSON 格式文件转换为 Spine JSON 格式文件。 33 | 34 | $ `db2 -t spine` 35 | * 将当面目录下所有包含 `hero` 关键字的龙骨 JSON 格式文件转换为龙骨二进制格式文件。 36 | 37 | $ `db2 -t binary -f hero` 38 | * 将输入目录所有的龙骨 JSON 格式文件转换为龙骨二进制格式文件并输出到指定目录。 39 | 40 | $ `db2 -t binary -i d:/input -o d:/output -d` 41 | 42 | ## 注意事项 43 | * 请确认在转换文件之前备份原始文件。 44 | 45 | ## 如何编译 46 | * $ `npm install` 47 | * $ `npm install typescript --global` 48 | * $ `tsc` 49 | * $ `npm link` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DragonBones tools 2 | [中文 README](./README-zh_CN.md) 3 | ## JSON format 4 | * [V 5.5](./doc/dragonbones_json_format_5.5.md) 5 | 6 | ## Installation 7 | * Install [Node.JS](https://nodejs.org/). 8 | * Install a browser that supports html5. 9 | * Execute the following command from the command line: 10 | * $ `npm install dragonbones-tools --global` 11 | 12 | ## Help 13 | * use `2db` convert other format files to DragonBones json format files. 14 | 15 | $ `2db --help` 16 | * use `db2` convert DragonBones json format files to other format files. 17 | 18 | $ `db2 --help` 19 | 20 | ## How to use 21 | * Convert Spine json format files to DragonBones json format files in current path. 22 | 23 | $ `2db -t spine` 24 | * Convert Live2d json format files to DragonBones json format files in current path. 25 | 26 | $ `2db -t live2d` 27 | * Convert old DragonBones json format files to new DragonBones json format files in current path. 28 | 29 | $ `db2 -t new` 30 | * Convert DragonBones json format files to Spine json format files in current path. 31 | 32 | $ `db2 -t spine` 33 | * Convert DragonBones json format files (file path contains "hero" key word) to DragonBones binary format files in current path. 34 | 35 | $ `db2 -t binary -f hero` 36 | * Convert DragonBones json format files to DragonBones binary format files from input path to output path and delete raw files. 37 | 38 | $ `db2 -t binary -i d:/input -o d:/output -d` 39 | 40 | ## Notice 41 | * Make sure backup your raw resources before convert. 42 | 43 | ## How to build 44 | * $ `npm install` 45 | * $ `npm install typescript --global` 46 | * $ `tsc` 47 | * $ `npm link` -------------------------------------------------------------------------------- /doc/QA-zh_CN.md: -------------------------------------------------------------------------------- 1 | * 问:龙骨的官网在哪里? 2 | * 答: 3 | * 龙骨官网:http://dragonbones.com/ (仅仅做为官网,维护稍慢) 4 | * github 龙骨官网:https://github.com/DragonBones/ (源码和例子都在这里,维护良好,建议以此为准) 5 | 6 | * 问:为什么 PS 导出插件会报各种莫名其妙的错误? 7 | * 答:建议使用完整版的 PS 而不是各种精简版的 PS。 8 | 9 | * 问:为什么无法导出文件或无法预览? 10 | * 答:确保设备的浏览器是支持 html5 的浏览器。项目名称不可含有特殊符号等,例如空格、小数点。 11 | 12 | * 问:文件、骨架、骨骼、插槽、显示资源、动画的命名有什么规范吗? 13 | * 答:为了不给程序开发带来不必要的麻烦,尽量只使用英文,尽量不要使用奇怪的符号。 14 | 15 | * 问:库的文件结构有什么特殊要求吗? 16 | * 答:某些引擎可能不支持多级文件夹,而找不到贴图,这个时候将贴图资源都放到库的根目录可以解决这类问题。 17 | 18 | * 问:为什么浏览器支持 html5 仍然无法导出文件或无法预览? 19 | * 答:可能由于 DragonBones Pro 的缓存损坏造成的,建议删除缓存文件夹 `C:\Users\{你的用户名}\AppData\Roaming\DragonBonesPro\Local Store`。 20 | 21 | * 问:为什么导入的 Spine 文件不太正确? 22 | * 答:DragonBones Pro 的导入插件有一些兼容问题,可以尝试全新的转换方式 [DragonBones Tools](https://github.com/DragonBones/Tools) ,该转换插件需要依赖 NodeJS 和 npm。转换后的文件可以在 [DragonBones Viewer](https://dbplayer.egret-labs.org/viewer/v1/index.html) 中查看(将转换的文件全选,并拖拽到浏览器窗口即可),这些文件可以在龙骨运行时中良好的运行,但目前还无法在 DragonBones Pro 中运行,DragonBones Pro 有一些兼容问题。 23 | 24 | * 问:为什么修改项目的 library 后,会出现各种错误?比如找不到图片等? 25 | * 答:因为 DragonBonesPro 的 library 功能开发的不够完善,为了保证工程地安全,建议不要使用此功能。 26 | 27 | * 问:为什么我修改了图片名后,可能出现找不到图片的情况? 28 | * 答:因为 DragonBonesPro 的 library 功能开发的不够完善,为了保证工程地安全,建议不要使用此功能。 29 | 30 | * 问:Flash 动画怎么导入 DragonBones Pro? 31 | * 答:可部分参考[这篇文章](http://dragonbones.com/2015/getting_startedV20_cn.htmlhttp://dragonbones.com/2015/getting_startedV20_cn.html),将插件导出的 JSON 导入到 DragonBones Pro 即可。 32 | 33 | * 问:为什么导出的动画贴图在游戏引擎会有黑边? 34 | * 答:Cocos Creater 的[解决办法](http://forum.cocos.com/t/creater-blend-premultiply-alpha/43260/3),Unity 同理。 35 | -------------------------------------------------------------------------------- /doc/dragonbones_bin_format_1.0-zh_CN.md: -------------------------------------------------------------------------------- 1 | # DragonBones 5.1 二进制数据格式标准说明 2 | 3 | ## 4 | * 不需要反序列化 5 | * 运行效率 6 | * 扩展性 7 | * 结构复杂度 8 | * 文件尺寸 9 | 10 | 11 | ## Data Fromat 12 | * [Data Tag](#data_tag) 13 | * [Header](#header) 14 | * [Color](#color) 15 | * [Weights](#weights) 16 | * [Surface](#surface) 17 | * [Mesh](#mesh) 18 | * [Timeline](#timeline) 19 | * [Frame](#frame) 20 | * [Tween Frame](#tween_frame) 21 | * [Action Frame](#action_frame) 22 | * [ZOrder Frame](#zorder_frame) 23 | * [FFD Frame](#ffd_frame) 24 | * [Tween Type](#tween_type) 25 | 26 | 27 |

Data Tag

28 | 29 | Name | Data Type | Size (Bytes) 30 | :---:|:---------:|:-----------: 31 | Tag | Uint32 | 4 32 | Version | Uint32 | 4 33 | | | 34 | 35 | 36 | 37 | 38 | Name | Data Type | Size (Bytes) 39 | :---:|:---------:|:-----------: 40 | Header Length | Uint32 | 4 41 | Header | Uint16 | 2 42 | ... | ... | ... 43 | | | 44 | 45 | ```javascript 46 | { 47 | // DragonBones 数据名称(请区分 DragonBones 数据名称和骨架名称,一个 DragonBones 数据可包含多个骨架) 48 | "name": "dragonBonesName", 49 | // 数据版本 50 | "version": "5.0", 51 | // 最低兼容版本 52 | "compatibleVersion": "4.5", 53 | // 动画帧频 54 | "frameRate": 24, 55 | // 自定义数据 (可选属性 默认: null) 56 | "userData": null, 57 | 58 | // 区块偏移和长度 59 | "offset":[ 60 | IntOffset, length, 61 | FloatOffset, length, 62 | FrameIntOffset, length, 63 | FrameFloatOffset, length, 64 | FrameOffset, length, 65 | TimelineOffset, length, 66 | ColorOffset, length 67 | ], 68 | 69 | "search":{ 70 | "color": 0; 71 | } 72 | 73 | // 骨架列表 74 | "armature": [{ 75 | // 骨架名称 76 | "name": "armatureName", 77 | // 动画帧频 (可选属性 默认: 使用 DragonBones 数据的动画帧频) 78 | "frameRate": 24, 79 | // 骨架类型 (可选属性 默认: "Armature") 80 | // ["Armature": 骨骼动画, "MovieClip": 基本动画, "Stage": 场景动画] 81 | "type": "Armature", 82 | // 自定义数据 (可选属性 默认: null) 83 | "userData": null, 84 | 85 | // 区块偏移 86 | "offset":[IntOffset, FloatOffset, FrameOffset], 87 | 88 | // 骨骼列表 89 | "bone": [{ 90 | // 骨骼名称 91 | "name": "boneName", 92 | // 父级骨骼名称 93 | "parent": "parentBoneName", 94 | // 是否继承移动 (可选属性 默认: true) 95 | "inheritTranslation": true, 96 | // 是否继承旋转 (可选属性 默认: true) 97 | "inheritRotation": true, 98 | // 是否继承缩放 (可选属性 默认: true) 99 | "inheritScale": true, 100 | // 是否继承镜像 (可选属性 默认: true) 101 | "inheritReflection": true, 102 | // 长度 (可选属性 默认: 0.00) 103 | "length": 0.00, 104 | // 自定义数据 (可选属性 默认: null) 105 | "userData": null, 106 | 107 | // 注册到骨架的位移/ 旋转/ 斜切/ 缩放 (可选属性 默认: null) 108 | "transform": { 109 | "x": 0.0000, 110 | "y": 0.0000, 111 | "r": 0.0000, 112 | "sk": 0.0000, 113 | "scX": 1.0000, 114 | "scY": 1.0000 115 | } 116 | }], 117 | 118 | // 插槽列表 119 | "slot": [{ 120 | // 插槽名称 121 | "name": "slotName", 122 | // 插槽所属骨骼名称 123 | "parent": "parentBoneName", 124 | // 插槽默认显示对象 125 | "displayIndex": 0, 126 | // 插槽显示混合模式 127 | "blendMode": null, 128 | // 自定义数据 (可选属性 默认: null) 129 | "userData": null, 130 | // 插槽默认颜色 131 | "color": { 132 | "aM": 100, 133 | "rM": 100, 134 | "gM": 100, 135 | "bM": 100, 136 | "aO": 0, 137 | "rO": 0, 138 | "gO": 0, 139 | "bO": 0, 140 | } 141 | }], 142 | 143 | // 皮肤列表 144 | "skin": [{ 145 | // 皮肤名称 146 | "name": "skinName", 147 | 148 | // 皮肤插槽配置列表 149 | "slot": { 150 | "name": [{ 151 | // 显示对象名称 152 | "name": "displayName", 153 | // 显示对象类型 154 | "type": "image", 155 | 156 | "subType": "rectangle", 157 | // 如果共享网格是否继承 FFD 动画 158 | "inheritFFD": true, 159 | // 数据地址 160 | "offset": 10000, 161 | // 矩形或椭圆的宽高 (可选属性 默认: 0, 仅对边界框有效), 162 | "width": 100, "height": 100 163 | // 164 | "share": "meshName", 165 | // 子骨架指向的骨架名称或网格包含的贴图名称 (可选属性 默认: null, 仅对子骨架、网格有效) 166 | "path": "path", 167 | 168 | // 图片显示对象的轴点 (可选属性 默认: null) 169 | "pivot": { 170 | "x": 0.50, // 水平轴点 [0.00~1.00] (可选属性 默认: 0.50) 171 | "y": 0.50, // 垂直轴点 [0.00~1.00] (可选属性 默认: 0.50) 172 | }, 173 | 174 | // 注册到骨骼的位移/ 旋转/ 斜切/ 缩放 (可选属性 默认: null) 175 | "transform": { 176 | "x": 0.0000, 177 | "y": 0.0000, 178 | "rt": 0.0000, 179 | "sk": 0.0000, 180 | "scX": 1.0000, 181 | "scY": 1.0000 182 | }, 183 | 184 | // 添加到舞台后的默认行为列表 (可选属性 默认: null) 185 | "actions":[{ 186 | "type": "play", 187 | "name": "animationName" 188 | }] 189 | }] 190 | } 191 | }], 192 | 193 | // ik 约束列表 194 | "ik": [{ 195 | // ik 约束名称 196 | "name": "ikName", 197 | // 绑定骨骼名称 198 | "bone": "boneName", 199 | // 目标骨骼名称 200 | "target": "ikBoneName", 201 | // 弯曲方向 (可选属性 默认: true) 202 | // [true: 正方向/ 顺时针, false: 反方向/ 逆时针] 203 | "bendPositive": true, 204 | // 骨骼链的长度 (可选属性 默认: 0) 205 | // [0: 只约束 bone, n: 约束 bone 及 bone 向上 n 级的父骨骼] 206 | "chain": 0, 207 | // 权重 [0.00: 不约束 ~ 1.00: 完全约束] (可选属性 默认: 1.00) 208 | "weight": 1.00 209 | }], 210 | 211 | // 添加到舞台后的默认行为列表 (可选属性 默认: null) 212 | "defaultActions":[{ 213 | "type": "play", 214 | "name": "animationName" 215 | }], 216 | 217 | // 动画列表 218 | "animation": [{ 219 | // 动画名称 220 | "name": "animationName", 221 | // 持续的帧 (可选属性 默认: 1) 222 | "duration": 1, 223 | // 循环播放次数 [0: 循环播放无限次, n: 循环播放 n 次] (可选属性 默认: 1) 224 | "playTimes": 1, 225 | // 动画淡入时间 (以秒为单位,可选属性 默认: 0) 226 | "fadeInTime": 1.00, 227 | // 动画时间的缩放 228 | "scale": 1.00, 229 | // 自定义数据 (可选属性 默认: null) 230 | "userData": null, 231 | 232 | // 行为时间轴地址 233 | "action": 00000, 234 | // 层级时间轴地址 235 | "zOrder": 00001, 236 | 237 | // 区块偏移 238 | "offset":[FrameIntOffset, FrameFloatOffset, FrameOffset], 239 | 240 | "bone": { 241 | "name": [TimelineTag, TimelineOffset, TimelineTag, TimelineOffset, ...] 242 | }, 243 | 244 | "slot": { 245 | "name": [TimelineTag, TimelineOffset, TimelineTag, TimelineOffset, ...] 246 | }, 247 | 248 | "animation": { 249 | "name": [TimelineTag, TimelineOffset, TimelineTag, TimelineOffset, ...] 250 | } 251 | }] 252 | }] 253 | } 254 | ``` 255 | 256 | 257 |

Color (Int Array)

258 | 259 | Name | Data Type | Size (Bytes) | Value range 260 | :---:|:---------:|:------------:|:----------: 261 | Alpha Multiplier | Int16 | 2 | 0 ~ 100 262 | Red Multiplier | Int16 | 2 | 0 ~ 100 263 | Greed Multiplier | Int16 | 2 | 0 ~ 100 264 | Blue Multiplier | Int16 | 2 | 0 ~ 100 265 | Alpha Offset | Int16 | 2 | -256 ~ 256 266 | Red Offset | Int16 | 2 | -256 ~ 256 267 | Greed Offset | Int16 | 2 | -256 ~ 256 268 | Blue Offset | Int16 | 2 | -256 ~ 256 269 | | | 270 | 271 | 272 |

Weights (Int Array)

273 | 274 | Name | Data Type | Size (Bytes) | Value range 275 | :---:|:---------:|:------------:|:-----------: 276 | Bone Count | Int16 | 2 | 277 | Float Array Offset | Int16 | 2 | 278 | Bone Indices | Int16 | 2 | 279 | ... | ... | ... 280 | Vertex Bone Count, Vertex Bone Indices | Int16 | 2 | 3, 4, 7, 6 281 | ... | ... | ... 282 | | | 283 | Weight, X, Y (Float Array) | Float32 | 4 | 1.0, 12.3, 45.6 284 | ... | ... | ... 285 | | | 286 | 287 | 288 |

Surface (Int Array)

289 | 290 | Name | Data Type | Size (Bytes) | Value range 291 | :---:|:---------:|:------------:|:-----------: 292 | Vertex Count | Int16 | 2 | 293 | Empty | Int16 | 2 | 0 294 | Float Array Offset | Int16 | 2 | 295 | Empty | Int16 | 2 | -1 296 | | | 297 | Vertices (Float Array) | Float32 | 4 | 298 | ... | ... | ... 299 | | | 300 | 301 | 302 |

Mesh (Int Array)

303 | 304 | Name | Data Type | Size (Bytes) | Value range 305 | :---:|:---------:|:------------:|:-----------: 306 | Vertex Count | Int16 | 2 | 307 | Triangle Count | Int16 | 2 | 308 | Float Array Offset | Int16 | 2 | 309 | Weight Offset | Int16 | 2 | -1: No Weights, N: [Weights](#weidhts) Offset 310 | Vertex indices | Int16 | 2 | 311 | ... | ... | ... 312 | | | 313 | Vertices (Float Array) | Float32 | 4 | 314 | ... | ... | ... 315 | UVs (Float Array) | Float32 | 4 | 316 | ... | ... | ... 317 | | | 318 | 319 | 320 |

Path / Polygon BoundingBox (Int Array)

321 | 322 | Name | Data Type | Size (Bytes) | Value range 323 | :---:|:---------:|:------------:|:-----------: 324 | Vertex Count | Int16 | 2 | 325 | Empty | Int16 | 2 | 0 326 | Float Array Offset | Int16 | 2 | 327 | Weight Offset | Int16 | 2 | -1: No Weights, N: [Weights](#weidhts) Offset 328 | | | 329 | Vertices (Float Array) | Float32 | 4 | 330 | ... | ... | ... 331 | | | 332 | 333 | 334 |

Timeline (Uint Array)

335 | 336 | Name | Data Type | Size (Bytes) 337 | :---:|:---------:|:-----------: 338 | Scale | Uint16 | 2 339 | Offset | Uint16 | 2 340 | Key Frame Count | Uint16 | 2 341 | Frame Value Count or Value Offset (Int Array or Float Array) | Uint16 | 2 342 | Frame Value Offset (Frame Int Array or Frame Float Array) | Uint16 | 2 343 | Frame Array Offsets | Uint16 | 2 344 | ... | ... | ... 345 | | | 346 | 347 | 348 |

Frame (Frame Array)

349 | 350 | Name | Data Type | Size (Bytes) 351 | :---:|:---------:|:-----------: 352 | Position | Int16 | 2 353 | | | 354 | 355 | 356 |

Tween Frame (Frame Array)

357 | 358 | Name | Data Type | Size (Bytes) | Value range 359 | :---:|:---------:|:------------:|:----------: 360 | Tween Type | Int16 | 2 | [Tween Type](#tween_type) 361 | Tween Easing or Curve Sample Count | Int16 | 2 | 0 ~ 100 or Count 362 | Curve Samples | Int16 | 2 363 | ... | ... | ... 364 | | | 365 | 366 | 367 |

Action Frame (Frame Array)

368 | 369 | Name | Data Type | Size (Bytes) 370 | :---:|:---------:|:-----------: 371 | Action Count | Int16 | 2 372 | Action Indices | Int16 | 2 373 | ... | ... | ... 374 | | | 375 | 376 | 377 |

ZOrder Frame (Frame Array)

378 | 379 | Name | Data Type | Size (Bytes) 380 | :---:|:---------:|:-----------: 381 | ZOrder Count | Int16 | 2 382 | ZOrders | Int16 | 2 383 | ... | ... | ... 384 | | | 385 | 386 | 387 |

Tween Type

388 | 389 | Value | Type 390 | :----:|:---: 391 | 0 | None 392 | 1 | Linear 393 | 2 | Curve 394 | 3 | EaseInQuad 395 | 4 | EaseOutQuad 396 | 5 | EaseInOutQuad 397 | ... | ... 398 | | -------------------------------------------------------------------------------- /doc/dragonbones_json_format_5.5-zh_CN.md: -------------------------------------------------------------------------------- 1 | # DragonBones 5.5 JSON 数据格式 2 | [English](./dragonbones_json_format_5.5.md) 3 | 4 | ```javascript 5 | { 6 | // 龙骨数据名称。 7 | "name": "dragonBonesName", 8 | 9 | // 数据版本。 10 | "version": "5.5", 11 | 12 | // 数据兼容的最低版本。 13 | "compatibleVersion": "5.5", 14 | 15 | // 动画帧率。 (可选属性,默认:24) 16 | "frameRate": 24, 17 | 18 | // 自定义数据。 (可选属性,默认:null) 19 | "userData": null, 20 | 21 | // 骨架列表。 (可选属性,默认:null) 22 | "armature": [{ 23 | 24 | // 骨架名称。 25 | "name": "armatureName", 26 | 27 | // 动画帧率。 (可选属性,默认:null) 28 | // [null: 使用龙骨数据的帧率, N: 动画帧率] 29 | "frameRate": 24, 30 | 31 | // 非必要。 32 | "type": "Armature", 33 | 34 | // 自定义数据。 (可选属性,默认:null) 35 | "userData": null, 36 | 37 | // 当该骨架做为子骨架加入到父骨架时的默认行为列表。 (可选属性,默认:null) 38 | "defaultActions": [ 39 | { 40 | "gotoAndPlay": "animationName" 41 | } 42 | ], 43 | 44 | // 骨骼列表。 (可选属性,默认:null) 45 | "bone": [{ 46 | 47 | // 骨骼名称。 48 | "name": "boneName", 49 | 50 | // 父骨骼名称。 (可选属性,默认:null) 51 | "parent": "parentBoneName", 52 | 53 | // 自定义数据。 (可选属性,默认:null) 54 | "userData": null, 55 | 56 | // 该骨骼相对与父骨骼或骨架的基础变换。 (可选属性,默认:null) 57 | "transform": { 58 | "x": 0.0, // 水平位移。 (可选属性,默认:0.0) 59 | "y": 0.0, // 垂直位移。 (可选属性,默认:0.0) 60 | "skX": 0.0, // 水平倾斜。 (可选属性,默认:0.0) 61 | "skY": 0.0, // 垂直倾斜。 (可选属性,默认:0.0) 62 | "scX": 1.0, // 水平缩放。 (可选属性,默认:1.0) 63 | "scY": 1.0 // 垂直缩放。 (可选属性,默认:1.0) 64 | } 65 | }], 66 | 67 | // 插槽列表。 68 | "slot": [{ 69 | 70 | // 插槽名称。 71 | "name": "slotName", 72 | 73 | // 父骨骼名称。 74 | "parent": "parentBoneName", 75 | 76 | // 默认的显示对象索引。 (可选属性,默认:0) 77 | "displayIndex": 0, 78 | 79 | // 混合模式。 (可选属性,默认:null) 80 | "blendMode": null, 81 | 82 | // 自定义数据。 (可选属性,默认:null) 83 | "userData": null, 84 | 85 | // 颜色变换。 (可选属性,默认:null) 86 | "color": { 87 | "aM": 100, // 透明相乘因子。 [0~100] (可选属性,默认:100) 88 | "rM": 100, // 红色相乘因子。 [0~100] (可选属性,默认:100) 89 | "gM": 100, // 绿色相乘因子。 [0~100] (可选属性,默认:100) 90 | "bM": 100, // 蓝色相乘因子。 [0~100] (可选属性,默认:100) 91 | "aO": 0, // 透明偏移。 [-255~255] (可选属性,默认:0) 92 | "rO": 0, // 红色偏移。 [-255~255] (可选属性,默认:0) 93 | "gO": 0, // 绿色偏移。 [-255~255] (可选属性,默认:0) 94 | "bO": 0, // 蓝色偏移。 [-255~255] (可选属性,默认:0) 95 | } 96 | }], 97 | 98 | // 皮肤列表。 99 | "skin": [{ 100 | 101 | // 皮肤名称。 102 | "name": "skinName", 103 | 104 | // 插槽列表。 105 | "slot": [{ 106 | 107 | // 插槽名称。 108 | "name": "slotName", 109 | 110 | // 显示对象列表。 111 | "display": [{ 112 | 113 | // 显示对象名称。 114 | "name": "displayName", 115 | 116 | // 显示对象类型。 (可选属性,默认:"image") 117 | // ["image": 贴图的矩形, "armature": 嵌套的子骨架, "mesh": 贴图的网格, "boundingBox": 自定义边界框] 118 | "type": "image", 119 | 120 | // 显示对象的资源路径。 (可选属性,默认:null) 121 | "path": null, 122 | 123 | // 共享网格的名称。 (可选属性,默认:null) 124 | "share": "meshName", 125 | 126 | // 是否继承共享网格的形变动画。 (可选属性,默认:true) 127 | "inheritDeform": true, 128 | 129 | // 显示对象的子类型。 130 | // 如果是边界框: (可选属性,默认:"rectangle") 131 | // ["rectangle": 矩形边界框, "ellipse": 椭圆边界框, "polygon": 多边形边界框] 132 | "subType": "rectangle", 133 | 134 | // 非必要。 135 | "color": 0x000000, 136 | 137 | // 相对于骨骼的变换。 (可选属性,默认:null) 138 | "transform": { 139 | "x": 0.0, // 水平位移。 (可选属性,默认:0.0) 140 | "y": 0.0, // 垂直位移。 (可选属性,默认:0.0) 141 | "skX": 0.0, // 水平倾斜。 (可选属性,默认:0.0) 142 | "skY": 0.0, // 垂直倾斜。 (可选属性,默认:0.0) 143 | "scX": 1.0, // 水平缩放。 (可选属性,默认:1.0) 144 | "scY": 1.0 // 垂直缩放。 (可选属性,默认:1.0) 145 | }, 146 | 147 | // 相对轴点。 (可选属性,默认:null) 148 | "pivot": { 149 | "x": 0.5, // 水平位移。 [0.0~1.0] (可选属性,默认:0.5) 150 | "y": 0.5, // 垂直位移。 [0.0~1.0] (可选属性,默认:0.5) 151 | }, 152 | 153 | // 显示对象的尺寸。 (仅对边界框有效) 154 | "width": 100, 155 | "height": 100, 156 | 157 | "vertices": [-64.0, -64.0, 64.0, -64.0, 64.0, 64.0, -64.0, 64.0], 158 | 159 | "uvs": [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0], 160 | 161 | "triangles": [0, 1, 2, 2, 3, 0], 162 | 163 | "weights": [1, 0, 1.0, 2, 0, 0.5, 1, 0.5], 164 | 165 | "slotPose": [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], 166 | 167 | "bonePose": [0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0], 168 | 169 | // 替换子骨架的默认行为列表。 (可选属性,默认:null) 170 | "actions": [ 171 | { 172 | "gotoAndPlay": "animationName" 173 | } 174 | ] 175 | }] 176 | }] 177 | }], 178 | 179 | // IK 约束列表。 180 | "ik": [{ 181 | 182 | // IK 约束名称。 183 | "name": "ikContaintName", 184 | 185 | // 约束骨骼的名称。 186 | "bone": "boneName", 187 | 188 | // 约束目标骨骼的名称 189 | "target": "ikBoneName", 190 | 191 | // IK 约束的方向。 (可选属性,默认:true) 192 | // [true: 正方向 / 顺时针, false: 反方向 / 逆时针] 193 | "bendPositive": true, 194 | 195 | // 被约束的骨骼链长度。 196 | // [0: 至约束骨骼, N: 该骨骼和向上 N 级的父骨骼] (可选属性,默认:0) 197 | "chain": 0, 198 | 199 | // 约束权重。 [0.0~1.0] (可选属性,默认:1.0) 200 | "weight": 1.0 201 | }], 202 | 203 | // 动画列表。 204 | "animation": [{ 205 | 206 | // 动画名称。 207 | "name": "animationName", 208 | 209 | // 动画播放次数。 [0: 循环播放, N: 播放 N 次] (可选属性,默认:1) 210 | "playTimes": 1, 211 | 212 | // 动画长度。 (可选属性,默认:1) 213 | "duration": 1, 214 | 215 | // 行为关键帧列表。 (可选属性,默认:null) 216 | "frame": [{ 217 | 218 | // 关键帧长度。 (可选属性,默认:1) 219 | "duration": 1, 220 | 221 | // 该帧的行为列表。 (可选属性,默认:null) 222 | "actions": [{ 223 | 224 | // 行为类型。 [0: 播放动画, 10: 帧事件, 11: 帧声音事件] (可选属性,默认:0) 225 | "type": 0, 226 | 227 | // 行为的名称。 (动画名称或事件名称) 228 | "name": "actionName", 229 | 230 | // 骨骼名称。 (可选属性,默认:null) 231 | "bone": "boneName", 232 | 233 | // 插槽名称。 (可选属性,默认:null) 234 | "slot": "slotName", 235 | 236 | // 自定义数据列表。 (可选属性,默认:null) 237 | "ints":[0, 1, 2], 238 | "floats":[0.01, 1.01, 2.01], 239 | "strings":["a", "b", "c"] 240 | }] 241 | }], 242 | 243 | // z 排序时间轴。 244 | "zOrder": { 245 | "frame": [{ 246 | 247 | // 关键帧长度。 (可选属性,默认:1) 248 | "duration": 1, 249 | 250 | // 该帧插槽 z 排序偏移列表。 [slotIndexA, offsetA, slotIndexB, offsetB, ...] (可选属性,默认:null) 251 | "zOrder": [0, 2, 4, 1, 6, -1] 252 | }] 253 | }, 254 | 255 | // 骨骼时间轴列表。 256 | "bone": [{ 257 | 258 | // 骨骼名称。 259 | "name": "boneName", 260 | 261 | // 时间轴缩放。 (可选属性,默认:0.0) 262 | "scale": 1.0, 263 | 264 | // 时间轴偏移。 (可选属性,默认:0.0) 265 | "offset": 0.0, 266 | 267 | // 位移关键帧列表。 (可选属性,默认:null) 268 | "translateFrame": [{ 269 | 270 | // 关键帧长度。 (可选属性,默认:1) 271 | "duration": 1, 272 | 273 | // 补间类型 [0.0: 线形, null: 不补间]. (可选属性,默认:0) 274 | "tweenEasing": 0.0, 275 | 276 | // 补间贝塞尔曲线。 [x1, y1, x2, y2, ...] (可选属性,默认:null) 277 | "curve": [0.0, 0.0, 1.0, 1.0], 278 | 279 | // 该帧骨骼的水平位移。 (可选属性,默认:0.0) 280 | "x": 0.0, 281 | 282 | // 该帧骨骼的垂直位移。 (可选属性,默认:0.0) 283 | "y": 0.00, 284 | }], 285 | 286 | // 旋转关键帧列表。 (可选属性,默认:null) 287 | "rotateFrame": [{ 288 | 289 | // 关键帧长度。 (可选属性,默认:1) 290 | "duration": 1, 291 | 292 | // 补间类型 [0.0: 线形, null: 不补间]. (可选属性,默认:0) 293 | "tweenEasing": 0.0, 294 | 295 | // 补间贝塞尔曲线。 [x1, y1, x2, y2, ...] (可选属性,默认:null) 296 | "curve": [0.0, 0.0, 1.0, 1.0], 297 | 298 | // 补间的旋转方式。 (可选属性,默认:0) 299 | // [0: 最小角度旋转, 1: 顺时针旋转, -1: 逆时针旋转, N: 至少顺时针旋转 N 圈, -N: 至少逆时针旋转 N 圈] 300 | "clockwise": 0, 301 | 302 | // 该帧骨骼的旋转。 [-PI ~ PI] (可选属性,默认:0.0) 303 | "rotate": 0.0, 304 | 305 | // 该帧骨骼的倾斜。 [-PI ~ PI] (可选属性,默认:0.0) 306 | "skew": 0.0 307 | }], 308 | 309 | // 缩放关键帧列表。 (可选属性,默认:null) 310 | "scaleFrame": [{ 311 | 312 | // 关键帧长度。 (可选属性,默认:1) 313 | "duration": 1, 314 | 315 | // 补间类型 [0.0: 线形, null: 不补间]. (可选属性,默认:0) 316 | "tweenEasing": 0.0, 317 | 318 | // 补间贝塞尔曲线。 [x1, y1, x2, y2, ...] (可选属性,默认:null) 319 | "curve": [0.0, 0.0, 1.0, 1.0], 320 | 321 | // 该帧骨骼的水平缩放。 (可选属性,默认:1.0) 322 | "x": 1.0, 323 | 324 | // 该帧骨骼的垂直缩放。 (可选属性,默认:1.0) 325 | "y": 1.0 326 | }] 327 | }], 328 | 329 | // 插槽时间轴列表。 330 | "slot": [{ 331 | 332 | // 时间轴名称。 333 | "name": "slotName", 334 | 335 | // 显示关键帧列表。 (可选属性,默认:null) 336 | "displayFrame": [{ 337 | 338 | // 关键帧长度。 (可选属性,默认:1) 339 | "duration": 1, 340 | 341 | // 该帧插槽的显示对象索引。 (可选属性,默认:1) 342 | "value": 0, 343 | 344 | // 该帧嵌套子骨架的行为列表。 (可选属性,默认:null) 345 | "actions": [ 346 | { 347 | "gotoAndPlay": "animationName" 348 | } 349 | ] 350 | }], 351 | 352 | // 颜色关键帧列表。 (可选属性,默认:null) 353 | "colorFrame": [{ 354 | 355 | // 关键帧长度。 (可选属性,默认:1) 356 | "duration": 1, 357 | 358 | // 补间类型 [0.0: 线形, null: 不补间]. (可选属性,默认:0) 359 | "tweenEasing": 0.0, 360 | 361 | // 补间贝塞尔曲线。 [x1, y1, x2, y2, ...] (可选属性,默认:null) 362 | "curve": [0.0, 0.0, 1.0, 1.0], 363 | 364 | // 该帧插槽的颜色变换。 (可选属性,默认:null) 365 | "color": { 366 | "aM": 100, // 透明相乘因子。 [0~100] (可选属性,默认:100) 367 | "rM": 100, // 红色相乘因子。 [0~100] (可选属性,默认:100) 368 | "gM": 100, // 绿色相乘因子。 [0~100] (可选属性,默认:100) 369 | "bM": 100, // 蓝色相乘因子。 [0~100] (可选属性,默认:100) 370 | "aO": 0, // 透明偏移。 [-255~255] (可选属性,默认:0) 371 | "rO": 0, // 红色偏移。 [-255~255] (可选属性,默认:0) 372 | "gO": 0, // 绿色偏移。 [-255~255] (可选属性,默认:0) 373 | "bO": 0, // 蓝色偏移。 [-255~255] (可选属性,默认:0) 374 | } 375 | }] 376 | }], 377 | 378 | // FFD 时间轴列表。(可选属性,默认:null) 379 | "ffd": [{ 380 | 381 | // 网格名称。 382 | "name": "meshName", 383 | 384 | // 皮肤名称。 385 | "skin": "skinName", 386 | 387 | // 插槽名称。 388 | "slot": "slotName", 389 | 390 | "frame": [{ 391 | 392 | // 关键帧长度。 (可选属性,默认:1) 393 | "duration": 1, 394 | 395 | // 补间类型 [0.0: 线形, null: 不补间]. (可选属性,默认:0) 396 | "tweenEasing": 0.0, 397 | 398 | // 补间贝塞尔曲线。 [x1, y1, x2, y2, ...] (可选属性,默认:null) 399 | "curve": [0.0, 0.0, 1.0, 1.0], 400 | 401 | // 变形顶点列表的索引偏移,偏移之前的数据都是 0。 (可选属性,默认:0) 402 | "offset": 0, 403 | 404 | // 变形顶点列表,队尾为 0 的数据会别省略。 405 | // [x0, y0, x1, y1, ...] (可选属性,默认:null) 406 | "vertices": [0.1, 0.1] 407 | }] 408 | }], 409 | 410 | // IK 约束时间轴。 (可选属性,默认:null) 411 | "ik": [{ 412 | 413 | // IK 约束名称。 414 | "name": "meshName", 415 | 416 | "frame": [{ 417 | 418 | // 关键帧长度。 (可选属性,默认:1) 419 | "duration": 1, 420 | 421 | // 补间类型 [0.0: 线形, null: 不补间]. (可选属性,默认:0) 422 | "tweenEasing": 0.0, 423 | 424 | // 补间贝塞尔曲线。 [x1, y1, x2, y2, ...] (可选属性,默认:null) 425 | "curve": [0.0, 0.0, 1.0, 1.0], 426 | 427 | // 该帧 IK 约束的方向。 (可选属性,默认:true) 428 | "bendPositive": true, 429 | 430 | // 该帧 IK 约束的权重。 (可选属性,默认:1.0) 431 | "weight": 1.0 432 | }] 433 | }] 434 | }] 435 | }] 436 | } 437 | ``` -------------------------------------------------------------------------------- /doc/dragonbones_json_format_5.5.md: -------------------------------------------------------------------------------- 1 | # DragonBones 5.5 JSON format 2 | [中文](./dragonbones_json_format_5.5-zh_CN.md) 3 | 4 | ```javascript 5 | { 6 | // The name of the DragonBones data. 7 | "name": "dragonBonesName", 8 | 9 | // The version of the DragonBones data. 10 | "version": "5.5", 11 | 12 | // The minimum compatible version of the DragonBones data. 13 | "compatibleVersion": "5.5", 14 | 15 | // The frame rate of animations. (Optional property, default: 24) 16 | "frameRate": 24, 17 | 18 | // The custom user data. (Optional property, default: null) 19 | "userData": null, 20 | 21 | // A list of the armatures. (Optional property, default: null) 22 | "armature": [{ 23 | 24 | // The name of the armature. 25 | "name": "armatureName", 26 | 27 | // The frame rate of animations. (Optional property, default: null) 28 | // [null: Same as the frame rate of the DragonBones data, N: The frame rate.] 29 | "frameRate": 24, 30 | 31 | // Nonessential. 32 | "type": "Armature", 33 | 34 | // The custom user data. (Optional property, default: null) 35 | "userData": null, 36 | 37 | // A list of default actions when added to a parent armature. (Optional property, default: null) 38 | "defaultActions": [ 39 | { 40 | "gotoAndPlay": "animationName" 41 | } 42 | ], 43 | 44 | // A list of the bones. (Optional property, default: null) 45 | "bone": [{ 46 | 47 | // The name of the bone. 48 | "name": "boneName", 49 | 50 | // The name of the parent bone. (Optional property, default: null) 51 | "parent": "parentBoneName", 52 | 53 | // The custom user data. (Optional property, default: null) 54 | "userData": null, 55 | 56 | // The transform of the bone relative to the parent bone or the armature for the base pose. 57 | // (Optional property, default: null) 58 | "transform": { 59 | "x": 0.0, // The horizontal translate. (Optional property, default: 0.0) 60 | "y": 0.0, // The vertical translate. (Optional property, default: 0.0) 61 | "skX": 0.0, // The horizontal skew. (Optional property, default: 0.0) 62 | "skY": 0.0, // The vertical skew. (Optional property, default: 0.0) 63 | "scX": 1.0, // The horizontal scale. (Optional property, default: 1.0) 64 | "scY": 1.0 // The vertical scale. (Optional property, default: 1.0) 65 | } 66 | }], 67 | 68 | // A list of the slots. 69 | "slot": [{ 70 | 71 | // The name of the slot. 72 | "name": "slotName", 73 | 74 | // The name of the parent bone. 75 | "parent": "parentBoneName", 76 | 77 | // The default display index of the slot. (Optional property, default: 0) 78 | "displayIndex": 0, 79 | 80 | // The blend mode of the slot. (Optional property, default: null) 81 | "blendMode": null, 82 | 83 | // The custom user data. (Optional property, default: null) 84 | "userData": null, 85 | 86 | // The color transform of the slot. (Optional property, default: null) 87 | "color": { 88 | "aM": 100, // The alpha multiplier. [0~100] (Optional property, default: 100) 89 | "rM": 100, // The red multiplier. [0~100] (Optional property, default: 100) 90 | "gM": 100, // The green multiplier. [0~100] (Optional property, default: 100) 91 | "bM": 100, // The blue multiplier. [0~100] (Optional property, default: 100) 92 | "aO": 0, // The alpha offset. [-255~255] (Optional property, default: 0) 93 | "rO": 0, // The red offset. [-255~255] (Optional property, default: 0) 94 | "gO": 0, // The green offset. [-255~255] (Optional property, default: 0) 95 | "bO": 0, // The blue offset. [-255~255] (Optional property, default: 0) 96 | } 97 | }], 98 | 99 | // A list of the skins. 100 | "skin": [{ 101 | 102 | // The name of the skin. 103 | "name": "skinName", 104 | 105 | // A list of the slots. 106 | "slot": [{ 107 | 108 | // The name of the slot. 109 | "name": "slotName", 110 | 111 | // A list of the displays. 112 | "display": [{ 113 | 114 | // The name of the display. 115 | "name": "displayName", 116 | 117 | // The type of the display. (Optional property, default: "image") 118 | // [ 119 | // "image": A textured rectangle, 120 | // "armature": A nested child armature, 121 | // "mesh": A textured mesh, 122 | // "boundingBox": A bounding box 123 | // ] 124 | "type": "image", 125 | 126 | // The resource path of the display. (Optional property, default: null) 127 | "path": null, 128 | 129 | // The name of the shared mesh. (Optional property, default: null) 130 | "share": "meshName", 131 | 132 | // Whether to inherit the deform animations of the shared mesh. (Optional property, default: true) 133 | "inheritDeform": true, 134 | 135 | // The sub type of the display. 136 | // If the display is a bounding box: (Optional property, default: "rectangle") 137 | // ["rectangle": A rectangle, "ellipse": An ellipse, "polygon": A pllygon] 138 | "subType": "rectangle", 139 | 140 | // Nonessential. 141 | "color": 0x000000, 142 | 143 | // The transform of the display relative to the slot's bone. (Optional property, default: null) 144 | "transform": { 145 | "x": 0.0, // The horizontal translate. (Optional property, default: 0.0) 146 | "y": 0.0, // The vertical translate. (Optional property, default: 0.0) 147 | "skX": 0.0, // The horizontal skew. (Optional property, default: 0.0) 148 | "skY": 0.0, // The vertical skew. (Optional property, default: 0.0) 149 | "scX": 1.0, // The horizontal scale. (Optional property, default: 1.0) 150 | "scY": 1.0 // The vertical scale. (Optional property, default: 1.0) 151 | }, 152 | 153 | // The relative pivot of the display. (Optional property, default: null) 154 | "pivot": { 155 | "x": 0.5, // The horizontal translate. [0.0~1.0] (Optional property, default: 0.5) 156 | "y": 0.5, // The vertical translate. [0.0~1.0] (Optional property, default: 0.5) 157 | }, 158 | 159 | // The size of display. (Valid for bounding box only) 160 | "width": 100, 161 | "height": 100, 162 | 163 | "vertices": [-64.0, -64.0, 64.0, -64.0, 64.0, 64.0, -64.0, 64.0], 164 | 165 | "uvs": [0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0], 166 | 167 | "triangles": [0, 1, 2, 2, 3, 0], 168 | 169 | "weights": [1, 0, 1.0, 2, 0, 0.5, 1, 0.5], 170 | 171 | "slotPose": [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], 172 | 173 | "bonePose": [0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0], 174 | 175 | // Override the default actions of the nested child armature. (Optional property, default: null) 176 | "actions": [ 177 | { 178 | "gotoAndPlay": "animationName" 179 | } 180 | ] 181 | }] 182 | }] 183 | }], 184 | 185 | // A list of the IK constraints. 186 | "ik": [{ 187 | 188 | // The name of the IK constraint. 189 | "name": "ikName", 190 | 191 | // The name of the bone. 192 | "bone": "boneName", 193 | 194 | // The name of the target bone. 195 | "target": "ikBoneName", 196 | 197 | // The IK constraint bend direction. (Optional property, default: true) 198 | // [true: Positive direction / Clockwise, false: Reverse Direction / Counterclockwise] 199 | "bendPositive": true, 200 | 201 | // The bone count of the bone chain in the constraint. 202 | // [0: Only the bone, N: The bone and the bone up N-level parent bones] (Optional property, default: 0) 203 | "chain": 0, 204 | 205 | // The weight of the IK constraint. [0.0~1.0] (Optional property, default: 1.0) 206 | "weight": 1.0 207 | }], 208 | 209 | // A list of the animations. 210 | "animation": [{ 211 | 212 | // The name of animation. 213 | "name": "animationName", 214 | 215 | // The play times of the animation. [0: Loop play, N: Play N times] (Optional property, default: 1) 216 | "playTimes": 1, 217 | 218 | // The duration of the animation. (Optional property, default: 1) 219 | "duration": 1, 220 | 221 | // A list of the action keyframes. (Optional property, default: null) 222 | "frame": [{ 223 | 224 | // The duration of the frame. (Optional property, default: 1) 225 | "duration": 1, 226 | 227 | // A list of actions. (Optional property, default: null) 228 | "actions": [{ 229 | 230 | // The type of the action. (Optional property, default: 0) 231 | // [0: Play animation, 10: Frame event, 11: Frame sound event] 232 | "type": 0, 233 | 234 | // The name of the action. (The name of a animation or an event) 235 | "name": "actionName", 236 | 237 | // A bone name. (Optional property, default: null) 238 | "bone": "boneName", 239 | 240 | // A slot name. (Optional property, default: null) 241 | "slot": "slotName", 242 | 243 | // The list of custom data. (Optional property, default: null) 244 | "ints":[0, 1, 2], 245 | "floats":[0.01, 1.01, 2.01], 246 | "strings":["a", "b", "c"] 247 | }] 248 | }], 249 | 250 | // The z order timeline. 251 | "zOrder": { 252 | "frame": [{ 253 | 254 | // The duration of the frame. (Optional property, default: 1) 255 | "duration": 1, 256 | 257 | // A list of slot indeices and numeric offsets. [slotIndexA, offsetA, slotIndexB, offsetB, ...] 258 | // (Optional property, default: null) 259 | "zOrder": [0, 2, 4, 1, 6, -1] 260 | }] 261 | }, 262 | 263 | // A list of the bone timelines. 264 | "bone": [{ 265 | 266 | // The name of the bone. 267 | "name": "boneName", 268 | 269 | // The scale of the timeline. (Optional property, default: 0.0) 270 | "scale": 1.0, 271 | 272 | // The offset of the timeline. (Optional property, default: 0.0) 273 | "offset": 0.0, 274 | 275 | // A list of the translate keyframes. (Optional property, default: null) 276 | "translateFrame": [{ 277 | 278 | // The duration of the frame. (Optional property, default: 1) 279 | "duration": 1, 280 | 281 | // The tween easing of the frame. [0.0: Linear, null: No easing]. (Optional property, default: 0) 282 | "tweenEasing": 0.0, 283 | 284 | // The interpolation to use between this and the next keyframe. [x1, y1, x2, y2, ...] 285 | // (Optional property, default: null) 286 | "curve": [0.0, 0.0, 1.0, 1.0], 287 | 288 | // The horizontal translate of a bone in the keyframe. (Optional property, default: 0.0) 289 | "x": 0.0, 290 | 291 | // The vertical translate of a bone in the keyframe. (Optional property, default: 0.0) 292 | "y": 0.00, 293 | }], 294 | 295 | // A list of the rotate keyframes. (Optional property, default: null) 296 | "rotateFrame": [{ 297 | 298 | // The duration of the frame. (Optional property, default: 1) 299 | "duration": 1, 300 | 301 | // The tween easing of the frame. [0.0: Linear, null: No easing]. (Optional property, default: 0) 302 | "tweenEasing": 0.0, 303 | 304 | // The interpolation to use between this and the next keyframe. [x1, y1, x2, y2, ...] 305 | // (Optional property, default: null) 306 | "curve": [0.0, 0.0, 1.0, 1.0], 307 | 308 | // The rotation behavior during a tween. (Optional property, default: 0) 309 | // [ 310 | // 0: Chooses a direction of rotation that requires the least amount of turning, 311 | // 1: Rotates clockwise, 312 | // -1: Rotates counterclockwise, 313 | // N: Rotates clockwise at least N-rings, 314 | // -N: Rotates counterclockwise at least N-rings 315 | // ] 316 | "clockwise": 0, 317 | 318 | // The rotation of a bone in the keyframe. [-PI ~ PI] (Optional property, default: 0.0) 319 | "rotate": 0.0, 320 | 321 | // The skew of a bone in the keyframe. [-PI ~ PI] (Optional property, default: 0.0) 322 | "skew": 0.0 323 | }], 324 | 325 | // A list of the scale keyframes. (Optional property, default: null) 326 | "scaleFrame": [{ 327 | 328 | // The duration of the frame. (Optional property, default: 1) 329 | "duration": 1, 330 | 331 | // The tween easing of the frame. [0.0: Linear, null: No easing]. (Optional property, default: 0) 332 | "tweenEasing": 0.0, 333 | 334 | // The interpolation to use between this and the next keyframe. [x1, y1, x2, y2, ...] 335 | // (Optional property, default: null) 336 | "curve": [0.0, 0.0, 1.0, 1.0], 337 | 338 | // The horizontal scale of a bone in the keyframe. (Optional property, default: 1.0) 339 | "x": 1.0, 340 | 341 | // The vertical scale of a bone in the keyframe. (Optional property, default: 1.0) 342 | "y": 1.0 343 | }] 344 | }], 345 | 346 | // A list of the slot timelines. 347 | "slot": [{ 348 | 349 | // The name of the slot. 350 | "name": "slotName", 351 | 352 | // A list of the display keyframes. (Optional property, default: null) 353 | "displayFrame": [{ 354 | 355 | // The duration of the frame. (Optional property, default: 1) 356 | "duration": 1, 357 | 358 | // The display index of a slot in the keyframe. (Optional property, default: 1) 359 | "value": 0, 360 | 361 | // The actions of a slot in the keyframe. (Optional property, default: null) 362 | "actions": [ 363 | { 364 | "gotoAndPlay": "animationName" 365 | } 366 | ] 367 | }], 368 | 369 | // A list of the color keyframes. (Optional property, default: null) 370 | "colorFrame": [{ 371 | 372 | // The duration of the frame. (Optional property, default: 1) 373 | "duration": 1, 374 | 375 | // The tween easing of the frame. [0.0: Linear, null: No easing]. (Optional property, default: 0) 376 | "tweenEasing": 0.0, 377 | 378 | // The interpolation to use between this and the next keyframe. [x1, y1, x2, y2, ...] 379 | // (Optional property, default: null) 380 | "curve": [0.0, 0.0, 1.0, 1.0], 381 | 382 | // The color transform of a slot in the frame. (Optional property, default: null) 383 | "color": { 384 | "aM": 100, // The alpha multiplier. [0~100] (Optional property, default: 100) 385 | "rM": 100, // The red multiplier. [0~100] (Optional property, default: 100) 386 | "gM": 100, // The green multiplier. [0~100] (Optional property, default: 100) 387 | "bM": 100, // The blue multiplier. [0~100] (Optional property, default: 100) 388 | "aO": 0, // The alpha offset. [-255~255] (Optional property, default: 0) 389 | "rO": 0, // The red offset. [-255~255] (Optional property, default: 0) 390 | "gO": 0, // The green offset. [-255~255] (Optional property, default: 0) 391 | "bO": 0, // The blue offset. [-255~255] (Optional property, default: 0) 392 | } 393 | }] 394 | }], 395 | 396 | // A list of the FFD timelines. (Optional property, default: null) 397 | "ffd": [{ 398 | 399 | // The name of the mesh. 400 | "name": "meshName", 401 | 402 | // The name of skin. 403 | "skin": "skinName", 404 | 405 | // The name of slot. 406 | "slot": "slotName", 407 | 408 | "frame": [{ 409 | 410 | // The duration of the frame. (Optional property, default: 1) 411 | "duration": 1, 412 | 413 | // The tween easing of the frame. [0.0: Linear, null: No easing]. (Optional property, default: 0) 414 | "tweenEasing": 0.0, 415 | 416 | // The interpolation to use between this and the next keyframe. [x1, y1, x2, y2, ...] 417 | // (Optional property, default: null) 418 | "curve": [0.0, 0.0, 1.0, 1.0], 419 | 420 | // The number of vertices to skip before applying vertices. (Optional property, default: 0) 421 | "offset": 0, 422 | 423 | // A list of number pairs that are the amounts to add to the setup vertex positions for the keyframe. 424 | // (Optional property, default: null) 425 | // [x0, y0, x1, y1, ...] 426 | "vertices": [0.1, 0.1] 427 | }] 428 | }], 429 | 430 | // A list of the IK constraint timelines. (Optional property, default: null) 431 | "ik": [{ 432 | 433 | // The name of the IK constraint. 434 | "name": "meshName", 435 | 436 | "frame": [{ 437 | 438 | // The duration of the frame. (Optional property, default: 1) 439 | "duration": 1, 440 | 441 | // The tween easing of the frame. [0.0: Linear, null: No easing]. (Optional property, default: 0) 442 | "tweenEasing": 0.0, 443 | 444 | // The interpolation to use between this and the next keyframe. [x1, y1, x2, y2, ...] 445 | // (Optional property, default: null) 446 | "curve": [0.0, 0.0, 1.0, 1.0], 447 | 448 | // The positive direction of the IK constraint in the frame. (Optional property, default: true) 449 | "bendPositive": true, 450 | 451 | // The weight of the IK constraint in the frame. (Optional property, default: 1.0) 452 | "weight": 1.0 453 | }] 454 | }] 455 | }] 456 | }] 457 | } 458 | ``` -------------------------------------------------------------------------------- /doc/如何在 Egret 中使用 Live2D 的动画资源.md: -------------------------------------------------------------------------------- 1 | # 如何在 Egret 中使用 Live2D 的动画资源 2 | 3 | * 使用[龙骨的运行库](https://github.com/DragonBones/DragonBonesJS/tree/master/Egret/4.x/out)取代 Egret 内置的龙骨运行库。 4 | * 该功能稍后会正式加入 Egret 内置库。 5 | * 使用 Live2D 2.x 版本的模型动画工具制作模型动画并导出 JSON 模型动画数据。 6 | * 使用 [DragonBones Tools](https://github.com/DragonBones/Tools) 将 Live2D 的 JSON 动画数据转换成龙骨动画数据。 7 | * 参考下面的例子使用转换后的龙骨动画数据。 8 | * [例子源码](https://github.com/DragonBones/DragonBonesJS/blob/master/Egret/Demos/src/demo/EyeTracking.ts) 9 | * [在线演示](https://dragonbones.github.io/demo/EyeTracking/index.html) 10 | 11 | * 如果在开发中对该功能有任何问题或建议,请戳[这里](https://github.com/DragonBones/DragonBonesJS/issues)提交你的问题或建议。 12 | -------------------------------------------------------------------------------- /out/resource/helper.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 |
28 | 29 |
30 |
31 | 32 |
33 | 34 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /out/resource/player/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 |
56 | 57 |
58 |
69 |
70 |
71 | 72 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /out/resource/player/local.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 |
56 | 57 |
58 |
69 |
70 |
71 | 72 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /out/resource/viewer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DragonBones Viewer 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 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 |
65 |
66 |
67 | 68 |
69 | 70 | 71 | 72 | 73 |
74 |
75 | DragonBones Name 76 | 79 |
80 |
81 | Armature Name 82 | 85 |
86 |
87 | Skin Name 88 | 91 |
92 |
93 | Animation Name 94 | 97 |
98 |
99 | 100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | 113 |
114 |
115 |
116 |
117 |
X 1.00
118 |
119 |
120 |
121 |
122 |
123 | 124 |
125 |
Background Color
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
Show Grid
135 |
136 |
137 |
138 |
139 |
140 |
Show Bone
141 |
142 |
143 |
144 |
145 |
146 | 147 |
148 |
149 | 150 |
151 |
152 |
153 |
154 | 155 |
156 |
157 |
158 |
159 | 160 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /out/resource/viewer/local.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DragonBones Viewer 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 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 |
65 |
66 |
67 | 68 |
69 | 70 | 71 | 72 | 73 |
74 |
75 | DragonBones Name 76 | 79 |
80 |
81 | Armature Name 82 | 85 |
86 |
87 | Skin Name 88 | 91 |
92 |
93 | Animation Name 94 | 97 |
98 |
99 | 100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | 113 |
114 |
115 |
116 |
117 |
X 1.00
118 |
119 |
120 |
121 |
122 |
123 | 124 |
125 |
Background Color
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
Show Grid
135 |
136 |
137 |
138 |
139 |
140 |
Show Bone
141 |
142 |
143 |
144 |
145 |
146 | 147 |
148 |
149 | 150 |
151 |
152 |
153 |
154 | 155 |
156 |
157 |
158 |
159 | 160 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dragonbones-tools", 3 | "version": "0.1.02", 4 | "author": "dragonbones", 5 | "description": "Dragonbones tools.", 6 | "license": "MIT", 7 | "keywords": [ 8 | "dragonbones", 9 | "convert" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/DragonBones/Tools" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/DragonBones/Tools/issues" 17 | }, 18 | "scripts": { 19 | "build": "tsc" 20 | }, 21 | "bin": { 22 | "db2": "./out/convertTo.js", 23 | "2db": "./out/convertFrom.js", 24 | "db-test-demos": "./out/test/testDemos.js" 25 | }, 26 | "dependencies": { 27 | "@types/fs-extra": "^4.0.0", 28 | "commander": "^2.11.0", 29 | "fs-extra": "^4.0.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/action/toFormat.ts: -------------------------------------------------------------------------------- 1 | import * as object from "../common/object"; 2 | import * as geom from "../format/geom"; 3 | import * as dbft from "../format/dragonBonesFormat"; 4 | import * as dbftV23 from "../format/dragonBonesFormatV23"; 5 | /** 6 | * Convert json string to DragonBones format. 7 | */ 8 | export default function (jsonString: string, getTextureAtlases: () => dbft.TextureAtlas[]): dbft.DragonBones | null { 9 | if (!dbft.isDragonBonesString(jsonString)) { 10 | return null; 11 | } 12 | 13 | try { 14 | const json = JSON.parse(jsonString); 15 | const version = json["version"]; 16 | 17 | if (dbft.DATA_VERSIONS.indexOf(version) < dbft.DATA_VERSIONS.indexOf(dbft.DATA_VERSION_4_0)) { 18 | textureAtlases = getTextureAtlases(); 19 | const data = new dbftV23.DragonBones(); 20 | object.copyObjectFrom(json, data, dbftV23.copyConfig); 21 | 22 | return V23ToV45(data); 23 | } 24 | 25 | const result = new dbft.DragonBones(); 26 | object.copyObjectFrom(json, result, dbft.copyConfig); 27 | 28 | return result; 29 | } 30 | catch (error) { 31 | } 32 | 33 | return null; 34 | } 35 | 36 | let textureAtlases: dbft.TextureAtlas[]; 37 | const helpMatrix = new geom.Matrix(); 38 | const helpTransform = new geom.Transform(); 39 | const helpPoint = new geom.Point(); 40 | /** 41 | * Convert v2 v3 to v4 v5. 42 | */ 43 | function V23ToV45(data: dbftV23.DragonBones): dbft.DragonBones | null { 44 | const result = new dbft.DragonBones(); 45 | 46 | result.frameRate = data.frameRate; 47 | result.name = data.name; 48 | result.version = dbft.DATA_VERSION_4_5; 49 | result.compatibleVersion = dbft.DATA_VERSION_4_0; 50 | 51 | for (const armatureV23 of data.armature) { 52 | const armature = new dbft.Armature(); 53 | armature.name = armatureV23.name; 54 | result.armature.push(armature); 55 | 56 | for (const boneV23 of armatureV23.bone) { 57 | const bone = new dbft.Bone(); 58 | bone.inheritScale = false; 59 | // bone.inheritReflection = false; 60 | bone.name = boneV23.name; 61 | bone.parent = boneV23.parent; 62 | bone.transform.copyFrom(boneV23.transform); 63 | armature.bone.push(bone); 64 | } 65 | 66 | for (const skinV23 of armatureV23.skin) { 67 | const skin = new dbft.Skin(); 68 | skin.name = skinV23.name; 69 | armature.skin.push(skin); 70 | skinV23.slot.sort(sortSkinSlot); 71 | 72 | for (const slotV23 of skinV23.slot) { 73 | let slot = armature.getSlot(slotV23.name); 74 | if (!slot) { 75 | slot = new dbft.Slot(); 76 | slot.blendMode = slotV23.blendMode || dbft.BlendMode[dbft.BlendMode.Normal].toLowerCase(); 77 | slot.displayIndex = slotV23.displayIndex; 78 | slot.name = slotV23.name; 79 | slot.parent = slotV23.parent; 80 | slot.color.copyFrom(slotV23.colorTransform); 81 | armature.slot.push(slot); 82 | } 83 | 84 | const skinSlot = new dbft.SkinSlot(); 85 | skinSlot.name = slotV23.name; 86 | skin.slot.push(skinSlot); 87 | 88 | for (const displayV23 of slotV23.display) { 89 | if (displayV23.type === dbft.DisplayType[dbft.DisplayType.Image].toLowerCase()) { 90 | const display = new dbft.ImageDisplay(); 91 | display.name = displayV23.name; 92 | display.transform.copyFrom(displayV23.transform); 93 | display.transform.pX = 0.0; 94 | display.transform.pY = 0.0; 95 | 96 | const texture = dbft.getTextureFormTextureAtlases(display.name, textureAtlases); 97 | if (texture) { 98 | display.transform.x += 0.5 * texture.width - displayV23.transform.pX; 99 | display.transform.y += 0.5 * texture.height - displayV23.transform.pY; 100 | } 101 | 102 | skinSlot.display.push(display); 103 | } 104 | else { 105 | const display = new dbft.ArmatureDisplay(); 106 | display.name = displayV23.name; 107 | display.transform.copyFrom(displayV23.transform); 108 | skinSlot.display.push(display); 109 | } 110 | } 111 | } 112 | } 113 | 114 | for (const animationV23 of armatureV23.animation) { 115 | const animation = new dbft.Animation(); 116 | animation.duration = animationV23.duration; 117 | animation.playTimes = animationV23.loop; 118 | animation.scale = animationV23.scale; 119 | animation.fadeInTime = animationV23.fadeInTime; 120 | animation.name = animationV23.name; 121 | (armature.animation as dbft.Animation[]).push(animation); 122 | 123 | for (const frameV23 of animationV23.frame) { 124 | const frame = new dbft.ActionFrame(); 125 | frame.duration = frameV23.duration; 126 | frame.action = frameV23.action; 127 | frame.event = frameV23.event; 128 | frame.sound = frameV23.sound; 129 | animation.frame.push(frame); 130 | } 131 | 132 | for (const timelineV23 of animationV23.timeline) { 133 | const bone = armature.getBone(timelineV23.name); 134 | const slot = armature.getSlot(timelineV23.name); 135 | const boneAllTimeline = new dbft.BoneTimeline(); 136 | const slotAllTimeline = new dbft.SlotTimeline(); 137 | boneAllTimeline.scale = slotAllTimeline.scale = timelineV23.scale; 138 | boneAllTimeline.offset = slotAllTimeline.offset = timelineV23.offset; 139 | boneAllTimeline.name = slotAllTimeline.name = timelineV23.name; 140 | animation.bone.push(boneAllTimeline); 141 | animation.slot.push(slotAllTimeline); 142 | 143 | let position = 0; 144 | let prevBoneFrame: dbft.BoneAllFrame | null = null; 145 | let prevSlotFrame: dbft.SlotAllFrame | null = null; 146 | for (const frameV23 of timelineV23.frame) { 147 | const boneAllFrame = new dbft.BoneAllFrame(); 148 | const slotAllFrame = new dbft.SlotAllFrame(); 149 | 150 | boneAllFrame.duration = frameV23.duration; 151 | if (frameV23.tweenEasing === null) { 152 | if (animationV23.autoTween) { 153 | if (animationV23.tweenEasing === null) { 154 | boneAllFrame.tweenEasing = 0; 155 | slotAllFrame.tweenEasing = 0; 156 | } 157 | else { 158 | boneAllFrame.tweenEasing = animationV23.tweenEasing; 159 | slotAllFrame.tweenEasing = animationV23.tweenEasing; 160 | } 161 | } 162 | else { 163 | boneAllFrame.tweenEasing = NaN; 164 | slotAllFrame.tweenEasing = NaN; 165 | } 166 | } 167 | else { 168 | boneAllFrame.tweenEasing = frameV23.tweenEasing; 169 | slotAllFrame.tweenEasing = frameV23.tweenEasing; 170 | } 171 | 172 | boneAllFrame.curve = frameV23.curve; 173 | boneAllFrame.tweenRotate = frameV23.tweenRotate; 174 | boneAllFrame.transform.copyFrom(frameV23.transform); 175 | slotAllFrame.duration = frameV23.duration; 176 | slotAllFrame.curve = frameV23.curve; 177 | slotAllFrame.displayIndex = frameV23.displayIndex; 178 | slotAllFrame.color.copyFrom(frameV23.colorTransform); 179 | boneAllTimeline.frame.push(boneAllFrame); 180 | slotAllTimeline.frame.push(slotAllFrame); 181 | 182 | if (prevBoneFrame && prevSlotFrame && frameV23.displayIndex < 0) { 183 | prevBoneFrame.removeTween(); 184 | prevSlotFrame.removeTween(); 185 | } 186 | 187 | boneAllFrame.transform.toMatrix(helpMatrix); 188 | helpMatrix.transformPoint(frameV23.transform.pX, frameV23.transform.pY, helpPoint, true); 189 | boneAllFrame.transform.x += helpPoint.x; 190 | boneAllFrame.transform.y += helpPoint.y; 191 | 192 | if (frameV23.hide) { 193 | slotAllFrame.displayIndex = -1; 194 | } 195 | 196 | if (frameV23.action) { 197 | const action = new dbft.Action(); 198 | action.type = dbft.ActionType.Play; 199 | action.name = frameV23.action; 200 | slotAllFrame.actions.push(action); 201 | } 202 | 203 | if (frameV23.event || frameV23.sound) { 204 | dbft.mergeActionToAnimation(animation, frameV23, position, bone, slot, true); 205 | } 206 | 207 | position += frameV23.duration; 208 | prevBoneFrame = boneAllFrame; 209 | prevSlotFrame = slotAllFrame; 210 | } 211 | } 212 | 213 | for (const slot of armature.slot) { 214 | let timeline = animation.getSlotTimeline(slot.name); 215 | if (!timeline) { 216 | const frame = new dbft.SlotAllFrame(); 217 | frame.displayIndex = -1; 218 | timeline = new dbft.SlotTimeline(); 219 | timeline.name = slot.name; 220 | timeline.frame.push(frame); 221 | animation.slot.push(timeline); 222 | } 223 | } 224 | } 225 | 226 | if (data.isGlobal) { 227 | armature.sortBones(); 228 | globalToLocal(armature); 229 | } 230 | } 231 | 232 | return result; 233 | } 234 | 235 | function sortSkinSlot(a: dbftV23.Slot, b: dbftV23.Slot): number { 236 | return a.z < b.z ? -1 : 1; 237 | } 238 | 239 | function globalToLocal(armature: dbft.Armature): void { 240 | const bones = armature.bone.concat().reverse(); 241 | for (const bone of bones) { 242 | const parent = armature.getBone(bone.parent); 243 | if (parent) { 244 | parent.transform.toMatrix(helpMatrix); 245 | helpMatrix.invert(); 246 | helpMatrix.transformPoint(bone.transform.x, bone.transform.y, helpPoint); 247 | bone.transform.x = helpPoint.x; 248 | bone.transform.y = helpPoint.y; 249 | bone.transform.skX -= parent.transform.skY; 250 | bone.transform.skY -= parent.transform.skY; 251 | } 252 | else { 253 | bone.parent = ""; 254 | } 255 | 256 | for (const animation of armature.animation as dbft.Animation[]) { 257 | const timeline = animation.getBoneTimeline(bone.name); 258 | if (!timeline) { 259 | continue; 260 | } 261 | 262 | const parentTimeline = parent ? animation.getBoneTimeline(parent.name) : null; 263 | let position = 0; 264 | for (const frame of timeline.frame) { 265 | if (parentTimeline) { 266 | getTimelineFrameMatrix(parentTimeline, position, helpTransform); 267 | helpTransform.toMatrix(helpMatrix); 268 | helpMatrix.invert(); 269 | helpMatrix.transformPoint(frame.transform.x, frame.transform.y, helpPoint); 270 | 271 | frame.transform.x = helpPoint.x; 272 | frame.transform.y = helpPoint.y; 273 | frame.transform.skX -= helpTransform.skY; 274 | frame.transform.skY -= helpTransform.skY; 275 | } 276 | 277 | frame.transform.x -= bone.transform.x; 278 | frame.transform.y -= bone.transform.y; 279 | frame.transform.skX = geom.normalizeDegree(frame.transform.skX - bone.transform.skY); 280 | frame.transform.skY = geom.normalizeDegree(frame.transform.skY - bone.transform.skY); 281 | frame.transform.scX /= bone.transform.scX; 282 | frame.transform.scY /= bone.transform.scY; 283 | 284 | position += frame.duration; 285 | } 286 | } 287 | } 288 | } 289 | 290 | function getTimelineFrameMatrix(timeline: dbft.BoneTimeline, framePosition: number, transform: geom.Transform): void { 291 | let position = 0; 292 | let currentFrame: dbft.BoneAllFrame | null = null; 293 | let nextFrame: dbft.BoneAllFrame | null = null; 294 | for (const frame of timeline.frame) { 295 | if (position <= framePosition && framePosition < position + frame.duration) { 296 | currentFrame = frame; 297 | break; 298 | } 299 | 300 | position += frame.duration; 301 | } 302 | 303 | if (!currentFrame) { 304 | currentFrame = timeline.frame[timeline.frame.length - 1]; 305 | } 306 | 307 | if ((!isNaN(currentFrame.tweenEasing) || currentFrame.curve.length > 0) && timeline.frame.length > 1) { 308 | let nextIndex = timeline.frame.indexOf(currentFrame) + 1; 309 | if (nextIndex >= timeline.frame.length) { 310 | nextIndex = 0; 311 | } 312 | 313 | nextFrame = timeline.frame[nextIndex]; 314 | } 315 | 316 | if (!nextFrame) { 317 | transform.copyFrom(currentFrame.transform); 318 | } 319 | else { 320 | let tweenProgress = currentFrame.getTweenProgress((framePosition - position) / currentFrame.duration); 321 | transform.x = nextFrame.transform.x - currentFrame.transform.x; 322 | transform.y = nextFrame.transform.y - currentFrame.transform.y; 323 | transform.skX = geom.normalizeRadian(nextFrame.transform.skX - currentFrame.transform.skX); 324 | transform.skY = geom.normalizeRadian(nextFrame.transform.skY - currentFrame.transform.skY); 325 | transform.scX = nextFrame.transform.scX - currentFrame.transform.scX; 326 | transform.scY = nextFrame.transform.scY - currentFrame.transform.scY; 327 | 328 | transform.x = currentFrame.transform.x + transform.x * tweenProgress; 329 | transform.y = currentFrame.transform.y + transform.y * tweenProgress; 330 | transform.skX = currentFrame.transform.skX + transform.skX * tweenProgress; 331 | transform.skY = currentFrame.transform.skY + transform.skY * tweenProgress; 332 | transform.scX = currentFrame.transform.scX + transform.scX * tweenProgress; 333 | transform.scY = currentFrame.transform.scY + transform.scY * tweenProgress; 334 | } 335 | } -------------------------------------------------------------------------------- /src/action/toNew.ts: -------------------------------------------------------------------------------- 1 | import * as geom from "../format/geom"; 2 | import * as dbft from "../format/dragonBonesFormat"; 3 | 4 | export default function (data: dbft.DragonBones, forRuntime: boolean): dbft.DragonBones { 5 | data.version = dbft.DATA_VERSION_5_5; 6 | data.compatibleVersion = dbft.DATA_VERSION_5_5; 7 | 8 | for (const armature of data.armature) { 9 | if (armature.type.toString().toLowerCase() === dbft.ArmatureType[dbft.ArmatureType.Stage]) { 10 | armature.type = dbft.ArmatureType[dbft.ArmatureType.MovieClip]; 11 | armature.canvas = new dbft.Canvas(); 12 | armature.canvas.x = armature.aabb.x; 13 | armature.canvas.y = armature.aabb.y; 14 | armature.canvas.width = armature.aabb.width; 15 | armature.canvas.height = armature.aabb.height; 16 | } 17 | 18 | for (const skin of armature.skin) { 19 | skin.name = skin.name || "default"; 20 | } 21 | 22 | if (forRuntime) { // Old action to new action. 23 | if (armature.defaultActions.length > 0) { 24 | for (let i = 0, l = armature.defaultActions.length; i < l; ++i) { 25 | const action = armature.defaultActions[i]; 26 | if (action instanceof dbft.OldAction) { 27 | armature.defaultActions[i] = dbft.oldActionToNewAction(action); 28 | } 29 | } 30 | } 31 | } 32 | 33 | if (forRuntime) { // Old action to new action and move action to display. 34 | for (const slot of armature.slot) { 35 | if (slot.actions.length > 0) { 36 | const defaultSkin = armature.getSkin("default"); 37 | if (defaultSkin) { 38 | const skinSlot = defaultSkin.getSlot(slot.name); 39 | if (skinSlot !== null && skinSlot instanceof dbft.SkinSlot) { 40 | for (const action of slot.actions) { 41 | if (action instanceof dbft.OldAction) { 42 | const display = skinSlot.display[slot.displayIndex]; 43 | if (display instanceof dbft.ArmatureDisplay) { 44 | display.actions.push(dbft.oldActionToNewAction(action)); 45 | } 46 | } 47 | } 48 | } 49 | } 50 | 51 | slot.actions.length = 0; 52 | } 53 | } 54 | } 55 | 56 | for (const animation of armature.animation as dbft.Animation[]) { 57 | if (forRuntime) { // Old animation frame to new animation frame. 58 | for (const frame of animation.frame) { 59 | if (frame.event) { 60 | const action = new dbft.Action(); 61 | action.type = dbft.ActionType.Frame; 62 | action.name = frame.event; 63 | frame.actions.push(action); 64 | frame.event = ""; 65 | } 66 | 67 | if (frame.sound) { 68 | const action = new dbft.Action(); 69 | action.type = dbft.ActionType.Sound; 70 | action.name = frame.sound; 71 | frame.actions.push(action); 72 | frame.sound = ""; 73 | } 74 | 75 | if (frame.action) { 76 | const action = new dbft.Action(); 77 | action.type = dbft.ActionType.Play; 78 | action.name = frame.action; 79 | frame.actions.push(action); 80 | frame.action = ""; 81 | } 82 | 83 | for (const event of frame.events) { 84 | event.type = dbft.ActionType.Frame; 85 | frame.actions.push(event); 86 | } 87 | 88 | frame.events.length = 0; 89 | } 90 | } 91 | // Modify bone timelines. 92 | for (const timeline of animation.bone) { 93 | if (timeline instanceof dbft.TypeTimeline) { 94 | continue; 95 | } 96 | 97 | const bone = armature.getBone(timeline.name); 98 | if (!bone) { 99 | continue; 100 | } 101 | 102 | let position = 0; 103 | const slot = armature.getSlot(timeline.name); 104 | // Bone frame to transform frame. 105 | for (let i = 0, l = timeline.frame.length; i < l; ++i) { 106 | const frame = timeline.frame[i]; 107 | const translateFrame = new dbft.DoubleValueFrame0(); 108 | const rotateFrame = new dbft.BoneRotateFrame(); 109 | const scaleFrame = new dbft.DoubleValueFrame1(); 110 | timeline.translateFrame.push(translateFrame); 111 | timeline.rotateFrame.push(rotateFrame); 112 | timeline.scaleFrame.push(scaleFrame); 113 | 114 | translateFrame.duration = frame.duration; 115 | rotateFrame.duration = frame.duration; 116 | scaleFrame.duration = frame.duration; 117 | 118 | translateFrame.tweenEasing = frame.tweenEasing; 119 | translateFrame.curve = frame.curve.concat(); 120 | rotateFrame.tweenEasing = frame.tweenEasing; 121 | rotateFrame.curve = frame.curve.concat(); 122 | scaleFrame.tweenEasing = frame.tweenEasing; 123 | scaleFrame.curve = frame.curve.concat(); 124 | 125 | translateFrame.x = frame.transform.x; 126 | translateFrame.y = frame.transform.y; 127 | rotateFrame.clockwise = frame.tweenRotate; 128 | rotateFrame.rotate = geom.normalizeDegree(frame.transform.skY); 129 | rotateFrame.skew = geom.normalizeDegree(frame.transform.skX - frame.transform.skY); 130 | scaleFrame.x = frame.transform.scX; 131 | scaleFrame.y = frame.transform.scY; 132 | 133 | if (frame.action && !slot) { // Error data. 134 | frame.action = ""; 135 | } 136 | 137 | if (frame.event || frame.sound || frame.action) { // Merge bone action frame to action timeline. 138 | dbft.mergeActionToAnimation(animation, frame, position, bone, slot, forRuntime); 139 | frame.event = ""; 140 | frame.sound = ""; 141 | frame.action = ""; 142 | } 143 | 144 | position += frame.duration; 145 | } 146 | 147 | timeline.frame.length = 0; 148 | } 149 | // Modify slot timelines. 150 | for (const timeline of animation.slot) { 151 | const slot = armature.getSlot(timeline.name); 152 | if (!slot) { 153 | continue; 154 | } 155 | 156 | let position = 0; 157 | // Slot frame to display frame and color frame. 158 | for (let i = 0, l = timeline.frame.length; i < l; ++i) { 159 | const frame = timeline.frame[i]; 160 | const displayFrame = new dbft.SlotDisplayFrame(); 161 | const colorFrame = new dbft.SlotColorFrame(); 162 | timeline.displayFrame.push(displayFrame); 163 | timeline.colorFrame.push(colorFrame); 164 | 165 | displayFrame.duration = frame.duration; 166 | colorFrame.duration = frame.duration; 167 | 168 | colorFrame.tweenEasing = frame.tweenEasing; 169 | colorFrame.curve = frame.curve.concat(); 170 | 171 | displayFrame.value = frame.displayIndex; 172 | colorFrame.value.copyFrom(frame.color); 173 | 174 | if (frame.actions.length > 0) { 175 | if (forRuntime) { 176 | dbft.mergeActionToAnimation(animation, frame, position, null, slot, true); 177 | } 178 | else { 179 | for (const action of frame.actions) { 180 | displayFrame.actions.push(action); 181 | } 182 | } 183 | } 184 | 185 | position += frame.duration; 186 | } 187 | 188 | timeline.frame.length = 0; 189 | // Merge slot action to action timeline. 190 | if (forRuntime) { 191 | position = 0; 192 | 193 | for (let i = 0, l = timeline.displayFrame.length; i < l; ++i) { 194 | const frame = timeline.displayFrame[i]; 195 | if (frame.actions.length > 0) { 196 | dbft.mergeActionToAnimation(animation, frame, position, null, slot, true); 197 | frame.actions.length = 0; 198 | position += frame.duration; 199 | } 200 | } 201 | } 202 | } 203 | } 204 | } 205 | 206 | return data; 207 | } -------------------------------------------------------------------------------- /src/action/toWeb.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | // import * as zlib from "zlib"; 4 | 5 | const DATA_TAG = "data"; 6 | 7 | type Input = { 8 | data: Buffer | any; 9 | textureAtlases: (Buffer | null)[]; 10 | config?: { 11 | isLocal?: boolean; 12 | isAlone?: boolean; 13 | showFPS?: boolean; 14 | frameRate?: number; 15 | backgroundColor?: number; 16 | orientation?: string; 17 | scaleMode?: string; 18 | }; 19 | }; 20 | 21 | type ZipData = { 22 | data: string; 23 | textureAtlases: string[]; 24 | }[]; 25 | 26 | export default function (data: Input, isPlayer: boolean): string { 27 | const isLocal = data.config ? data.config.isLocal : false; 28 | const isAlone = data.config ? data.config.isAlone : false; 29 | const zipData = [{ 30 | data: data.data instanceof Buffer ? data.data.toString("base64") : data.data, 31 | textureAtlases: data.textureAtlases.map((v) => { 32 | return v ? v.toString("base64") : ""; 33 | }) 34 | }] as ZipData; 35 | // const compressed = zlib.gzipSync(new Buffer(JSON.stringify(zipData))).toString("base64"); 36 | // let htmlString = fs.readFileSync(path.join(__dirname, isPlayer ? "../resource/player.html" : "../resource/viewer.html"), "utf-8"); 37 | // htmlString = replaceHTMLCommentTag(htmlString, DATA_TAG, `${compressed}`, false); 38 | 39 | let htmlString = fs.readFileSync(path.join(__dirname, `../resource/${isPlayer ? "player" : "viewer"}/${isLocal ? "local" : (isAlone ? "alone" : "index")}.html`), "utf-8"); 40 | htmlString = replaceHTMLCommentTag(htmlString, DATA_TAG, `${JSON.stringify(zipData)}`, false); 41 | 42 | if (data.config) { 43 | if (data.config.showFPS) { 44 | htmlString = htmlString.replace(`data-show-fps="false"`, `data-show-fps="${data.config.showFPS}"`); 45 | } 46 | 47 | if (data.config.frameRate) { 48 | htmlString = htmlString.replace(`data-frame-rate="60"`, `data-frame-rate="${data.config.frameRate}"`); 49 | } 50 | 51 | if (data.config.backgroundColor || data.config.backgroundColor === 0) { 52 | if (data.config.backgroundColor >= 0) { 53 | htmlString = htmlString.replace(`background: #333333;`, `background: #${data.config.backgroundColor.toString(16)};`); 54 | } 55 | else { 56 | htmlString = htmlString.replace(`background: #333333;`, ""); 57 | } 58 | } 59 | 60 | if (data.config.orientation) { 61 | htmlString = htmlString.replace(`data-orientation="auto"`, `data-orientation="${data.config.orientation}"`); 62 | } 63 | 64 | if (data.config.scaleMode) { 65 | htmlString = htmlString.replace(`data-scale-mode="fixedNarrow"`, `data-scale-mode="${data.config.scaleMode}"`); 66 | } 67 | } 68 | 69 | return htmlString; 70 | } 71 | 72 | function replaceHTMLCommentTag(htmlString: string, tag: string, string: string, keepTag: boolean): string { 73 | const startTag = ""; 74 | const endTag = ""; 75 | const startIndex = htmlString.indexOf(startTag); 76 | const endIndex = htmlString.indexOf(endTag); 77 | 78 | if (startIndex >= 0 && endIndex >= 0) { 79 | let replaceString: string; 80 | if (keepTag) { 81 | replaceString = htmlString.substring(startIndex + startTag.length, endIndex); 82 | } 83 | else { 84 | replaceString = htmlString.substring(startIndex, endIndex + endTag.length); 85 | } 86 | 87 | return htmlString.replace(replaceString, string); 88 | } 89 | 90 | return htmlString; 91 | } -------------------------------------------------------------------------------- /src/common/nodeUtils.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | import * as os from "os"; 4 | import { exec } from "child_process"; 5 | 6 | export function filterFileList(folderPath: string, filter?: RegExp, maxDepth: number = 0, currentDepth: number = 0): string[] { 7 | let fileFilteredList = [] as string[]; 8 | 9 | if (folderPath && fs.existsSync(folderPath)) { 10 | for (const file of fs.readdirSync(folderPath)) { 11 | const filePath = path.resolve(folderPath, file); 12 | const fileStatus = fs.lstatSync(filePath); 13 | if (fileStatus.isDirectory()) { 14 | if (maxDepth === 0 || currentDepth <= maxDepth) { 15 | fileFilteredList = fileFilteredList.concat(filterFileList(filePath, filter, currentDepth + 1)); 16 | } 17 | } 18 | else if (!filter || filter.test(filePath)) { 19 | fileFilteredList.push(filePath); 20 | } 21 | } 22 | } 23 | 24 | return fileFilteredList; 25 | } 26 | 27 | export function open(target: string, appName: string | null = null, callback: ((error: any) => void) | null = null) { 28 | let command = ""; 29 | 30 | switch (process.platform) { 31 | case "darwin": 32 | if (appName) { 33 | command = `open -a "${escape(appName)}"`; 34 | } 35 | else { 36 | command = `open`; 37 | } 38 | break; 39 | 40 | case "win32": 41 | // if the first parameter to start is quoted, it uses that as the title 42 | // so we pass a blank title so we can quote the file we are opening 43 | if (appName) { 44 | command = `start "" "${escape(appName)}"`; 45 | } 46 | else { 47 | command = `start ""`; 48 | } 49 | break; 50 | 51 | default: 52 | if (appName) { 53 | command = escape(appName); 54 | } 55 | else { 56 | // use Portlands xdg-open everywhere else 57 | command = path.join(__dirname, "xdg-open"); 58 | } 59 | break; 60 | } 61 | 62 | const sudoUser = process.env["SUDO_USER"]; 63 | if (sudoUser) { 64 | command = `sudo -u ${sudoUser} ${command}`; 65 | } 66 | 67 | command = `${command} "${escape(target)}"`; 68 | 69 | return exec(command, callback || undefined); 70 | } 71 | 72 | export function findIP(): string { 73 | const ipConfig = os.networkInterfaces(); 74 | let ip = "localhost"; 75 | for (const k in ipConfig) { 76 | const arr = ipConfig[k]!; 77 | for (let i = 0; i < arr.length; ++i) { 78 | const ipData = arr[i]; 79 | if (!ipData.internal && ipData.family === "IPv4") { 80 | ip = ipData.address; 81 | 82 | return ip; 83 | } 84 | } 85 | } 86 | 87 | return ip; 88 | } 89 | 90 | function escape(string: string): string { 91 | return string.replace(/"/g, '\\\"'); 92 | } -------------------------------------------------------------------------------- /src/common/object.ts: -------------------------------------------------------------------------------- 1 | export function copyObjectFrom(from: any, to: any, config: any[] | null): void { 2 | let dataConfig: any = null; 3 | if (config !== null) { 4 | const index = config.indexOf(to.constructor); 5 | if (index >= 0 && index < config.length - 1) { 6 | dataConfig = config[index + 1]; 7 | } 8 | } 9 | 10 | for (let k in to) { 11 | if (!(k in from)) { 12 | continue; 13 | } 14 | 15 | _copyObjectFrom(to, k, to[k], from[k], dataConfig ? dataConfig[k] : null, config); 16 | } 17 | } 18 | 19 | function _copyObjectFrom(parent: any, key: string | number, data: any, object: any, creater: any, config: any[] | null): any { 20 | const dataType = typeof data; 21 | const objectType = typeof object; 22 | if (objectType as any === "function") { // 23 | return; 24 | } 25 | 26 | if (object === null || object === undefined || objectType !== "object") { 27 | if (dataType === objectType) { 28 | parent[key] = object; 29 | } 30 | else if (dataType === "boolean") { 31 | // console.warn(`${key}: ${objectType} is not a boolean.`); 32 | switch (object) { 33 | case "0": 34 | case "NaN": 35 | case "": 36 | case "false": 37 | case "null": 38 | case "undefined": 39 | parent[key] = false; 40 | break; 41 | 42 | default: 43 | parent[key] = Boolean(object); 44 | break; 45 | } 46 | } 47 | else if (dataType === "number") { 48 | // console.warn(`${key}: ${objectType} is not a number.`); 49 | if (object === "NaN" || object === null) { 50 | parent[key] = NaN; 51 | } 52 | else { 53 | parent[key] = Number(object); 54 | } 55 | } 56 | else if (dataType === "string") { 57 | // console.warn(`${key}: ${objectType} is not a string.`); 58 | if (object || object === object) { 59 | parent[key] = String(object); 60 | } 61 | else { 62 | parent[key] = ""; 63 | } 64 | } 65 | else { 66 | parent[key] = object; 67 | } 68 | } 69 | else if (object instanceof Array) { 70 | if (!(data instanceof Array)) { 71 | // console.warn(`${key}: ${dataType} is not an array.`); 72 | parent[key] = data = []; 73 | } 74 | 75 | if (data instanceof Array) { 76 | data.length = object.length; 77 | for (let i = 0, l = data.length; i < l; ++i) { 78 | _copyObjectFrom(data, i, data[i], object[i], creater, config); 79 | } 80 | } 81 | } 82 | else { 83 | if (data !== null && data !== undefined && dataType === "object") { 84 | if (creater instanceof Array) { 85 | for (let k in object) { 86 | _copyObjectFrom(data, k, data[k], object[k], creater[0], config); 87 | } 88 | } 89 | else { 90 | copyObjectFrom(object, data, config); 91 | } 92 | } 93 | else if (creater) { 94 | if (creater instanceof Array) { 95 | if (creater[1] === Function) { 96 | const clazz = creater[0](object); 97 | parent[key] = data = new clazz(); 98 | copyObjectFrom(object, data, config); 99 | } 100 | else { 101 | parent[key] = data = creater[1] === Array ? [] : {}; 102 | for (let k in object) { 103 | _copyObjectFrom(data, k, data[k], object[k], creater[0], config); 104 | } 105 | } 106 | } 107 | else if (creater) { 108 | parent[key] = data = new creater(); 109 | copyObjectFrom(object, data, config); 110 | } 111 | else { 112 | // console.warn(`${key}: shallow copy.`); 113 | parent[key] = object; 114 | } 115 | } 116 | else { 117 | // console.warn(`${key}: shallow copy.`); 118 | parent[key] = object; 119 | } 120 | } 121 | } 122 | 123 | export function compress(data: any, config: any[]): boolean { 124 | if ((typeof data) !== "object") { 125 | return false; 126 | } 127 | 128 | if (data instanceof Array) { 129 | const array = data as any[]; 130 | for (const item of array) { 131 | if (item !== null) { 132 | compress(item, config); 133 | } 134 | } 135 | 136 | if (array.length === 0) { 137 | return true; 138 | } 139 | } 140 | else { 141 | let defaultData: any = null; 142 | for (defaultData of config) { 143 | if (data.constructor === defaultData.constructor) { 144 | break; 145 | } 146 | 147 | defaultData = null; 148 | } 149 | 150 | if (defaultData !== null || typeof data === "object") { 151 | let count = 0; 152 | for (let k in data) { 153 | if (k.charAt(0) === "_") { // Pass private value. 154 | delete data[k]; 155 | continue; 156 | } 157 | 158 | const value = data[k]; 159 | const valueType = typeof value; 160 | 161 | if (defaultData !== null && (value === null || valueType === "undefined" || valueType === "boolean" || valueType === "number" || valueType === "string")) { 162 | const defaultValue = defaultData[k]; 163 | if (value === defaultValue || (valueType === "number" && isNaN(value) && isNaN(defaultValue))) { 164 | delete data[k]; 165 | continue; 166 | } 167 | } 168 | else if (valueType === "object") { 169 | if (compress(value, config)) { 170 | if ((value instanceof Array) ? !(data["_" + k]) : true) { 171 | delete data[k]; 172 | } 173 | 174 | continue; 175 | } 176 | } 177 | else { 178 | continue; 179 | } 180 | 181 | count++; 182 | } 183 | 184 | return count === 0; 185 | } 186 | } 187 | 188 | return false; 189 | } -------------------------------------------------------------------------------- /src/common/server.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | import * as url from "url"; 4 | import * as http from "http"; 5 | import * as types from "./types"; 6 | 7 | export abstract class Server { 8 | public port: number; 9 | public host: string; 10 | public local: string; 11 | public readonly httpServer: http.Server = http.createServer(this._requestHandler.bind(this)); 12 | 13 | protected _requestHandler(request: http.IncomingMessage, response: http.ServerResponse): boolean { 14 | // tslint:disable-next-line:no-unused-expression 15 | response; 16 | if (!request.url) { 17 | return false; 18 | } 19 | 20 | return true; 21 | } 22 | 23 | public start(host: string, port: number, local: string): void { 24 | if (this.port) { 25 | return; 26 | } 27 | 28 | this.host = host; 29 | this.port = port; 30 | this.local = local; 31 | this.httpServer.listen(this.port, () => { 32 | // console.log("Listening on: " + this.host + ":" + this.port); 33 | }); 34 | // this.server.addListener("error", () => { 35 | // // process.exit(1501); 36 | // }); 37 | } 38 | 39 | public stop(): void { 40 | process.exit(0); 41 | } 42 | } 43 | 44 | export enum Code { 45 | Success = 200, 46 | UnknownAction = 2000, 47 | DataError, 48 | JSONError, 49 | ActionError, 50 | } 51 | 52 | export interface Result { code: Code; message: string; data: any; } 53 | 54 | export class Gate extends Server { 55 | public readonly actions: { [k: string]: (request: http.IncomingMessage, response: http.ServerResponse) => void } = {}; 56 | 57 | protected _requestHandler(request: http.IncomingMessage, response: http.ServerResponse): boolean { 58 | if (!super._requestHandler(request, response)) { 59 | return false; 60 | } 61 | 62 | const pathName = (url.parse(request.url as string).pathname as string).replace(this.local, ""); 63 | 64 | if (pathName in this.actions) { 65 | const action = this.actions[pathName as any]; 66 | action(request, response); 67 | } 68 | else { 69 | const localPath = path.join(__dirname, "../", pathName); 70 | let extName = path.extname(pathName); 71 | extName = extName ? extName.slice(1) : "unknown"; 72 | 73 | if (fs.existsSync(localPath) && !fs.statSync(localPath).isDirectory()) { 74 | const fileResult = fs.readFileSync(localPath, "binary"); 75 | response.writeHead(200, { "Content-Type": types.MineContentTypes[extName] || "text/plain" }); 76 | response.write(fileResult, "binary"); 77 | response.end(); 78 | } 79 | else { 80 | this.responseEnd(response, Code.UnknownAction, Code[Code.UnknownAction]); 81 | } 82 | } 83 | 84 | return true; 85 | } 86 | 87 | public responseEnd(response: http.ServerResponse, code: Code, message: string, data: any = null): void { 88 | const result: Result = { code: code, message: message, data: data }; 89 | response.writeHead(200, { "Content-Type": "application/json" }); 90 | response.write(JSON.stringify(result)); 91 | response.end(); 92 | } 93 | } -------------------------------------------------------------------------------- /src/common/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export const MineContentTypes: { [key: string]: string } = { 3 | "css": "text/css", 4 | "gif": "image/gif", 5 | "html": "text/html", 6 | "ico": "image/x-icon", 7 | "jpeg": "image/jpeg", 8 | "jpg": "image/jpeg", 9 | "js": "text/javascript", 10 | "json": "application/json", 11 | "pdf": "application/pdf", 12 | "png": "image/png", 13 | "svg": "image/svg+xml", 14 | "swf": "application/x-shockwave-flash", 15 | "tiff": "image/tiff", 16 | "txt": "text/plain", 17 | "wav": "audio/x-wav", 18 | "wma": "audio/x-ms-wma", 19 | "wmv": "video/x-ms-wmv", 20 | "xml": "text/xml" 21 | }; -------------------------------------------------------------------------------- /src/common/utils.ts: -------------------------------------------------------------------------------- 1 | export function formatJSONString(string: string): string { 2 | let firstCode = string.charCodeAt(0); 3 | if (firstCode < 0x20 || firstCode > 0x7f) { 4 | string = string.substring(1); // 去除第一个字符 5 | } 6 | 7 | return string; 8 | } 9 | 10 | export function rgbaToHex(r: number, g: number, b: number, a: number): string { 11 | let result = ""; 12 | let s = Math.round(r).toString(16); 13 | if (s.length < 2) (s = "0" + s); 14 | result += s; 15 | 16 | s = Math.round(g).toString(16); 17 | if (s.length < 2) (s = "0" + s); 18 | result += s; 19 | 20 | s = Math.round(b).toString(16); 21 | if (s.length < 2) (s = "0" + s); 22 | result += s; 23 | 24 | s = Math.round(a).toString(16); 25 | if (s.length < 2) (s = "0" + s); 26 | result += s; 27 | 28 | return result; 29 | } 30 | 31 | if (!String.prototype.padStart) { 32 | String.prototype.padStart = function (maxLength: number, fillString: string = ' ') { 33 | const source = this; 34 | if (source.length >= maxLength) return String(source); 35 | 36 | const fillLength = maxLength - source.length; 37 | let times = Math.ceil(fillLength / fillString.length); 38 | 39 | while (times >>= 1) { 40 | fillString += fillString; 41 | if (times === 1) { 42 | fillString += fillString; 43 | } 44 | } 45 | 46 | return fillString.slice(0, fillLength) + source; 47 | }; 48 | } 49 | 50 | export function getEnumFormString(enumerator: any, type: string | number, defaultType: number = -1): number { 51 | if (typeof type === "number") { 52 | return type; 53 | } 54 | 55 | for (let k in enumerator) { 56 | if (typeof k === "string") { 57 | if (k.toLowerCase() === type.toLowerCase()) { 58 | return enumerator[k]; 59 | } 60 | } 61 | } 62 | 63 | return defaultType; 64 | } -------------------------------------------------------------------------------- /src/convertFrom.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | import * as fs from "fs-extra"; 3 | import * as path from "path"; 4 | import * as commander from "commander"; 5 | import * as object from "./common/object"; 6 | import * as utils from "./common/utils"; 7 | import * as nodeUtils from "./common/nodeUtils"; 8 | import * as dbft from "./format/dragonBonesFormat"; 9 | import * as spft from "./format/spineFormat"; 10 | import * as l2ft from "./format/live2DFormat"; 11 | import fromSpine from "./action/fromSpine"; 12 | import fromLive2D from "./action/fromLive2D"; 13 | import format from "./action/formatFormat"; 14 | import * as helper from "./helper/helperRemote"; 15 | 16 | function execute(): void { 17 | const commands = commander 18 | .version("0.1.0") 19 | .option("-i, --input [path]", "Input path") 20 | .option("-o, --output [path]", "Output path") 21 | .option("-t, --type [type]", "Convert from type [spine, live2d]", /^(spine|live2d)$/i, "none") 22 | .option("-f, --filter [keyword]", "Filter") 23 | .option("-d, --delete", "Delete raw files after convert complete.") 24 | .parse(process.argv); 25 | 26 | const input = path.resolve(path.normalize(commands["input"] as string || process.cwd())); 27 | const output = "output" in commands ? path.resolve(path.normalize(commands["output"])) : input; 28 | const type = commands["type"] as string || ""; 29 | const filter = commands["filter"] as string || ""; 30 | const deleteRaw = commands["delete"] as boolean || false; 31 | // let loadTextureAtlasToData = false; 32 | // let megreTextureAtlasToData = false; 33 | 34 | switch (type) { 35 | case "spine": { 36 | const files = nodeUtils.filterFileList(input, /\.(json)$/i); 37 | for (const file of files) { 38 | if (filter && file.indexOf(filter) < 0) { 39 | continue; 40 | } 41 | 42 | const fileString = fs.readFileSync(file, "utf-8"); 43 | if (!spft.isSpineString(fileString)) { 44 | continue; 45 | } 46 | 47 | const fileName = path.basename(file, ".json"); 48 | const textureAtlasFile = path.join(path.dirname(file), fileName + ".atlas"); 49 | let textureAtlasString = ""; 50 | if (fs.existsSync(textureAtlasFile)) { 51 | textureAtlasString = fs.readFileSync(textureAtlasFile, "utf-8"); 52 | } 53 | 54 | const spine = new spft.Spine(); 55 | object.copyObjectFrom(JSON.parse(fileString), spine, spft.copyConfig); 56 | const result = fromSpine({ name: fileName, data: spine, textureAtlas: textureAtlasString }); 57 | const outputFile = (output ? file.replace(input, output) : file).replace(".json", "_ske.json"); 58 | format(result); 59 | 60 | const textureAtlases = result.textureAtlas.concat(); // TODO 61 | result.textureAtlas.length = 0; 62 | 63 | object.compress(result, dbft.compressConfig); 64 | if (!fs.existsSync(path.dirname(outputFile))) { 65 | fs.mkdirsSync(path.dirname(outputFile)); 66 | } 67 | fs.writeFileSync( 68 | outputFile, 69 | JSON.stringify(result) 70 | ); 71 | console.log(outputFile); 72 | 73 | if (deleteRaw) { 74 | fs.removeSync(file); 75 | fs.removeSync(textureAtlasFile); 76 | } 77 | 78 | let index = 0; 79 | for (const textureAtlas of textureAtlases) { 80 | const pageName = `_tex${textureAtlases.length > 1 ? "_" + index++ : ""}`; 81 | const outputFile = (output ? file.replace(input, output) : file).replace(".json", pageName + ".json"); 82 | const textureAtlasImage = path.join(path.dirname(file), textureAtlas.imagePath); 83 | 84 | textureAtlas.imagePath = path.basename(outputFile).replace(".json", ".png"); 85 | 86 | const imageOutputFile = path.join(path.dirname(outputFile), textureAtlas.imagePath); 87 | if (!fs.existsSync(path.dirname(imageOutputFile))) { 88 | fs.mkdirsSync(path.dirname(imageOutputFile)); 89 | } 90 | 91 | object.compress(textureAtlas, dbft.compressConfig); 92 | if (!fs.existsSync(path.dirname(outputFile))) { 93 | fs.mkdirsSync(path.dirname(outputFile)); 94 | } 95 | 96 | fs.writeFileSync( 97 | outputFile, 98 | JSON.stringify(textureAtlas) 99 | ); 100 | 101 | if (deleteRaw) { 102 | fs.moveSync(textureAtlasImage, imageOutputFile); 103 | } 104 | else { 105 | fs.copySync(textureAtlasImage, imageOutputFile); 106 | } 107 | 108 | let hasRotated = false; 109 | for (const texture of textureAtlas.SubTexture) { 110 | if (texture.rotated) { 111 | hasRotated = true; 112 | } 113 | } 114 | 115 | if (hasRotated) { 116 | const helperInput = { 117 | type: "modify_spine_textureatlas", 118 | data: { 119 | file: imageOutputFile, 120 | config: textureAtlas, 121 | texture: fs.readFileSync(imageOutputFile, "base64") 122 | } 123 | }; 124 | 125 | helper.addInput(helperInput); 126 | } 127 | 128 | console.log(outputFile); 129 | console.log(imageOutputFile); 130 | } 131 | } 132 | 133 | break; 134 | } 135 | 136 | case "live2d": { 137 | const files = nodeUtils.filterFileList(input, /\.(model.json)$/i); 138 | for (const file of files) { 139 | if (filter && file.indexOf(filter) < 0) { 140 | continue; 141 | } 142 | // Parse config. 143 | const dirURL = path.dirname(file); 144 | const fileName = path.basename(file, ".model.json"); 145 | const fileString = fs.readFileSync(file, "utf-8"); 146 | const modelConfig = JSON.parse(fileString) as l2ft.ModelConfig; 147 | const modelURL = path.join(dirURL, modelConfig.model); 148 | const deleteFiles = [file]; 149 | modelConfig.name = modelConfig.name || fileName; 150 | 151 | // Parse model. 152 | if (fs.existsSync(modelURL)) { 153 | const fileBuffer = fs.readFileSync(modelURL); 154 | const model = l2ft.parseModel(fileBuffer.buffer as ArrayBuffer); 155 | if (!model) { 156 | console.log("Model parse error.", modelURL); 157 | continue; 158 | } 159 | 160 | modelConfig.modelImpl = model; 161 | deleteFiles.push(modelURL); 162 | } 163 | else { 164 | console.log("File does not exist.", modelURL); 165 | continue; 166 | } 167 | 168 | for (let i = 0, l = modelConfig.textures.length; i < l; ++i) { // Parse textures. 169 | const textureURI = modelConfig.textures[i] as string; 170 | const textureURL = path.join(dirURL, textureURI); 171 | 172 | if (fs.existsSync(textureURL)) { 173 | const texture = { file: textureURI, width: 0, height: 0 }; 174 | const textureAtlasBuffer = fs.readFileSync(textureURL); 175 | modelConfig.textures[i] = texture; 176 | 177 | if (textureAtlasBuffer.toString('ascii', 12, 16) === "CgBI") { 178 | texture.width = textureAtlasBuffer.readUInt32BE(32); 179 | texture.height = textureAtlasBuffer.readUInt32BE(36); 180 | } 181 | else { 182 | texture.width = textureAtlasBuffer.readUInt32BE(16); 183 | texture.height = textureAtlasBuffer.readUInt32BE(20); 184 | } 185 | } 186 | else { 187 | console.log("File does not exist.", textureURL); 188 | } 189 | } 190 | 191 | if (modelConfig.motions) { // Parse animation. 192 | for (const k in modelConfig.motions) { 193 | const motionConfigs = modelConfig.motions[k]; 194 | for (const motionConfig of motionConfigs) { 195 | const motionURL = path.join(dirURL, motionConfig.file); 196 | if (fs.existsSync(motionURL)) { 197 | motionConfig.motion = l2ft.parseMotion(fs.readFileSync(motionURL, "utf-8")); 198 | deleteFiles.push(motionURL); 199 | } 200 | else { 201 | console.log("File does not exist.", motionURL); 202 | } 203 | } 204 | } 205 | } 206 | 207 | if (modelConfig.expressions) { 208 | for (const k in modelConfig.expressions) { 209 | const expressionConfig = modelConfig.expressions[k]; 210 | const expressionURL = path.join(dirURL, expressionConfig.file); 211 | if (fs.existsSync(expressionURL)) { 212 | expressionConfig.expression = JSON.parse(utils.formatJSONString(fs.readFileSync(expressionURL, "utf-8"))); 213 | deleteFiles.push(expressionURL); 214 | } 215 | else { 216 | console.log("File does not exist.", expressionURL); 217 | } 218 | } 219 | } 220 | 221 | const result = fromLive2D(modelConfig); 222 | if (result === null) { 223 | continue; 224 | } 225 | 226 | const outputDirURL = dirURL.replace(input, output); 227 | const outputURL = path.join(outputDirURL, fileName + "_ske.json"); 228 | format(result); 229 | console.log(outputURL); 230 | 231 | if (!fs.existsSync(outputDirURL)) { 232 | fs.mkdirsSync(outputDirURL); 233 | } 234 | 235 | if (outputDirURL !== dirURL) { 236 | for (const textureAtlas of result.textureAtlas) { 237 | const rawImageURL = path.join(dirURL, textureAtlas.imagePath); 238 | const outputImageURL = path.join(outputDirURL, textureAtlas.imagePath); 239 | console.log(outputImageURL); 240 | 241 | if (!fs.existsSync(path.dirname(outputImageURL))) { 242 | fs.mkdirsSync(path.dirname(outputImageURL)); 243 | } 244 | 245 | if (deleteRaw) { 246 | fs.moveSync(rawImageURL, outputImageURL); 247 | } 248 | else { 249 | fs.copySync(rawImageURL, outputImageURL); 250 | } 251 | } 252 | } 253 | 254 | object.compress(result, dbft.compressConfig); 255 | fs.writeFileSync( 256 | outputURL, 257 | JSON.stringify(result) 258 | ); 259 | 260 | if (deleteRaw) { 261 | for (const file of deleteFiles) { 262 | fs.unlinkSync(file); 263 | } 264 | } 265 | } 266 | 267 | break; 268 | } 269 | 270 | case "cocos": 271 | // loadTextureAtlasToData = true; 272 | // megreTextureAtlasToData = true; 273 | // break; 274 | 275 | default: 276 | console.log(`Unknown type: ${type}`); 277 | return; 278 | } 279 | 280 | console.log("Convert complete."); 281 | 282 | if (helper.hasInput()) { 283 | helper.start(); 284 | console.log("Waitting for helper."); 285 | } 286 | } 287 | 288 | execute(); -------------------------------------------------------------------------------- /src/convertTo.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | import * as fs from "fs-extra"; 3 | import * as path from "path"; 4 | import * as commander from "commander"; 5 | import * as object from "./common/object"; 6 | import * as nodeUtils from "./common/nodeUtils"; 7 | import * as dbft from "./format/dragonBonesFormat"; 8 | import * as spft from "./format/spineFormat"; 9 | import * as dbUtils from "./format/utils"; 10 | import toFormat from "./action/toFormat"; 11 | import toNew from "./action/toNew"; 12 | import toBinary from "./action/toBinary"; 13 | import toWeb from "./action/toWeb"; 14 | import toSpine from "./action/toSpine"; 15 | import format from "./action/formatFormat"; 16 | 17 | function execute(): void { 18 | const commands = commander 19 | .version("0.1.0") 20 | .option("-i, --input [path]", "Input path") 21 | .option("-o, --output [path]", "Output path") 22 | .option("-t, --type [type]", "Convert to type [binary, new, v45, player, viewer, spine]", /^(binary|new|v45|player|viewer|spine|none)$/i, "none") 23 | .option("-f, --filter [keyword]", "Filter") 24 | .option("-d, --delete", "Delete raw files after convert complete") 25 | .option("-p, --parameter [parameter]", "Parameter") 26 | .parse(process.argv); 27 | 28 | const input = path.resolve(path.normalize(commands["input"] as string || process.cwd())); 29 | const output = "output" in commands ? path.resolve(path.normalize(commands["output"])) : input; 30 | const type = commands["type"] as string || ""; 31 | const filter = commands["filter"] as string || ""; 32 | const deleteRaw = commands["delete"] as boolean || false; 33 | const parameter = commands["parameter"] as string || ""; 34 | let loadTextureAtlasToData = false; 35 | let megreTextureAtlasToData = false; 36 | 37 | switch (type) { 38 | case "binary": 39 | break; 40 | 41 | case "new": 42 | break; 43 | 44 | case "v45": 45 | break; 46 | 47 | case "player": 48 | case "viewer": 49 | loadTextureAtlasToData = true; 50 | megreTextureAtlasToData = true; 51 | break; 52 | 53 | case "spine": 54 | loadTextureAtlasToData = true; 55 | megreTextureAtlasToData = true; 56 | break; 57 | 58 | default: 59 | console.log(`Unknown type: ${type}`); 60 | return; 61 | } 62 | 63 | const files = nodeUtils.filterFileList(input, /\.(json)$/i); 64 | for (const file of files) { 65 | if (filter && file.indexOf(filter) < 0) { 66 | continue; 67 | } 68 | 69 | const dirURL = path.dirname(file); 70 | const fileName = path.basename(file, ".json"); 71 | const fileString = fs.readFileSync(file, "utf-8"); 72 | let textureAtlasFiles: string[] | null = null; 73 | let textureAtlasImages: string[] | null = null; 74 | let textureAtlases: dbft.TextureAtlas[] | null = null; 75 | const dragonBonesData = toFormat(fileString, () => { 76 | textureAtlasFiles = dbUtils.getTextureAtlases(file); 77 | textureAtlases = textureAtlasFiles.map((v) => { 78 | return getTextureAtlas(v); 79 | }); 80 | 81 | return textureAtlases; 82 | }); 83 | 84 | if (!dragonBonesData) { 85 | continue; 86 | } 87 | 88 | if (dragonBonesData.textureAtlas.length > 0) { 89 | textureAtlasFiles = null; 90 | textureAtlasImages = dragonBonesData.textureAtlas.map((v) => { 91 | return v.imagePath; 92 | }); 93 | textureAtlases = dragonBonesData.textureAtlas; 94 | } 95 | else { 96 | if (!textureAtlasFiles) { 97 | textureAtlasFiles = dbUtils.getTextureAtlases(file); 98 | } 99 | textureAtlasImages = textureAtlasFiles.map((v) => { 100 | return v.replace(".json", ".png"); 101 | }); 102 | 103 | if (loadTextureAtlasToData && textureAtlasFiles.length > 0) { 104 | textureAtlases = textureAtlasFiles.map((v) => { 105 | return getTextureAtlas(v); 106 | }); 107 | } 108 | } 109 | 110 | if (megreTextureAtlasToData && textureAtlases && dragonBonesData.textureAtlas.length === 0) { 111 | for (const textureAtals of textureAtlases) { 112 | dragonBonesData.textureAtlas.push(textureAtals); 113 | } 114 | 115 | textureAtlases = dragonBonesData.textureAtlas; 116 | } 117 | 118 | switch (type) { 119 | case "binary": { 120 | toNew(dragonBonesData, true); 121 | format(dragonBonesData); 122 | 123 | const outputDirURL = dirURL.replace(input, output); 124 | const outputURL = path.join(outputDirURL, fileName + ".dbbin"); 125 | const result = toBinary(dragonBonesData); 126 | 127 | if (!fs.existsSync(outputDirURL)) { 128 | fs.mkdirsSync(outputDirURL); 129 | } 130 | 131 | fs.writeFileSync(outputURL, new Buffer(result)); 132 | console.log(outputURL); 133 | 134 | if (deleteRaw) { 135 | fs.unlinkSync(file); 136 | } 137 | 138 | if (outputDirURL !== dirURL) { 139 | if (textureAtlasFiles && !megreTextureAtlasToData) { 140 | for (const textureAtlasFile of textureAtlasFiles) { 141 | const outputURL = textureAtlasFile.replace(input, output); 142 | 143 | if (deleteRaw) { 144 | fs.moveSync(textureAtlasFile, outputURL); 145 | } 146 | else { 147 | fs.copySync(textureAtlasFile, outputURL); 148 | } 149 | 150 | console.log(outputURL); 151 | } 152 | } 153 | 154 | for (const textureAtlasImage of textureAtlasImages) { 155 | const outputURL = textureAtlasImage.replace(input, output); 156 | 157 | if (deleteRaw) { 158 | fs.moveSync(textureAtlasImage, outputURL); 159 | } 160 | else { 161 | fs.copySync(textureAtlasImage, outputURL); 162 | } 163 | 164 | console.log(outputURL); 165 | } 166 | } 167 | else if (textureAtlasFiles && megreTextureAtlasToData) { 168 | for (const textureAtlasFile of textureAtlasFiles) { 169 | fs.removeSync(textureAtlasFile); 170 | } 171 | } 172 | 173 | break; 174 | } 175 | 176 | case "new": { 177 | toNew(dragonBonesData, false); 178 | format(dragonBonesData); 179 | object.compress(dragonBonesData, dbft.compressConfig); 180 | 181 | const outputDirURL = dirURL.replace(input, output); 182 | const outputURL = path.join(outputDirURL, fileName + ".json"); 183 | const result = JSON.stringify(dragonBonesData); 184 | 185 | if (!fs.existsSync(outputDirURL)) { 186 | fs.mkdirsSync(outputDirURL); 187 | } 188 | 189 | fs.writeFileSync(outputURL, new Buffer(result)); 190 | console.log(outputURL); 191 | 192 | if (outputDirURL !== dirURL) { 193 | if (deleteRaw) { 194 | fs.unlinkSync(file); 195 | } 196 | 197 | if (textureAtlasFiles && !megreTextureAtlasToData) { 198 | for (const textureAtlasFile of textureAtlasFiles) { 199 | const outputURL = textureAtlasFile.replace(input, output); 200 | 201 | if (deleteRaw) { 202 | fs.moveSync(textureAtlasFile, outputURL); 203 | } 204 | else { 205 | fs.copySync(textureAtlasFile, outputURL); 206 | } 207 | 208 | console.log(outputURL); 209 | } 210 | } 211 | 212 | for (const textureAtlasImage of textureAtlasImages) { 213 | const outputURL = textureAtlasImage.replace(input, output); 214 | 215 | if (deleteRaw) { 216 | fs.moveSync(textureAtlasImage, outputURL); 217 | } 218 | else { 219 | fs.copySync(textureAtlasImage, outputURL); 220 | } 221 | 222 | console.log(outputURL); 223 | } 224 | } 225 | else if (textureAtlasFiles && megreTextureAtlasToData) { 226 | for (const textureAtlasFile of textureAtlasFiles) { 227 | fs.removeSync(textureAtlasFile); 228 | } 229 | } 230 | 231 | break; 232 | } 233 | 234 | case "player": 235 | case "viewer": { 236 | toNew(dragonBonesData, true); 237 | format(dragonBonesData); 238 | 239 | const outputDirURL = dirURL.replace(input, output); 240 | const outputURL = path.join(outputDirURL, fileName + ".html"); 241 | const result = toWeb({ 242 | data: new Buffer(toBinary(dragonBonesData)), 243 | textureAtlases: textureAtlasImages.map((v) => { 244 | const imagePath = path.join(dirURL, v); 245 | if (fs.existsSync(imagePath)) { 246 | return fs.readFileSync(imagePath); 247 | } 248 | 249 | return null; 250 | }), 251 | config: { 252 | isLocal: parameter !== "alone", 253 | isAlone: parameter === "alone", 254 | } 255 | }, type === "player"); 256 | 257 | if (!fs.existsSync(outputDirURL)) { 258 | fs.mkdirsSync(outputDirURL); 259 | } 260 | 261 | fs.writeFileSync(outputURL, new Buffer(result)); 262 | console.log(outputURL); 263 | 264 | if (deleteRaw) { 265 | fs.unlinkSync(file); 266 | 267 | if (textureAtlasFiles) { 268 | for (const textureAtlasFile of textureAtlasFiles) { 269 | fs.unlinkSync(textureAtlasFile); 270 | } 271 | } 272 | 273 | for (const textureAtlasImage of textureAtlasImages) { 274 | fs.unlinkSync(textureAtlasImage); 275 | } 276 | } 277 | 278 | break; 279 | } 280 | 281 | case "spine": { 282 | toNew(dragonBonesData, true); 283 | format(dragonBonesData); 284 | 285 | const outputDirURL = dirURL.replace(input, output); 286 | const result = toSpine(dragonBonesData, "3.6.0", !output); 287 | const suffix = outputDirURL === dirURL ? "_spine" : ""; 288 | dragonBonesData.name = fileName.replace("_ske", ""); 289 | console.log(dragonBonesData.name); 290 | 291 | if (!fs.existsSync(outputDirURL)) { 292 | fs.mkdirsSync(outputDirURL); 293 | } 294 | 295 | for (const spine of result.spines) { 296 | object.compress(spine, spft.compressConfig); 297 | const outputURL = path.join(outputDirURL, (result.spines.length > 1 ? dragonBonesData.name + "_" + spine.skeleton.name : dragonBonesData.name) + suffix + ".json"); 298 | delete spine.skeleton.name; // Delete keep name. 299 | fs.writeFileSync(outputURL, JSON.stringify(spine)); 300 | console.log(outputURL); 301 | } 302 | 303 | const outputURL = path.join(outputDirURL, dragonBonesData.name + suffix + ".atlas"); 304 | fs.writeFileSync(outputURL, result.textureAtlas); 305 | console.log(outputURL); 306 | 307 | if (deleteRaw) { 308 | fs.unlinkSync(file); 309 | 310 | if (textureAtlasFiles) { 311 | for (const textureAtlasFile of textureAtlasFiles) { 312 | fs.unlinkSync(textureAtlasFile); 313 | } 314 | } 315 | } 316 | 317 | if (outputDirURL !== dirURL) { 318 | let index = 0; 319 | for (const textureAtlasImage of textureAtlasImages) { 320 | const outputURL = path.join( 321 | path.dirname(textureAtlasImage.replace(input, output)), 322 | dragonBonesData.name + suffix + (textureAtlasImages.length > 1 ? "_" + index : "") + ".png" 323 | ); 324 | 325 | if (fs.existsSync(textureAtlasImage)) { 326 | if (deleteRaw) { 327 | fs.moveSync(textureAtlasImage, outputURL); 328 | } 329 | else { 330 | fs.copySync(textureAtlasImage, outputURL); 331 | } 332 | } 333 | 334 | console.log(outputURL); 335 | index++; 336 | } 337 | } 338 | break; 339 | } 340 | 341 | default: 342 | break; 343 | } 344 | } 345 | 346 | console.log("Convert complete."); 347 | } 348 | 349 | function getTextureAtlas(textureAtlasFile: string): dbft.TextureAtlas { 350 | const textureAtlas = new dbft.TextureAtlas(); 351 | object.copyObjectFrom(JSON.parse(fs.readFileSync(textureAtlasFile, "utf-8")), textureAtlas, dbft.copyConfig); 352 | 353 | return textureAtlas; 354 | } 355 | 356 | execute(); -------------------------------------------------------------------------------- /src/format/dragonBonesFormatV23.ts: -------------------------------------------------------------------------------- 1 | import * as utils from "../common/utils"; 2 | import { Transform, ColorTransform } from "./geom"; 3 | import * as dbft from "./dragonBonesFormat"; 4 | /** 5 | * DragonBones format v23. 6 | */ 7 | export class DragonBones { 8 | isGlobal: boolean = true; 9 | frameRate: number = 0; 10 | name: string = ""; 11 | version: string = ""; 12 | readonly armature: Armature[] = []; 13 | } 14 | 15 | export class Armature { 16 | name: string = ""; 17 | readonly bone: Bone[] = []; 18 | readonly skin: Skin[] = []; 19 | readonly animation: Animation[] = []; 20 | 21 | getBone(name: string): Bone | null { 22 | for (const bone of this.bone) { 23 | if (bone.name === name) { 24 | return bone; 25 | } 26 | } 27 | 28 | return null; 29 | } 30 | } 31 | 32 | export class Bone { 33 | name: string = ""; 34 | parent: string = ""; 35 | readonly transform: Transform = new Transform(); 36 | } 37 | 38 | export class Skin { 39 | name: string = "default"; 40 | readonly slot: Slot[] = []; 41 | } 42 | 43 | export class Slot { 44 | blendMode: string = dbft.BlendMode[dbft.BlendMode.Normal].toLowerCase(); 45 | z: number = 0; 46 | displayIndex: number = 0; 47 | name: string = ""; 48 | parent: string = ""; 49 | readonly colorTransform: ColorTransform = new ColorTransform(); 50 | readonly display: Display[] = []; 51 | } 52 | 53 | export abstract class Display { 54 | type: string = dbft.DisplayType[dbft.DisplayType.Image].toLowerCase(); 55 | name: string = ""; 56 | readonly transform: Transform = new Transform(); 57 | } 58 | 59 | export class ImageDisplay extends Display { 60 | constructor(isDefault: boolean = false) { 61 | super(); 62 | if (!isDefault) { 63 | this.type = dbft.DisplayType[dbft.DisplayType.Image].toLowerCase(); 64 | } 65 | } 66 | } 67 | 68 | export class ArmatureDisplay extends Display { 69 | constructor(isDefault: boolean = false) { 70 | super(); 71 | if (!isDefault) { 72 | this.type = dbft.DisplayType[dbft.DisplayType.Armature].toLowerCase(); 73 | } 74 | } 75 | } 76 | 77 | export abstract class Timeline { 78 | scale: number = 1.0; 79 | offset: number = 0.0; 80 | name: string = ""; 81 | readonly frame: T[] = []; 82 | } 83 | 84 | export class Animation { 85 | autoTween: boolean = true; 86 | tweenEasing: number | null = null; 87 | duration: number = 1; 88 | loop: number = 1; 89 | scale: number = 1.0; 90 | fadeInTime: number = 0.0; 91 | name: string = "default"; 92 | readonly frame: AnimationFrame[] = []; 93 | readonly timeline: AllTimeline[] = []; 94 | } 95 | 96 | export class AllTimeline extends Timeline { 97 | } 98 | 99 | export abstract class Frame { 100 | duration: number = 1; 101 | } 102 | 103 | export abstract class TweenFrame extends Frame { 104 | tweenEasing: number | null = null; 105 | readonly curve: number[] = []; 106 | } 107 | 108 | export class AnimationFrame extends Frame { 109 | action: string = ""; 110 | event: string = ""; 111 | sound: string = ""; 112 | } 113 | 114 | export class AllFrame extends TweenFrame { 115 | hide: boolean = false; 116 | tweenRotate: number = 0; 117 | displayIndex: number = 0; 118 | action: string = ""; 119 | event: string = ""; 120 | sound: string = ""; 121 | readonly transform: Transform = new Transform(); 122 | readonly colorTransform: ColorTransform = new ColorTransform(); 123 | } 124 | 125 | export const copyConfig = [ 126 | DragonBones, { 127 | armature: Armature, 128 | textureAtlas: dbft.TextureAtlas 129 | }, 130 | Armature, { 131 | bone: Bone, 132 | skin: Skin, 133 | animation: Animation 134 | }, 135 | Bone, { 136 | transform: Transform 137 | }, 138 | Slot, { 139 | display: [ 140 | function (display: any): { new(): Display } | null { 141 | let type = display.type; 142 | if (type !== undefined) { 143 | if (typeof type === "string") { 144 | type = utils.getEnumFormString(dbft.DisplayType, type, dbft.DisplayType.Image); 145 | } 146 | } 147 | else { 148 | type = dbft.DisplayType.Image; 149 | } 150 | 151 | switch (type) { 152 | case dbft.DisplayType.Image: 153 | return ImageDisplay; 154 | 155 | case dbft.DisplayType.Armature: 156 | return ArmatureDisplay; 157 | } 158 | 159 | return null; 160 | }, 161 | Function 162 | ] 163 | }, 164 | Skin, { 165 | slot: Slot 166 | }, 167 | Animation, { 168 | frame: AnimationFrame, 169 | timeline: AllTimeline 170 | }, 171 | AllTimeline, { 172 | frame: AllFrame 173 | }, 174 | AllFrame, { 175 | transform: Transform, 176 | colorTransform: ColorTransform 177 | }, 178 | dbft.TextureAtlas, { 179 | SubTexture: dbft.Texture 180 | } 181 | ]; -------------------------------------------------------------------------------- /src/format/geom.ts: -------------------------------------------------------------------------------- 1 | export const PI_D: number = Math.PI * 2.0; 2 | export const PI_H: number = Math.PI / 2.0; 3 | export const PI_Q: number = Math.PI / 4.0; 4 | export const RAD_DEG: number = 180.0 / Math.PI; 5 | export const DEG_RAD: number = Math.PI / 180.0; 6 | 7 | export interface Position { 8 | x: number; 9 | y: number; 10 | } 11 | 12 | export function normalizeRadian(value: number): number { 13 | value = (value + Math.PI) % (PI_D); 14 | value += value > 0.0 ? -Math.PI : Math.PI; 15 | 16 | return value; 17 | } 18 | 19 | export function normalizeDegree(value: number): number { 20 | value = (value + 180.0) % (180.0 * 2.0); 21 | value += value > 0.0 ? -180.0 : 180.0; 22 | 23 | return value; 24 | } 25 | 26 | export function distance(pA: Position, pB: Position): number { 27 | const dX = pB.x - pA.x; 28 | const dY = pB.y - pA.y; 29 | 30 | return Math.sqrt(dX * dX + dY * dY); 31 | } 32 | 33 | export function multiply(pA: Position, pB: Position, pC: Position): number { 34 | return ((pA.x - pC.x) * (pB.y - pC.y) - (pB.x - pC.x) * (pA.y - pC.y)); 35 | } 36 | 37 | export class Matrix { 38 | public constructor( 39 | public a: number = 1.0, public b: number = 0.0, 40 | public c: number = 0.0, public d: number = 1.0, 41 | public tx: number = 0.0, public ty: number = 0.0 42 | ) { 43 | } 44 | 45 | public copyFrom(value: Matrix): Matrix { 46 | this.a = value.a; 47 | this.b = value.b; 48 | this.c = value.c; 49 | this.d = value.d; 50 | this.tx = value.tx; 51 | this.ty = value.ty; 52 | 53 | return this; 54 | } 55 | 56 | public copyFromArray(value: number[], offset: number = 0): Matrix { 57 | this.a = value[offset]; 58 | this.b = value[offset + 1]; 59 | this.c = value[offset + 2]; 60 | this.d = value[offset + 3]; 61 | this.tx = value[offset + 4]; 62 | this.ty = value[offset + 5]; 63 | 64 | return this; 65 | } 66 | 67 | public identity(): Matrix { 68 | this.a = this.d = 1.0; 69 | this.b = this.c = 0.0; 70 | this.tx = this.ty = 0.0; 71 | 72 | return this; 73 | } 74 | 75 | public rotate(radian: number): Matrix { 76 | const u = Math.cos(radian); 77 | const v = Math.sin(radian); 78 | const ta = this.a; 79 | const tb = this.b; 80 | const tc = this.c; 81 | const td = this.d; 82 | const ttx = this.tx; 83 | const tty = this.ty; 84 | this.a = ta * u - tb * v; 85 | this.b = ta * v + tb * u; 86 | this.c = tc * u - td * v; 87 | this.d = tc * v + td * u; 88 | this.tx = ttx * u - tty * v; 89 | this.ty = ttx * v + tty * u; 90 | 91 | return this; 92 | } 93 | 94 | public concat(value: Matrix): Matrix { 95 | let aA = this.a * value.a; 96 | let bA = 0.0; 97 | let cA = 0.0; 98 | let dA = this.d * value.d; 99 | let txA = this.tx * value.a + value.tx; 100 | let tyA = this.ty * value.d + value.ty; 101 | 102 | if (this.b !== 0.0 || this.c !== 0.0) { 103 | aA += this.b * value.c; 104 | bA += this.b * value.d; 105 | cA += this.c * value.a; 106 | dA += this.c * value.b; 107 | } 108 | 109 | if (value.b !== 0.0 || value.c !== 0.0) { 110 | bA += this.a * value.b; 111 | cA += this.d * value.c; 112 | txA += this.ty * value.c; 113 | tyA += this.tx * value.b; 114 | } 115 | 116 | this.a = aA; 117 | this.b = bA; 118 | this.c = cA; 119 | this.d = dA; 120 | this.tx = txA; 121 | this.ty = tyA; 122 | 123 | return this; 124 | } 125 | 126 | public invert(): Matrix { 127 | let aA = this.a; 128 | let bA = this.b; 129 | let cA = this.c; 130 | let dA = this.d; 131 | const txA = this.tx; 132 | const tyA = this.ty; 133 | 134 | if (bA === 0.0 && cA === 0.0) { 135 | this.b = this.c = 0.0; 136 | if (aA === 0.0 || dA === 0.0) { 137 | this.a = this.b = this.tx = this.ty = 0.0; 138 | } 139 | else { 140 | aA = this.a = 1.0 / aA; 141 | dA = this.d = 1.0 / dA; 142 | this.tx = -aA * txA; 143 | this.ty = -dA * tyA; 144 | } 145 | 146 | return this; 147 | } 148 | 149 | let determinant = aA * dA - bA * cA; 150 | if (determinant === 0.0) { 151 | this.a = this.d = 1.0; 152 | this.b = this.c = 0.0; 153 | this.tx = this.ty = 0.0; 154 | 155 | return this; 156 | } 157 | 158 | determinant = 1.0 / determinant; 159 | let k = this.a = dA * determinant; 160 | bA = this.b = -bA * determinant; 161 | cA = this.c = -cA * determinant; 162 | dA = this.d = aA * determinant; 163 | this.tx = -(k * txA + cA * tyA); 164 | this.ty = -(bA * txA + dA * tyA); 165 | 166 | return this; 167 | } 168 | 169 | public transformPoint(x: number, y: number, result: Position, delta: boolean = false): void { 170 | result.x = this.a * x + this.c * y; 171 | result.y = this.b * x + this.d * y; 172 | 173 | if (!delta) { 174 | result.x += this.tx; 175 | result.y += this.ty; 176 | } 177 | } 178 | } 179 | 180 | export class Transform { 181 | 182 | public constructor( 183 | public x: number = 0.0, 184 | public y: number = 0.0, 185 | public skX: number = 0.0, 186 | public skY: number = 0.0, 187 | public scX: number = 1.0, 188 | public scY: number = 1.0, 189 | public pX: number = 0.0, // Deprecated. 190 | public pY: number = 0.0 // Deprecated. 191 | ) { 192 | } 193 | 194 | public toString(): string { 195 | return `${this.x}_${this.y}_${this.skX}_${this.skY}_${this.scX}_${this.scY}`; 196 | } 197 | 198 | public toFixed(): void { 199 | this.x = Number(this.x.toFixed(2)); 200 | this.y = Number(this.y.toFixed(2)); 201 | this.skX = Number(this.skX.toFixed(2)); 202 | this.skY = Number(this.skY.toFixed(2)); 203 | this.scX = Number(this.scX.toFixed(4)); 204 | this.scY = Number(this.scY.toFixed(4)); 205 | } 206 | 207 | public copyFrom(value: Transform): Transform { 208 | this.x = value.x; 209 | this.y = value.y; 210 | this.skX = value.skX; 211 | this.skY = value.skY; 212 | this.scX = value.scX; 213 | this.scY = value.scY; 214 | 215 | return this; 216 | } 217 | 218 | public equal(value: Transform): boolean { 219 | return this.x === value.x && this.y === value.y && 220 | this.skX === value.skY && this.skY === value.skY && 221 | this.scX === value.scX && this.scY === value.scY; 222 | } 223 | 224 | public identity(): Transform { 225 | this.x = this.y = this.skX = this.skY = 0.0; 226 | this.scX = this.scY = 1.0; 227 | 228 | return this; 229 | } 230 | 231 | public fromMatrix(matrix: Matrix): Transform { 232 | 233 | this.x = matrix.tx; 234 | this.y = matrix.ty; 235 | const backupScaleX = this.scX, backupScaleY = this.scY; 236 | let skX = Math.atan(-matrix.c / matrix.d); 237 | let skY = Math.atan(matrix.b / matrix.a); 238 | this.scX = (skY > -PI_Q && skY < PI_Q) ? matrix.a / Math.cos(skY) : matrix.b / Math.sin(skY); 239 | this.scY = (skX > -PI_Q && skX < PI_Q) ? matrix.d / Math.cos(skX) : -matrix.c / Math.sin(skX); 240 | 241 | if (backupScaleX >= 0.0 && this.scX < 0.0) { 242 | this.scX = -this.scX; 243 | skY = normalizeRadian(skY - Math.PI); 244 | } 245 | 246 | if (backupScaleY >= 0.0 && this.scY < 0.0) { 247 | this.scY = -this.scY; 248 | skX = normalizeRadian(skX - Math.PI); 249 | } 250 | 251 | this.skX = skX * RAD_DEG; 252 | this.skY = skY * RAD_DEG; 253 | 254 | return this; 255 | } 256 | 257 | public toMatrix(matrix: Matrix): Transform { 258 | const skX = this.skX * DEG_RAD; 259 | const skY = this.skY * DEG_RAD; 260 | matrix.a = Math.cos(skY) * this.scX; 261 | matrix.b = Math.sin(skY) * this.scX; 262 | matrix.c = -Math.sin(skX) * this.scY; 263 | matrix.d = Math.cos(skX) * this.scY; 264 | matrix.tx = this.x; 265 | matrix.ty = this.y; 266 | 267 | return this; 268 | } 269 | } 270 | 271 | export class ColorTransform { 272 | public constructor( 273 | public aM: number = 100, 274 | public rM: number = 100, public gM: number = 100, public bM: number = 100, 275 | public aO: number = 0, 276 | public rO: number = 0, public gO: number = 0, public bO: number = 0 277 | ) { 278 | } 279 | 280 | public toString(): string { 281 | return `${this.aM}_${this.rM}_${this.gM}_${this.bM}_${this.aO}_${this.rO}_${this.gO}_${this.bO}`; 282 | } 283 | 284 | public toFixed(): void { 285 | this.aM = Math.round(this.aM); 286 | this.rM = Math.round(this.rM); 287 | this.gM = Math.round(this.gM); 288 | this.bM = Math.round(this.bM); 289 | this.aO = Math.round(this.aO); 290 | this.rO = Math.round(this.rO); 291 | this.gO = Math.round(this.gO); 292 | this.bO = Math.round(this.bO); 293 | } 294 | 295 | public copyFrom(value: ColorTransform): void { 296 | this.aM = value.aM; 297 | this.rM = value.rM; 298 | this.gM = value.gM; 299 | this.bM = value.bM; 300 | this.aO = value.aO; 301 | this.rO = value.rO; 302 | this.gO = value.gO; 303 | this.bO = value.bO; 304 | } 305 | 306 | public copyFromRGBA(value: number): void { 307 | this.rM = Math.round(((0xFF000000 & value) >>> 24) / 255 * 100); 308 | this.gM = Math.round(((0x00FF0000 & value) >>> 16) / 255 * 100); 309 | this.bM = Math.round(((0x0000FF00 & value) >>> 8) / 255 * 100); 310 | this.aM = Math.round((0x000000FF & value) / 255 * 100); 311 | } 312 | 313 | public identity(): void { 314 | this.aM = this.rM = this.gM = this.bM = 100; 315 | this.aO = this.rO = this.gO = this.bO = 0; 316 | } 317 | 318 | public equal(value: ColorTransform): boolean { 319 | return this.aM === value.aM && this.rM === value.rM && this.gM === value.gM && this.bM === value.bM && 320 | this.aO === value.aO && this.rO === value.rO && this.gO === value.gO && this.bO === value.bO; 321 | } 322 | } 323 | 324 | export class Point implements Position { 325 | public constructor( 326 | public x: number = 0.0, 327 | public y: number = 0.0 328 | ) { 329 | } 330 | 331 | public toString(): string { 332 | return "[object Point x: " + this.x + " y: " + this.y + " ]"; 333 | } 334 | 335 | public clear(): void { 336 | this.x = this.y = 0.0; 337 | } 338 | 339 | public copyFrom(value: Position): this { 340 | this.x = value.x; 341 | this.y = value.y; 342 | 343 | return this; 344 | } 345 | 346 | public setTo(x: number, y: number): this { 347 | this.x = x; 348 | this.y = y; 349 | 350 | return this; 351 | } 352 | 353 | public polar(length: number, radian: number): this { 354 | this.x = length * Math.cos(radian); 355 | this.y = length * Math.sin(radian); 356 | 357 | return this; 358 | } 359 | } 360 | 361 | export class Rectangle implements Position { 362 | public constructor( 363 | public x: number = 0.0, 364 | public y: number = 0.0, 365 | public width: number = 0.0, 366 | public height: number = 0.0 367 | ) { 368 | } 369 | 370 | public toString(): string { 371 | return "[object Rectangle x: " + this.x + " y: " + this.y + " width: " + this.width + " height: " + this.height + " ]"; 372 | } 373 | 374 | public toFixed(): void { 375 | this.x = Number(this.x.toFixed(2)); 376 | this.y = Number(this.y.toFixed(2)); 377 | this.width = Number(this.width.toFixed(2)); 378 | this.height = Number(this.height.toFixed(2)); 379 | } 380 | 381 | public clear(): void { 382 | this.x = this.y = this.width = this.height = 0.0; 383 | } 384 | 385 | public copyFrom(value: this): this { 386 | this.x = value.x; 387 | this.y = value.y; 388 | this.width = value.width; 389 | this.height = value.height; 390 | 391 | return this; 392 | } 393 | 394 | public setTo(x: number, y: number, width: number, height: number): this { 395 | this.x = x; 396 | this.y = y; 397 | this.width = width; 398 | this.height = height; 399 | 400 | return this; 401 | } 402 | } 403 | 404 | export const helpMatrixA: Matrix = new Matrix(); 405 | export const helpMatrixB: Matrix = new Matrix(); 406 | export const helpTransformA: Transform = new Transform(); 407 | export const helpTransformB: Transform = new Transform(); 408 | export const helpPointA: Point = new Point(); 409 | export const helpPointB: Point = new Point(); -------------------------------------------------------------------------------- /src/format/resFormat.ts: -------------------------------------------------------------------------------- 1 | export enum ResourceType { 2 | JSON, 3 | BIN, 4 | Image 5 | } 6 | 7 | class ResourceGroup { 8 | public name: string = ""; 9 | public keys: string = ""; 10 | } 11 | 12 | class Resource { 13 | public name: string = ""; 14 | public type: string = ""; 15 | public url: string = ""; 16 | } 17 | 18 | export class ResourceJSON { 19 | public readonly resources: Resource[] = []; 20 | public readonly groups: ResourceGroup[] = []; 21 | 22 | public clear(): void { 23 | this.resources.length = 0; 24 | this.groups.length = 0; 25 | } 26 | 27 | public getResource(name: string): Resource | null { 28 | for (let i = 0, l = this.resources.length; i < l; ++i) { 29 | const resource = this.resources[i]; 30 | if (resource.name === name) { 31 | return resource; 32 | } 33 | } 34 | return null; 35 | } 36 | 37 | public getResourceGroup(name: string): ResourceGroup | null { 38 | for (let i = 0, l = this.groups.length; i < l; ++i) { 39 | const group = this.groups[i]; 40 | if (group.name === name) { 41 | return group; 42 | } 43 | } 44 | return null; 45 | } 46 | 47 | public addResource(name: string, type: ResourceType, url: string, ...groupNames: string[]): void { 48 | const resource = this.getResource(name) || new Resource(); 49 | 50 | resource.name = name; 51 | resource.type = ResourceType[type].toLowerCase(); 52 | resource.url = url; 53 | 54 | if (this.resources.indexOf(resource) < 0) { 55 | this.resources.push(resource); 56 | } 57 | 58 | if (groupNames && groupNames.length) { 59 | for (let i = 0, l = groupNames.length; i < l; ++i) { 60 | const groupName = groupNames[i]; 61 | const resourceGroup = this.getResourceGroup(groupName) || new ResourceGroup(); 62 | resourceGroup.name = groupName; 63 | 64 | if (resourceGroup.keys) { 65 | const keys = resourceGroup.keys.split(","); 66 | if (keys.indexOf(name) < 0) { 67 | keys.push(name); 68 | } 69 | 70 | resourceGroup.keys = keys.join(","); 71 | } 72 | else { 73 | resourceGroup.keys = name; 74 | } 75 | 76 | if (this.groups.indexOf(resourceGroup) < 0) { 77 | this.groups.push(resourceGroup); 78 | } 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /src/format/spineFormat.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Spine format. 3 | */ 4 | export type TransformType = "normal" | "onlyTranslation" | "noRotationOrReflection" | "noScale" | "noScaleOrReflection"; 5 | export type AttachmentType = "region" | "mesh" | "linkedmesh" | "boundingbox" | "path" | "point" | "clipping" | "skinnedmesh"; 6 | export type BlendMode = "normal" | "additive" | "multiply" | "screen"; 7 | 8 | type Map = { 9 | [key: string]: T; 10 | }; 11 | 12 | export function isSpineString(string: string): boolean { 13 | return string.indexOf("skeleton") > 0 && string.indexOf("spine") > 0; 14 | } 15 | 16 | export class Spine { 17 | readonly skeleton: Skeleton = new Skeleton(); 18 | readonly bones: Bone[] = []; 19 | readonly slots: Slot[] = []; 20 | readonly ik: IKConstraint[] = []; 21 | readonly transform: TransformConstraint[] = []; 22 | readonly path: PathConstraint[] = []; 23 | readonly skins: Map>> = {}; 24 | readonly animations: Map = {}; 25 | readonly events: Map = {}; 26 | } 27 | 28 | export class Skeleton { 29 | width: number = 0.00; 30 | height: number = 0.00; 31 | fps: number = 30; // Nonessential. 32 | spine: string = ""; 33 | hash: string = ""; // Nonessential. 34 | images: string = "./images/"; // Nonessential. 35 | name: string = ""; // Keep DragonBones armature name. 36 | } 37 | 38 | export class Bone { 39 | inheritRotation: boolean = true; 40 | inheritScale: boolean = true; 41 | length: number = 0; 42 | color: number = 0x989898FF; // Nonessential. 43 | x: number = 0.00; 44 | y: number = 0.00; 45 | rotation: number = 0.00; 46 | shearX: number = 0.00; 47 | shearY: number = 0.00; 48 | scaleX: number = 1.00; 49 | scaleY: number = 1.00; 50 | name: string = ""; 51 | parent: string = ""; 52 | transform: TransformType = "normal"; 53 | } 54 | 55 | export class Slot { 56 | name: string = ""; 57 | bone: string = ""; 58 | color: string = "FFFFFFFF"; 59 | dark: string = "FFFFFF"; 60 | blend: BlendMode = "normal"; 61 | attachment: string = ""; 62 | } 63 | 64 | export class IKConstraint { 65 | bendPositive: boolean = true; 66 | order: number = 0; 67 | mix: number = 1.00; 68 | name: string = ""; 69 | target: string = ""; 70 | readonly bones: string[] = []; 71 | } 72 | 73 | export class TransformConstraint { 74 | local: boolean = false; 75 | relative: boolean = false; 76 | order: number = 0; 77 | x: number = 0.00; 78 | y: number = 0.00; 79 | rotation: number = 0.00; 80 | shearX: number = 0.00; 81 | shearY: number = 0.00; 82 | scaleX: number = 0.00; 83 | scaleY: number = 0.00; 84 | translateMix: number = 1.00; 85 | rotateMix: number = 1.00; 86 | scaleMix: number = 1.00; 87 | shearMix: number = 1.00; 88 | name: string = ""; 89 | bone: string = ""; 90 | target: string = ""; 91 | } 92 | 93 | export class PathConstraint { 94 | positionMode: "fixed" | "percent" = "percent"; 95 | spacingMode: "length" | "fixed" | "percent" = "length"; 96 | rotateMode: "tangent" | "chain" | "chain scale" = "tangent"; 97 | order: number = 0; 98 | rotation: number = 0.00; 99 | position: number = 0.00; 100 | spacing: number = 0.00; 101 | translateMix: number = 1.00; 102 | rotateMix: number = 1.00; 103 | name: string = ""; 104 | target: string = ""; 105 | readonly bones: string[] = []; 106 | } 107 | 108 | export abstract class Attachment { 109 | type: AttachmentType; 110 | color: string = "FFFFFFFF"; 111 | name: string = ""; 112 | } 113 | 114 | export class RegionAttachment extends Attachment { 115 | width: number = 0; 116 | height: number = 0; 117 | x: number = 0.00; 118 | y: number = 0.00; 119 | rotation: number = 0.00; 120 | scaleX: number = 1.00; 121 | scaleY: number = 1.00; 122 | path: string = ""; 123 | 124 | constructor(isDefault: boolean = false) { 125 | super(); 126 | if (!isDefault) { 127 | // this.type = "region"; 128 | } 129 | } 130 | } 131 | 132 | export class MeshAttachment extends Attachment { 133 | width: number = 0; // Nonessential. 134 | height: number = 0; // Nonessential. 135 | hull: number = 0; 136 | path: string = ""; 137 | triangles: number[] = []; 138 | uvs: number[] = []; 139 | edges: number[] = []; // Nonessential. 140 | vertices: number[] = []; 141 | 142 | constructor(isDefault: boolean = false) { 143 | super(); 144 | if (!isDefault) { 145 | this.type = "mesh"; 146 | } 147 | } 148 | } 149 | 150 | export class LinkedMeshAttachment extends Attachment { 151 | deform: boolean = true; 152 | width: number = 0; // Nonessential. 153 | height: number = 0; // Nonessential. 154 | path: string = ""; 155 | skin: string = ""; 156 | parent: string = ""; 157 | 158 | constructor(isDefault: boolean = false) { 159 | super(); 160 | if (!isDefault) { 161 | this.type = "linkedmesh"; 162 | } 163 | } 164 | } 165 | 166 | export class BoundingBoxAttachment extends Attachment { 167 | vertexCount: number = 0; 168 | color: string = "60F000FF"; 169 | vertices: number[] = []; 170 | 171 | constructor(isDefault: boolean = false) { 172 | super(); 173 | if (!isDefault) { 174 | this.type = "boundingbox"; 175 | } 176 | } 177 | } 178 | 179 | export class PathAttachment extends Attachment { 180 | color: string = "FF7F00FF"; 181 | closed: boolean = false; 182 | constantSpeed: boolean = true; 183 | vertexCount: number = 0; 184 | lengths: number[] = []; 185 | vertices: number[] = []; 186 | 187 | constructor(isDefault: boolean = false) { 188 | super(); 189 | if (!isDefault) { 190 | this.type = "path"; 191 | } 192 | } 193 | } 194 | 195 | export class PointAttachment extends Attachment { 196 | x: number = 0.0; 197 | y: number = 0.0; 198 | color: string = "F1F100FF"; 199 | rotation: number = 0.0; 200 | 201 | constructor(isDefault: boolean = false) { 202 | super(); 203 | if (!isDefault) { 204 | this.type = "point"; 205 | } 206 | } 207 | } 208 | 209 | export class ClippingAttachment extends Attachment { 210 | vertexCount: number = 0.0; 211 | end: string = ""; 212 | color: string = "CE3A3AFF"; 213 | vertices: number[] = []; 214 | 215 | constructor(isDefault: boolean = false) { 216 | super(); 217 | if (!isDefault) { 218 | this.type = "clipping"; 219 | } 220 | } 221 | } 222 | 223 | export class Event { 224 | int: number = 0; 225 | float: number = 0.0; 226 | string: string = ""; 227 | name: string = ""; // Keep to alive. 228 | } 229 | 230 | export class Animation { 231 | readonly bones: Map = {}; 232 | readonly slots: Map = {}; 233 | readonly ik: Map = {}; 234 | readonly transform: Map = {}; 235 | readonly deform: Map>> = {}; 236 | readonly ffd: Map>> = {}; // Deprecated. 237 | readonly events: EventFrame[] = []; 238 | readonly drawOrder: DrawOrderFrame[] = []; 239 | } 240 | 241 | export class BoneTimelines { 242 | readonly translate: TranslateFrame[] = []; 243 | readonly rotate: RotateFrame[] = []; 244 | readonly scale: ScaleFrame[] = []; 245 | readonly shear: ShearFrame[] = []; 246 | } 247 | 248 | export class SlotTimelines { 249 | readonly attachment: AttachmentFrame[] = []; 250 | readonly color: ColorFrame[] = []; 251 | } 252 | 253 | export class Frame { 254 | time: number = 0.0; 255 | 256 | constructor(isDefault: boolean = false) { 257 | if (isDefault) { 258 | this.time = NaN; // spine import data bug 259 | } 260 | } 261 | } 262 | 263 | export class TweenFrame extends Frame { 264 | curve: number[] | "linear" | "stepped" = "linear"; 265 | } 266 | 267 | export class TranslateFrame extends TweenFrame { 268 | x: number = 0.0; 269 | y: number = 0.0; 270 | } 271 | 272 | export class RotateFrame extends TweenFrame { 273 | angle: number = 0.0; 274 | 275 | constructor(isDefault: boolean = false) { 276 | super(isDefault); 277 | 278 | if (isDefault) { 279 | this.angle = NaN; // Spine import data bug. 280 | } 281 | } 282 | } 283 | 284 | export class ShearFrame extends TweenFrame { 285 | x: number = 0.0; 286 | y: number = 0.0; 287 | } 288 | 289 | export class ScaleFrame extends TweenFrame { 290 | x: number = 1.0; 291 | y: number = 1.0; 292 | 293 | constructor(isDefault: boolean = false) { 294 | super(isDefault); 295 | 296 | if (isDefault) { 297 | this.x = NaN; // spine import data bug 298 | this.y = NaN; // spine import data bug 299 | } 300 | } 301 | } 302 | 303 | export class AttachmentFrame extends Frame { 304 | name: string = ""; 305 | 306 | constructor(isDefault: boolean = false) { 307 | super(isDefault); 308 | 309 | if (isDefault) { 310 | this.name = null as any; // Spine import data bug. 311 | } 312 | } 313 | } 314 | 315 | export class ColorFrame extends TweenFrame { 316 | color: string = "FFFFFFFF"; 317 | 318 | constructor(isDefault: boolean = false) { 319 | super(isDefault); 320 | 321 | if (isDefault) { 322 | this.color = null as any; // Spine import data bug. 323 | } 324 | } 325 | } 326 | 327 | export class IKConstraintFrame extends TweenFrame { 328 | bendPositive: boolean = true; 329 | mix: number = 1.0; 330 | } 331 | 332 | export class TransformConstraintFrame extends TweenFrame { 333 | rotateMix: number = 1.0; 334 | translateMix: number = 1.0; 335 | scaleMix: number = 1.0; 336 | shearMix: number = 1.0; 337 | } 338 | 339 | export class DeformFrame extends TweenFrame { 340 | offset: number = 0; 341 | vertices: number[] = []; 342 | } 343 | 344 | export class EventFrame extends Frame { 345 | int: number = 0; 346 | float: number = 0.0; 347 | string: string = ""; 348 | name: string = ""; 349 | } 350 | 351 | export class DrawOrderFrame extends Frame { 352 | offsets: { slot: string, offset: number }[] = []; 353 | } 354 | 355 | export const copyConfig = [ 356 | Spine, { 357 | bones: Bone, 358 | slots: Slot, 359 | ik: IKConstraint, 360 | transform: TransformConstraint, 361 | path: PathConstraint, 362 | skins: [[[[ 363 | function (attachment: any): { new(): Attachment } | null { 364 | const type: AttachmentType = attachment.type || "region"; 365 | switch (type) { 366 | case "region": 367 | return RegionAttachment; 368 | 369 | case "mesh": 370 | case "skinnedmesh": 371 | return MeshAttachment; 372 | 373 | case "linkedmesh": 374 | return LinkedMeshAttachment; 375 | 376 | case "boundingbox": 377 | return BoundingBoxAttachment; 378 | 379 | case "path": 380 | return PathAttachment; 381 | 382 | case "point": 383 | return PointAttachment; 384 | 385 | case "clipping": 386 | return ClippingAttachment; 387 | 388 | default: 389 | return null; 390 | } 391 | }, 392 | Function 393 | ]]]], 394 | animations: [Animation], 395 | events: [Event], 396 | }, 397 | Animation, { 398 | bones: [BoneTimelines], 399 | slots: [SlotTimelines], 400 | ik: [ 401 | IKConstraintFrame, 402 | Array 403 | ], 404 | transform: [ 405 | TransformConstraintFrame, 406 | Array 407 | ], 408 | deform: [[[ 409 | DeformFrame, 410 | Array 411 | ]]], 412 | ffd: [[[ 413 | DeformFrame, 414 | Array 415 | ]]], 416 | events: EventFrame, 417 | drawOrder: DrawOrderFrame, 418 | }, 419 | BoneTimelines, { 420 | rotate: RotateFrame, 421 | translate: TranslateFrame, 422 | scale: ScaleFrame, 423 | shear: ShearFrame 424 | }, 425 | SlotTimelines, { 426 | attachment: AttachmentFrame, 427 | color: ColorFrame 428 | } 429 | ]; 430 | 431 | export const compressConfig = [ 432 | new Spine(), 433 | new Skeleton(), 434 | new Bone(), 435 | new Slot(), 436 | new IKConstraint(), 437 | new TransformConstraint(), 438 | new PathConstraint(), 439 | 440 | new RegionAttachment(true), 441 | new MeshAttachment(true), 442 | new LinkedMeshAttachment(true), 443 | new BoundingBoxAttachment(true), 444 | new PathAttachment(true), 445 | new PointAttachment(true), 446 | new ClippingAttachment(true), 447 | 448 | new Event(), 449 | 450 | new Animation(), 451 | new BoneTimelines(), 452 | new SlotTimelines(), 453 | new TranslateFrame(true), 454 | new RotateFrame(true), 455 | new ShearFrame(true), 456 | new ScaleFrame(true), 457 | new AttachmentFrame(true), 458 | new ColorFrame(true), 459 | new IKConstraintFrame(true), 460 | new TransformConstraintFrame(true), 461 | new DeformFrame(true), 462 | new EventFrame(true), 463 | new DrawOrderFrame(true), 464 | ]; -------------------------------------------------------------------------------- /src/format/utils.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | 4 | export function getTextureAtlases(filePath: string, rawName: string | null = null, suffix: string = "texture", fileType: ".json" | ".png" = ".json"): string[] { // TODO 5 | const folder = path.dirname(filePath); 6 | let name = rawName !== null ? rawName : path.basename(filePath, path.extname(filePath)); 7 | let index = 0; 8 | let textureAtlasName = ""; 9 | let textureAtlas = ""; 10 | let textureAtlases = new Array(); 11 | 12 | textureAtlasName = name ? name + (suffix ? "_" + suffix : suffix) : suffix; 13 | textureAtlas = path.join(folder, textureAtlasName + fileType); 14 | 15 | if (fs.existsSync(textureAtlas)) { 16 | textureAtlases.push(textureAtlas); 17 | 18 | return textureAtlases; 19 | } 20 | 21 | while (true) { 22 | textureAtlasName = (name ? name + (suffix ? "_" + suffix : suffix) : suffix) + "_" + (index++); 23 | textureAtlas = path.join(folder, textureAtlasName + fileType); 24 | 25 | if (fs.existsSync(textureAtlas)) { 26 | textureAtlases.push(textureAtlas); 27 | } 28 | else if (index > 1) { 29 | break; 30 | } 31 | } 32 | 33 | if (textureAtlases.length > 0 || rawName !== null) { 34 | return textureAtlases; 35 | } 36 | 37 | textureAtlases = getTextureAtlases(filePath, "", suffix); 38 | if (textureAtlases.length > 0) { 39 | return textureAtlases; 40 | } 41 | 42 | index = name.lastIndexOf("_"); 43 | if (index >= 0) { 44 | name = name.substring(0, index); 45 | 46 | textureAtlases = getTextureAtlases(filePath, name, suffix); 47 | if (textureAtlases.length > 0) { 48 | return textureAtlases; 49 | } 50 | 51 | textureAtlases = getTextureAtlases(filePath, name, ""); 52 | if (textureAtlases.length > 0) { 53 | return textureAtlases; 54 | } 55 | } 56 | 57 | if (suffix === "atlas") { 58 | return textureAtlases; 59 | } 60 | 61 | if (suffix !== "tex") { 62 | textureAtlases = getTextureAtlases(filePath, null, "tex"); 63 | } 64 | else if (suffix !== "atlas" as any) { 65 | textureAtlases = getTextureAtlases(filePath, null, "atlas"); 66 | } 67 | 68 | return textureAtlases; 69 | } -------------------------------------------------------------------------------- /src/helper/helper.ts: -------------------------------------------------------------------------------- 1 | let internalID: number = -1; 2 | const actions: any = {}; 3 | const canvasA = document.getElementById("helpCanvasA") as HTMLCanvasElement; 4 | const canvasB = document.getElementById("helpCanvasB") as HTMLCanvasElement; 5 | const contextA = canvasA.getContext("2d") as CanvasRenderingContext2D; 6 | const contextB = canvasB.getContext("2d") as CanvasRenderingContext2D; 7 | 8 | function load(url: string, data: any = null, callback: ((json: any) => void) | null = null): void { 9 | const xhr = new XMLHttpRequest(); 10 | 11 | if (callback) { 12 | xhr.addEventListener("load", function () { 13 | callback(JSON.parse(xhr.response)); 14 | }); 15 | xhr.addEventListener("error", stopHelper); 16 | xhr.addEventListener("timeout", stopHelper); 17 | } 18 | 19 | if (data) { 20 | xhr.open("POST", `${url}?v=${Math.random()}`, true); 21 | xhr.send(JSON.stringify(data)); 22 | } 23 | else { 24 | xhr.open("GET", `${url}?v=${Math.random()}`, true); 25 | xhr.send(); 26 | } 27 | } 28 | 29 | function startHelper(): void { 30 | const actionKey = "modify_spine_textureatlas"; 31 | actions[actionKey] = function (input: any) { 32 | const image = document.createElement("img"); 33 | image.src = "data:image/png;base64," + input.data.texture; 34 | image.onload = function (): void { 35 | canvasA.width = image.width; 36 | canvasA.height = image.height; 37 | contextA.drawImage(image, 0, 0); 38 | 39 | for (const subTexture of input.data.config.SubTexture) { 40 | if (!subTexture.rotated) { 41 | continue; 42 | } 43 | 44 | subTexture.x = subTexture.x || 0.0; 45 | subTexture.y = subTexture.y || 0.0; 46 | subTexture.width = subTexture.width || 0.0; 47 | subTexture.height = subTexture.height || 0.0; 48 | canvasB.width = subTexture.width; 49 | canvasB.height = subTexture.height; 50 | contextB.save(); 51 | contextB.translate(subTexture.x + subTexture.width, subTexture.y + subTexture.height); 52 | contextB.rotate(Math.PI); 53 | contextB.drawImage(image, 0, 0); 54 | contextB.restore(); 55 | 56 | const imageData = contextB.getImageData(0, 0, subTexture.width, subTexture.height); 57 | contextA.putImageData(imageData, subTexture.x, subTexture.y); 58 | } 59 | 60 | input.data.texture = canvasA.toDataURL("image/png").replace("data:image/png;base64,", ""); 61 | load("../" + actionKey, input); 62 | }; 63 | }; 64 | 65 | internalID = setInterval( 66 | function () { 67 | load("../get_input", null, (result: any): void => { 68 | const input = result.data; 69 | 70 | if (input) { 71 | const action = actions[input.type]; 72 | 73 | if (action) { 74 | action(input); 75 | } 76 | else { 77 | console.log("Unknown action:", input.type); 78 | } 79 | } 80 | else { 81 | console.log(result.code, result.message); 82 | } 83 | }); 84 | }, 85 | 1000 86 | ) as any; 87 | } 88 | 89 | function stopHelper(): void { 90 | clearInterval(internalID); 91 | window.close(); 92 | } -------------------------------------------------------------------------------- /src/helper/helperRemote.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs-extra"; 2 | import * as http from "http"; 3 | import * as nodeUtils from "../common/nodeUtils"; 4 | import { Code, Gate } from "../common/server"; 5 | 6 | type Input = { 7 | id?: number; 8 | type: string; 9 | data: any; 10 | }; 11 | 12 | let inputCount: number = 0; 13 | const inputs: Input[] = []; 14 | const inputeds: Input[] = []; 15 | const gate = new Gate(); 16 | 17 | gate.actions["/modify_spine_textureatlas"] = (request, response) => { 18 | let jsonString = ""; 19 | 20 | request.addListener("data", (data: any) => { 21 | jsonString += data; 22 | }); 23 | 24 | request.addListener("end", () => { 25 | request.removeAllListeners(); 26 | 27 | let result: Input; 28 | try { 29 | result = JSON.parse(jsonString); 30 | } 31 | catch (error) { 32 | gate.responseEnd(response, Code.JSONError, Code[Code.JSONError]); 33 | return; 34 | } 35 | 36 | const input = getAndRemoveInputs(result.id as number); 37 | const textureData = new Buffer(result.data.texture, "base64"); 38 | fs.writeFileSync(input.data.file, textureData); 39 | gate.responseEnd(response, Code.Success, Code[Code.Success], false); 40 | 41 | console.log("Modify texture atlas.", input.data.file); 42 | if (inputs.length === 0 && inputeds.length === 0) { 43 | stop(); 44 | } 45 | }); 46 | }; 47 | 48 | gate.actions["/get_input"] = (request, response) => { 49 | // let jsonString = ""; 50 | 51 | request.addListener("data", () => { 52 | // jsonString += data; 53 | }); 54 | 55 | request.addListener("end", () => { 56 | request.removeAllListeners(); 57 | 58 | // let result: Input; 59 | // try { 60 | // result = JSON.parse(jsonString); 61 | // } 62 | // catch (error) { 63 | // gate.responseEnd(response, Code.JSONError, Code[Code.JSONError]); 64 | // return; 65 | // } 66 | 67 | if (inputs.length > 0) { 68 | const input = inputs.shift() as any; 69 | inputeds.push(input); 70 | gate.responseEnd(response, Code.Success, Code[Code.Success], input); 71 | } 72 | else { 73 | gate.responseEnd(response, Code.Success, Code[Code.Success], false); 74 | } 75 | }); 76 | }; 77 | 78 | export function start(): void { 79 | console.log("Helper start."); 80 | 81 | const portServer = http.createServer(); 82 | portServer.listen(0, () => { 83 | const port = (portServer.address() as any).port; 84 | portServer.close(); 85 | gate.start("dragonbones", port, "/dragonbones_helper"); 86 | nodeUtils.open(`http://${nodeUtils.findIP()}:${port}/dragonbones_helper/resource/helper.html`); 87 | }); 88 | } 89 | 90 | export function stop(): void { 91 | console.log("Helper stop."); 92 | gate.stop(); 93 | } 94 | 95 | export function hasInput(): boolean { 96 | return inputs.length > 0 || inputeds.length > 0; 97 | } 98 | 99 | export function addInput(input: Input): void { 100 | input.id = inputCount++; 101 | inputs.push(input); 102 | } 103 | 104 | function getAndRemoveInputs(id: number): Input { 105 | for (let i = 0, l = inputeds.length; i < l; ++i) { 106 | const input = inputeds[i]; 107 | if (input.id === id) { 108 | inputeds.splice(i, 1); 109 | return input; 110 | } 111 | } 112 | 113 | throw new Error("Never"); 114 | } -------------------------------------------------------------------------------- /src/remote.ts: -------------------------------------------------------------------------------- 1 | import * as http from "http"; 2 | import * as object from "./common/object"; 3 | import * as nodeUtils from "./common/nodeUtils"; 4 | import { Code, Gate } from "./common/server"; 5 | import * as dbft from "./format/dragonBonesFormat"; 6 | import * as spft from "./format/spineFormat"; 7 | import fromSpine from "./action/fromSpine"; 8 | import toFormat from "./action/toFormat"; 9 | import toNew from "./action/toNew"; 10 | import toBinary from "./action/toBinary"; 11 | import toWeb from "./action/toWeb"; 12 | // import toSpine from "./action/toSpine"; 13 | import format from "./action/formatFormat"; 14 | 15 | type Input = { 16 | from: "spine" | "cocos"; 17 | to: "binary" | "new" | "v45" | "player" | "viewer" | "spine"; 18 | data: string; // DragonBones JSON string | spine JSON string { data: string, textureAtlas: string } 19 | compress?: boolean; 20 | forPro?: boolean; 21 | textureAtlases?: string[]; // PNG Base64 string. 22 | config?: any; // { web: web config, spine: spine verison } 23 | }; 24 | 25 | type FormatType = "string" | "base64" | "binary"; 26 | 27 | class Output { 28 | public format: FormatType; 29 | public name: string; 30 | public suffix: string; 31 | public data: any; 32 | 33 | public constructor(data: any, name: string = "", suffix: string = "", format: FormatType = "string") { 34 | this.data = data; 35 | this.format = format; 36 | this.name = name; 37 | this.suffix = suffix; 38 | } 39 | } 40 | 41 | const gate = new Gate(); 42 | gate.actions["/convert"] = (request, response) => { 43 | let jsonString = ""; 44 | 45 | request.addListener("data", (data: any) => { 46 | jsonString += data; 47 | }); 48 | 49 | request.addListener("end", () => { 50 | request.removeAllListeners(); 51 | 52 | let input: Input; 53 | try { 54 | input = JSON.parse(jsonString); 55 | } 56 | catch (error) { 57 | gate.responseEnd(response, Code.JSONError, Code[Code.JSONError], jsonString); 58 | return; 59 | } 60 | 61 | try { 62 | if (input.from) { 63 | switch (input.from) { 64 | case "spine": { 65 | let spineInput: { name: string, data: string, textureAtlas: string } | null = null; 66 | try { 67 | spineInput = JSON.parse(input.data); 68 | } 69 | catch (error) { 70 | } 71 | 72 | if (!spineInput) { 73 | gate.responseEnd(response, Code.DataError, Code[Code.DataError]); 74 | return; 75 | } 76 | 77 | const spine = new spft.Spine(); 78 | object.copyObjectFrom(JSON.parse(spineInput.data), spine, spft.copyConfig); 79 | const result = fromSpine({ name: spineInput.name, data: spine, textureAtlas: spineInput.textureAtlas }, true); 80 | format(result); 81 | object.compress(result, dbft.compressConfig); 82 | gate.responseEnd(response, Code.Success, Code[Code.Success], result); 83 | 84 | // TODO 85 | break; 86 | } 87 | 88 | case "cocos": { 89 | break; 90 | } 91 | } 92 | } 93 | else if (input.to) { 94 | let dragonBonesData: dbft.DragonBones | null = null; 95 | try { 96 | dragonBonesData = toFormat( 97 | input.data, 98 | () => { 99 | return []; 100 | } 101 | ); 102 | } 103 | catch (error) { 104 | } 105 | 106 | if (!dragonBonesData) { 107 | gate.responseEnd(response, Code.DataError, Code[Code.DataError], input.data); 108 | return; 109 | } 110 | 111 | const toOutput: Output[] = []; 112 | 113 | switch (input.to) { 114 | case "binary": { 115 | toNew(dragonBonesData, true); 116 | format(dragonBonesData); 117 | const result = new Buffer(toBinary(dragonBonesData)).toString("base64"); 118 | 119 | toOutput.push( 120 | new Output( 121 | result, 122 | dragonBonesData.name, 123 | "_ske.dbbin", 124 | "base64" 125 | ) 126 | ); 127 | break; 128 | } 129 | 130 | case "new": { 131 | toNew(dragonBonesData, false); 132 | format(dragonBonesData); 133 | 134 | if (input.compress !== false) { 135 | object.compress(dragonBonesData, dbft.compressConfig); 136 | } 137 | 138 | const result = JSON.stringify(dragonBonesData); 139 | toOutput.push( 140 | new Output( 141 | result, 142 | dragonBonesData.name, 143 | "_ske.json", 144 | "string" 145 | ) 146 | ); 147 | break; 148 | } 149 | 150 | case "player": 151 | case "viewer": { 152 | toNew(dragonBonesData, true); 153 | format(dragonBonesData); 154 | 155 | const result = toWeb( 156 | { 157 | data: new Buffer(toBinary(dragonBonesData)), 158 | textureAtlases: input.textureAtlases ? input.textureAtlases.map((v) => { 159 | return new Buffer(v, "base64"); 160 | }) : [], 161 | config: input.config 162 | }, 163 | input.to === "player" 164 | ); 165 | toOutput.push( 166 | new Output( 167 | result, 168 | dragonBonesData.name, 169 | ".html", 170 | "string" 171 | ) 172 | ); 173 | break; 174 | } 175 | 176 | case "spine": { 177 | // toNew(dragonBonesData, true); 178 | // format(dragonBonesData); 179 | // const result = toSpine(dragonBonesData, input.config, false); 180 | 181 | // for (const spine of result.spines) { 182 | // if (input.compress !== false) { 183 | // object.compress(spine, spft.compressConfig); 184 | // } 185 | 186 | // toOutput.push( 187 | // new Output( 188 | // JSON.stringify(spine), 189 | // result.spines.length > 1 ? `${dragonBonesData.name}_${spine.skeleton.name}` : dragonBonesData.name, 190 | // ".json", 191 | // "string" 192 | // ) 193 | // ); 194 | // } 195 | 196 | // if (result.textureAtlas) { 197 | // toOutput.push( 198 | // new Output( 199 | // result.textureAtlas, 200 | // dragonBonesData.name, 201 | // ".atlas", 202 | // "string" 203 | // ) 204 | // ); 205 | // } 206 | break; 207 | } 208 | 209 | default: 210 | gate.responseEnd(response, Code.DataError, Code[Code.DataError], input.to); 211 | return; 212 | } 213 | 214 | gate.responseEnd(response, Code.Success, Code[Code.Success], toOutput); 215 | } 216 | } 217 | catch (error) { 218 | gate.responseEnd(response, Code.ActionError, Code[Code.ActionError]); 219 | return; 220 | } 221 | }); 222 | }; 223 | 224 | function execute(): void { 225 | if (process.argv.length > 1) { 226 | const port = Number(process.argv[2]); 227 | if (port === port && port >= 0 && port <= 65535) { 228 | const url = `http://${nodeUtils.findIP()}:${port}/dragonbones`; 229 | 230 | gate.actions["/working_directory"] = (request, response) => { 231 | // let jsonString = ""; 232 | 233 | request.addListener("data", () => { 234 | // jsonString += data; 235 | }); 236 | 237 | request.addListener("end", () => { 238 | request.removeAllListeners(); 239 | gate.responseEnd(response, Code.Success, Code[Code.Success], { url: url, workingDirectory: __dirname }); 240 | }); 241 | }; 242 | 243 | gate.start("dragonbones", port, "/dragonbones"); 244 | console.log(url); 245 | return; 246 | } 247 | } 248 | 249 | const portServer = http.createServer(); 250 | portServer.listen(0, () => { 251 | const port = (portServer.address() as any).port; 252 | portServer.close(); 253 | gate.start("dragonbones", port, "/dragonbones"); 254 | 255 | console.log(`http://${nodeUtils.findIP()}:${port}/dragonbones`); 256 | }); 257 | } 258 | 259 | execute(); 260 | -------------------------------------------------------------------------------- /src/test/testDemos.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | import * as fs from "fs"; 3 | import * as path from "path"; 4 | import * as nodeUtils from "../common/nodeUtils"; 5 | import * as dbft from "../format/dragonBonesFormat"; 6 | import * as resft from "../format/resFormat"; 7 | import * as dbUtils from "../format/utils"; 8 | 9 | const RESOURCE_PATH = "resource"; 10 | const RESOURCE_NAME = "base_test.res.json"; 11 | const PRELOAD_NAME = "baseTest"; 12 | const SEARCH_GROUP_NAME = "search"; 13 | 14 | function modifyResourcesPath(file: string): string { 15 | const index = file.indexOf(RESOURCE_PATH); 16 | if (index > 0) { 17 | file = file.substr(index + RESOURCE_PATH.length + 1); 18 | } 19 | 20 | file = file.replace(/\\/g, "/"); 21 | 22 | return file; 23 | } 24 | 25 | function execute(): void { 26 | const args = process.argv.slice(2); 27 | const root = process.cwd(); 28 | const include = args[0]; 29 | const resourcesJSON = new resft.ResourceJSON(); 30 | const files = nodeUtils.filterFileList(root, /\.(json)$/i); 31 | 32 | for (let i = 0, l = files.length; i < l; ++i) { 33 | const file = files[i]; 34 | if (include && file.indexOf(include) < 0) { 35 | continue; 36 | } 37 | 38 | const fileDir = path.dirname(file); 39 | const fileName = path.basename(file, ".json"); 40 | const fileString = fs.readFileSync(file).toString(); 41 | if (!dbft.isDragonBonesString(fileString)) { 42 | continue; 43 | } 44 | 45 | const dragonBonesJSON = JSON.parse(fs.readFileSync(file).toString()); 46 | const dataName = dragonBonesJSON.name; 47 | 48 | resourcesJSON.addResource( 49 | dataName, 50 | resft.ResourceType.JSON, 51 | modifyResourcesPath(file), 52 | PRELOAD_NAME, SEARCH_GROUP_NAME 53 | ); 54 | 55 | // Binary 56 | const binaryFile = path.join(fileDir, fileName + ".dbbin"); 57 | if (fs.existsSync(binaryFile)) { 58 | console.log(binaryFile); 59 | 60 | resourcesJSON.addResource( 61 | dataName + "_binary", 62 | resft.ResourceType.BIN, 63 | modifyResourcesPath(binaryFile), 64 | PRELOAD_NAME 65 | ); 66 | } 67 | 68 | // // Movie 69 | // const movieFile = dragonBonesFile.replace(".json", ".dbmv"); 70 | // if (fs.existsSync(movieFile)) { 71 | // console.log(movieFile); 72 | 73 | // resourcesJSON.addResource( 74 | // dataName + "_mov", 75 | // resft.ResourceType.BIN, 76 | // modifyResourcesPath(movieFile), 77 | // PRELOAD_NAME 78 | // ); 79 | // } 80 | 81 | const textureAtlass = dragonBonesJSON.textureAtlas as any[]; 82 | if (textureAtlass) { 83 | for (let i = 0, l = textureAtlass.length; i < l; ++i) { 84 | const textureAtlas = textureAtlass[i]; 85 | const textureAtlasFile = path.join(fileDir, textureAtlas.imagePath); 86 | resourcesJSON.addResource( 87 | dataName + "_texture_" + i, 88 | resft.ResourceType.Image, 89 | modifyResourcesPath(textureAtlasFile), 90 | PRELOAD_NAME 91 | ); 92 | 93 | console.log(textureAtlasFile); 94 | } 95 | } 96 | else { 97 | const textureAtlass = dbUtils.getTextureAtlases(file); // TextureAtlas config and TextureAtlas. 98 | if (textureAtlass.length > 0) { 99 | for (let i = 0, l = textureAtlass.length; i < l; ++i) { 100 | const textureAtlasConfig = textureAtlass[i]; 101 | const textureAtlas = textureAtlasConfig.replace(".json", ".png"); 102 | 103 | resourcesJSON.addResource( 104 | dataName + "_texture_config_" + i, 105 | resft.ResourceType.JSON, 106 | modifyResourcesPath(textureAtlasConfig), 107 | PRELOAD_NAME 108 | ); 109 | 110 | resourcesJSON.addResource( 111 | dataName + "_texture_" + i, 112 | resft.ResourceType.Image, 113 | modifyResourcesPath(textureAtlas), 114 | PRELOAD_NAME 115 | ); 116 | 117 | console.log(textureAtlasConfig); 118 | console.log(textureAtlas); 119 | } 120 | } 121 | } 122 | } 123 | 124 | if (resourcesJSON.resources.length > 0) { 125 | let file = root; 126 | if (file.indexOf(RESOURCE_PATH) >= 0) { 127 | file = path.join(file, RESOURCE_NAME); 128 | } 129 | else { 130 | file = path.join(file, RESOURCE_PATH, RESOURCE_NAME); 131 | } 132 | 133 | fs.writeFileSync(file, new Buffer(JSON.stringify(resourcesJSON))); 134 | console.log(file); 135 | } 136 | 137 | console.log("Complete."); 138 | } 139 | 140 | execute(); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": false, 4 | "declaration": false, 5 | "skipLibCheck": true, 6 | "alwaysStrict": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es5", 10 | "module": "commonjs", 11 | "newLine": "LF", 12 | "baseUrl": "./src", 13 | "outDir": "./out", 14 | "lib": [ 15 | "es6", 16 | "dom", 17 | "es2017" 18 | ] 19 | }, 20 | "exclude": [ 21 | "node_modules", 22 | "out", 23 | "backup" 24 | ] 25 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-unused-expression": true, 4 | "no-unreachable": true, 5 | "no-duplicate-variable": true, 6 | "no-duplicate-key": true, 7 | "no-unused-variable": false, 8 | "curly": false, 9 | "class-name": true, 10 | "semicolon": [ 11 | true 12 | ], 13 | "triple-equals": true 14 | } 15 | } -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "node": "registry:dt/node#6.0.0+20160801161248" 4 | } 5 | } 6 | --------------------------------------------------------------------------------