# 像素勇者 **Repository Path**: ikunFS/Roguelike-A ## Basic Information - **Project Name**: 像素勇者 - **Description**: 一个cocos做的肉鸽游戏 像素勇者 - **Primary Language**: TypeScript - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2020-03-13 - **Last Updated**: 2026-11-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 像素勇者 · 无尽肉鸽 v1.1 一款竖屏像素风 2D 动作肉鸽(Roguelike)游戏,使用 **Cocos Creator 3.8.8** 开发。 > 游戏全名:**像素勇者** | 玩法副标题:**无尽肉鸽** --- ## 📸 界面截图 由 `tools/gen-game-shots.js`(游戏端)与 `tools/gen-admin-shots.js`(管理后台)。 **主页面与开局** | 主页面 | 开始游戏 | |---|---| | ![](docs/screenshots/01-menu.png) | ![](docs/screenshots/02-start-dialog.png) | **战斗与结算** | 战斗中(第 6 波) | 升级三选一 | |---|---| | ![](docs/screenshots/03-battle.png) | ![](docs/screenshots/04-levelup.png) | | ·BOSS· 波(第 40 波) | 通关结算 | |---|---| | ![](docs/screenshots/05-boss.png) | ![](docs/screenshots/06-victory.png) | **功能面板** | 角色管理 | 排行榜 | 好友 | |---|---|---| | ![](docs/screenshots/07-charmgr.png) | ![](docs/screenshots/08-rank.png) | ![](docs/screenshots/09-friends.png) | | 聊天 | 兑换码 | 设置 | |---|---|---| | ![](docs/screenshots/10-chat.png) | ![](docs/screenshots/11-redeem.png) | ![](docs/screenshots/12-settings.png) | **管理后台** | 登录 | 仪表盘 | |---|---| | ![](docs/screenshots/admin/01-login.png) | ![](docs/screenshots/admin/02-dashboard.png) | | 用户管理 | 角色管理 | |---|---| | ![](docs/screenshots/admin/03-users.png) | ![](docs/screenshots/admin/04-chars.png) | | 游戏局内管理 | 兑换码 | |---|---| | ![](docs/screenshots/admin/05-gameplay.png) | ![](docs/screenshots/admin/06-redeem.png) | | 问题反馈 | 消息审计 | |---|---| | ![](docs/screenshots/admin/07-feedback.png) | ![](docs/screenshots/admin/08-messages.png) | | 排行榜 | 账号与日志 | |---|---| | ![](docs/screenshots/admin/09-ranks.png) | ![](docs/screenshots/admin/10-accounts.png) | --- ## ✨ 功能特性 ### 主页面 - 月夜沙漠背景,像素风 UI - 主按钮区:**开始游戏** / **角色管理** / **无尽试炼**,最下方右侧是一个方形**奖杯**按钮(打开排行榜) - 右上角三个入口:设置齿轮 / 好友 / 礼物盒(兑换码与反馈) - 开始游戏弹窗:单人游戏 / 多人游戏(多人未开放) ### 玩法模式 - **单人游戏(normal)**:40 波,第 10 / 20 / 30 波为精英波、**第 40 波为最终 BOSS**, 击败 BOSS 即「通关成功!」;血量归零则「游戏结束」 - **无尽试炼(endless)**:没有通关、**没有 BOSS**,波数无限,只有死亡才结束; 结算面板会额外显示本局已通过波数(刷新纪录会标注「新纪录!」)与击败数 - 两种模式的「再来一把」都会**沿用本局模式** ### 战斗系统(无尽肉鸽) - 竖屏 540×960 像素风地图,虚拟摇杆控制移动(固定 / 跟随两种模式,可在设置中切换) - 角色自动索敌攻击(伤害带 0.9~1.1 随机浮动,基础暴击率 6%) - 攻击方式由武器决定 - **40 波编排**(单人模式):**普通波与精英波都不清场**(上波活着的怪直接留到下一波、接着打); **第 10 / 20 / 30 波为精英怪波、第 40 波为最终 BOSS**,精英波/BOSS 波会弹全屏横幅(跟随相机固定在屏幕上方); **只有最终 BOSS 波清场一次**(给 BOSS 战一个干净的开场);无尽试炼没有特殊波、波数无限 - **波次节奏**:第 w 波时长 = `w≤10 ? 22+0.95w : 31.5+0.45(w-10)` 秒,**第 10 波之后增量减半(过波加速)**; 波间喘息 `w≥10 ? 0.8 : 2` 秒(见 `config.ts` 的 `waveDuration` / `restDuration`,调平衡只改这里) - **6 种怪、每种独立像素贴图**,体型按阶位递增(普通 < 精英 < BOSS) | 怪 | 攻击方式 | 体型 | 解锁 | |---|---|---|---| | 游魂 | 近战 | 14 | 第 1 波 | | 巫火 | 远程火球 | 13 | 第 5 波 | | 石甲兽 | 近战坦克 | 22 | 第 10 波 | | 精英·重装卫兵 | 近战(脚下金环) | 26 | 第 10 波 | | 精英·巫火术士 | 远程火球 | 24 | 第 20 波 | | 石像王(BOSS) | 近战(脚下红橙双环) | 44 | 第 40 波 | - **远程怪**停在 190~210px 外发射火球弹道(命中才扣血,飞行约 1.6 秒,来得及走位) - 怪的血量 / 攻击 / 速度随波数线性缩放(`config.ts` 的 `scaleHp/scaleAtk/scaleSpeed`) - 怪物被消灭时播放**消散特效**(白闪 + 扩散环 + 像素碎块四散,体型越大越夸张) - **击败最终 BOSS → 清场并直接结算**,弹出「通关成功!」;血量归零则显示「游戏结束」; 通关记录会写到「当前登场角色」上(见「角色管理」的通关印章) - 经验晶掉落自动吸附;**升级**时回满血,并**自动在攻击 / 生命 / 防御中随机一项 +2**, 同时弹出**三选一**面板(前两位出技能、第三位出属性)—— 这次「白送」的那点加成落在哪一项 会写在面板标题下方(如「升级自动奖励:生命上限 +2」),不用再盯着血条猜它为什么没动 - **技能**(各 5 级,Lv4→Lv5 显示「大升级!!」): - **法阵**:脚下展开**像素风魔法阵** —— 外圈 + 符文刻度环 + 内接五角星 + 棋盘格抖动底纹, 图案**左右严格对称**;按固定时间步进做**慢帧(跳帧)旋转**(约 3.9 秒一圈),持续对范围内敌人结算伤害(Lv1-4 蓝色、满级粉色) - **土豆雷**:埋设 → 待机 → 敌人靠近引爆 - **局内道具**:击杀怪物后按概率掉落,走近即自动拾取。概率由管理后台「游戏局内管理」维护, 随 `/api/state` 下发到客户端;默认三种各 **2%**,且**互相独立判定**(概率都很小,几乎不会同时掉) | 道具 | 图标 | 拾取效果 | |---|---|---| | 炸弹 | 圆弹体 + 斜引线 + 火花 | 直接秒杀全图上所有**非精英、非 BOSS** 怪(精英与 BOSS 免疫),并在玩家脚下炸开一场**爆炸特效**(尖刺火球 + 冲击波环 + 四散火花)| | 磁铁 | U 形马蹄(两极朝上,比另两个更小更紧凑) | 把场上所有经验晶**吸向玩家**(缓慢起步、逐渐加速飞向玩家) | | 医疗箱 | 白箱 + 红十字 | 立刻**回满血** | > 后台把某项概率改成 0 或停用,就是**真的不掉落**(不会回落到默认值)。 > 概率语义:**启用**时可在 `0.01% ~ 100%` 之间填(不能为 0,否则等于「启用了却永不掉落」); > **停用**时概率自动归 0。数据库 `item_drops.rate` 上有 `CHECK(rate >= 0 AND rate <= 1)` 约束, > 即使有人绕过后台直接改库,也写不进 >100% 的值。 - **怪物走路「果冻感」**:移动中的怪身体一胀一缩(横向胀、纵向缩,体积守恒式挤压,像史莱姆一弹一弹地爬, 约 1.1Hz、幅度 ±11%),静止(进入攻击距离)时立刻复原。缩放只作用于挂在怪身上的**视觉子节点**, 逻辑坐标不受影响 —— 索敌、碰撞、命中判定、掉落位置全都仍然精确。 - HUD:血条 + 经验条 + 波数 + 场上怪物计数;伤害飘字(暴击为金色大字) - 波数只显示「第 N 波」,不再跟段位/精英后缀;**BOSS 波整段换成「·BOSS·」**; **无尽试炼在波数前加「无尽·」(「无尽·第 20 波」)**,一眼看出打的是哪个模式 - 结算界面按模式区分:**单人只有「获得金币」一行**;**无尽试炼**额外显示本局过波数、历史最高纪录与击败数。 - **金币收益**(只在**结算时发一次**,没有逐波发放):`floor((已通过波数 × 25 + 击败数 × 0.4) / 2)` - 「已通过波数」口径:**中途死亡/主动退出**时 HUD 那一波还没打完,不计(显示「第 2 波」= 过了 1 波); **通关**时最后那波(第 40 波 BOSS)已经打完,计满(通过 40 关 = 40 波)。 结算面板的过波数、`users.best_wave`、排行榜都跟这个口径一致 - 前端算完写进 `/api/save` 的 `coins`,服务端只做 `UPDATE users SET coins=coins+X`(不做倍率) - 另一处金币来源是**兑换码**(后台生成,可直接发金币) 胜负两种界面配色不同(胜利金色、失败红色) ### 美术风格 - 全部手搓:像素位图由 `px.ts` 的 `drawPixMap()` 按「字符 → 颜色」点阵绘制,UI 用 `Graphics` + `Label` 直接画, **不使用 ScrollView / 预制体** - 怪物、土豆雷、三种局内道具用多色像素位图;法阵把图形**栅格化到像素网格**(逐格画方块, 网格始终轴对齐 → 旋转时像素依旧是方的,不会跟着歪);死亡特效、炸弹爆炸特效、弹道用 `Graphics` 实时绘制 ### 角色 | 角色 | 武器 | 生命 | 攻击 | 防御 | 攻击范围(格) | 攻击间隔(s) | 获取 | |---|---|---|---|---|---|---|---| | 草莓勇士 | 长剑(范围挥砍) | 10 | 5 | 2 | 3 | 0.8 | 默认拥有 | | 镇魂法师 | 法镜(单体索敌) | 8 | 7 | 3 | 15 | 0.3 | 500 金币购买 | | 山岳霸主 | 长棍(范围横扫 + 旋转棍影) | 15 | 3 | 5 | 2 | 0.5 | 800 金币购买 | > 获取方式可在管理后台「角色管理」里改成 **正常售卖 / 暂停售卖 / 活动获取** > (见「角色管理」一节),`chars.sale_status` 决定游戏内购买按钮的文案与可购买性。 - 登场角色的属性即玩家属性(`battle.applyCharStats` 以服务端数据为准);头像、游戏内立绘 与战斗贴图都按登场角色自动切换(贴图映射见 `config.ts` 的 `CHAR_SKINS`) - 武器类型决定攻击方式:`sword` 范围挥砍 / `staff` 单体索敌 / `club` 范围横扫 - ⚠️ 新角色的贴图不在构建包的 resources 里(热注入加不进构建产物),`CHAR_SKINS` 里标 `url: true` 的会**先试 `resources.load`,失败才退回** `/textures/.png` + `ImageAsset` 运行时加载 (同一张贴图只解析一次,结果缓存在 `_texCache`)。所以在 Cocos 里正式 Build 之后, 这两张图会被打进 resources 包,自动走第一条路,**零额外请求**,代码不用改 ### 角色管理 - 地牢拱门背景 + 木板信息面板 - **多角色浏览**:左侧一列圆形头像,点哪个切哪个(含未拥有的 —— 那是购买入口) - 圆形头像框 + 角色待机立绘展示(待机动画循环,按角色加载对应贴图) - 属性面板:生命值、武器类型、攻击力、防御力、攻击范围、攻击间隔、击败数量 (数值全部来自服务端角色数据,非前端写死) - 按钮按「是否拥有 + 售卖方式」出文案: - 已登场 → 灰色「已登场」;已拥有未登场 → 红色「登场」 - 未拥有且**正常售卖** → 金色「购买 · N金币」(点击二次确认后扣金币) - 未拥有且**暂停售卖** → 「限时购买」+ 小字「请关注开放购买时间」(不可购买,仅提示) - 未拥有且**活动获取** → 「活动获取」+ 小字「请关注游戏活动」(不可购买,仅提示) - 售卖方式由管理后台维护(`chars.sale_status`),服务端 `api_buy` 同样会拒绝非正常售卖的角色 - **通关印章**:用过该角色打完 40 波(单人模式通关)后,属性区右下角盖一枚斜置的红色「通·关」印章 ### 昵称与个人资料 - 点击主页面左上角头像名牌 → 修改昵称(最长 12 字,重名校验) - **改名冷却 7 天**:冷却期内面板上方显示「当前冷却期剩余:x天x小时x分钟」,不在冷却期则不显示该行 ### 排行榜 - 主页面奖杯按钮打开,顶部「好友 / 全服」切换 - **历史最高榜(实时更新)**:直接取每位用户的最高通过波数(`users.best_wave`),打出新纪录刷新即见 - 显示前 100 名,每行 = 名次(**1~3 名金 / 银 / 铜奖杯**,其余为数字)+ 圆形头像 + 昵称 + 「最高通过波数:XX 波」 - 底部固定一行「我的排名」,未上榜时名次显示 `-` ### 好友与聊天 - 好友列表(头像 + 昵称 + uid + 未读红点),支持按 uid / 昵称搜索 - 我的 uid 可点击复制,方便分享给好友 - 好友申请:收到 / 发出两个分页,可接受、拒绝、删除 - **删除好友有二次确认**:标题「删除好友」、按钮「确定删除」,避免误点一下就删掉 - **屏蔽词打码**:消息里命中后台维护的屏蔽词时,显示为等长 `*` - 聊天窗口:气泡分左右、消息上方自带「日期 + 时间」(昨天显示为「昨天」) - **实时刷新走 WebSocket**(`/ws/chat`):服务端消息一落库就推给收件人,窗口开着立刻可见 - **多行输入**:回车换行(最多显示 3 行,超出自动滚到底部),`Ctrl/⌘ + Enter` 发送;也可以直接点「发送」按钮 - 长消息气泡自动换行并自适应高度,不会溢出边框 ### 兑换码与反馈 - 主页面右上角礼物盒进入 - **兑换码**:输入兑换码换取金币 / 解锁角色(统一转大写比较,同一账号同一码只能领一次) - 标题栏左侧的感叹号图标可切到**反馈页**:填写标题 + 内容提交给后台 - 兑换码由 `tools/gen_redeem.py` 生成(见下文) ### 账号封禁 - 在管理后台封禁某位玩家(`users.banned`)后,该账号打开游戏时:服务端对 `/api/*` 与 `/ws/chat` 一律返回 **403 `{err:'banned'}`**(静态资源照常返回,页面仍能打开),前端随即盖上一层 **不可关闭的全屏提示层**(「账号已被封禁 / 请联系管理员 / 游戏功能已停用」)。 - 提示层会**吞掉全部触摸事件**,各面板入口另有守卫,因此**主菜单任何按钮都点不动**; 被封禁期间**不做任何数据伪装** —— 名字、金币保持原样,只是被遮罩整体盖住。 - 解封后刷新即恢复正常:封禁可逆,不影响存档。 ### 设置 - 音乐音量 / 音效音量 - 摇杆模式:固定摇杆 / 跟随摇杆 - 退出到主页面(当前局游戏直接结算) --- ## 🛠 技术栈 | 层 | 技术 | |---|---| | 引擎 | Cocos Creator 3.8.8 | | 语言 | TypeScript | | 渲染 | Cocos 内置 WebGL | | 字体 | 像素字体 ZLabsPixel(已子集化,见「包体积」一节) | | 后端 | Python `http.server` + PyMySQL(存档 / 好友 / 聊天 / 排行榜 / 兑换码) | | 管理后台 | Python **FastAPI** + Uvicorn(`admin/`,独立进程,共用同一 MySQL) | | 实时通道 | 手写 WebSocket(`socketserver.ThreadingMixIn`,零第三方依赖) | | 数据库 | MySQL 8(表结构见 `schema.sql`) | | 构建 | web-mobile 平台 + 微信小游戏(`build/wechatgame`,本地测试用) | ### 屏幕适配(竖屏设计 + 宽窗口留边) 设计分辨率 540×960,策略**按窗口比例自动切**(`Player.applyResolution()`,每帧检测、拖窗口即时生效): | 窗口 | 策略 | 结果 | |---|---|---| | 手机竖屏(比例 ≤ 0.6,如 375×667 / 375×812 / 20:9) | `FIXED_WIDTH` | 宽恒 540,高度随比例变(960 / 1169…),铺满全屏 | | 平板 / 电脑浏览器(更宽) | `SHOW_ALL` | 整个 540×960 等比装进去,左右留深棕边,**任何比例都不会裁 UI** | > ⚠️ 之前只有 `FIXED_WIDTH`,在 1440×900 的电脑浏览器上可视区只有 **540×337**,上下两端的 UI 直接被切 > (玩家反馈的「电脑浏览器 UI 被遮挡」)。**别再假设可视高 = 960。** > - 纵向要按 `screenH()`(`= max(960, 可视高)`)算,横向全屏背景按 `screenW()`(= 画布实际渲染宽度) > - 触摸/世界坐标换算一律用 `view.getVisibleSize()`,禁止 `DESIGN_W/H ÷ 2` ### 界面约定(全屏遮罩必须"吃掉"触摸) Cocos 的命中测试只认**有触摸监听且 UITransform 命中区覆盖该点**的节点。只画一块全屏 `Graphics` 是拦不住点击的(Graphics 自带的命中区是 100×100,且没监听)→ 点击会**穿透**到下层。 ```ts this.blockTouch(maskNode); // 整屏 this.blockTouch(maskNode, 800, 1200); // 更大(升级/结算那种画大一圈的遮罩) ``` 已覆盖:开始游戏 / 退出确认 / 好友 / 排行榜 / 兑换码 / 设置(两处)/ 聊天 / 二次确认 / 升级选择 / 结算。 **新增任何全屏弹窗,都要在遮罩上补这一句**,否则会出现「弹窗开着还能点到主菜单按钮」。 ### 代码约定(`Game.prototype` 上不要重复定义同名方法) 每个功能模块都是 `installXxx(Game)` 往 `Game.prototype` 上挂方法,**同名会静默覆盖** —— 不报编译错误、类型检查也看不出来,只有运行时表现异常: - 踩过的坑:`ui.ts` 与 `profile.ts` 各自定义了 `showConfirm`,`installProfile` 在 `installUI` 之后执行 → 实际生效的是两参版本,而好友删除按四参调用(`parent/title/message/onOk`)→ 弹窗内容错位、 点「确定」直接抛错(表现为「确认弹窗没了」)。现已拆成 `showConfirm`(四参,通用) 与 `showBuyConfirm`(两参,购买角色专用,标题固定「确认购买」)。 - 新增 `P.xxx = function` 前先 `grep -rn "P.xxx =" assets/scripts` 确认名字没被占用。 - 注释只写「这段在管什么 + 为什么必须这样」:不要记日期与改动史(「2026-10-30 改成…」), 不要复述代码(「// 创建节点」)。跨模块的口径(已通过波数、聊天贴底 vs 排行榜贴顶)、 平台限制、`⚠️` 标记的坑必须留。 --- ## 📋 环境要求 | 依赖 | 版本 / 说明 | |---|---| | Cocos Creator | 3.8.8(只有在重新构建、或改场景与挂载组件时才需要) | | Node.js | ≥ 18(跑 `_inject.js` 热注入和 `tools/` 下的验证脚本) | | Python | 3.x,需 `pymysql`(`pip install pymysql`) | | 管理后台依赖 | FastAPI / Uvicorn / PyMySQL / PyJWT(见 `admin/requirements.txt`) | | MySQL | 8.x,监听 `127.0.0.1:3306`;默认 `root` / `123456`,见 `server.py` 顶部 `MYSQL_CONF`,与本机不一致就改这里 | | 可选:`fonttools` | 只在重新做字体子集化时需要(`pip install fonttools`) | > `node_modules` 里只装了一个包 `ws`,供 `tools/` 下的测试脚本做 CDP 调试与 WebSocket 通信用。 --- ## 🚀 快速开始 ### 本地预览 `server.py` 同时提供后端 API(MySQL)和构建产物托管: ```bash python server.py ``` 浏览器访问:**http://127.0.0.1:8200/** > 服务默认监听 **`0.0.0.0`**(手机真机才能连上),启动时会打印一行 > `局域网(手机真机用这个): http://10.x.x.x:8200`。只想本机用就 `HOST=127.0.0.1 python server.py` > —— 局域网里任何人都能打开你的存档页,公用 WiFi 下建议关掉。 > **日志**:两个服务的输出都会自动写到 `logs/`(`server.log` / `admin.log`), > 单文件 5MB、保留 5 份自动轮转,无需清空。所以**不要**再写 `python server.py > _server.log` > —— 日志改动为接管 `print` 后,重定向的文件会一直是空的;直接前台运行即可, > 终端里照常能看到(只有检测到输出被重定向时才关闭终端副本,避免重复写一份不会轮转的文件)。 首次运行会自动建表。想用两个身份互加好友 / 聊天,开两个标签页分别加 `?uid=10000001`、`?uid=10000002` 即可(没有登录体系,用 URL 里的 uid 指定身份)。 > 身份**只认 uid** —— uid 唯一且稳定,改名后依然定位到同一个人; > 昵称可以随时改、还可能重名,所以不能当作身份标识(历史版本支持过的 `?as=<昵称>` 已废弃)。 > 纯静态预览也可以用 `python -m http.server 8200`,但好友、聊天、进度保存 > 这些依赖 API 的功能会不可用。 ### 启动网页管理后台(独立服务) `admin/` 是一个**独立的 FastAPI 服务**,与游戏后端互不影响,共用同一个 MySQL 库: ```bash pip install -r admin/requirements.txt # 首次 python admin/main.py # 默认 http://127.0.0.1:8300/ ``` - 首次启动会自动建 `admins` / `admin_logs` 表,并创建默认超级管理员 **`admin` / `admin123`**(登录后请立刻在「修改密码」里改掉) - 默认**只绑定 127.0.0.1**;需要换地址/端口用 `ADMIN_HOST` / `ADMIN_PORT` 环境变量 - 接口文档:http://127.0.0.1:8300/docs 模块:仪表盘 / 用户管理 / 角色管理 / **游戏局内管理** / 兑换码 / 问题反馈 / 消息审计 / 排行榜 / 账号与日志。 身份分三级:**超级管理员 `super`**(唯一,可见「账号与日志」)/ **管理员 `admin`**(可改数据)/ **只读 `readonly`**; 所有写操作都会记进 `admin_logs` 审计日志。 - **超级管理员受保护**:不可新增、不可把别人改成超管,且超管账号不可被「修改身份 / 重置密码 / 停用 / 删除」 (本人要改口令用右上角「修改密码」)。 - **屏蔽词**:在「消息审计」里点「屏蔽词管理」增删改查。词表由游戏后端 `/api/words` 下发给客户端, 聊天中命中的词会打码成等长 `*`(服务端下发时也会打码,DB 里保留原文供审计)。 - **角色管理**:改数值(生命/攻击/防御/范围/攻速/价格)与**售卖方式**。 **武器类型只读**(它决定攻击逻辑,改错会让表现与手感对不上); 售卖方式分 **正常售卖 / 暂停售卖 / 活动获取**,直接决定游戏内购买按钮的文案与可购买性。 - **游戏局内管理**:改局内道具(炸弹 / 磁铁 / 医疗箱)的**掉落概率**与启用开关。 按百分比填(`0.05` = 0.05%),最多两位小数;**启用时有效值 0.01 ~ 100**,**停用时概率自动变 0**。 悬停/越界的提示文案为「输入值为0.01-100」,越界即时标红并在保存时被前端拦截。 配置写入 `item_drops` 表,游戏端下次拉取 `/api/state` 即生效; 表上另有 `CHECK(rate >= 0 AND rate <= 1)` 约束兜底(防直接改库写出 >100%)。 - **排行榜**:前三名金银铜奖牌 + 头像,行内可直接「清除波数 / 封禁」。 - **封禁**:在「用户管理」或「排行榜」里封禁玩家后,该账号打开游戏会看到**不可关闭的全屏提示层**、 主菜单完全点不动(详见「账号封禁」一节);解封即恢复。 ### 改完代码热更新(免重新构建) ```bash node _inject.js # 把 assets/scripts 下的 TS 转译并注入 build 产物 ``` 然后浏览器 `Ctrl+Shift+R` 强刷。 > ⚠️ 只对**热注入产物**有效。**Cocos 刚 Build 完的包不要跑它**(会把包注入成白屏), > 详见「热注入 vs 正式构建」一节。 ### 重新构建 ```bash "C:\ProgramData\cocos\editors\Creator\3.8.8\CocosCreator.exe" --project "<项目路径>" --build "platform=web-mobile;debug=true;buildPath=build;startScene=9f9fd7c4-93f0-3947-0cb9-01300b2bbcfc" ``` > 本机 CLI 构建可能卡在「导入资源之前」(`temp/logs/project.log` 报 `engine - query-engine-info` + > 扩展列表 403),几十分钟无产出 —— 这时直接用编辑器 UI「项目 → 构建发布」。 > 构建是一次**ES5 降级**,跑完务必用 `tools/smoke-test.js` 验一遍(异常应为 0)。 --- ### 微信小游戏(本地测试) 编辑器「构建发布」平台选 **微信小游戏**,输出 `build/wechatgame`(不动 `build/web-mobile`), 再用微信开发者工具「导入项目」指向该目录。 - 启动参数:`uid=10000001`(可选 `&base=http://127.0.0.1:8200`,这是默认值) - 详情 → 本地设置 → 勾「**不校验合法域名、web-view、TLS 版本以及 HTTPS 证书**」 - 当前限制:音效/BGM 走 Web Audio(小游戏没有这套)、聊天走 wss(要合法域名)—— 这两块在小游戏里自动降级、不崩 #### 包体积(主包 4MB 是硬限制) | 限制 | 大小 | |---|---| | 主包 | **4 MB** | | 单个分包 | 4 MB | | 所有分包合计 | 20 MB | 开发者工具的「预览 / 真机调试」在本地可放宽到 **8MB**(详情 → 本地设置),但正式上传送审仍按 4MB 主包算。 | 手段 | |---|---|---| | **音频搬出 `assets`** → `media/audio`(`server.py` 的 `AUDIO_DIR` 同步改) | −2.5 MB | 无。`sfx.ts` 一直走 `fetch('/audio/x')` + Web Audio,**从不经过 `resources`**;小游戏又没有 Web Audio,打进包纯属浪费 | | **字体子集化**:15535 字形 → 7556 字(ASCII + GB2312 全集 + 源码/README 里出现的所有字) | −1.76 MB | GB2312 以外的生僻字显示方块。 | > ⚠️ **别把音频放回 `assets/resources`** —— 它会被无条件打进所有构建平台。 > 想再减:主包只留引擎 + 代码,构建面板展开「**小游戏分包**」把 `resources` 设为分包 > (压完约 3.9MB,刚好 < 4MB)。 #### 真机 / 手机预览 手机上 `127.0.0.1` 指**手机自己**,必须换成电脑的局域网 IP。三件事缺一不可: 1. **服务监听 0.0.0.0**:`server.py` 默认已是 `0.0.0.0`,启动时会打印 `局域网(手机真机用这个): http://10.x.x.x:8200`,直接抄这个地址。 (只想本机用:设环境变量 `HOST=127.0.0.1` 再启动) 2. **base 指向局域网 IP**:编译模式的启动参数写 `uid=10000001&base=http://10.x.x.x:8200`。 懒得改编译模式的话,在「调试器 → Console」跑一次 `wx.setStorageSync('base','http://10.x.x.x:8200')` 再重启小游戏 (优先级:启动参数 > 本地存储 > 常量 `MINI_API_BASE`)。 换 WiFi / 电脑 IP 变了,记得 `wx.removeStorageSync('base')`。 3. **Windows 防火墙放行 8200**(用**管理员身份**开终端执行一次,`profile=any` 不能省): ``` netsh advfirewall firewall add rule name="PixelHero-8200" dir=in action=allow protocol=TCP localport=8200 profile=any ``` 本机网卡常被判为「**公用**网络」,而公用配置文件是 `BlockInbound`(默认拒绝所有入站)→ 不写 `profile=any`(或只放行「专用」)时手机流量仍被丢弃,Python 连请求都收不到。 替代做法:关掉后台服务,用**看得见窗口**的终端重新 `python server.py`, Windows 弹「是否允许访问网络」时勾上「专用 + 公用」,规则会自动建好。 **连不上时先二分**(不用猜):手机浏览器直接打开 `http://<电脑IP>:8200/` - 能出页面 → 网络与防火墙都通,问题只在 `base`(回第 2 步) - 打不开 / 一直转圈 → 网络层问题,见下面两条 ⚠️ 手机和电脑要在**同一个 WiFi**;校园网/公司网(`10.x` 段很常见)多半开了「**AP 隔离**」, 同 WiFi 设备互相访问不到 —— 这时**用手机开热点给电脑连**(电脑 IP 会变成 `192.168.x.x`, 重启服务后按它打印的地址改 `base`),或用手机 USB 共享网络。 ⚠️ `xhr error` 只有三种来源:服务没起 / 防火墙拦住 / `base` 还是 `127.0.0.1`(手机上的 127.0.0.1 指手机自己)。 ⚠️ 真机同样依赖「不校验合法域名」:它写在 `project.config.json` 的 `setting.urlCheck` 里, 会跟预览/真机调试一起下发,所以要**先勾好再点预览**。 --- ## 📁 项目结构 ``` 像素勇者Cocos/ ├── assets/ │ ├── resources/ │ │ ├── textures/ # 游戏贴图 │ │ │ ├── hero.png # 草莓勇士精灵图 1152×576,6列×3行,单帧 192×192 │ │ │ ├── hero_mage.png # 镇魂法师精灵图(与 hero.png 同布局,tools/gen_mage_art.py 生成) │ │ │ ├── portrait.png # 草莓勇士头像 1024×1024 │ │ │ ├── portrait_mage.png # 镇魂法师头像 1024×1024 │ │ │ ├── hero_peak.png # 山岳霸主精灵图(同 1152×576,tools/gen_peak_art.py 生成) │ │ │ ├── portrait_peak.png # 山岳霸主头像 1024×1024(AI 生成 + gen_peak_portrait.py 处理) │ │ │ │ # 已有 .meta 并随项目提交:正式构建后进 resources(零额外请求); │ │ │ │ # 热注入包里没有 → 才会退回 /textures/.png 直出 │ │ │ ├── coin.png # 金币图标 200×200 │ │ │ ├── friends.png # 好友图标 1254×1254 │ │ │ ├── dungeon_bg.png # 角色管理地牢背景 1024×2048 │ │ │ ├── wood_bg.png # 角色管理木板纹理 1024×2621 │ │ │ └── menu/ │ │ │ └── login-bg.jpg # 主页面月夜沙漠背景 1080×1920 │ │ └── fonts/ │ │ └── ZLabsPixel.ttf # 像素字体(**已子集化**:ASCII + GB2312 全集 + 源码用字 = 7556 字,3.19MB→1.43MB) │ └── scripts/ │ ├── Game.ts # 主组件:生命周期 + 共享状态 │ ├── Player.ts # 主角移动与动画 │ └── game/ # 按职责拆分的功能模块 │ ├── types.ts # 共享结构类型 │ ├── config.ts # 战斗数值配置表(波次 / 缩放 / 怪表 / 属性池 / 技能表) │ ├── net.ts # 后端 API / 身份参数 / 时间格式化 │ ├── px.ts # 像素位图与绘制 │ ├── ui.ts # 通用控件工厂(按钮/单选/滑块/弹窗) │ ├── input.ts # 文本输入框与剪贴板 │ ├── hud.ts # 血条 / 经验条 / 波数 │ ├── battle.ts # 战斗循环(刷怪/伤害/升级/结算) │ ├── menu.ts # 主菜单与头像名牌 │ ├── settings.ts # 设置面板 │ ├── friends.ts # 好友列表 / 搜索 / 申请 │ ├── chat.ts # 聊天窗口 │ ├── rank.ts # 排行榜弹窗(好友 / 全服) │ ├── redeem.ts # 兑换码与反馈面板 │ ├── sfx.ts # 音效 / BGM(Web Audio:懒加载、音量、静默降级) │ └── profile.ts # 改名与角色管理 ├── server.py # 游戏后端:MySQL + 构建产物托管(8200) ├── schema.sql # 数据库表结构(含 admins / admin_logs) ├── admin/ # 网页管理后台(独立 FastAPI 服务,8300) │ ├── main.py # 应用入口:启动时自动建表 + 建默认超管 │ ├── db.py # 数据库访问层(共用 roguelike 库) │ ├── auth.py # 登录令牌(JWT)+ 密码哈希(PBKDF2)+ 角色权限 + 审计 │ ├── routers/ # 按模块拆分的路由 │ │ ├── auth_routes.py # 登录 / 当前账号 / 改密 │ │ ├── dashboard.py # 仪表盘统计 │ │ ├── users.py # 用户管理(金币 / 昵称 / 冷却 / 封禁) │ │ ├── chars.py # 角色数值 + 给玩家解锁 / 设登场 │ │ ├── gameplay.py # 游戏局内管理(道具掉落概率 / 启用开关) │ │ ├── redeem.py # 兑换码生成 / 停用 / 启用 / 删除 / 流水 │ │ ├── feedback.py # 问题反馈状态流转 │ │ ├── messages.py # 消息审计(游戏端不支持玩家撤回,故无撤回) │ │ ├── ranks.py # 排行榜 │ │ ├── words.py # 屏蔽词增删改查(聊天打码用) │ │ └── admins.py # 管理员账号 + 审计日志(仅 super) │ ├── static/ # 前端单页(无框架:index.html + app.js + style.css) │ └── requirements.txt # 后台依赖 ├── _inject.js # TS 热注入脚本(改完代码免重新构建) ├── logkit.py # 日志模块:接管 print → logs/(5MB × 5 份自动轮转) ├── media/audio/ # BGM 与音效(走 /audio/ 路由 + Web Audio 播放) │ # ⚠️ 故意放在 assets 外:放进去会被打进小游戏包 +2.5MB, │ # 而小游戏没有 Web Audio 根本播不了(见「包体积」一节) │ ├── bgm.mp3 # BGM「Chip Bit Danger」(OpenGameArt,CC0) │ ├── attack/attack_staff/hurt/levelup/click/explosion.mp3(SFXMint,CC0) │ └── pickup.wav # 拾取经验:MC 风格双音 plink,tools/gen-pickup-sfx.py 合成(原创) ├── docs/screenshots/ # README 截图:游戏端 540×960 + admin/ 后台 1250×660(tools/gen-*-shots.js 生成) ├── tools/ # 辅助脚本(本地验证用,不参与构建) │ ├── typecheck.js # 类型检查(只看「会炸的」):专抓「块内 const 跨块引用」这类 │ │ # 构建能过、运行时 ReferenceError 的错误(已滤掉动态挂载的噪音) │ ├── gen-game-shots.js # 生成 README 截图:无头浏览器按固定流程拍 12 张页面图到 docs/screenshots/ │ ├── gen-admin-shots.js # 生成后台截图:登录后逐个模块拍 10 张到 docs/screenshots/admin/ │ ├── smoke-test.js # 面板冒烟测试:逐个开/关面板,收集异常并截图 │ ├── chat-test.js # 聊天端到端:双身份验证多行输入 / Ctrl+Enter / 实时推送 / 气泡自适应 │ ├── ws-smoke.js # 只验 WebSocket 通道(握手 + 广播),不经浏览器,最快 │ ├── admin-smoke.js # 管理后台端到端:登录 / 越权拒绝 / 写操作 / 封禁联动游戏后端 │ ├── admin-shot.js # 管理后台页面探针:登录 + 逐个模块截图 + 收集 JS 异常 │ ├── probe-censor.js # 屏蔽词端到端:加词 → 发消息 → 断言气泡里出现 * │ ├── probe-sale.js # 售卖方式端到端:暂停售卖/活动获取时游戏按钮文案与小字 │ ├── probe-ban.js # 封禁端到端:封禁后游戏盖全屏提示层 + 主菜单点不动(真实鼠标点击对照) │ ├── probe-items.js # 升级奖励与连升弹窗 / 局内道具掉落·效果·图标 / 法阵像素旋转(端到端断言 + 截图) │ ├── probe-death.js # 受击闪红·击退·飘字·红晕 / 死亡碎裂 / 结算后摇杆彻底失效(端到端断言 + 截图) │ ├── probe-sqli.js # 管理后台 SQL 注入渗透:登录·鉴权·列表·路径·分页·写接口·错误泄漏(83 项) │ ├── probe-auth.js # 改密后旧令牌立即失效 / 新令牌不被误杀 / 停用仍立刻生效(20 项) │ ├── probe-audio.js # 音频系统:八类音频 fetch+解码 / BGM 循环 / 音量钳制 / 增益分层 / 拾取随机音高(19 项) │ ├── probe-mage.js # 角色管理/战斗内截图探针 │ ├── probe-peak.js # 山岳霸主全链路:角色数据 / 换装帧表 / 长棍横扫范围 / 旋转棍特效生命周期(轴心=手心)/ 立绘(21 项) │ ├── probe-screen.js # 屏幕适配:任意「宽×高×DPR」下量 view / 相机正交高度 / Label 字号与字体名并截图(排查溢出、字体异常) │ ├── probe-joystick.js # 摇杆对齐:模拟触摸量「底座世界坐标 → UI 坐标」与手指的偏差(跟随须 dx=dy=0、固定须离底 200) │ ├── probe-walk.js # 走路动画:三个角色逐个换装后检查 walk 帧表(6 帧 / y=192)与帧是否真的推进 │ ├── probe-waves.js # 波次节奏:波时长曲线 / 波间喘息 / 精英波不清场 / BOSS 清场 / 波次文案(25 项) │ ├── probe-gold.js # 金币经济:结算公式 / 已通过波数口径(通关计满 40 波)/ 不写库取真实结算文本(11 项) │ ├── probe-confirm.js # 二次确认弹窗:两套弹窗各归各位 + 好友列表垃圾桶真实入口(14 项) │ ├── probe-joystick-fixed.js # 固定摇杆(真实触摸):点底座中心不移动、点上方向上、点别处不驱动 │ ├── probe-input-ui.js # 弹窗不穿透 / 攻击不减速 / 急速不丢伤害 / 宽窗口不裁 UI(14 项) │ ├── gen-pickup-sfx.py # 拾取音效合成器(MC 风格木质双音 plink,纯 stdlib,可反复重生成) │ ├── gen_redeem.py # 兑换码生成器(命令行版;网页后台也能生成) │ ├── gen_mage_art.py # 镇魂法师像素贴图生成器(精灵图,可反复运行重生成) │ ├── gen_peak_art.py # 山岳霸主像素贴图生成器(壮汉 + 长棍,同 1152×576 帧表) │ ├── gen_portrait.py # 镇魂法师头像后处理(统一背景 + 去水印) │ └── gen_peak_portrait.py # 山岳霸主头像后处理(同上) ├── build/web-mobile/ # 构建产物(web-mobile 单页包,gitignore) ├── library/ # Cocos 资源缓存(gitignore,删了会触发编辑器全量重新导入) ├── temp/ profiles/ settings/ # Cocos 编辑器临时产物与工程配置(gitignore) └── README.md ``` --- ## 🔁 热注入 vs 正式构建(**不能混用**) `assets/scripts/game/*.ts` 现在**已有 `.meta`**(编辑器打开项目时生成,且已随仓库提交), 所以它们进入了 Cocos 的编译链 —— 也就是说「热注入」和「正式构建」是两条独立的路,各走各的: | | 热注入 `node _inject.js` | Cocos 构建 | |---|---|---| | 产物 | 覆盖 `build/web-mobile/assets/main/index.js` | 生成/覆盖整个 `build/` | | 编译 target | esbuild 现代 target(`[...set]` 原样保留) | **降级到 ES5**(`[...set]` → `[].concat(set)`) | | 用途 | 改完秒看效果,不重启编辑器 | 发布 / 小游戏 / 验收「真实产物」 | - **建议节奏**:改代码 → `node _inject.js` → 刷新秒看 → 反复调 → 定稿后 Cocos Build 一次。 - `_inject.js` 除了从 `Game.ts` 沿 import 链收集模块,还会**额外整块替换 `Player.ts`** (它不在 import 链里、由场景直接挂)。以前改 `Player.ts` 只能手工去 `index.js` 里改字符串 —— 变量一改错就是「构建能过、运行时炸」,现在和别的文件一样注入即可。 - ⚠️ **构建完不要再跑 `_inject.js`**:新构建产物里 `game/*.ts` 被编成**扁平模块名** (`chunks:///_virtual/battle.ts`),而 `main` 的依赖写的是 `./game/battle.ts`;注入后解析成 `_virtual/game/battle.ts` → 找不到 → 整包白屏(SystemJS #3)。真遇到就删掉 `build/web-mobile` 重新 Build。 --- ## ✅ 本地验证 **改完 TS 先跑类型检查**(不需要后端,几秒钟): ```bash node tools/typecheck.js # 输出「✓ 没有会炸的错误」才算过 ``` > ⚠️ Cocos 构建**只转译、不做类型检查**:像 `if (x) { const halfVH = ... }` 这样在块里声明、 > 块外引用,Build 一样成功,运行时才 `ReferenceError: halfVH is not defined`(整段逻辑失效)。 > 这个脚本专抓这类,已把 `Game.ts`(installXxx 动态挂 prototype)等 27 条结构性噪音滤掉。 改动后不必手动点一遍,`tools/` 下的脚本能自动跑(都需要先 `python server.py` 把后端起起来): ```bash cd <项目根> NODE_PATH=node_modules node tools/ws-smoke.js # 最快:只验 WebSocket 握手 + 广播,不经浏览器 NODE_PATH=node_modules node tools/smoke-test.js # 面板冒烟:逐个开/关面板,收集异常并截图 NODE_PATH=node_modules node tools/chat-test.js # 聊天端到端:双身份验多行输入 / Ctrl+Enter / 实时推送 ``` 管理后台(需先同时起 `python admin/main.py` 与 `python server.py`): ```bash NODE_PATH=node_modules node tools/admin-smoke.js # 登录 / 越权拒绝 / 写操作 / 超管保护 / 屏蔽词 / 封禁联动(自动清理测试数据) NODE_PATH=node_modules node tools/admin-shot.js # 无头浏览器走一遍登录 + 各模块,收集 JS 异常并截图 NODE_PATH=node_modules node tools/probe-censor.js # 屏蔽词端到端:加词 → 发消息 → 断言气泡里出现 * NODE_PATH=node_modules node tools/probe-sale.js # 售卖方式端到端:暂停售卖/活动获取时游戏按钮文案与小字 NODE_PATH=node_modules node tools/probe-ban.js # 封禁端到端:全屏提示层 + 主菜单冻结(跑完自动解封还原) NODE_PATH=node_modules node tools/probe-items.js # 升级自动奖励 / 道具掉落与三种效果 / 法阵像素旋转 NODE_PATH=node_modules node tools/probe-death.js # 受击反馈 / 死亡特效 / 结算后摇杆与按键彻底失效 NODE_PATH=node_modules node tools/probe-sqli.js # SQL 注入渗透测试(83 项:登录·鉴权·列表·路径·分页·写接口·错误泄漏) NODE_PATH=node_modules node tools/probe-auth.js # 改密后旧令牌立即失效(20 项,自动建/删临时管理员) NODE_PATH=node_modules node tools/probe-audio.js # 音频系统(19 项:加载解码 / BGM 循环 / 音量钳制 / 增益分层 / 拾取随机音高) NODE_PATH=node_modules node tools/probe-peak.js # 山岳霸主全链路(21 项:角色数据 / 换装帧表 / 长棍横扫 / 旋转棍特效(轴心=手心)/ 立绘) ``` 屏幕适配 / 字体(不需要后台,只需 `python server.py` 起着)——按不同「宽 × 高 × DPR」量一遍并截图: ```bash node tools/probe-screen.js 375 667 2 base # 9:16 基准 node tools/probe-screen.js 375 600 2 short # 矮窗口:查底部是否被切 node tools/probe-screen.js 375 812 2 tall # 刘海屏:查两侧是否被切 ``` 判断标准:`cams[].ortho` 必须等于 `visible.height / 2`(否则界面会被横向裁掉); `labels[].fontName` 必须是 `ZLabsPixel`(出现别的名字说明像素字体没套上)。 摇杆对齐(同样只需后端起起来): ```bash node tools/probe-joystick.js 375 667 2 base # 9:16 node tools/probe-joystick.js 375 812 2 tall # 20:9:跟随摇杆 dx/dy 必须为 0,固定摇杆离底必须 200 ``` 判断标准:跟随摇杆的 `dx`/`dy` 必须是 **0**(底座以手指为中心);固定摇杆的 `离底` 恒为 **200**。 UI 原点在屏幕左下角、尺寸 = **可视区**,别拿设计分辨率 `960/2` 当半高。 ```bash node tools/probe-walk.js # 三个角色换装后 walk 帧表 + 行走时帧是否推进 node tools/probe-input-ui.js # 弹窗穿透 / 攻击不减速 / 急速不丢刀 / 宽窗口不裁 UI ``` `probe-input-ui.js` 的四个断言(都是踩过的真 bug): | 断言 | 判据 | |---|---| | 弹窗不穿透 | 开始游戏弹窗开着时点「角色管理」位置 → 不打开角色管理;且弹窗不能被误关 | | 攻击不减速 | 攻击中往右走 1 秒,位移 ≈ 180px(120 就是还有 ×0.35 减速) | | 急速不丢刀 | 间隔压到 0.1667s 时,累计伤害 > 0 且 `spawnShotFx` 次数 > 0 | | 宽窗口不裁 UI | 1440×900 下 `getVisibleSize()` = 540×960,所有 Label 的世界坐标都落在画布内 | 判断标准:每个角色的 `frames.walk` 都要有 6 帧、rect 落在 `y=192`;走起来 `不同帧数 ≥ 3`(卡在 1 帧就是动画没推进)。 ```bash node tools/probe-waves.js # 波次节奏:波时长曲线 / 喘息 / 精英波不清场 / BOSS 清场 / 波次文案 ``` 判断标准:第 10 波之后时长增量减半(第 20 波 36s、第 39 波 44.6s)、喘息 0.8s; 过精英波时 `enemies.length` **不变**(不消怪),过 BOSS 波才归零;文案只有「第 N 波」/「·BOSS·」,无尽模式为「无尽·第 N 波」。 结算与二次确认弹窗(同样只需后端起起来): ```bash node tools/probe-gold.js # 金币:11 项(公式 / 已通过波数口径 / coins 增量 / best_wave 上报值) node tools/probe-confirm.js # 确认弹窗:14 项(两套实现各归各位 + 好友垃圾桶真实入口) ``` 金币口径 = `floor((已通过波数 × 25 + 击败数 × 0.4) / 2)`,两个数字记牢: 显示「第 2 波」时退出只算 1 波 = **12 金币**;**通关时最后那波算满** —— 通过 40 关 + 400 击杀 = **580** (旧的「一律减 1」只给 567)。`best_wave` 上报的也是已通过波数(显示第 61 波 → 上报 60,通关 40 关 → 40)。 探针把 `apiPost` 换成空实现,只读结算面板文本、不写库。 `probe-confirm.js` 断言 `showConfirm` 是四参版(标题可传)、`showBuyConfirm` 是两参版(标题写死「确认购买」), 并走真实入口点好友行尾的垃圾桶,确认弹出的是「删除好友 / 确定删除」;点「确定」回调执行一次、弹窗随即销毁。 截图落在 `tools/shots/`(已被 gitignore,每次运行自动重建)。 `smoke-test.js` / `chat-test.js` 走无头 Edge + CDP,需要本机装有 Edge; 也可用 `BROWSER_PATH` 环境变量指向别的 Chromium 内核浏览器。 --- ## 🔌 后端接口一览 `server.py`(8200)同时提供 API 与构建产物托管。**身份一律由请求自带的 `?uid=` 决定** (uid 唯一且稳定,昵称可改、可重名,不作为身份)。绝大多数接口是 `POST /api/xxx`; 只读接口 `/api/state`、`/api/chat_history`、`/api/whoami`、`/api/rank` 也支持 GET,可直接在浏览器里调试: | 分类 | 接口 | |---|---| | 状态与存档 | `/api/state`(含 `chars` / `blocked_words` / `items` 局内道具掉落配置)、`/api/save`、`/api/whoami`、`/api/set_name`、`/api/switch` | | 角色 | `/api/buy_char`、`/api/deploy` | | 好友 | `/api/friend_add`、`/api/friend_search`、`/api/friend_find`、`/api/friend_handle`、`/api/friend_remove` | | 聊天 | `/api/chat_send`、`/api/chat_history`、`/api/chat_read` | | 排行榜 | `/api/rank`(`scope=friends` / `global`,GET 调试:`/api/rank?uid=10000001&scope=global`) | | 兑换与反馈 | `/api/redeem`、`/api/feedback` | | 屏蔽词 | `/api/words`(GET/POST 均返回 `{ok, words:[...]}`,供前端对聊天打码) | | 实时 | `/ws/chat?uid=`(WebSocket,消息落库即推送) | 兑换码生成(写库): ```bash python tools/gen_redeem.py --coins 500 [--char strawberry] [--max-uses N] [--note x] [--list] ``` ### 管理后台接口 `admin/main.py`(8300)所有接口都在 `/api/admin/*` 下,除 `/login` 外都需要 `Authorization: Bearer `(登录返回的 JWT)。完整交互文档见 http://127.0.0.1:8300/docs 。 | 分类 | 接口 | |---|---| | 鉴权 | `/api/admin/login`、`/api/admin/me`、`/api/admin/change_pwd` | | 仪表盘 | `/api/admin/stats` | | 用户 | `/api/admin/users`、`/api/admin/users/{id}`、`/{id}/coins`、`/{id}/name`、`/{id}/cooldown_reset`、`/{id}/ban`、`/{id}/clear_wave` | | 角色 | `/api/admin/chars`、`/api/admin/chars/{id}`、`/api/admin/users/{uid}/chars/{cid}/unlock`、`/deploy` | | 游戏局内 | `/api/admin/gameplay`、`/api/admin/gameplay/{item_id}`(改掉落概率 / 启用开关) | | 兑换码 | `/api/admin/redeem`、`/{code}/logs`、`/{code}/max_uses`、`/{code}/disable`、`/{code}/enable` | | 反馈 | `/api/admin/feedback`、`/api/admin/feedback/{id}/status` | | 消息 | `/api/admin/messages`、`/api/admin/conversations`(游戏端无撤回,故无撤回接口) | | 屏蔽词 | `/api/admin/words`、`/api/admin/words/{id}`(POST 改 / DELETE 删) | | 排行 | `/api/admin/ranks`(前端「功能」列可清除波数 / 封禁) | | 管理员(仅 super) | `/api/admin/admins`、`/{id}/role`、`/{id}/enabled`、`/{id}/pwd`、`/api/admin/logs` | --- ## 🎮 操作 - 鼠标点击 / 触摸操作 - 左下角摇杆控制角色移动 - 设置中切换摇杆模式(固定 / 跟随)