EP1 把地基框架层 启动流程搭好了但屏幕上还没有游戏。这一集EP2在地基上长出核心玩法——Block Blast 式的方块消除。但延续这个系列的原则重点不是做个看起来能玩的而是把规则写成可断言的纯逻辑用一组测试把它钉死。能不能玩之前先证明它对不对。先剧透这套玩法跑起来真机长什么样后面接上输入和 UI 后、Unity 里实拍拖放落子、消行、计分都在跑玩法规则8×8 网格。每轮发 3 个方块各种形状玩家把它们拖到网格的空位上只要某一行或某一列被填满就整行/整列消除得分3 个方块都用完就补发新的 3 个当剩下的方块在棋盘上没有任何位置放得下判定死局、游戏结束。把这套拆成几个纯逻辑件都不依赖 Unity可独立测BoardModel网格占用 放置 / 消行列 / 死局判定Piece/PieceLibrary方块形状定义 Block Blast 风格形状库1~5 格直条、方块、L/T/S/Z…Scoring计分公式BlockGameController把上面串成一局的循环棋盘逻辑放置、消除、死局publicclassBoardModel{publicreadonlyintW,H;readonlyint[,]_cell;// 0 空0 为颜色 idpublicboolCanPlace(inPiecep,intox,intoy)// 全部格子在界内且为空{foreach(varcinp.cells){intxoxc.x,yoyc.y;if(!InBounds(x,y)||_cell[x,y]!0)returnfalse;}returntrue;}// 同时找出所有填满的行和列一次性清掉先判定再清避免边清边判public(intlines,intcells)ClearFullLines(){varfullRowsnewListint();varfullColsnewListint();for(inty0;yH;y){boolfulltrue;for(intx0;xW;x)if(_cell[x,y]0){fullfalse;break;}if(full)fullRows.Add(y);}for(intx0;xW;x){boolfulltrue;for(inty0;yH;y)if(_cell[x,y]0){fullfalse;break;}if(full)fullCols.Add(x);}varclearednewHashSet(int,int)();foreach(intyinfullRows)for(intx0;xW;x)cleared.Add((x,y));foreach(intxinfullCols)for(inty0;yH;y)cleared.Add((x,y));foreach(var(x,y)incleared)_cell[x,y]0;return(fullRows.CountfullCols.Count,cleared.Count);}// 死局给定可用方块没有一个能放下publicboolAnyPlaceable(IEnumerablePiecepieces){foreach(varpinpieces)if(p.CellCount0HasSpotFor(p))returntrue;returnfalse;}}一个容易写错的点消除必须先把所有满行满列判定完再一次性清。如果边判边清清掉一行会影响列的判定行列同时满的情况就会算错。用一个HashSet收集所有要清的格子再统一清行列交叉的格子也不会被算两次。计分鼓励一次多消publicstaticclassScoring{publicstaticintForPlacement(intcells)cells;// 放下给小分publicstaticintForClear(intlines)// 消除按行列数平方放大lines0?0:lines*lines*10;// 1条10, 2条40, 3条90, 4条160}平方放大是故意的一次消两条给的不是 20 而是 40鼓励玩家攒着一次多消——这是 Block Blast 上瘾感的来源之一。一局的循环BlockGameController把这些串起来发 3 个 → 放一个消行列、计分、标记已用→ 三个用完补新的 → 每次放完检查剩余方块是否还有处可放没有就 GameOver 并刷新最高分写进 EP1 的Property存档。publicPlaceResultTryPlace(intindex,intox,intoy){varpTray[index];if(p.CellCount0||!Board.CanPlace(p,ox,oy))returndefault;// 放不下Board.Place(p,ox,oy);Tray[index]/* 标记已用 */;var(lines,cells)Board.ClearFullLines();Score.ValueScoring.ForPlacement(p.CellCount)Scoring.ForClear(lines);if(TrayAllUsed())DealTray();if(!Board.AnyPlaceable(UnusedTray())){GameOvertrue;/* 刷新最高分 */}// ...返回详细结果给视图做表现}验证13 条断言把规则钉死这是 EP2 的核心交付不是跑起来看着对而是断言。一个工程细节funplay 的execute_code内联编译器较老不支持局部函数 /in参数 / 元组命名这些新语法。所以把自测写成项目里一个正常的.cs由 Unity 的编译器编译execute_code只调一行Ep2SelfTest.Run()——绕开老编译器。这条以后写 MCP 驱动的测试都用得上。PASS A 单行消除 lines1 PASS A 消除后清空 filled0 PASS B 行列同时消除 lines2 PASS C 满盘 HasSpotFor(d2h)false PASS C 满盘 AnyPlaceablefalse 死局 PASS C 空盘 AnyPlaceable([dot])true PASS D ForClear(1)10 PASS D ForClear(2)40 PASS D ForClear(3)90 PASS D ForPlacement(5)5 PASS E NewGame tray3 / score0 / !GameOver 13/13 PASS 覆盖了最容易错的几处单行消除后棋盘真清空、行列同时填满算作 2 条边清边判会算错的那种、满盘时各种方块都判无处可放死局、计分公式的平方放大、控制器开局状态。规则钉死了。顺手把画面立起来AI 出图流水线接上玩法光有逻辑没画面不像游戏。用 EP0 验证的那条gpt-image-2 → 程序扣图流水线并发生成了一套 6 色糖果方块同一段提示词、只换颜色词保证风格统一程序扣掉品红底成透明 PNG导入 Unity 当 Sprite按棋盘网格渲染出一个中局状态 底部 3 个待放方块竖屏 720×1559这一张是这个系列第一张像商业产品的画面——方块是 AI 生成的、布局是代码摆的没有一处占位色块。这一集的产物与诚实的话5 个玩法逻辑文件BoardModel / Piece / PieceLibrary / Scoring / BlockGameController 自测编译零错误。13 条断言全绿。一套 6 色 AI 方块 第一张棋盘渲染图。诚实地讲现在还不能用手拖——拖拽输入、落块动画、消除特效都还没做那是 EP7 的 juice 输入层。这一集证明的是规则正确 画面能渲染不是手感好玩。手感要等表现层和数值调校。但核心循环的对错已经被钉死后面所有东西数据、经济、留存都长在这套可验证的规则上。下一篇EP3把方块库、难度曲线做成数据驱动ScriptableObject / 配置并把玩法状态接进 EP1 的存档——让内容和代码分家。工具funplay-unity-mcp开源工程本系列做出来的完整 Unity 工程已开源上一篇EP1 地基
全程用 AI 做一款商业级手游 · EP2 核心玩法:方块消除,13 条断言全绿
EP1 把地基框架层 启动流程搭好了但屏幕上还没有游戏。这一集EP2在地基上长出核心玩法——Block Blast 式的方块消除。但延续这个系列的原则重点不是做个看起来能玩的而是把规则写成可断言的纯逻辑用一组测试把它钉死。能不能玩之前先证明它对不对。先剧透这套玩法跑起来真机长什么样后面接上输入和 UI 后、Unity 里实拍拖放落子、消行、计分都在跑玩法规则8×8 网格。每轮发 3 个方块各种形状玩家把它们拖到网格的空位上只要某一行或某一列被填满就整行/整列消除得分3 个方块都用完就补发新的 3 个当剩下的方块在棋盘上没有任何位置放得下判定死局、游戏结束。把这套拆成几个纯逻辑件都不依赖 Unity可独立测BoardModel网格占用 放置 / 消行列 / 死局判定Piece/PieceLibrary方块形状定义 Block Blast 风格形状库1~5 格直条、方块、L/T/S/Z…Scoring计分公式BlockGameController把上面串成一局的循环棋盘逻辑放置、消除、死局publicclassBoardModel{publicreadonlyintW,H;readonlyint[,]_cell;// 0 空0 为颜色 idpublicboolCanPlace(inPiecep,intox,intoy)// 全部格子在界内且为空{foreach(varcinp.cells){intxoxc.x,yoyc.y;if(!InBounds(x,y)||_cell[x,y]!0)returnfalse;}returntrue;}// 同时找出所有填满的行和列一次性清掉先判定再清避免边清边判public(intlines,intcells)ClearFullLines(){varfullRowsnewListint();varfullColsnewListint();for(inty0;yH;y){boolfulltrue;for(intx0;xW;x)if(_cell[x,y]0){fullfalse;break;}if(full)fullRows.Add(y);}for(intx0;xW;x){boolfulltrue;for(inty0;yH;y)if(_cell[x,y]0){fullfalse;break;}if(full)fullCols.Add(x);}varclearednewHashSet(int,int)();foreach(intyinfullRows)for(intx0;xW;x)cleared.Add((x,y));foreach(intxinfullCols)for(inty0;yH;y)cleared.Add((x,y));foreach(var(x,y)incleared)_cell[x,y]0;return(fullRows.CountfullCols.Count,cleared.Count);}// 死局给定可用方块没有一个能放下publicboolAnyPlaceable(IEnumerablePiecepieces){foreach(varpinpieces)if(p.CellCount0HasSpotFor(p))returntrue;returnfalse;}}一个容易写错的点消除必须先把所有满行满列判定完再一次性清。如果边判边清清掉一行会影响列的判定行列同时满的情况就会算错。用一个HashSet收集所有要清的格子再统一清行列交叉的格子也不会被算两次。计分鼓励一次多消publicstaticclassScoring{publicstaticintForPlacement(intcells)cells;// 放下给小分publicstaticintForClear(intlines)// 消除按行列数平方放大lines0?0:lines*lines*10;// 1条10, 2条40, 3条90, 4条160}平方放大是故意的一次消两条给的不是 20 而是 40鼓励玩家攒着一次多消——这是 Block Blast 上瘾感的来源之一。一局的循环BlockGameController把这些串起来发 3 个 → 放一个消行列、计分、标记已用→ 三个用完补新的 → 每次放完检查剩余方块是否还有处可放没有就 GameOver 并刷新最高分写进 EP1 的Property存档。publicPlaceResultTryPlace(intindex,intox,intoy){varpTray[index];if(p.CellCount0||!Board.CanPlace(p,ox,oy))returndefault;// 放不下Board.Place(p,ox,oy);Tray[index]/* 标记已用 */;var(lines,cells)Board.ClearFullLines();Score.ValueScoring.ForPlacement(p.CellCount)Scoring.ForClear(lines);if(TrayAllUsed())DealTray();if(!Board.AnyPlaceable(UnusedTray())){GameOvertrue;/* 刷新最高分 */}// ...返回详细结果给视图做表现}验证13 条断言把规则钉死这是 EP2 的核心交付不是跑起来看着对而是断言。一个工程细节funplay 的execute_code内联编译器较老不支持局部函数 /in参数 / 元组命名这些新语法。所以把自测写成项目里一个正常的.cs由 Unity 的编译器编译execute_code只调一行Ep2SelfTest.Run()——绕开老编译器。这条以后写 MCP 驱动的测试都用得上。PASS A 单行消除 lines1 PASS A 消除后清空 filled0 PASS B 行列同时消除 lines2 PASS C 满盘 HasSpotFor(d2h)false PASS C 满盘 AnyPlaceablefalse 死局 PASS C 空盘 AnyPlaceable([dot])true PASS D ForClear(1)10 PASS D ForClear(2)40 PASS D ForClear(3)90 PASS D ForPlacement(5)5 PASS E NewGame tray3 / score0 / !GameOver 13/13 PASS 覆盖了最容易错的几处单行消除后棋盘真清空、行列同时填满算作 2 条边清边判会算错的那种、满盘时各种方块都判无处可放死局、计分公式的平方放大、控制器开局状态。规则钉死了。顺手把画面立起来AI 出图流水线接上玩法光有逻辑没画面不像游戏。用 EP0 验证的那条gpt-image-2 → 程序扣图流水线并发生成了一套 6 色糖果方块同一段提示词、只换颜色词保证风格统一程序扣掉品红底成透明 PNG导入 Unity 当 Sprite按棋盘网格渲染出一个中局状态 底部 3 个待放方块竖屏 720×1559这一张是这个系列第一张像商业产品的画面——方块是 AI 生成的、布局是代码摆的没有一处占位色块。这一集的产物与诚实的话5 个玩法逻辑文件BoardModel / Piece / PieceLibrary / Scoring / BlockGameController 自测编译零错误。13 条断言全绿。一套 6 色 AI 方块 第一张棋盘渲染图。诚实地讲现在还不能用手拖——拖拽输入、落块动画、消除特效都还没做那是 EP7 的 juice 输入层。这一集证明的是规则正确 画面能渲染不是手感好玩。手感要等表现层和数值调校。但核心循环的对错已经被钉死后面所有东西数据、经济、留存都长在这套可验证的规则上。下一篇EP3把方块库、难度曲线做成数据驱动ScriptableObject / 配置并把玩法状态接进 EP1 的存档——让内容和代码分家。工具funplay-unity-mcp开源工程本系列做出来的完整 Unity 工程已开源上一篇EP1 地基