🐰

ブラウザベースのメタバースを作る5

に公開

前回までのあらすじ

この記事シリーズではブラウザベースのメタバースを作っていきます。
前回はメタバース実装の準備として、2Dの箱をアバターに見立てて位置と画像の同期を実装しました。

記載するコードの動かし方

第1回をまだ見てない人は、第1回の最後にある環境構築の項目を呼んで環境を構築してください。
https://zenn.dev/velserm/articles/localverse_0001
環境構築したディレクトリを「配置先ディレクトリ」とします。
その後、ターミナルを開いて以下のコマンドを入力してhttpサーバを起動します。

cd 配置先ディレクトリ
node httpd.js

次に、もう1窓ターミナルを開いて以下のコマンドを入力してシグナリングサーバを起動します。

cd 配置先ディレクトリ
node wss_signaling.js

配置先ディレクトリを基準として、ファイルの種類によって以下のディレクトリに配置します。

ファイル種類 配置先
html web/
js web/
vrm web/asset/vrm/
fbx web/asset/animation/
glb web/asset/model/
ssl証明書 ssl/
その他設定ファイル config/

ロードマップ

  1. 環境構築
  2. three-vrmのサンプルを基にしてプレイヤー操作でアバターを動かせるようにする
  3. WebRTCによるチャットを実装する
  4. (前回)WebRTCによるdom要素の位置同期と画像同期を実現する
  5. (今回)他のプレイヤーのVRMアバターを表示して同期する

今回やりたいこと

今回は、4回目で作った同期処理で、2回目で作ったVRMアバターシステムを同期します。
3回目でシグナリングサーバは完成したので、今回もクライアントの試作だけになります。

実装方針

2回目で作った move_vrm_refactor_phys.html と4回目で作った p2p_syncpic.html を統合してVRMアバターを同期するようにします
p2p_syncpic.htmlの2D箱アバターをmove_vrm_refactor_phys.htmlのVRMアバターに置き換える形で統合します。
ベースはp2p_syncpic.html側にします。
3D描画処理のコードはmove_vrm_refactor_phys.htmlから持ってきます。

所作のリモートへの反映はモーション再生時にリモートにメッセージを投げて、受信側でそのモーションを持っていれば再生、ない場合は送信側にアニメーションfbxの送信を要求します。
この通信用にrpcというデータチャネルを追加します。
rpcで、チャットと移動とファイル転送以外の状態同期の通信を行うようにします。
rpcで扱う処理は拡張される可能性が高いのでファイル転送制御用としてtransportはそのまま残します。

統合する際に、追加・削除・修正する必要がある項目を検討します。

move_vrm_refactor_phys.html側の要素

  • DOM
ID 内容 移植
myCanvas rendererの描画対象 移植
emotes モーション選択要素 移植
btn_emote 選択モーション実行ボタン 移植
  • クラス
名前 内容 移植 改修点
Avatar VRMアバター処理 移植
InputSystem アバター操作入力処理 移植 移動発生時に位置データ送信
  • 関数
名前 内容 移植
initRenderSystem 描画処理初期化 移植
setupWorld ワールド設定 移植
loadVrmFromUrl urlからVRM読み込み 移植
loadVrmFromArrayBuffer バイナリデータをVRMとして読み込み 移植
animation_loop フレーム処理関数 移植
onDropFile ファイルドロップ時処理 p2p_syncpic.html側のonDropFileにvrmとfbxの対応を移植する
setupEmote モーション選択要素設定 移植
doEmote モーション実行 移植 リモートに通知する処理追加
toggleEmote 所作ui表示切替 移植
setLocalMotion モーション設定 移植 リモートへの通知を追加
initPhysics 物理演算の初期化 移植
createPlayerBody プレイヤーの物理判定作成 移植
setPlayerVelocity プレイヤーの移動入力の物理演算への反映 移植
setPlayerJump ジャンプ操作の物理演算への判定 移植
getPlayerPhysics プレイヤーの物理演算の位置と速度取得 移植
fixFollowCamera 地形に遮蔽されている場合カメラの位置を調整する 移植
createStaticBody 地形判定の作成 移植
createStaticBodyFromMesh 地形判定の作成 移植
tickPhysics 物理演算の実行 移植
procMove 物理演算のプレイヤー位置への反映 移植
procVelocity 移動入力の処理 移植

p2p_syncpic.html側の要素

move_vrm_refactor_phys.html側で定義しているものと重複するクラス・関数・削除は削除します。
また、Avatar,InputSystemの仕様が変わるので、それらを扱う処理は修正が必要になります。
Avatarへの入力情報の仕様が変わるので、transformの通信に使っている関数も修正が必要になります。

  • DOM
ID 内容 移植
box_space 箱アバター表示コンテナ 箱アバター用なので移植せず削除
  • クラス
名前 内容 移植 改修点
Avatar アバター単位の処理をまとめている move_vrm_refactor_phys.html側を使うので移植せず削除
InputSystem 入力処理 move_vrm_refactor_phys.html側を使うので移植せず削除

AvatarとInputSystemはmove_vrm_refactor_phys.html側のものを使うのでこちら側は削除です。

  • 関数
名前 内容 移植 改修点
animation_loop フレーム処理関数 move_vrm_refactor_phys.html側を使うので移植せず削除
packTransform アバター状態を送信用データに変換 移植 データ項目拡張
unpackTransform 受信データをアバター状態に変換 移植 データ項目拡張
sendTransform アバター状態送信 移植 データ項目拡張
createLocalPlayer ログイン時のアバター関係初期化処理 移植 AvatarとInputSystemの初期化パラメータ変更
createOther 他のプレイヤー参加時のアバター追加処理 移植 Avatarの初期化パラメータ変更
removeOther 他のプレイヤー離脱時のアバター削除処理 移植
moveOther 他のプレイヤーの移動通知受信時処理 移植 パラメータ拡張
sayOther 他のプレイヤーの発言時処理 移植
restoreLocalAvatar indexedDBからアバターを復元 移植 保存場所の変更
setLocalAvatar アバターを設定しindexedDBに保存 移植 保存場所の変更
setRemoteAvatar リモートアバターに設定 移植
sendAvatar 他のプレイヤーにアバターを送信する 移植
onCompleteFileReceive ファイル受信完了時の処理 移植 モーション受信時の処理追加
onDropFile 画面にファイルをドロップしたときの処理 移植 move_vrm_refactor_phys.htmlの実装を参考にしてvrm,fbxをドロップした場合の対応を追加

onDropFileでvrm/fbxをドロップした場合は、アバター変更/モーション追加の処理をすると同時に、indexedDBへの保存をします。
これにより、リロード時や部屋移動時もアバターとモーションを維持します。
意図しないアバターを選んでしまわないようにログインが必要になった時点で保存したアバターとモーションは削除します。

  • 変数
名前 内容 移植 改修点
dic_ticks フレーム処理関数で実行する処理を積む連想配列 move_vrm_refactor_phys.html側を使うので移植せず削除
avatars アバター(箱)の管理用連想配列。 移植 中身がmove_vrm_refactor_phys.htmlのAvatarに変わる
channels_def データチャネル定義。 移植 rpcの処理追加

新規に追加する要素

  • 関数
名前 内容 説明
restoreEmote モーション復元 indexedDBに保存したモーションを復元する
setRemoteMotion リモートアバターにモーション設定 リモートから受信したデータを設定

実装結果

ソース
https://github.com/frakiaongithub/localverse/blob/main/web/p2p_syncvrm_phys.html

動画
https://velserm.com/movies/localverse/localverse-001.mp4

操作方法

