部落格

他還沒開口,身體先縮了一下

模擬器裡那個孩子的表情、呼吸、視線是怎麼決定的:把「他現在是什麼狀態」跟「這一刻的反應」分開,把一回合拆成幾個時機,以及我們怎麼檢查 AI 做的選擇。

·7 分鐘

你對一個站在教室後面玩球的孩子說:「全班都在等你一個人。」

他還沒回話,肩膀先縮了一下,眼睛往旁邊的同學飄過去。 過了一秒,他才開口:「我又沒怎樣。」

老師在現場讀的,其實大半是那一秒。 嘴上說的「我又沒怎樣」可以有很多意思;縮一下、偷看同學,才告訴你他在意的是什麼。

這篇講模擬器裡那個孩子的「反應」是怎麼設計的。不講細節,講我們怎麼切這件事。

一開始的做法:每回合讓 AI 挑一個表情

最早的版本很直接:老師每說一句話,學生 AI 除了回話,順便挑一個表情—— 生氣、困惑、不耐煩、難過,選一個演出來。

跑一陣子之後,有三個問題一直出現:

  • 一開口就變回撲克臉。 他生氣地瞪你兩秒,然後開始講話,講話那一段是中性的臉。
  • 防衛值 90 跟 40 看起來差不多。 表情是這一回合挑的,不是「他現在整個人的狀態」, 所以一個快爆炸的孩子和一個只是有點煩的孩子,只要這回合挑到同一個表情,看起來就一樣。
  • 沒有連續性。 上一回合還在發抖,下一回合 AI 挑了「困惑」,他就突然平靜下來。 真實的情緒不會這樣切換。

這三個問題其實是同一件事:「他現在是什麼狀態」和「他聽到這句話那一刻的反應」被混成一件事,交給 AI 每回合重新決定。

把兩件事分開

後來的做法是把它拆成兩層。

底下一層是他的情緒狀態:平靜、緊繃、壓著火、爆發、退縮、崩潰、關機。 這一層不由 AI 決定,而是由模擬器本來就在算的數字——他對老師的防衛程度——一路推出來的。 每個狀態有自己的一套樣子:臉、姿勢、呼吸的方式、眼睛看哪裡、會不會發抖。

狀態的變化有幾條規矩,都是照著「真實的情緒」訂的:

  • 升得快,降得慢。 一句重話可以讓他一下子從緊繃跳到爆發; 但要讓他從爆發降下來,要連續好幾句都對,而且一次只降一格。
  • 不會在門口來回。 數字剛好卡在門檻附近上下晃的時候,狀態不會跟著一直切換。

上面一層才是 AI 的工作:聽到這句話的那一刻他怎麼反應(嚇一跳、別過頭、跺腳), 這句話用什麼口氣講,這一回合他看哪裡。

演完這一刻,他會回到底下那一層的樣子。 所以他可以被你一句話激得跺腳,然後回到「壓著火」的狀態繼續瞪著你——而不是回到一張白臉。

兩層之間還有一道過濾:狀態不允許的反應,演不出來。 一個正在崩潰的孩子,AI 就算挑了「大笑」,畫面上也不會出現。 AI 偶爾會挑錯,這道過濾讓挑錯的代價變小。

一回合不是只有「他回話」那一刻

第二個發現是:對話裡學生有反應的時機,比「回話」多很多。

我們把一回合拆成幾段:

  1. 你還沒開口。 你盯著他、想下一句要說什麼。沉默越久,他越不自在——會偷瞄你、開始躁動,或把頭埋得更低。
  2. 你開始打字。 他注意到你要說話了,眼睛轉過來。
  3. 你按下送出的那一瞬間。 他聽到了。這一下最短,也最誠實。
  4. 他在想怎麼回。 AI 正在寫他的回應,畫面上的他也在「想」——壓著火的會咬著牙,退縮的會低著頭。
  5. 他回話,或選擇不回話。 不回話也是一種回應,要演得出「他選擇不理你」,不是「他在專心聽」。
  6. 講完之後停一下。 然後才回到第一段。

第 4 段以前的做法是播一小段固定長度的「聆聽」動畫。AI 比較慢的時候,動畫先播完, 孩子會先回到發呆的樣子,再突然跳進反應——那個斷點比什麼都不做還明顯。 現在「在想」這一段沒有長度,AI 多慢都接得上。

第 3 段最麻煩:送出那一瞬間,AI 還沒開始寫回應,但畫面上的他必須馬上有反應。 所以你還在打字的時候,旁邊有一個小模型在背景先猜:「聽到目前為止的這幾個字,他的第一反應會是什麼。」 你一按送出,他就用猜好的那個反應演出來。

這裡有一個取捨我們想講清楚: 如果你打字的時候他的態度就跟著變,等於在你送出之前就把答案透露給你—— 你可以邊打邊看他的臉色,改到他不皺眉為止。 練習時我們把這當作一種提示留著;獨立操作的那一場(沒有任何鷹架的後測)就關掉。

另外,沉默久了他會不自在,但這只影響畫面,不影響分數。 想清楚再說話不該被懲罰。

能調、能看、能重來

這套東西有很多數字:每個狀態的臉長什麼樣、呼吸多重、多久會開始不自在。 這些數字我們刻意跟程式分開,放在一份可以直接編輯的設定裡, 再做一個調整介面:選一個狀態,就能看到它的臉,一格一格調。

還有兩個工具,是從遊戲開發借來的習慣:

  • 一條看得到內部的除錯列。 畫面旁邊隨時顯示:現在是哪個狀態、哪一段、AI 挑了什麼反應、被過濾掉多少。 之前「看不出來現在是哪一層在動」,大多是因為看不到,而不是因為太複雜。
  • 可以重播的片段。 把一場真實的對話錄下來,每次改完設定就重播同一段,前後比較。 不然每次都要重新演一場,AI 每次回應都不一樣,根本比不出是設定變好了還是運氣變好了。

我們怎麼檢查 AI 做的選擇

AI 挑的反應、語氣、視線,也要檢查。我們做了一組固定的測試: 同一個孩子、四種不同的防衛程度、九種不同的老師說法,每種組合跑好幾次,看它選了什麼。

結果有一些預料之外的事:

  • 語氣主要跟著「老師說了哪種話」走,不太跟著「孩子現在怎麼了」走。 只要老師在要求他做某件事,他幾乎一定回「不要」,語氣就是嘴硬——就算他其實已經不太防衛了。
  • 語氣其實只是台詞的標籤。 AI 先想好台詞,再替台詞貼語氣。台詞在拒絕,語氣就只能是拒絕。 想讓反應更有層次,要改的是台詞本身:這個年紀的孩子拒絕的方式很多——討價還價、轉移話題、裝沒聽到、開玩笑帶過——不是只有「不要」。
  • 有一件事改得很成功:轉折。 老師道歉、給台階、慢慢把他接住的時候, 改寫過的版本會讓他鬆一口氣,眼神也會回到老師身上。舊版本從頭到尾都是同一種口氣。

這些測試會在每次改學生的設定前後各跑一次。 AI 的選擇很容易悄悄塌到少數幾個答案上,不量的話看不出來。

為什麼要花力氣在這一秒

這個模擬器要練的是讀懂一個孩子。

讀懂的前提是他的反應要有因果、有連續性: 你說了什麼,他身體先怎樣,然後慢慢變成什麼狀態;你換個方式,他慢慢鬆下來。 如果他的臉每回合隨機換一張,老師能練到的就只剩下讀台詞—— 而台詞,往往是一個孩子最不誠實的地方。