> For the complete documentation index, see [llms.txt](https://docs.convai.com/api-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/embodiment/emotion/scripting-api.md).

# Emotion 脚本 API

说明如何从脚本中读取角色的情绪状态、控制情绪、锁定或覆盖表情，以及订阅情绪事件。

Emotion 系统在运行时提供两条路径用于响应和控制情绪状态。 **检查器路径** 使用 `ConvaiCharacterEventRelay` ——一个将原始情绪回调暴露为 Unity 事件的组件，无需编写代码。 **脚本路径** 使用 `ConvaiEmotionController` 直接使用，公开完整的 C# API，用于读取组合状态、控制心情、注入覆盖以及锁定表情。两条路径可同时使用。关于瞬时情绪与静息心情的概念区别，参见 [心情](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/embodiment/emotion/moods.md).

### 检查器路径 — ConvaiCharacterEventRelay

`ConvaiCharacterEventRelay` 是一个 MonoBehaviour，它将角色回调桥接到 Unity 事件，使设计师无需编写任何代码即可完全在检查器中配置情绪响应。

**添加组件：** **Convai → Events → Convai Character Event Relay**

将其放到场景中的任意 GameObject 上。它会自动查找 `ConvaiCharacter` 在同一个 GameObject 上的组件，或者你也可以通过 **角色** 字段中。

#### Inspector 字段

| 字段                       | 默认    | 说明                                                 |
| ------------------------ | ----- | -------------------------------------------------- |
| `角色`                     | *（无）* | 指向一个 `ConvaiCharacter`的可选显式引用。留空以使用自动解析。           |
| `Auto Resolve Character` | `是`   | 启用后，中继会自动查找一个 `ConvaiCharacter` 在同一个 GameObject 上。 |

#### OnEmotionChanged 事件

该中继公开一个 **On Emotion Changed** Unity 事件，每当 Convai 发送原始情绪信号时都会触发。该事件会传递一个 `CharacterEmotionRelayData` 负载：

| 属性              | 类型    | 说明                          |
| --------------- | ----- | --------------------------- |
| `角色 ID`         | `字符串` | 角色的唯一标识符。                   |
| `CharacterName` | `字符串` | 角色的显示名称（回退为 GameObject 名称）。 |
| `情绪`            | `字符串` | 原始服务器标签（例如 `"快乐"`).         |
| `强度`            | `整数`  | 由 Convai 发送的 1–3 整数等级。      |

**示例绑定：** 添加一个 `ConvaiCharacterEventRelay` 到你的 NPC 的 GameObject 上。在 **On Emotion Changed** 列表中，单击 **+**，将一个 UI Text 组件拖到对象字段中，然后选择 `Text.text` ——标签会在每次情绪变化时自动更新。

`ConvaiCharacterEventRelay` 会在原始服务器标签上触发，发生在分类法解析或平滑处理之前。可将其用于 UI 显示、音频提示或简单分支逻辑。若要获取带分数和保持时间的平滑后解析状态，请使用 `ConvaiEmotionController.Current` 并从脚本中读取。

### 从脚本访问控制器

Retrieve `ConvaiEmotionController` 按其具体类型访问——它所实现的跨模块契约（`IEmotionStateSource` 以及相关接口）是 SDK 内部实现，不属于公共 API。

```csharp
using Convai.Modules.Emotion.Components;
using UnityEngine;

public sealed class EmotionDrivenBehavior : MonoBehaviour
{
    [SerializeField] private ConvaiEmotionController emotionController;

    private void Awake()
    {
        if (emotionController == null)
            emotionController = GetComponentInChildren<ConvaiEmotionController>();
    }
}
```

### 读取当前情绪状态

`ConvaiEmotionController.Current` 返回一个 `EmotionReading` ——仅在组合状态发生变化时才重建的不可变快照。可在 `更新` 中轮询它，或通过任意事件对其作出响应。

```csharp
using Convai.Domain.Embodiment.Readings;
using Convai.Modules.Emotion.Components;
using UnityEngine;

public sealed class EmotionLogger : MonoBehaviour
{
    [SerializeField] private ConvaiEmotionController emotionController;

    private void Update()
    {
        EmotionReading reading = emotionController.Current;

        if (!reading.IsNeutral)
            Debug.Log($"主导情绪：{reading.DominantLabel} ({reading.DominantScore:F2})");
    }
}
```

#### EmotionReading 属性和方法

