first commit

yityu-patch-1
guofei 2024-12-05 21:14:41 +08:00
commit a670fc4997
15 changed files with 2862 additions and 0 deletions

BIN
.DS_Store vendored 100644

Binary file not shown.

1
.gitignore vendored 100644
View File

@ -0,0 +1 @@
node_modules

68
README.md 100644
View File

@ -0,0 +1,68 @@
# cocos js开发
将`cocos-embed/SoyooFacade.ts`加进项目
然后注释调用对应的SoyooFacade对象的方法。
必须要调用的方法有:
```
SoyooFacade.ReportLifeCycle(SoyooLifecyle.Ready)
SoyooFacade.ReportLifeCycle(SoyooLifecyle.Start)
SoyooFacade.ReportLifeCycle(SoyooLifecyle.Complete)
SoyooFacade.InstallGame()
```
# zip
## facebook
把`networks/facebook.js`复制到web-mobile目录下。
在w`web-mobile/index.html`的"</head>"的上一行添加代码
```
<script src="facebook.js" charset="utf-8"></script>
```
## moloco
把`networks/moloco.js`复制到web-mobile目录下。
在w`web-mobile/index.html`的"</head>"的上一行添加代码
```
<script src="moloco.js" charset="utf-8"></script>
```
## google
把`networks/google.js`复制到web-mobile目录下。
在w`web-mobile/index.html`的"</head>"的上一行添加代码
```
<meta name="ad.size" content="width=480,height=320">
<script src="google.js" charset="utf-8"></script>
```
## tiktok
把`tiktok/tiktok.js`和`tiktok/config.json`复制到web-mobile目录下。
在w`web-mobile/index.html`的"</head>"的上一行添加代码
```
<script src="tiktok.js" charset="utf-8"></script>
```
# 单HTML渠道
## 环境配置
需要安装nodejs
打开soyoo-cocos后先执行`npm install`安装依赖
### 预处理html
0. 把目标目录web-mobile复制到soyoo-cocos目录下。
1. 删掉index.html里的js代码部分
2. 在body最后加上
`<script src="loader-and-starter.js" charset="utf-8"></script>
<script src="asset-map.js" charset="utf-8"></script>`
## 渠道处理
### applovin
1. 把networks/applovin.js 复制到web-mobile下
2. 编辑复制的applovin.js, 在前几行填入对应的iosUrl和androidUrl
3. 编辑index.html在body最后加上
`<script src="applovin.js" charset="utf-8"></script>`
### applovin
1. 把networks/mintegral.js 复制到web-mobile下
3. 编辑index.html在body最后加上
`<script src="mintegral.js" charset="utf-8"></script>`
## 打包
执行命令 `node single-html/build.js`
生成目标文件 `dist/index.html`即为目标文件

View File

@ -0,0 +1,49 @@
// v1.0 2024/12/01
// Soyoo facade to handle assets network transfroming.
export enum SoyooLifecyle {
Ready = 100, // 游戏初始化完成
Start = 200, // 游戏正式开始
Pause = 300, // 游戏暂停
Resume = 400, // 游戏从暂停恢复
Complete = 500, // 游戏结束, 全部完成
}
interface ISoyooFacadeImpl {
onLifecyleReport(lifecycle: SoyooLifecyle): void
onGameInstall(): void
}
// Only support web now.
let soyooFacadeImpl: ISoyooFacadeImpl;
(function() {
const window = globalThis;
const global = globalThis;
const self = globalThis;
(function() {
if ((window as any)?.$soyooFacadeImpl) {
soyooFacadeImpl = (window as any)?.$soyooFacadeImpl
console.log("SoyooFacade inited")
} else {
console.warn("soyooFacadeImpl not provided")
}
}).call(this);
}).call(this);
// 渠道转换的中间协议
export class SoyooFacade {
// 在游戏相应的生命周期时调用该方法。其中Ready/Start/Complete必须调用
static ReportLifeCycle(lifecycle: SoyooLifecyle) {
console.log("[SoyooFacade] ReportLifeCycle: " + lifecycle)
soyooFacadeImpl?.onLifecyleReport?.(lifecycle)
}
// 当用户点击并需要跳转到商店下载页面时,调用该方法。
static InstallGame() {
console.log("[SoyooFacade] InstallGame")
soyooFacadeImpl?.onGameInstall?.()
}
}

View File

@ -0,0 +1,67 @@
// 填入对应的商店地址
var iosUrl = ""
var androidUrl = ""
class SoyooLifecyle {
static Ready = 100 // 游戏初始化完成
static Start = 200 // 游戏正式开始
static Pause = 300 // 游戏暂停
static Resume = 400 // 游戏从暂停恢复
static Complete = 500 // 游戏结束, 全部完成
}
window.$soyooFacadeImpl = {
onLifecyleReport(lifecyle) {
switch(lifecyle) {
default:
console.log("[SoyooFacadeImpl] not handled: ", lifecyle)
}
},
onGameInstall() {
const gameUrl = /iphone|ipad|ipod|macintosh/i.test(
window.navigator.userAgent.toLowerCase()
)
? iosUrl
: androidUrl;
mraid.open(gameUrl)
console.log("[SoyooFacadeImpl] onGameInstall")
}
}
!(function () {
var n = !1,
e = !1;
function i() {
return mraid.isViewable() && "hidden" !== mraid.getState();
}
function a() {
n
? i() && e
? (window.resumeGame(), (e = !1))
: i() || e || (window.pauseGame(), (e = !0))
: i() && (window.startGame(), (n = !0));
}
function t() {}
function d(n) {
n ? window.unmuteAudio() : window.muteAudio()
}
var o = function () {
"undefined" != typeof mraid
? (mraid.removeEventListener("ready", o),
mraid.addEventListener("viewableChange", a),
mraid.addEventListener("stateChange", a),
mraid.addEventListener("orientationchange", t),
mraid.addEventListener("audioVolumeChange", d),
a())
: window.startGame();
};
window.addEventListener("DOMContentLoaded", function () {
"undefined" != typeof mraid
? "loading" === mraid.getState()
? mraid.addEventListener("ready", o)
: o()
: window.startGame();
});
})();

View File

@ -0,0 +1,21 @@
class SoyooLifecyle {
static Ready = 100 // 游戏初始化完成
static Start = 200 // 游戏正式开始
static Pause = 300 // 游戏暂停
static Resume = 400 // 游戏从暂停恢复
static Complete = 500 // 游戏结束, 全部完成
}
window.$soyooFacadeImpl = {
onLifecyleReport(lifecyle) {
switch(lifecyle) {
default:
console.log("[SoyooFacadeImpl] not handled: ", lifecyle)
}
},
onGameInstall() {
FbPlayableAd && FbPlayableAd.onCTAClick();
console.log("[SoyooFacadeImpl] onGameInstall")
}
}

21
networks/google.js 100644
View File

@ -0,0 +1,21 @@
class SoyooLifecyle {
static Ready = 100 // 游戏初始化完成
static Start = 200 // 游戏正式开始
static Pause = 300 // 游戏暂停
static Resume = 400 // 游戏从暂停恢复
static Complete = 500 // 游戏结束, 全部完成
}
window.$soyooFacadeImpl = {
onLifecyleReport(lifecyle) {
switch(lifecyle) {
default:
console.log("[SoyooFacadeImpl] not handled: ", lifecyle)
}
},
onGameInstall() {
ExitApi && ExitApi.exit && ExitApi.exit();
console.log("[SoyooFacadeImpl] onGameInstall")
}
}

View File

@ -0,0 +1,42 @@
class SoyooLifecyle {
static Ready = 100 // 游戏初始化完成
static Start = 200 // 游戏正式开始
static Pause = 300 // 游戏暂停
static Resume = 400 // 游戏从暂停恢复
static Complete = 500 // 游戏结束, 全部完成
}
window.$soyooFacadeImpl = {
onLifecyleReport(lifecyle) {
switch(lifecyle) {
case SoyooLifecyle.Start:
window.gameReady && window.gameReady();
break;
case SoyooLifecyle.Complete:
window.gameEnd && window.gameEnd();
break;
default:
console.log("[SoyooFacadeImpl] not handled: ", lifecyle)
}
},
onGameInstall() {
window.install && window.install()
console.log("[SoyooFacadeImpl] onGameInstall")
}
}
!(function() {
let n = !1;
(window.gameStart = function() {
n
? window.resumeGame()
: ((n = !0), window.startGame());
}),
(window.gameClose = function() {
window.pauseGame();
}),
window.addEventListener("DOMContentLoaded", () => {
window.startGame()
})
})();

21
networks/moloco.js 100644
View File

@ -0,0 +1,21 @@
class SoyooLifecyle {
static Ready = 100 // 游戏初始化完成
static Start = 200 // 游戏正式开始
static Pause = 300 // 游戏暂停
static Resume = 400 // 游戏从暂停恢复
static Complete = 500 // 游戏结束, 全部完成
}
window.$soyooFacadeImpl = {
onLifecyleReport(lifecyle) {
switch(lifecyle) {
default:
console.log("[SoyooFacadeImpl] not handled: ", lifecyle)
}
},
onGameInstall() {
FbPlayableAd && FbPlayableAd.onCTAClick();
console.log("[SoyooFacadeImpl] onGameInstall")
}
}

