1. 项目背景与整体思路拆解
1.1 为什么第二周就敢碰国际象棋
先交代一下背景。我接触UE5是从零开始的,第一周基本在啃界面、摆方块、搞懂坐标系和 Actor 的概念,属于那种“看了很多教程、自己一动手就卡住”的状态。第二周我给自己定了一个稍微有点挑战性的目标:用纯蓝图做一个能玩的国际象棋。
说实话,刚定这个目标的时候心里是没底的。国际象棋听起来简单,棋盘就 8x8,棋子就 6 种,但真要把规则写清楚,尤其是马走日、兵升变、王车易位、吃过路兵这些细节点,正常人第一反应是“这得上 C++ 才搞得定吧”。但实际动手之后我发现,蓝图在原型验证阶段的优势非常明显,尤其是 UI 和逻辑的联调速度,比写代码快太多了。当然,如果你打算做商业项目或者对性能有极致要求,C++ 确实是最终归宿,但入门阶段用蓝图完全足够。
这个项目最适合的人群,是真的想把 UE5 的基础组件串起来的人。很多人学 UE5 是今天学个材质、明天学个灯光,知识点都是散的。国际象棋这个项目的好处在于,它天然需要你同时处理以下问题:
- 场景搭建:棋盘和棋子的网格布局
- 数据存储:用数组或结构体保存棋盘状态
- 交互逻辑:鼠标点击、拾取、高亮
- 规则校验:走法合法性判断
- UI 反馈:回合提示、目标提示
任何一个环节卡住,整个项目就玩不转。所以做完这个项目,你对 UE5 的“项目感”会有一个质的提升。
1.2 技术选型:为什么是纯蓝图
我需要先说一个很多新手容易产生的误区:觉得蓝图是“给不会编程的人用的玩具”。实际上蓝图在 UE5 里是一个完整的可视化脚本系统,它编译后和 C++ 在同一个框架里运行,只是语法表达方式不同。对于逻辑复杂度在千行以内的项目,蓝图完全hold住。
选纯蓝图还有一个实际好处:迭代速度快。我做国际象棋的时候,改一个走法规则,改完直接点播放就能测试,不需要编译。遇到变量类型不匹配、引用丢失这些问题,蓝图会直接在图表面板上报错,定位非常直观。对于入门者来说,这种“所见即所得”的调试体验能极大降低挫败感。
当然也要正视蓝图的短板。国际象棋的 AI 搜索算法(比如 Minimax + Alpha-Beta 剪枝)如果用蓝图实现,节点会非常混乱,维护成本高到你想砸电脑。所以我的规划是:先用蓝图把完整的双人对战流程做出来,后续如果要加 AI 对战,再把核心搜索算法用 C++ 实现,蓝图只负责调用。这个思路也符合 UE5 官方推荐的开发模式。
1.3 项目结构规划:先搭骨架,再填肉
动工之前,我把整个项目拆成了四个模块,每个模块解决一类问题:
| 模块 | 负责内容 | 核心问题 |
|---|---|---|
| 棋盘网格 | 64 个格子的生成与坐标映射 | 怎么把 AI 里的棋盘位置和 3D 空间位置对应起来 |
| 棋子逻辑 | 所有棋子的属性和基础行为 | 怎么让棋子知道自己是谁、在哪儿、能不能动 |
| 规则系统 | 走法合法性和回合判断 | 怎么拦住非法操作、切换回合 |
| 交互反馈 | 点击选中、高亮显示、动效 | 怎么让玩家直观地看到游戏状态 |
先搭骨架的好处是,你能在改造过程中始终知道“当前在做的东西属于哪一块”,不会绕晕。实际开发中我强烈建议你在 UE5 里把文件夹结构也按模块来分,Blueprint、Materials、Maps、UI 各占一个顶级目录,等你要改动的时候,找东西真的要省很多时间。
2. 核心细节解析与实操要点
2.1 棋盘的生成:不要手摆了 64 个方块
如果你打开 UE5 开始搞棋盘,第一个诱惑就是往场景里拖 64 个 Static Mesh。千万别这么干,二维数组循环生成才是正确的打开方式。
具体思路是这样:我们定义一个嵌套循环,外层代表行(Rank),内层代表列(File),从 (0,0) 到 (7,7) 遍历。每个格子通过 Add Actor From Class 或者 Spawn Actor 生成一个 Cube 静态网格体,然后根据行列的奇偶关系决定是白色还是黑色。
这里有一个关键知识点:国际象棋的棋盘是 8x8,左上角是黑格,右下角是黑格。传统棋盘的 a1 格是深色格,也就是坐标映射的时候需要注意——在 UE5 里,我们可以让 (0,0) 对应 a1,但 (0,0) 的颜色要根据你的视角方向来定。为了避免脑子绕晕,我的做法是:直接用行列的奇偶性判断颜色,即(行 + 列) % 2 == 0时设为白色格,其他设为黑色格。这样无论你的视角怎么旋转,颜色分布规律都正确。
每个格子在生成的时候,建议立即给它命名,格式就用Tile_X_Y,比如Tile_3_5。命名对调试有极大帮助,因为运行时你只要看到报错信息里有 Tile_3_5,就能立刻定位是哪个格子出了问题。这个习惯在整个项目开发中都非常值得坚持。
嵌套循环伪代码: For Row = 0 to 7 For Col = 0 to 7 Spawn Tile at (Col * 200, Row * 200, 0) Set TileName = "Tile_" + Row + "_" + Col Set TileColor = ((Row + Col) % 2 == 0) ? White : Black格子之间的间距要根据你的场景大小调整。我这边用的 Scale 是 1.9 倍的基础 Cube,间距 200 个单位,看起来比较舒适。如果你用纯 Cube 作为棋盘格子而不加 Border,格子和格子之间会连成一片,不好分辨边界。所以我把格子的 Z 轴厚度压到 0.5,然后稍微抬高一点,让每块格子之间有阴影缝隙,视觉上就自然分开了。
2.2 棋子的创建:用枚举和结构体管理身份
棋盘搞定之后,就要创建棋子了。这一步最容易犯的错误是给 32 个棋子各建一个蓝图类。实际上我们应该做一个通用的 BP_Piece 类,然后用变量来区分它是什么棋子,再用枚举类型管理棋子的类型和颜色。
在 UE5 里创建枚举的方法:内容浏览器右键 -> Blueprint -> Enumeration。我用两个枚举:
EPieceType: Pawn, Rook, Knight, Bishop, Queen, King EPieceColor: White, Black然后在 BP_Piece 里声明两个变量:
- PieceType(类型:EPieceType)
- PieceColor(类型:EPieceColor)
再配合一个 Static Mesh 组件和 Material 组件,根据不同棋子的类型动态切换模型和材质。这样做的好处显而易见:你只需要维护一个蓝图类,后续要改所有棋子的行为逻辑,改一个地方就够了。
创建棋子的时候,我建议你在关卡蓝图或者 GameMode 里写一个初始化的自定义事件InitPiece。在事件里传入棋子的类型、颜色和初始格子位置(比如 E2),然后根据类型设置网格体,根据颜色设置材质,再根据格子位置换算成 3D 坐标。开局摆位用一张映射表来写,代码会干净很多。
棋子模型的替换也很重要。我最初用的全都是 Low Poly 风格,因为 UE5 Marketplace 上免费资源比较多。如果你不喜欢 Low Poly,也可以直接用 UE5 自带的基础几何体先顶替着,等逻辑跑通了再替换美术资源。记住:逻辑优先,美术后置,这是做原型开发的铁律。
2.3 棋盘坐标系的建立:一图看懂 2D 坐标与 3D 坐标的换算
我在这里卡了整整一个晚上,所以要把这个点单独拎出来讲清楚。
国际象棋的棋盘坐标是用字母 + 数字表示的,列是从 a 到 h,行是从 1 到 8。AI 的逻辑层面,我们习惯用二维数组坐标,比如(4, 3),代表第 5 列第 4 行的格子。但 UE5 的三维空间坐标是一个连续的三维向量,棋盘里的每一格都有固定中心点。你必须要写一个换算函数,在这两者之间自由切换。
推荐做法:在 GameMode 或者专门的一个 BP_ChessBoard 蓝图里写两个函数:
GetWorldLocationFromGrid(GridX, GridY)-> 返回 VectorGetGridFromWorldLocation(WorldLocation)-> 返回 (GridX, GridY)
换算的核心逻辑其实很公式化:
WorldX = StartX + GridX * TileSize WorldY = StartY + GridY * TileSize其中 StartX 和 StartY 是棋盘左下角的起点坐标,TileSize 是每个格子的边长。反过来就是从世界坐标减去起点坐标再除以格子大小,然后四舍五入取整。
需要注意的是,国际象棋的行号方向和 UE5 的 Y 轴方向可能需要反转。如果你在世界里摆放棋盘时,a1 格放在原点附近,使用如上公式后,你会发现 b1 在 X 正方向,而这在棋局坐标里是“向右移动一列”,符合直觉。但如果你把棋盘旋转了 90 度,那换算公式里的 X/Y 顺序就要调整。
我自己踩过的坑:把棋盘在场景里旋转了 90 度,结果换算函数没有同步调整,导致棋子全跑到格子间的缝隙里。后来我干脆把棋盘完全摆正,所有换算都基于正向坐标来做,问题就消失了。如果你非要旋转棋盘,请把所有换算函数里统一加上旋转偏移参数。
2.4 选中与高亮:让玩家知道自己在干嘛
选棋子和下棋的交互,是整个项目里“手感”的关键。没有高亮和提示,玩家根本不知道当前选中的是哪个棋子,也不知道自己能走到哪些格子。
我的实现方式是这样的:
在 BP_Piece 里挂一个 Box Component 作为碰撞体,同时开启Generate Overlap Events。然后在棋子蓝图的 Event Actor OnClicked 里触发选中逻辑。注意:UE5 默认的鼠标点击事件需要在 Player Controller 里设置bEnableClickEvents = true和bEnableMouseOverEvents = true,不然点击不了。
选中之后,要做两件事:
- 在棋子本身上加一个选中的高亮效果,我用的是 Post Process 的
Custom Depth Stencil Value加 Outline 材质。简单说就是把高亮的物体单独渲染到 Custom Depth 通道,再叠一层轮廓描边。这个效果在虚幻引擎里属于基础特效,做完之后棋子会有明显的彩色描边,很有成就感。 - 根据当前棋子的走法规则,计算出所有合法的目标格子,然后在目标格子上生成半透明的蓝色方块作为提示。
这个设计逻辑敲定之后,后面的规则判断就能一起复用。玩家点击某个格子时,先判断格子是否在合法走法列表里,如果在,就执行移动;如果不在,且格子上有棋子,就切换选中对象。
高亮提示对新手来说不是可选项,是必选项。没有高亮的国际象棋,就像没有光标的文本编辑器,用户体验极其痛苦。
3. 实操过程与核心环节实现
3.1 棋子的通用移动逻辑
所有棋子的移动本质上都干同一件事:把棋子的位置从当前格子更新到目标格子,同时更新棋子自身记录的当前坐标变量。区别只在于哪些目标格子是合法的。
所以我在 BP_Piece 里写了一个通用函数MoveTo(GridX, GridY):
- 把 Piece 的坐标变量更新为新的 GridX 和 GridY
- 用 GetWorldLocationFromGrid 把网格坐标转成世界坐标
- 用
Set Actor Location把棋子瞬移到新位置 - 触发一个移动动画(可选)
移动动画我是用 Timeline 做的,线性插值从旧位置到新位置,时长 0.2 秒。为什么用 0.2 秒?太快了看不见动效,太慢了影响连续操作的手感,0.2 是我实测下来比较跟手的值。如果你要模拟真实棋子的滑动感,还可以在 Timeline 的曲线编辑器里拉一个 Ease In Out。
至于吃子逻辑,从通用视角来看本质上也是移动,只是在移动前需要判断目标格子上有敌方棋子且无我方棋子。如果有敌方棋子,直接 Destroy 对方之后把自己移动过去即可。
这里一定要提醒一点:Destroy 之后,你原来的引用就失效了。如果你在某个数组或者 Map 里保存了被销毁棋子的引用,一定要记得同步移除。我在第一次写完吃子逻辑时,发现吃掉的棋子残留在了棋盘状态数组里,导致后面的规则判断出现了灵异事件——明明棋子上没了,数组里还有,于是游戏以为那里还有子,挡住了一整条线路。排查了半天才发现是数组忘了清理。
3.2 每种棋子的走法判断
这是一个纯逻辑过程,适合用蓝图函数来处理。我建议把规则判断全部下沉到一个IsMoveValid(FromGrid, ToGrid, PieceType, PieceColor) -> bool函数里,这样以后不管哪个入口调用,规则都会保持统一。
兵的走法(Pawn)
兵的逻辑是最容易出 bug 的,因为它自带方向性。白兵从第 2 行出发向上走,黑兵从第 7 行出发向下走。移动方向相反,初始两步规则也只适用于未曾移动过的兵。
我实现的时候分了三种情况:
- 普通移动:前进 1 格,目标格为空
- 初始两格:如果兵从未移动过且当前在第 2 行或第 7 行,可前进 2 格,路径上的格子都必须为空
- 斜向吃子:斜前方 1 格有敌方棋子时可移动
这里需要用一个布尔变量bHasMoved记录兵是否移动过,防止玩家把兵从第 2 行直接拖到第 4 行并声称是“初始两步”。翼侧还有个“吃过路兵”的特殊规则,我第一版没有实现,加在规则函数里需要额外传递“上一步的着法”这个全局上下文,复杂度较高,建议入门第二周先跳过。
马(Knight)
马的走法没有“挡子”概念,是唯一可以跳过棋子的棋子。它的合法走法一共就 8 种组合:横向增减 1、纵向增减 2,或者横向增减 2、纵向增减 1。
相对位移集合: (±1, ±2), (±2, ±1)用绝对值判断最简洁:
if ((abs(dx) == 1 && abs(dy) == 2) || (abs(dx) == 2 && abs(dy) == 1)) { return true; }象(Bishop)、车(Rook)、后(Queen)
这三个放在一起说,因为它们都是“射线型”棋子,区别只在于方向集不同:
- 象的方向集:4 条对角线 — (1,1), (1,-1), (-1,1), (-1,-1)
- 车的方向集:4 条直线 — (1,0), (-1,0), (0,1), (0,-1)
- 后的方向集:上述两者并集 — 8 个方向
算法统一为:检查从起点开始沿着方向逐步前进,直到遇到边界;如果路径上有己方棋子,则该方向停止;如果路径上有敌方棋子,则该方向可走该格子且该方向也停止。
这就是典型的“射线检测循环”。蓝图里用 while 循环 + 变量自增实现,每次循环检查目标格是否在棋盘内、是否为空、是否为敌方棋子在。这个循环逻辑要写得规范一些,不然很容易出现无限循环。建议加上最大循环次数保护(最大 8 步,因为棋盘最多跨 7 格,从第 1 行走到第 8 行是 7 步)。
王(King)
王只能走邻近的 8 个格,也就是 Manhattan 距离和切比雪夫距离同时满足条件的情况。用相对位移表示就是:
if (abs(dx) <= 1 && abs(dy) <= 1 && !(dx == 0 && dy == 0)) { return true; }王的走法本身简单,麻烦的是王车易位(Castling)和“不能走进被攻击的格子”的规则。我先没做王车易位,第二周先把 8 方向移动和吃子做对。关于“不能走进被攻击的格子”,这个规则需要反向扫描所有敌子的合法走法,逻辑是庞大的,建议放到后续版本再加。
3.3 回合管理机制
国际象棋是双人对战,需要限制玩家只能操控自己的子。我用的方法是在 GameMode 里设置一个CurrentTurn枚举变量,初始是 White。在点击棋子的 OnClick 事件里,加一个判断:
if (ClickedPiece.PieceColor != CurrentTurn) return;移动完成后切换回合:
CurrentTurn = (CurrentTurn == White) ? Black : White;同时在 UI 上更新当前的回合提示。这一步在蓝图里非常简单,但有一个容易忽略的点:棋子的 OnClick 事件最好只对玩家可见的 Player Controller 响应,否则单纯用 Collision 点击会在多人模式下造成混乱。如果你是单机项目,直接用 Actor OnClicked 的事件绑定就足够了。
回合切换需要和 UI 联动,在我的实现里是调用 UI 蓝图里的UpdateTurnText函数。这个函数把CurrentTurn变量转成文本:如果当前是白棋回合,显示“白棋走”,同时可以把棋盘氛围光调成亮色;黑棋回合就把光调暗,让玩家有非常清晰的氛围反馈。
3.4 UI 界面的搭建:胜利与提示
UE5 里做 UI 用的是 UMG(Unreal Motion Graphics)。我的 UI 布局很简单:
- 屏幕顶部:当前回合文本 + 重置按钮
- 屏幕中央偏下:隐藏的“将军!”提示(通过 Animation 弹出)
- 游戏结束时:全屏半透明遮罩 + 胜利者文本
UMG 里的 UI 控件想和蓝图通信,我推荐用 Event Dispatcher(事件分发器)。我在 GameMode 里定义了三个事件:
- OnTurnChanged
- OnCheck
- OnGameOver
在 UI 蓝图里绑定这些事件。
UMG 的做法:
- 在 UI 蓝图里选中对应控件
- 在 Event Graph 里绑定 GameMode 的事件分发器
- 事件触发时修改 TextBlock 的文本和可见性
这个小流程是整个项目里最容易让人有成就感的部分,因为 UI 一接上,游戏立刻就有“完整产品”的感觉了。我建议你在 UI 上多花一点时间,哪怕只是做一点颜色渐变和动效,玩家的体验提升远比你想的大。
3.5 吃子与棋盘状态的同步更新
在你把棋盘看成一个“状态库”时,吃子其实做两件事:物理世界里销毁棋子对象,逻辑世界里标记格子为空。没有这一步同步,game rule 会迅速失效。
我选择用一个一维数组来管理棋盘状态,数组长度 64,索引公式是GridX + GridY * 8。数组每个元素存的是引用(如果该格子上有棋子),存空值(如果是空格)。
每次移动后要更新数组:
- 将原格子的引用置空
- 将目标格子的引用设为当前移动的棋子
- 检查目标格子原本是否存在棋子,如果有,记录并销毁
这个数组是后续所有规则判断的“数据中心”。写规则函数时不要去看场景里有哪些 Actor,直接查数组,速度和准确性都更好。
4. 常见问题与排查技巧实录
4.1 复制出来的蓝图变量丢失
这个问题是我在操作时碰到的典型坑。你从现有蓝图复制一个新的出来,改了一下变量名,然后发现场景里引用它的 Actor 的某个变量突然变成 None 了。这是 UE5 的常见问题,根源在于复制蓝图时,部分对象引用没有被重新映射,尤其是当被引用的对象是关卡里的 Actor 时,引用会在复制后自动清空。
排查方法:
- 检查复制的蓝图里有没有变量引用了关卡里的特定 Actor。
- 打开蓝图详情面板,查看变量是否是 Instance Editable,如果是且在关卡详情里被覆盖了值,那么复制后大概率会丢掉。
- 遇到这种情况不要试图手动补引用,最好的办法是重新在关卡里设置一次。
提示:如果要从一个蓝图派生多个实例,推荐使用 Data Asset 来存配置,或者用接口动态获取目标 Actor,避免硬引用关卡对象。
4.2 射线检测与点击穿透
有些时候,你明明点击了棋盘上方的棋子,选中的却是棋盘地面。这是典型的“点击穿透”问题——多个碰撞体叠加在一起,UE5 的点击检测默认选择最近的阻挡命中,但如果你没有设置bTraversing或者忽略了阻挡类型,点击结果就不稳定。
解决方法是给所有棋子和棋盘格子分别设置不同的 Object Channel。我在项目里新建了ChessPiece和ChessTile两个 Object Channel,然后把棋子和格子的碰撞响应设置成只对应自己的类型,这样点击事件就不会相互干扰。你可以在 Project Settings -> Collision 里创建 Object Channel,然后在 Actor 的 Collision 配置文件里绑定。
还有一个细节:如果棋盘边缘有隐形碰撞体或者 UI 控件没设置 Ignore Hit Test,也会拦截点击。UI 这块尤其坑,明明你想点击 3D 场景里的棋子,结果屏幕上方一个看不见的 Canvas Panel 把点击给吃掉了。遇到点击不响应时,先去检查 UMG 控件的 Hit Test 设置——解决方案是把 Canvas Panel 的Visibility属性设为Self Hit Test Invisible,只让里面的按钮等控件响应点击。
4.3 触摸操作与双指支持的问题
如果你打算把项目打包到平板或者手机上,需要注意 UE5 默认的触摸输入和 PC 鼠标点击逻辑不太一样。匹配我的项目来说,我最初只实现了鼠标点击,在平板上测试时发现点击无反应。
原因是移动端上 Actor 的 OnClicked 不会自动触发,需要你在 Player Controller 里启用 Touch Interface 或者手动绑定输入事件。具体步骤如下:
- 在 Project Settings -> Input 里启用
Mobile HDR和Mobile MSAA - 在 Player Controller 蓝图中绑定
Touch事件 - 把 Touch 事件处理里加一个
Get Hit Result Under Finger节点,拿到手指位置对应的 Actor
双指触摸(比如双指缩放视角)也很常见,背景里自然有需求。关键是不要和单指点击混淆。通常单指负责选中和移动,双指负责旋转缩放。UE5 的 Input 系统有 Finger 索引,可以通过Input Touch的返回值判断是第几根手指触发的。我在项目里给单指绑定了选中逻辑,给双指绑定了摄像机旋转缩放,实测不会冲突。
4.4 渲染内存不足
UE5 在较大场景中容易触发渲染内存不足,尤其是当你在同一帧里生成大量 Actor 或加载高分辨率贴图时。国际象棋项目本身场景小,但我踩到过一次是连续点击“重置棋子”,反复创建和销毁 Actor 导致内存碎片增加,最终触发渲染 OOM。
排查方向:
- 确认场景里是否反复生成 Actor 但没有正确销毁;每次重置之前,遍历旧棋子做 Destroy 而不是让它们残留。
- 检查贴图大小,如果棋盘和棋子都用的 4K 贴图,内存压力会很大,建议常规棋子用 1K 足矣。
- 在项目设置里增加渲染资源预算,或者在控制台输出
r.Streaming.PoolSize和r.Streaming.MaxTempMemoryAllowed来观察流送池占用。
棋子重置我最终采用的办法是:一键删除所有旧棋子,并且调用Flush Streaming手动刷新一下网格体流送状态,实测稳定多了。
4.5 缓存配置文件的版本号不匹配
有几次我改动完蓝图,进入 Play 模式时发现还是旧逻辑,甚至出现材质变紫。排查了很久发现是缓存问题。UE5 的 DerivedDataCache(DDC)有时候不会及时刷新,或者版本号不匹配导致加载了旧的缓存资源。
处理方式:
- 关闭编辑器
- 删除项目下
Saved/DerivedDataCache文件夹 - 重启编辑器,强制重新编译所有资源
如果是版本更新后出现不匹配,最好直接在启动时加上-NoP4 -RRF -DDC=NoLocal参数,忽略本地缓存。但注意这种操作会拖慢启动时间,别频繁用。
4.6 UE5 的渲染管线与 SVT 设置
如果你在项目里用了光追或者高分辨率特效,需要搞清楚 UE5 的渲染管线设置。国际象棋这个项目本身不吃画质,但入门阶段很有必要跑通一遍渲染流程。SVT 指的是 Virtual Texture,UE5 默认会启用虚拟纹理来优化大型场景的贴图加载。但对于我们这种小型棋盘项目,SVT 反而可能导致某些贴图一开始模糊,等流送完成才清晰。
如果你遇到棋盘贴图模糊、棋子材质粗糙的问题,试着在项目设置里关闭虚拟纹理,或者把r.VT.MaxAnisotropy调低一点。还有一个常见误区:DirectX 12 和 Vulkan 的渲染行为差异很大,同一种半透明材质在 DX12 下正常,切到 Vulkan 就变黑。如果做跨平台项目,务必在目标平台上提前测试渲染效果。
4.7 先装低版本还是高版本?
这个可能是刚入门 UE5 的人最大的纠结。我的建议非常简单粗暴:
- 如果你纯粹为了学习和做原型,直接装最新稳定版
- 如果你的目标是在 UE5 上做正式项目,并且有第三方插件依赖,优先匹配插件支持的版本
国际象棋这种小项目对版本不敏感,直接最新版即可。我在第二周使用的是 5.x 的正式版,整体稳定性已经非常好了。不要因为网上说“某某版本不稳定”就去装老版本,大部分所谓不稳定的反馈都来自早期预览版(有 Preview 后缀的版本),正式版基本可以放心用。装两个版本在磁盘空间允许的情况下也可以,但我建议只保留一个,避免项目文件版本混认导致打不开。
5. 第二周项目的心得体会
5.1 蓝图的组织方式直接决定你的迭代速度
第二周最深的感触是:蓝图脚本本身不难,难的是怎么组织。刚开始我所有逻辑都堆在关卡蓝图里,结果节点多了之后像一团乱麻,改一个地方要找半天。后来我把逻辑拆到了 GameMode、BP_Piece、BP_Tile 三个类里,每个类只负责自己的部分,关卡蓝图只留初始化逻辑,整个项目清爽了很多。
具体划分方式:
- GameMode:持有棋盘状态数组、回合变量、规则判断函数
- BP_Piece:持有棋子类型、颜色、所在坐标,以及自身移动、销毁、动画逻辑
- BP_Tile:持有格子坐标、高亮效果显示、点击后的事件转发
- UI_ChessGame:负责所有 UI 控件的更新,被动响应 GameMode 的事件
这基本就是 MVC(模型-视图-控制器)思想的简化版。你在第二周阶段不需要刻意学架构,但按这个思路去划分蓝图职责,到了第三周加功能时会发现有多省事。
5.2 逻辑验证比画面好看更重要
入门项目的通病是沉迷于把场景做得好看,环境光、后期处理、材质调半天,结果核心逻辑漏洞百出。我踩过的教训:有一个晚上我在调棋盘材质,调到了凌晨两点,第二天测试走子规则时发现马的走法判断有个符号写反了,修正只花了五分钟。从那以后我给自己立了一条规矩:先把 Play 模式里所有规则跑通,再回头看画面。
验证规则我写了一个很简单的自动化流程:进入 Play 模式后,手动把每类棋子的合法走法打印到日志里,再把每类棋子的非法走法也打印一份,肉眼对比预期结果。这种方法虽然土,但确实能帮你快速建立起对规则函数的信心。
5.3 下一步还能怎么扩展
第二周的项目做完,如果你还有精力,我建议顺着下面这几个方向扩展,对能力的提升会非常大:
- 将棋盘状态数组升级为 TMap 或结构体,支持记录上一步的完整信息,为“悔棋”和“吃过路兵”做准备
- 增加简易 AI,第一版先实现“随机走一步”到第三版再上 Minimax
- 尝试导出到移动端,处理触摸交互适配
- 给棋子增加音效反馈,点击和吃子各配一个音效,用户反馈的真实感会提高一大截
每加一个功能,你都会发现 UE5 里有一个新的系统等着你去学。这就是做项目最大的价值。我第二周做完国际象棋,最大的收获不是学会了“如何做国际象棋”,而是终于搞明白了 GameMode、Actor、UI、碰撞、事件分发器这些零散概念是怎么串起来协作的。这种感觉,看教程是永远得不到的。