| 成员                                                     | 类型                                   | 说明                                                                         |
| ------------------------------------------------------ | ------------------------------------ | -------------------------------------------------------------------------- |
| `DominantLabel`                                        | `字符串`                                | 得分最高的瞬时情绪的规范标签（例如 `“joy”`, `"anger"`).                                     |
| `DominantScore`                                        | `float`                              | 在平滑和突发处理后，主导瞬时情绪的归一化分数 \[0–1]。                                             |
| `AllScores`                                            | `IReadOnlyDictionary<string, float>` | 按规范标签键控的完整分数字典。分类法中的每种情绪都有一个条目；本帧没有贡献的情绪得分为 `0`.                           |
| `MouthInfluence`                                       | `float`                              | \[0–1] 提示，被 LipSync 合成器在非说话帧期间用于混合口型。                                      |
| `DominantHoldSeconds`                                  | `float`                              | 当前主导标签连续保持的实际秒数。                                                           |
| `MoodLabel`                                            | `字符串`                                | 角色已解析的静息心情的规范标签——不同于 `DominantLabel`。请参见 [运行时心情控制](#runtime-mood-control). |
| `MoodScore`                                            | `float`                              | 的归一化 \[0–1] 强度 `MoodLabel`.                                                |
| `IsNeutral`                                            | `布尔值`                                | `是` 当主导标签是 `"neutral"` 或当 `DominantScore ≤ 0`.                             |
| `NeutralLabel`                                         | `const string`                       | 字符串常量 `"neutral"`.                                                         |
| `GetScore(string canonicalLabel)`                      | `float`                              | 返回给定规范标签的平滑分数，若不存在则返回 `0` 。                                                |
| `CopyScoresTo(IDictionary<string, float> destination)` | `void`                               | 将完整分数表复制到调用方拥有的字典中。复制前会清空目标字典。                                             |

#### CurrentFrame — 零分配帧视图

`ConvaiEmotionController.CurrentFrame` 返回一个 `EmotionStateFrame` ——对同一组合状态的借用式、零分配视图，在控制器下一次 tick 之前有效。若在不需要调用方拥有副本分配的逐帧热点路径中使用，它比 `Current` 更适合。

| 成员                                                        | 类型                                              | 说明                                                             |
| --------------------------------------------------------- | ----------------------------------------------- | -------------------------------------------------------------- |
| `版本`                                                      | `整数`                                            | 每当帧内容变化时递增。                                                    |
| `DominantLabel`, `DominantScore`                          | `字符串`, `float`                                  | 与……上的含义相同 `EmotionReading`.                                    |
| `标签`, `分数`                                                | `IReadOnlyList<string>`, `IReadOnlyList<float>` | 按索引对齐的标签/分数列表，涵盖分类法中的每种情绪，由控制器持有。                              |
| `MoodLabel`, `MoodScore`                                  | `字符串`, `float`                                  | 与……上的含义相同 `EmotionReading`.                                    |
| `维度`                                                      | `EmotionDimensions`                             | 主导情绪的 `效价`/`唤醒度`/`能动性`/`趋近` 信号。参见 [情绪维度](#emotion-dimensions). |
| `MouthInfluence`, `DominantHoldSeconds`                   | `float`                                         | 与……上的含义相同 `EmotionReading`.                                    |
| `IsNeutral`                                               | `布尔值`                                           | 与……上的含义相同 `EmotionReading`.                                    |
| `GetScore(int index)` / `GetScore(string canonicalLabel)` | `float`                                         | 按索引在 `标签`/`分数`中查找分数，或者按规范标签查找。                                 |

### 已解析状态和心情

| 成员                                                                | 类型                      | 说明                                                                                                                                                            |
| ----------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `CurrentResolvedEmotion`                                          | `字符串`                   | 在分类法解析、平滑以及配置文件组合之后的规范标签——等同于 `Current.DominantLabel`.                                                                                                        |
| `CurrentNormalizedIntensity`                                      | `float`                 | 的组合后归一化强度 \[0, 1]。 `CurrentResolvedEmotion`.                                                                                                                  |
| `CurrentMoodLabel`                                                | `字符串`                   | 角色人格/气质静息心情的规范标签。明确地 **不** 瞬时主导情绪。                                                                                                                            |
| `CurrentMoodScore`                                                | `float`                 | 的归一化 \[0, 1] 强度 `CurrentMoodLabel`.                                                                                                                           |
| `KnownEmotionLabels`                                              | `IReadOnlyList<string>` | 该角色当前分类法识别出的非中性规范标签，按分类法编排顺序排列。流水线构建前为空。                                                                                                                      |
| `TryResolveEmotionLabel(string label, out string canonicalLabel)` | `布尔值`                   | 解析 `label` 针对当前分类法（规范标签和别名）解析，返回规范的非中性标签。返回 `否` 适用于空标签、无法解析的标签、解析为分类法中性条目的标签，或在流水线构建之前的情况。在调用 `SetMood`/`SetEmotionOverride`之前请先用它验证标签，否则未知标签会被静默降级为中性，而不是失败。 |

```csharp
if (emotionController.TryResolveEmotionLabel(userSuppliedLabel, out string canonicalLabel))
    emotionController.SetMood(canonicalLabel, 0.6f);
else
    Debug.LogWarning($"'{userSuppliedLabel}' 不在该角色的情绪词汇表中。")
```

### 创作时锁定

控制器有三个序列化字段，可直接在检查器中将表情固定为某种特定情绪——在创作和调试期间很有用，或者在不进入播放模式的情况下在场景视图中预览表情结果。

| 字段                   | 类型      | 默认          | 说明                                 |
| -------------------- | ------- | ----------- | ---------------------------------- |
| `lockEmotion`        | `布尔值`   | `否`         | 启用后，所有传入的服务器情绪事件都会被忽略，角色会保持锁定的情绪。  |
| `lockedEmotionLabel` | `字符串`   | `"neutral"` | 在 `lockEmotion` 处于活动状态时保持的分类法规范标签。 |
| `lockedIntensity`    | `float` | `1.0`       | 锁定情绪的强度 \[0–1]。                    |

`ConvaiEmotionController` 继承自 `[ExecuteAlways]` 其基类，因此在检查器中设置 `lockEmotion = true` 会立即在场景视图中更新表情，无需进入播放模式。

{% hint style="danger" %}
`lockEmotion` 是一个 **序列化字段** ——其值会与场景或预制体一起保存。如果你一直保持它启用却忘记重置，那么角色在正式构建中会静默忽略所有实时情绪信号，不会有运行时错误或警告。构建前务必将其禁用。
{% endhint %}

### SetEmotionOverride 和 ClearEmotionOverride

`SetEmotionOverride` 在 Convai 发送的内容之上，向累加器注入一个额外的瞬时分数。覆盖仍会受到平滑处理——它会以 `lerpSpeed`的速度混入，而不是立即生效。当应用逻辑需要根据场景内事件放大或引导瞬时情绪时使用它。

```csharp
using Convai.Modules.Emotion.Components;
using UnityEngine;

public sealed class HazardZoneTrigger : MonoBehaviour
{
    [SerializeField] private ConvaiEmotionController emotionController;

    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Trainee"))
            emotionController.SetEmotionOverride("fear", 0.9f);
    }

    private void OnTriggerExit(Collider other)
    {
        if (other.CompareTag("Trainee"))
            emotionController.ClearEmotionOverride();
    }
}
```

`ClearEmotionOverride` 移除覆盖，并将累加器恢复为由服务器驱动的状态。回退过渡会被平滑处理。

### LockEmotion 和 UnlockEmotion

`LockEmotion` 完全绕过累加器，直接将角色切换到某个特定表情并保持其不变，无论 Convai 发送什么。当脚本化序列需要一个有保证、稳定的表情时使用它。

```csharp
using Convai.Modules.Emotion.Components;
using UnityEngine;

public sealed class WelcomeSequenceController : MonoBehaviour
{
    [SerializeField] private ConvaiEmotionController emotionController;

    public void BeginWelcome()
    {
        emotionController.LockEmotion("joy", 0.75f);
    }

    public void EndWelcome()
    {
        emotionController.UnlockEmotion();
    }
}
```

`UnlockEmotion` 释放锁定并恢复锁定前处于活动状态的目标——若有活动的 `SetEmotionOverride` 则恢复它，否则恢复为中性。累加器会继续响应服务器事件。

**API 签名：**

```csharp
void LockEmotion(string label, float intensity = 1f);
void UnlockEmotion();
void SetEmotionOverride(string label, float score);
void ClearEmotionOverride();
```

### 运行时心情控制

`SetMood` 和 `ClearMood` 更改角色的 **静息心情** 在运行时——即面部在瞬时情绪之间回落到的心情，与 `SetEmotionOverride`的瞬时通道不同。两者都会平滑交叉淡入淡出，而不是突然切换。关于心情的概念模型及其相对于配置文件人格基线的优先级，参见 [心情](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/embodiment/emotion/moods.md).

```csharp
using Convai.Modules.Emotion.Components;
using UnityEngine;

public sealed class MoodDirector : MonoBehaviour
{
    [SerializeField] private ConvaiEmotionController emotionController;

    public void OnGoodNewsDelivered()
    {
        // 在 2 秒内平滑进入更愉快的静息心情。
        emotionController.SetMood("joy", 0.5f, transitionSeconds: 2f);
    }

    public void OnSceneReset()
    {
        // 使用默认的 1.5 秒过渡返回到作者设定的基线。
        emotionController.ClearMood();
    }
}
```

**API 签名：**

```csharp
void SetMood(string label, float intensity, float transitionSeconds = 1.5f);
void ClearMood(float transitionSeconds = 1.5f);
```

* `SetMood` 会解析为 `label` 通过角色的活动分类法。空标签或中性标签、无法识别的标签（每个标签只记录一次警告，然后回退），或非正的 `intensity` 都会过渡到“无心情”，而不是抛出异常。
* `ClearMood` 回退到作者设定的基线——该角色自身设置的静息心情覆盖，如果未设置则回退到配置文件的人格基线——不一定回退到零。
* 在流水线构建之前（例如在禁用的组件上）二者都安全地无操作，且永不抛出异常。
* 会话重置（断开连接或错误）总会丢弃运行时 `SetMood` 覆盖，并恢复到作者设定的基线，与 `LockEmotion`.

### 已解析情绪和心情事件

两个具备迟滞感知的事件让游戏代码无需重新实现平滑逻辑，就能对角色实际表达的内容作出响应：

```csharp
public event Action<string, float> DominantEmotionChanged;
public event Action<string, float> MoodChanged;
```

```csharp
using Convai.Modules.Emotion.Components;
using UnityEngine;

public sealed class ExpressionListener : MonoBehaviour
{
    [SerializeField] private ConvaiEmotionController emotionController;

    private void OnEnable()
    {
        emotionController.DominantEmotionChanged += HandleDominantEmotionChanged;
        emotionController.MoodChanged += HandleMoodChanged;
    }

    private void OnDisable()
    {
        emotionController.DominantEmotionChanged -= HandleDominantEmotionChanged;
        emotionController.MoodChanged -= HandleMoodChanged;
    }

    private void HandleDominantEmotionChanged(string label, float score) =>
        Debug.Log($"表达的情绪变为 {label} @ {score:F2}");

    private void HandleMoodChanged(string label, float score) =>
        Debug.Log($"静息心情变为 {label} @ {score:F2}");
}
```

* `DominantEmotionChanged` 在平滑后的主导（瞬时）情绪标签—— `CurrentResolvedEmotion` ——发生变化时触发，携带新标签及其 `CurrentNormalizedIntensity`.
* `MoodChanged` 在 `CurrentMoodLabel` 发生变化时触发，携带新标签及其 `CurrentMoodScore`。它涵盖所有会改变心情的来源：作者设定的基线首次生效、 `SetMood`/`ClearMood`以及心情漂移接管或释放。
* 二者仅在标签转换时触发——标签持续存在时不会每个 tick 都触发，也不会在标签不变而仅分数变化时触发。
* 二者都会在 `Current`/`CurrentMoodLabel` 已更新之后触发，因此处理程序始终观察到一致的状态。在流水线尚未构建或正在拆卸期间都不会触发，而订阅者抛出的异常会被捕获并记录，而不会破坏 tick。
* 这与 `ConvaiManager.Events.OnCharacterEmotionChanged` 不同，后者是在接收到后端原始数据包时就进行转发，在平滑、迟滞或人格基线应用之前。

### 订阅原始情绪事件

若要响应 Convai 发送的每个原始情绪信号——用于日志、分析或自适应场景逻辑——请订阅 `OnCharacterEmotionChanged` 时 `ConvaiManager.Events`。这是一个标准的 C# 事件；请在 `OnEnable` 并在 `OnDisable`.

```csharp
using Convai.Domain.DomainEvents.Runtime;
using Convai.Runtime.Components;
using UnityEngine;

public sealed class EmotionEventListener : MonoBehaviour
{
    [SerializeField] private ConvaiManager convaiManager;

    private void OnEnable()
    {
        convaiManager.Events.OnCharacterEmotionChanged += HandleEmotionChanged;
    }

    private void OnDisable()
    {
        convaiManager.Events.OnCharacterEmotionChanged -= HandleEmotionChanged;
    }

    private void HandleEmotionChanged(CharacterEmotionChanged e)
    {
        Debug.Log($"[{e.CharacterId}] {e.Emotion} — 强度 {e.Intensity} ({e.NormalizedIntensity:F2})");
    }
}
```

#### CharacterEmotionChanged 属性

| 属性                     | 类型         | 说明                                                                       |
| ---------------------- | ---------- | ------------------------------------------------------------------------ |
| `角色 ID`                | `字符串`      | 其情绪发生变化的角色的唯一标识符。                                                        |
| `情绪`                   | `字符串`      | 原始服务器标签（例如 `"快乐"`，而不是规范化的 `“joy”`).                                      |
| `强度`                   | `整数`       | 由 Convai 发送的 1–3 整数等级（已钳制）。                                              |
| `NormalizedIntensity`  | `float`    | `Intensity / 3f` ——将 1–3 等级映射到 `(0, 1]`。微弱（等级 1）的信号仍会映射到 `0.33`，而不是 `0`. |
| `时间戳`                  | `DateTime` | 事件创建时的 UTC 时间戳。                                                          |
| `Sequence`             | `long`     | 用于排序的可选服务器序列号， `-1` 当后端省略它时。可让延迟数据包被忽略，而不是回退角色表情。                        |
| `UtteranceId`          | `字符串`      | 可选标识符，用于将该情绪与某个特定回复关联。若省略则为空。                                            |
| `Confidence`           | `float`    | 可选的 \[0, 1] 检测置信度， `1` 当省略时。                                             |
| `DurationMilliseconds` | `整数`       | 来自后端的可选持续时间提示， `0` 当省略时。                                                 |
| `IsNeutral`            | `布尔值`      | `是` 如果 `情绪` 是 `"neutral"`.                                               |
| `IsHighIntensity`      | `布尔值`      | `是` 如果 `Intensity >= 3`.                                                 |
| `IsLowIntensity`       | `布尔值`      | `是` 如果 `Intensity <= 1`.                                                 |

{% hint style="warning" %}
`CharacterEmotionChanged.Emotion` 包含 **原始服务器标签** （例如 `"快乐"`），而不是规范的分类法标签（`“joy”`）。如果你需要规范标签——例如要在 `Current.AllScores` 中查找分数——请通过 `TryResolveEmotionLabel`.
{% endhint %}

### 情绪维度

`EmotionDimensions` 是一种连续的、跨模块的情感信号—— `效价`, `唤醒度`, `能动性`以及 `趋近`，每个都以 `[-1, 1]` ——由 Emotion、Gaze、Body Language 和 locomotion 共享。分类标签仍是作者设定面部表情配方的权威；维度提供一个统一的调制信号，其他模块基于此进行混合。

| 属性    | 范围     | 说明                                         |
| ----- | ------ | ------------------------------------------ |
| `效价`  | -1 – 1 | 情绪有多愉快。 `+1` 是欣喜， `-1` 是痛苦。                |
| `唤醒度` | -1 – 1 | 情绪有多激动。 `+1` 是亢奋而快速的， `-1` 是低沉而缓慢的。        |
| `能动性` | -1 – 1 | 角色感觉有多强的掌控感。 `+1` 是掌控局面的， `-1` 是任由事件摆布的。   |
| `趋近`  | -1 – 1 | 情绪是让角色朝着其原因前进，还是远离其原因。 `+1` 会靠近， `-1` 会后退。 |

从 `CurrentFrame.Dimensions`读取主导情绪的维度。内置标签会解析为保守默认值；自定义分类法条目可按标签覆盖它们——参见 [情绪分类体系](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/embodiment/emotion/emotion-taxonomy.md).

### TryGetMouthWeight

`bool TryGetMouthWeight(BlendshapeTargetKey key, out float weight)` 返回本帧合成器针对特定 blendshape 目标解析出的由情绪驱动的口型权重，若该目标没有解析出任何口型权重则返回 `否` 。这是 LipSync 读取的交接点，用于在非主动说话时将其自身的口型与情绪姿态进行混合；除非实现自定义面部输出消费者，否则大多数应用代码不会直接调用它。

### 下一步

如需将配置文件配置与这些 API 调用结合的完整示例，请参见 [情绪示例](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/embodiment/emotion/usage-examples.md)。如果在运行时有内容未按预期工作，请参见 [情绪故障排查](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/embodiment/emotion/troubleshooting-and-diagnostics.md).

{% content-ref url="/pages/f5a3aaba473b4dfb20969b46c9a49bc4b176f223" %}
[心情](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/embodiment/emotion/moods.md)
{% endcontent-ref %}

{% content-ref url="/pages/6c46f45b25431c91dffb50ae4e5dd2ea5cc7ef50" %}
[情绪示例](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/embodiment/emotion/usage-examples.md)
{% endcontent-ref %}

{% content-ref url="/pages/65ec9decd2473b72bc06bf3c946f3008c8528dda" %}
[排查情绪问题](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/embodiment/emotion/troubleshooting-and-diagnostics.md)
{% endcontent-ref %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/embodiment/emotion/scripting-api.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
