VRぶつかり銭湯

  • Unity
  • VIVE

2017.7.15

はじめまして。宇都宮と申します。普段はWEBサイトの企画・制作・運営をなりわいとしています。そのかたわらで、自分の住んでいる所が高齢化の問題を抱えているのでVRでなんとか町おこしできないかと、VRコンテンツの研究・開発をおこなっています。
自宅の一室を“VR研究室”として普段からVRコンテンツを視聴したり開発したりしているなか、今回なにを思ったのか銭湯に入り込むVRを作ってみました。銭湯。

銭湯と聞いて、ムフフな想像をした諸兄は多いと思います。正しい反応だと思います。あなたのそういうところ、つまりスケベな想像力を隠そうともしないところ、私は好きです。しかしながら今回のVR銭湯、そういう要素は一切無いんですよね。作ったVRのキャプチャ動画を撮りましたので、まずは見てください。繰り返しますが、スケベな要素一切なしです。

子供の頃の等身大に戻って、銭湯ではぐれてしまった父親を探す、という内容になっています。「なぜ子供なのか?」「なぜ銭湯なのか?」の疑問に対しての説明をする前に、まずは映像の内容について、スクリーンショットを並べながら触れていきましょう。終始湯けむりな雰囲気でどうぞ。

映像についての説明

初期状態

まずはじめに、プレイヤーは銭湯の中に立っています。屋根が異次元空間なのはそういう設計の銭湯ということでしょう。子供なので周囲の大人たちがとても大きく見えます。

キョロキョロ

お父さんはどこ?探してみます。

さて、ここで“プレイヤーはどうやって父親を探すのか”について説明をさせてください。

もともとは“背中にほくろがある”などの情報を与えて、それを探すようにしようか、などと考えていたのですが、他人の背中ばっかりを凝視させたいわけではないのでクイズゲームみたいな手法はとらずに、“お父さんっぽい!と思う人を感じて抱きつくんだ!”という、あえて雑な判断方法で父親を見分けるように設定しています。せっかくVR体験なのだから、感じ取って欲しいのです。間違ってるかもしれないけど、「この人お父さんだー」と思ったら無鉄砲に抱きつく、という子供のような体験をしてもらいたいのです。

移動し始める

近くにはお父さんがいなさそうなので、プレイヤーは移動をし始めます。

ここ長々と語らせてください。

ご存じの方も多いと思いますが、本来、VRで移動をさせるのって鬼門なんです。VR酔いといって気持ち悪くなって吐くから。私は三半規管が強いほうで、3D酔いなどはしたことがないのですが、VRではジェットコースターに乗ったら1分と持たず酔いました。自由に歩き回るコンテンツなども10分もやれば気分が悪くなって立ってられません。このVR酔い問題はVRコンテンツにとって今まで致命的で、これをなんとか解決しようとすると

  • 視覚と聴覚だけでなくさらに多くの感覚を連動して体感してもらう
    • 例えばプレイヤーを乗せて傾くことで重力を使ってプレイヤーに加速度として伝える装置とか、そういった大掛かりな機材が必要
    • まあ、ATLはこれが使い放題なので、つくるものによっては、ここはクリアされてるっちゃクリアされてるともいえるんですけど。てかすげえな!!
  • テレポート(瞬間移動)やワープなど、酔いにくい移動方法をコンテンツ側で用意
    • 操作が煩雑になるし、体験としてやはり少し没入度が落ちる
  • もういっそ動けなくしちゃう
    • その場でしか展開しないミニゲームのようなコンテンツしか作れず、やはり体験の没入度としては少し落ちてしまう
  • そもそも人間がVR酔いに慣れてもらうしかない。たとえプレイヤーが酔おうとお構いなしに動き回る設計にする
    • 気持ち悪くなっても我慢してもらう

以上のような課題を抱えていました。

ところがこの“VRお父さんと銭湯”では、プレイヤーは動き回っています。酔うのを覚悟で動き回っているのでしょうか。いいえ。特別な機材もなしに酔わずに自由に移動できる画期的な方法を実装したからです。もったいぶらずにタネをあかすと、映像にあるように“腕を振ること”で前進するようにしています。つまり体を動かすことで実際に動いているかのように脳を騙しているのです。ランニングマシンみたいな器械にプレイヤーが乗って、その上で実際に歩き回らせることでVR空間を移動するデバイスがありますが、あれと原理は同じで、歩いてるかのように体を動かせば、VR酔いが軽減されるんです。で、大掛かりな装置を使わず、腕を振るだけでも充分に軽減されることが今回わかりました。

もしかしたら、私が知らなかっただけで「そんなの昔からあるよ」な案件なのかもしれませんけど、そのときは「よかった、画期的なVR移動方法は前からあったんだ」といういわゆる病気のこどもはいないのと同じことなので、それはそれでよかったと思います。

ATLではSIMVRが使い放題。人類の夢はここにあったか。僕がルフィならワンピースもうこれでええやんて思って冒険やめるレベル。

ぶつかる!

おっと、明らかにお父さんじゃない人にぶつかったら、弾き飛んでいきました。本来はプレイヤーが子供なのでぶつかって大人の方が飛んでいくのはおかしいのですが、ここでは面白さ楽しさを優先しました。

