Narazaka::Blog

奈良阪という人のなにか

うちのNoyちゃんが一番可愛い!を実現する グローバル同期プレイヤー追従NPC in VRChat

最近VRChatで、プレイヤーについてくるNPCシステムがちょっとした話題になりました。

ワールドに入ると静かに佇んで待っているNoyちゃん。

booth.pm

なんだろうと思いつつサイバーな雰囲気の街を探検しようとすると、なんとNoyちゃんが自分の後ろからトコトコついてくるのです!

つかずはなれずの位置でこっちを見ているNoyちゃんが大変愛らしい最高の体験ができるワールドなのですが、しかしその愛らしさを今そこに感じているのはあなただけ。残念なことにこのNoyちゃんはローカルでしか見えません

つまりそのかけがえのない光景を他人には共有できないのです。

今、そこに確かにいるNoyちゃんを、他の人から見ることはできない……。私の隣にいるNoyちゃんが他の誰よりもかわいいんだと(錯覚)、その抑えきれない感情を、誰にも証明することができない……。

……まあ、そういうのはそういうのでまたエモみがあるシチュエーションではあるのですが……。

というわけで、前置きが長くなりましたが…… グローバルに同期するプレイヤー追従NPCアセット を配布します。

グローバル同期プレイヤー追従NPC

narazaka.booth.pm

f:id:narazaka:20191107144938p:plain
みんなについてきているNPCが見えます

基本的に既存のローカルNPCシステムとグローバルプレイヤー追従システムを組み合わせただけです。

すでにこれを実現したワールドとして Bless The Rainsが存在し、犬(Dogeミーム)のGenericアバターがついてきます。

このワールドを参考にして、Humanoidアバターでグローバル同期するプレイヤー追従NPCを構築したのがこちらのアセットです。

以下このアセットについての紹介です。

ちなみに実際にこれを動作させたサンプルワールドはこちら vrchat.com

必要アセット

基本的な使い方

  1. 必要アセットをインポートしてからこのアセットをインポート
  2. Sync_NPCシーンを開く
  3. PropSpawner/SpawnedItem/pony-tail-01-BUを好きなアバターに入れ替えてコンポーネントの設定を移す。
  4. ワールドの床を好きな地形に変更してから「Window→Navigation」を開き、AgentsとBakeの項目を設定してから「Bake」ボタンを押してNPCが歩ける領域をベイクする。
  5. アップロードする

注意点

  • PropSpawner/SpawnerについているVRC_TriggerのうちSpawnObjectが設定されたActionを通常のモードで開いてはいけません。 もし開くとSpawnedItemをSpawnObjectする設定が消えてしまいます。 debugモードでのみここは編集可能です。 詳しくはToyBoxの当該部分のReadmeを読んでください。
  • PropSpawnerを複数使いたいという場合は特殊な操作が必要になるので、下記の「別々のNPCを選択して出現させたい」を参考にしてください。
  • 必要アセットのプレハブをそのまま使用するためにあえて変更したPropSpawnerをプレハブ化していません。複製などする場合はそれぞれでお願いします。(プレハブのネストが解禁されるUnity 2018も来るしね)

参考: コンポーネントの設定のうつし方

Copy ComponentとPaste Component Valuesなどを使ってください。

f:id:narazaka:20191107002228p:plain
copy component

ユースケース

しゃがみや滞空モーションを消したい

  • ThirdPersonAnimatorControllerをCtrl+Dで複製して、Animatorウインドウで「Crouching(しゃがみ)」や「Airborne(滞空)」ステートを削除したものをアバターのControllerとして設定します。
  • しゃがみだけ消して滞空モーションを残しておきたい場合は、「Airborne→Grounded(接地)」のTransitionから「Jump」に関する条件を削除しないと、なぜかずっと滞空モーションになる問題が発生したりします。

キャラクターを押したい

  • Combat Systemを有効にする(最も簡易的にはVRC_PlayerModsでHealthの項目を追加する)とプレイヤーにコライダーがつくらしく、NPCを押せるようになります。(ColliderのIsTriggerをオフにする必要があるかもしれません)
    • たくさんNPCがいるのを押しのけていくの、なんだか結構気持ちいい体感がありました。

近づいたときにのけぞりすぎるのをなんとかしたい

  • Final IKの数値設定を頑張る(Clamp Weight調整が良さそうな気がするが試行錯誤がそれなりに必要そう)。
    • Final IKの挙動確認自体はUnity上で可能なので、Targetをてきとうに近づけたりして確認すると早いです。
  • Combat Systemを有効にして、一定距離以上近づけないようにする。
  • しゃがみモーションで回避する(のけぞるのと同じくしゃがみもアレなので微妙そう)。
  • 身長の小さいアバターNPCとして使う(Tioちゃんくらい小さいと案外気にならない感じあります)。

