├── .gitignore
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
└── src
├── game-start.js
├── new-res-loader.js
└── start.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /src/web-mobile
3 | /dist
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 fengyong
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.md:
--------------------------------------------------------------------------------
1 | 2020.7.28,最近工作较忙,没有精力将版本支持到2.4,更多的支持会在2020年9月后更新。
2 |
3 | # cocos-to-playable-ad
4 | 将 cocos creator 构建出来的 web-mobile 项目打包为 playable-ad 项目,即单 html 文件。
5 | 一些说明:
6 | - 参考资料:
7 | - https://github.com/chongshengzhujue/playableFBCompile
8 | - https://www.ifeelgame.net/cocoscreator/%E4%BD%BF%E7%94%A8cocoscreator%E5%88%B6%E4%BD%9Cfacebook%E7%9A%84playable-ad/
9 | - 其他网络资料
10 | - 改进部分:
11 | - 支持 cocos creator 到 2.1.3
12 | - 完善了核心算法描述(请参考 README.md)
13 | - 精简了使用流程,并将游戏项目与打包项目完全分开,游戏项目只需要提供 web-mobile 文件夹即可
14 | - 使用 node.js 完成,完善了开发环境描述,代码注释
15 | - 本项目不包括对图片,声音资源的压缩,需要自行压缩。
16 | - 本项目不包括使用 cocos creator 打包时的模块选择,需要自行筛选。
17 | - 如果使用过程中出现问题,请提交到项目下的 Issues,或者参与论坛讨论,https://forum.cocos.com/t/cocos-creator-web-mobile-playable-ad-html/84260
18 |
19 | ## 如何使用?
20 | - 开发环境:
21 | - macOs Cataline 10.15
22 | - node.js 12.9.0
23 | - cocos creator 2.1.3
24 | - Chrome 77
25 | - 输入:使用 cocos creator 构建出来的 web-mobile 项目文件夹。
26 | - 输出:index.html。
27 | - 使用方法:
28 | 1. 将构建出来的 web-mobile 整个文件夹整个放入项目的 src 目录下。此时目录为:src/web-mobile/...
29 | 2. 修改 src/web-mobile/main.js,注释掉 154 到 163 行,**目的是不在代码中载入 project.js**,而是在流程中载入。
30 | * **特别说明**:考虑不同版本下打出来的 main.js 代码位置可能会有差异,未必在相应的行,所以我把需要注释掉的代码补充进来。
31 | ```javascript
32 | // jsList
33 | var jsList = settings.jsList;
34 |
35 | // var bundledScript = settings.debug ? 'src/project.dev.js' : 'src/project.js';
36 | // if (jsList) {
37 | // jsList = jsList.map(function (x) {
38 | // return 'src/' + x;
39 | // });
40 | // jsList.push(bundledScript);
41 | // }
42 | // else {
43 | // jsList = [bundledScript];
44 | // }
45 | ```
46 | 3. 在根目录下执行 npm run build,会显示流程执行过程以及相应的消耗时间。
47 | 4. 点击输出文件 dist/index.html,检查在浏览器中是否显示正常。
48 |
49 | ## 核心算法
50 | - 将项目所依赖的资源读取并写入到 window.res,保存为 res.js。
51 | - 通过 cc.loader.addDownloadHandlers 修改资源载入方式,从 window.res 中载入资源,参考 new-res-loader.js。
52 | - 将 index.html 中所依赖的 css 和 js 文件,包括一些新的 js 文件写入到 html 文件本身。
53 |
54 | ## 依赖模块:
55 | - https://github.com/GoalSmashers/clean-css 压缩 css。
56 | - https://github.com/mishoo/UglifyJS2 压缩 js。
57 | - fs 模块,读写文件。
58 | - path 模块,处理路径相关(其实在项目中只用来获取文件的后缀名)。
59 | - typescript 相关,本项目使用 ts 编写,使用 ts-node 执行。
60 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cocos-to-playable-ad",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@types/clean-css": {
8 | "version": "4.2.1",
9 | "resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.1.tgz",
10 | "integrity": "sha512-A1HQhQ0hkvqqByJMgg+Wiv9p9XdoYEzuwm11SVo1mX2/4PSdhjcrUlilJQoqLscIheC51t1D5g+EFWCXZ2VTQQ==",
11 | "dev": true,
12 | "requires": {
13 | "@types/node": "*"
14 | }
15 | },
16 | "@types/node": {
17 | "version": "12.7.8",
18 | "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.8.tgz",
19 | "integrity": "sha512-FMdVn84tJJdV+xe+53sYiZS4R5yn1mAIxfj+DVoNiQjTYz1+OYmjwEZr1ev9nU0axXwda0QDbYl06QHanRVH3A==",
20 | "dev": true
21 | },
22 | "@types/uglify-js": {
23 | "version": "3.0.4",
24 | "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.4.tgz",
25 | "integrity": "sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ==",
26 | "dev": true,
27 | "requires": {
28 | "source-map": "^0.6.1"
29 | }
30 | },
31 | "arg": {
32 | "version": "4.1.1",
33 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.1.tgz",
34 | "integrity": "sha512-SlmP3fEA88MBv0PypnXZ8ZfJhwmDeIE3SP71j37AiXQBXYosPV0x6uISAaHYSlSVhmHOVkomen0tbGk6Anlebw==",
35 | "dev": true
36 | },
37 | "buffer-from": {
38 | "version": "1.1.1",
39 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
40 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
41 | "dev": true
42 | },
43 | "clean-css": {
44 | "version": "4.2.1",
45 | "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz",
46 | "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==",
47 | "dev": true,
48 | "requires": {
49 | "source-map": "~0.6.0"
50 | }
51 | },
52 | "commander": {
53 | "version": "2.20.1",
54 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.1.tgz",
55 | "integrity": "sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==",
56 | "dev": true
57 | },
58 | "diff": {
59 | "version": "4.0.1",
60 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz",
61 | "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==",
62 | "dev": true
63 | },
64 | "make-error": {
65 | "version": "1.3.5",
66 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
67 | "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==",
68 | "dev": true
69 | },
70 | "source-map": {
71 | "version": "0.6.1",
72 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
73 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
74 | "dev": true
75 | },
76 | "source-map-support": {
77 | "version": "0.5.13",
78 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
79 | "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
80 | "dev": true,
81 | "requires": {
82 | "buffer-from": "^1.0.0",
83 | "source-map": "^0.6.0"
84 | }
85 | },
86 | "ts-node": {
87 | "version": "8.4.1",
88 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.4.1.tgz",
89 | "integrity": "sha512-5LpRN+mTiCs7lI5EtbXmF/HfMeCjzt7DH9CZwtkr6SywStrNQC723wG+aOWFiLNn7zT3kD/RnFqi3ZUfr4l5Qw==",
90 | "dev": true,
91 | "requires": {
92 | "arg": "^4.1.0",
93 | "diff": "^4.0.1",
94 | "make-error": "^1.1.1",
95 | "source-map-support": "^0.5.6",
96 | "yn": "^3.0.0"
97 | }
98 | },
99 | "typescript": {
100 | "version": "3.6.3",
101 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz",
102 | "integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==",
103 | "dev": true
104 | },
105 | "uglify-js": {
106 | "version": "3.6.0",
107 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz",
108 | "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==",
109 | "dev": true,
110 | "requires": {
111 | "commander": "~2.20.0",
112 | "source-map": "~0.6.1"
113 | }
114 | },
115 | "yn": {
116 | "version": "3.1.1",
117 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
118 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
119 | "dev": true
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cocos-to-playable-ad",
3 | "version": "1.0.0",
4 | "description": "将 cocos creator 构建出来的 web-mobile 项目打包为 playable-ad 项目,即单 html 文件。",
5 | "main": "start.ts",
6 | "dependencies": {},
7 | "devDependencies": {
8 | "@types/clean-css": "^4.2.1",
9 | "@types/node": "^12.7.8",
10 | "@types/uglify-js": "^3.0.4",
11 | "clean-css": "^4.2.1",
12 | "ts-node": "^8.4.1",
13 | "typescript": "^3.6.3",
14 | "uglify-js": "^3.6.0"
15 | },
16 | "scripts": {
17 | "build": "ts-node ./src/start.ts"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/fkworld/cocos-to-playable-ad.git"
22 | },
23 | "author": "",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/fkworld/cocos-to-playable-ad/issues"
27 | },
28 | "homepage": "https://github.com/fkworld/cocos-to-playable-ad#readme"
29 | }
30 |
--------------------------------------------------------------------------------
/src/game-start.js:
--------------------------------------------------------------------------------
1 | // 游戏启动脚本
2 |
3 | window.boot()
4 |
--------------------------------------------------------------------------------
/src/new-res-loader.js:
--------------------------------------------------------------------------------
1 | // 新的资源载入方式脚本
2 |
3 | /** 官网范例,反正看不懂
4 | * - https://developer.mozilla.org/zh-CN/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#Solution_1_%E2%80%93_JavaScript's_UTF-16_%3E_base64
5 | */
6 | function b64ToUint6(nChr) {
7 | return nChr > 64 && nChr < 91
8 | ? nChr - 65 : nChr > 96 && nChr < 123
9 | ? nChr - 71 : nChr > 47 && nChr < 58
10 | ? nChr + 4 : nChr === 43
11 | ? 62 : nChr === 47
12 | ? 63 : 0
13 | }
14 |
15 | /** 官网范例+1,看不懂+1,作用是将base64编码的字符串转为ArrayBuffer */
16 | function base64DecToArr(sBase64, nBlockSize) {
17 | var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length
18 | var nOutLen = nBlockSize ? Math.ceil((nInLen * 3 + 1 >>> 2) / nBlockSize) * nBlockSize : nInLen * 3 + 1 >>> 2
19 | var aBytes = new Uint8Array(nOutLen)
20 | for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
21 | nMod4 = nInIdx & 3
22 | nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4
23 | if (nMod4 === 3 || nInLen - nInIdx === 1) {
24 | for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++ , nOutIdx++) {
25 | aBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
26 | }
27 | nUint24 = 0
28 | }
29 | }
30 | return aBytes
31 | }
32 |
33 | /**
34 | * 修改部分资源的载入方式,可以根据项目中实际用到的资源进行修改
35 | * - [注意] window.res 是自己定义的,名称可以修改
36 | */
37 | cc.loader.addDownloadHandlers({
38 | json: function (item, callback) {
39 | callback(null, window.res[item.url])
40 | },
41 | plist: function (item, callback) {
42 | callback(null, window.res[item.url])
43 | },
44 | png: function (item, callback) {
45 | var img = new Image()
46 | img.src = "data:image/png;base64," + window.res[item.url] // 注意需要给base64编码添加前缀
47 | callback(null, img)
48 | },
49 | jpg: function (item, callback) {
50 | var img = new Image()
51 | img.src = "data:image/jpeg;base64," + window.res[item.url]
52 | callback(null, img)
53 | },
54 | webp: function (item, callback) {
55 | var img = new Image()
56 | img.src = "data:image/webp;base64," + window.res[item.url]
57 | callback(null, img)
58 | },
59 | mp3: function (item, callback) {
60 | // 只支持以webAudio形式播放的声音
61 | // 将base64编码的声音文件转化为ArrayBuffer
62 | cc.sys.__audioSupport.context.decodeAudioData(
63 | base64DecToArr(window.res[item.url]).buffer,
64 | // success
65 | function (buffer) {
66 | callback(null, buffer)
67 | },
68 | // fail
69 | function (buffer) {
70 | callback(new Error("mp3-res-fail"), null)
71 | }
72 | )
73 | },
74 | })
75 |
--------------------------------------------------------------------------------
/src/start.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs"
2 | import * as path from "path"
3 | import * as uglify from "uglify-js"
4 | import CleanCSS = require("clean-css")
5 |
6 | export namespace X {
7 |
8 | /** 一些配置参数
9 | * - [注意] 路径问题.start脚本与web-mobile同层级,因此相对路径需要带上web-mobile;cocos在调用资源时没有web-mobile,需要在最后去掉
10 | */
11 | const C = {
12 | BASE_PATH: "src/web-mobile", // web-mobile包基础路径
13 | RES_PATH: "src/web-mobile/res", // web-mobile包下的res路径
14 | RES_BASE64_EXTNAME_SET: new Set([ // 需要使用base64编码的资源后缀(根据项目自行扩充)
15 | ".png", ".jpg", ".webp", ".mp3",
16 | ]),
17 | OUTPUT_RES_JS: "dist/res.js", // 输出文件res.js
18 | OUTPUT_INDEX_HTML: "dist/index.html", // 输出文件index.html的路径
19 | INPUT_HTML_FILE: "src/web-mobile/index.html",
20 | INPUT_CSS_FILES: [
21 | "src/web-mobile/style-mobile.css"
22 | ],
23 | INPUT_JS_FILES: [
24 | "dist/res.js", // 注意这里先输出再输入
25 | "src/web-mobile/cocos2d-js-min.js",
26 | "src/web-mobile/main.js",
27 | "src/web-mobile/src/settings.js",
28 | "src/web-mobile/src/project.js",
29 | "src/new-res-loader.js",
30 | "src/game-start.js",
31 | ],
32 | }
33 |
34 | /**
35 | * 读取文件内容
36 | * - 特定后缀返回base64编码后字符串,否则直接返回文件内容字符串
37 | * @param filepath
38 | */
39 | function get_file_content(filepath: string): string {
40 | let file = fs.readFileSync(filepath)
41 | return C.RES_BASE64_EXTNAME_SET.has(path.extname(filepath)) ? file.toString("base64") : file.toString()
42 | }
43 |
44 | /**
45 | * 获取路径下的所有子文件路径(深度遍历)
46 | * @param filepath
47 | */
48 | function get_all_child_file(filepath: string): string[] {
49 | let children = [filepath]
50 | for (; ;) {
51 | // 如果都是file类型的,则跳出循环
52 | if (children.every(v => fs.statSync(v).isFile())) { break }
53 | // 如果至少有1个directroy类型,则删除这一项,并加入其子项
54 | children.forEach((child, i) => {
55 | if (fs.statSync(child).isDirectory()) {
56 | delete children[i]
57 | let child_children = fs.readdirSync(child).map(v => `${child}/${v}`)
58 | children.push(...child_children)
59 | }
60 | })
61 | }
62 | return children
63 | }
64 |
65 | /**
66 | * 将所有res路径下的资源转化为res.js
67 | * - 存储方式为:res-url(注意是相对的),res文件内容字符串或编码
68 | */
69 | function write_resjs() {
70 | // 读取并写入到一个对象中
71 | let res_object = {}
72 | get_all_child_file(C.RES_PATH).forEach(path => {
73 | // 注意,存储时删除BASE_PATH前置
74 | let store_path = path.replace(new RegExp(`^${C.BASE_PATH}/`), "")
75 | res_object[store_path] = get_file_content(path)
76 | })
77 | // 写入文件
78 | fs.writeFileSync(C.OUTPUT_RES_JS, `window.res=${JSON.stringify(res_object)}`)
79 | }
80 |
81 | /** 将js文件转化为html文件内容(包括压缩过程) */
82 | function get_html_code_by_js_file(js_filepath: string): string {
83 | let js = get_file_content(js_filepath)
84 | let min_js = uglify.minify(js).code
85 | return ``
86 | }
87 |
88 | /** 将css文件转化为html文件内容(包括压缩过程) */
89 | function get_html_code_by_css_file(css_filepath: string): string {
90 | let css = get_file_content(css_filepath)
91 | let min_css = new CleanCSS().minify(css).styles
92 | return ``
93 | }
94 |
95 | /** 执行任务 */
96 | export function do_task() {
97 | // 前置:将res资源写成res.js
98 | console.time("写入res.js")
99 | write_resjs()
100 | console.timeEnd("写入res.js")
101 |
102 | // 清理html
103 | console.time("清理html")
104 | let html = get_file_content(C.INPUT_HTML_FILE)
105 | html = html.replace(//gs, "")
106 | html = html.replace(//gs, "")
107 | console.timeEnd("清理html")
108 |
109 | // 写入css
110 | console.log("写入所有css文件")
111 | C.INPUT_CSS_FILES.forEach(v => {
112 | console.time(`---${path.basename(v)}`)
113 | html = html.replace(/<\/head>/, `${get_html_code_by_css_file(v)}\n`)
114 | console.timeEnd(`---${path.basename(v)}`)
115 | })
116 |
117 | // 写入js
118 | console.log("写入所有js到html")
119 | C.INPUT_JS_FILES.forEach(v => {
120 | console.time(`---${path.basename(v)}`)
121 | html = html.replace("