ちなみになんですが、もし大人とぶつかってプレイヤー側をこかせたりよろめかせたりしたい場合、VRなので実際のプレイヤーをこかすことはできません。画面をゆらせばいいと思うかもしれませんが、これは「画面揺らし」といって最大級のVRご法度になります。VR画面をゆらすとプレイヤーは確実に気持ち悪くなるからです。速攻で吐きます。嘔吐です。やめましょう。

ということで、せっかくのVRなので童心に帰って“ぶつかったら大人が弾き飛ぶ”という、子供ならではのわかりやすい面白さをぞんぶんに楽しみましょう。このようなリアルと虚構の境界線を面白い方に振っていく作業をしているときが、VR作品を作る楽しみのひとつになります。

さまよい歩く

VR空間をさまよい歩きながらしばしのご歓談を。最初普通の銭湯だと思っていたのに、いつのまにか壁をぬけるとだだっぴろいスーパー銭湯の一部だったことが発覚していますね。というかこの頃にはシチュエーションが銭湯だったってこと忘れてますね。なんなんだろう、ただひたすら人にぶつかって弾き飛ばす快感。この作品ってそういう趣旨だったっけ?

どうやらお父さんはシャンプーしながら息子に向かって定期的に喋りかけているようです。声のする方向を参考にしながらお父さんを探すことになります。3D空間内を実際に動き回れるからこそ、音声がビーコンのような役割を果たすことができます。このへんも今後のVR作品つくりのアイデアのタネになることと思います。

お父さん……?

心の底から湧き上がる、お父さんに間違いない感。これです。この人です。声のする方向も間違いないし、感じたままにお父さんに向かって抱きつきましょう!

VR上で、抱きついたあとどうなるかといったフィードバックについて悩んだんですが、結局のところ抱きついたことによる特別なフィードバックは行いませんでした。せいぜいコントローラーの振動を行っているくらいですが、それだけでもVRではけっこうなフィードバックを感じます。本来なら抱きつく寸前を検知して風船でも膨らませて抱きつく感触を実際にフィードバックとかできればいいんですが、そのへんの大掛かりな装置に関しては今後の課題にしたいです。

なぜ銭湯なのか、シチュエーションの選定について

VR空間のシチュエーションを決めるときに、個人的に目安にしてることがあります。それは“誰もがすぐに想像できるシチュエーションでなおかつ少しだけファンタジーなもの”という状況です。あまり突拍子もないシチュエーションすぎるとVRで体験してもピンとこないですし、かといってあまり現実路線過ぎてもせっかくのVR体験なのにつまらないと思うからです。

そんななかで“銭湯”という誰もが想像できてなおかつみんな裸でのんびりしているという少し変わった空間というのは、VRにしてみたい題材としてぴったりでした。加えて、プレイヤーを子供のスケールにすることでVRならではの程よいファンタジー感も生まれました。

具体的な実装方法について

それでは実際にどういう方法で移動しているのかなど、具体的な実装方法についてお話しいたします。

私のVR開発環境がUnity5.6 + HTC VIVEなのでそれに沿って説明していきます。

てっとりばやく結果だけを紹介したほうがいいのかもしれませんが、せっかくのラボですし、結果に至るまでの失敗の過程から順を追って説明したほうが読者のみなさんのヒントにもなり面白いかと思います。

移動方法、腕振りの検知

腕を振ることによってVR空間を移動するので、まずはコントローラーの加速度を検知する必要があります。

もともとはフレームごとのコントローラーの位置情報の差分から距離を求め、ついでに移動平均をとったりしていました。スクリプトは次のような感じです。

// フレームごとの位置情報から距離を求め移動平均を取得する
float dist = Vector3.Distance(device.transform.position, old);
rec.Add(move);
if(MAX < rec.Count){
  rec.RemoveAt(0);
}
var avg = rec.Average();

ところが、尊敬するVRクリエイターの桟さんから次のようなアドバイスをいただき、コントローラーのvelocityをそのまま使用することにしました。

// コントローラーのvelocityをもとに移動した距離を取得
void Update() {
	var mag = deviceL.velocity.magnitude;
}

改良したのがこちらのコードです。これで腕の振りは数値化できます。無加工の情報すごい。私もいつかVRで賞をもらいたい!

コントローラーを動かすたびにプレイヤーが意図せずVR空間を移動しては困るので、腕の振りはコントローラーのグリップボタンを押してあるときだけ取得するようにしました。

// グリップ状態のみ動作するように
// このままでは意図した通りには動作しない
void Update() {
	if (!deviceL.GetPress(SteamVR_Controller.ButtonMask.Grip))
	{
		body.velocity = Vector3.zero;
		return;
	}
	var forward = cam.transform.forward;
	var mag = deviceL.velocity.magnitude;
	var pow = forward * mag * POWER;
	var move = new Vector3(pow.x, 0, pow.z);
	body.velocity = move;
}

コントローラーからの加速度が取れたならあとはヘッドマウントディスプレイの正面ベクトルを取得してその方向に力を加えてやればいい、理屈としては合っています。ですのでこんな感じでコードを書いてみました。