booth.pm

NPCを自動出現ではなくボタン等で出現させたい

  • PropSpawner/SpawnerについているAnimatorを無効にすることで、自動Spawnしないようにします。
  • あとは任意のVRC_TriggerからActivateCustomTriggerアクションでPropSpawner/SpawnerのSpawnOwnedアクションを呼び出してください。

別々のNPCを選択して出現させたい

複数のNPCを選択して出現させるなどが必要な場合は、PropSpawner自体を複数設定します。

このとき

  • それぞれのSpawnedItemは全てVRC_SceneDescriptorのDynamic Prefabsに指定しなければならない
  • Dynamic Prefabsに指定したシーン内のオブジェクトは全てオブジェクト名がユニークでなければならない
  • SpawnedItemのオブジェクト名とPropSpawner/SpawnerのSpawnObjectアクションに指定している対象オブジェクト名が一致しなければならない

という全ての制約を満たす必要があります。

なので、PropSpawnerを複製してPropSpawner (1)を作った場合、

  1. PropSpawner (1)/SpawnedItemPropSpawner (1)/SpawnedItem (1)等に変更する。
  2. PropSpawner (1)/SpawnedItem (1)をVRC_SceneDescriptorのDynamic Prefabsに指定する。
  3. インスペクターをDebugモードに変更する(インスペクタ右上の錠前マークの横メニュー、あるいはタブで右クリックしてDebugを選択)。
  4. PropSpawner (1)/SpawnerのVRC_TriggerのうちEvent TypeSpawnObjectになっているものを見つけ出し、そのParameter StringSpawnedItemからSpawnedItem (1)に書き換える。
  5. インスペクターをNormalモードに変更する。(注意: 以後PropSpawner (1)/Spawnerの当該SpawnObjectアクションは開かない)

という手順を行ってこの制約を満たします。

日本語での参考: SpawnObject - VRChat 技術メモ帳 - VRChat tech notes

これを行った後、「NPCを自動出現ではなくボタン等で出現させたい」を参考にTriggerなどを設置すると良いと思います。

NPCを何かのボタンで消す等、その他のアクションを外部から行いたい

  • ToyBox/PropSpawnerのreadmeにありますが、(Collider) RelayというActionを使う必要がある模様です。readmeのとおりToyBoxのサンプルシーンを見てください。

複数のNPCを追従させたい

  • 固定メンバーで良い場合、SpawnItemの中に複数のアバターを放り込めばそのまま使えます。
  • そうでない場合「別々のNPCを選択して出現させたい」で複数出現可能にするのがよさそうです。

近づく時に表情変化させたい

アバターにColliderを付けてVRC_TriggerでAnimatorのステートを操作します。

アニメーションオーバーライドと同じ表情アニメーションをそのまま使いたい場合
  1. ThirdPersonAnimatorControllerをCtrl+Dで複製し、Animatorに複製後のものを指定し直します。
  2. Animatorウインドウ左上の「Patameters」からbool型でnearという名前のパラメーターを追加します。
  3. Animatorウインドウ左上の「Layers」からレイヤーを作成し、歯車マークの設定から「Weight」を1にします。
  4. そのレイヤーの何もないところを右クリックして「Create State→Empty」で2つステートを作ります。Entryステートからすでに矢印が出ている方をidle、そうでない方をnearなどと名付けます。
  5. idleステートを右クリックして「Make Transition」から遷移矢印を作り、nearにつなぎます。nearステートからも同様にidleにつなぎます。
  6. idle→near、near→idle両方の矢印の設定で「Has Exit Time」のチェックを外し、Settings→Transition Durationを0にします。
  7. idle→nearのConditionsにnearパラメーターがtrueという条件、near→idleのConditionsにnearパラメーターがfalseという条件を追加します。
  8. nearステートに笑顔のAnimationを指定します。
  9. アバターの直下に空のGameObjectを作り、Colliderを付けます。ColliderのIsTriggerを有効にします。layerはDefaultで良いと思います。
  10. VRC_TriggerのOnEnterTriggerでAnimationBoolをnearパラメーターに対してtrue、OnExitTriggerでfalseになるよう設定します。 f:id:narazaka:20191107013311p:plain

なお笑顔Animationが変な挙動になる場合、Animationの中にあるボーン関係のキーを消しておくと上手く動くと思います。

