# 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/引用查找器/` 做资源一致性维护。