2284
package-lock.json generated 100644

File diff suppressed because it is too large Load Diff

9
package.json 100644
View File

@ -0,0 +1,9 @@
{
"scripts": {
"build": "node single-html/build.js"
},
"devDependencies": {
"clean-css": "^5.3.3",
"uglify-js": "^3.19.3"
}
}

View File

@ -0,0 +1,128 @@
const fs = require("fs")
const path = require("path")
const uglify = require("uglify-js")
const CleanCSS = require("clean-css")
/**
* - [注意] 路径问题.start脚本与web-mobile同层级,因此相对路径需要带上web-mobile;cocos在调用资源时没有web-mobile,需要在最后去掉
*/
const C = {
BASE_PATH: "web-mobile", // web-mobile包基础路径
RES_PATH: "web-mobile/assets", // web-mobile包下的res路径
RES_BASE64_EXTNAME_SET: new Set([ // 需要使用base64编码的资源后缀(根据项目自行扩充)
".png", ".jpg", ".webp", ".mp3",
]),
INPUT_LOADER_STARTER_JS: "single-html/loader-and-starter.js",
OUTPUT_RES_JS: "./web-mobile/asset-map.js", // 输出文件asset-map.js
OUTPUT_LOADER_STARTER_JS: "./web-mobile/loader-and-starter.js",
OUTPUT_INDEX_HTML: "dist/index.html", // 输出文件index.html的路径
INPUT_HTML_FILE: "./web-mobile/index.html",
}
/**
* 读取文件内容
* - 特定后缀返回base64编码后字符串,否则直接返回文件内容字符串
* @param filepath
*/
function get_file_content(filepath) {
let file = fs.readFileSync(filepath)
return C.RES_BASE64_EXTNAME_SET.has(path.extname(filepath)) ? file.toString("base64") : file.toString()
}
/**
* 获取路径下的所有子文件路径(深度遍历)
* @param filepath
*/
function get_all_child_file(filepath) {
let children = [filepath]
for (; ;) {
// 如果都是file类型的,则跳出循环
if (children.every(v => fs.statSync(v).isFile())) { break }
// 如果至少有1个directroy类型,则删除这一项,并加入其子项
children.forEach((child, i) => {
if (fs.statSync(child).isDirectory()) {
delete children[i]
let child_children = fs.readdirSync(child).map(v => `${child}/${v}`)
children.push(...child_children)
}
})
}
return children
}
/**
* 将所有res路径下的资源转化为res.js
* - 存储方式为:res-url(注意是相对的),res文件内容字符串或编码
*/
function write_resjs() {
// 读取并写入到一个对象中
let res_object = {}
get_all_child_file(C.RES_PATH).forEach(path => {
// 注意,存储时删除BASE_PATH前置
let store_path = path.replace(new RegExp(`^${C.BASE_PATH}/`), "")
res_object[store_path] = get_file_content(path)
})
// 写入文件
fs.writeFileSync(C.OUTPUT_RES_JS, `window.assetMap=${JSON.stringify(res_object)}`)
}
/** 将js文件转化为html文件内容(包括压缩过程) */
function get_html_code_by_js_file(js_filepath) {
let js = get_file_content(js_filepath)
let min_js = uglify.minify(js).code
return `<script type="text/javascript" charset="utf-8">${min_js}`
}
/** 将css文件转化为html文件内容(包括压缩过程) */
function get_html_code_by_css_file(css_filepath) {
let css = get_file_content(css_filepath)
let min_css = new CleanCSS().minify(css).styles
return `<style>${min_css}</style>`
}
function replaceRelativeCss(html) {
var reg = /\<link rel\="stylesheet" type\="text\/css" href\="(.+)"\/\>/g
var matchedCsss = html.match(reg);
matchedCsss.forEach((script) => {
var CssPath = reg.exec(script)[1];
html = html.replace(script, () => get_html_code_by_css_file(path.resolve(C.BASE_PATH, CssPath)))
});
return html;
}
function replaceRelativeScript(html) {
var matchedScripts = html.match(/\<script src\="(.+)"\>/g);
matchedScripts.forEach((script) => {
var reg = /(\<script src\=")(.+\.js)(".*>)/g;
var jsPath = reg.exec(script)[2];
html = html.replace(script, () => get_html_code_by_js_file(path.resolve(C.BASE_PATH, jsPath)))
});
return html;
}
/** 执行任务 */
function do_task() {
// 前置:将res资源写成res.js
console.time("写入res")
write_resjs()
console.timeEnd("写入res")
fs.writeFileSync(C.OUTPUT_LOADER_STARTER_JS, get_file_content(C.INPUT_LOADER_STARTER_JS))
let html = get_file_content(C.INPUT_HTML_FILE)
console.time("替换js")
html = replaceRelativeScript(html)
html = replaceRelativeCss(html)
console.timeEnd("替换js")
// 写入文件并提示成功
console.time("输出html文件")
fs.writeFileSync(C.OUTPUT_INDEX_HTML, html)
console.timeEnd("输出html文件")
}
do_task()

