VRChatでOSCを使ってケモ耳をアナログ時計にした

# はじめに
アバターの耳をアナログ時計にしてみたかったのでしてみました。簡単に手順を紹介します。各種用語の説明についてはリンク先をご覧ください。
# できたもの
アホ毛を引っ張ると両耳で時刻を教えてくれます。アホ毛を固定するとそのまま時刻を表示しつつけ、元の位置に戻すと解除されます。

https://twitter.com/__tou__tou/status/1529087201220255744?s=20&t=Uj8lSo_g1ZYfJQVkhMJ7qQ

# 誰向け
- このアホ毛耳時計がどうやって実現されているのかなんとなく知りたい人
- [Avatars 3.0](https://docs.vrchat.com/docs/avatars-30)と[PhysBones](https://docs.vrchat.com/docs/physbones)がちょっとわかる人
- Animatorを組むときにWriteDefaultsの挙動がちょっとわかってる人向け
    - [Write Defaults on States](https://docs.vrchat.com/docs/avatars-30#write-defaults-on-states)
- ちょっとコード書ける人
    - 本記事では簡単なOSCクライアントをgolangで実装しています
    - golang以外で実装したい人は[OSC Overview - VRChat](https://docs.vrchat.com/docs/osc-overview)を参考にしてください。


# 用意したもの
- [lyuma/Av3Emulator v2.9.8](https://github.com/lyuma/Av3Emulator/releases)
    - ダウンロードしてUnityPackageをProjectフォルダーにD＆Dすると導入できます
- VRChat v2022.2.1
- Unity 2019.4.31f1
- VRCSDK3-AVATAR-2022.05.04.17.47
- Golang1.16

# 手順

大まかな手順は下記の通りです。
1. アホ毛を引っ張れるようにする
1. VRChatのExpressionMenuから耳を操作できるようにする
1. 時刻をOSCでVRChatに送る
1. タスクスケジューラーにOSCクライアントを登録する

### アホ毛を引っ張れるようにする
アホ毛をお持ちの方はアホ毛に下図のように[PhysBones](https://docs.vrchat.com/docs/physbones)を追加し[MaxStretch](https://docs.vrchat.com/docs/physbones#grab--pose)と[OptionsのParameterとIs Animated](https://docs.vrchat.com/docs/physbones#options)を指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1333433/c19b67e8-04be-5982-0c25-902b2ba327ee.png)


Editorの再生ボタンを押して、アホ毛が固定できることを確認します。また、Optionsで指定したパラメーター(今回の場合`ahoge`)から`ahoge_Stretch`が生成されます。
このパラメーターは[PhysBone AV3 Parameters](https://docs.vrchat.com/docs/physbones#physbone-av3-parameters)と呼ばれ、`VRCExpressionParameters`のように定義しなくてもローカルとリモートで同期されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1333433/91183b6e-444f-dcc4-cc97-19b32d3ab1d7.png)


### VRChatのExpressionMenueから耳を操作できるようにする
ExpressionParameterから操作できる必要はないのですがデバッグ用です。

#### FXLayerに時計用のアニメーションを追加する
[Playable Layers FX](https://docs.vrchat.com/docs/playable-layers#fx)を読む限り、耳は非HumanoidBoneであることとFXLayerは(おそらく)GestureLayerの非ヒューマノイドボーンのTransformを上書するという点からFXLayerで耳のTransfomのAnimationさせるのでよさそうです。
##### Animatorの作成
まずは下図のようにAnimatorのParametersに先ほど生成した`ahoge_Stretch`と時計の時と分のためのParameterを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1333433/7f3aee44-0867-368a-5f79-5591d9ada252.png)

次に左耳用と右耳用の空のアニメーションを作成し、下図のように左耳と右耳用のAnimatorを組みます。私の環境ではFXLayerはすべてWriteDefaultがOnなのでWriteDefaultをOnにしています。Offの人はNewStateでデフォルトの耳のアニメーションを作り追加しましょう。
また、Layersの上から下の順にanimationが実行され上書されることに注意が必要です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1333433/f7b85fce-5269-b250-c443-130729f998dc.png)

Transitionは下図のようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1333433/fd086b3a-615e-e8b3-2fe2-06db17d0a3dc.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1333433/e4c997ff-08ca-eb3b-ec2a-c7f17c4b2954.png)


##### animationの作成
下図のようにFXLayerのコントローラーをAnimatorのContollerに設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1333433/d9ae0636-b24f-691c-4e78-ba10b171548d.png)

HierarchyでAnimatorコンポーネントがついたGameObjectを選択し、Animationタブを開くと、FXLayerのAnimation
一覧がでてくるので先ほど作った空のアニメーションを選択します。その後、いい感じに60フレーム分(0~59)のアニメーションを作成します。また、Animationで耳の長さを変えたい場合は、回転させるボーンの子のボーンのPositionを移動させるのが良いです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1333433/ea3b4dfd-fb97-5074-9140-2c33041fb4ae.png)


デフォルトだと回転の仕方が滑らかなので線形にします。keyframeを右クリックしてLinearを指定します。[カーブの編集 - Unity マニュアル](https://docs.unity3d.com/ja/2019.4/Manual/EditingCurves.html)が参考になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1333433/6b3d949e-529a-d52d-aa53-d23620781df9.png)

右耳のAnimationも同様に作成します。


#### ExpressionParameterとMenuを設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1333433/09da82be-4651-7eb2-6c4e-ee96424b2443.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1333433/6792783d-33eb-304d-0bf1-376ced3e685c.png)

#### Editor上で実行してlyuma/Av3Emulatorで確認
[lyuma/Av3Emulator v2.9.8](https://github.com/lyuma/Av3Emulator/releases)を導入し、ToolsからAvatar3.0Emulatorを追加し、Editor上で再生、GameViewでアホ毛を右クリックで掴んで左クリックで固定、Hierarcyでアバターを選択し、InspectorのGesture Manager Av 3 MenuでExpressionMenuを操作します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1333433/fecf7914-1b87-6872-5db8-0dae1cee1527.png)

### OSCクライアントの実装
私はgolangで書きましたがclientはなんでもいいです。VRChat的には[これ](https://docs.vrchat.com/docs/osc-overview#osc-tools)がおすすめのようです。

[OpenSound Control - Wikipedia](https://ja.wikipedia.org/wiki/OpenSound_Control)はアプリケーションプロトコルでトランスポートはUDPが多いようです。(時々VRChatのデバッグ画面でOSCの受信が確認できないのはUDPだからなのか？)
#### VRChatでOSCを有効化する
下図のようにアクションメニューからOSCをEnabledにすると、AvatarParametersのconfigファイルが生成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1333433/72e82259-4b86-49c9-e1d8-966ecc0fecd3.png)

#### OSCのアドレスを確認
OSC用のアドレスは`C:\Users\{User}\AppData\LocalLow\VRChat\VRChat\OSC\{userId}\Avatars\{avatarId}.json`にあります。[OSC Avatar Parameters](https://docs.vrchat.com/docs/osc-avatar-parameters)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1333433/3abdfe05-778b-5ad9-a89d-334f54866bd2.png)

#### golangでのOSCクライアントの実装

golangだとこんな感じで書けます。
```OSCSend.go
package main

import (
	"time"

	"github.com/hypebeast/go-osc/osc"
)

func main() {
	client := osc.NewClient("127.0.0.1", 9000)

	for {
		msgMin := osc.NewMessage("/avatar/parameters/LeftMin")
		msgHour := osc.NewMessage("/avatar/parameters/RightHour")
		t := time.Now()
		hour := t.Hour()
		minute := t.Minute()
		// fmt.Print(t.Local().Hour(), t.Local().Minute(), "\n")
		// 分を60分に対応する[0,1]のfloatに変換
		fminute := float32(minute) / float32(60)
		// 時間を12時間に対応する[0,1]のfloatに変換
		fhour := float32(int(hour%12))/float32(12) + fminute/float32(12)
		// fmt.Print(fhour, fminute, "\n")
		msgMin.Append(float32(fminute))
		msgHour.Append(float32(fhour))
		client.Send(msgHour)
		client.Send(msgMin)
		time.Sleep(time.Second)
	}
}
```
https://github.com/tou-tou/go-osc

#### ビルド
```
go build OSCSend.go
```

- GUIを表示したくない場合
```
go build -ldflags="-H windowsgui" OSCSend.go
```

OSCSend.exeの名前の実行ファイルが生成されます。

### タスクスケジューラーでWindowsログオン時にプログラムを実行する
VRChat起動時に毎回OSCをアプリを起動するのは手間なのでWindows標準アプリのタスクスケジューラーに任せます。

Windowsの検索欄から「タスク　スケジューラ」を入力し起動します。

下図のようにタスクの作成からトリガータブを選択、新規ボタンからタスクの開始をログオン時に指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1333433/4a881388-0d8c-ab27-b1d9-c30b4ed46d43.png)


下図のように先ほどビルドした実行ファイルを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1333433/778b4b64-a8be-40dc-4554-85cf6c8c0ddc.png)


これでWindows起動時にOSCクライアントが起動するようになりました。この状態でVRChatを起動すればOSCで時間情報を受信し両耳に時間が反映されるはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1333433/a3e61080-81e9-6c81-1fd6-0b2ffbbc2b38.png)


# 最後に
Avatar3.0周りはそれぞれで環境が違うので同じように実装できるとは限らないのですが、部分的にでも誰かのお役に立てれば幸いです。また、ツール開発者に感謝を！