追記 (2023/01): この記事は 2020 年に書いたもので、現在の Udon の話ではないので、いろいろと差異があることを前提として読んでください。
参考資料
- Merlin-san/UdonSharp
- 前提
- UdonSharpコード走り書きメモ - やぎりのブログ
- コード片
- UDONメモ - 黒鳥のメモ
- 用語や概念など
- U# 入門 ① - ハツェの真時代傾向璋
- 入門記事
- Udon Extern Search
- 関数の存在を確かめるのに便利
- その他
基本
- ファイル名と一致する名前でないクラスを宣言できない
Only one class declaration per file is currently supported by UdonSharp
- 多次元配列はダメ (1次元にして)
UdonSharp does not supporrt multidimensional or jagged array accesses yet
- (v0.15.5) もともとjagged array (
int[][]
) は動いたらしいのでコレは使えます (int[,]
はダメ、まぁ大丈夫そう)
- Genericのある型もダメ (配列のみ)
Cannot resolve generic arguments on non-method expression
- 宣言時の型がここでの
expression
にあたりそう - 逆に言えばメソッドのGenericコールはできる (
GetComponent<>
は動く)
- 宣言時の型がここでの
- 確保とかは自由にできるので自前実装すれば良い
- 自作の
out
変数を受け取る関数も動かない (が、コンパイルは通るので怖い) uint
やlong
の剰余演算など、幾らか存在しないメソッドがある- 伴ってTexture系やComputeShaderも使えない
VRC系の機能
- まずPickupやStationは今までと変わらず対応コンポーネントが存在する
VRC.SDK3.Components
名前空間- PickupやStationなどが定義されてるところ
- Pickupは(従来どおり)
PlayHaptics
で振動させたりできる
Networking
名前空間LocalPlayer
自分のこと (Unity上では存在しない)SetOwner
所有権を与えるのに使えるIsOwner
所有権を確認するのに使えるIsMaster
Masterか判定するのに使える- 他はほとんど使えないと思います
VRCPlayerApi
(Networking.LocalPlayer
の型)UdonSharpBehaviour
- 諸々をoverrideすることで今までのTriggerの代替を実装することができる
Interact
だけ "On" がついていない (何故?)- 一覧 U# 入門 おまけ - ハツェの真時代傾向璋
SendCustomNetworkEvent
- publicメソッドの名前を指定するとそれをネットワーク越しに呼んでくれる (同期用)
- 引数は与えられない
VRCInstantiate
- 諸々をoverrideすることで今までのTriggerの代替を実装することができる
同期
基本的に従来どおり大変です。まず分散処理は大変なのでしょうがないのです。
[UdonSynced]
属性- 全プレイヤーに同期される変数
- Primitive型とか
Vector
/Quaternion
くらいしかやってくれない - 代入できるのはそのGameObjectのOwnerのみ
- 所有権を持ってない人が使う場合は
Networking.SetOwner
してからちょっと待たないとダメ- どれくらい待つべきかは未検証な気がする (分散処理的にちゃんと合意が取れてからじゃないと怖そう)
- 所有権を持ってない人が使う場合は
- 代入後即時は反映されない
- late-joinerはおそらくこれを受け取ってから実行を開始する?(未検証) ので最初から代入されてるかも (知らん)
- ちょっと待つと同期されるけどその間隔は未規定だと思います
SendCustomNetworkEvent
メソッド- 全プレイヤーに指定したpublicメソッドを呼ばせることでトリガー的同期を行う
- Eventの消失は起きないが順序は規定されない
- 1F間隔で呼ぶとほぼ確実に順序通り届く (検証)
- UdonBehaviourのSynchronize Position
- 位置と回転を同期します (Scaleはされないと思う(Photon的に))
- おそらく UdonSynced と同じくらいの粒度なので即時の反映は期待できない
トリガーをひたすら叩くタイプの仕組みは基本的にうまくいくと思いますが、複数人がほぼ同時に SendCustomNetworkEvent
を発火したときに大丈夫にするようにする必要はありそう (Ownerしか呼ばないとか)
デバッグ
- ログファイルは
C:/Users/.../AppData/LocalLow/VRChat/VRChat/output_log_....txt
にある- 現在のログは最新の時刻が記録されているもの (過去のはほっとくと消えていく)
- Udonがクラッシュした場合の詳細はここを見るのが早い
- VRChatの起動オプションで内部で色々見れる
- VRCのデバッグ情報は「右シフト +
@
+ 数字キー」で表示切り替えができる (つまりバッククォート + 数字)3
でコンソール、7
でトリガー、8
で所有権やオブジェクト名などの情報7
,8
での表示はワールド作者 (か運営) のみ (ワールドの設定で許可した場合は全員見られる)
Debug.Log
は動く (VRCのコンソールにも出る)- ちょっと面倒なので自分でCanvas/Text使ったLoggerを作ったりもしたけど
注意点
- そこそこの頻度でUdonまたはU#がクラッシュする
System
とかその辺のエラーログが出てきたらヤバいSystem.Reflection.ReflectionTypeLoadException
とかDid not capture a valid namespace
とかRecursive type definition detected
とか
- エラーログをクリア → 適当なC#ファイルを編集、UnityにコンパイルさせてUdon本体っぽいエラーが出てきたらおしまい (再起動)
- エラーが出てこないが既存のC#ファイルで上書き保存をしてUdonSharpがコンパイルして正常っぽくないエラーが出てきたらおしまい (再起動)
- 一応治ることもそこそこある
- UdonBehaviourを編集してもシーンに変更があったことにならない (SetDirtyされない)
- つまりこの状態でいくらCtrl+Sしても保存されない
- 新規GameObjectを作成して消去したりUdonBehaviour以外のチェックボックスをダブルクリックしてから保存したりしています
Start
メソッドの実行順は未規定らしい (詳細は未検証)- 乱数の同期について
- 基本的に
Random.InitState
で同じ値を与えられれば以降の乱数は同じものが吐かれる- が、これは「誰か」が乱数にアクセスしただけでズレるので怖い
- ループ内でひたすらアクセスする分には大丈夫そう (ureishiさんによる検証)
- または複数のUdonBehaviourが触りに行くと順序未規定でズレるかもしれない
- ちなみに
Random.state
を取得できないのでUnityの機構を流用することができない
- が、これは「誰か」が乱数にアクセスしただけでズレるので怖い
- 自前で乱数生成が安定感ある (私のコード)
- ちなみにseedをUdonSyncedさせたりするとヤバい (Owner以外が新たな乱数を引けない)
- 「あるタイミングで現行の乱数の種を全員統一する」のが正しい
- ちなみにseedをUdonSyncedさせたりするとヤバい (Owner以外が新たな乱数を引けない)
- 基本的に
SendCustomNetworkEvent
について- VRChatじゃないと動かない
- デバッグ用に
#if debug
とかでイベントを直接呼ぶコードを書いたりした
- デバッグ用に
- publicじゃないと動かない (仕組み的にそうっぽい)
- VRChatじゃないと動かない
OnTriggerEnter
はプレイヤーを取得できない- セキュリティ的な問題だと思う (参考)
VRCPlayerApiのBone取得について
GetTrackingData
で取れる位置は見かけの位置と一致するGetBonePosition
/GetBoneRotation
は本当にボーンの (ワールド空間上の) 位置を拾ってくる
Vector3[] tipDirCandidate = new Vector3[6] { new Vector3(+1,0,0), new Vector3(0,+1,0), new Vector3(0,0,+1), new Vector3(-1,0,0), new Vector3(0,-1,0), new Vector3(0,0,-1) }; Vector3 estimatedTipDir = Vector3.zero; Vector3 dir = (fingerPos[3] - fingerPos[2]).normalized; Quaternion rot = fingerRot[2]; float matchAmount = 0; for(int i=0;i<6;i++) { Vector3 cand = rot * tipDirCandidate[i]; float d = Vector3.Dot(dir, cand); if(matchAmount < d) { estimatedTipDir = tipDirCandidate[i]; matchAmount = d; } }
しょうもない話
- 宣言されてない変数で
a;
みたいなことをやってもUdonSharpは文句を言わない (C#でコンパイルエラーにはなる) 宣言されてる変数で(これはC#の仕様だった (まぁそれはそう))a;
みたいなことをやると文句を言う (Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement
)(v0.15.5で修正)int b = a;
で宣言されてない変数a
を参照するとエラーメッセージがおかしい (The name '' does not exist in the current context
)- 普通にバグだと思うけどソースをちらっとみた感じ直すのがだるそうだったので何も報告していない (よくないけどどうせ困らん気はする)
(v0.15.5で追加)while(f());
が書けない (UdonSharp does not currently support node type EmptyStatement
)- まぁ書き直せばいい
直接の報告は投げてないのに対応してくださってありがとうございます :pray: