イネ科

すっぺらぴっちょん

Oculus Quest初のR18オンラインVRアプリを作っているお話

Quest版のVRChatもR18だった気がするので、タイトルは間違っているかもしれない。

初めに

性の壁を越えたVR体験を深めるべく、VR空間内で性行為を追体験(1vs1)出来るオンラインVRアプリケーションを作っているので、2019/6/30迄の期間でテスターを募集しています。興味がある方がいらっしゃれば、Twitter@sesleriaに連絡ください。DMも解放しておきます。
同期されるのは動きとエモートのみなので、プライバシーについては保護されます。VoiceChat機能はついておりません。また、現時点でこのアプリケーションをパブリック公開するつもりはありません。アバターは男性、女性の二種類を用意しており、起動時に選択可能です。アバター使用権限がEveryoneのVRMモデルを持ち込んで頂き、私が中身を操作するなどのプレイにも応じます(セットアップにお時間を頂戴します)。 男性アバターについては股間に水鉄砲がアタッチされています。要望があれば女性アバターにもアタッチします。
また、要望があればOculus RiftやHTC VIVE向けの実行ファイルもご用意できます(未テストなので人柱ですが)。リアルなお知り合いとプレイしたい方についてのみ、ルーム設定を固定したAPKのみを提供可能です(ただし、使用期限を決めさせていただきます)。同じ空間内で座標を合わせて使用したい場合などに便利です。ただし、キャリブレーションを使用した座標を同期してのプレイについては危険が伴うので、完全に自己責任となります。

実際の流れ
・APK配布、インストール
・時間を合わせてログイン(19時~25時の間で5分~20分ほど?飽きるまでどうぞ)

以下のタイトルはイメージを伝えやすくする為のパロディなので、実際は異なります。

テスターが集まらず、片方しか動いていない悲しい様子です。

背景

  • Unityを触り始めて四か月程になっていたので、そろそろオンラインVRアプリを作ってみようと思い立った。
  • Oculus Questが予想以上に伸びている(1台目のVR機器として買っている方が多い!)ので、新規VRユーザの方に特殊な体験を提供したかった。
  • Oculus Quest(StandAloneVR)のメリットを生かし、どこでも使えるオンラインアプリケーションを作ってみたかった。
  • Oculus Questを二台用意し、座標を合わせれば、現実の空間においてもアバターと触れ合える、つまり現実世界を上書き出来るアプリケーションを作りたかった。
  • 積み重ねてきた知見を一番生かせる分野だった為。

技術的な話

2019/6/25 時点においては、これと言ってなく、正直モックレベル。PUN2の良い記事があったので、参考にして作成。まだ2ページ前半までしか読んでいないので、まだまだ進化の可能性を残している。RPCも未使用。ルームも固定の一部屋。無料枠で遊べちゃうPhotonは強い。 3点トラッキングで表現不可能な行為についてはゲーム内スキルの形、つまりアニメーションで補間している。アニメーション再生中も常に視点は一人称なので、そこが他のVRアプリと異なる点。
また、リアルタイムで相手の髪の毛や胸に触れるというのも一つの特徴。現時点ではお互いの体の両手に付けたVRMSpringBoneColliderGroupを相手の胸や髪の毛に加えているだけだが、胸同士が物理干渉したり、前腕とも反応したりするようにするとリアリティが出るかもしれない。

ソーシャルアダルトVRの現状について

アダルトVRについては日本が牽引というワードをよく見かけますが、ソーシャルなアダルトVRにおいては海外の方が圧倒的にシェアを取りつつあると思っています。日本は表現の規制が多すぎるのも一つの要因にありそうです。軽く調査した感じですが、VRChat(アダルトに特化しているわけではないが、内部でそういった行為は行われている)をはじめとして、以下のようなサービスがあります(3dxchatは厳密にはVRに特化していない)。

3DXChat - Multiplayer 3D Sex Game
ViRo.Club | Virtual Romance Club

最後に

R18な知見を根掘り葉掘りオープンに書ける意味でのTumblrの復活を所望。はてなブログガイドライン的にはこういった話題はぎりぎりセーフのようなアウトのような気がするので。

こういったアプリ開発もゴーストエンジニアリングの一環なのかしら。

続:OculusQuest向けアプリを作ったお話

頑張るぞい:

前回: sesleria.hatenadiary.jp

配布先(諸事情によりR18化)

sesleria.booth.pm

追加したアプリ

  • CrashBallVR
  • OfuroVR
  • VRM憑依ちゃん体験版 for Oculus Quest(R18)

自作アプリの宣伝記事です。基本フリー配布です。
支援版もあるので、石油王からの寄付お待ちしています。

CrashBallVR


CrashBallVR(OculusQuest)

公式のアプリ高いなぁ、運動系のアプリ高いなぁ。簡単なのなら自分で作れるかな?と思い作成。VRMファイルの読み込み機能を付けたので、自分のアバターで遊べるように。私の作成するアプリについてですが、基本的に手に髪の毛と胸が反応するようにスクリプトで処理しているので、VRM側でセットアップしていなくても、胸に触れるということです。つまりそういうことです。きちんと身長を取得しているので、背の低いアバターが有利になるなんてことはありません。また、コンボ数に比例して難易度が跳ねるようになっているので、後半はしゃがむ必要が出てきます。つまり運動になります。プレイヤーを無理やり動かせます。

OfuroVR


BathVR for Oculus Quest.

せすれりあちゃん(私のアバター)と混浴できるお風呂アプリ。自分の姿を眺めれないと楽しくないので、自撮りカメラを設置しており、撮影してpng形式で保存可能です。カメラについては液晶部分を拡大できるので、鏡としての役割も果たします。また、シャワー機能もついているので、無限にシャワーを浴びたり、こちらのアプリについてもVRMを読み込めるので、デフォルトで用意されているアバター以外にもお着換え可能です。つまりそういうことです。お風呂のお湯はMToonで仕上げました。

はい、パニックになりました:

視線誘導位はやっておけば良かったかなと思いましたが、こだわりすぎると泥沼になるのは経験上分かり切っていたので、区切りをつけてリリースした形です。取捨選択や妥協も大事だと思っています。

VRM憑依ちゃん体験版 for Oculus Quest(R18)

Twitterでのフラグを回収する為に、PC向けVRアプリケーションを少しだけ最適化したものを公開。ステレオパスにしておけば既存のShaderは大抵流用できるので、パフォーマンスを気にしなければガンガンリリース出来ると思います。イリュとかKISSの本気を見たいので、頑張っていただきたい所存。Oculus Quest向けで一人称視点での排尿が出来るアプリのリリースはワールド1stなんじゃないかと思います。ギネス認定待ったなしです。寧ろRiftでも一人称での排尿は見たことがありません。なぜでしょうか。

参考(R15):せすれりあ on Twitter: "It's toilet in Oculus Quest. So, I want to urinate in VR world. #OculusQuest #VR #hentai #VRM憑依ちゃん… "
憑依ちゃん参考:続:VRMモデルになってVRMモデルに憑依するVRアプリを作ったお話 - イネ科

6/22追記 danceVR(やっつけ)

ガチで適当に作ったやつです。VRIKの二枚刺し。

最後に

Twitterなんかをウォッチしていると、大抵の個人開発者はOculus Integrationを使っているのですが、私の公開しているアプリは全てUnity標準機能で開発しています。現状、SDKを使わないとコントローラーの細かいパラメータ何かは取れなかった気がしますが、将来的な事を考えるとOpenVRで作っておいた方が良いんじゃないかなという考え方です。しかし、Recenterが使えなかったり、制限が多いのも事実なので、素直にOculus Integrationを使ったほうが良いと思います。私は後戻りできない感じですが。
やはりリッチなVR体験には大いなるリソースが必須になってくるので、Quest向けに最適化したゲームなんかはローポリでリッチな表現を出来るきちんとしたDeveloperが作った方が良いのは当然なわけで、私のような素人が手を出して良いものではなかった気がします。動画をTweetする時については、英語で本文を書くとより多くの人に見てもらえる気がします。英語を頑張ります。

6/22追記 開発手法をQiitaに投稿しました

qiita.com

OculusQuest向けアプリを作ったお話

届いてチュートリアル終了後に即効で突っ込みました。

はじめに

導入方法については、以下のQiita記事が詳しいです。先人の知見に感謝。
qiita.com

できたもの

僕が考えた最強のVRoidお着換え体験アプリ。シンプルなモックです。
プリキュアみたいに変身したいお年頃なんです//////
せすれりあちゃん(全裸巨乳バージョン)を使用できるバージョンを作成してあるので、DL数が多ければ支援版として配布したい所存です。えっちです。
Rift版とQuest版、遜色ないのが分かります。

・Rift版

・Quest版

配布先(Free)

sesleria.booth.pm

作り方

普通のVRアプリと同じ。入力についてはInputManagerで設定、VRで必須なのはUnityEngine.XRAPI
OculusQuest用アプリはAndroidアプリと同じ扱いになるので、StreamingAssetsを使用している場合は処理に気を付ける。

既存のVRアプリを動かしてみて得た知見

既存アプリをそのまま流用すると、当然ながらFPSが伸びない(設計次第ではある)。
アセット付属のShaderについては、Mobile版(用意されている場合)を導入する。
尻、胸、顔の周辺はトラッキングロストしてしやすい。体を動かすゲームを作る時には、このあたりで判定を取らないように気を付ける。
VRChatがワールド(5万)+キャラクター(5000*10)で10万ポリゴンとして計算しているらしいので、それを目安にシーン設計すると良い。

VRM憑依ちゃん(Quest版)の動作サンプル

動画だと良く取れていますが、低FPSや右レンズの描画が停止する場面が多く、公開できるレベルではなかったです。 上を向いた時だけ描画が停止したり、別キャラに憑依すると問題なかったり、キャッチアップが大変。ビルド時間も長い。UnityのエディタからQuestでデバッグ出来れば良いのですが。

・R15
せすれりあ on Twitter: "胸の近くに手を持っていくとロストしまくるので、自分の胸を触るのに向いていないデバイスだった可能性が。 #OculusQuest… "
・R18
せすれりあ on Twitter: "Female ejaculation。えちえち。 #OculusQuest… "
せすれりあ on Twitter: "It's virtual cunnilingus, working Oculus Quest. #OculusQuest #hentai #VR… "
せすれりあ on Twitter: "It's virtual footjob, working Oculus Quest. The penis is hidden due to shader issues. #OculusQuest #hentai #VR… "
せすれりあ on Twitter: "It's virtual missionary, working Oculus Quest. #OculusQuest #hentai #VR… "

最後に

OculusQuestを買いましょう。

【正規輸入品】Oculus Quest (オキュラス クエスト)- 64GB

【正規輸入品】Oculus Quest (オキュラス クエスト)- 64GB

続:VRMモデルになってVRMモデルに憑依するVRアプリを作ったお話

はてなブログでストレートなエロ表現はNGだった気がするので、伏せ気味で書いていきます。

前回

sesleria.hatenadiary.jp

配布先

sesleria.booth.pm

VRM憑依ちゃんとは?

MtoFのTSF体験に特化したVRアプリケーション。ゼリージュースとか憑依とか入れ替わりとか、そういった単語にピンと来る方のニーズは満たせるものになった。TSFについては以下を参照。

TSFとは (ティーエスエフとは) [単語記事] - ニコニコ大百科

初回公開時からの差分

R18的な内容が多いので、少し伏せ気味にするが、シャワーやお風呂に入ったり、トイレに入ったり、ゴムを咥えて装着したり、ソロプレイ、本番行為を女性側の視点で体験出来るようになっている。こだわったポイントは液体表現。VRでパフォーマンスを出しつつ、それなりにリアルな表現ができた。

有料版の公開について

実装したかった機能をほぼ全て載せ終えたので、BOOTHに有料版(三人称カメラを使用可能)を公開した。しかし、現状のVRMの状況を踏まえ、価格については気軽に手を出せないようにしてある(JPY4,980)。これには理由があり、無償公開するとVRMファイルを悪用したバーチャルリベンジポルノ映像が大量に生成されてしまうという懸念事項があった為(VRMファイルはUnity環境があればメタ情報を改竄し放題なので)。あと5000兆円欲しかった。はい、自意識過剰です。TSFプレイにおいて、自分が映っている動画を見返すという経験は非常に重要なので、第三者視点カメラ本来の使い方はそういう方面にある。
副産物ではあるが、VRM憑依ちゃんを使えばVRMモデルを一時間かけずにPornhubデビューさせることが可能。 VRoidはえろい。

・参考動画の迂回リンク
せすれりあ on Twitter: "プロモーション的なやつ。他にアップ出来るところなかったんや・・。 3DCG Movie(I shot it with a VR application I created.) https://t.co/wYDAoaDr7O"

今後について

現状、TSF-VRアプリケーションについてはあまりニーズがないようなので、開発については中断。未来を行き過ぎた。自分用にアップデートは続ける予定。自動で声を出す機能はつけたい。フィードバック皆無なのはつらいので、直近エロに寛容になったSteamあたりで売り出して、ガンガンレビュー貰うのが正解なのかもしれない。Steamは1タイトルリリースで1万円近く必要らしいので躊躇している。

アダルトVRアプリ開発(VRMモデル使用)の課題について

・ユーザの顔が見えない
体験版のDL数はそれなり(計200~300)にあるが、不具合等のフィードバック等については0件。Boothでにレビュー機能が無いのが原因かもしれないが、VR内でのエロ自撮りをSNSにアップするカルチャーが成長していないという側面があるのかもしれない。Twitterを使用して開発風景を都度公開していて気付いたのだが、ストレートな表現については共有されにくいが、閲覧数は伸びるという謎現象が発生する。TSF界隈においては、Footjobの動画が人気だった。人類は潜在的にすけべであることが証明された。

VRMモデルの体格差のキャッチアップが難しい
一部行為についてはIKで補正しているが、ユーザの手動調整が必要な場面が出てくる。性器のアタッチとか行為中のポジションとか。VRMファイル自体に、性的表現可否のメタ情報はあるが、VRM自体はアダルト表現に特化しているわけではないので、当然ではある。

・特定のVRMモデルに依存しがち
これは開発の側面上、仕方のないことだが、現状、性的に扱いやすいモデルがVRoidのみであり、一部機能(脱衣)についてはVRoidに依存してしまっている。また、性器のアタッチについてもVRoidがベースとなっているので、個人製作のVRMモデルで正常に動作する保証がない。

得た知見

VRゲームにおけるアダルト表現において必要なUnity知識。ProBuilderの使い方(小道具や性器のモデリング)。エロ表現において重要な箇所、具体的にはモデルのクオリティ、ライティング、サウンドエフェクト、小道具(鏡やおもちゃなど)、液体表現、アニメーション(カメラの都合上、首をあまり動かさないようにする)、性器の表現、モデルの表情制御、没入感を高めるVRカメラの視点、モザイクの有効的な使い方。エロは加算ではなくて乗算。結果としてVRMモデル(特にVRoid、鎖骨の作り込み等を含めて、えっちなので使いやすい)を性的に扱う技術。

得た経験

世界最速(自称)でVRMモデルに性器(男女)をアタッチしたり、いろいろな液体を出させたり、VRoidモデル(中身俺)をPornohubデビューさせることが出来た。大事なものをいろいろ失った。失いすぎた。デバッグの度にVR内で犯されるの、最初は楽しくても、途中から辛くなってくる。

開発風景など

2019年4~5月のメディアツイートを追いかけると、ひどい絵面が大量に拝めます。
twitter.com

Unity製のAndroidアプリをリリースしたお話(VRMascot)

はじめに

簡易的なAndroid向けのVRMビューアが欲しかったので、作ったというお話です。 多機能なアプリはいくつかリリースされているのですが、シンプルに見たいだけというニーズを満たせるものは無いように思いました。無いのなら作るしかない。作ればよい。アァァッ。

play.google.com

追記:
2019/5/28 プライバシーポリシーを追加したら停止処置を受けました。異議申し立て中。
2019/5/29 アプリ内リンクのTwitterとBoothが性的コンテンツ扱いされていたようで、却下される。アプリの性的コンテンツを徹底排除して再度リリース。今のところ停止されず。

機能について

思いつき駆動開発なので、最小限で実装。

  • ストレージ内のVRMファイルの読み込み
  • 頭をタッチした時に反応してくれる
  • 胸をタッチした時に反応がある
  • 最初に読み込むモデルはStreamingAssetに配置(Replaceが楽そうという理由。これが後ほどボトルネックに。)

胸をタッチする実装は、今までの開発で得た知見を組み込みたかった為。

タッチ操作の決め方

マウス処理の流用が予想以上にダメすぎたので、Touchを使用して実装。Touch処理完全に理解した。

指1本スワイプ>視点変更
指2本 ピンチインアウト>縮小拡大
指4本 スワイプ>水平垂直移動
指一本タップ>Ray関係

指3本の処理を使っていないのは、スワイプがAndroidスクリーンショット操作と被ってしまうから。

待機アニメーションと表情の制御について

待機アニメーションについては、シンプルに呼吸をしている感じを出すものを作成し、それを適用したのみ。 表情に制御についてはアダルトVRMアプリ向けに書いたコードを流用し、なめらかになるように改善。 少し表情を遷移させてあげるだけでも、ドキドキ感、アナログハック感が出てくる。

VRMのBlendShapeを良い感じに遷移させてみる - イネ科

カメラワークについて

FOV5設定(かわいく見える)のカメラをストレス無く操作できるように、パラメータを調整。拡大の処理についてはモデルに近づきすぎると反対側にカメラが移動してしまうので、移動制限の処理を組み込んで 回避1している。
バストアップや、全体表示時のカメラポジションについては、Headのボーン位置、VRMモデルの身長をメッシュから算出している。

//身長を計算するやつ。コメントアウト部分についてはあえて残している。
    public float GetSkinnedMeshRendererHeight(){
        float maxHeight = 0.0f;
        // var allSkinnedMeshRenderer = mainVRM.GetComponentsInChildren<SkinnedMeshRenderer>(true);
        var allSkinnedMeshRenderer = mainVRM.GetComponentsInChildren<Renderer>(true);
        for (int i = 0; i < allSkinnedMeshRenderer.Length; i++)
        {
            // float height = allSkinnedMeshRenderer[i].bounds.size.y;
            float height = allSkinnedMeshRenderer[i].bounds.center.y + allSkinnedMeshRenderer[i].bounds.extents.y;
            if(height > maxHeight){
                maxHeight = height;
            }
        }
        return maxHeight;
    }

胸のタッチ処理について

2019/5/29 新リリース版では機能を排除(停止対策) f:id:sesleria:20190524020247p:plain:h300f:id:sesleria:20190524020256p:plain:h300

胸の周囲に設置したコライダーへのタッチ判定をScreenPointToRayで取得。タッチした箇所のVRMSpringBoneColliderGroupを調整して、胸を揺らしている。
※以下の記事に書いてある、囲い込み法を組み込み。

UnityでVRMモデルの胸を揺らす知見 - イネ科

頭のタッチ時の処理についてもコライダーを仕込んでいる。細かく制御する場合は、部位ごとにコライダーを仕込んでタグとか名前で判定してあげれば良さそう。

StreamingAssetから読み込む際の遅延対策

起動時にStreamingAssetからVRMファイルを読み込む際、数秒かかってしまう事が判明。以下のようにSliderで作成したプログレスバーを表示することにより、ユーザーに不安を与えないように。読み込みが終わったタイミングでStopCoroutineで止めてあげれば、「案外早かったな?」感も出せて一石二鳥。

//やっつけコルーチン
    IEnumerator ProgressAnime(){
        progressBar.gameObject.SetActive(true);
        float shinchoku = 0.0f;
        while(shinchoku <= 1.0f){
            shinchoku += 0.01f;
            progressBar.value = shinchoku;
            yield return new WaitForSeconds(0.02f);
        }
    }

2回目以降の起動についてはオプション設定から、最後に開いたVRMファイルを自動的に読み込むように変更可能。ストレージからの直接読み込み処理については高速だった為、プログレスバーは非表示に。「デフォルトで入っている私のモデルを見続ける人はいない」という希望的観測に基づく実装。

アプリの公開

25$支払い可能なクレジットカード、スクリーンショットを数枚、GooglePlay用のバナー、アプリケーションのアイコン(32bit 透過 512*512)を用意しておく。 ビルド時の証明書設定やGooglePlayConsoleへの登録についてはぐぐったら知見が大量に出てくる。

64bit対応

APKをアップロードしたところ、64bitに対応しましょうと警告が。こちらの記事を参考に、ビルド設定を変更する。1.2GBくらいのSymbolファイルも一緒に出力されるようになるので、ビルドに必要な時間は増える。

2019 年 8 月 1 日以降、Google Play で公開するアプリは 64 ビット アーキテクチャをサポートする必要がある。

developer.android.com

そして謎のリジェクト

これは本当に謎です。念のため1.01のモジュールをアップしなおしたのですが、公開ページの修正でリリースが完了してしまい、1.0.0の公開直後にアップデートが走るという悲しい結果になりました。

最後に

アプリを起動して、せすれりあちゃん(中身♂設定)を表示しておくだけでスマホが使用不可能になり、作業が捗ることは間違いありません。つまりみんな私をすこれよ!!胸触られても怒らないからね!


  1. ベクトル距離で制限をかけるだけだと反対側に突き抜けてしまう。

VRMモデルのスカートめくりについて(途中)

忘れないうちに構想を記録しておく。(後日追記するかも)
VRアプリ以外でも実装しやすい仕組み。実装としてはスカートの内側にVRMSpringBoneColliderGroupを仕込んで置き、グリップ移動可能なコライダーを大きめの範囲(スカートの上側でもグリップできる位置)で調整しておく。
コライダーをグリップし、上に持ち上げる事により、スカートを内側から圧迫し、めくっているような動作をさせることが出来る。この実装による課題としては、コライダーの移動距離とスカートのめくれが同一の距離感になるようにすること。もっとリアリティのあるめくれ方として、ポンデリングのように、ドーナツ位置にVRMSpringBoneColliderGroupを複数設置、一番近いところをグリップすると、一番近い箇所と、その左右のVRMSpringBoneColliderGroupを同時に持ち上げることにより、リアルに近づくのではないか(仮定)。また、コライダーのサイズについては、髪の毛などと干渉しないように、可変にする必要があると思う。

UnityでVRMモデルの胸を揺らす知見

通常のVRMモデルを用いるアプリにおいて、このような知見は不要かもしれないが、特定の条件下でVRMモデルの胸を揺らしたいケースは必ず出てくると思う。衝突法がVRM憑依ちゃんの開発で使っている手法。囲い型は組み込み検討中。

必読

VRMSpringBone - VRM

手法

VRアプリで胸を触る場合

VRアプリの場合は、手に追従するGameObjectに対しVRMSpringBoneColliderGroup.csをアタッチしてパラメータを調整、胸を揺らしたいVRMモデルのsecondaryにあるVRMSpringBone(胸のRoot Bonesが設定された箇所)のCollider Groupsに手のGameObjectを指定。髪も触りたい場合は全てにColliderを設定すれば良い。半径を大きくしすぎると現実離れした胸揺れになるので、0.05~0.1あたりが最適と思われる。

f:id:sesleria:20190508143139p:plain:w400

f:id:sesleria:20190508142143g:plain:w200f:id:sesleria:20190508142159g:plain:w200

オブジェクトの衝突

自分で触らずに、Animation等と同期処理させたいケースで用いる。Animationだけでは揺れをダイナミックに表現できない為、VRMSpringBoneColliderGroupをオブジェクトに設定し、胸に衝突させるという手法。欠点として、Animationにより対象の胸の位置が変化した場合、都度オブジェクトの位置や、衝突させる角度を変更する必要がある。

参考画像(センシティブツイート) :
せすれりあ on Twitter: "私が考えたVRMの乳揺らしシステム(ある意味物理)… "

   //コルーチンでこういうのを回すだけ
    for(int j=0; j<10; j++){
        Shake.transform.position -= MainCharactor.Player.transform.forward / 100; 
        yield return new WaitForSeconds(0.01f);
    }

