一、项目架构因为Oops Framework整个的流程是基本固定的所以要做的就是手动填满所有的流程做的所有东西实际上都要按部就班的按规则做。在动手编码前先明确整体架构流程确保各模块职责清晰。┌─────────────────────────────────────────────────┐ │ Main.ts │ │ 游戏入口继承框架Root初始化ECS/UI/SDK │ └──────────────┬──────────────────────────────────┘ │ ┌───────────▼────────────┐ │ Initialize (ECS) │ │ 首个ECS实体启动资源加载流程 │ └───────────┬────────────┘ │ ┌───────────▼────────────┐ │ InitResSystem │ │ 异步队列加载Bundle→多语言→通用资源 │ └───────────┬────────────┘ │ ┌───────────▼────────────┐ │ LoadingViewComp │ │ 加载界面原生热更逻辑 │ └───────────┬────────────┘ │ ┌───────────▼────────────┐ │ Login Component │ │ 多渠道登录测试/SDK/微信 账号鉴权 │ └───────────┬────────────┘ │ ┌───────────▼────────────┐ │ Account / AccountData │ │ 账号数据管理登录状态维护 │ └─────────────────────────┘二、环境准备开始前确保以下环境就绪安装 Cocos Creator 3.8兼容 Oops Framework安装 Oops Framework 插件执行框架提供的update-oops-plugin-framework.bat/.sh确认项目目录结构规范assets/resources里面只保证config.json文件assets/bundle为框架默认资源加载目录。三、核心模块手动实现步骤 1基础配置文件1.1 框架核心配置config.json创建assets/resources/config.json定义项目基础配置版本、服务器、多语言、UI 层级等json{ type: prod, config: { prod: { version: 1.0.0, package: com.example.game, frameRate: 60, localDataKey: your-encrypt-key, // AES加密密钥16位 localDataIv: your-encrypt-iv-, // AES加密向量16位 httpServer: , httpTimeout: 10000, mobileSafeArea: false, stats: 0 } }, language: { type: [zh, en], default: zh, path: { json: language/json } }, bundle: { default: bundle }, gui: [ { type: UI, name: LayerUI }, { type: PopUp, name: LayerPopUp }, { type: Dialog, name: LayerDialog } ] }1.2 多语言配置这里只是示例创建多语言 JSON 文件统一管理文本避免硬编码assets/bundle/language/json/zh.json{ loading_load_player: 加载玩家数据, loading_load_json: 加载配置表, update_tips_check_update: 检查更新中 }assets/bundle/language/json/en.json{ loading_load_player: Loading player data, loading_load_json: Loading config tables, update_tips_check_update: Checking for updates }1.3 常量与枚举定义存储键枚举StorageKey.ts-assets/script/game/common/config/StorageKey.tsexport enum StorageKey { Account Account, Server Server, LoginUsePolicy LoginUsePolicy, }游戏常量GameConst.ts-assets/script/game/common/config/GameConst.ts// 协议ID枚举 export enum MsgID { Login 1001, LoginAuth 1002, } // 埋点类型枚举 export enum SubmitTypes { EnterGame 1, ExitGame 2, }游戏事件枚举GameEvent.ts-assets/script/game/common/config/GameEvent.tsexport enum GameEvent { GameServerConnected GameServerConnected, LoginSuccess LoginSuccess, LoginError LoginError, Tick1S Tick1S, // 1秒心跳 }资源路径工具GameResPath.ts-assets/script/game/common/config/GameResPath.tsexport class GameResPath { // 获取龙骨模型路径 static getModelPath(name: string): { skeleton: string; atlas: string } { return { skeleton: model/${name}/${name}_ske, atlas: model/${name}/${name}_tex, }; } // 获取通用精灵帧路径 static getSpriteFrameCommon(name: string): string { return common/texture/${name}/spriteFrame; } } // 音频路径常量 export const AudioClipMusic { Main: audio/bgm_main, Game: audio/bgm_game, };UI 配置GameUIConfig.ts-assets/script/game/common/config/GameUIConfig.tsimport { LayerType } from ../../../../../extensions/oops-plugin-framework/assets/core/gui/layer/LayerEnum; import { UIConfig } from ../../../../../extensions/oops-plugin-framework/assets/core/gui/layer/UIConfig; // UI唯一标识 export enum UIID { Loading 1, Login 2, Main 3, } // UIID与层/预制体映射 export var UIConfigData: { [key: number]: UIConfig } { [UIID.Loading]: { layer: LayerType.UI, prefab: gui/loading/loading }, [UIID.Login]: { layer: LayerType.UI, prefab: gui/login/login }, [UIID.Main]: { layer: LayerType.UI, prefab: gui/main/main }, };步骤 2单例模块注册创建assets/script/game/common/SingletonModuleComp.ts统一管理全局单例模块简化调用运行import { ecs } from ../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS; import { account, Account } from ../account/Account; import { Initialize } from ../initialize/Initialize; ecs.register(SingletonModule) export class SingletonModuleComp extends ecs.Comp { // 初始化模块 initialize: Initialize null!; // 账号模块getter封装 get account(): Account { return account; } reset() { } } // 全局单例访问器 export var smc: SingletonModuleComp ecs.getSingleton(SingletonModuleComp);步骤 3游戏入口Main.ts创建assets/script/Main.ts作为游戏根入口继承框架Root类初始化核心能力运行import { director, game, macro, _decorator } from cc; import { oops } from ../../extensions/oops-plugin-framework/assets/core/Oops; import { Root } from ../../extensions/oops-plugin-framework/assets/core/Root; import { ecs } from ../../extensions/oops-plugin-framework/assets/libs/ecs/ECS; import { GameEvent } from ./game/common/config/GameEvent; import { UIConfigData, UIID } from ./game/common/config/GameUIConfig; import { smc } from ./game/common/SingletonModuleComp; import { EcsInitializeSystem, Initialize } from ./game/initialize/Initialize; import { PlatformSDK } from ./game/common/sdk/PlatformSDK; const { ccclass } _decorator; ccclass(Main) export class Main extends Root { start() { // 初始化平台SDK PlatformSDK.init(); // 注册定时心跳事件 this.schedule(() { oops.message.dispatchEvent(GameEvent.Tick1S) }, 1, macro.REPEAT_FOREVER, 0.1); } onDestroy() { this.unscheduleAllCallbacks(); } // 配置加载完成后启动初始化ECS实体 protected run() { smc.initialize ecs.getEntityInitialize(Initialize); } // 注册UI配置 protected initGui() { oops.gui.init(UIConfigData); } // 注册ECS系统 protected initEcsSystem() { oops.ecs.add(new EcsInitializeSystem()); } } // 全局快捷访问 export var app new Main();心跳网络游戏开发中使用单机可以注释掉步骤 4初始化模块ECS4.1 初始化实体Initialize.ts创建assets/script/game/initialize/Initialize.ts作为首个 ECS 实体启动资源加载运行import { ecs } from ../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS; import { CCEntity } from ../../../../extensions/oops-plugin-framework/assets/module/common/CCEntity; import { InitResComp, InitResSystem } from ./bll/InitRes; ecs.register(Initialize) export class Initialize extends CCEntity { protected init() { this.add(InitResComp); // 添加资源加载组件 } } // ECS初始化系统 export class EcsInitializeSystem extends ecs.System { constructor() { super(); this.add(new InitResSystem()); } }4.2 资源加载逻辑InitRes.ts创建assets/script/game/initialize/bll/InitRes.ts实现异步资源加载队列typescript运行import { oops } from ../../../../../extensions/oops-plugin-framework/assets/core/Oops; import { AsyncQueue, NextFunction } from ../../../../../extensions/oops-plugin-framework/assets/libs/collection/AsyncQueue; import { ecs } from ../../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS; import { UIID } from ../../common/config/GameUIConfig; import { Initialize } from ../Initialize; import { LoadingViewComp } from ../view/LoadingViewComp; ecs.register(InitRes) export class InitResComp extends ecs.Comp { reset() { } } // 资源加载系统Bundle→多语言→通用资源→加载界面 export class InitResSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem { filter(): ecs.IMatcher { return ecs.allOf(InitResComp); } entityEnter(e: Initialize): void { var queue: AsyncQueue new AsyncQueue(); // 1. 加载远程Bundle this.loadBundle(queue); // 2. 加载多语言包 this.loadLanguage(queue); // 3. 加载通用资源 this.loadCommon(queue); // 4. 加载完成后打开加载界面 this.onComplete(queue, e); // 执行队列 queue.play(); } private loadBundle(queue: AsyncQueue) { queue.push(async (next: NextFunction) { const bundleName oops.res.defaultBundleName; if (bundleName bundleName ! resources) { await oops.res.loadBundle(bundleName); } next(); }); } private loadLanguage(queue: AsyncQueue) { queue.push((next: NextFunction) { let lan oops.storage.get(language); if (!lan) { lan oops.config.game.languageDefault; oops.storage.set(language, lan); } oops.language.setLanguage(lan, next); }); } private loadCommon(queue: AsyncQueue) { queue.push((next: NextFunction) { oops.res.loadDir(common, next); }); } private onComplete(queue: AsyncQueue, e: Initialize) { queue.complete async () { var node await oops.gui.open(UIID.Loading); if (node) e.add(node.getComponent(LoadingViewComp)!); e.remove(InitResComp); }; } }步骤 5加载界面与热更5.1 加载界面组件LoadingViewComp.ts创建assets/script/game/initialize/view/LoadingViewComp.ts实现加载进度展示与资源加载import { Prefab, sys, _decorator } from cc; import { oops } from ../../../../../extensions/oops-plugin-framework/assets/core/Oops; import { ecs } from ../../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS; import { CCViewVM } from ../../../../../extensions/oops-plugin-framework/assets/module/common/CCViewVM; import { UIID } from ../../common/config/GameUIConfig; import { HotUpdate } from ./HotUpdate; import { PlatformSDK } from ../../common/sdk/PlatformSDK; import { Initialize } from ../Initialize; const { ccclass } _decorator; ccclass(LoadingViewComp) ecs.register(LoadingView, false) export class LoadingViewComp extends CCViewVMInitialize { // 绑定UI的数据进度、提示文本 data: any { finished: 0, total: 0, progress: 0, prompt: }; private progress: number 0; reset(): void { this.data.prompt oops.language.getLangByID(loading_load_player); oops.gui.open(UIID.Login); // 加载完成打开登录界面 oops.gui.remove(UIID.Loading); // 关闭加载界面 } start() { // 原生平台且不跳过热更时启动热更 if (sys.isNative !PlatformSDK.isSkipHotUpdate()) { this.addComponent(HotUpdate); } else { this.enter(); } } enter() { this.loadRes(); } private async loadRes() { this.data.progress 0; await this.loadCustom(); // 加载JSON配置表 this.loadGameRes(); // 加载游戏预制体/纹理 } private loadCustom() { this.data.prompt oops.language.getLangByID(loading_load_json); return new Promise(async (resolve) { // 此处扩展加载自定义JSON配置表 resolve(); }); } private loadGameRes() { this.data.prompt oops.language.getLangByID(loading_load_game); oops.res.loadDir( gui/main, Prefab, this.onProgressCallback.bind(this), this.onCompleteCallback.bind(this) ); } // 加载进度回调 private onProgressCallback(finished: number, total: number) { this.data.finished finished; this.data.total total; var progress finished / total; if (progress this.progress) { this.progress progress; this.data.progress (progress * 100).toFixed(2); } } // 加载完成回调 private onCompleteCallback() { this.ent.remove(LoadingViewComp); } }5.2 热更管理HotUpdate.ts Hot.tsHotUpdate.ts-assets/script/game/initialize/view/HotUpdate.ts运行import { Component, game, _decorator } from cc; import { oops } from ../../../../../extensions/oops-plugin-framework/assets/core/Oops; import { Hot, HotOptions } from ./Hot; import { LoadingViewComp } from ./LoadingViewComp; const { ccclass } _decorator; ccclass(HotUpdate) export class HotUpdate extends Component { private hot new Hot(); private lv: LoadingViewComp null!; onLoad() { this.lv this.getComponent(LoadingViewComp)!; this.lv.data.prompt oops.language.getLangByID(update_tips_check_update); this.startHotUpdate(); } private startHotUpdate() { let options new HotOptions(); // 热更进度回调 options.onUpdateProgress (event: jsb.EventAssetsManager) { let pc event.getPercent(); if (!isNaN(pc)) { this.lv.data.finished event.getDownloadedFiles(); this.lv.data.total event.getTotalFiles(); this.lv.data.progress (pc * 100).toFixed(2); } }; // 无需热更 options.onNoNeedToUpdate () { this.lv.enter(); }; // 热更成功重启游戏 options.onUpdateSucceed () { this.lv.data.progress 100; setTimeout(() { game.restart(); }, 1000); }; // 热更失败重试 options.onUpdateFailed () { this.hot.checkUpdate(); }; this.hot.init(options); } }Hot.ts-assets/script/game/initialize/view/Hot.ts热更核心逻辑运行export class HotOptions { onUpdateProgress?: (event: any) void; onNoNeedToUpdate?: () void; onUpdateSucceed?: () void; onUpdateFailed?: (code: number, msg: string) void; } export class Hot { private options: HotOptions null!; init(options: HotOptions) { this.options options; this.checkUpdate(); } // 检查热更需对接项目实际的manifest地址 checkUpdate() { if (!jsb) { this.options.onNoNeedToUpdate?.(); return; } // 扩展点实现jsb.AssetsManager的热更逻辑 this.options.onNoNeedToUpdate?.(); } // 版本对比工具方法 versionCompareHandle(versionA: string, versionB: string): number { const partsA versionA.split(.).map(Number); const partsB versionB.split(.).map(Number); for (let i 0; i Math.max(partsA.length, partsB.length); i) { const a partsA[i] || 0; const b partsB[i] || 0; if (a ! b) return a - b; } return 0; } }步骤 6账号与登录模块6.1 账号管理Account.ts AccountData.tsAccount.ts-assets/script/game/account/Account.ts登录 / 登出 / 鉴权运行import { oops } from ../../../../extensions/oops-plugin-framework/assets/core/Oops; import { GameEvent } from ../common/config/GameEvent; import { MsgID } from ../common/config/GameConst; import { httpChannel } from ../common/net/HttpChannelManager; import { AccountData } from ./AccountData; export class Account { data: AccountData null; // 账号密码登录 login(req: LoginReq) { httpChannel.req(MsgID.Login, req, this.onLoginHttpResp.bind(this), () { oops.message.dispatchEvent(GameEvent.LoginError); } ); } // SDK token登录 loginAuth(req: LoginAuthReq) { httpChannel.req(MsgID.LoginAuth, req, this.onLoginHttpResp.bind(this), () { oops.message.dispatchEvent(GameEvent.LoginError); } ); } // 登出 logout() { this.data null; } // 检查是否登录 isLogin(): boolean { return this.data ! null; } // 登录成功回调 private onLoginHttpResp(httpResp: HttpSuccResp) { if (httpResp.code ! 0) { oops.message.dispatchEvent(GameEvent.LoginError, 登录失败(${httpResp.message})); return; } const resp httpResp.data as LoginResp; if (!resp?.ok) { oops.message.dispatchEvent(GameEvent.LoginError, 登录失败(${resp.msg})); return; } this.data new AccountData(httpResp.data); oops.storage.setUser(this.data.id); httpChannel.setAuthorization(this.data.token); oops.message.dispatchEvent(GameEvent.LoginSuccess); } } // 全局账号实例 export var account new Account(); // 类型定义可迁移到NetProtocol.ts export interface LoginReq { username: string; password: string; serverid: number; } export interface LoginAuthReq { userid: string; gameLoginToken: string; serverid: number; channelType: number; } export interface HttpSuccResp { code: number; message: string; data: any; } export interface LoginResp { ok: boolean; msg?: string; gt: string; role: { id: string; name: string }; info: { rolelevel: number }; }AccountData.ts-assets/script/game/account/AccountData.ts账号数据封装运行import { LoginResp } from ./Account; export class AccountData { protected data: LoginResp null; constructor(data: LoginResp) { this.data data; } get info() { return this.data; } get id(): string { return this.data.role.id; } get name(): string { return this.data.role.name; } get token(): string { return this.data.gt; } get heroLevel(): number { return this.data.info.rolelevel; } }6.2 登录 UI 组件Login.ts创建assets/script/game/login/Login.ts实现登录界面交互运行import { Component, EditBox, Toggle, _decorator } from cc; import { oops } from ../../../../extensions/oops-plugin-framework/assets/core/Oops; import { app } from ../../Main; import { GameEvent } from ../common/config/GameEvent; import { UIID } from ../common/config/GameUIConfig; import { StorageKey } from ../common/config/StorageKey; import { SubmitTypes } from ../common/config/GameConst; import { smc } from ../common/SingletonModuleComp; import { PlatformSDK } from ../common/sdk/PlatformSDK; import { LoginReq, LoginAuthReq } from ../account/Account; const { ccclass, property } _decorator; ccclass(Login) export class Login extends Component { property(EditBox) private edtAccount: EditBox; property(Toggle) private tglUsePolicy: Toggle; property(Node) private nodeUsePolicy: Node; private _inLoading false; get loading() { return this._inLoading; } set loading(v: boolean) { this._inLoading v; } async onLoad() { // 监听登录事件 oops.message.on(GameEvent.LoginSuccess, this._eventHandler, this); oops.message.on(GameEvent.LoginError, this._eventHandler, this); } async start() { // 测试渠道预填账号 if (PlatformSDK.isTestChannel()) { var user oops.storage.get(StorageKey.Account); if (user) { this.edtAccount.string user; } } else { this.edtAccount.node.active false; // 已同意协议则自动登录 if (oops.storage.getNumber(StorageKey.LoginUsePolicy, 0)) { PlatformSDK.doLogin(); } } this.initView(); } onDestroy() { oops.message.off(GameEvent.LoginSuccess, this._eventHandler, this); oops.message.off(GameEvent.LoginError, this._eventHandler, this); } // 初始化协议勾选状态 initView() { var storage oops.storage.get(StorageKey.LoginUsePolicy); this.nodeUsePolicy.active (storage || Number(storage) ! 1); } // 事件处理 private async _eventHandler(event: string, msg: string null) { if (event GameEvent.LoginSuccess) { PlatformSDK.doSubmit(SubmitTypes.EnterGame); await oops.gui.open(UIID.Main); oops.gui.remove(UIID.Login, true); } else if (event GameEvent.LoginError) { this.loading false; oops.gui.toast(msg || 登录失败); } } // 登录按钮点击 onLoginClick() { if (this.loading) return; if (PlatformSDK.isTestChannel()) { this.testLogin(); } else { this.sdkLogin(); } } // 测试渠道登录 private testLogin() { const account this.edtAccount.string.trim(); if (!account) { oops.gui.toast(请输入账号); return; } oops.storage.set(StorageKey.Account, account); const req: LoginReq { username: account, password: 123123, serverid: 1 }; smc.account.login(req); this.loading true; } // SDK登录 private async sdkLogin() { const data await PlatformSDK.doLogin(); if (!data) return; await this.serverLogin(data); } // 对接游戏服务器登录 private async serverLogin(data: RespLogin) { this.loading true; // 扩展点对接平台鉴权服务器、获取游戏服务器信息、最终登录 const reqLogin: LoginAuthReq { userid: data.uid, gameLoginToken: , // 从平台鉴权服务器获取 serverid: 1, channelType: PlatformSDK.cfg.channel_type, }; smc.account.loginAuth(reqLogin); } // 同意协议 onClickSure() { if (!this.tglUsePolicy.isChecked) { oops.gui.toast(请同意隐私政策); return; } oops.storage.set(StorageKey.LoginUsePolicy, 1); PlatformSDK.doLogin(); this.nodeUsePolicy.active false; } // 退出游戏 onClickCancel() { app.exit(); } } // 类型定义 export interface RespLogin { uid: string; token: string; }步骤 7网络与 SDK 封装7.1 HTTP 请求管理HttpChannelManager.ts创建assets/script/game/common/net/HttpChannelManager.ts封装游戏服务器 HTTP 请求typescript运行import { oops } from ../../../../../extensions/oops-plugin-framework/assets/core/Oops; import { GameServerConfig } from ../../login/Login; class HttpChannelManager { private authorization: string ; private gameServer: GameServerConfig null; // 设置鉴权Token setAuthorization(token: string) { this.authorization token; } // 设置游戏服务器配置 setGameServer(s: GameServerConfig) { this.gameServer s; } // 发送HTTP请求 req(msgId: number, req: any, onOk: Function, onErr?: Function) { if (!this.gameServer?.gameUrl) { onErr?.(游戏服务器未配置); return; } const url ${this.gameServer.gameUrl}/api/${msgId}; const headers: Recordstring, string { Content-Type: application/json, }; if (this.authorization) { headers[Authorization] Bearer ${this.authorization}; } // 扩展点实现oops.http.post请求 // oops.http.post(url, req, headers, onOk, onErr); } } export var httpChannel new HttpChannelManager();7.2 平台 SDK 抽象PlatformSDK.ts创建assets/script/game/common/sdk/PlatformSDK.ts统一多平台 SDK 接入运行import { game } from cc; import { SubmitTypes } from ../config/GameConst; import { RespLogin } from ../../login/Login; class PlatformSDK { cfg { channel_type: 0, skip_hot_update: false, }; // 初始化SDK init() { /* 扩展点对接微信/字节/QQ等SDK */ } // 销毁SDK destroy() { /* 扩展点清理SDK资源 */ } // 是否跳过热更 static isSkipHotUpdate(): boolean { return platformSDK.cfg.skip_hot_update; } // SDK登录 static async doLogin(): PromiseRespLogin | null { /* 扩展点实现平台登录 */ return null; } // 埋点上报 static doSubmit(type: SubmitTypes) { /* 扩展点实现埋点逻辑 */ } // 是否测试渠道 static isTestChannel(): boolean { return true; } // 退出游戏 static doExit() { game.end(); } } export var platformSDK new PlatformSDK(); export { PlatformSDK };四、项目验证与扩展4.1 验证要点确认Main组件挂载到场景根节点且根节点包含game和gui子节点检查config.json的localDataKey/localDataIv为 16 位JSON 格式合法这步也可以忽略确保 UI 预制体路径与UIConfigData中的配置一致多语言文件包含所有使用的labId无缺失。五、核心 API 速查API用途oops.gui.open(UIID.X)打开指定 UIoops.gui.toast(msg)显示提示弹窗oops.message.dispatchEvent(ev, data)派发全局事件oops.res.loadDir(path, type, progCb, doneCb)加载目录资源oops.storage.get/set(key, val)本地存储读写oops.language.getLangByID(id)获取多语言文本ecs.getEntityT(Class)获取 ECS 实体最终测试界面如下这个基本算是游戏开发的最简流程可基于此骨架补充业务逻辑快速落地各类小游戏 / 手游项目。
Oops Framework-8-由空项目创建第一个登录界面
一、项目架构因为Oops Framework整个的流程是基本固定的所以要做的就是手动填满所有的流程做的所有东西实际上都要按部就班的按规则做。在动手编码前先明确整体架构流程确保各模块职责清晰。┌─────────────────────────────────────────────────┐ │ Main.ts │ │ 游戏入口继承框架Root初始化ECS/UI/SDK │ └──────────────┬──────────────────────────────────┘ │ ┌───────────▼────────────┐ │ Initialize (ECS) │ │ 首个ECS实体启动资源加载流程 │ └───────────┬────────────┘ │ ┌───────────▼────────────┐ │ InitResSystem │ │ 异步队列加载Bundle→多语言→通用资源 │ └───────────┬────────────┘ │ ┌───────────▼────────────┐ │ LoadingViewComp │ │ 加载界面原生热更逻辑 │ └───────────┬────────────┘ │ ┌───────────▼────────────┐ │ Login Component │ │ 多渠道登录测试/SDK/微信 账号鉴权 │ └───────────┬────────────┘ │ ┌───────────▼────────────┐ │ Account / AccountData │ │ 账号数据管理登录状态维护 │ └─────────────────────────┘二、环境准备开始前确保以下环境就绪安装 Cocos Creator 3.8兼容 Oops Framework安装 Oops Framework 插件执行框架提供的update-oops-plugin-framework.bat/.sh确认项目目录结构规范assets/resources里面只保证config.json文件assets/bundle为框架默认资源加载目录。三、核心模块手动实现步骤 1基础配置文件1.1 框架核心配置config.json创建assets/resources/config.json定义项目基础配置版本、服务器、多语言、UI 层级等json{ type: prod, config: { prod: { version: 1.0.0, package: com.example.game, frameRate: 60, localDataKey: your-encrypt-key, // AES加密密钥16位 localDataIv: your-encrypt-iv-, // AES加密向量16位 httpServer: , httpTimeout: 10000, mobileSafeArea: false, stats: 0 } }, language: { type: [zh, en], default: zh, path: { json: language/json } }, bundle: { default: bundle }, gui: [ { type: UI, name: LayerUI }, { type: PopUp, name: LayerPopUp }, { type: Dialog, name: LayerDialog } ] }1.2 多语言配置这里只是示例创建多语言 JSON 文件统一管理文本避免硬编码assets/bundle/language/json/zh.json{ loading_load_player: 加载玩家数据, loading_load_json: 加载配置表, update_tips_check_update: 检查更新中 }assets/bundle/language/json/en.json{ loading_load_player: Loading player data, loading_load_json: Loading config tables, update_tips_check_update: Checking for updates }1.3 常量与枚举定义存储键枚举StorageKey.ts-assets/script/game/common/config/StorageKey.tsexport enum StorageKey { Account Account, Server Server, LoginUsePolicy LoginUsePolicy, }游戏常量GameConst.ts-assets/script/game/common/config/GameConst.ts// 协议ID枚举 export enum MsgID { Login 1001, LoginAuth 1002, } // 埋点类型枚举 export enum SubmitTypes { EnterGame 1, ExitGame 2, }游戏事件枚举GameEvent.ts-assets/script/game/common/config/GameEvent.tsexport enum GameEvent { GameServerConnected GameServerConnected, LoginSuccess LoginSuccess, LoginError LoginError, Tick1S Tick1S, // 1秒心跳 }资源路径工具GameResPath.ts-assets/script/game/common/config/GameResPath.tsexport class GameResPath { // 获取龙骨模型路径 static getModelPath(name: string): { skeleton: string; atlas: string } { return { skeleton: model/${name}/${name}_ske, atlas: model/${name}/${name}_tex, }; } // 获取通用精灵帧路径 static getSpriteFrameCommon(name: string): string { return common/texture/${name}/spriteFrame; } } // 音频路径常量 export const AudioClipMusic { Main: audio/bgm_main, Game: audio/bgm_game, };UI 配置GameUIConfig.ts-assets/script/game/common/config/GameUIConfig.tsimport { LayerType } from ../../../../../extensions/oops-plugin-framework/assets/core/gui/layer/LayerEnum; import { UIConfig } from ../../../../../extensions/oops-plugin-framework/assets/core/gui/layer/UIConfig; // UI唯一标识 export enum UIID { Loading 1, Login 2, Main 3, } // UIID与层/预制体映射 export var UIConfigData: { [key: number]: UIConfig } { [UIID.Loading]: { layer: LayerType.UI, prefab: gui/loading/loading }, [UIID.Login]: { layer: LayerType.UI, prefab: gui/login/login }, [UIID.Main]: { layer: LayerType.UI, prefab: gui/main/main }, };步骤 2单例模块注册创建assets/script/game/common/SingletonModuleComp.ts统一管理全局单例模块简化调用运行import { ecs } from ../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS; import { account, Account } from ../account/Account; import { Initialize } from ../initialize/Initialize; ecs.register(SingletonModule) export class SingletonModuleComp extends ecs.Comp { // 初始化模块 initialize: Initialize null!; // 账号模块getter封装 get account(): Account { return account; } reset() { } } // 全局单例访问器 export var smc: SingletonModuleComp ecs.getSingleton(SingletonModuleComp);步骤 3游戏入口Main.ts创建assets/script/Main.ts作为游戏根入口继承框架Root类初始化核心能力运行import { director, game, macro, _decorator } from cc; import { oops } from ../../extensions/oops-plugin-framework/assets/core/Oops; import { Root } from ../../extensions/oops-plugin-framework/assets/core/Root; import { ecs } from ../../extensions/oops-plugin-framework/assets/libs/ecs/ECS; import { GameEvent } from ./game/common/config/GameEvent; import { UIConfigData, UIID } from ./game/common/config/GameUIConfig; import { smc } from ./game/common/SingletonModuleComp; import { EcsInitializeSystem, Initialize } from ./game/initialize/Initialize; import { PlatformSDK } from ./game/common/sdk/PlatformSDK; const { ccclass } _decorator; ccclass(Main) export class Main extends Root { start() { // 初始化平台SDK PlatformSDK.init(); // 注册定时心跳事件 this.schedule(() { oops.message.dispatchEvent(GameEvent.Tick1S) }, 1, macro.REPEAT_FOREVER, 0.1); } onDestroy() { this.unscheduleAllCallbacks(); } // 配置加载完成后启动初始化ECS实体 protected run() { smc.initialize ecs.getEntityInitialize(Initialize); } // 注册UI配置 protected initGui() { oops.gui.init(UIConfigData); } // 注册ECS系统 protected initEcsSystem() { oops.ecs.add(new EcsInitializeSystem()); } } // 全局快捷访问 export var app new Main();心跳网络游戏开发中使用单机可以注释掉步骤 4初始化模块ECS4.1 初始化实体Initialize.ts创建assets/script/game/initialize/Initialize.ts作为首个 ECS 实体启动资源加载运行import { ecs } from ../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS; import { CCEntity } from ../../../../extensions/oops-plugin-framework/assets/module/common/CCEntity; import { InitResComp, InitResSystem } from ./bll/InitRes; ecs.register(Initialize) export class Initialize extends CCEntity { protected init() { this.add(InitResComp); // 添加资源加载组件 } } // ECS初始化系统 export class EcsInitializeSystem extends ecs.System { constructor() { super(); this.add(new InitResSystem()); } }4.2 资源加载逻辑InitRes.ts创建assets/script/game/initialize/bll/InitRes.ts实现异步资源加载队列typescript运行import { oops } from ../../../../../extensions/oops-plugin-framework/assets/core/Oops; import { AsyncQueue, NextFunction } from ../../../../../extensions/oops-plugin-framework/assets/libs/collection/AsyncQueue; import { ecs } from ../../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS; import { UIID } from ../../common/config/GameUIConfig; import { Initialize } from ../Initialize; import { LoadingViewComp } from ../view/LoadingViewComp; ecs.register(InitRes) export class InitResComp extends ecs.Comp { reset() { } } // 资源加载系统Bundle→多语言→通用资源→加载界面 export class InitResSystem extends ecs.ComblockSystem implements ecs.IEntityEnterSystem { filter(): ecs.IMatcher { return ecs.allOf(InitResComp); } entityEnter(e: Initialize): void { var queue: AsyncQueue new AsyncQueue(); // 1. 加载远程Bundle this.loadBundle(queue); // 2. 加载多语言包 this.loadLanguage(queue); // 3. 加载通用资源 this.loadCommon(queue); // 4. 加载完成后打开加载界面 this.onComplete(queue, e); // 执行队列 queue.play(); } private loadBundle(queue: AsyncQueue) { queue.push(async (next: NextFunction) { const bundleName oops.res.defaultBundleName; if (bundleName bundleName ! resources) { await oops.res.loadBundle(bundleName); } next(); }); } private loadLanguage(queue: AsyncQueue) { queue.push((next: NextFunction) { let lan oops.storage.get(language); if (!lan) { lan oops.config.game.languageDefault; oops.storage.set(language, lan); } oops.language.setLanguage(lan, next); }); } private loadCommon(queue: AsyncQueue) { queue.push((next: NextFunction) { oops.res.loadDir(common, next); }); } private onComplete(queue: AsyncQueue, e: Initialize) { queue.complete async () { var node await oops.gui.open(UIID.Loading); if (node) e.add(node.getComponent(LoadingViewComp)!); e.remove(InitResComp); }; } }步骤 5加载界面与热更5.1 加载界面组件LoadingViewComp.ts创建assets/script/game/initialize/view/LoadingViewComp.ts实现加载进度展示与资源加载import { Prefab, sys, _decorator } from cc; import { oops } from ../../../../../extensions/oops-plugin-framework/assets/core/Oops; import { ecs } from ../../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS; import { CCViewVM } from ../../../../../extensions/oops-plugin-framework/assets/module/common/CCViewVM; import { UIID } from ../../common/config/GameUIConfig; import { HotUpdate } from ./HotUpdate; import { PlatformSDK } from ../../common/sdk/PlatformSDK; import { Initialize } from ../Initialize; const { ccclass } _decorator; ccclass(LoadingViewComp) ecs.register(LoadingView, false) export class LoadingViewComp extends CCViewVMInitialize { // 绑定UI的数据进度、提示文本 data: any { finished: 0, total: 0, progress: 0, prompt: }; private progress: number 0; reset(): void { this.data.prompt oops.language.getLangByID(loading_load_player); oops.gui.open(UIID.Login); // 加载完成打开登录界面 oops.gui.remove(UIID.Loading); // 关闭加载界面 } start() { // 原生平台且不跳过热更时启动热更 if (sys.isNative !PlatformSDK.isSkipHotUpdate()) { this.addComponent(HotUpdate); } else { this.enter(); } } enter() { this.loadRes(); } private async loadRes() { this.data.progress 0; await this.loadCustom(); // 加载JSON配置表 this.loadGameRes(); // 加载游戏预制体/纹理 } private loadCustom() { this.data.prompt oops.language.getLangByID(loading_load_json); return new Promise(async (resolve) { // 此处扩展加载自定义JSON配置表 resolve(); }); } private loadGameRes() { this.data.prompt oops.language.getLangByID(loading_load_game); oops.res.loadDir( gui/main, Prefab, this.onProgressCallback.bind(this), this.onCompleteCallback.bind(this) ); } // 加载进度回调 private onProgressCallback(finished: number, total: number) { this.data.finished finished; this.data.total total; var progress finished / total; if (progress this.progress) { this.progress progress; this.data.progress (progress * 100).toFixed(2); } } // 加载完成回调 private onCompleteCallback() { this.ent.remove(LoadingViewComp); } }5.2 热更管理HotUpdate.ts Hot.tsHotUpdate.ts-assets/script/game/initialize/view/HotUpdate.ts运行import { Component, game, _decorator } from cc; import { oops } from ../../../../../extensions/oops-plugin-framework/assets/core/Oops; import { Hot, HotOptions } from ./Hot; import { LoadingViewComp } from ./LoadingViewComp; const { ccclass } _decorator; ccclass(HotUpdate) export class HotUpdate extends Component { private hot new Hot(); private lv: LoadingViewComp null!; onLoad() { this.lv this.getComponent(LoadingViewComp)!; this.lv.data.prompt oops.language.getLangByID(update_tips_check_update); this.startHotUpdate(); } private startHotUpdate() { let options new HotOptions(); // 热更进度回调 options.onUpdateProgress (event: jsb.EventAssetsManager) { let pc event.getPercent(); if (!isNaN(pc)) { this.lv.data.finished event.getDownloadedFiles(); this.lv.data.total event.getTotalFiles(); this.lv.data.progress (pc * 100).toFixed(2); } }; // 无需热更 options.onNoNeedToUpdate () { this.lv.enter(); }; // 热更成功重启游戏 options.onUpdateSucceed () { this.lv.data.progress 100; setTimeout(() { game.restart(); }, 1000); }; // 热更失败重试 options.onUpdateFailed () { this.hot.checkUpdate(); }; this.hot.init(options); } }Hot.ts-assets/script/game/initialize/view/Hot.ts热更核心逻辑运行export class HotOptions { onUpdateProgress?: (event: any) void; onNoNeedToUpdate?: () void; onUpdateSucceed?: () void; onUpdateFailed?: (code: number, msg: string) void; } export class Hot { private options: HotOptions null!; init(options: HotOptions) { this.options options; this.checkUpdate(); } // 检查热更需对接项目实际的manifest地址 checkUpdate() { if (!jsb) { this.options.onNoNeedToUpdate?.(); return; } // 扩展点实现jsb.AssetsManager的热更逻辑 this.options.onNoNeedToUpdate?.(); } // 版本对比工具方法 versionCompareHandle(versionA: string, versionB: string): number { const partsA versionA.split(.).map(Number); const partsB versionB.split(.).map(Number); for (let i 0; i Math.max(partsA.length, partsB.length); i) { const a partsA[i] || 0; const b partsB[i] || 0; if (a ! b) return a - b; } return 0; } }步骤 6账号与登录模块6.1 账号管理Account.ts AccountData.tsAccount.ts-assets/script/game/account/Account.ts登录 / 登出 / 鉴权运行import { oops } from ../../../../extensions/oops-plugin-framework/assets/core/Oops; import { GameEvent } from ../common/config/GameEvent; import { MsgID } from ../common/config/GameConst; import { httpChannel } from ../common/net/HttpChannelManager; import { AccountData } from ./AccountData; export class Account { data: AccountData null; // 账号密码登录 login(req: LoginReq) { httpChannel.req(MsgID.Login, req, this.onLoginHttpResp.bind(this), () { oops.message.dispatchEvent(GameEvent.LoginError); } ); } // SDK token登录 loginAuth(req: LoginAuthReq) { httpChannel.req(MsgID.LoginAuth, req, this.onLoginHttpResp.bind(this), () { oops.message.dispatchEvent(GameEvent.LoginError); } ); } // 登出 logout() { this.data null; } // 检查是否登录 isLogin(): boolean { return this.data ! null; } // 登录成功回调 private onLoginHttpResp(httpResp: HttpSuccResp) { if (httpResp.code ! 0) { oops.message.dispatchEvent(GameEvent.LoginError, 登录失败(${httpResp.message})); return; } const resp httpResp.data as LoginResp; if (!resp?.ok) { oops.message.dispatchEvent(GameEvent.LoginError, 登录失败(${resp.msg})); return; } this.data new AccountData(httpResp.data); oops.storage.setUser(this.data.id); httpChannel.setAuthorization(this.data.token); oops.message.dispatchEvent(GameEvent.LoginSuccess); } } // 全局账号实例 export var account new Account(); // 类型定义可迁移到NetProtocol.ts export interface LoginReq { username: string; password: string; serverid: number; } export interface LoginAuthReq { userid: string; gameLoginToken: string; serverid: number; channelType: number; } export interface HttpSuccResp { code: number; message: string; data: any; } export interface LoginResp { ok: boolean; msg?: string; gt: string; role: { id: string; name: string }; info: { rolelevel: number }; }AccountData.ts-assets/script/game/account/AccountData.ts账号数据封装运行import { LoginResp } from ./Account; export class AccountData { protected data: LoginResp null; constructor(data: LoginResp) { this.data data; } get info() { return this.data; } get id(): string { return this.data.role.id; } get name(): string { return this.data.role.name; } get token(): string { return this.data.gt; } get heroLevel(): number { return this.data.info.rolelevel; } }6.2 登录 UI 组件Login.ts创建assets/script/game/login/Login.ts实现登录界面交互运行import { Component, EditBox, Toggle, _decorator } from cc; import { oops } from ../../../../extensions/oops-plugin-framework/assets/core/Oops; import { app } from ../../Main; import { GameEvent } from ../common/config/GameEvent; import { UIID } from ../common/config/GameUIConfig; import { StorageKey } from ../common/config/StorageKey; import { SubmitTypes } from ../common/config/GameConst; import { smc } from ../common/SingletonModuleComp; import { PlatformSDK } from ../common/sdk/PlatformSDK; import { LoginReq, LoginAuthReq } from ../account/Account; const { ccclass, property } _decorator; ccclass(Login) export class Login extends Component { property(EditBox) private edtAccount: EditBox; property(Toggle) private tglUsePolicy: Toggle; property(Node) private nodeUsePolicy: Node; private _inLoading false; get loading() { return this._inLoading; } set loading(v: boolean) { this._inLoading v; } async onLoad() { // 监听登录事件 oops.message.on(GameEvent.LoginSuccess, this._eventHandler, this); oops.message.on(GameEvent.LoginError, this._eventHandler, this); } async start() { // 测试渠道预填账号 if (PlatformSDK.isTestChannel()) { var user oops.storage.get(StorageKey.Account); if (user) { this.edtAccount.string user; } } else { this.edtAccount.node.active false; // 已同意协议则自动登录 if (oops.storage.getNumber(StorageKey.LoginUsePolicy, 0)) { PlatformSDK.doLogin(); } } this.initView(); } onDestroy() { oops.message.off(GameEvent.LoginSuccess, this._eventHandler, this); oops.message.off(GameEvent.LoginError, this._eventHandler, this); } // 初始化协议勾选状态 initView() { var storage oops.storage.get(StorageKey.LoginUsePolicy); this.nodeUsePolicy.active (storage || Number(storage) ! 1); } // 事件处理 private async _eventHandler(event: string, msg: string null) { if (event GameEvent.LoginSuccess) { PlatformSDK.doSubmit(SubmitTypes.EnterGame); await oops.gui.open(UIID.Main); oops.gui.remove(UIID.Login, true); } else if (event GameEvent.LoginError) { this.loading false; oops.gui.toast(msg || 登录失败); } } // 登录按钮点击 onLoginClick() { if (this.loading) return; if (PlatformSDK.isTestChannel()) { this.testLogin(); } else { this.sdkLogin(); } } // 测试渠道登录 private testLogin() { const account this.edtAccount.string.trim(); if (!account) { oops.gui.toast(请输入账号); return; } oops.storage.set(StorageKey.Account, account); const req: LoginReq { username: account, password: 123123, serverid: 1 }; smc.account.login(req); this.loading true; } // SDK登录 private async sdkLogin() { const data await PlatformSDK.doLogin(); if (!data) return; await this.serverLogin(data); } // 对接游戏服务器登录 private async serverLogin(data: RespLogin) { this.loading true; // 扩展点对接平台鉴权服务器、获取游戏服务器信息、最终登录 const reqLogin: LoginAuthReq { userid: data.uid, gameLoginToken: , // 从平台鉴权服务器获取 serverid: 1, channelType: PlatformSDK.cfg.channel_type, }; smc.account.loginAuth(reqLogin); } // 同意协议 onClickSure() { if (!this.tglUsePolicy.isChecked) { oops.gui.toast(请同意隐私政策); return; } oops.storage.set(StorageKey.LoginUsePolicy, 1); PlatformSDK.doLogin(); this.nodeUsePolicy.active false; } // 退出游戏 onClickCancel() { app.exit(); } } // 类型定义 export interface RespLogin { uid: string; token: string; }步骤 7网络与 SDK 封装7.1 HTTP 请求管理HttpChannelManager.ts创建assets/script/game/common/net/HttpChannelManager.ts封装游戏服务器 HTTP 请求typescript运行import { oops } from ../../../../../extensions/oops-plugin-framework/assets/core/Oops; import { GameServerConfig } from ../../login/Login; class HttpChannelManager { private authorization: string ; private gameServer: GameServerConfig null; // 设置鉴权Token setAuthorization(token: string) { this.authorization token; } // 设置游戏服务器配置 setGameServer(s: GameServerConfig) { this.gameServer s; } // 发送HTTP请求 req(msgId: number, req: any, onOk: Function, onErr?: Function) { if (!this.gameServer?.gameUrl) { onErr?.(游戏服务器未配置); return; } const url ${this.gameServer.gameUrl}/api/${msgId}; const headers: Recordstring, string { Content-Type: application/json, }; if (this.authorization) { headers[Authorization] Bearer ${this.authorization}; } // 扩展点实现oops.http.post请求 // oops.http.post(url, req, headers, onOk, onErr); } } export var httpChannel new HttpChannelManager();7.2 平台 SDK 抽象PlatformSDK.ts创建assets/script/game/common/sdk/PlatformSDK.ts统一多平台 SDK 接入运行import { game } from cc; import { SubmitTypes } from ../config/GameConst; import { RespLogin } from ../../login/Login; class PlatformSDK { cfg { channel_type: 0, skip_hot_update: false, }; // 初始化SDK init() { /* 扩展点对接微信/字节/QQ等SDK */ } // 销毁SDK destroy() { /* 扩展点清理SDK资源 */ } // 是否跳过热更 static isSkipHotUpdate(): boolean { return platformSDK.cfg.skip_hot_update; } // SDK登录 static async doLogin(): PromiseRespLogin | null { /* 扩展点实现平台登录 */ return null; } // 埋点上报 static doSubmit(type: SubmitTypes) { /* 扩展点实现埋点逻辑 */ } // 是否测试渠道 static isTestChannel(): boolean { return true; } // 退出游戏 static doExit() { game.end(); } } export var platformSDK new PlatformSDK(); export { PlatformSDK };四、项目验证与扩展4.1 验证要点确认Main组件挂载到场景根节点且根节点包含game和gui子节点检查config.json的localDataKey/localDataIv为 16 位JSON 格式合法这步也可以忽略确保 UI 预制体路径与UIConfigData中的配置一致多语言文件包含所有使用的labId无缺失。五、核心 API 速查API用途oops.gui.open(UIID.X)打开指定 UIoops.gui.toast(msg)显示提示弹窗oops.message.dispatchEvent(ev, data)派发全局事件oops.res.loadDir(path, type, progCb, doneCb)加载目录资源oops.storage.get/set(key, val)本地存储读写oops.language.getLangByID(id)获取多语言文本ecs.getEntityT(Class)获取 ECS 实体最终测试界面如下这个基本算是游戏开发的最简流程可基于此骨架补充业务逻辑快速落地各类小游戏 / 手游项目。