像素调色板
三消的美术不需要画得多细,只要 32 个颜色互相不打架,整屏看起来就成立。
有限调色板
本作的图集用 Dawnbringer 32——一位像素画作者设计的 32 色通用调色板。它覆盖暗部、肤色、草木、金属和几档高饱和点缀,画任何题材都够用。
限定颜色数量有三个直接收益。第一是协调:32 个颜色是被人挑过一遍的,放在一起不会出现刺眼的撞色。第二是决策成本低,可选颜色少,选错的概率就低。第三是可控的文件体积,索引色 PNG 每像素只占几位。
| 平台 / 方案 | 色彩上限 |
|---|---|
| Game Boy | 4 档同色 |
| NES | 主调色板 54 色,每块用 4 色 |
| SNES | 15 位色,共 32768 色 |
| Dawnbringer 32 | 32 色 |
老主机的限制不是设计选择,是硬件成本。今天的限制则是主动选择的约束——约束会逼出风格。
真彩色 PNG 存的是每个像素的 RGB,调色板在这里只是美术纪律,不是技术约束。想享受索引色的省空间与秒换色,得在导出时选 PNG-8,之后加载和绘制都需要专门的兼容处理。多数 2D 引擎默认按 RGBA 处理,选前者等于自找麻烦——除非项目真的要跑在老硬件或极小的内存预算里。
抖动
32 色画不出平滑渐变,但可以骗眼睛。抖动(dithering)把两种颜色的像素按固定图案交替排列,退后一步看就是中间色。
- 棋盘格:两色各占一半,混出居中的第三色
- 1/4 密度:主色多,用于浅一档的过渡
- 3/4 密度:辅色多,用于深一档的过渡
密度调细,同一色相就能撑出四档,32 色在观感上够用上百档。
抖动的代价是纹理感。大面积平涂加抖动会显得脏,所以它通常只用在渐变区和阴影边缘。用错地方的典型症状是:远处看是渐变,近看全是噪点。
调色板交换
索引色图像的每个像素存的是「第几号颜色」,不是 RGB 值。换一张颜色表,整张图立刻变成另一套配色,像素数据一个字节都不用改。老主机正是靠这个做敌人变种:同一份精灵数据,指向不同的调色板内存,红史莱姆就变成蓝史莱姆。
| 存储 | 每像素内容 | 换配色的代价 |
|---|---|---|
| 索引色 | 调色板下标 | 换一张颜色表 |
| 真彩色 | RGB 三个分量 | 改贴图,或用 tint |
现代引擎没有硬件调色板,等价做法是准备多份着色器参数或直接用 setColor 乘色。
本作的 18 色 × 6 花样是另一种变体堆叠:不换颜色表,而是把每种颜色的六个花样都画进图集。省掉了运行时逻辑,代价是图集要装下全部 108 格,而索引色方案只需一份形状图加若干颜色表。
代码里的颜色
LÖVE 11 的 setColor 收 0–1 的浮点,所以游戏代码里到处是 /255:
local COLORS = {
red = {217/255, 87/255, 99/255},
blue = {95/255, 205/255, 228/255},
yellow = {251/255, 242/255, 54/255},
}
love.graphics.setColor(COLORS.red)保留 /255 的写法是为了和调色板文件里的十六进制值一一对照,查色时不用心算。写成 0.85 反而对不上原始色号。
setColor 是全局状态,画完必须显式改回来。Tile:render 先设成深靛色画投影,再设回纯白画本体;漏掉第二句,整张棋盘会一起变暗。同理,PlayState 画选中高亮时切到 add 混合模式,画完要切回 alpha,否则后面所有绘制都会叠亮。
标题的循环配色
StartState 用一个 6 项颜色表加 Timer.every(0.075, ...) 做标题变色。每次触发把整张表往后挪一位,最后一项绕回第一项:
self.colors[0] = self.colors[6]
for i = 6, 1, -1 do
self.colors[i] = self.colors[i - 1]
end借用下标 0 作为中转,避免额外变量。6 个颜色轮转,每个字母按 letterTable 里的偏移量各自错开,画面就有了流动感。这个计时器在离开标题页时必须 :remove() 掉,否则它会跟着进入下一关继续改一张没人看的表。
做像素美术时可以先从 8 色或 16 色起步,画不下再加。四色的练习尤其有用:Game Boy 只有四档明度,所有形状、层次、可读性都得靠这四档撑住,练完再回到 32 色会觉得空间很大。