打開水母實驗室,你就會遇見我們的吉祥物在辦公室裡游動,身後拖著串珠狀的觸手,地板上還有一片彩色光斑。它看起來像一張精靈圖,但其實是在頁面執行時從頭繪製出來的。
鐘形本體的形狀、臉、觸手、像素顆粒感與光暈全都來自一小組參數。每一個參數都以滑桿呈現,因此本文所談的內容,都對應到你能在實驗室裡親自試玩的控制項。
- 鐘形本體是逐列計算出來的。五種鐘形使用五條不同的寬度曲線;一張臉每隻眼睛只需要一到三個像素。
- 整個世界會先渲染到一個尺寸為視窗四分之一的畫布上,再在不做平滑處理的情況下放大回來。
- 光暈來自一張十六分之一比例的光圖,搭配平方衰減、分階的光照等級,以及一個 4×4 抖動矩陣。
這裡的每一張圖都是實驗室的截圖。我們從頭到尾都使用同一隻蜜桃色水母,並套用作者手繪的臉部預設。沒有任何一張是為了這篇文章而假造的。
1. 角色是一個函式
像素藝術角色通常是從影像編輯器開始的。我們的角色則始於一個問題:對於「一隻水母」,最小的描述是什麼,而且還能讓人做出與我不同的版本?
答案原來是一條寬度曲線。鐘形本體是從上到下逐列繪製的,而鐘形、圓頂、團塊、燈籠與水滴之間唯一不同的地方,就是那個說明每一列有多寬的函式。圓頂張開得快,肩部柔和;團塊在中間最寬,兩端收窄;燈籠在最初六分之一處張開,並在最後五分之一處收攏;水滴大約在往下四分之三處達到全寬,然後變窄;而單純的鐘形則是一道筆直的喇叭口。
曲線底下有兩條小規則。「圓潤度」滑桿(1–4)決定頂冠要花幾列才張開。每一列的寬度也都是奇數。那一列中央欄位比聽起來更重要:眼睛和嘴巴可以正好落在正中央,而不是偏半個像素。
從這裡開始,身體其餘部分就順勢而生。寬度、頂部寬度、高度、圓潤度與壓扁程度是五個滑桿。因為形狀是計算出來的而非儲存的,鐘形本體還能變形:當你拖曳它時,你抓住的那一列會比離游標較遠的列傾斜得更多。就是這條規則,讓它感覺像柔軟的橡膠,而不是一張僵硬的卡片。
旋轉的處理方式相同,但方向相反:與其把像素往前旋轉(那會留下空洞),渲染器會走過旋轉後的邊界框,把每個螢幕格子映射回鐘形空間,然後問「這在r列裡面嗎?」。採最近鄰取樣、不做反鋸齒,所以傾斜的水母和直立的一樣銳利。
臉
眼睛位於鐘形本體高度的 54% 處,間距為鐘形本體寬度的 18%。這兩個比例就是整個版面系統。表情接著就只是哪一到三個像素被點亮:一個點、一條短橫、一個^,或是一個往上或往內挪一格的點。好奇是一隻眼睛比另一隻略高。卡住是兩隻眼睛都往鼻子擠。想睡是兩條短橫。暈眩——在 850 毫秒內連續三次急轉時觸發——是兩條寬短橫加上一陣搖晃。
讓臉生動的不是像素,而是它們看往哪裡。眼睛是身體上唯一擁有自己動作規則的部位:它們會朝你拉扯鐘形本體的反方向偏移一個像素,而當你用鍵盤游動時,它們會先於身體轉入轉向。嘴巴只在水母說話或表達情緒時出現;說話時,它會隨著快速的 sine 波開合。這些全都不需要繪製任何一格動畫。
觸手
沒有口腕,也沒有觸手精靈圖。每條觸手都是一串方形珠子,以 Verlet 積分計算,錨定在鐘形本體往下 28% 處,每幀進行三次距離約束迭代,讓珠子維持在它們的「間隙」距離。讓它們感覺身在水下的是三條不同頻率的 sine 波——緩慢的漂移、較快的擺動,以及偏向尾端的捲曲——所以根部保持平靜,而末端則擺盪。把游標移過它們,它們會分開並打旋;停下來,它們會以自己的節奏安定下來。
觸手滑桿(數量 3–12、節段 4–18、重力、摩擦力、張力、水流、捲曲、避讓)直接開放,而四個一鍵「動作預設」——靜止、活潑、狂野、漂移——不過就是那六個數字的四組已儲存組合。
顏色
預設隨附七組三色調色盤——鐘形本體、觸手、眼睛——外加每項的自由選色器:
| 調色盤 | 鐘形本體 | 觸手 | 眼睛 |
|---|---|---|---|
| 海洋 | #8ae2ff | #8ae2ff | #303030 |
| 日落 | #ffb38a | #ff8fc7 | #3a2a2a |
| 青檸 | #b6f5a0 | #8ce6c0 | #26331f |
| 葡萄 | #c7a2ff | #a98cff | #2c2640 |
| 玫瑰 | #ff9ec4 | #ffc0dd | #3a2630 |
| 金色 | #ffe08a | #ffd06a | #3a3320 |
| 單色 | #cfd8e3 | #aab4c2 | #20242b |
鐘罩顏色不只是替鐘罩上色。引擎會把它寫進頁面的強調色變數(並把觸手顏色寫進次要強調色),因此整個實驗室介面都會跟著你正在製作的水母重新換上主題色。黑、白、灰與語意紅永遠不變;其他一切則跟著你走。而且,正如我們會在第三部分看到的,它也會成為你所攜帶之光的顏色。
如果程序化鐘罩還不夠,你可以把它關掉,在 6×6 到 28×28 的格子上自己畫,並在上面畫上最多八張自訂臉孔。我們有十三位隊友正是這麼做的;他們的水母現在以 NPC 的身分住在辦公室場景裡,每一隻都有自己的身體、觸手數量、表情和對白。
2. 像素外觀是一種解析度
在網路上做出「像素外觀」最常見的方法,是以完整解析度渲染再後製——把顏色吸附到固定值、疊上一層網格,也許再加上模糊並重新量化。我們做的正好相反:這個世界實際上非常小。它被畫進一個只有視窗四分之一大小的畫布——在預設縮放下,一個世界格等於四個螢幕像素——然後告訴瀏覽器在不平滑的情況下把它放大。
引擎裡的每一次繪製呼叫,都會把打包好的 32 位元顏色值直接寫進那個小緩衝區——鐘罩、觸手珠、地形磚,甚至文字。世界圖層裡完全沒有任何向量路徑,所以沒有任何東西會意外被反鋸齒。位置在每個錨點都會對齊格線:鐘罩頂端、眼睛那一列、每一塊地形磚、光照格線所用的相機偏移。
這對「像素大小」滑桿有個很好的結果。因為它改變的是一個世界格涵蓋多少螢幕像素(4 × 0.7 到 4 × 1.6),它會一次同時改變畫面的顆粒以及水母的視覺大小。沒有另外的縮放。
兩塊畫布與一層真正的文字
我們特別喜歡的一個結構決定:這個世界是兩塊畫布,中間夾著一層 HTML。房間地板與標題畫在底層畫布上。住在辦公室裡的部落格風格文案,則是疊在其上的普通、可選取、無障礙的 HTML。頂層畫布——水波紋、物品、傳送門、水母本身——讓每個未觸及的格子保持透明,所以文字能穿過空無一物的水被讀到,而水母則游過其上。
結果是文字感覺沉進場景裡,而不是貼在上面。背景畫布也會被快取:只有當相機移動整整一格或場景改變時,它才會重繪。
用抖動,不用漸層
當畫面需要漸層時——主要是在光照上——我們使用有序抖動,而不是平滑插值。一個 4×4 的 Bayer 矩陣,以世界座標為索引,讓圖案在你捲動時保持不動,把光拆成平坦的色帶,然後把接縫打散。「光照階數」以滑桿呈現(2–8,預設 3)。在 2 時,地板看起來像雙色木刻版畫;在 8 時,它接近平滑。3 則是它看起來仍像手工製作的樣子。
色彩分級——全域色調與飽和度調整——會經過一張 32×32×32 的查找表,這會順帶把調色盤悄悄量化成每通道五位元。介面裡的每個預覽畫布都關閉平滑;引擎唯一使用 CSS 模糊的地方是介面轉場,從不用在世界本身。
3. 光暈是一個光場
不會發光的水母就不是水母。顯而易見的實作方式——在場景上混合一層柔和的放射狀光暈,或做一次模糊處理——第一天就被排除了,因為它會產生平滑、次像素的漸層,在硬像素格線上顯得很不對。平滑的光暈會浮在世界之上;它不屬於這個世界。
所以我們不畫光暈,而是照亮房間。每一盞光——水母、檯燈、發光物品——都會被蓋進一張小光照圖,解析度是畫布的四分之一(螢幕的十六分之一),並帶有(1 − d/r)²衰減:不是物理上正確,而是柔和、便宜且容易調整。地板與家具接著會依照有多少光到達它們而被提亮,用的是同樣三個平坦階數與同樣的抖動。我們保留兩張圖——一張給地板,一張給物件,半徑寬 1.6 倍,所以道具會落在比其下方地面稍大的光暈裡。
著色處理會把每個像素乘以環境光加上光場,並限制在一個上限。那個上限——1.25,意思是光可以把顏色推到自己本身的 125%,不能再多——就是全部的「泛光」預算。光暈核心稍微過曝就是從這裡來的,也是燈籠與鏡頭光斑之間的差別。
有三個實作細節很容易被忽略:
- 鐘罩顏色也是光的顏色。挑一隻玫瑰色水母,附近地板就會轉為玫瑰色;同一個十六進位值也會更新實驗室的強調色。
- 顏色會以其峰值通道正規化,所以深藏青色的鐘罩仍然會投出全強度的藏青色調,而不是幾乎照不亮房間。
- 閃爍每 8 幀更新一次。輕微的頓挫比完美平滑的微光更適合這個像素世界。
還有一條規則讓檯燈不會看起來不對:任何會發光的東西都被排除在受光之外。帶有發光元件的物品會以自發光遮罩繪製,著色處理會原封不動地複製那些像素——所以檯燈永遠不會被自己的環境光變暗。
旋鈕
所有這些都暴露在「場景 → 光照」面板中。這些是我們最後出貨的數字:
| 控制項 | 範圍 | 預設值 |
|---|---|---|
| 環境亮度 | 0.2 – 1 | 0.55 |
| 環境顏色 | — | #6d8fae |
| 光照階數 | 2 – 8 | 3 |
| 高光上限 | 1 – 1.5 | 1.25 |
| 水母光暈 · 半徑 | 8 – 80 | 32 |
| 水母光暈 · 強度 | 0 – 1.5 | 0.6 |
| 水母光暈 · 閃爍 | 0 – 0.6 | 0.05 |
地板只有在關卡、著色器參數或光源位置改變時才會重新著色;視埠外的光源在蓋印前會被剔除;分級表會被快取。實務上,整個世界——背景、水體模擬、物品、觸手、光照、水母——在筆記型電腦上能輕鬆塞進單一動畫幀,而且在一個 1440px 寬的視窗只有 360 格寬的解析度下。
4. 關於它如何移動的幾件事
嚴格說來不是關於外觀,但它們塑造了角色的讀感,所以簡短說明:
- 鍵盤推力會累積並衰減,上限略低於每幀一格。速度也會影響鐘罩變形,所以快速游動時會明顯看到它被「水流」壓扁。
- 滾輪一格會把衝量送進與鍵盤相同的速度中,換算下來等於一格的穩態移動距離。
- 相機會在你游動時緩慢跟隨,穩定下來時跟得更快,而在拖曳後會非常緩慢地跟隨 1.6 秒,以免它猛然彈回。
- 水平速度會設定一個目標傾斜角。一條僵硬、有阻尼的彈簧會跟隨它,並讓鐘罩在停止時稍微過衝。
- 游標會在 40 px 內把觸手珠推開。在 58 px 內,它還會加上漩渦與放射狀脈動,並偏向尾部。
- 每移動兩格,就會在 2D 波浪模擬中加入一個柔和的脈衝。這些波浪可以觸發道具,而道具又能把脈衝繼續傳遞下去。
5. 為什麼要用資料來製作角色
我們並不是從一套關於 AI 角色的理論開始的。我們從一個水母編輯器開始,然後注意到一個實際的優勢:讓角色可自訂的那些參數,同時也讓它對 agent 而言易於理解。
參數對 agent 來說更容易使用
精靈圖對模型來說是不透明的。我們的水母大約是三十個數字和幾個列舉值,小到足以放進一個提示詞、一個 JSON blob 或一次工具呼叫。
- agent 可以讀取參數,知道它的使用者那隻水母長什麼樣子。
- 它可以把「幫我做一隻有長觸手的愛睏紫色水母」變成一個預設組合,而不必生成一張新圖像。每位隊友的 NPC 都是一列資料,而不是一項美術資產。
- 表情、發光和動作都是執行時期的狀態,所以吉祥物可以在 agent 工作時看起來專注,或在它搞混時看起來頭暈。
- 這些參數可以有很寬的變化範圍,而結果仍然看起來像是同一種水母。
這就是對 AirJelly 有用的部分。吉祥物可以扮演 agent 的身體,因為它的外觀和行為都是 agent 可以讀取和更改的資料。我們永遠不必要求模型去編輯一張不透明的圖像。
為什麼我們仍然喜歡像素(和 ASCII)
有一個實際的理由。像素精靈是一小格一小格的調色盤索引;ASCII 藝術本身就已經是文字。兩者都可以被精簡地描述,並逐格編輯。對於我們需要的精確修改,16×16 的精靈圖留給模型即興發揮的空間,就是比一幅畫作來得少。
還有一個更主觀的理由:我們喜歡硬性的網格。它們坦率地承認自己是由機器製作的,而一個像素的錯誤往往看起來很討喜,而不是詭異。這種調性很適合一個關於 AI 住在你電腦上的產品。這或許也是為什麼終端機在 AI 工具裡讓人感到自在。
所以這就是我們最後定案的地方:一個由數字描述、畫在網格上的角色。它剛好看起來很復古,但真正有用的部分,是它對人和 agent 來說都很容易精確地修改。
最後一件事。如果你在實驗室裡調出一隻你喜歡的水母,把它的參數匯出並寄到xiaohua@airjelly.ai——我們會把它放上網站。辦公室裡已經住著十三隻了;永遠還有空間再多一隻。


