View File

@ -0,0 +1,127 @@
window.startGame = function() {
window.boot()
}
window.pauseGame = function() {
cc.game.pause()
}
window.resumeGame = function() {
cc.game.resume()
}
window.muteAudio = function() {
cc.audioEngine.pauseAll()
}
window.unmuteAudio = function() {
cc.audioEngine.resumeAll()
}
console.log("entry file loaded")
function b64ToUint6(nChr) {
return nChr > 64 && nChr < 91
? nChr - 65 : nChr > 96 && nChr < 123
? nChr - 71 : nChr > 47 && nChr < 58
? nChr + 4 : nChr === 43
? 62 : nChr === 47
? 63 : 0
}
/** 官网范例+1,看不懂+1,作用是将base64编码的字符串转为ArrayBuffer */
function base64DecToArr(sBase64, nBlockSize) {
var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length
var nOutLen = nBlockSize ? Math.ceil((nInLen * 3 + 1 >>> 2) / nBlockSize) * nBlockSize : nInLen * 3 + 1 >>> 2
var aBytes = new Uint8Array(nOutLen)
for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
nMod4 = nInIdx & 3
nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4
if (nMod4 === 3 || nInLen - nInIdx === 1) {
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++ , nOutIdx++) {
aBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
}
nUint24 = 0
}
}
return aBytes
}
/**
* 修改部分资源的载入方式,可以根据项目中实际用到的资源进行修改
* - [注意] assetMap 是自己定义的,名称可以修改
*/
function loadJs(item, _, callback) {
if (!item.startsWith("assets")) {
item = "assets/" + item
}
eval(assetMap[item])
callback(null, null)
}
function loadJson(item, _, callback) {
if (!item.startsWith("assets")) {
item = "assets/" + item
}
callback(null, JSON.parse(assetMap[item]))
}
var assetMap = window.assetMap;
cc.assetManager.downloader.register({
"bundle": function (item, _, callback) {
var bundleJsKey = Object.keys(assetMap).find(key => key.includes(item + "/index") && key.endsWith(".js"))
if (!bundleJsKey) {
console.error(`bundle js ${item} not found`)
}
loadJs(bundleJsKey, _, (a1, data) => {
console.log("bundle js loaded:", bundleJsKey)
})
var bundleJsonKey = Object.keys(assetMap).find(key => key.includes(item + "/config") && key.endsWith(".json"))
if (!bundleJsonKey) {
console.error(`bundle ${item} not found`)
}
loadJson(bundleJsonKey, _, (a1, data) =>
{
var completeData = data;
completeData.base = "assets/" + item + "/"
callback(null, completeData)
})
},
".json": loadJson,
".plist": function (item, _, callback) {
callback(null, assetMap[item])
},
".png": function (item, _, callback) {
var img = new Image()
img.src = "data:image/png;base64," + assetMap[item] // 注意需要给base64编码添加前缀
callback(null, img)
},
".jpg": function (item, _, callback) {
var img = new Image()
img.src = "data:image/jpeg;base64," + assetMap[item]
callback(null, img)
},
".webp": function (item, _, callback) {
var img = new Image()
img.src = "data:image/webp;base64," + assetMap[item]
callback(null, img)
},
".mp3": function (item, _, callback) {
// 只支持以webAudio形式播放的声音
// 将base64编码的声音文件转化为ArrayBuffer
cc.sys.__audioSupport.context.decodeAudioData(
base64DecToArr(assetMap[item]).buffer,
// success
function (buffer) {
callback(null, buffer)
},
// fail
function (buffer) {
callback(new Error("mp3-res-fail"), null)
}
)
},
})

View File

@ -0,0 +1,3 @@
{
"playable_orientation": 0
}

21
tiktok/tiktok.js 100644
View File

@ -0,0 +1,21 @@
class SoyooLifecyle {
static Ready = 100 // 游戏初始化完成
static Start = 200 // 游戏正式开始
static Pause = 300 // 游戏暂停
static Resume = 400 // 游戏从暂停恢复
static Complete = 500 // 游戏结束, 全部完成
}
window.$soyooFacadeImpl = {
onLifecyleReport(lifecyle) {
switch(lifecyle) {
default:
console.log("[SoyooFacadeImpl] not handled: ", lifecyle)
}
},
onGameInstall() {
window.openAppStore();
console.log("[SoyooFacadeImpl] onGameInstall")
}
}