• 简体中文
  • 状态

    状态分四组,下面全部是实时运行的,共用同一个舞台。在默认的 cute 脸下,每个状态有自己的眼型、嘴型和腮红,部分还带着特效(见表情);在线条脸模式下,每个状态会戴上 STATE_MOUTH 里对应的嘴形。

    静息(Resting)

    idle空闲
    sleeping睡眠
    drowsy困倦
    bored无聊
    waking唤醒
    powering-down休眠中

    处理中(Attending)

    listening聆听
    thinking思考
    searching搜索
    reading阅读
    writing书写
    working工作
    loading加载
    progress进度
    radar扫描
    orbit环绕
    dictating听写
    uploading上传
    sending发送
    receiving接收
    messaging消息
    waiting等待
    spawning启动
    composing编排
    dragging拖动

    情绪(Feeling)

    happy开心
    excited兴奋
    laughing大笑
    playful顽皮
    proud骄傲
    celebrate庆祝
    bouncing弹跳
    shy害羞
    sad难过
    angry生气
    confused困惑
    suspicious怀疑
    surprised惊讶
    scared害怕
    curious好奇
    humming哼歌

    提示(Signalling)

    alerting警示
    notifying通知
    refusing拒绝

    一个状态定义了什么

    表作用
    STATE_CUTE_EYEScute 脸要轮播的眼型序列
    STATE_CUTE_MOUTHcute 脸的嘴形
    STATE_BLUSHcute 脸的腮红强度
    STATE_FX身体之外的情绪或活动特效
    STATE_EYE_TOPOLOGIESeyes / line 脸的表情序列
    STATE_EYE_SWITCH_MS两次切换之间的随机间隔
    STATE_BLINK_MS眨眼间隔,null 表示不眨
    STATE_FACE_TUNE叠加在默认值上的面部微调
    STATE_GAZE凝视漂移的幅度与重新取点的频率
    STATE_BELTeyes / line 脸的腰带覆盖动效
    STATE_MOUTH线条脸模式下戴的嘴形
    STATE_ENTRY_EFFECTS进入时的一次旋转、弹跳或撒花
    behaviour每帧返回身体目标值的函数

    身体运动

    一个 behaviour 每帧返回四个数——tilt、x、y、squash——由弹簧去追。因为平滑交给弹簧处理,behaviour 可以让目标值直接跳变而不会产生可见的突兀。

    idle 最值得读:

    ({ clock }) => ({
      tilt: Math.sin(clock * 0.5) * 1.5 + Math.sin(clock * 0.17) * 0.6,
      x: Math.sin(clock * 0.27),
      y: Math.sin(clock * 0.85) * 1.2,
      squash: 1 + Math.sin(clock * 0.85) * 0.007,
    });

    三个频率互不相关的正弦波。由于周期永远对不齐,这段运动不存在可感知的循环——这正是一个角色在侧边栏里待十分钟仍然像活的、而不像一段循环 GIF 的原因。

    脉冲

    有些状态需要的是离散事件而非连续曲线。angry 每 1.8 到 3.2 秒踢一次竖直方向的弹簧,listening 会点头。它们直接给速度加值,而不是移动目标:

    impulse(70); // 加到 y 弹簧的速度上

    弹簧吸收这一下并自行收敛,产生的回弹感如果要用曲线手写会非常繁琐。

    弹簧通道

    所有连续量都是阻尼弹簧,以固定 120 Hz 子步积分,与显示器刷新率无关。

    通道ωζ驱动
    tilt50.9身体旋转,乘以 tiltScale
    x3.51水平漂移
    y41垂直漂移与脉冲
    squash100.8垂直缩放
    blink261眼睛开合
    eyeScale90.85强调
    gazeX / gazeY131凝视偏移
    eyeMorph81表情过渡
    spin6.21命令式转身,绕竖直轴
    cuteBlend101cute 脸与其他脸之间的过渡
    blush81腮红强度
    fx71特效淡入
    beltBlend141身体到腰带的过渡
    beltFade111两种腰带之间的交叉淡化
    hudBlend121HUD 表盘的淡入淡出
    badge90.55通知点

    固定子步正是 144 Hz 与 60 Hz 显示器收敛到同一结果的原因。若直接用帧间隔积分,角色在高刷屏上会收敛得更快。

    腰带状态在 cute 脸下的样子

    在 eyes 和 line 脸下,有 16 个状态把身体交给腰带覆盖动效(STATE_BELT):身体先形变成圆,再收缩成核心,含义由围绕它运动的元素来说。在默认的 cute 脸下这样做会丢掉脸,所以它们不收缩身体:整个角色连同脸都留着,同样的含义改由身旁的特效来说(STATE_FX)——thinking 是升起的点,progress 是右上角逐渐填满的小进度条,writing 是一支铅笔。powering-down 让身体变暗,bouncing 让身体自己弹跳。

    sleeping睡眠zz
    drowsy困倦zz
    thinking思考dots
    writing书写pencil
    working工作arc
    loading加载spinner
    progress进度progress
    radar扫描ripple
    orbit环绕orbit
    dictating听写waves
    uploading上传rise
    sending发送send
    receiving接收fall
    messaging消息bubbles
    waiting等待ellipsis
    spawning启动gather
    happy开心heart
    proud骄傲sparkle
    celebrate庆祝sparkle
    sad难过tear
    surprised惊讶strokes
    scared害怕strokes
    alerting警示bang
    refusing拒绝bang

    特效和情绪特效一样是时间的纯函数,随快照的 fx 字段交给渲染器,向量、像素、颜文字三个系列自动一致。cute 脸下 snapshot.belt 恒为 null。

    腰带

    eyes 和 line 脸用腰带画这些状态:身体被收成核心,腰带接管。过渡如何发生、每条腰带如何绘制,见腰带与粒子。下面的演示用 face="eyes" 让腰带本身可见。

    dotsthinking
    orbitorbit
    orbitmessaging
    orbitwaiting
    radarradar
    progressprogress
    gatherspawning
    wavedictating
    sendsending
    receivereceiving
    dockuploading
    ballbouncing
    whirlloading
    standbypowering-down
    pencilwriting
    bangalerting
    import { MarkStageProvider, StagedMark, STATE_BELT } from '@aora-bot/react';
    
    export default function Demo() {
      const entries = Object.entries(STATE_BELT) as [string, string][];
      return (
        <MarkStageProvider maxSources={entries.length}>
          <div className="site-demo">
            {entries.map(([state, belt]) => (
              <div className="site-demo__cell" key={state}>
                <StagedMark shape="mochi" state={state as never} color="blue" face="eyes" sizePx={62} />
                <span className="site-demo__label">{belt}</span>
                <span className="site-demo__label" style={{ opacity: 0.6 }}>
                  {state}
                </span>
              </div>
            ))}
          </div>
        </MarkStageProvider>
      );
    }

    腰带是时间的纯函数,内部不累积任何状态,因此可以在任意时刻暂停、拖动、恢复而依然正确——这正是能冻结上百个角色、恢复时又不失同步的原因。

    总表

    所有状态放在一张表里:轮播的眼型序列、嘴形、特效(cute 脸)或交出身体的腰带(eyes / line 脸)、能到达它的协议编号,以及一个通常与之搭配的 HUD 表盘。除最后一列外,每一列都在渲染时从引擎的表里读出;HUD 一列只是建议——表盘是宿主手里的数据,状态是智能体正在做的事,引擎有意让两者彼此独立。

    静息

    状态眼睛嘴特效编号HUD
    idle空闲dot → dot → dot → dotLeft → dot → dotRightnone—02—
    sleeping睡眠curvenonezz00clock
    drowsy困倦curveSleepy → dotHalf → curveSleepynonezz0615—
    bored无聊dotHalf → dotLeft → dotHalf → dotRightflat—04—
    waking唤醒curve → dotHalf → dotdot—0107—
    powering-down休眠中dot → dotHalf → curvenone—41—

    处理中

    状态眼睛嘴特效编号HUD
    listening聆听dotLeft → dot → dotLeftnone—3537—
    thinking思考dotLook → dotUp → dotLookflatdots30context
    searching搜索dotLeft → dotRight → dotUp → dotdot—40—
    reading阅读dotLeft → dotRightnone—42—
    writing书写dotDown → dotnonepencil43—
    working工作dotDown → dot → dotDownnonearc1632gauge
    loading加载dot → dotLooknonespinner0536—
    progress进度dotnoneprogress47progress
    radar扫描dot → dotLeft → dotRightnoneripple51—
    orbit环绕dot → dotLooknoneorbit49—
    dictating听写dotLeft → dotowaves46voice
    uploading上传dotUp → dotnonerise45sync
    sending发送dot → archsmilesend44—
    receiving接收dotWide → dotdotfall31—
    messaging消息dot → archcatbubbles52notify
    waiting等待dot → dotLeft → dot → dotRightnoneellipsis48timer
    spawning启动dotWide → dotdotgather08—
    composing编排dotDown → dotcat—39—
    dragging拖动dotRight → dot → dotRighto—09—

    情绪

    状态眼睛嘴特效编号HUD
    happy开心arch → archWide → archcatheart10—
    excited兴奋dotWide → archWidegrin—22—
    laughing大笑archWide → archlaugh—23—
    playful顽皮arch → wink → archWide → dotsmirk—24—
    proud骄傲dotUp → archsmilesparkle19—
    celebrate庆祝star → archWide → wink → archgrinsparkle2533kpi
    bouncing弹跳arch → dotWidesmile—26—
    shy害羞dotDown → dotHalfcat—14—
    sad难过sadfrowntear12—
    angry生气madfrown—21—
    confused困惑dotSuspicious → dotLook → dotHalfwavy—1120—
    suspicious怀疑dotSuspicious → dotHalfflat—18—
    surprised惊讶dotWideostrokes13—
    scared害怕dotWide → dotSmallwavystrokes17—
    curious好奇dotWide → dotLeft → dotWide → dotRightdot—03—
    humming哼歌curve → dotHalf → curve → dotsmile—27—

    提示

    状态眼睛嘴特效编号HUD
    alerting警示dotWide → dotobang34—
    notifying通知dotWide → arch → dotdot—50notify
    refusing拒绝cross → dotHalfwavybang38—

    映射应用状态

    大多数应用不会用这些词思考。两个辅助函数负责翻译。

    工具状态
    searchsearching
    web_searchsearching
    grepsearching
    globsearching
    browsereading
    fetchreading
    read_filereading
    readreading
    write_filewriting
    writewriting
    editwriting
    str_replacewriting
    runworking
    shellworking
    bashworking
    terminalworking
    uploaduploading
    thinkthinking
    planthinking
    asklistening
    waitwaiting
    { awaitingUser: true }waiting
    { running: true }working
    import { characterStateForAgent, activityFromTool, TOOL_ACTIVITIES } from '@aora-bot/react';
    
    export default function Demo() {
      return (
        <table>
          <thead>
            <tr>
              <th>工具</th>
              <th>状态</th>
            </tr>
          </thead>
          <tbody>
            {Object.keys(TOOL_ACTIVITIES).map((tool) => (
              <tr key={tool}>
                <td>
                  <code>{tool}</code>
                </td>
                <td>
                  <code>{activityFromTool(tool).state}</code>
                </td>
              </tr>
            ))}
            <tr>
              <td>
                <code>{'{ awaitingUser: true }'}</code>
              </td>
              <td>
                <code>{characterStateForAgent({ awaitingUser: true })}</code>
              </td>
            </tr>
            <tr>
              <td>
                <code>{'{ running: true }'}</code>
              </td>
              <td>
                <code>{characterStateForAgent({ running: true })}</code>
              </td>
            </tr>
          </tbody>
        </table>
      );
    }

    接下来