NPCが階段をのぼらない

  • NPCシステム構築メモ - VRChat KemonoClub Wiki によればRigidBodyをIsKinematicにすると解決するらしいですが、代わりにモーションに対して移動距離がすくない違和感のある挙動になるので、どうすれば良いのか教えてください。
  • とりあえずスロープにすればのぼってくれます。

NPCが段差にひっかかる

  • 階段のぼれるようになると解決しそうです。
  • 飛び降りができる設定を無くしても解決するかも?

アバター展示会みたいなのやりたい

  • 是非やってください。
  • 近づくと笑顔とか手を振るとか設定すると愛らしそうです。
  • VRChat用アバターはワールドに利用して良い規約のものも多いので、作者以外の人も規約を確認して自分が買った販売アバターを布教するワールド作っても良いかもしれません。宣伝画像とかも置いておくと良いかも。

NPCと手をつなぎたい

  • ちょっと試してみたけどうまくいかないので教えて……。
  • Final IKに含まれる「Biped IK」で手を引くことはできるのだけど、そのオブジェクトをPickupにするとRigidBodyとNavMeshAgentがなぜか干渉して挙動が変になるんですよね……。

NPCと無理心中したい

  • 無限に落下させることは難しそうなので、低い床を作って移動範囲にしたうえで、その少し上を水面にしたり、リスポーン平面にすると良さそう(未検証)?

NPCドラクエみたいな隊列にしたい

  • やってないので分からないですが、固定メンバーで良いなら前のNPCのheadボーンに追従させると良さそうな気がします。

無言でどこまでもついてくるのホラーでは?

  • ホラワに実際使えそうです。
    • 特定の人にとりついて追いかけ回すと怖そう(移動速度も速くできる)。
      • Colliderも付けておいて当たると死ぬとか(Combat System)
    • たくさん追従させて取り囲むとか。
    • というかゾンビっぽい。

重くない?

  • 画面としてはNPCアバター分人が増えたのと同じだし……。
  • 通信負荷としてはObjectSync分は増えると思います(普通のPickupとかと同じ?)。

参考: 1からこのアセットを作るときのおおざっぱな構成

  1. ToyBoxのPlayerTrackingをワールドにいれる。(プレイヤー視点追従の仕組み)
  2. ToyBoxのPropSpawnerをワールドに入れる。(プレイヤー視点にクリスタルが追従するプレハブ)
  3. PropSpawner/SpawnedItemをVRC_SceneDescriptorのDynamicPrefabに追加する
  4. PropSpawnerが現行VRChatだと動かないため、SpawnedItem内のOnEnableを全てOnTimerの0秒後実行に変更する。
  5. PropSpawner/SpawnedItem/FloatCrystal/Visualを非アクティブにする。(クリスタルの見た目は使わないため)
  6. PropSpawner/SpawnedItemアバターを突っ込む。
  7. Navigationを設定し、アバターにAICharacterControlやAnimation Controllerをつけ、数値などを調整する。(ローカルNPCシステム)
  8. AICharacterControlの追従ターゲットをFloatCrystalに設定する。またFloatCrystalの高さオフセットは0にする。(プレイヤー視点追従)
  9. アバターにVRC_ObjectSyncとVRC_Pickup(Pickupableオフ)をつけ、アニメーションなどが同期するようにする。
  10. 今回サンプルにしたアバターではBodyにもAnimation Controllerがついているのでそちらにも同様にVRC_ObjectSyncとVRC_Pickupをつける。
  11. Final IKをアバターに付け、追従ターゲットをFloatCrystalに設定し、ボーン等の設定を行う。(プレイヤーの方を向くなどの姿勢制御)(オプショナル)

この方法はプレイヤー視点の位置を同期し、各自のローカルでNPC移動を計算するのがベースの方式ですが、逆にNPC移動した結果を同期しようとすると方向などが同期されないことがわかりました(試行錯誤した結果)(ObjectSyncの中身を知らないので理由はよく分からない)。

ライセンス

  • アセットの仕組み自体はパブリックドメインとします。
  • サンプルとして使用しているアバターAINAは以下のライセンスを参照してください。商用利用でなければ基本ほぼ何も気にせず使ってかまいません。 narazaka.booth.pm
  • AINAのためにまんまるシェーダーを同梱しています。こちらはまんまるシェーダーのライセンスに従ってください。
  • 各種依存アセットはそれぞれのライセンスに従ってください。

宣伝

このほかにも、エレベーター的に使える床昇降システムとか、複数アバターを自動連続アップロードできる拡張とか、スクショを日付別にフォルダに入れるやつとか、色々便利なものがあるので奈良阪配本地を見ていってください。

narazaka.booth.pm narazaka.booth.pm narazaka.booth.pm narazaka.booth.pm