> 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/features/vision/usage-examples.md).

# 视觉使用示例

查找常见视觉设置的代码模式，包括安全培训、摄像头选择、注视激活、按需触发和 WebGL 部署。

这些示例涵盖了最常见的 Vision 集成模式。每个示例都是自包含的——复制相关脚本，将其附加到相应的 GameObject，并在 Inspector 中配置序列化字段。

### 在安全培训中监控物体放置

一个安全培训应用，Convai 角色会监控用户是否将设备放置在正确区域，并给出语音反馈。该角色使用实时场景摄像头画面来实时观察放置情况。

**预期结果：** 当玩家移动物体时，角色会描述其位置并确认放置是否正确，或者标记出安全问题。

```csharp
using Convai.Modules.Vision;
using Convai.Runtime.Vision.Publishing;
using UnityEngine;

/// <summary>
/// 在培训序列开始时启用 Vision，并在完成时禁用它。
/// 连接到与 ConvaiVisionPublisher 相同的 GameObject。
/// </summary>
public class SafetyTrainingVisionController : MonoBehaviour
{
    [SerializeField] private ConvaiVisionPublisher _publisher;

    void Awake()
    {
        // 以 Manual 模式开始，这样 Vision 只会在主动培训期间采集
        _publisher.SetPublishPolicy(VisionPublishPolicy.Manual);
    }

    public void BeginTrainingSequence()
    {
        _publisher.SetPublishPolicy(VisionPublishPolicy.HighResponsiveness);
        _publisher.EnablePublishing(true);
    }

    public void EndTrainingSequence()
    {
        _publisher.EnablePublishing(false);
        _publisher.SetPublishPolicy(VisionPublishPolicy.Manual);
    }
}
```

### 在运行时选择摄像头设备

一个桌面引导应用，用户在会话开始前选择要使用的物理摄像头。当用户的工作站有多个摄像头（内置摄像头、USB 摄像头等）时很有用。

**预期结果：** 下拉菜单会在启动时填充所有检测到的摄像头名称。选择一个摄像头名称并点击 **切换** 即可在不中断会话的情况下切换采集设备。

```csharp
using System.Collections.Generic;
using Convai.Runtime.Vision.Sources;
using TMPro;
using UnityEngine;

/// <summary>
/// 用可用的摄像头名称填充 TMP_Dropdown，并按需切换设备。
/// 需要场景中存在 WebcamVisionFrameSource。
/// </summary>
public class WebcamSelectorUI : MonoBehaviour
{
    [SerializeField] private WebcamVisionFrameSource _webcamSource;
    [SerializeField] private TMP_Dropdown _deviceDropdown;

    private List<string> _deviceNames = new();

    async void Start()
    {
        _deviceNames = new List<string>(WebcamVisionFrameSource.GetAvailableDeviceNames());
        _deviceDropdown.ClearOptions();
        _deviceDropdown.AddOptions(_deviceNames);

        _deviceDropdown.onValueChanged.AddListener(async index =>
        {
            if (index >= 0 && index < _deviceNames.Count)
                await _webcamSource.SwitchWebcamAsync(_deviceNames[index]);
        });
    }
}
```

{% hint style="info" %}
`TMP_Dropdown` 需要 TextMeshPro 包。如果你的项目使用的是旧版 `UnityEngine.UI.Dropdown`，请替换为 `TMP_Dropdown` 使用 `Dropdown` — `AddOptions(List<string>)` 的作用完全相同。
{% endhint %}

### 流式传输一个俯视安防摄像头

一个建筑导览应用，俯视安防摄像头监控整个平面布局。发布器使用 `低开销` 策略，因为场景变化缓慢，而且必须为音频保留带宽。

**预期结果：** 当被询问时，角色会描述俯视视图中可见的内容——家具布局、占用情况或危险。

将俯视摄像头分配给 **Target Camera** 字段，在 `CameraVisionFrameSource` 组件位于 Inspector 中。 `CameraVisionFrameSource.TargetCamera` 具有私有 setter，因此脚本无法在运行时将帧源重新指向其他摄像头——Inspector 字段是唯一受支持的选择其读取摄像头的方式。

