> 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/unity-plugin-beta-overview/features/emotion/scripting-api-reference.md).

# 脚本 API 参考

## 运行时控制与观察情绪状态

Emotion 系统提供两条不同路径，用于在运行时响应和控制情绪状态。The **Inspector 路径** 使用 `ConvaiCharacterEventRelay` —— 一个将情绪变化回调以标准 Unity Events 形式公开的组件，无需编写代码。The **脚本路径** 使用 `ConvaiEmotionController` 直接提供完整的 C# API，用于读取实时状态、注入覆盖和锁定表情。两条路径可同时使用。

## Inspector 路径 — ConvaiCharacterEventRelay

`ConvaiCharacterEventRelay` 是一个 MonoBehaviour，用于将角色回调桥接到 Unity Events，使设计师无需编写任何代码即可完全在 Inspector 中配置情绪反应。

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

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

### Inspector 字段

| 字段       | 默认值    | 说明                                                    |
| -------- | ------ | ----------------------------------------------------- |
| `角色`     | *（无）*  | 可选地显式引用一个 `ConvaiCharacter`。留空以使用自动解析。                |
| `自动解析角色` | `true` | 启用后，中继器会找到一个 `ConvaiCharacter` 并自动定位到同一 GameObject 上。 |

### OnEmotionChanged 事件

中继器公开一个 **On Emotion Changed** Unity 事件，每当角色的情绪信号发生变化时都会触发。该事件传递一个 `CharacterEmotionRelayData` 载荷：

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

**连接示例：** 添加一个 `ConvaiCharacterEventRelay` 到你的 NPC 的 GameObject 上。在 **On Emotion Changed** 列表，点击 **+**&#x4E2D;，将一个 UI Text 组件拖入对象字段，并选择 `Text.text` —— 标签会在每次情绪变化时自动更新。

{% hint style="info" %}
`ConvaiCharacterEventRelay` 会在分类法解析或平滑处理之前基于原始服务器标签触发。可用于 UI 显示、音频提示或简单分支逻辑。若要获取平滑后的、解析后的状态（包含分数和保持时间），请使用 `ConvaiEmotionController.Current` 在脚本中访问。
{% endhint %}

## 脚本路径 — 访问控制器

该 `ConvaiEmotionController` 组件可通过其具体类型或通过 `IEmotionStateSource` 接口获取。当你希望让代码与具体组件实现解耦时，请使用该接口：

```csharp
using Convai.Modules.Emotion.Components;
using Convai.Domain.Embodiment.Interfaces;

// 具体类型 —— 完整 API 访问，包括覆盖和锁定
var controller = npcGameObject.GetComponent<ConvaiEmotionController>();

// 接口 —— 与实现解耦的当前情绪读取只读访问
var source = npcGameObject.GetComponent<IEmotionStateSource>();
EmotionReading reading = source.Current;
```

## 读取当前情绪状态

`ConvaiEmotionController.Current` 会返回一个 `EmotionReading` —— 一个不可变快照，每帧根据累加器输出重建。可在 `Update` 中轮询，或在任意事件中对其作出响应。

```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($"Dominant emotion: {reading.DominantLabel} ({reading.DominantScore:F2})");
    }
}
```

### EmotionReading 属性

| 属性                    | 类型                                   | 说明                                         |
| --------------------- | ------------------------------------ | ------------------------------------------ |
| `DominantLabel`       | `string`                             | 得分最高情绪的规范标签（例如 `"joy"`, `"愤怒"`).           |
| `DominantScore`       | `float`                              | 经平滑和突发处理后的主导情绪归一化分数 \[0–1]。                |
| `AllScores`           | `IReadOnlyDictionary<string, float>` | 以规范标签为键的完整分数表。分类法中的每种情绪都有一项。               |
| `MouthInfluence`      | `float`                              | LipSync 合成器在非说话帧中用于混合嘴型的提示值 \[0–1]。        |
| `DominantHoldSeconds` | `float`                              | 当前主导标签连续保持的实际秒数。                           |
| `IsNeutral`           | `bool`                               | 当主导标签为 `"neutral"` 或当 `DominantScore ≤ 0`. |
| `NeutralLabel`        | `const string`                       | 字符串常量 `"neutral"`.                         |

## Inspector 锁定

控制器有三个序列化字段，可让你直接在 Inspector 中将表情固定为某个特定情绪——这在创作和调试时很有用，或者当你想在不进入 Play Mode 的情况下在 Scene 视图中预览 blendshape 槽位结果时也很有用。

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

