# Game SDK 这个包同时提供两部分能力: 1. 浏览器运行时的全局 `GameSDK` 类型定义。 2. 发布到正式平台的 CLI 命令:`game-sdk publish`、`game-sdk asset`、`game-sdk delete`。 目标用法很简单: 1. 游戏项目安装这个包。 2. 游戏代码里直接使用全局 `GameSDK`。 3. `package.json` 里写 `npm run build` 和 `npm run publish`。 4. 用 `npm run publish` 发布到正式平台。 这个 SDK 只面向正式平台发布,不支持本地部署平台服务。 ## 快速开始 ### 1. 安装 如果当前还没发到 npm,直接用 git 安装: ```bash npm install git+https://gitee.com/personal-site-micro/game-sdk.git ``` 再安装打包工具,例如: ```bash npm install --save-dev esbuild ``` ### 2. 配置脚本 `package.json`: ```json { "scripts": { "build": "esbuild src/game.js --bundle --outfile=bundle.js --platform=browser --format=iife", "publish": "npm run build && game-sdk publish" } } ``` ### 3. 配置发布信息 在项目根目录创建 `game-sdk.config.json`: ```json { "game-id": "my-game", "name": "我的游戏", "bundle": "./bundle.js", "description": "游戏描述", "controls": "方向键移动" } ``` ### 4. 编写游戏入口 `src/game.js`: ```js ;(function () { const container = document.getElementById('game-container') if (!container) return const canvas = document.createElement('canvas') canvas.style.cssText = 'width:100%;height:100%;display:block;' container.appendChild(canvas) const ctx = canvas.getContext('2d') let width = 0 let height = 0 let score = 0 let submitted = false let startedAt = 0 let ended = false function resize() { const rect = container.getBoundingClientRect() width = rect.width height = rect.height if (width <= 0 || height <= 0) return false const dpr = window.devicePixelRatio || 1 canvas.width = width * dpr canvas.height = height * dpr ctx.setTransform(1, 0, 0, 1, 0, 0) ctx.scale(dpr, dpr) return true } async function endGame() { if (submitted) return submitted = true ended = true const result = await GameSDK.submit(score) if (result.ok) { console.log('当前排名:', result.rank) } else { console.error('提交失败:', result.error) } } function update(now) { if (ended) return const elapsed = now - startedAt score = Math.floor(elapsed / 1000) if (elapsed >= 30000) { endGame() } } function render() { ctx.fillStyle = '#111' ctx.fillRect(0, 0, width, height) ctx.fillStyle = '#fff' ctx.font = '24px sans-serif' ctx.fillText(`score: ${score}`, 20, 40) } function loop(now) { update(now) render() requestAnimationFrame(loop) } function boot() { if (!resize()) { requestAnimationFrame(boot) return } startedAt = performance.now() requestAnimationFrame(loop) } window.addEventListener('resize', resize) boot() })() ``` ### 5. 发布 ```bash npm run publish ``` 发布成功后,游戏地址为: ```text https://loganz2.cn/games/ ``` ## 你会用到的两个东西 ### 1. 浏览器里的 `GameSDK` 平台会在加载你的 bundle 前自动注入全局变量: ```ts GameSDK.gameId GameSDK.getPlayerName() GameSDK.submit(score) ``` 类型: ```ts type GameSDKSubmitResult = | { ok: true; rank: number } | { ok: false; error: string } ``` 说明: 1. `gameId` 是当前游戏 id。 2. `getPlayerName()` 返回玩家昵称。 3. `submit(score)` 提交一局游戏的最终分数。 ### 2. 终端里的 `game-sdk` 命令 这是发布命令,不是浏览器 API。 它负责: 1. 上传 `bundle.js` 2. 上传资源文件 3. 删除游戏 常用命令: ```bash game-sdk publish game-sdk asset --file ./player.png game-sdk delete --game-id my-game ``` 如果命令参数没有写全,CLI 会继续从这些位置读取配置: 1. 命令行参数 2. `game-sdk.config.json` 3. `game.config.json` 4. `package.json` 里的 `gameSdk` ## 平台运行规则 这些规则必须满足,否则很容易出现空白页、无法开始、尺寸错误、重复提交分数等问题。 ### 1. 必须挂载到 `#game-container` 平台页面只提供这个容器: ```html
``` 你的游戏必须自己把 Canvas、DOM、WebGL 内容挂到这里。 ### 2. 推荐把入口包在 IIFE 里 ```js ;(function () { // game code })() ``` 这样可以避免污染全局作用域。 ### 3. 不要假设容器一开始就有稳定尺寸 平台页面由 React 渲染,容器虽然存在,但你的 bundle 执行时它的尺寸可能还没稳定。 所以: 1. Canvas 游戏必须处理 `resize`。 2. 读取到 `0` 宽高时要等待下一帧重试。 3. 每次重设 canvas 尺寸后,要先重置 transform,再重新缩放。 推荐模式: ```js function resize() { const rect = container.getBoundingClientRect() const width = rect.width const height = rect.height const dpr = window.devicePixelRatio || 1 if (width <= 0 || height <= 0) return false canvas.width = width * dpr canvas.height = height * dpr ctx.setTransform(1, 0, 0, 1, 0, 0) ctx.scale(dpr, dpr) return true } ``` ### 4. 游戏必须有明确结束条件 平台不会替你判断一局是否结束。 你必须自己定义,例如: 1. 倒计时结束 2. 生命耗尽 3. 碰撞失败 4. 关卡清空 5. 无法继续操作 结束时应该: 1. 停止本局逻辑 2. 固化最终分数 3. 调用一次 `GameSDK.submit(finalScore)` ### 5. `submit` 只在一局结束时调用一次 不要这样用: 1. 每得 1 分就提交 2. 在渲染循环里提交 3. 同一个结束条件反复触发时重复提交 推荐加保护: ```js let submitted = false async function endGame(score) { if (submitted) return submitted = true await GameSDK.submit(score) } ``` ### 6. 资源必须走平台绝对路径 资源上传后,运行时请用: ```text /api/games//assets/ ``` 例如: ```js const img = new Image() img.src = '/api/games/my-game/assets/player.png' ``` 不要依赖本地相对路径结构。 ## 发布命令 ### 发布 bundle 如果配置文件已经写好,直接: ```bash game-sdk publish ``` 也可以显式传参: ```bash game-sdk publish \ --game-id my-game \ --name "我的游戏" \ --bundle ./bundle.js \ --description "游戏描述" \ --controls "方向键移动" ``` 参数: 1. `--game-id`:必填。只允许字母、数字、`_`、`-`。 2. `--name`:必填。平台展示名称。 3. `--bundle`:必填。bundle 文件路径。 4. `--description`:可选。简短描述。 5. `--controls`:可选。操作说明。 ### 上传资源 ```bash game-sdk asset --game-id my-game --file ./player.png ``` 或者: ```bash game-sdk asset --file ./player.png ``` 上面这个简写成立的前提是 `game-id` 已经写进配置文件。 如果你想指定平台上的文件名: ```bash game-sdk asset \ --game-id my-game \ --file ./assets/player-v2.png \ --name player.png ``` ### 删除游戏 ```bash game-sdk delete --game-id my-game ``` ## 本地离线预览 本地只能 stub `window.GameSDK` 来调试游戏逻辑,不代表本地部署平台。 `index.html`: ```html
``` 预览: ```bash npm run build npx serve . ``` ## 常见错误 ### 页面空白,但 bundle 已上传 优先检查: 1. 是否真的挂载到了 `#game-container` 2. 是否读取到了 `0` 尺寸后就再也没重试 3. 是否多次 resize 后重复缩放了 canvas 坐标系 4. 是否有运行时错误导致第一帧前中断 ### 分数反复提交 通常是因为: 1. 在动画循环里调用了 `GameSDK.submit` 2. 没有 `submitted` 保护 3. 同一个结束条件被重复触发 ### 资源 404 通常是因为: 1. 资源没有上传 2. 路径不是 `/api/games//assets/` 3. 上传文件名和代码里使用的文件名不一致 ## 最后 这个 SDK 故意保持很薄。 平台只关心三件事: 1. 你的游戏能在 `#game-container` 里正确运行 2. 一局结束时能提交最终分数 3. 你能稳定地发布 bundle 和资源 其余渲染、输入、状态机、资源管理,都由游戏自己决定。