Nincs leírás

LoganZ2 1050acd691 add CLI publish/asset/delete commands, rewrite README with config-file support 3 napja
README.md 1050acd691 add CLI publish/asset/delete commands, rewrite README with config-file support 3 napja
cli.js 1050acd691 add CLI publish/asset/delete commands, rewrite README with config-file support 3 napja
game-sdk.d.ts 1050acd691 add CLI publish/asset/delete commands, rewrite README with config-file support 3 napja
game-sdk.js 1050acd691 add CLI publish/asset/delete commands, rewrite README with config-file support 3 napja
package.json 1050acd691 add CLI publish/asset/delete commands, rewrite README with config-file support 3 napja

README.md

Game SDK

这个包同时提供两部分能力:

  1. 浏览器运行时的全局 GameSDK 类型定义。
  2. 发布到正式平台的 CLI 命令:game-sdk publishgame-sdk assetgame-sdk delete

目标用法很简单:

  1. 游戏项目安装这个包。
  2. 游戏代码里直接使用全局 GameSDK
  3. package.json 里写 npm run buildnpm run publish
  4. npm run publish 发布到正式平台。

这个 SDK 只面向正式平台发布,不支持本地部署平台服务。

快速开始

1. 安装

如果当前还没发到 npm,直接用 git 安装:

npm install git+https://gitee.com/personal-site-micro/game-sdk.git

再安装打包工具,例如:

npm install --save-dev esbuild

2. 配置脚本

package.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

{
  "game-id": "my-game",
  "name": "我的游戏",
  "bundle": "./bundle.js",
  "description": "游戏描述",
  "controls": "方向键移动"
}

4. 编写游戏入口

src/game.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. 发布

npm run publish

发布成功后,游戏地址为:

https://loganz2.cn/games/<game-id>

你会用到的两个东西

1. 浏览器里的 GameSDK

平台会在加载你的 bundle 前自动注入全局变量:

GameSDK.gameId
GameSDK.getPlayerName()
GameSDK.submit(score)

类型:

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. 删除游戏

常用命令:

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

平台页面只提供这个容器:

<div id="game-container"></div>

你的游戏必须自己把 Canvas、DOM、WebGL 内容挂到这里。

2. 推荐把入口包在 IIFE 里

;(function () {
  // game code
})()

这样可以避免污染全局作用域。

3. 不要假设容器一开始就有稳定尺寸

平台页面由 React 渲染,容器虽然存在,但你的 bundle 执行时它的尺寸可能还没稳定。

所以:

  1. Canvas 游戏必须处理 resize
  2. 读取到 0 宽高时要等待下一帧重试。
  3. 每次重设 canvas 尺寸后,要先重置 transform,再重新缩放。

推荐模式:

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. 同一个结束条件反复触发时重复提交

推荐加保护:

let submitted = false

async function endGame(score) {
  if (submitted) return
  submitted = true
  await GameSDK.submit(score)
}

6. 资源必须走平台绝对路径

资源上传后,运行时请用:

/api/games/<game-id>/assets/<filename>

例如:

const img = new Image()
img.src = '/api/games/my-game/assets/player.png'

不要依赖本地相对路径结构。

发布命令

发布 bundle

如果配置文件已经写好,直接:

game-sdk publish

也可以显式传参:

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:可选。操作说明。

上传资源

game-sdk asset --game-id my-game --file ./player.png

或者:

game-sdk asset --file ./player.png

上面这个简写成立的前提是 game-id 已经写进配置文件。

如果你想指定平台上的文件名:

game-sdk asset \
  --game-id my-game \
  --file ./assets/player-v2.png \
  --name player.png

删除游戏

game-sdk delete --game-id my-game

本地离线预览

本地只能 stub window.GameSDK 来调试游戏逻辑,不代表本地部署平台。

index.html:

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <style>
      * { box-sizing: border-box; margin: 0; padding: 0; }
      html, body { width: 100%; height: 100%; background: #0a0a0a; }
      #game-container { width: 100%; height: 100%; }
    </style>
  </head>
  <body>
    <div id="game-container"></div>
    <script>
      window.GameSDK = {
        gameId: 'my-game',
        getPlayerName: function () { return 'dev' },
        submit: async function (score) {
          console.log('submit score:', score)
          return { ok: true, rank: 1 }
        }
      }
    </script>
    <script src="./bundle.js"></script>
  </body>
</html>

预览:

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/<id>/assets/<filename>
  3. 上传文件名和代码里使用的文件名不一致

最后

这个 SDK 故意保持很薄。

平台只关心三件事:

  1. 你的游戏能在 #game-container 里正确运行
  2. 一局结束时能提交最终分数
  3. 你能稳定地发布 bundle 和资源

其余渲染、输入、状态机、资源管理,都由游戏自己决定。