VRChat 用 OSC ライブラリの VRCOscLib を使ってみる[雑記]

# はじめに
VRChat で OSC 触ってみるー
**VRCOscLib は続々と新しい機能に対応しています。最新版を用いて開発することをおすすめします。**

VRCOscLib の GitHub リポジトリにもいくつか[サンプル](https://github.com/ChanyaVRC/VRCOscLib/tree/main/src/Sample)があります。

## 環境
* Visual Studio 2022
* .NET 6
* C# 10
* [VRCOscLib](https://github.com/ChanyaVRC/VRCOscLib) v1.1.3 (バージョンは逐次更新)

## VRCOscLib を入れてみる
とりあえずコンソールアプリ作成
ソリューション右クリック→「NuGetパッケージの管理」みたいなやつ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419430/2b4a30a6-0af2-a7c3-aaa7-26c22438070a.png)
「VRCOscLib」で検索→こいつをインストールする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419430/ee268558-e3d2-f721-6c31-ec93eafd9b64.png)

# とりあえずいろいろいじる
## アバターのIDと名前を取得
```cs
using BuildSoft.VRChat.Osc.Avatar;

var avatarConfigs = OscAvatarConfig.CreateAll();

foreach (var config in avatarConfigs)
{
    Console.WriteLine($"config: ");
    Console.WriteLine($"      id: {config.Id}");
    Console.WriteLine($"    name: {config.Name}");
}
```

### 実行結果

```
config:
      id: avtr_8b024156-1ee4-467a-XXXX-f7b11297550b
    name: Bell_v5?0?0
config:
      id: avtr_bf4a3985-d29d-45c2-XXXX-681620fe4d9b
    name: My CustaChan v1?0
```

お、文字化けした。コンソールの文字コード変えよう。

## アバターのIDと名前を取得 (UTF-8で出力)

```cs
using BuildSoft.VRChat.Osc.Avatar;
using System.Text;

var avatarConfigs = OscAvatarConfig.CreateAll();

Console.OutputEncoding = Encoding.UTF8;

foreach (var config in avatarConfigs)
{
    Console.WriteLine($"config: ");
    Console.WriteLine($"      id: {config.Id}");
    Console.WriteLine($"    name: {config.Name}");
}
```

### 実行結果

```
config:
      id: avtr_8b024156-1ee4-467a-XXXX-f7b11297550b
    name: Bell_v5․0․0
config:
      id: avtr_bf4a3985-d29d-45c2-XXXX-681620fe4d9b
    name: My CustaChan v1․0
```

いい感じ、いい感じ。

かすたちゃん(My CustaChan v1․0)の方だけ欲しいので、かすたちゃんのIDで引っ張ってくる。

## かすたちゃんのパラメータを列挙

```cs
using BuildSoft.VRChat.Osc.Avatar;
using System.Text;

Console.OutputEncoding = Encoding.UTF8;

var avatarConfig = OscAvatarConfig.CreateAll()
    .First(config => config.Id == "avtr_bf4a3985-d29d-45c2-XXXX-681620fe4d9b");

foreach (var param in avatarConfig.Parameters.Items)
{
    Console.WriteLine($"{param.Name}: " +
        $"input {(param.Input != null ? "○" : "×")}, " +
        $"output {(param.Output != null ? "○" : "×")}"
    );
}
```

### 実行結果

```
Bottoms: input ○, output ○
Wear: input ○, output ○
VRCFaceBlendV: input ○, output ○
VRCFaceBlendH: input ○, output ○
VRCEmote: input ○, output ○
VelocityZ: input ×, output ○
VelocityY: input ×, output ○
VelocityX: input ×, output ○
InStation: input ×, output ○
Seated: input ×, output ○
AFK: input ×, output ○
Upright: input ×, output ○
AngularY: input ×, output ○
Grounded: input ×, output ○
MuteSelf: input ×, output ○
VRMode: input ×, output ○
TrackingType: input ×, output ○
GestureRightWeight: input ×, output ○
GestureRight: input ×, output ○
GestureLeftWeight: input ×, output ○
GestureLeft: input ×, output ○
Voice: input ×, output ○
Viseme: input ×, output ○
```

いっぱいありゅ！

## 値の読み取り

```cs
using BuildSoft.VRChat.Osc.Avatar;
using System.Text;

Console.OutputEncoding = Encoding.UTF8;

var avatarConfig = OscAvatarConfig.CreateAll()
    .First(config => config.Id == "avtr_bf4a3985-d29d-45c2-XXXX-681620fe4d9b");


avatarConfig.Parameters.ParameterChanged += (parameter, e) =>
{
    DateTime now = DateTime.Now;
    Console.WriteLine($"[{now.ToShortDateString()} {now.ToShortTimeString()}] " +
        $"{parameter.Name}: {e.OldValue} => {e.NewValue}");
};

await Task.Delay(-1);
```

### 実行結果(一部抜粋)
VRChat を動かしながら確認

```
[2022/02/22 23:04] VelocityY: 0.16600055 => 0.05700054
[2022/02/22 23:04] VelocityY: 0.05700054 => -0.051999465
[2022/02/22 23:04] VelocityY: -0.051999465 => -0.16099948
[2022/02/22 23:04] VelocityY: -0.16099948 => -0.26999947
[2022/02/22 23:04] VelocityY: -0.26999947 => -0.37899947
[2022/02/22 23:04] VelocityY: -0.37899947 => -0.48799947
[2022/02/22 23:04] VelocityY: -0.48799947 => -0.59699947
[2022/02/22 23:04] VelocityY: -0.59699947 => -0.7059995
[2022/02/22 23:04] VelocityY: -0.7059995 => -0.8149995
[2022/02/22 23:04] VelocityY: -0.8149995 => -0.92399955
[2022/02/22 23:04] VelocityY: -0.92399955 => -1.0329995
[2022/02/22 23:04] VelocityY: -1.0329995 => -1.1419995
[2022/02/22 23:04] VelocityY: -1.1419995 => -1.2509995
[2022/02/22 23:04] VelocityY: -1.2509995 => -1.3599994
[2022/02/22 23:04] VelocityY: -1.3599994 => -1.4689994
[2022/02/22 23:04] MuteSelf: True => False
[2022/02/22 23:04] VelocityY: -1.4689994 => -1.5779994
[2022/02/22 23:04] VelocityY: -1.5779994 => -1.6869993
[2022/02/22 23:04] VelocityY: -1.6869993 => -1.7959993
[2022/02/22 23:04] VelocityY: -1.7959993 => -1.9049993
[2022/02/22 23:04] VelocityY: -1.9049993 => -2.0139992
[2022/02/22 23:04] VelocityY: -2.0139992 => -2.1229992
[2022/02/22 23:04] VelocityY: -2.1229992 => -2.2319992
[2022/02/22 23:04] VelocityY: -2.2319992 => -2.3409991
[2022/02/22 23:04] VelocityY: -2.3409991 => -2.449999
[2022/02/22 23:04] VelocityY: -2.449999 => -2.558999
[2022/02/22 23:04] VelocityY: -2.558999 => -2.667999
[2022/02/22 23:04] VelocityY: -2.667999 => 0
[2022/02/22 23:04] Grounded: False => True
[2022/02/22 23:05] AngularY: 0 => -8
[2022/02/22 23:05] AngularY: -8 => -0
[2022/02/22 23:05] AngularY: -0 => -8
[2022/02/22 23:05] AngularY: -8 => -0
[2022/02/22 23:05] AngularY: -0 => 8
[2022/02/22 23:05] AngularY: 8 => 0
[2022/02/22 23:05] MuteSelf: False => True
[2022/02/22 23:05] MuteSelf: True => False
[2022/02/22 23:05] AngularY: 0 => -8
[2022/02/22 23:05] AngularY: -8 => -0
[2022/02/22 23:05] AngularY: -0 => -8
[2022/02/22 23:05] AngularY: -8 => -0
[2022/02/22 23:05] AngularY: -0 => -8
[2022/02/22 23:05] AngularY: -8 => -16
[2022/02/22 23:05] AngularY: -16 => -8
[2022/02/22 23:05] AngularY: -8 => 0
[2022/02/22 23:05] AngularY: 0 => -8
[2022/02/22 23:05] AngularY: -8 => -16
[2022/02/22 23:05] AngularY: -16 => 0
[2022/02/22 23:05] AngularY: 0 => -24
[2022/02/22 23:05] AngularY: -24 => -16
[2022/02/22 23:05] AngularY: -16 => -8
[2022/02/22 23:05] AngularY: -8 => 0
```