操作 動作
W 前進
S 後退
A 左移動
D 右移動
space ジャンプ
マウスドラッグ 視点制御
C カメラ切り替え
ファイルドロップ(.vrm) アバター変更
ファイルドロップ(.fbx) モーション追加
ファイルドロップ(そのほか) ファイル転送
M 所作選択ui表示切替
名札クリック 所作選択ui表示切替

テスト方法
https://localhost:10443/p2p_syncvrm_phys.html を2窓で開きます。

それぞれの画面で部屋aを指定してログインします。
移動やチャットがリモートにも反映されることを確認します。
vrmファイルをドロップすると自分のアバターが更新されます。
リモートにも反映されることを確認します。
モーション用fbxファイルをドロップするとそのモーションを再生します。
自分の名札をクリックすると、モーション再生用のドロップダウンと所作ボタンが表示されます。
ドロップダウンでモーションを選択して所作ボタンを押すと、そのモーションを再生します。
このとき、リモートにもモーションが反映されることを確認します。
ブラウザのリロードを行い、アバターが維持されることを確認します。
リモートにも反映されていることを確認します。
自分の名札をクリックして、追加したモーションが維持されていることを確認します。
所作ボタンでモーションを再生されることを確認します。
このとき、リモートにもモーションが反映されることを確認します。

修正点の解説

ほとんどの修正は、Avatarクラス及びInputSystemクラスを2章のものに差し替えることへの対応ですが一部複雑なものがあります。

  • 位置同期の情報
    4章の実装は2次元用なので、3次元の座標と速度、アバターの向きを通信するように修正する必要があります。
関連関数
packTransform
unpackTransform
sendTransform
moveOther
  • 位置同期を送信する条件
    4章では、移動入力が変化したか1秒以上経過で送信しています。
    しかし物理演算がある場合は、入力がなくても位置と速度の変化がありえます。
    そこで、実際の位置と速度の変動があるか1秒以上経過で送信するようにします。
    実際にはそれだと頻度が高すぎるので最低送信間隔を設定します。
関連関数
InputSystem.tickSync
  • アバター位置の決定
    4章の実装では、ローカルアバター・リモートアバター共に入力情報から位置と速度を決定していました。
    しかし物理演算がある場合はローカルアバターは物理演算の結果から位置と速度をする必要があります。
    このためローカルアバター用とリモートアバター用で処理を分ける必要があります。
関連関数 備考
procVelocityLocal ローカルアバター用
procVelocityRemote リモートアバター用
procMove ローカルアバター用
InputSystem.defaultProcMove リモートアバター用

カスタマイズ指針

ワールドを増やしたい場合は、ページ自体を増やしてカスタマイズすることになります。
ワールドのデザインはsetupWorld関数の中でシーンに対するオブジェクトの追加として記述します。
移動やチャット以外の操作をリモートに反映したい場合は、

  const msg = JSON.stringify({"mode":"何かの処理"})
  for(const id in connections){
    connections[id].send("rpc",msg);
  }
  //ここでローカルへの反映を実行

こんな感じで送信を行い、受信側はchannels_defのrpcのonmessageで

  }else if(info.mode=="何かの処理"){
    //ここでリモートプレイヤーの操作を反映する
  }

という感じの分岐を追加します。

この試作で未対応の機能

ボイスチャットやVRデバイスについては対応していませんが、この辺については
「WebRTC ボイスチャット」とか「THREE.JS VRデバイス」とかで検索すれば実装のための情報は手に入ると思います。

終わりに

駆け足ですが、最低限の機能を持つメタバースの実装を行いました。

WebRTCやthree-vrmそのものの使い方を書いている記事は検索ですぐに見つかるのですが、それらを使ってクライアント間の同期を行う手順についてあまり見当たらないのでこの記事を書きました。

第一回にも書いた通り、インターネット上で商用サービスを構築するのには向きませんが、プログラミング教育の題材としては面白いのではないかと考えています。

Discussion