この2021年5月4日に、VRSNSのRec RoomにてCircuits Version2(CV2)というVR内でできるビジュアルプログラミング言語が正式リリースされました。
Rec Roomは、VRで楽しめるレゴやマイクラに近い無料のプラットフォームです。好きな部屋を構築したり、用意されているゲームを遊んだり、また、RecRoom内で作成した様々な制作物を売買することができるプラットフォームです。VRのプラットフォームと言いつつ、非常に多種多様なプラットフォームに対応しており、スクリーンモードとしてはWindowsPC(Steam)、iOS、XBoxに対応しており、VRのプラットフォームとしては、Oculus Quest、Valve Index、HTC Vive、Oculus Rift、Windows MR、PSVRで遊ぶことができます。
よくVR空間づくりやVRでの3Dアート制作においてはUnityやBlenderを活用することが多いのですが、このRec RoomはRec Roomだけで3Dモデリング、プログラミング、そして作ったアセットの売買までを完結することが可能です。今よく流通しているOculus Quest2だけあれば、Rec Roomにおける制作活動の全てが行えます。またこの3月にクリエイター報酬プログラムが発表され、Rec Room内通貨が現金化できるようになりました。
MakerPenとは
では、Rec Room内のクリエーションはどうやって行うのかというと、このMakerPenというツールを使います。
腕時計のウォッチメニューのBackpack内にあるMakerPenを選択すると利用できます。
このようにMakerPenには多種多様のメニューが存在し、
パレットではRec Roomの部屋に作成することができる様々なパーツを選択することができます。
Circuits V2 (CV2)とは
CV2はMakerPenのパレットのタブの一番右側に存在しています。
MakerPenのCreateを選択し、パレットから、これらのガジェットを選択して配置し、Wireで結ぶことでプログラムを作ることが可能です。
またGadgetという動作する素子の中にも、CV2対応のものがあります。
CV2の文法について
早速、CV2の基本的な文法について解説していきます。
HelloWorld!
現在では、HelloWold!を実装することはほとんどのプログラミング学習で行いませんが、ご容赦ください。Hello World!文字列を表示させるコードはこのようになります。
Event RecieverノードとShow Notificationノードの二つで表現します。Event Recieverノードは、部屋が読み込まれたイベントや30Hzで実行するなどのイベントを受け取ることも可能です。ここではMakerPenのメニューのConfigureを使って以下のようにTest Eventを受け取ります。
Event RecieverノードとShow NotificationノードはMakerPenのWireを使って結びます。またShow Notificationノードはデフォルトでは空文字列を表示する設定になっており、何も表示しないので、ここではWireを使って、”Hello World!”と設定しました。その後、パレットのCV2のタブの「Send Test Event」ボタンを押すと
このボタンを押すと
以上のように、通知として”Hello World!”が表示されます。
CV2 で利用できるガジェットについて
CV2で使えるのはこのChipだけではありません。様々なRec Room内のオブジェクトに影響を与えることができるガジェットという動作素子を利用することも可能です。
以上を簡単に説明すると、
ガジェット | 内容 |
---|---|
Button V2 | 押した/離したを検知できるボタン |
Dice Set V2 | サイコロ |
Emmiter V2 | 爆発、煙、落ち葉など様々なパーティクルを出す素子 |
Piston V2 | オブジェクトにWireで貼り付け特定の軸の位置を設定できる素子 |
Point Light V2 | 全方位を照らす明かり |
Rotater V2 | Wireで貼り付け回転をさせる素子 |
SFX V2 | 様々な効果音を出す素子 |
Spotlive V2 | スポットライトを出す明かり |
Text V2 | スタイルが設定された文字列を表示する素子、デバッグ用途にも便利 |
Toggle Button V2 | オンオフの状態を持つボタン、飛翔物の検知も可能 |
Trigger Volume V2 | 物体検知する衝突エリア |
Vector Gadget | 向きのベクトルオブジェクトを取得する素子 |
以上このようなガジェットを利用可能です。なお今後このようなCV2対応のガジェットは増えていくと思います。
順次実行について: execシグナルについて
まずは手続き型プログラミングの基礎である順次実行です。全てのオレンジ色のソケットを持つノードは、オレンジ色のソケットの左側からexecシグナルを受けて動作します。つまりこのexecシグナルの伝搬が順次実行を表しています。
以上のHelloWorldではEvent Recieverノードより、execシグナルを受けて、Show Notificationノードが動作します。この仕組みに関してはVRChatのUdonなどを実装したことがある方にはなじみ深いかもしれません。
オレンジ色のソケットだけはexecシグナルが順次に実行され、その他の色のソケットは値を表すソケットで、execを受けたノードから遡るように値を確定をしていきます。ちなみにここでは、Event Recieverノードからexecシグナルを受けっていますが、ボタンのガジェットやTrigger Volumeからもexecシグナルを受け取ることが可能です。
四則演算について
Circuit V2のタブからSearch Chipsボタンを押し、Mathを選択すると
以上のように全ての数学系のチップが入手できます。
ここでは試しに、三角形の面積である底辺x高さ÷2を実装してみましょう。ここでは数学系チップ以外にも使って、
このように実装できます。
まず最初にParse Floatノードを使って、5と4という文字列をFloatという浮動小数点数に変換し、お互い掛け算をして、2で割っています。結果をTo Stringノードで浮動小数点数から文字列に変換して、Show Notificationノードで表示します。イベントは先ほど、使ったTest Eventをそのまま利用しました。実行すると、底辺が4、高さが5の三角形の面積、10が通知として表示されます。
先ほどのexecシグナルが来たらノードが実行され、逆に、値を表すソケットは、そこから求められる値を遡るように計算されると言いましたが、ここでは、それがよくわかるのではないでしょうか。
ちなみに基本的な四則演算のノードは以下のようになっています。
- Add: 足し算
- Subtract: 引き算
- Multiply: 掛け算
- Divide: 割り算
- Modulo: 剰余
これらの他にも、アークタンジェントを求めるatanや線形補完をするLerp関数などまで存在しています。ここでは掛け算と割り算を利用しました。ちなみに足し算や掛け算は、Configureからソケットを増やすことができ、複数の足し算や、複数の掛け算を定義したノードを作ることも可能です。
データ型と変数について
次にデータ型と変数についてです。プリミティブのものだけで以下ががあります。ほぼUnity C#と同じ、というか内部的にはUnity C#のデータとなっているようです。
- bool: 真偽値
- float: 浮動小数点数
- int: 整数
- string: 文字列
- Vector3: 3次元ベクトル
となっています。これらの変数にはexecソケットが存在しており、このような感じです。
気をつけなくてはならないのは、左側のexecソケットにexecシグナルが来ると左側の値ソケットから代入が行われます。それまでは初期値は全て0になっています。boolの場合はfalseが初期値となっています。
そしてひとつ重要なことがあります。この変数名なのですがデフォルトは “Variable (int)”ですが、同じ名前の変数名は全て同じ変数となります。なので、複数の同じ型の変数を利用する際には必ず別名をつけましょう。また、Inventionとしてリリースする際には思わぬバグを埋め込みかねないので、ストアで販売するようなものは、必ず被らないように名前空間を設定して変数を用意しておくといいです(例: SifueTouchCount等)
ちなみに、この変数、デフォルトではローカル変数となり他のRec Roomの同じ部屋にいるユーザーとは共有されません。 Configure から見れる Synced というオプションを選択すると全ユーザーで同期される変数となります。
ただし、この全ユーザー共有の変数、恐ろしくCPUリソースとNetの通信リソースを利用しますので、ローカル変数のままでよければSyncedオプションを入れて同期しない方が良いでしょう。また実はSyncedオプションをオンオフすると、変数の中身が初期化されて0になるという性質があるので、簡易的に初期化したい際にこのオプションをon/offすると便利です。
論理演算について
Search Chipsの中からLogicを選択すると論理演算を行うノードが見つかります。真偽値や数値などに用います。
よく使うものを抜き出すと、
- And: かつ - 論理積
- Or: または - 論理和
- Not: 否定
- Equals: 同じならば =
- Greather Than: より大きい >
- Greather or Equals: 以上 ≧
- Less Than: より小さい <
- Less or Equals: 以下 ≦
のようになっています。他のプログラミング言語をやっている人にはなじみ深いかもしれません。
分岐について: IfノードとIf式ノードについて
分岐にはIfノードを用います。ここでは30Hz(30分の1秒)で生じるexecシグナルを1秒に1回起こるものに変換する回路をみてみましょう。
この回路では、30Hzでexecが行われ、Variable (int) という整数変数が毎回1ずつ増加するような代入が行われます。その後、その値を30で割り余りが0であればexecシグナルをThenソケットから受け取りテキストの代入に利用し、そうでなけばElseソケットのexecシグナルを利用しないという動作をします。
これが基本的なIfノードの使い方になります。ただIfノードはいわゆる順次実行を行うIfのexecシグナルの分岐を行うものですが、If Expressionノード(If式ノード)は、値の分岐を行います。
If Expressionノードの例も見てみましょう。ここでは、Aソケットに年齢を入力して18歳より大きい年齢なら、2000円の入園料が、そうでないならば1600円の入園料がresultから得られるという回路を実装しています。
このようにIf Expressionノードはexecシグナルを使わず必要とされたときに値ソケットを遡っていきながら計算が算出される仕組みになっています。関数型プログラミングをやっている人にはこのIf式はおなじみかもしれません。
イベントの送受信及びイベントの定義について
既にイベントの受信を定義しましたが、自分自身でイベントの定義とイベントの送信も行うことができます。イベントの定義はEvent Definitionノードで、イベントの送信はEvent Senderノードで実装可能です。
ここでは、CellUpdateというイベントを定義し、そのイベントにはAliveListというList<int>型の情報を持たせています。この情報も沢山情報を持たせることも可能です。そしてEvent Senderノードでは作成したAliveListを別から値取得してCelUpdateイベントを配信しています。配信されたイベントは全ユーザーにグローバルに配信され、それをEvent Recieverで受け取ることができます。
繰り返しについて: Forノード
繰り返しにはこのForノードを使います。
index値が巡回数値になりますが、Fromに0を入れて、Toに6を入れた場合には、0, 1 , 2 , 3 , 4 , 5がindexの値となり、Loopが6回実行されますが、indexが6で来る場合はないので注意しましょう。また全てのループが実行されたら、Doneソケットよりexecシグナルがでます。
ただしこのForノード、かなり曲者で、オブジェクトの処理を20~30させるとすぐにリソースのCPUを100%全て使い切ってしまいます。Deleyノードをつかって、次の処理を数秒待って実行するということもできずにLoopソケットからexecシグナルが出て、並行処理がされてしまうという問題があります。
それを回避するためには、Event DefinitionノードでLoopEventというような実行回数を持つイベントを作成し、それをRestCountという実行残数を管理しながらイベントを再送信するということで疑似ループをつくってやり、その中でDelayをさせてやることで解決が可能です。
以上のようなLoopEventはCPUリソース管理のためにおそらく必須のテクニックとなるでしょう。
コレクションの操作について
コレクションとしてはリストを利用可能です。変数としてもListを用意できますが基本的には変数名をしっかりと定義しておかないと複数のリストを扱う際に事故が起こりやすいです。
以上では、UpdateAliveListという名前のList<int>を定義しています。これは、整数が入ったリストのコレクションになります。ただし、これをエレメントを追加したりForEachノードで巡回したりする場合には、まずは空のリストを追加しておく必要があるので要注意です。
また、List<RecRoomObject>というリストがあるのですが、RecRoomのオブジェクトを入れられるリストですがそれがピストンなのかボタンなのかという型情報は失われるので注意が必要です。タグを読み込んでそこから型がなんだったかを予測することはできますが、Get Elementノードで値を取得後、Piston Set Distanceノードを利用して、ピストンの距離を設定をするということなどはできません。
ノード定義及びノード内の関数の定義について
ノードは自作することが可能です。Circuit Boardノードを用いて、MakerPenメニューでEditすると、中に存在する関数とその処理を実装することができます。
以上では、インデックスが25までしかないListからエレメントを取得する際、そのインデックス外の値を参照した際にはデフォルトの値を取得するというSaftyGetElementノードというものを定義しています。(ちなみにGet Elementでインデックス外を参照すると内部的にエラーが起こりexecシグナルが消失します)
このCircuit Boardは同じ回路の繰り返しなどや処理をまとめたい際にとても便利なので多用したほうがよさそうです。またChip数に制限があるため、ある程度Circuit Boardノードで回路をまとめてその制限を回避していくためにも利用できます。
実装時の制限について: CPU、Net、Chips
ここまでで既にちょっと話には出てきましたが、実はこのCV2にはリソースの制限があります。
パレットのCircuit V2タブを見ると表示されていますが、
- CPU: 計算リソース
- Net: 通信リソース
- Chips: チップ数
となっており、表示されているゲージを超えることはできません。CPUが100%を超えた瞬間にexecシグナルが停止するという挙動をし、エラーなどは表示されないため、このリソース制限越えによる不具合は非常に厄介です。
対策としては、CPUはForノードを回したりすると上がってしまうのでLoop用イベントとDelayを使うことでCPUのスパイクを避けます。そしてNetに関しては、そもそもイベントを投げる回数を減らすように工夫しましょう。ChipsはCircuit Boardノードを利用して同じような処理を抽象化して利用するチップ数を減らすようにしましょう。
他にも沢山有益なノードは沢山あるのですが、一般的なプログラミングが、これでCV2を使ってできるようになったのではないでしょうか。
参考実装: コンウェイのライフゲーム
最後にここで得た知識をつかって、コンウェイのライフゲームをCV2で実装してみました。
ルーム: ^SifueLifeGameにて、見たりコピーすることができますので、もしよかったら見てみてください。またこの部屋の30秒程度の紹介動画もどうぞ。
参考資料
最後に
CV2でのプログラミングはいかがだったでしょうか? ぜひ、Rec Roomを利用して、VRのプラットフォームでのものづくりやプログラミングに入門して楽しんでいってもらえればと思います。ちなみにRec Roomでは2~3カ月に一回ルームコンテストが行われていたり、毎月、ゲーム内通貨がもらえるテーマが決まっているものづくりコンテスト、MakerPenChallenge が行われていますので、ぜひチャレンジしてみてください。