大枠
まずサブフロアは、最初は何も動いていません。それは電気が来ていないからっていうのは勿論ですが、初めはやっぱりメインフロアで集中してほしかったから。
メインフロアでいろいろ遊んだ後に初めてサブフロアに気づいた人も居るんだと思います。「電源ついてないんだけどどうして?」みたいな質問を貰った覚えもあります。
最初はそうやって背景として作用することを想定しているんですが、恐らくあまりにも不穏な背景なので「動きそうな雰囲気」は感じて貰えたのではないかと。多分。
この階は全面真っ暗にしようという話になって、廊下にすらランプが置いてなかったりします。見える範囲には無いのです。
そしてあるとき雷が落ち、ロッカーが開いたりするのを見つつ偶然サブフロアに訪れて。この階で唯一光っている謎のボタン。押しますよね。
うっかり起動する謎の機械、明滅するモニタ、意味のわからないコントローラ、こう、まぁ浪漫が9割だと思います。
主な役割は「知らず知らずのうちに水没を進行させること」だと思うので、ここでは「わからなさ」を全面に出しているとも言えそうです。
これは「わかりたいプレイヤー」と相反する願いなので厄介、っていうのは本当にそう。
さて、浪漫が9割とは言え各々ちゃんと「意味」は持っています。一個ずつ見ていきます。
ロッカー
割と重要なのがこれです。ロッカーは機械階とは離れていますが、ちゃんとケーブルで繋がってて機械と同じタイミングで電源が入り、開きます。
最初の世界遷移は「楽器の自由度を上げる」役割を担っていたわけですが、それを実現するのがロッカーという仕組み、ということになっています。単純に楽器を隠すことで自由度を下げて「楽しみやすくしている」とも言えます。
まぁ中に入ってるのはやけに音の良い一斗缶と金属板で加工された缶とかだけではなく、鉄パイプと電子楽器っていう… 自由度の領域を思いっきり広げる物体になっていて。「出来ることがまるっと変化したような感覚」になれていたら幸いです。
さて、一応小さく実装の話を書きます。
まず元々あそこにはpickup類が置いてあります。が、MeshCollider
がオフになっていること、かつ見た目を司るGameObjectがinactiveになっていることから観測できません。pickupされる物体ごとオフにしないのはなんだか怖いからです。
最初の世界遷移でタイマーが走って一定タイミングで「pickup類の有効化」「開く音の再生」「開くアニメーションの開始」を行います。
late-joinerへの同期は確か「開いているはずなんだけど世界遷移が行われない、ならば一瞬で開けたことにする」だったと思います。
音もアニメーションもわりとぴたっと嵌ってめちゃ良いです。あの緑色のランプも点滅してるんですよ。
あとドアの話。…あれは自前の制御機構です。バケツの取っ手の親戚です。
pickupにExact GripとOrientationを指定してあげることで手の位置ぴったりに物体原点を持ってくることができます。
後はそれを元にドアの軸から角度計算、見た目に反映。手から離した瞬間にpickup位置をドアの位置に合わせる。
これはJointとかConstraintにあまり頼りたくない (怖いので) っていうのと、ドアの開閉をちゃんと制御してあげたいっていうモチベーションです (同じですね)。
開きすぎると閉じるようになってるんです。120度までで動かなくなって、180度以降は逆に閉じていく。急に閉じるのも変だと思ったので、こういう実装をしました。正確な腕の動きみたいなのは誰も知らないから、そんなに違和感は無かったのではないかなと思い…ます。多分。
同期はこの角度情報をそのままUdonSyncedさせることで行っています。あんまりうまくいってない印象はある。まぁドアを開けなくても中の物は取り出せるので、ゆるい実装でもさほど問題ではないのです。
あと「開くアニメーション」は、元々あの角度にドアがあるんだけど見た目を弄ることでいい感じに見せています。元々開くタイミングは人によって違うのでローカルで処理しなければならない部分はまぁあるのです。
ちなみにランプの明滅はコレ。
float2 seed = _Color.rg + _Time.xy;
float t = TransitTime;
float th = smoothstep(132, 133, t);
float emi = rand(seed) < th ? 1 : 0;
emi *= pow(rand(seed+1), lerp(0.5, 0.0, _Stability)) * 0.5 + 0.5;
色をseed
にしてるのは… 状態表示の緑ランプと明るい白ランプは実は同じシェーダで。同じタイミングで明滅するのはなんか違うと思ったので。(電子楽器のランプの明滅は全部同じなんだけど同じ形をしてるのでOKだと思った)
_Stability
はどうやら固定値で1っぽいんだけど、割と振動しててちょっと不思議ですね…。まぁ不連続点だしな。ちょっと怖いね。
コンソール
デバッグに便利なの作った (まだガワだけ) pic.twitter.com/Kd5h9p67DY
— phi16 (@phi16_) July 6, 2020
元々は本当にデバッグ用だったんです。世界状態とか天候とか水位を好き勝手設定できるようになってて、VRで実際に見て調節するのに使っていました。
**だからこれは本当に使う為に作られています。**あのキー配列は私が昔から使ってる配置そのまんまなんです。基本的に。
でもそれはタッチパネル用の設計で、例えばスライドみたいな操作があった。VRだとちょっとやりにくい (離したことの判定が不安) と思ったので、両手の強みを使ってみることにしました。それがこれです。
触ってるとわかると思いますが、2つ同時押しする場合は順番は関係なく、また「全ての指が離れたタイミング」で検知をするので「意図されてない文字が入力される」ことはあまり無かったんじゃないかなと。
26個ボタンを置くのはちょっと入力精度が気になるところですが、まぁ10個なら人類割と慣れてそうだし。割とありではないかと思ったりはする。まぁ日常的に使えるかというとそうではないかなまぁ…。
あのボタンは押しやすいように見た目とか色々やってるんですが概ね見たらわかりますね…。
- 指が近づいた段階でもう動いていて、「これは指に反応する」ということを示す。
- 板と指の位置が一致すると入力判定。タッチパネルとして捉えてもいいし、ボタンの押下としても捉えられる。
- ちゃんと入力を受理したことを示す為にオレンジ色に変化
-
瞬間的な反応を表す為にエフェクト円の半径は
pow(t,0.3)
で変化 (t=0
で微分が発散するので) - それはそうと持続的な反応を示すためにゆっくりとしたエフェクト円もある
- これは
pow(t,0.5)*0.7
- これは
- 押下した瞬間にボタンが手前に来る。そしてその位置を指が貫通したら指を離したという判定が発生。
- これはヒステリシスの可視化です。センサの出力を離散化するときに一般に考えたほうがいいやつ。多分。
- 押すときの閾値と離すときの閾値が違うのでチャタらないっていうやつ。押した感も出る。
- これはヒステリシスの可視化です。センサの出力を離散化するときに一般に考えたほうがいいやつ。多分。
デバッグ用にしては凝りすぎかしら。まぁ転用できたらしようという気持ちは実際あったんですけどね。
これを使ったコンソールの機能の実装はまぁ大したものではないので書くことはないですね。同期しないし…。
あ、見た目はほぼシェーダ製です…。1024ポリを適当に配置してる。立体感出すために文字だけは固定位置にしています。
あと小さな話として… 右側にある6枚のパネルは全部何かしらに使われている内部情報です。あのログがどばーって吐かれてるやつはどちらも元々デバッグに使ってたもの。オブジェクト名とかある程度まともにしたほうがいいかなみたいな話をしたこともあったんだけど、まぁ困ることは無いかということでそのままになりました。totan
とかtarai
とかそのままです。
流れるログと流れないログを分けて置いておくのはめちゃくちゃ便利ですね。状態系はやっぱり流れないほうが嬉しいです。
あとは「lightshaft用のテクスチャ」「FallRainの高さと法線用のテクスチャ」「ファンから出る空気のパーティクル用のテクスチャ」、あとおまけで「msdfの描画工程」が描いてあります。Amebientにある文字群はただのCanvasとmsdfベースのやつが結構混ざってます (わかる人にはわかる)。
左側の6枚のパネルは「どこかの写真」ですね。4枚は各エリアの写真、そして鐘の写真が置いてあって謎の示唆になってるのと、あと生産者表示…ではない謎の写真です。Special ThanksにCuteって書いてある方です。
製作中にちょっと呼んでのんびりお話したりとお世話になっておりましたので。あととてもかわいい。
作った人紹介を書くのは (一人制作じゃないので) 必須、とは言え色々悩みがある (メタになっちゃう) ところがあったんですが、右側のログとかが実質メタなのでここが丁度良いなとなって。ちゃんと雷落とさないと見れない程度に主張をせず、それでいてある段階から自然に存在する。
特に「ここまで到達したら終わり」みたいなものはこの世界に無いし、水没してスタッフクレジット出たりしたら最悪みたいな感じになっちゃうんで、作った人紹介が書いてあっても終わった雰囲気にならないというのはある程度重要だったのかもしれませんね。
大きいモニタ
左から「現在の空気発生量」「天候」「世界進行度」です。言語化出来ずともなんとなくわかってた人が多いと思います。
天候、どうせどしゃ降りの雨なのは変わらなくて、唯一「雷が落ちる瞬間にマークが出る」以外に変化はありません。
これの描画は全部2Dシェーダおえかきで出来ています。輪郭線とか綺麗に出しやすいんですよ。真面目に。
// thunder
float2 thUV = uv - float2(0.07,-0.1);
if(thUV.y < 0.15) {
float ta = -0.1;
thUV = mul(thUV, float2x2(cos(ta),-sin(ta),sin(ta),cos(ta)));
float thunderD = abs(dot(thUV, normalize(float2(-2.5,1)))) - 0.05;
if(thUV.y < 0) thUV *= -1;
float trD = sdBox(thUV - float2(0.1,0.12), float2(0.1,0.1));
thunderD = max(thunderD, - trD);
float animTime = ThunderTime*2;
if(abs(animTime) < 1) {
float th = lerp(0.15, -0.15, frac(animTime));
if(animTime < 0) thunderD = max(thunderD, th - (uv.y+0.1));
else thunderD = max(thunderD, - th + (uv.y+0.1));
thunderD = max(thunderD, - 0.25 - uv.y);
d = min(d, thunderD);
}
}
泡の描画に無駄な苦労をしたんだけど解説するの大変すぎてしたくない…。16個パーティクル出てるんだけど、forの回数は8回に押さえているっていう謎の… 逆に中間での計算が増えていて本末転倒説無いわけでもない。
まぁとりあえずこういう「やりたい放題のアニメーション」を組むにはシェーダはとても便利です。Udonの実行コストとか関係ないし。
ちなみに最初はこの大きいモニタの大きさに合わせて作ってたんだけど、コンソールの上の正方形のモニタにも同じものを表示してしまったせいでもうちょっと縦の領域も頑張って作る必要が出てきてしんどかった、みたいなこともあった。
そういえばミラーがここに出ます。コマンドで。
ぶいちゃ民はミラーに集まるとはよく言いますが (私もですが)、ここは珍しくミラーの前に人が集まらないワールドだと思います。…冗談で言っています。
というかその冗談を言う為にミラーを置きました。はい。
MonitorController
えーと。
とりあえず目的は、大量のモニタ群の色をいい感じに制御する機械です。そして前に書いたように「ある程度の計算能力を示すこと」というのもあります。
そこでニューラルネットの重みを直接操作するUIとして、コレが設計されました。某アレと目的が近いので同様に「パネルの上のpickupを操作する」スタイルを取っていますが、その中身は全然違うものになってます。はい。
まぁでもこういう謎UIを作るときの気持ちは一貫していて、必要な物を必要なだけ置くようにしています。
さて、まず状態空間は64x64x2個の実数で、見た目としては64x64の二次元領域の各点に2つの実数の情報が乗る形になっています。それにXとYっていう名前がついているのです。
左の8+1の部分で3x3の畳み込みになっていて (円形に並んでるのはかっこいいから近傍の気持ち)、隣にある1
がbias項。ここまでは線形で、四角いのがclamp(x,-1,1)
を実行します。これで一応非線形。
そしてかっこいいクロスしてる部分が2x2行列の乗算です。要はXとYの2次元ベクトルを好きに線形変換できるということ。丸いのがスカラー倍、分岐点は複製、合流点が加算です。
で、定数項1
の重み付き加算と、あと現在の自分の値の重み付き加算が出来るようになっています。そしてまたclamp(x,-1,1)
ですね。
その先に上(下)に線が伸びているのが実は (意味解釈的には) 重要で、この線は左側に伝って最初の入力位置まで届きます。つまりこれを繰り返すフィードバックループになっているということを示していました。そのつもりでした。
下に行かずにまっすぐ行くと色選択の領域に来ます。交差しててわかりにくいですけど、上の四角がブラウン管用 (CRTのC)。下の四角がモニタ用 (LCDのL) です。まぁ名前が付いていることだけが大事。
色は角の4色を指定することが出来て、その中間は単純に補間されます。(0,0)
に近づくと明るさが減っていきます。
…さて、なんでこんなことになっているのか。
まず3x3の畳み込みを使えば「全体を左に動かす」とか「右に動かす」とかが簡単に出来ます。そしてこれをフィルタとして考えると… Xを現在位置、Yを速度として扱ってあげれば波動方程式が作れそうな…気がするじゃないですか。そう考えるとXとYはお互い好きに参照できるようにするべきで、あと「一個前の値」を使うことで微小変化の扱いが簡単になります。
で、後はライフゲームを作るために何が必要かなと考えて…。bias項と非線形性によってうまいこと「周囲に自分を含めて3個以上生きている」「周囲に自分を含めずに3個以下しか生きていない」ことを表現できる、そしてこれをandで繋ぐために最後にもbias項が乗ってます。そういうやつです。
-1
と1
で出来た古典論理モデルでのandはclamp(2x+2y-2, -1, 1)
として表せます。…たしかpresetに入ってるやつはなんか境界らへんで危うかったんだっけ。なんか変な動きしますよね。
というわけで仕組み自体は単純ながらに色々できる謎の機械が出来ました。多分計算関連についてはソースコード見たほうがはやいと思います。まる。
好き勝手に作ったのでまぁ「謎の機械」として解釈されればいいなくらいの気持ちでやっておりました。謎の仕組みにしたがって確かに動く機械です。かっこいいですよね。それで十全です。
なんか「わかり」のある人が居たりすると実際おもしろい模様とか出来てすごい見てて楽しいです。
今日の練習 (と寝る前ちゃん) #Amebient pic.twitter.com/TTEAz3gIv0
— phi16 (@phi16_) July 25, 2020
#Amebient pic.twitter.com/h01H1JlgJQ
— phi16 (@phi16_) August 3, 2020
今日の #Amebient その1 pic.twitter.com/8beaYeUwPr
— fotfla (@fotfla) July 28, 2020
— スズキ (@suzuki_ith) July 21, 2020
Amebient完全に理解した#Amebient pic.twitter.com/RW7qIoTHcv
— Kagerou(shiromac) (@shiromacKagerou) July 29, 2020
まぁ私は内部構造を知っているとは言え「どうすればいい感じの模様になるか」は知らないので。練習したりもしてました…が、なんかどうしようもなくない?みたいな気持ちにはなってた。
ある程度型を知っていればそこから微妙に動かしたりして面白い動きの調節が出来たりするので、弄ってる感みたいなのはめちゃくちゃ得られる気はしています。そしてそれでいいんだと思う。
入力系が分かれているのはCapになんとなく2つ置いてもらったから…だったような気がする。その時はどう使うかとかは考えてなくて、なんかあったらまぁ使い方考えられるでしょう、くらいだった。
入力系は概ね固定で良さそうだと思ったのでそこだけ分離して置いてもらうことにしたのです。…でも結構動的に弄りたいことあるのよね。これ。
さて、これはある範囲での音の発生をある範囲への値の加算に変換する装置です。独立に4つあって同時に重ね合わせる事ができます。一番左のスライダーがその選択です。
真ん中の領域が上から見たマップになってるのはすぐわかると思います。そして音が出ている場所では波紋のマークが出る。それらを含むようにいい感じに範囲指定してあげて、さらに右下の四角で出力範囲を決める。そうするとその入力領域内で音が発生すると、出力範囲に対して既定の数値が加算されるという動作をします。右上のXとYのスライダーがその数値を指定する場所。
右上のCとLのスライダーはこの入力機械のモニタがCとLのどっちの色を映すかですね。
そういえば今回全体的にスライダーを使っているのは… 同期を自前でやっているから、っていうのと。あとパネルの奥に手を突っ込むUIが面白そうだったから、っていうのもあったと思います。負の数を入力する時には突っ込めばいいんです。うん。
突っ込む時にこのUIたちが消えると困るので、裏にある板には
ZWrite Off
が書いてあります。ちょっとおもしろいのは、これ、全モニタのシェーダと同一で。実はその辺のぴかぴか光るパネルは全部ZWrite Off
なんですけど、Geometry+1
になってるし裏に板がちゃんとあるので誰も気づいてないと思う。
あとスライダーだけだと色指定に困るんですけど、今回は「基本的に明るい色」ということでHSV色空間のV=1の部分をうまいこと指定できるようにしてあります。ちなみに奥に突っ込むと暗くなる。回転を入力に使うのも面白いですよね、特に色相は循環する空間なので「正しい」感じがします。
MonitorControllerくんはなかなか物議を醸していた印象がありますが、まぁやってることが複雑なだけで目的とかはすごい単純なのです。「UI」ですし。
そういえば Monitor Controller Help で説明を書いたつもりだったんですけど (勿論全部書いてあるとは思ってない)、"3x3 Conv" のConvがConversionみたいな解釈をされていてなるほどみがありました。Convolutionです。
分岐で複製・合流で加算するのは (この辺から来ている気持ちもあったと思うけど) まぁ自然かな…と思ったので書かなかった。まぁまずこの線を左から右に数値が流れることが一番必要な情報だったのではないかとは思う。特に反省はしていません。
微妙に怒られが発生しないか怖かった気もするけど作ってるときはそんなことを考える余裕もなかった気もする。まぁ私のやりたかったことができていたのは確かでしたから。
元々あのテレビのモニタは単色ではなくいろいろ遊べたらな、とは思ってたんですけどあまり考察の時間もなくてこうなってしまった。
これはマジでバグらせたときの写真。これはこれでな、かっこいいんすよね。
あと実はモニタ関連は全部同一Materialなんですけど、これはワールド座標で分岐することでどうにかしてます。
これらは開発中にCapから送られてきたスクショ。
Discord漁ってたら「鉄パイプで機械叩いたら壊れて一瞬で水没する案」とかあったの思い出した…。そのためにわざわざ音も実は作ってもらってたんだけど使わなかった (なんか悩ましい気がした)。あと起動音はたしからくとさんと微妙に違う解釈で組み込んでしまったんだけど変えたんだっけ、そのままなんだっけ、うーん (まぁなんか今ので綺麗だと思ったのでそのままにしたとかな気もする)。
あと同期の闇に嵌ったりもした。
同期した pic.twitter.com/32m0CJnPxP
— phi16 (@phi16_) July 14, 2020
1つのUdonBehaviourが出来る同期容量に限界があるっぽかったのでUdonBehaviourを増やせばいいじゃない案です。動いた。
UIの実装についてはぐわーっとやったらわりと動いたのであまり書くことが無いです。よくやったよなこれ…
細かい書いてないことは色々ありますけど、まぁ書くべきことは書いたと思うのでこのへんで…。
起動時のアニメーションが無駄にちゃんとしているとかな。というか全部にアニメーション入れてると思う。そうあってほしかったから。たとえ見ている人が居なくとも。
あ、そういえばサブフロアのモニタの出力が微妙にメインフロアに届いてるっていうのもありましたね…
これです。いい感じの場所があったので使わせてもらいました。大体Capに好きにいろいろ「描ける場所」を置いてもらって、私がそれに好きに色を置く、という流れで作っていました。スピーディでこれはこれでよかったと思います。
おわり
初めの初めのときは海に丸々柱建てるみたいなこと考えていた覚えがあるんですけど、現実的な感じに落ち着きました。もっといろいろVisualに関してはやりたいことたくさんあるんだけど今の所それが出力出来ていません。うーむ。
波紋が壁を伝うみたいなことも考えていた気がするな、最初…。うーん。
Visualを弄る機械として設計したものの、同時に「謎」的な物体 (あと文脈を持った物体) にしてしまったので直感的操作には… って思ったけどアレもまぁ謎物体はそうなんだよな。めっちゃ直感的なんだけどな。
やっぱりNNのパラメータは機械に計算させるのが正解ってことかもしれない。まぁそうか。
いつかやりきれなかったVisualの感じは出そうとは思っているので、のんびりと…。
では。次回は水没する世界の話をします。