Commit 0b8505a1 authored by sunboylu's avatar sunboylu

feat: test null03

parent d5442ef7
# TestZG001 项目架构与关键函数示例
## 项目总览
- 目标:在 Cocos Creator 环境中,通过 GameCenter 展示课程卡片,点击后动态加载对应模板(Bundle)并运行场景。
- 平台:iOS / Android / Web Desktop(按平台选择不同的模板目录与版本)。
- 关键模块:GameCenter(页面控制)、GameCard(卡片渲染与交互)、middleLayer(模板加载与运行层)、工具与发布脚本。
## 目录结构
```
root
├── assets/testzg001/
│ ├── pages/gamecenter/ # 页面层(GameCenter、GameCard、prefab、scene等)
│ ├── script/ # 业务与运行层(middleLayer、util、air等)
│ ├── scene/ # 场景资源
│ ├── textures|audios|fonts|dragonBones # 素材资源
│ └── *.meta # 资源元数据
├── bin/ # 构建与发布脚本
├── packages/ # 项目工具插件(校验命名、替换UUID、引用查找器)
├── form/、form_angular/ # 发布与表单UI
├── settings/、project.json # 工程配置
└── tsconfig.json # TypeScript 编译配置
```
## 核心模块
- GameCenter(assets/testzg001/pages/gamecenter/gamecenter_testzg001.ts)
- 展示课程卡片列表,处理点击,调用 middleLayer 加载模板与场景。
- 覆盖 middleLayer 的进度更新以驱动 UI。
- GameCard(assets/testzg001/pages/gamecenter/gamecard_testzg001.ts)
- 组件:渲染单个卡片,`init(data)` 初始化显示与交互。
- middleLayer(assets/testzg001/script/middleLayer_testzg001.ts)
- 通过 `config.json` 决定 `sceneName``version``bundle` 路径。
- 封装 `cc.assetManager.loadBundle``bundle.loadScene`,处理进度、错误、运行场景。
- 提供关闭按钮、日志输出、资源清理等能力。
## 关键流程总览
1. GameCenter 初始化 → 渲染列表 → 用户点击卡片。
2. 调用 `middleLayer.loadPageBundle(courseItem, cb)` 获取模板配置并选择平台路径。
3. `middleLayer.loadBundle(sceneName, version, bondleUrl, cb)` 加载 Bundle 与 Scene。
4. 进度回调更新 UI → 场景运行 → 显示关闭按钮与交互。
5. 退出时清理资源。
## 关键函数调用逻辑与示例
### 1) GameCenter → middleLayer 加载模板
```ts
// 假设已拿到 middleLayerComp 与 courseItem
middleLayerComp.loadPageBundle(courseItem, (err?: any) => {
if (err) {
console.error('模板加载失败', err);
return;
}
console.log('模板加载完成,场景已运行');
});
```
### 2) 覆盖进度更新(overrideProgressUpdate)
```ts
private overrideProgressUpdate(middleLayerComp: any) {
const original = middleLayerComp.setLoadingProgressBar.bind(middleLayerComp);
// 将 middleLayer 的进度回调输出到 GameCenter 的 UI
middleLayerComp.setLoadingProgressBar = (progress: number) => {
// 示例:映射到页面的进度条与文本
if (this.progressLabel) this.progressLabel.string = `加载进度:${progress}%`;
if (this.progressBar) this.progressBar.progress = progress / 100;
// 保留原逻辑(如内部日志/动画)
original(progress);
};
}
```
### 3) GameCard 组件初始化
```ts
// 通过 prefab 实例化卡片后,确保拿到组件并初始化数据
const cardNode = cc.instantiate(this.gameCardPrefab);
const gameCardComp = cardNode.getComponent(GameCard) || cardNode.addComponent(GameCard);
// 确保 GameCardData 要求的 cw_cover 存在;没有则使用 cover 兜底
const cardData: GameCardData = {
...gameData,
cw_cover: (gameData as any).cw_cover || (gameData as any).cover,
};
gameCardComp.init(cardData);
```
### 4) middleLayer 的 Bundle/Scene 加载(平台选择 + 进度保护 + 错误日志)
```ts
// 平台路径选择(已修复:Android 使用 conf.android.sceneName)
if (cc.sys.isNative && cc.sys.os == cc.sys.OS_IOS) {
sceneName = conf.ios.sceneName;
version = conf.ios.version;
bondleUrl = `${templateBaseUrl}/ios/${conf.ios.sceneName}`;
} else if (cc.sys.isNative && cc.sys.os == cc.sys.OS_ANDROID) {
sceneName = conf.android.sceneName;
version = conf.android.version;
bondleUrl = `${templateBaseUrl}/android/${conf.android.sceneName}`; // 修复点
} else {
sceneName = conf.android.sceneName;
version = '';
bondleUrl = `${templateBaseUrl}/web_desktop`;
}
// Bundle 加载与场景运行
cc.assetManager.loadBundle(bondleUrl, { version }, (err, bundle) => {
if (err || !bundle) {
console.error('[Bundle加载失败]', { bondleUrl, version, err });
this.showLog(`Bundle加载失败: ${bondleUrl}`);
callback && callback(err);
return;
}
this.curBundle = bundle;
bundle.loadScene(
sceneName,
null,
(finish, total, item) => {
const safeTotal = total || 1; // 防止 total 为 0
const progress = Math.floor((finish / safeTotal) * 100);
if (item && (item as any).url) {
console.log('[场景加载进度]', { finish, total: safeTotal, url: (item as any).url });
}
this.setLoadingProgressBar(progress);
},
(err, scene) => {
if (err || !scene) {
console.error('[场景加载失败]', { sceneName, bondleUrl, err });
this.showLog(`场景加载失败: ${sceneName}`);
callback && callback(err);
return;
}
try {
cc.director.runScene(scene, null, () => {
const middleLayer = cc.find('middleLayer');
middleLayer.getComponent(cc.Widget).updateAlignment();
this.showCloseGameBtn();
const destroyer = cc.instantiate(cc.find('middleLayer/Destroyer'));
destroyer.parent = cc.find('Canvas');
destroyer.active = true;
callback && callback();
});
} catch (e) {
console.error('[运行场景异常]', e);
this.showLog('运行场景异常');
callback && callback(e);
}
},
);
});
```
### 5) 关闭与退出流程
```ts
showCloseGameBtn() {
const closeGameBtn = cc.find('middleLayer/back_btn');
closeGameBtn.active = true;
}
hideCloseGameBtn() {
const closeGameBtn = cc.find('middleLayer/back_btn');
closeGameBtn.active = false;
}
exitGame() {
this.onExitGame(); // 触发资源与状态清理
}
```
### 6) 网络请求示例
```ts
callNetworkApiGet(uri: string, data: any, cb: Function) {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 400) {
cb(JSON.parse(xhr.responseText));
}
};
xhr.open('GET', `${this.BASE_URL}${uri}`, true);
xhr.send();
}
callNetworkApiPost(uri: string, data: any, cb: Function) {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 400) {
cb(JSON.parse(xhr.responseText));
}
};
xhr.open('POST', `${this.BASE_URL}${uri}`, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(data));
}
```
## 调试与注意事项
- 引擎对象不可直接 `JSON.stringify`(存在循环引用),调试时输出“安全摘要”(name、uuid、active、parent/children、组件类型)。
- 若进度更新报错(如 `TypeError: Cannot read property '0' of undefined`),优先检查 bundle 路径与 `config.json``sceneName` 与目录一致性,使用上面的进度与错误日志定位。
- 命名规则:项目内脚本需以 `_testzg001` 后缀命名,确保发布校验通过。
## 构建与发布
- 通过 `bin/buildCocos.js``bin/build.js` 执行资源打包与发布。
- 使用 `packages/check-file-name/` 校验脚本命名;必要时用 `packages/replace-uuid/``packages/引用查找器/` 做资源一致性维护。
\ No newline at end of file
......@@ -3,6 +3,35 @@ const testMockData = {"eof":1,"file":"/storage/emulated/0/Android/data/com.examp
const recordMockData = {"text":"what can i do for you","audioUrl":"https://staging-teach.cdn.ireadabc.com/368ad56174f85e2ff1f39f12bac5c693.mp3"};
export function initAir(_this) {
// 安全兜底:确保 _this 存在且具备 log/showTips 方法,避免 e.log 为 null
try {
if (!_this) {
_this = {};
}
if (typeof _this.log !== 'function') {
_this.log = (...args) => {
try {
console.log.apply(console, args);
} catch (e) {
// 在某些 JSB 环境中 apply 可能异常,使用降级
console.log(args && args.length === 1 ? args[0] : args.join(' '));
}
};
}
if (typeof _this.showTips !== 'function') {
_this.showTips = (msg) => {
try {
console.warn(msg);
} catch (e) {
// 安全降级
}
};
}
} catch (e) {
// 极端情况下保持不抛异常
try { console.warn('initAir context fallback failed:', e); } catch (_) {}
}
const realAir = {
uploadUrl: "",
uploadData: null,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment