Narazaka::Blog

奈良阪という人のなにか

凄く雑に今のVRソーシャル感をメモする(VRChatなど)

最近「最近のVR業界ってどんな感じ?」と聞かれたので、超雑に答えた時のログを雑に貼る。

2019年末の感覚のメモとして。

VRChat

相変わらず一人勝ちVRコンテンツVRChatだけど、思ったより人数は伸長しないまま、とはいえそれなりに人の入れ替わりがある模様。

ツイで定期的に話題が蒸し返されるのも新しい人が入っている証拠でほほえましいです。(これはいわゆるまとめWikiみたいなのがユーザー向けには全く機能しておらず、全面VRかつボイチャという書き留め不能の環境による情報の逸散ぶりが凄いともいえます。あと界隈によって乱立するDiscordが無限に情報を分断してくる……。)

ある面過剰な期待は薄れつつ、入ってくる人はごく順当にVRすげーってなったりしてる感じに見えます。

とはいえろくにチュートリアルもないので定着には結構壁があり、何も分からん壁→(日本人チュートリアルをうけて生存)→VR体験等新しさに慣れる壁→(親しいフレンドがいる場合生存)→人とトラブる壁→(解決できた場合生存)→場合によってメンがヘラる壁(なんかやっぱ多いと思う)→… みたいに濾過されている気がします。

Oculus Questの民も定着する界隈ができてきてるっぽいです。PCより体験が均一だったり稼働時間が少ないせいかメンがヘラってる率が少なめな気がします。

また非VRの人もそれなりに多く定着しているのは特徴的で、コンテンツである「人口」の底上げ、多様性に寄与していると思います。

美少女おじさん声とボイチェンとゆかりネットなどが入り乱れて受け入れられている文化はその基盤となっており、これは間違いなくバーチャルのじゃロリ一般男性氏のおかげが多分にあるので本当に日本は運が良かったと思います。あのムーブメントももはや歴史上の存在なので、新規さんでその辺に違和感を覚える人とか、ボイチェンなどを罵倒する荒らしなどが時々現れたりはしてますが……。

なおその他の外部干渉に対する反応としては、VRChatにのっかってVケットやってるHIKKYが醸し出すよく分からんウザさに辟易する空気が一部であったりします。

VRChat内からの印象として3Dモデルの界隈がMMDと分断しているのもなかなか特徴的です。規約きっちりのVRChatモデル&VRMアプリ界隈に対して、MMDは(ニコニコの印象と相まって)一昔前のよくわからんとこみたいな印象があります。たぶんニコニコでMMDに親しんでいた人は違った感覚なんでしょうが、自分はそうではないので。ただしモデラーはやはりMMD時代からの猛者がいるのか、MMDモーフへの対応(シェイプキー名を規格にあわせる)はされているものもちょくちょくある気がします。

またVRChat長い人はVRCが不便を放置しながらどこに力を入れているのかよく分からない具合等にマンネリを感じており、Alt VRChatを望む空気もありました。

そんな中ギリギリ2019年中に遙か昔から噂されていたVRChat Udonがαとは言えリリースされたのは朗報です。 APIを制限しただけでほぼC#ができること丸ごと書けると思わしきプログラミング言語が顕現したので、このブレイクスルーによってあらゆる既存ゲームやその他コンテンツをVRで実装し直すプラットフォームとして新たな方向性が芽吹く予感がします。

VRChat以外

他はVRCよりやや重くて色々あるけど過疎だったHigh Fidelityが終焉の勢いだったり、SansarとAltspace VRが相変わらず空気だったり……。

さらに直近リリースのVRをNostosというVRゲームの酷評ぶりと、同じく出たマストドン相当LavendarVRのα感に非難囂々集まっているのを見ると、なんだかんだVRChatのクオリティが(不安定と言われているとはいえ比較的)高すぎて大変だなあという感じ。(SansarとHigh Fidelityについても間違いなくVRChatのほうが負荷が軽かったし)

ただしLavendarVRは恐らくこういう類いの中で始めて「VRChat後」を強く意識した感じがあり、また初めての「VRChatより軽い可能性がある」VRSNSなので、頑張って欲しいなあと言う気持ちがあります。

またその中でこの前オープンアクセスを閉じたambrは最初から割と整った出来でよかった(ambrのVRユーザー意見聞く会に参加した感想)んですが、Oculus GoというVRCと全くかすらないハード故に、VARKとともにVケットにあったけど全然話題になっておらず、ハードの壁が厚いのは感じます。Quest対応して欲しい(VARKは12月に対応したらしいですが)。

あとバーチャルキャスト2.0はよ。待ってる。アバターとワールド以外にやっぱりアイテムは欲しいです。延期後いつになるんや。はよ。

ハード・その他

ハード的にはQuestが万能最終兵器感を醸し出してきており、逆にHTCが迷走しているためにフルトラッキングのハードルが前より高くなっている印象。Windowsハードはここでも多分勝てない。

そういえばARとかLooking Glassとかの話題はほぼ完全にVTuberの文脈で語られていて、VR民とは断絶があるように感じています。

あとVRC内で話題になるVRゲームはBeat Saverと東京クロノスと任意シミュレーター系と言う印象?案外数がないです。狼と香辛料は上位互換の体験がVRCで得られるせいか話題にならなかった。VRゲーム難しそう。

以上

あんまどこかへ発信するつもりの文章ではないので、ツッコミは優しくお願いします。

Udon完全に理解したけど、C#からUdonGraphにするトランスパイラを誰か作ってくれ

Udon完全に理解したので超雑にメモしておく。

つまりこれがこう

f:id:narazaka:20191221180447p:plain

f:id:narazaka:20191221180426p:plain

基本的に

  • 制御がif, for, while, breakのみ
  • ローカル変数はなく全ての変数はフィールド
  • イベント=メソッド
  • SendCustomEvent=メソッドコール
  • メソッドは引数無し返値voidのみ
  • SubGraph=コード片(他言語のMixinみたいな感じ)
  • ジェネリクス無し(System.Collection.Genericが使えなさそう)

C#プログラミングだと思えばヨサソウ。

プログラマ勢としては上記の条件でC#をざらっとかいて、それをUdonノードに起こすのが今のところ楽そうな感じ。

ただここ変換があると嬉しい気はする。

ILでやるという手もあるのだろうけど、構文解析の概要 (Roslyn API) | Microsoft Docs のSyntaxWalkerを使って、

  • 制御はif, while, breakのみ(forは若干めんどそうなので)
  • 変数はフィールドのみ
  • メソッドは引数無し返値voidのみ

C#ソースをUdonGraphにトランスパイルすると楽そうと感じた。

途中まで(まだ何も始まってないレベルだが……)やったのでメモすると、

まず GitHub - mwahnish/Unity-Roslyn: The Microsoft C# binaries compiled for Unity をインポートしてMicrosoft.CodeAnalysisを使えるようにする。

その上で

using System.Collections.Generic;
using UnityEngine;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Linq;

public class UdonCsharp : CSharpSyntaxWalker
{
    public class Error : Exception {
        public Error(string message) : base(message) { }
    }

    string Code { get; }
    Microsoft.CodeAnalysis.SyntaxTree SyntaxTree;

    public UdonCsharp(string code) {
        Code = code;
    }

    // Start is called before the first frame update
    public void Compile()
    {
        GetTree();
        Visit(SyntaxTree.GetCompilationUnitRoot());
    }

    void GetTree() {
        // パース
        SyntaxTree = CSharpSyntaxTree.ParseText(Code);
        var diag = SyntaxTree.GetDiagnostics();
        PrintDiagnostics(diag);
        if (diag.Any(item => item.Severity == DiagnosticSeverity.Error)) throw new Error("parse error");

        // アセンブリ参照 雑に全部突っ込む
        var assemblies =
            AppDomain.CurrentDomain.GetAssemblies().
            Where(assembly => !assembly.IsDynamic && !string.IsNullOrEmpty(assembly.Location));
        var assemblyLocations = assemblies.Select(assembly => assembly.Location);
        // コンパイル
        var comp = CSharpCompilation.Create(
            "asmname",
            syntaxTrees: new Microsoft.CodeAnalysis.SyntaxTree[] { SyntaxTree },
            references: assemblyLocations.Select(location => MetadataReference.CreateFromFile(location)),
            options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
            );
        var diag2 = comp.GetDiagnostics();
        PrintDiagnostics(diag2);
        if (diag2.Any(item => item.Severity == DiagnosticSeverity.Error)) throw new Error("compile error");
    }

    void PrintDiagnostics(IEnumerable<Diagnostic> diagnostics) {
        foreach (var item in diagnostics) {
            var span = item.Location.SourceSpan;
            var sub = Code.Substring(span.Start, span.Length);
            var message = $"[{item.Severity.ToString()}:{item.Id}] {span} {item.GetMessage()}\n{sub}";
            switch (item.Severity) {
                case DiagnosticSeverity.Error:
                    Debug.LogError(message);
                    break;
                case DiagnosticSeverity.Warning:
                    Debug.LogWarning(message);
                    break;
                default:
                    Debug.Log(message);
                    break;
            }
        }
    }

    public override void VisitFieldDeclaration(FieldDeclarationSyntax node) => VisitNode(node);
    public override void VisitMethodDeclaration(MethodDeclarationSyntax node) => VisitNode(node);

    public void VisitNode(SyntaxNode syntax, int level = 0) {
        var kind = syntax.Kind();
        var str = syntax.ToFullString();
        Debug.Log($"{new string(' ', level)}{kind.ToString()}> {str}");
        // このへんでトークンを追ってUdonNodeに変換したい
        foreach (var node in syntax.ChildNodesAndTokens()) {
            if (node.IsNode) VisitNode(node.AsNode(), level + 1);
            if (node.IsToken) VisitToken(node.AsToken(), level + 1);
        }
    }

    public void VisitToken(SyntaxToken syntax, int level = 0) {
        var kind = syntax.Kind();
        var str = syntax.ToFullString();
        Debug.Log($"{new string(' ', level)}{kind.ToString()}> {str}");
    }
}

という感じでフィールド定義とメソッド定義を追って、出会った型はUdonNodeSearchMenuの型検索を駆使してUdonNodeDefinitionを取得し、良い感じにUdonGraphに積んでいく物を誰か作ってほしい(他力本願)。

追記

もうちょっと進めてみたけどノード定義はどうやらコード生成だし、グラフデータの値もGUIに強依存している感じがあり微妙だった……なんか良い方法はないか、それともasmアプローチを待つか。

github.com

asmなアプローチ↓

togetter.com

今よりもっと「クロスプラットフォーム」なVRChatを作りませんか?

こんにちは。VRChatはじめて以来1年半ずっとstatusに「まちカドまぞくを読んで下さい」と書いている事で有名な奈良阪です。まちカドまぞくを読んで下さい。

クロスプラットフォームなVRChat

VRChat Advent Calendar 2019 8日目の今日は2019年12月8日。 同年5月21日にVRChat Questが来てからはや半年。つまりVRChatに「クロスプラットフォーム」概念が本格的に到来してからもう半年です。

開始当初はごく一部という感じだったPC/Quest両対応アバターも、今では結構対応している人が多くなってきたイメージです。

自分が半年前に書いたQuest対応記事を「読みました!」と言っていただけることもたびたびあり嬉しいです。 みんなクロスプラットフォーム対応していこうな!

narazaka.hatenablog.jp (最近シェーダーについての記述修正と裏面描画対応あたりを追加してアップデートしました。)

より広い意味での「クロスプラットフォーム

……しかしふと思いました。「クロスプラットフォーム」、「仕様が全く異なる環境上で、同じ仕様のものを動かすこと」……たしかそういうの……Quest以外にも何か……?

そう、VRChatには「仕様が全く異なる環境」、つまり「デスクトップ民」、「VR民」、「フルトラ民」がいます!(たまに「腰トラ民」も)

つまりVRChatは実は元からクロスプラットフォーム性のあるゲーム(?)だったのですね。

そして「Quest民にはDynamicBoneが見えない」のと同じように、「デスクトップ民はカメラが使えない」、「VR民は寝転べない」、「フルトラ民がアバター椅子に座ると変になる」等々、環境固有の仕様制約というのはたびたび顕在化しています。

環境によってある程度非互換な所があるのは仕方ないのですが、やはりできる限り同じ体験をしてみたいという気持ちは生まれます。

特にやはり「フルトラ民」の自由度が半端じゃないです。様々な可愛い動き、かっこいい動きをほしいままにするその姿。そんな体験してみたい!

そのフルトラ民のみに許された娯楽の一つ、「VR睡眠」。 もちろん「VR民」にも「VR HMDかぶったまま眠ること」はできるわけですが、本当にVRの中で寝ているかのような没入感の高い体験はやはり「寝転がれる」フルトラ民ならではという感じがしますね。

たまたまフレンドのなんとかルームにJOINした時にフルトラの人が4、5人くらい同じ布団で寝転がって寝てるという光景を見て、ああこういうの良いなあと思って……ふと、思いつきました。

これ、「クロスプラットフォーム」でできる……!

クロスプラットフォーム睡眠ワールド「Just Sleep For Everyone」

(もちろんQuestにも対応!)

デスクトップでも、VRでも、Questでも、もちろんフルトラでも、「ベッドに寝転がれる」ワールドを作りました。

仕組みとしては単純で、「立ってる状態でも布団と並行になるよう部屋を90度傾けた」&「床で頭位置調整できるようにした」です。

f:id:narazaka:20191208152356p:plain
横から見た図
f:id:narazaka:20191208152521p:plain
昇降床で位置調整をして布団に寝よう!(床は透明にできます)

実際の使用例はこんな感じ。

予想だにしなかった使い方も含めていろいろな遊びが生まれて面白かったです。

(ややトリッキーな実装方法ゆえ、ぶっちゃけクソワールドとの境界線上っぽいかもと思ってたんですが、結構普通に好評を博したので良かったですね。)

寝ている正面を鏡で見るのも良いですが、横を向いて添い寝感を楽しむのはまた格別。たぶんこういう所がこれまでフルトラ民にしか体験できなかった領域なんだろうなあ……。

今よりもっと「クロスプラットフォーム」なVRChatを作りませんか?

そんな感じで、「フルトラならではの体験」だったものを「クロスプラットフォーム」化したワールドを作ってみたら思った以上に楽しめました。

ワールドでもアバターでも、Quest対応することばかりがクロスプラットフォームではありません。

PC/QuestクロスプラットフォームワールドでPC民とQuest民がそれぞれの違いを面白く感じるように、デスクトップ、VR、フルトラなどの環境差にクロスプラットフォームの架け橋をかけてもまた新しい面白い体験ができてくるんじゃないかと思います。

環境の違いは文化の違い。 より広い意味での「クロスプラットフォーム」で、「異文化」の輸入・展開をするというのも、ワールドやアバター作りのネタの一つとして考えてみてはどうでしょうか?

自分の環境ならではの面白体験、あるいは隣の青い芝にあこがれての無理矢理面白アイデア、色々見てみたい!

宣伝

ちなみに上で紹介した「Just Sleep For Everyone」の床昇降システムはBoothで無料配布してたりします。

narazaka.booth.pm

他にもQuest対応ローポリアバター、Questにも対応できる複数差分アバター自動連続アップロードツールや、スクショを整理するやつ、その他いろいろある「奈良阪配本地」、良かったら見てってください。

narazaka.booth.pm

ついでにTips

最も有名な環境依存非互換の一つ、「デスクトップだとカメラが使えない(故に表情確認などができない)」問題に関しては、以下の「KawaiiCamera」をアバターに導入することで一応擬似的に解消可能だったりします。

デスクトップでアバターをアップロードしている勢は使ってみると便利かも。

(作者は動いたり動かなかったりする城の人です。)

booth.pm

うちの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