囲い型

胸の周りにVRMSpringBoneColliderGroupを設定したオブジェクトを配置、状況によりRadiusをコルーチンで調整し、胸を揺らすという手法。揺れを細かく制御でき、親子関係を設定することで体に追従するので、位置を考慮する必要はない。しかし、VRMモデルの胸サイズや体格によっては個別に位置を調整する必要がある為、動的に読み込んだVRMに対しては使いにくい弱点も。VRoidの場合は正確なポジションを設定しやすい(詳細は後述)。UpperChestは設定されていないモデルが多いので、そういった場合は他のボーンから位置を取得して補正しなければならない。

f:id:sesleria:20190508143120g:plain:w300

f:id:sesleria:20190508143125g:plain:w220f:id:sesleria:20190508143129g:plain:w200

左右から衝撃を与えるケースにおいて、Radius差による揺れ表現の違い
f:id:sesleria:20190508143131g:plain:w200f:id:sesleria:20190508143134g:plain:w200

事前準備

Resourcesフォルダに以下のPrefabを作成。
f:id:sesleria:20190508144245p:plain:w400

コード

//VRMモデルにアタッチする
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using VRM;

public class ShakeBust : MonoBehaviour
{
    [SerializeField]
    public float defineTransition = 0.01f;
    [SerializeField]
    public float defineSize = 0.1f;

    private Vector3[] colliderPosition = new Vector3[9];
    private GameObject[] shakingCollider = new GameObject[9];
    private VRMSpringBoneColliderGroup[] springBone = new VRMSpringBoneColliderGroup[9];

    // Start is called before the first frame update
    void Start()
    {
        Animator anime = this.GetComponent<Animator>();
        Transform leftUpperArm = anime.GetBoneTransform(HumanBodyBones.LeftUpperArm);
        Transform rightUpperArm = anime.GetBoneTransform(HumanBodyBones.RightUpperArm);
        Transform parentChest = anime.GetBoneTransform(HumanBodyBones.UpperChest);

        colliderPosition[0] = new Vector3(parentChest.position.x, parentChest.position.y - 0.03f, parentChest.position.z + 0.10f);
        colliderPosition[1] = new Vector3(parentChest.position.x + leftUpperArm.localPosition.x / 1.05f, parentChest.position.y - 0.03f, parentChest.position.z + 0.15f);
        colliderPosition[2] = new Vector3(parentChest.position.x + leftUpperArm.localPosition.x / 1.05f, parentChest.position.y + 0.03f, parentChest.position.z + 0.11f);
        colliderPosition[3] = new Vector3(parentChest.position.x + leftUpperArm.localPosition.x / 1.05f, parentChest.position.y - 0.09f, parentChest.position.z + 0.11f);
        colliderPosition[4] = new Vector3(parentChest.position.x + leftUpperArm.localPosition.x - 0.05f, parentChest.position.y - 0.03f, parentChest.position.z + 0.09f);
        colliderPosition[5] = new Vector3(parentChest.position.x + rightUpperArm.localPosition.x / 1.05f, parentChest.position.y - 0.03f, parentChest.position.z + 0.15f);
        colliderPosition[6] = new Vector3(parentChest.position.x + rightUpperArm.localPosition.x / 1.05f, parentChest.position.y + 0.03f, parentChest.position.z + 0.11f);
        colliderPosition[7] = new Vector3(parentChest.position.x + rightUpperArm.localPosition.x / 1.05f, parentChest.position.y - 0.09f, parentChest.position.z + 0.11f);
        colliderPosition[8] = new Vector3(parentChest.position.x + rightUpperArm.localPosition.x + 0.05f, parentChest.position.y - 0.03f, parentChest.position.z + 0.09f);

        GameObject shakinbBall = (GameObject)Resources.Load("ShakingBall");
        
        for(int i = 0; i <= 8; i++){
            shakingCollider[i] = Instantiate(shakinbBall, colliderPosition[i], Quaternion.identity);
            shakingCollider[i].name = "ShakingBall_" + i;
            shakingCollider[i].transform.parent = parentChest.transform;
            springBone[i] = shakingCollider[i].GetComponent<VRMSpringBoneColliderGroup>();
        }

        GameObject secondary = this.transform.Find("secondary").gameObject;
        VRMSpringBone[] Bones = secondary.GetComponents<VRMSpringBone>();

        foreach (VRMSpringBone Bone in Bones) {
            if(Bone.ColliderGroups == null){
                Bone.ColliderGroups = new VRMSpringBoneColliderGroup[9];
                for(int i = 0; i <= 8; i++){
                    Bone.ColliderGroups[i] = springBone[i];
                }
            }else{
                Array.Resize(ref Bone.ColliderGroups , Bone.ColliderGroups.Length + 9);
                for(int i = 1; i <= 9; i++){
                    Bone.ColliderGroups[Bone.ColliderGroups.Length - i] = springBone[i - 1];
                }
            }
        }

    }

