> 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).

# 视觉使用示例

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

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

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

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

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

/// <summary>
/// 在培训序列开始时启用视觉，并在完成时禁用。
/// 将其附加到与 ConvaiVisionPublisher 相同的 GameObject 上。
/// </summary>
public class SafetyTrainingVisionController : MonoBehaviour
{
    [SerializeField] private ConvaiVisionPublisher _publisher;

    void Awake()
    {
        // 以手动模式开始，因此视觉仅在主动培训期间捕获
        _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 摄像头等）非常有用。

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

```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 %}

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

一个建筑漫游场景，头顶的安防摄像头监控整个平面布局。发布器使用 `LowOverhead` 策略，因为场景变化缓慢，而且带宽必须留给音频。

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

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

/// <summary>
/// 使用特定的头顶摄像头和低开销传输策略配置视觉。
/// 将其附加到 ConvaiVisionRoot GameObject。
/// </summary>
public class SecurityCameraVisionSetup : MonoBehaviour
{
    [SerializeField] private ConvaiVisionPublisher _publisher;
    [SerializeField] private CameraVisionFrameSource _frameSource;
    [SerializeField] private Camera _overheadCamera;

    void Awake()
    {
        // 将帧源指向头顶安防摄像头
        _frameSource.TargetCamera = _overheadCamera;

        // 低开销策略：5 fps，350 kbps——适用于缓慢变化的场景
        _publisher.SetPublishPolicy(VisionPublishPolicy.LowOverhead);
    }
}
```

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

持续流式传输视觉代价很高。此模式仅在玩家注视特定对象（例如一台机器）时激活发布，而在其他情况下暂停。

**预期结果：** 只有当玩家注视该对象时，角色才会响应对象状态。当玩家移开视线时，网络和 GPU 开销为零。

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

/// <summary>
/// 在玩家注视指定对象时激活视觉发布。
/// 将其附加到目标对象上。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()`捕获浏览器画布。将 **Connection Type** 设置为 **Video** 和发布策略按需设置即可——其余全部自动完成。

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

```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。请先将你的 WebGL 构建部署到 HTTPS 主机，再在生产环境中测试 Vision。 `http://localhost` 是唯一的例外。
{% endhint %}

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

前面的示例都通过 `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()` 会阻止角色对新的视觉帧做出反应，直到 `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 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/features/vision/usage-examples.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.