うごいてるー！！！やったー

## `OscAvatarConfig.WaitAndCreateAtCurrentAsync()` 使ってみる
`OscAvatarConfig.WaitAndCreateAtCurrentAsync()` は現在のアバターのコンフィグを取得できる。
取得できるまで待機する。 (Reset Avatar すれば取得できるようになる)

```csharp
using BuildSoft.VRChat.Osc.Avatar;
using System.Text;

Console.OutputEncoding = Encoding.UTF8;

Console.WriteLine("取得中...");
var avatarConfig = await OscAvatarConfig.WaitAndCreateAtCurrentAsync();
Console.WriteLine($"取得完了: {avatarConfig.Name}");

avatarConfig.Parameters.ParameterChanged += (parameter, e) =>
{
    DateTime now = DateTime.Now;
    Console.WriteLine($"[{now.ToShortDateString()} {now.ToShortTimeString()}] " +
        $"{parameter.Name}: {e.OldValue} => {e.NewValue}");
};

await Task.Delay(-1);
```

### 実行結果(一部抜粋)
VRChat を動かしながら確認

```
取得中...
取得完了: Koyuki
[2022/02/23 18:59] AngularY:  => -8
[2022/02/23 18:59] AngularY: -8 => -16
[2022/02/23 18:59] AngularY: -16 => -8
[2022/02/23 18:59] AngularY: -8 => 0
[2022/02/23 18:59] AngularY: 0 => -16
[2022/02/23 18:59] AngularY: -16 => -8
[2022/02/23 18:59] AngularY: -8 => -16
[2022/02/23 18:59] AngularY: -16 => -8
[2022/02/23 18:59] AngularY: -8 => -16
[2022/02/23 18:59] AngularY: -16 => -48
[2022/02/23 18:59] AngularY: -48 => 0
[2022/02/23 18:59] AngularY: 0 => -24
[2022/02/23 18:59] AngularY: -24 => -32
[2022/02/23 18:59] AngularY: -32 => -56
[2022/02/23 18:59] AngularY: -56 => -24
[2022/02/23 18:59] AngularY: -24 => -16
[2022/02/23 18:59] AngularY: -16 => -0
[2022/02/23 18:59] AngularY: -0 => 8
[2022/02/23 18:59] AngularY: 8 => 16
[2022/02/23 18:59] VelocityX:  => -0.0034728758
[2022/02/23 18:59] VelocityZ:  => 0.111127004
[2022/02/23 18:59] VelocityX: -0.0034728758 => -0.009260967
[2022/02/23 18:59] VelocityZ: 0.111127004 => 0.22448
[2022/02/23 18:59] AngularY: 16 => 8
```

## 送信をやってみる

```csharp
using BuildSoft.VRChat.Osc.Input;
using System.Text;

Console.OutputEncoding = Encoding.UTF8;

while (true)
{
    Console.WriteLine("ジャンプ！");

    OscButtonInput.Jump.Press();    // ジャンプボタンを押す
    await Task.Delay(500);
    OscButtonInput.Jump.Release();  // ジャンプボタンを離す

    await Task.Delay(500);
}
```