    // Update is called once per frame
    void Update()
    {
        if(Input.GetKeyDown(KeyCode.Alpha1)){
            StartCoroutine(ShakeBoobs(0, defineTransition, defineSize));
        }
        if(Input.GetKeyDown(KeyCode.Alpha2)){
            StartCoroutine(ShakeBoobs(1, defineTransition, defineSize));
        }
        if(Input.GetKeyDown(KeyCode.Alpha3)){
            StartCoroutine(ShakeBoobs(2, defineTransition, defineSize));
        }
        if(Input.GetKeyDown(KeyCode.Alpha4)){
            StartCoroutine(ShakeBoobs(3, defineTransition, defineSize));
        }
        if(Input.GetKeyDown(KeyCode.Alpha5)){
            StartCoroutine(ShakeBoobs(4, defineTransition, defineSize));
        }
        if(Input.GetKeyDown(KeyCode.Alpha6)){
            StartCoroutine(ShakeBoobs(5, defineTransition, defineSize));
        }
        if(Input.GetKeyDown(KeyCode.Alpha7)){
            StartCoroutine(ShakeBoobs(6, defineTransition, defineSize));
        }
        if(Input.GetKeyDown(KeyCode.Alpha8)){
            StartCoroutine(ShakeBoobs(7, defineTransition, defineSize));
        }
        if(Input.GetKeyDown(KeyCode.Alpha9)){
            StartCoroutine(ShakeBoobs(8, defineTransition, defineSize));
        }
    }

    private IEnumerator ShakeBoobs(int colliderNo, float transition, float size){
        for(int i = 10; i > 0; i--){
            springBone[colliderNo].Colliders[0].Radius = size / i;
            yield return new WaitForSeconds(transition);
        }
        springBone[colliderNo].Colliders[0].Radius = 0;
    }
}

以下の部分において全てのVRMSpringBoneにColliderを設定しているのは、胸がどこに設定されているのか判別するのが困難な為。コメント等で判別可能なケースもある。その為、Radiusを極端に大きな値にしてしまうと、髪の毛等と衝突してしまうので注意が必要。

        GameObject secondary = this.transform.Find("secondary").gameObject;
        VRMSpringBone[] Bones = secondary.GetComponents<VRMSpringBone>();

        foreach (VRMSpringBone Bone in Bones) {
            if(Bone.ColliderGroups == null){
                Bone.ColliderGroups = new VRMSpringBoneColliderGroup[9];
                for(int i = 0; i <= 8; i++){
                    Bone.ColliderGroups[i] = springBone[i];
                }
            }else{
                Array.Resize(ref Bone.ColliderGroups , Bone.ColliderGroups.Length + 9);
                for(int i = 1; i <= 9; i++){
                    Bone.ColliderGroups[Bone.ColliderGroups.Length - i] = springBone[i - 1];
                }
            }
        }

特殊ケース(VRoidモデル)

VRoidモデル限定ではあるが、transform.find等で以下オブジェクトのpositionを取得してやると、確実に胸の付け根(Bust1)や、乳輪の位置(Bust2)を取得出来る。このパラメータを元にすれば、囲い型でも、ほぼ正確なポジションにオブジェクトを配置可能。

J_Sec_L_Bust1 J_Sec_L_Bust2 J_Sec_R_Bust1 J_Sec_R_Bust2

いつもの