本文正在参加 #全新 TRAE 实战体验
赛道:全链路开发实战
这是一篇 "AI 当主力开发" 的实战复盘:整个项目从 0 到 1 的每一行代码都是 TRAE 写的 (包括最初的项目搭建)。
我的角色是产品经理 + 验收者:提想法、定验收标准、试玩给反馈、截屏记录
TRAE 的角色是全栈工程师:技术选型、目录设计、编码、调试、重构、搭建测试,一条龙跑完
项目先后经历两代架构,分别落在两个分支:
main分支是 Taro + React + TypeScript 微信小程序版feature/minigame分支是 重构后的原生微信小游戏版
两代累计 5 万行级代码变更,全程在 TRAE 里完成。
第一代架构(main 分支):TRAE 从空目录搭起 Taro 小程序
项目第一步就是让 TRAE 直接起项目,我给 TRAE 的原始需求只有一句话级别的想法:
帮我做一个微信小游戏,其有多种不同的小游戏,现在暂时做两个:
- 做一个二维码迷宫小游戏,二维码就是迷宫,并且扫二维码可以看到正确的路线
- 做一个平衡球小游戏,靠手机的重力模块,通过手机倾斜、转动,让小球从一边的起点,滚到另一边的终点,中间有障碍和洞,掉下洞直接失败
它做了完整的技术选型决策:目标是"低成本快速验证玩法",选 Taro(React + TypeScript + SCSS)开发,并主动说明了理由 ------ 组件化开发 UI 页面快,TS 保证游戏逻辑可维护。
随后一次性产出了整个工程骨架:
js
src/
├── pages/ # index / qrmaze / balance / records / mine
├── utils/ # maze.ts / qr.ts / storage.ts / format.ts / haptic.ts
├── data/ # levels.ts 确定性关卡工厂
└── types/ # game.ts 全局类型
如下图:

重点
TRAE 直接给我搭建项目结构,并引入需要的插件,真的强大!
TRAE 基本上可以理解我说的全部需求,并能生成基本不需要人工审核或者改动的代码,我很多情况都是直接看界面就行,说出界面上的问题,根本没有看代码实现!
过程
后续就是我不断的跟TRAE沟通,去达到我想要的效果!
如图:






到后面菜鸟又让其加了一个游戏,并对老的游戏进行优化:







重点
遇见界面上的问题,也可以直接截图发给TRAE,甚至可以框起来告诉TRAE,TRAE也可以完全理解我描述的问题!
如图:


最终产物
反正通过TRAE优化了很多次,这里就不列举了,最终产出包含三个玩法:
- 二维码迷宫:迷宫的墙本身就是一张真实可扫的二维码 ------ 用程序扫码后,会出现最优路线的线,直接照着走就行
- 火力全开:竖版射击,带小队机制、算式门二选一、精英怪、BOSS 战特效
- 重力平衡球:倾斜手机控制小球避开深渊洞(漩涡)滚进传送门
外加首页、战绩统计、我的(tabBar)三个页面。



不吹不黑
不吹也不黑,功能实现、完成度、代码质量TRAE完成得是真的不错,但是没有画师在场,这个游戏简陋也确实是简陋(其实可以用TRAE Work,即现在的 Agent 模式辅助生成插画,双管齐下,这里菜鸟是企业用户搞不了,咋普通用户还比企业用户先用上了 (T ^ T))
二、小程序发不成功,发现小游戏只能小游戏类目:TRAE 完成 Taro → 原生小游戏迁移
玩法全部跑通、准备发布时,我才知道踩中了一个平台规则的坑:项目本身是小游戏,但微信不允许用"小程序"类目发布游戏。想发出去只有一条路------把账号类目切换为"小游戏",并更换新的小游戏 AppID。
类目一换,麻烦立刻出现:Taro 构建的小程序代码在小游戏运行时里根本跑不起来。
小程序和小游戏是两套完全不同的运行时 ------ 小程序要 app.js + 页面路由 + WXML 组件体系,而小游戏只认 game.js 这一个入口,没有页面、没有组件,一切 UI 都要在 Canvas 上自己画。
代码一行没改,但就是运行起不来。
我让 TRAE 评估两条路:是继续在 Taro 上找小游戏适配方案,还是整体迁移原生小游戏?
TRAE的结论很直接:Taro 的小游戏支持对这类 "单 Canvas 逐帧循环" 的项目帮助有限,组件模型帮不上忙反而多一层桥接;而这个项目三个玩法全靠 Canvas,原生小游戏 + 手绘 UI 才是正路。
重构决策就这么来的 ------ 不是性能优化,是平台规则导致的重构。
于是整包重构:141 个文件、+24332 / -33181 行,等于把整个项目按小游戏运行时重写了一遍。TRAE 在 Builder 模式下产出了新架构骨架,再逐场景"翻译"旧逻辑:
js
minigame/
├── game.js / game.json
├── jsQR.js # 出口扫码验证
├── js/
│ ├── games/shooter/ # engine / timeline / render / layout / constants
│ ├── scenes/ # home / qrmaze / shooter / balance / records / mine
│ ├── qr.js # 自实现 QR 编码器(v1~8)
│ ├── maze.js # 迷宫生成(524 行)
│ ├── levels.js # 确定性关卡工厂
│ └── ui.js / scroll.js / tabbar.js / haptic.js / icons.js
小游戏架构:

小程序架构:

重点
整个重构非常的丝滑,且完全是等比移植,只有样式有点细节调整,游戏体验基本完全一致!
三、TRAE 在全链路各环节的真实表现
1. 需求 → 方案:会反问、会给依据
"做一个小游戏合集"这种模糊需求,TRAE 没有直接开写,而是先确认玩法范围、离线要求和验收方式,再给技术方案。
选型理由能落到"为什么是这个而不是那个",而不是无脑堆新技术。
2. 编码:跨文件的结构性改动是强项
迁移阶段最典型 ------ 一次对话让 TRAE 把 226 行的 TypeScript 射击时间线(timeline.ts)翻译成 CommonJS 的 timeline.js,波次编排和"减益保底"语义完整保留。
这种跨 20+ 文件的结构性工程,手写至少按天算,TRAE 是按分钟算。
3. 调试:给现象,拿根因
两个印象最深的修复,都是我描述现象、TRAE 定位根因:
-
边缘弹幕漏怪:怪物从屏幕边缘死角漏进防线,TRAE 定位到弹幕生成的边界采样区间缺陷
-
加固路线时墙格被误删 (main 分支
eff053c):迷宫"加固"功能破坏已生成的墙,根因是回填顺序与去重逻辑冲突。这类跨函数状态问题,TRAE 通读调用链的效率远高于我加断点。
4. 测试:它自己搭了验证体系
这是我认为 TRAE 最出彩的部分。
因为我做的时候提及了关卡是确定性生成的、逻辑层不依赖微信运行时,TRAE 顺手搭了一套 Node 冒烟测试 (scripts/minigame-smoke.cjs):注入一个 wx stub,就能在 Node 里跑关卡工厂、QR 推导和射击引擎的回归验证。