### 実行結果(VRChat)

<iframe width="1280" height="707" src="https://www.youtube.com/embed/EpToFbFipg0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>


## 10秒間ぐるぐるする
```cs
using BuildSoft.VRChat.Osc.Input;

OscAxisInput.LookHorizontal.Send(0.5f);
OscButtonInput.MoveForward.Press();         // 前に行く

Console.WriteLine("ぐるぐる");
await Task.Delay(10000);
Console.WriteLine("ぐるぐるおわりー");

OscAxisInput.LookHorizontal.Send(0);
OscButtonInput.MoveForward.Release();       // 前に行くのやめる
```

### 実行結果(VRChat)

<iframe width="1280" height="707" src="https://www.youtube.com/embed/ZdpEMWnfiP8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

## アバターが変わったことを検知
アバターが変わったら使用可能なパラメータを列挙するようにする。

```cs
using BuildSoft.VRChat.Osc.Avatar;
using System.Text;

Console.OutputEncoding = Encoding.UTF8;

OscAvatarUtility.AvatarChanged += (sender, e) =>
{
    var config = OscAvatarConfig.CreateAtCurrent()!;
    Console.WriteLine($"{config.Name}:");
    foreach (var param in config.Parameters.Items)
    {
        Console.WriteLine($"    {param.Name}: " +
            $"input {(param.Input != null ? "○" : "×")}, " +
            $"output {(param.Output != null ? "○" : "×")}"
        );
    }
    Console.WriteLine();
};

await Task.Delay(-1);
```

### 実行結果(一部抜粋)

VRChat を動かしながら確認

```
My CustaChan v1․0:
    Bottoms: input ○, output ○
    Wear: input ○, output ○
    VRCFaceBlendV: input ○, output ○
    VRCFaceBlendH: input ○, output ○
    VRCEmote: input ○, output ○
    VelocityZ: input ×, output ○
    VelocityY: input ×, output ○
    VelocityX: input ×, output ○
    InStation: input ×, output ○
    Seated: input ×, output ○
    AFK: input ×, output ○
    Upright: input ×, output ○
    AngularY: input ×, output ○
    Grounded: input ×, output ○
    MuteSelf: input ×, output ○
    VRMode: input ×, output ○
    TrackingType: input ×, output ○
    GestureRightWeight: input ×, output ○
    GestureRight: input ×, output ○
    GestureLeftWeight: input ×, output ○
    GestureLeft: input ×, output ○
    Voice: input ×, output ○
    Viseme: input ×, output ○

Bell_v5․0․0:
    Adder: input ○, output ○
    Menu: input ○, output ○
    BlendMode: input ○, output ○
    UseBlend: input ○, output ○
    IsEnteredBlend: input ○, output ○
    TailIsActive: input ○, output ○
    DressIsActive: input ○, output ○
    Shoes: input ○, output ○
    Socks: input ○, output ○
    Headgear: input ○, output ○
    Bottoms: input ○, output ○
    Under: input ○, output ○
    Wear: input ○, output ○
    VRCFaceBlendV: input ○, output ○
    VRCFaceBlendH: input ○, output ○
    VelocityZ: input ×, output ○
    VelocityY: input ×, output ○
    VelocityX: input ×, output ○
    InStation: input ×, output ○
    Seated: input ×, output ○
    AFK: input ×, output ○
    Upright: input ×, output ○
    AngularY: input ×, output ○
    Grounded: input ×, output ○
    MuteSelf: input ×, output ○
    VRMode: input ×, output ○
    TrackingType: input ×, output ○
    GestureRightWeight: input ×, output ○
    GestureRight: input ×, output ○
    GestureLeftWeight: input ×, output ○
    GestureLeft: input ×, output ○
    Voice: input ×, output ○
    Viseme: input ×, output ○
```

## 随時追加予定……