```csharp
using Convai.Modules.Vision;
using Convai.Runtime.Vision.Publishing;
using UnityEngine;

/// <summary>
/// 为一个场景配置发布策略，其中俯视安防摄像头
/// 已在 Inspector 中分配给 CameraVisionFrameSource 的 Target Camera 字段。
/// 附加到 ConvaiVisionRoot GameObject。
/// </summary>
public class SecurityCameraVisionSetup : MonoBehaviour
{
    [SerializeField] private ConvaiVisionPublisher _publisher;

    void Awake()
    {
        // LowOverhead 适用于 WebGL——画布捕获上限为 15 fps
        _publisher.SetPublishPolicy(VisionPublishPolicy.LowOverhead);
    }
}
```

### 在玩家注视时激活发布

持续流式传输 Vision 的开销很大。此模式只会在玩家注视某个特定物体（例如一台机器）时激活发布，并在其他时候暂停。

**预期结果：** 只有当玩家看着它时，角色才会响应该物体的状态。当玩家移开视线时，网络和 GPU 开销为零。

```csharp
using Convai.Modules.Vision;
using Convai.Runtime.Vision.Publishing;
using UnityEngine;

/// <summary>
/// 当玩家注视指定对象时激活 Vision 发布。
/// 附加到目标对象。ConvaiVisionPublisher 必须设置为 Manual 策略。
/// </summary>
public class LookAtVisionTrigger : MonoBehaviour
{
    [SerializeField] private ConvaiVisionPublisher _publisher;
    [SerializeField] private Camera _playerCamera;
    [SerializeField] private float _maxViewAngle = 15f;

    void Awake()
    {
        _publisher.SetPublishPolicy(VisionPublishPolicy.Manual);
    }

    void Update()
    {
        Vector3 directionToObject = (transform.position - _playerCamera.transform.position).normalized;
        float angle = Vector3.Angle(_playerCamera.transform.forward, directionToObject);
        bool isLooking = angle < _maxViewAngle;

        if (isLooking && !_publisher.IsPublishing)
            _publisher.EnablePublishing(true);
        else if (!isLooking && _publisher.IsPublishing)
            _publisher.EnablePublishing(false);
    }
}
```

### 为 WebGL 配置 Vision

在 WebGL 上，不需要帧源组件。 `ConvaiVisionPublisher` 会自动通过 `canvas.captureStream()`。将 **连接类型** 为 **视频** 捕获浏览器画布，并按需设置发布策略——其余一切都会自动完成。

**预期结果：** 角色接收浏览器画布的实时画面。场景中没有帧源组件。

```csharp
using Convai.Modules.Vision;
using Convai.Runtime.Vision.Publishing;
using UnityEngine;

/// <summary>
/// WebGL 专用设置。无需帧源——发布器使用 canvas.captureStream()。
/// 连接到与 ConvaiVisionPublisher 相同的 GameObject。
/// 在 Play 之前将 ConvaiRoomManager.ConnectionType 设置为 Video。
/// </summary>
public class WebGLVisionSetup : MonoBehaviour
{
    [SerializeField] private ConvaiVisionPublisher _publisher;

    void Awake()
    {
        // LowOverhead 适用于 WebGL——画布捕获上限为 15 fps
        _publisher.SetPublishPolicy(VisionPublishPolicy.LowOverhead);
    }
}
```

{% hint style="danger" %}
**WebGL 需要 HTTPS。** 该 `canvas.captureStream()` 浏览器会阻止非 HTTPS 源上的 API。在生产环境中测试 Vision 之前，请将你的 WebGL 构建部署到 HTTPS 主机。 `http://localhost` 是唯一的例外。
{% endhint %}

### 按需触发 Vision 并调整响应模式

前面的示例都通过 `ConvaiVisionPublisher`持续发布帧。 `IConvaiRoomConnectionService` 的不同，更底层的接口让你可以让后端按需检查缓冲帧——独立于发布器自身的节奏——并控制该检查是否让角色开口说话。此模式适合“立即查看”按钮、玩家手动触发的检查工具，或需要在精确时刻进行视觉检查的脚本化流程中的某一步。

`RequestVisionStatus()` 会要求后端报告会话帧缓冲区的状态。 `TriggerVision(ConvaiVisionTriggerRequest)` 会要求后端将缓冲帧附加到下一轮交互中，并根据请求的响应模式调用模型。 `UpdateRespondMode(ConvaiRespondModeLane, ConvaiRespondMode)` 会更改整个输入通道在整个会话剩余时间内如何影响角色的语音输出。这三个操作都通过领域事件异步确认，而不是通过返回值——参见 [Vision 脚本 API](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/vision/scripting-api.md) 以获取完整的方法签名、请求字段和事件负载。