{% hint style="info" %}
`ConvaiEmotionController` 为 `[ExecuteAlways]`。设置 `lockEmotion = true` 在 Inspector 中会立即更新 Scene 视图中的 blendshapes，无需进入 Play Mode。这使其成为验证槽位映射是否在角色网格上产生正确视觉结果的实用工具。
{% endhint %}

{% hint style="danger" %}
`lockEmotion` 是一个 **序列化字段** —— 其值会与场景或 prefab 一起保存。如果你在 Inspector 中保持启用并忘记重置，角色将在正式构建中静默忽略所有实时情绪信号。发布前务必将其禁用。
{% endhint %}

## SetEmotionOverride / ClearEmotionOverride

`SetEmotionOverride` 在服务器发送的内容之上向累加器注入额外分数。该覆盖仍受平滑处理影响——它会以 `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` 完全绕过累加器，直接将角色切换到某个特定表情并保持在那里，无论服务器发送什么都不受影响。当你在脚本序列或过场动画期间需要一个有保证、稳定的表情时使用。

```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` 解除锁定。累加器从中立状态恢复，并重新开始响应服务器事件。

**API 参考：**

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

## 在代码中监听情绪变化事件

若要响应后端发送的每个新情绪——用于日志记录、分析或自适应场景逻辑——请订阅 `OnCharacterEmotionChanged` on `ConvaiManager.Events`。这是标准 C# 事件；无需管理订阅令牌。

```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} — intensity {e.Intensity} ({e.NormalizedIntensity:F2})");
    }
}
```

### CharacterEmotionChanged 属性

| 属性                    | 类型         | 说明                                             |
| --------------------- | ---------- | ---------------------------------------------- |
| `CharacterId`         | `string`   | 情绪发生变化的角色的唯一标识符。                               |
| `情绪`                  | `string`   | 原始服务器标签（例如 `"快乐"`，而不是 `"joy"`).                |
| `强度`                  | `int`      | 后端发送的 1–3 整数刻度（已钳制）。                           |
| `NormalizedIntensity` | `float`    | `(Intensity - 1) / 2f` —— 将 1–3 范围映射到 \[0, 1]。 |
| `IsNeutral`           | `bool`     | 如果情绪字符串是，则为 True `"neutral"`.                  |
| `IsHighIntensity`     | `bool`     | 如果……则为 True `Intensity >= 3`.                  |
| `IsLowIntensity`      | `bool`     | 如果……则为 True `Intensity <= 1`.                  |
| `时间戳`                 | `DateTime` | 事件创建时的 UTC 时间戳。                                |

{% hint style="warning" %}
`CharacterEmotionChanged.Emotion` 包含 **原始服务器标签** （例如 `"快乐"`），而不是规范分类标签（`"joy"`）。如果你需要规范标签——例如，要在……中查找分数 `AllScores` —— 请通过分类法解析： `taxonomy.TryResolve(e.Emotion, out EmotionDescriptor descriptor)`.
{% endhint %}

## IEmotionStateSource 接口

仅需要读取情绪状态、而不需要控制覆盖或锁定的代码，应依赖于 `IEmotionStateSource` 而不是具体的 `ConvaiEmotionController`。这样可将依赖降到最低，并使消费类更易于独立测试。

```csharp
using Convai.Domain.Embodiment.Interfaces;
using Convai.Domain.Embodiment.Readings;
using UnityEngine;

public sealed class EmotionDrivenUI : MonoBehaviour
{
    // 在 Inspector 中分配一个 ConvaiEmotionController —— 它实现了 IEmotionStateSource
    [SerializeField] private MonoBehaviour emotionSource;

    private IEmotionStateSource _source;

    private void Awake()
    {
        _source = emotionSource as IEmotionStateSource;
    }

    private void Update()
    {
        EmotionReading reading = _source.Current;
        // 根据 reading.DominantLabel 和 reading.DominantScore 更新 UI
    }
}
```

## 结论

情绪脚本接口涵盖从无需代码的 Unity Events 到完整的类型化 C# 订阅和运行时覆盖的所有集成模式。有关将配置文件设置与这些 API 调用结合起来的完整示例，请参见 [使用示例](/api-docs/zh/cha-jian-yu-ji-cheng/unity-plugin-beta-overview/features/emotion/usage-examples.md)。如果某些内容在运行时表现不符合预期，请参见 [故障排查](/api-docs/zh/cha-jian-yu-ji-cheng/unity-plugin-beta-overview/features/emotion/troubleshooting-and-diagnostics.md).


---

# 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:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/unity-plugin-beta-overview/features/emotion/scripting-api-reference.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