実際に動かしてみるとこれがものすごくうまくいかなくて、まっすぐ進めないのです。理屈は合っているだけに3日間ほどいろいろ試してしまって、この泥沼にハマりました。VRでまっすぐ進めないというのはとても気持ち悪く、前に進んだかと思えば突然横スライドしはじめて、かと思えばいきなり後ろに下がりだすといった不可思議な状況が起こりました。この気持ち悪い動きが2晩も夢に出てきました。VR技術者は開発中とても気持ち悪い思いをするから注意しろというのは私から特筆して忠告したい一言になります。

結局のところどうやって成功したの?

結論から言いますと、“処理するタイミングを変更した”ことでうまくいくようになりました。具体的に言うと移動するタイミングをupdateメソッド内で行うのではなく、FixedUpdateのところに変更しました。

そうです。移動をpositionの変更で行っているのではなく、Rigidbodyに力を加えるやり方で行っていたので、適切なタイミングで処理しないと変な動きになっていたんですね。

// タイミングを変更
void Update() {
}
private void FixedUpdate(){
	if (!deviceL.GetPress(SteamVR_Controller.ButtonMask.Grip))
	{
		body.velocity = Vector3.zero;
		return;
	}
	var forward = cam.transform.forward;
	var mag = deviceL.velocity.magnitude;
	var pow = forward * mag * POWER;
	var move = new Vector3(pow.x, 0, pow.z);
	body.velocity = move;
}

これでひとまず“腕を振ることでVR空間を自由に移動できるようになる”という動作が実装できました。

Rigidbodyに力を加えて移動ってどういうこと?

「FixedUpdateにしたら解決した」というところでいきなり出てきたRigidBody。“プレイヤーがぶつかった人や物は派手に弾き飛ばされたら面白い”と思ったので、プレイヤー側にもRigidbodyが仕込まれています。

弾き飛ぶためには、物体とぶつかったことを検知して、ぶつかった対象のvelocityをn倍(例えば10倍とか。気持ちいい飛び方になるように何倍が良いかは微調整されています)にしています。さらに上方向に大きくはじけ飛んだほうが気持ちが良いので、y方向にはさらにありえないくらいの力を加えています。

// ぶつかったときの処理
private void OnCollisionExit(Collision collision)
{
	var vel = collision.rigidbody.velocity;
	var vel2 = vel * IMPACT;
	collision.rigidbody.velocity = new Vector3(vel2.x, EXPLOSIVE, vel2.z);
}

自分の作ったVR空間の物理法則を好き勝手に調整する作業は、やっててとても楽しいです。

「上方向にもっと飛び上がったほうが面白いから、Y軸だけさらに力を加えよう」と処理をいじって、実際に世界が思い通りに動くようになったとき、まさに神になったかのような感覚を覚えます。これはVRプレイヤーでは絶対に味わえない、VR開発者だからこそ味わえるものづくりの楽しさの特権ですね!

その他、苦労したこと

銭湯というテーマにしたので、アセットストアなどで適切な素材が見つからず、アセットを全て自分で作成しなければいけなかったことが、今回一番苦労しました。

モデリングはBlenderで行ったのですが、このモデリングソフトの操作にまだ慣れておらずテクスチャをUnityにインポートすることひとつとっても問題が発生する始末でした。

しかしながら、操作を覚えてしまえば「VRの世界を全て自分たちで作ることが出来る」VRクリエイターとしてはかなり高い理想に近づくことが出来るので、今後楽しみでもあります。

個人的に見えてきた課題もありました。
まだまだアセットストア頼りなところが多くて、いざアセットがなくて全て手作りするとなると作業時間と品質が読めないですね。
Unityで、“モーションごとに持っているものが違う”というのが出来なくて、おじさんたちは体を洗ったりしてるのに何も持ってないのも気になるところです。ふだん一人で開発してると簡単なところでもつまづいてしまうので、そういった小さな疑問を共有したり課題を解決できる場としても今後ATLの存在が頼もしいと思いました。

最後に

コンテンツクリエイターにとって、VR空間を作るということは白昼夢を作ることに似ていると感じます。まだまだ発展途上のVRだからこそ、リアルには程遠いからこそ感じる白昼夢の感覚、それを自由に作り出せるとしたらどんなに素晴らしいか考えるだけでわくわくします。

UnityやUnrealEngineが個人でも自由に使えるようになったからこそ、誰もが素晴らしいVRを作るチャンスが巡ってきたといえます。殆どの人が字が書けるのに誰もが詩をかけるわけではないのと同じく、誰もがVRを作ることが可能になったからこそどんなVRを作るのかの重要性が増してきたように思います。

この記事を通して、「楽しそうだな、自分もやってみたいな」「大した事なさそうだな、自分のほうがもっとすごいことできそうだな」と思っていただいたら、こんなにうれしいことはありません。


宇都宮正宗

「コンピューターの中に仮想的な世界があるから」という理由“だけ”でコンピューターゲームをやり続けた仮想現実好き。自分の作った世界に入り込めるとか、控えめに言ってVR最高。