Files
HexTrails/docs/art-direction.md
T

15 KiB
Raw Blame History

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)。

验收要求

  1. 五套背景逐一检查场景文字与箭头
  2. 检查离场、旋转、撤销后颜色稳定
  3. 390×844 竖屏 + 较矮屏幕
  4. 主界面、选关、设置、通关/失败、碰撞状态
  5. 弹窗不穿透操作、图标居中、轨道接缝不断裂

本项目目前没有任何视觉回归基线。 建立截图基线是值得做的一件事,但目前没有;改动视觉时应提供实机证据(截图),而不是只报"测试通过"。


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 计算),不是转述。未验证:真人观感、实机触感、原生端渲染效果 —— 这三项都还没有可引用的证据。