**预期结果：** 调用 `RequestLookNow()` 会记录缓冲区状态，然后角色描述发生了什么并说出回答。调用 `SilenceVisionUntilAsked()` 会阻止角色对新的 Vision 帧作出反应，直到 `RequestLookNow()` 再次被调用。

```csharp
using Convai.Domain.DomainEvents.Vision;
using Convai.Domain.EventSystem;
using Convai.Runtime;
using Convai.Runtime.Components;
using Convai.Runtime.Room;
using Convai.Runtime.Vision.Context;
using UnityEngine;

/// <summary>
/// 请求一次按需视觉检查，发生在发布器的常规帧节奏之外，
/// 然后报告缓冲区状态和触发结果。附加到场景中的任意位置。
/// </summary>
public class OnDemandVisionInspector : MonoBehaviour
{
    private IConvaiRoomConnectionService _roomService;
    private SubscriptionToken _statusToken;
    private SubscriptionToken _triggerToken;

    void OnEnable()
    {
        ConvaiManager manager = ConvaiManager.ActiveManager;
        if (manager == null || !manager.TryGetRoomConnectionService(out _roomService))
            return;

        if (!manager.TryGetEventHub(out IEventHub hub))
            return;

        _statusToken = hub.Subscribe<VisionContextStatusReceived>(OnVisionStatus, EventDeliveryPolicy.MainThread);
        _triggerToken = hub.Subscribe<VisionContextTriggerReceived>(OnVisionTriggerAck, EventDeliveryPolicy.MainThread);
    }

    void OnDisable()
    {
        if (ConvaiManager.ActiveManager == null || !ConvaiManager.ActiveManager.TryGetEventHub(out IEventHub hub))
            return;

        hub.Unsubscribe(_statusToken);
        hub.Unsubscribe(_triggerToken);
    }

    // 从“立即查看”UI 按钮调用
    public void RequestLookNow()
    {
        _roomService?.RequestVisionStatus();
        _roomService?.TriggerVision(new ConvaiVisionTriggerRequest
        {
            Text = "自从我上次查看后，工作台上发生了什么变化？",
            RespondMode = ConvaiRespondMode.MustRespond
        });
    }

    // 例如，当玩家进入一个视觉响应会分散注意力的区域时调用一次
    public void SilenceVisionUntilAsked()
    {
        _roomService?.UpdateRespondMode(ConvaiRespondModeLane.Vision, ConvaiRespondMode.Silent);
    }

    private void OnVisionStatus(VisionContextStatusReceived status)
    {
        Debug.Log($"[Vision] {status.Outcome} — 上一帧的年龄：{status.LastFrameAgeMs} 毫秒（来源：{status.ActiveSourceLabel}）");
    }

    private void OnVisionTriggerAck(VisionContextTriggerReceived ack)
    {
        if (ack.Downgraded)
            Debug.Log($"[Vision] 触发降级为 {ack.ActualRespondMode}：{ack.DowngradeReason}");
        else if (ack.LlmTriggered)
            Debug.Log($"[Vision] 角色使用了 {ack.FramesAttached} 帧作出了响应。");
    }
}
```

`RequestLookNow()` 上面的内容创建了一个新的 `ConvaiVisionTriggerRequest` ，没有显式的 `UpdateId` ，因此在一次调用后如果确认丢失，再次调用会发送一个不同的触发，而不是安全重放——每次调用都会获得各自生成的 ID。要使重试具备幂等性，请预先生成一个 `UpdateId` ，将其传递给构造函数，并在重试过程中复用同一个值；这样后端会为该 ID 重放原始确认，而不是再次触发。构造函数签名见 [Vision 脚本 API](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/vision/scripting-api.md#convaivisiontriggerrequest) 。

### 下一步

{% content-ref url="/pages/9ee175b12da238182d707abd778abdc3a8706c80" %}
[Vision 脚本 API](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/vision/scripting-api.md)
{% endcontent-ref %}

{% content-ref url="/pages/86612bc613c5a299a468b71b3fb8a40e625ee1fe" %}
[排查视觉问题](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/vision/troubleshooting-and-diagnostics.md)
{% endcontent-ref %}

{% content-ref url="/pages/cb7758289bc48a93210bce51933fd819fd05e245" %}
[自定义帧来源](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/vision/custom-frame-sources.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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/vision/usage-examples.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