四、两个最有含金量的技术点(全是 TRAE 产出)
1. 手写 QR 编码器 + jsQR 回环验证
二维码迷宫的墙必须是一张真实可扫 的二维码,而小游戏环境没有现成的 Canvas 二维码库。TRAE 自实现了一个 QR Model 2 编码器(qr.js):Byte 模式 UTF-8、纠错等级 L、支持版本 1 ~ 8(21 ~ 49 模块),v6+ 按规范做 2 个 RS 块交织,底层是 GF(256) 域上基于本原多项式 0x11D 的 RS 纠错。
验证方式很硬核 ------ 用 jsQR 做解码回环:把编码器输出的 0/1 矩阵栅格化成 RGBA,喂给 jsQR 解码,解出的字符串必须等于输入的 8 位关卡码。
2. 确定性关卡工厂 + Node 冒烟测试
所有关卡由 mulberry32(seed) 从关号推导:第 N 关的 QR 版本、迷宫策略、最低步数(100+)、射击波次全部由 N 决定。配合上面的 wx stub:
bash
node scripts/minigame-smoke.cjs
# ✓ 关卡工厂 / QR 迷宫推导 + jsQR 扫码回环 / 射击时间线与引擎
这套"逻辑层纯函数化 + wx stub + Node 冒烟"的模式,给 TRAE 的每次改动提供了可执行的验收标准 ------ 它改完,我试玩几关就行。
我只验手感,机器校验逻辑!
关卡生成逻辑
js
// ============================================
// 关卡数据
// ============================================
/** 二维码迷宫关卡配置 */
export interface QRMazeLevel {
id: number;
name: string;
/** QR 版本(决定迷宫尺寸:v4=33格, v5=37格, v6=41格, v7=45格, v8=49格) */
version: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
/** 二维码内容(可被真实扫码识别,含盐值后缀) */
content: string;
/** 路线策略:short 简单 / long 困难 */
strategy: "short" | "long";
/** 最佳路线步数下限(确定性搜索尽量保证达标) */
minSteps: number;
}
/** 关卡主题名(前 10 关),之后为「无尽迷阵 N」 */
const MAZE_NAME_POOL = [
"初识密文",
"回纹密林",
"双重迷阵",
"深渊方阵",
"时序回廊",
"校准星图",
"掩码幻境",
"光路折返",
"镜像解码",
"无尽回响"
];
/** 确定性随机数(mulberry32):同一关卡号永远生成同一套密语 */
export function mulberry32(seed: number): () => number {
let a = seed >>> 0;
return () => {
a = (a + 0x6d2b79f5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
const CODE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
/**
* 无限关卡工厂:第 N 关完全由 N 确定性生成(可重玩、可离线)
* - 难度阶梯:迷宫必须是真实可扫的 QR 矩阵,尺寸只能取 21+4×版本(30×30/35×35 等非法尺寸无法扫码),
* 故按 v5(37×37) → v6(41×41) → v7(45×45) → v8(49×49) 每关 +4 递增,v8 封顶;
* 全关卡 long 策略(弯绕长廊),并要求最佳路线 ≥ minSteps(100 步)------确定性搜索保证达标
* - 密语随机:QW{N}#XXXXXXXX(填充区可自由塑形,扫码恒为原文)
*/
export function makeQRMazeLevel(n: number): QRMazeLevel {
const version = Math.min(4 + n, 8) as QRMazeLevel["version"];
const strategy: "short" | "long" = "long";
const minSteps = 100;
const codeLen = 8;
const rng = mulberry32(n * 7919 + 13);
let code = "";
for (let i = 0; i < codeLen; i++) {
code += CODE_CHARS[Math.floor(rng() * CODE_CHARS.length)];
}
const name = n <= MAZE_NAME_POOL.length ? MAZE_NAME_POOL[n - 1] : `无尽迷阵 ${n}`;
return { id: n, name, version, content: `QW${n}#${code}`, strategy, minSteps };
}
/** 平衡球障碍墙(左上角 + 宽高,单位坐标 0~100 × 0~137) */
export interface BalanceWall {
x: number;
y: number;
w: number;
h: number;
}
/** 平衡球深渊洞 */
export interface BalanceHole {
x: number;
y: number;
r: number;
}
/** 平衡球关卡配置 */
export interface BalanceLevel {
id: number;
name: string;
start: { x: number; y: number };
goal: { x: number; y: number; r: number };
holes: BalanceHole[];
walls: BalanceWall[];
/** 洞口吸引强度倍率:越往后越难(默认 1) */
suction?: number;
}
/** 逻辑坐标系:宽 100,高 137(与画布 686×940rpx 等比) */
export const BALANCE_FIELD = { width: 100, height: 137 };
export const BALANCE_BALL_R = 3.2;
export const BALANCE_LEVELS: BalanceLevel[] = [
{
id: 1,
name: "热身滚道",
start: { x: 10, y: 10 },
goal: { x: 90, y: 126, r: 6 },
holes: [
{ x: 50, y: 66, r: 5 },
{ x: 80, y: 36, r: 4.5 },
{ x: 60, y: 3.2, r: 4.5 }, // 贴顶边洞:打断沿顶边滑行的捷径
{ x: 96.8, y: 80, r: 4.5 }, // 贴右边洞:打断沿右边滑行的捷径
{ x: 48, y: 133.8, r: 4.5 } // 贴底边洞:打断沿左边+底边绕行的捷径
],
walls: []
},
{
id: 2,
name: "回旋走廊",
start: { x: 10, y: 10 },
goal: { x: 91, y: 126, r: 5.5 },
holes: [
{ x: 44, y: 112, r: 4.5 },
{ x: 74, y: 24, r: 5 },
{ x: 86, y: 84, r: 4.5 },
{ x: 96.8, y: 64, r: 4.2 } // 贴右边洞:右边走廊的封锁点
],
walls: [
{ x: 26, y: 0, w: 4, h: 88 },
{ x: 58, y: 49, w: 4, h: 88 }
]
},
{
id: 3,
name: "针尖方阵",
start: { x: 9, y: 9 },
goal: { x: 91, y: 126, r: 5.5 },
holes: [
{ x: 34, y: 34, r: 4 },
{ x: 66, y: 34, r: 4 },
{ x: 34, y: 103, r: 4 },
{ x: 66, y: 103, r: 4 },
{ x: 50, y: 68, r: 5.5 },
{ x: 96.8, y: 72, r: 4.2 }, // 贴右边洞
{ x: 70, y: 133.8, r: 4.2 } // 贴底边洞
],
walls: [
{ x: 18, y: 62, w: 24, h: 3 },
{ x: 58, y: 62, w: 24, h: 3 }
]
},
{
id: 4,
name: "深渊回廊",
start: { x: 9, y: 9 },
goal: { x: 91, y: 129, r: 5.5 },
holes: [
{ x: 86, y: 28, r: 5 },
{ x: 14, y: 70, r: 5 },
{ x: 50, y: 123, r: 5 },
{ x: 86, y: 108, r: 4.5 },
{ x: 58, y: 70, r: 4.5 },
{ x: 24, y: 18, r: 4 }
],
walls: [
{ x: 0, y: 44, w: 72, h: 3 },
{ x: 28, y: 97, w: 72, h: 3 }
]
}
];
/** 平衡球关卡主题名(第 5~16 关),之后为「无尽滚道 N」 */
const BALANCE_NAME_POOL = [
"碎星浅滩",
"引力回廊",
"迷雾缓坡",
"旋涡窄桥",
"微光裂谷",
"静默深渊",
"镜面斜坡",
"薄冰长阶",
"乱流航段",
"无垠坡道",
"坠星海峡",
"永夜回环"
];
/** 两点距离(Math.sqrt 为 IEEE-754 精确定义,跨设备结果一致) */
function balanceDist(ax: number, ay: number, bx: number, by: number): number {
const dx = ax - bx;
const dy = ay - by;
return Math.sqrt(dx * dx + dy * dy);
}
/** 点到矩形(墙)的最近距离 */
function pointRectDist(px: number, py: number, w: BalanceWall): number {
const cx = Math.min(Math.max(px, w.x), w.x + w.w);
const cy = Math.min(Math.max(py, w.y), w.y + w.h);
return balanceDist(px, py, cx, cy);
}
/** 矩形(墙)与矩形(墙)的最近距离 */
function rectRectDist(a: BalanceWall, b: BalanceWall): number {
const dx = Math.max(b.x - (a.x + a.w), a.x - (b.x + b.w), 0);
const dy = Math.max(b.y - (a.y + a.h), a.y - (b.y + b.h), 0);
return Math.sqrt(dx * dx + dy * dy);
}
/**
* 无限平衡球关卡工厂:第 N 关完全由 N 确定性生成
* - 整数种子 + 可复现随机数,同一关号在任何手机上布局完全一致(可离线重玩)
* - 前 4 关为手工调校的教学关,第 5 关起程序生成
* - 难度曲线:深渊洞 5→10 个、障碍墙 2→6 条、传送门半径 5.5→4.2
* - 可解性保证:漂浮墙距边框/彼此 ≥9(球径 6.4,必有绕行通道)、
* 洞互不粘连(间距 ≥8)、洞与墙不重叠、洞不压起终点
* - 防贴边滑行:边缘鳍墙(从边框伸入的短墙)截断边框通道 +
* 贴边洞(半嵌在边框上的洞),让沿边一路滚到终点不再可行
*/
export function makeBalanceLevel(n: number): BalanceLevel {
if (n <= BALANCE_LEVELS.length) return BALANCE_LEVELS[n - 1];
const d = n - BALANCE_LEVELS.length; // 生成难度档(第 5 关 = 1)
const rng = mulberry32(n * 104729 + 7);
const r1 = (lo: number, hi: number) => Math.round((lo + rng() * (hi - lo)) * 10) / 10;
const holeCount = Math.min(4 + Math.ceil(d / 2), 12);
const wallCount = Math.min(2 + Math.floor(d / 2), 7);
const finCount = Math.min(2 + Math.floor(d / 4), 4);
const edgeHoleCount = Math.min(1 + Math.floor(d / 3), 4);
const goalR = Math.max(3.8, 5.5 - d * 0.12);
// 洞口吸引强度:难度档每 +1 增强 8%,封顶 1.8 倍(后期洞的"引力"明显更凶)
const suction = 1 + Math.min(0.8, d * 0.08);
const start = { x: r1(8, 14), y: r1(8, 14) };
const goal = { x: r1(85, 91), y: r1(122, 129), r: goalR };
// 墙体集合:主鳍 → 漂浮墙 → 随机鳍,后放者对先放者保持净空
const walls: BalanceWall[] = [];
// 边缘鳍墙:从边框伸入场内的短墙,截断"贴边滑行"的捷径。
// 顶边与右边的主鳍最先落位(此时场地为空,扫描必命中,主贴边路线必被封锁):
// 沿边取候选点、按种子随机取一;漂浮墙与随机鳍随后避让;洞最后放。
const scanFin = (edge: number): BalanceWall | null => {
const len = r1(11, 17 + Math.min(5, d * 0.8));
const N = 34;
const cands: BalanceWall[] = [];
for (let s = 0; s <= N; s++) {
const t =
edge === 0 || edge === 2
? 16 + ((BALANCE_FIELD.width - 32) * s) / N
: 16 + ((100 - 16) * s) / N;
const fin: BalanceWall =
edge === 0
? { x: t, y: 0, w: 3, h: len } // 顶边下垂
: edge === 1
? { x: BALANCE_FIELD.width - len, y: t, w: len, h: 3 } // 右边伸入
: edge === 2
? { x: t, y: BALANCE_FIELD.height - 3, w: 3, h: len } // 底边竖起
: { x: 0, y: t, w: len, h: 3 }; // 左边伸入
if (pointRectDist(start.x, start.y, fin) < 13) continue;
if (pointRectDist(goal.x, goal.y, fin) < goal.r + 10) continue;
cands.push(fin);
}
const clear = (f: BalanceWall) =>
walls.reduce((m, o) => Math.min(m, rectRectDist(f, o)), Infinity);
const pool = cands.filter((f) => clear(f) >= 10);
const fallback = pool.length ? pool : cands.filter((f) => clear(f) >= 9);
return fallback.length ? fallback[Math.floor(rng() * fallback.length)] : null;
};
const finTop = scanFin(0);
if (finTop) walls.push(finTop);
const finRight = scanFin(1);
if (finRight) walls.push(finRight);
// 再放漂浮横/竖杆:距边框与彼此 ≥9,保证必有绕行通道
for (let i = 0; i < wallCount; i++) {
const len = r1(22, Math.min(38, 26 + d * 1.2));
for (let tries = 0; tries < 50; tries++) {
const horiz = rng() < 0.5;
const wall: BalanceWall = horiz
? {
x: r1(9, BALANCE_FIELD.width - 9 - len),
y: r1(9, BALANCE_FIELD.height - 30),
w: len,
h: 3
}
: {
x: r1(9, BALANCE_FIELD.width - 12),
y: r1(9, BALANCE_FIELD.height - 9 - len),
w: 3,
h: len
};
if (pointRectDist(start.x, start.y, wall) < 12) continue;
if (pointRectDist(goal.x, goal.y, wall) < goal.r + 9) continue;
if (walls.some((o) => rectRectDist(wall, o) < 9)) continue;
walls.push(wall);
break;
}
}
// 其余随机鳍(净空不足时允许放弃,不硬塞)
for (let i = 2; i < finCount; i++) {
for (let tries = 0; tries < 40; tries++) {
const edge = Math.floor(rng() * 4);
const len = r1(11, 17 + Math.min(5, d * 0.8));
const fin: BalanceWall =
edge === 0
? { x: r1(16, BALANCE_FIELD.width - 16), y: 0, w: 3, h: len }
: edge === 1
? { x: BALANCE_FIELD.width - len, y: r1(16, 100), w: len, h: 3 }
: edge === 2
? { x: r1(16, BALANCE_FIELD.width - 16), y: BALANCE_FIELD.height - 3, w: 3, h: len }
: { x: 0, y: r1(16, 100), w: len, h: 3 };
if (pointRectDist(start.x, start.y, fin) < 13) continue;
if (pointRectDist(goal.x, goal.y, fin) < goal.r + 10) continue;
if (walls.some((o) => rectRectDist(fin, o) < 10)) continue;
walls.push(fin);
break;
}
}
// 再放洞:前若干个为"贴边洞"(半嵌在边框上,贴边走位即有坠洞风险),
// 其余在场地内部生成;均远离起终点、不压墙、互不粘连
const holes: BalanceHole[] = [];
for (let i = 0; i < holeCount; i++) {
const asEdge = i < edgeHoleCount;
for (let tries = 0; tries < 60; tries++) {
const r = r1(4, Math.min(6, 4.4 + d * 0.14));
let hole: BalanceHole;
if (asEdge) {
// 洞心距边框 1.6~3.4:半个洞嵌在边框上;避开四角起终点区
const edge = Math.floor(rng() * 4);
const off = r1(1.6, 3.4);
if (edge === 0) hole = { x: r1(20, BALANCE_FIELD.width - 20), y: off, r };
else if (edge === 1) hole = { x: BALANCE_FIELD.width - off, y: r1(20, 112), r };
else if (edge === 2)
hole = { x: r1(20, BALANCE_FIELD.width - 20), y: BALANCE_FIELD.height - off, r };
else hole = { x: off, y: r1(20, 112), r };
} else {
hole = {
x: r1(7 + r, BALANCE_FIELD.width - 7 - r),
y: r1(7 + r, BALANCE_FIELD.height - 7 - r),
r
};
}
if (
balanceDist(hole.x, hole.y, start.x, start.y) <
hole.r + BALANCE_BALL_R + (asEdge ? 12 : 8)
)
continue;
if (balanceDist(hole.x, hole.y, goal.x, goal.y) < hole.r + goal.r + (asEdge ? 8 : 6))
continue;
// 洞与墙间距留出球径 + 反吸余量:洞的吸引圈不会贴墙封死通道
if (walls.some((w) => pointRectDist(hole.x, hole.y, w) < hole.r + 3 + BALANCE_BALL_R))
continue;
if (holes.some((o) => balanceDist(hole.x, hole.y, o.x, o.y) < hole.r + o.r + 8)) continue;
holes.push(hole);
break;
}
}
const name = d <= BALANCE_NAME_POOL.length ? BALANCE_NAME_POOL[d - 1] : `无尽滚道 ${n}`;
return { id: n, name, start, goal, holes, walls, suction };
}
// ============================================
// 火力全开(射击)
// ============================================
/** 火力全开关卡配置(数值曲线由关号确定性生成) */
export interface ShooterLevel {
id: number;
name: string;
/** 初始队友数量 */
initCount: number;
/** 开火间隔(秒) */
fireGap: number;
/** 怪兽基础下压速度(单位/秒) */
monsterSpeed: number;
/** 怪兽生成间隔(秒) */
spawnGap: number;
/** 算式门生成间隔(秒) */
gateGap: number;
/** 算式门难度档:0 仅 + ×;1 加入 −;2 全量 + − × ÷ */
opsMode: 0 | 1 | 2;
}
/** 射击逻辑坐标系:与平衡球一致(宽 100,高 137,画布 686×940rpx) */
export const SHOOTER_FIELD = { width: 100, height: 137 };
/** 火力全开关卡主题名(前 12 关),之后为「无尽战线 N」 */
const SHOOTER_NAME_POOL = [
"初试枪火",
"合金弹幕",
"蜂群来袭",
"裂口巨兽",
"钢铁洪流",
"烈焰走廊",
"深渊虫巢",
"雷鸣防线",
"末日壁垒",
"星陨要塞",
"湮灭风暴",
"无尽硝烟"
];
/**
* 火力全开关卡工厂:第 N 关完全由 N 确定性生成
* - 通关条件 = 击杀 BOSS(登场时机与血量由页面时间线估算)
* - 怪兽生成序列 / 算式门序列由页面用 mulberry32(id*9973+5) 二次展开(同关同布局)
* - 血量随机分档:小 1-3 / 中 5-15 / 大 20-30 / 超大 40-100(数字越大体型越大,页面按血量分档)
* - 初始 6 人小队;+−×÷ 双门并排各占半屏、强制二选一(增益比谁加得多、减益比谁减得少)
* - 前 2 关只出双增益门(教学),第 3 关起混入减益组合,第 6 关起 ÷ 门现身
*/
export function makeShooterLevel(n: number): ShooterLevel {
return {
id: n,
name: n <= SHOOTER_NAME_POOL.length ? SHOOTER_NAME_POOL[n - 1] : `无尽战线 ${n}`,
initCount: 6,
fireGap: 0.3,
// 怪兽整体提速:基础与成长都调高,高关卡封顶 8
monsterSpeed: Math.min(3.4 + n * 0.3, 8),
// 出怪更密:同屏怪更多(配合成簇出场)
spawnGap: Math.max(1.3 - n * 0.06, 0.5),
// 算式门波间隔:≈ 一对半屏门的穿场时间(GATE_SPEED),保证上一波二选一走完再出下一波
gateGap: 9.5,
opsMode: n <= 2 ? 0 : n <= 5 ? 1 : 2
};
}
五、效率提升
- 两代架构累计 5.7 万行级变更,Taro 起项目 + 三玩法从零实现 + 全量重构,全程由 TRAE 编码
- 同等工作量按传统方式(我手写 + 查资料 + 调试)估算要 1 月以上,实际只是断断续续的几天就完成了
- 其中 TRAE 贡献最大的三件事:从零搭工程骨架、跨文件批量重构、根因定位、代码开发修复
- 我的实际投入:需求澄清、试玩反馈、验收截图即可
六、写在最后
这次实战我最大的体会是:AI 编码工具的上限,取决于你定的 "验收标准",以及确定的需求!。我设计了确定性关卡和逻辑/渲染分离,让它的产出可以被机器验收;我只需要扮演好产品经理 ------ 把"对"定义清楚,"快"就自然发生。
如果你也想用 TRAE 完成一个从零开始的完整项目,我的建议就一条:别把 AI 当补全工具,把它当一个人来协作------给它完整的需求、明确的验收标准和一个能自动验证的测试体系,它会还你一整个项目。