Skip to content

Commit a46a72c

Browse files
authored
Add OSC scripting Info, and details. Includes translations. (#113)
1 parent 349c2f3 commit a46a72c

13 files changed

Lines changed: 4115 additions & 1 deletion

File tree

content/docs/scripting/index.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,9 @@ Basis also makes use of the `Cilbox` sandboxed CIL emulator Unity package.
1818
description="How to use Scripts"
1919
href="/docs/scripting/scripting"
2020
/>
21+
<Card
22+
title="OSC"
23+
description="Publish and subscribe to OSC data from Basis scripts"
24+
href="/docs/scripting/osc"
25+
/>
2126
</Cards>

content/docs/scripting/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"title": "Scripting",
33
"icon": "FolderOpen",
4-
"pages": ["index", "scripting"]
4+
"pages": ["index", "scripting", "osc"]
55
}

content/docs/scripting/osc.cn.mdx

Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
---
2+
title: OSC
3+
description: 在 Basis 脚本中发布和接收 OSC 数据
4+
icon: Activity
5+
---
6+
7+
import { Callout } from 'fumadocs-ui/components/callout';
8+
import { Card, Cards } from 'fumadocs-ui/components/card';
9+
10+
## 概览
11+
12+
Basis 为需要接收 OSC 消息、订阅实时地址范围,或将数值重新发布回 OSC bridge 运行时的创作者脚本提供了 `BasisOsc` shim。
13+
14+
该组件会感知作用域。同一个相对地址的解析结果可能不同,取决于对象运行在本地 avatar、远程 avatar、prop 还是场景下。
15+
16+
<Callout type="info">
17+
在 prop、avatar 和场景中的创作者 OSC 工作流里使用 `BasisOsc`。bridge 会将活动订阅和已发布的值反映到 OSC Query,而数值型已发布值也可以更新 Vixxy avatar 菜单变量。
18+
</Callout>
19+
20+
## 设置
21+
22+
1.`BasisOsc` 组件添加到与脚本相同的 GameObject 上,或者添加到你可以引用的附近对象上。调用 `GetComponent<BasisOsc>()` 时,Cilbox 可以自动添加这个 shim。
23+
2. 将该对象放在一个受支持的 Basis GameObject 下,并带有以下组件,从而处于其作用域内:
24+
- `Basis.Scripts.BasisSdk.BasisAvatar`
25+
- `Basis.Scripts.BasisSdk.BasisProp`
26+
- `Basis.Scripts.BasisSdk.BasisScene`
27+
3. 在订阅或发布之前,先通过 `GetComponent<BasisOsc>()` 读取组件。
28+
4. 当对象被禁用或销毁时,移除订阅。
29+
30+
在 Unity 编辑器中,`BasisOsc` 检视器会在运行时显示已解析的作用域、发布前缀、默认订阅前缀、`ReceiveAll` 状态,以及当前的精确订阅和前缀订阅注册情况。
31+
32+
```csharp title="获取 BasisOsc 引用"
33+
using Basis.Shims;
34+
using UnityEngine;
35+
36+
[Cilboxable]
37+
public class OscConsumer : MonoBehaviour
38+
{
39+
private BasisOsc osc;
40+
41+
private void Start()
42+
{
43+
osc = GetComponent<BasisOsc>();
44+
if (osc == null)
45+
{
46+
Debug.LogError("OscConsumer requires a BasisOsc component.");
47+
}
48+
}
49+
}
50+
```
51+
52+
<Callout type="warn">
53+
如果无法解析 `BasisAvatar``BasisProp``BasisScene` 作用域,相对订阅会默认回退到 `/avatar/parameters/...`。只有当组件处于受支持的作用域下时,发布才可用。
54+
</Callout>
55+
56+
## 订阅 OSC
57+
58+
当你只想要一个地址时,使用精确订阅。
59+
60+
```csharp title="精确订阅"
61+
osc.Subscribe("/avatar/parameters/TestToggle", OnToggleMessage);
62+
63+
private void OnToggleMessage(OscMessage message, OscData[] arguments)
64+
{
65+
Debug.Log($"Received {message.Path} with {arguments.Length} value(s).");
66+
}
67+
```
68+
69+
允许使用相对地址。在本地 avatar 上,`"Face/Smile"` 会解析为 `/avatar/parameters/Face/Smile`
70+
71+
```csharp title="相对精确订阅"
72+
osc.Subscribe("Face/Smile", OnSmileChanged);
73+
```
74+
75+
如果你需要知道最终注册的规范化地址,请使用带 `out` 变量的重载。
76+
77+
```csharp title="获取精确订阅的解析地址"
78+
osc.Subscribe("Face/Smile", OnSmileChanged, out string resolvedAddress);
79+
Debug.Log($"Subscribed to {resolvedAddress}");
80+
```
81+
82+
如果你只想让订阅存在于本地实例上,请使用 `localOnly` 重载。在远程 avatar 上不会注册任何内容。
83+
84+
```csharp title="仅在本地实例上订阅"
85+
osc.Subscribe("Face/Smile", OnSmileChanged, localOnly: true, out string resolvedAddress);
86+
if (resolvedAddress != null)
87+
{
88+
Debug.Log($"Local-only subscription resolved to {resolvedAddress}");
89+
}
90+
```
91+
92+
当你想要所有匹配的子地址时,使用前缀订阅。
93+
94+
```csharp title="前缀订阅"
95+
osc.SubscribePrefix("FT/", OnFaceTrackingMessage);
96+
97+
private void OnFaceTrackingMessage(OscMessage message, OscData[] arguments)
98+
{
99+
Debug.Log($"Matched prefix for {message.Path}");
100+
}
101+
```
102+
103+
```csharp title="获取解析后的前缀地址"
104+
osc.SubscribePrefix("FT/", OnFaceTrackingMessage, out string resolvedPrefix);
105+
Debug.Log($"Watching prefix {resolvedPrefix}");
106+
```
107+
108+
前缀匹配使用路径段边界。像 `/avatar/parameters` 这样的前缀会匹配 `/avatar/parameters/Face/Smile`,但不会匹配 `/avatar/parametersExtra`
109+
110+
### 值回调
111+
112+
当你只关心第一个参数时,使用值回调。
113+
114+
```csharp title="精确值回调"
115+
osc.SubscribeValue("Speed", value =>
116+
{
117+
if (value.Kind == OscDataKind.Float32)
118+
{
119+
Debug.Log($"Speed is now {value.FloatValue}");
120+
}
121+
});
122+
```
123+
124+
```csharp title="仅本地的值回调"
125+
osc.SubscribeValue("Speed", value =>
126+
{
127+
Debug.Log($"Only the local instance will receive this callback: {value.Kind}");
128+
}, localOnly: true, out string resolvedAddress);
129+
```
130+
131+
```csharp title="前缀值回调"
132+
osc.SubscribePrefixValue("/avatar/parameters/Hands/", value =>
133+
{
134+
Debug.Log($"First value kind: {value.Kind}");
135+
});
136+
```
137+
138+
`SubscribeValue``SubscribePrefixValue` 只有在消息至少包含一个参数时才会触发。
139+
140+
### 移除订阅
141+
142+
当脚本结束时,移除你添加的处理器。这样可以让运行时路由器和 OSC Query 表面与活动脚本保持同步。
143+
144+
```csharp title="移除精确订阅"
145+
osc.Unsubscribe("Face/Smile", OnSmileChanged);
146+
osc.UnsubscribeValue("Speed", OnSpeedChanged);
147+
```
148+
149+
```csharp title="移除前缀订阅"
150+
osc.UnsubscribePrefix("FT/", OnFaceTrackingMessage);
151+
osc.UnsubscribePrefixValue("FT/", OnFaceTrackingValue);
152+
```
153+
154+
当你想移除某个规范化地址或前缀下注册的所有回调时,请使用仅地址版本。
155+
156+
```csharp title="移除某个地址或前缀的所有回调"
157+
osc.Unsubscribe("Face/Smile");
158+
osc.UnsubscribePrefix("FT/");
159+
```
160+
161+
当组件需要清除自己拥有的所有精确和前缀订阅时,使用 `ClearSubscriptions()`
162+
163+
### 接收所有消息
164+
165+
当你希望组件观察其允许范围内的每一条路由 OSC 消息时,设置 `ReceiveAll`
166+
167+
```csharp title="接收所有消息"
168+
osc.ReceiveAll = true;
169+
osc.OnMessage += (message, arguments) =>
170+
{
171+
Debug.Log($"Saw {message.Path}");
172+
};
173+
```
174+
175+
`ReceiveAll` 故意受到安全限制。它不会绕过正常的作用域边界。
176+
177+
- 本地 avatar 的 `BasisOsc` 只会看到 `/avatar/parameters/*`
178+
- 远程 avatar 的 `BasisOsc` 只会看到 `/avatar/public/*`
179+
- prop 和场景的 `BasisOsc` 看到的默认接收作用域与相对订阅相同,即 `/avatar/public/*`
180+
181+
这意味着 `ReceiveAll` 更像一个带作用域的通配符,而不是整个进程范围的 OSC tap。
182+
183+
## 发布 OSC
184+
185+
单个参数使用 `PublishValue`,多个参数使用 `PublishValues`
186+
187+
```csharp title="发布一个值"
188+
osc.PublishValue("Status", OscData.String("ready"));
189+
```
190+
191+
```csharp title="发布多个值"
192+
osc.PublishValues("Blend", new[]
193+
{
194+
OscData.Float32(0.5f),
195+
OscData.String("armed"),
196+
});
197+
```
198+
199+
如果你需要知道最终发布到的绝对地址,请使用带 `out` 变量的重载。
200+
201+
```csharp title="获取解析后的发布地址"
202+
osc.PublishValue("Status", OscData.String("ready"), out string resolvedAddress);
203+
Debug.Log($"Published to {resolvedAddress}");
204+
```
205+
206+
```csharp title="获取多值发布的解析地址"
207+
osc.PublishValues("Blend", new[]
208+
{
209+
OscData.Float32(0.5f),
210+
OscData.String("armed"),
211+
}, out string resolvedAddress);
212+
213+
Debug.Log($"Published blend packet to {resolvedAddress}");
214+
```
215+
216+
你可以发布多种 OSC 数据类型,包括布尔值、整数、浮点数、字符串、符号、MIDI 数据、数组和 blob。
217+
218+
本地 avatar 也可以直接发布到 `/avatar/public/...`,如果这正是你要公开的地址。
219+
220+
```csharp title="本地 avatar 发布到 avatar public"
221+
osc.PublishValue("/avatar/public/Status", OscData.String("shareable"));
222+
```
223+
224+
<Callout type="warn">
225+
远程 avatar 的 `BasisOsc` 组件不能发布。不要假设远程 avatar 的发布行为与本地 avatar、prop 或场景相同。
226+
</Callout>
227+
228+
### Vixxy avatar 菜单支持
229+
230+
`BasisOsc` 能通过 avatar 通信或采集服务解析到变量时,发布的数值也会提交到 Vixxy 变量存储。
231+
232+
支持的 Vixxy 值类型如下:
233+
234+
- `OscData.Boolean`
235+
- `OscData.Int32`
236+
- `OscData.Int64`
237+
- `OscData.Float32`
238+
- `OscData.Float64`
239+
240+
对于 `PublishValues`,只有第一个值会提交给 Vixxy。非数值 OSC 值仍会发布到 OSC,但不会作为 Vixxy 变量提交。
241+
242+
Vixxy 变量地址来源于解析后的 OSC 地址。Basis 会在提交数值前,从受作用域限制的 prop 和 scene 地址中移除 `/avatar/parameters/``/avatar/public/`,或第一个 `/parameters/` 段。
243+
244+
```csharp title="发布到 OSC 并更新 Vixxy 变量"
245+
osc.PublishValue("Expression/Smile", OscData.Float32(1.0f));
246+
```
247+
248+
在本地 avatar 上,这会发布到 `/avatar/parameters/Expression/Smile`,并提交 Vixxy 变量 `Expression/Smile`
249+
250+
## 地址与作用域规则
251+
252+
`BasisOsc` 在路由前会规范化地址。这对订阅和发布都很重要。
253+
254+
### 本地 avatar
255+
256+
- 相对订阅默认解析到 `/avatar/parameters/...`
257+
- 相对发布地址会发布到 `/avatar/parameters/...`
258+
- 也允许绝对 `/avatar/public/...` 发布地址。
259+
260+
### 远程 avatar
261+
262+
- 相对订阅默认解析到 `/avatar/public/...`
263+
- 绝对 `/avatar/parameters/...` 订阅会重写为 `/avatar/public/...`
264+
- 不支持发布。
265+
266+
### Prop
267+
268+
- 相对订阅默认解析到 `/avatar/public/...`
269+
- 绝对 avatar 订阅仅限于 `/avatar/public/...`
270+
- 相对发布地址会作用于 prop 实例:
271+
272+
```text
273+
/prop/<prop-id>/parameters/...
274+
```
275+
276+
### 场景
277+
278+
- 相对订阅默认解析到 `/avatar/public/...`
279+
- 绝对 avatar 订阅仅限于 `/avatar/public/...`
280+
- 相对发布地址会作用于 scene 实例:
281+
282+
```text
283+
/scene/<scene-id>/parameters/...
284+
```
285+
286+
<Callout type="warn">
287+
不要假设原始路径会原样使用。如果组件具有作用域,`BasisOsc` 可能会重写相对地址,或拒绝受限的绝对 avatar 路径。
288+
</Callout>
289+
290+
## 地址报告变体
291+
292+
有些 `BasisOsc` 方法提供重载,会通过 `out string resolvedAddress` 参数返回规范化后的绝对路径。
293+
294+
通常最常用的变体如下:
295+
296+
- `Subscribe(string, OscMessageEvent, out string resolvedAddress)`
297+
- `Subscribe(string, OscMessageEvent, bool localOnly)`
298+
- `Subscribe(string, OscMessageEvent, bool localOnly, out string resolvedAddress)`
299+
- `SubscribeValue(string, OscValueEvent, out string resolvedAddress)`
300+
- `SubscribeValue(string, OscValueEvent, bool localOnly)`
301+
- `SubscribeValue(string, OscValueEvent, bool localOnly, out string resolvedAddress)`
302+
- `SubscribePrefix(string, OscMessageEvent, out string resolvedAddress)`
303+
- `SubscribePrefixValue(string, OscValueEvent, out string resolvedAddress)`
304+
- `PublishValue(string, OscData, out string resolvedAddress)`
305+
- `PublishValues(string, OscData[], out string resolvedAddress)`
306+
307+
这些重载在以下情况下很有用:
308+
309+
- 你想记录相对地址最终解析到了哪里
310+
- 你想保存规范化地址供后续 `unsubscribe` 使用
311+
- 你想让脚本明确知道一次发布实际发送到了哪里
312+
313+
对于 `Subscribe(...)``SubscribeValue(...)``localOnly: true` 表示回调只会注册到本地实例。在远程 avatar 上,解析地址为 `null`,不会添加订阅。
314+
315+
没有被动式的 `Subscribe(...)``SubscribePrefix(...)` 重载。`BasisOsc` 中的订阅都是回调驱动的,`osc.OnMessage` 只有在某个基于回调的订阅或 `ReceiveAll` 匹配时才会触发。
316+
317+
## OSC 数据类型
318+
319+
`OscData` 封装了解码后的 OSC 参数,并通过 `BoolValue``IntValue``FloatValue``StringValue``BlobValue``Elements` 以及 MIDI 或颜色字段等属性暴露类型化的值。
320+
321+
发布时请使用这些工厂方法:
322+
323+
- `OscData.Boolean(bool)`
324+
- `OscData.Nil()`
325+
- `OscData.Impulse()`
326+
- `OscData.Int32(int)`
327+
- `OscData.Float32(float)`
328+
- `OscData.TimeTag(uint seconds, uint nanoseconds)`
329+
- `OscData.String(string)`
330+
- `OscData.Symbol(string)`
331+
- `OscData.Blob(byte[])`
332+
- `OscData.Color(byte r, byte g, byte b, byte a)`
333+
- `OscData.Int64(long)`
334+
- `OscData.Float64(double)`
335+
- `OscData.Char(uint)`
336+
- `OscData.Midi(byte port, byte status, byte data1, byte data2)`
337+
- `OscData.ArrayValue(params OscData[] elements)`
338+
339+
## OSC Query 行为
340+
341+
OSC bridge 会根据运行时的实时状态更新 OSC Query。
342+
343+
- 精确订阅会为订阅地址创建 query 节点。
344+
- 前缀订阅会为订阅前缀创建 query 分支。
345+
- 已发布的值会创建带有最新负载的 query 节点。
346+
- 随着订阅变化,query 节点会动态创建和移除。
347+
348+
这意味着 OSC Query 表面不是固定静态 schema。它取决于当前活动的 `BasisOsc` 组件,以及它们此刻拥有的订阅或已发布值。
349+
350+
<Callout type="info">
351+
由于接收器状态按实体 ID 集中管理,清理非常重要。请在 `OnDisable``OnDestroy` 中移除订阅,或者在脚本完成时调用 `ClearSubscriptions()`
352+
</Callout>
353+
354+
## 示例
355+
356+
该分支添加了一个名为 `OSC Subscription Example` 的示例,使用 `BasisOscCilboxSubscriptionExample.cs`。它展示了显式和隐式地址订阅,并在销毁时注销处理器。
357+
358+
<Cards>
359+
<Card
360+
title="脚本概览"
361+
description="返回主脚本指南,了解 Cilbox 和脚本设置的更广泛背景。"
362+
href="/docs/scripting"
363+
/>
364+
</Cards>
365+
366+
## 故障排除
367+
368+
- 如果相对地址的解析结果出乎意料,先检查最近的 `BasisAvatar``BasisProp``BasisScene` 祖先。
369+
- 如果远程 avatar 没有收到 `/avatar/parameters/...`,请改为订阅 `/avatar/public/...`,或者使用相对路径。
370+
- 如果值回调没有触发,请确认传入消息至少包含一个参数。
371+
- 如果发布的数据没有出现在预期位置,请检查组件是否位于本地 avatar、prop 或 scene 的作用域下。
372+
- 如果旧的 query 节点或监听器仍然存在,请确保脚本在关闭时解除订阅。
373+

0 commit comments

Comments
 (0)