15 KiB
HEX TRAILS 美术方向与制作简报
版本:2026-09-11。本文面向承担本项目美术设计工作的会话或成员,说明角色边界、设计系统事实、改色与素材流程、已知设计债,以及三条工作线的入手点。
本文是工作简报,不是设计规范本身。数值与规则的唯一来源仍是 src/design-tokens.js 与 Design.md;本文负责把"怎么改、别踩什么、先做什么"讲清楚。
1. 角色边界
职责
- 配色、主题、图标、动效时长、布局与响应式表现
- 素材生成与裁切:图集、图标、背景,以及
src/sprites.js/src/ui-skin.js中的裁切框 - 设计规范与生成产物的一致性:
Design.md/Design.html/design/*/assets/ui-surfaces.png - 视觉验收:改动后提供实机证据,而不只是"测试通过"
- 原生端视觉对齐:
cocos/、wechat/HexTrail/
不做
- 不修改玩法规则、求解器、碰撞数学(
src/core.js、src/path-math.js) - 不修改关卡布局与生成参数(
levels/*-seeds.json、生成脚本) - 不静默替换已接受的素材,不另写一套 HEX,不用滤镜临时拼配色(见 Design.md §3.5)
前置阅读:Design.md(设计规范全文)· cocos-port.md(移植约束)· level-design-principles-v2.md(了解产品方向,避免设计脱离玩法)
2. 设计系统事实(当前状态)
2.1 单一来源
src/design-tokens.js(80 行)是颜色、字体、动效、圆角的唯一数值来源。
| 导出 | 内容 | 规模 |
|---|---|---|
THEME |
语义角色 | 30 个 |
SURFACES |
面板角色(对应 ui-surfaces.png 的色块) |
13 个 |
PIGMENTS |
棋子色库,箭头色按对比度自动选白/深蓝灰 | 12 色 |
LEVEL_THEMES |
场景主题,按难度分数选取 | 5 套 |
TYPOGRAPHY |
level 28 / moves 32 / dialogTitle 24 / body 14 / label 12 / caption 11 | 6 档 |
MOTION |
动效时长,见 §6 债务 D3 | 17 项 |
RADII |
control 14 / dialog 24 / small 8 | 3 档 |
2.2 主题按难度分数选取
themeForScore(score) 的分档与 analyze().label 同构(已穷举 0–120 分及 140 关实测,零不一致):
| 分数 | 标签 | 主题 | 背景 | 棋子搭配 |
|---|---|---|---|---|
| 0–29 | 轻松 | 薄荷浅湾 lagoon | #C5E4DF |
晴空蓝、湖水青、日光黄 |
| 30–49 | 进阶 | 杏色午后 apricot | #F1D7B8 |
珊瑚橙、鸢尾蓝、湖水青、蜜桃杏 |
| 50–69 | 烧脑 | 鸢尾花园 lavender | #D8D0EB |
葡萄紫、嫩芽绿、玫瑰红、冰川青 |
| 70–100 | 挑战 | 玫瑰暮色 rose | #E7C3CE |
玫瑰红、鸢尾蓝、日光黄、兰花紫 |
| 特别主题 | 每周挑战 | 午夜极光 midnight | #293956 |
冰川青、兰花紫、日光黄、莓果粉 |
midnight 不被 themeForScore 返回,只能通过 ?theme=midnight 或 showcase 进入。周赛功能尚未接入(Design.md 已声明)。
2.3 取色机制(容易搞错)
src/renderer.js:23 取的是棋子序号,不是棋子自带的 color 字段:
pigmentIndex(state.theme
? Math.max(0, level.tiles.findIndex(t => t.id === tile.id)) // 棋子序号
: tile.color, state.theme) // 仅 edit/trial 用 color
因此主线是调色板循环分配:20 枚棋子、7 色主题 → 只有 7 种颜色,多枚同色是设计如此。换来的是 Design.md 承诺的稳定性:移走棋子、旋转、撤销都不会让剩余棋子变色(已端到端验证,逐棋子变化数 0)。
图集只有 4 帧(TILE_FRAMES),palette 索引 ≥4 时会降级为纯色多边形绘制。这是 CanvasPainter.sprite() 返回 false 触发的正常降级路径,不是错误。
3. 改色工作流
只有这一条路:改 src/design-tokens.js → 重新生成 → 检查产物 → 跑测试。
# 1. 改 src/design-tokens.js
# 2. 重新生成设计产物
npm run design # = build-design.mjs + build-design-guide.mjs
# 3. 检查产物与对比度报告
npm test # 107 项,含 design.test.js / themes.test.js
npm run build # 全量构建;对比度有失败会 exitCode=1
npm run design 会重写这些文件:
| 产物 | 内容 |
|---|---|
design/tokens.css |
kebab 命名 CSS 变量 |
design/tokens.json |
theme / pigments / levelThemes / surfaces / typography / motion / radii |
assets/ui-surfaces.png |
由脚本手写 PNG 编码器烘出的 13 个 32×32 纯色块 |
design/contrast-report.json |
33 项对比度校验结果 |
Design.html |
2.6MB 单文件离线手册,内嵌图集 base64 与 SVG 复刻的棋盘示例 |
对比度有三道校验:构建脚本(失败 exitCode=1)、tests/design.test.js、tests/themes.test.js。
注意 npm run build 不生成设计产物,它只调用 levels:versions → levels:v04 → design → build.mjs,其中 build.mjs 仅把文件复制进 dist/。所以改色必须单独跑 npm run design。
4. 素材管线
4.1 当前使用中的素材
| 用途 | 文件 | 规格 |
|---|---|---|
| 棋子图集 | assets/tiles-matte-clean-v8.png |
1293×1216,RGB 无 alpha |
| 图标图集 | assets/ui-icons-generated.png |
1254×1254,16 个图标 |
| 面板色块 | assets/ui-surfaces.png |
416×32,生成物,勿手改 |
4.2 裁切框是代码,不是图片
- 棋子:
src/sprites.js的TILE_FRAMES(4 个540×465框)+src/tile-shape.js的tileSample()(取每框中央 20%)与roundedHex()(顶点内缩 7.5%) - 图标:
src/ui-skin.js的ICONS(16 个手写光学中心 bounds,扩成正方形 +12px 留白)
图片只能在原图不透明、无杂点的区域采样。 tiles-matte-clean-v8.png 之所以叫 "clean",就是因为 assets/GENERATION.md 记录两次边缘杂点清理尝试均失败(其中一次产出不透明棋盘格),最终改为只采样中央干净色块。改动裁切框前先读那份记录。
4.3 生成流程与提示词
assets/GENERATION.md 保存完整生成史;提示词档案为 assets/prompt-v4/v6/v7/v8.txt、assets/prompts-v3.json。历史素材(tiles-candy-v2/v3/v4、tiles-matte-v5/v6/v7、ui-icons.png、ui-panels.png、board-tray.png、background-candy-v2.png、logo-candy-v2.png)保留但不用于当前界面;ui-icons-flat.png 是代码重绘版,明确不再引用。
产出新素材的规则:原始 PNG 不做脚本像素修改;替换已接受的素材要明确告知用户来源与变化,并同步更新 Design.md §5 的资产表。
5. 三条工作线与入手点
5.1 打磨现有界面(优先级最高的具体项见 §6)
改视觉前先确认改的是语义角色而不是某个具体位置。Design.md §8 的流程要求:明确角色 → 改 token → npm run design → 检查产物 → npm test / npm run build → 五套背景逐一检查。
响应式要点:竖屏 390×844 为主线,较矮屏幕需纵向滚动;点击区至少 44×44;棋盘支持 1–4 倍缩放与双指手势。
5.2 产出新素材
先读 assets/GENERATION.md 了解既有提示词的措辞风格与失败教训,再决定是否需要新素材。优先复用现有图集——ui-surfaces.png 是生成的,改 token 即可变色,不需要新图片。
5.3 为微信 / Cocos 移植准备视觉资产
现状:wechat/HexTrail/ 是 Cocos Creator 3.8.8 空壳工程(assets/ 只有 .gitkeep,无场景无脚本);cocos/HexTrailsBoard.ts 是适配示例,未运行场景、未做 Android/iOS 构建。
移植的视觉契约是:Cocos 读取同一份 JSON/JS token 和 PNG,不得在原生侧另外猜颜色(Design.md §8)。CandyUiSkin 用纯色 Sprite + 圆角 Mask + 居中图标实现。
已知会阻塞移植的两项(详见 §6 D1、D2):复制清单漏文件会导致 import 失败;原生端硬编码的动效时长与网页现行模型不一致。
6. 已知设计债(按入手价值排序)
以下均为实测复核过的结论,不是推测。修复它们不需要改动玩法或关卡数据。
D1 · 移植复制清单漏 src/exit-animation.js(会直接报错)
docs/cocos-port.md 的复制清单列了 collision-animation.js,但 src/renderer.js:1 直接 import { exitTiming, smoothMotion, travelProgress } from './exit-animation.js'。照清单复制 → import 失败。
D2 · 原生端硬编码动效时长,与网页不一致
| 项 | 网页(MOTION) |
Cocos(HexTrailsBoard.ts) |
|---|---|---|
| 离场 | 120ms/单位 + ramp≤80 + 180ms 收束 |
Math.min(1.7, 0.55 + steps*0.085)(:117) |
| 碰撞 | 800ms |
1.25(:120) |
| 旋转 | 240ms |
0.42(:138) |
HexTrailsBoard.ts 完全没有 import MOTION。另外它的 redraw() 只传 {path, hint, animation, boardAngle},缺 theme / reduced / queued / shake / selected —— 原生端因此没有主题配色、没有 prefers-reduced-motion 分支、没有连点紫环反馈。
D3 · MOTION 有 7 个死 token
exitMinTravelMs、exitMaxTravelMs、exitLongBaseMs、exitLongCellMs、exitLongMaxMs、exitLongStartCells、exitLongFullCells —— 在 design-tokens.js 之外引用数均为 0,但仍在导出给原生端。
根因:提交 e92cccb 把 exitTiming 从"clamp 140–800ms + 长路混合曲线"换成了纯路径长度模型,没有清理 token、没有同步文档与适配器。
D4 · Design.md 两处数字错误
- §7 离场(
Design.md:166):写的是已废弃的模型("每单位 60ms、滑行段限 140–800ms、5 格及以上 550ms+85ms/格、最长 1700ms")。实际是 120ms/单位、无任何 clamp。tests/themes.test.js的断言(120ms、"without a duration cap")是对的,文档是错的。 - §8 对比度(
Design.md:175):写"轨道/底板约 3.55:1",实测contrast(track, board) = **5.7636**。源码里没有任何一对等于 3.55。 连带问题:Design.html自相矛盾 —— 生成表格(:43)写 5.76,正文(:105)与内嵌 JSON(:114)写 3.55。改Design.md后重跑npm run design即可一并消除。
D5 · 棋子面 vs 棋盘:12 色中 7 色低于 3:1(未声明的覆盖缺口)
CONTRAST_PAIRS 的 33 项没有校验棋子面与它实际相邻的颜色。而 renderer.js:44 把所有格子统一画成 THEME.board(#F2EEE5),drawTile() 只填纯色、无描边 —— 棋子的真实相邻面是棋盘,不是主题背景。
| 棋子色 | vs 棋盘 #F2EEE5 |
|---|---|
日光黄 #FDBC28 |
1.4626 |
蜜桃杏 #F5AA83 |
1.6548 |
冰川青 #58C9D5 |
1.6902 |
嫩芽绿 #91C95B |
1.6935 |
珊瑚橙 #EF704F |
2.5542 |
晴空蓝 #2392FD |
2.7474 |
莓果粉 #FC3E80 |
2.9539 |
诚实定性:项目从未声明过这条要求,所以这是未声明的缺口而非违约。缓解因素是每枚棋子上都有 ≥3:1 的箭头(已证实),且颜色不参与玩法。最坏后果是"棋子面与棋盘融在一起、只剩箭头悬空"。 是否补描边、调棋盘底色、还是明确声明"不承诺此项",属设计决策,需与用户确认后再动。
D6 · style.css 旧色板与 token 脱钩
style.css 仍是旧的薰衣草紫体系(--purple:#8772ed、--line:#d4d9f0、--muted:#8c94b3),var(--color-*) 引用数为 0。当前靠 design/components.css 后加载(元素选择器同特异性下后者胜出)+ 7 处 !important 覆盖。
仍未被覆盖的硬编码包括 .rotate-button.hinted{outline:4px solid #ffd667}、dialog/metric 的旧灰紫(#8d9abb、#95a1c0)。
D7 · components.css 星光辉光违反本项目自己的禁令
.result-emblem .ui-icon[data-icon="star"]{
filter:brightness(0) saturate(100%) invert(79%) sepia(85%) saturate(1450%) ...}
硬编码了 #c88b2380、#ffd45c99、#ffe8a366 并用 brightness/invert/saturate 滤镜链调色 —— 直接违反 Design.md §3.5「禁止用 brightness / invert / saturate 临时调面板色」与「src/design-tokens.js 是颜色数值的唯一来源」。同一规则复制在 design/guide-template.html:19。
D8 · 两处冗余配对与小余量
CONTRAST_PAIRS里"轨道/棋盘"与"轨道/浅色外缘"实为同一项(trackHalo === board === #F2EEE5),是冗余校验- 最紧的两项:分格/棋盘 +0.1434、箭头/晴空蓝 +0.1811
- 脆弱点:珊瑚橙的白箭头比值 2.9574,只比阈值低 0.0426 —— 阈值若降到 2.95 就会选白箭头并跌破 3:1(当前判定正确)
D9 · lagoon 裸主题色板碰撞(当前发行路径不可达)
LEVEL_THEMES[0] 的 palette 只有 3 项 [0,4,2],pigmentIndex 取模后 [0,1,2,3] → [0,4,2,0],colour 0 与 3 都是晴空蓝。tests/themes.test.js 只断言 palette 长度 3–4,测不出来。
当前不可达:app.js 播放态总是先过 themeWithColors()(补足到 4/7/9/12)。属潜在缺陷,改动主题时注意。
7. 视觉验收
自动测试不能替代视觉验收。 现有测试是"测试全绿但覆盖不到视觉"的典型 —— §6 的 D5、D7、D8、D9 都没有测试保护,所以"107 项通过"不构成对它们的反驳。
可用的预览入口
| 入口 | 用途 |
|---|---|
/design-showcase.html |
五套主题切换 + 真实棋盘 + 离场动效;src/showcase.js 支持主题标签与配色数切换 |
/?theme=<id> |
主游戏强制指定主题,例如 ?theme=midnight(唯一能看午夜极光的方式) |
/?colors=<4|7|9|12> |
覆盖单关配色数 |
/Design.html |
单文件离线设计手册,内嵌图集与组件示例 |
/?editor=1 |
关卡工坊,含完整 12 色库 |
需要 npm run dev 起服务(默认 4173),必须通过 HTTP 访问(ES modules)。
验收要求
- 五套背景逐一检查场景文字与箭头
- 检查离场、旋转、撤销后颜色稳定
- 390×844 竖屏 + 较矮屏幕
- 主界面、选关、设置、通关/失败、碰撞状态
- 弹窗不穿透操作、图标居中、轨道接缝不断裂
本项目目前没有任何视觉回归基线。 建立截图基线是值得做的一件事,但目前没有;改动视觉时应提供实机证据(截图),而不是只报"测试通过"。
8. 参考
| 路径 | 内容 |
|---|---|
Design.md / Design.html |
设计规范(前者手写,后者生成) |
docs/cocos-port.md |
移植接入步骤与验证限制 |
docs/level-design-principles-v2.md |
产品方向与关卡准则 |
assets/GENERATION.md |
素材生成史与提示词 |
design/tokens.json |
给原生端读的 token 导出 |
design/contrast-report.json |
33 项对比度实测结果 |
本文的核查范围:§2、§3、§4、§6 的全部数字来自对当前工作区的实测复核(构建、测试、内联 node 计算),不是转述。未验证:真人观感、实机触感、原生端渲染效果 —— 这三项都还没有可引用的证据。