Readroom についての雑記

Readroom-りどるーむ-

Readroom

※※※この記事にはネタバレが含まれます※※※

はじめに

Readroom
りどるーむ

謎解きも出来るChillワールド、だと思います。

ワールド名は”Riddle” + “Room” から来ています。
単純にRiddleRoomでも良かったのですが、明らかに謎解きワールドっぽい名前になるのは避けてひとひねりしてます。
(あくまでもChillワールドなので)

一説によると「Read the room」で「空気を読む」という意味になるようで、謎解きであることを読み取るというニュアンスがあるとか

また別の説では各開発者の名前のイニシャル

R: ヒャクアシさんの別名義のイニシャル
e: 円フツさんのイニシャル
a: aki_lua87のイニシャル
d: らぃむさんのIDのイニシャル(dimebag29)

だとか。


元々は「ギミック製作者何人かで謎解きっぽいの作って持ち寄れば謎解きワールドができるんじゃね?」みたいな雑談から始まったような気がします。

Kano’s Lake をみんなで解いた際にぽっと話が出始め
個人的にもApril Waters を訪れた際に「和み雑談する人」と「謎解きを進める人」が共生できるのいいなぁと思っていたので
Chillワールド+謎ギミックみたいな方針で作成が始まりました。

まぁ、Chillワールド成分は全部円フツさんにやってもらったわけですが
まぁ、めっちゃいいワールドになっててすっげえなと思いました。
個人的好きポイントは寝室のスライドドア閉めるやつです。

以下、aki_lua87作成部分。

謎1「神社」

神社
利用アセット

神社は別ワールドでフラグを立てることで回答が可能になるというギミックを用いた謎解き(?)です。

神社自体は特に何か意味がある物ではなく単純に景色のプラトーが埼玉って聞いてなんとなく武蔵一之宮氷川神社が浮かんだので神社にしました。

最初はホラーワールド風にして1人で来た人を恐怖のドン底に落としてやろうかとも思ったのですが
Chillの部分が想像以上のハイクオリティに仕上がっていくのを見て流石に壊すのは如何かと思い変更しました。

入室上限が1人なのはその名残でもあり、インスタンスに人を残すメッセージでもあったのですがあまり有効には働かず失敗だったかな。

折角部屋っぽいので「ベランダから見えるランドマークポイントに訪れる。」というようなフレーバーもあります。
ベランダからレイを飛ばしてワールドに入るみたいなとこも気に入ってます。
(が意外と知られて無い機能だったかもしれない)

謎自体は「ワールドを跨いだ謎解き」「答えが毎回変化する謎解き」になります。

  1. ワールドA(Readroom)にてStringLoaderをコール、インスタンス固有のコードを受け取る
  2. インスタンス固有コードは同期変数で同期
  3. ワールドB(神社)にて固有コードより推測される解除コードを入力しStringLoaderで送信
  4. ワールドAの解答入力時に解除コード入力済みかのフラグをStringLoaderで確認し解除済みであればクリア

となります。

草案図

インスタンスの固有コードの概念については「謎解きワールドで謎の答えを教えてもらう」のカウンターにならないかと考えて「答えじゃなくて必ず解き方を教えてもらわないと解けない」を目指しました。
まぁこれ自体はStringLoader使わずにワールド内で生成しても馴染事は出来ますが。
Webを使う利点はチェックすることで被り無しで発行できることですかね・・・

ただいろいろミスリードさせてしまったみたいで…ちょっと混乱を招いちゃったかなとは思ってます。
ほかの謎はギミック自体が謎であるのに対してこれはギミック自体は初見だとギミックでもなんでもないのでちゃんと謎解きに落とし込むまで練り上げる必要があったかなとも。

StringLoader呼び出し用のVRCURLはプログラム上で動的に作成はできないので
予め発行可能な固有コード全通り分のURLを生成して変数で管理してます。

結果としてトータルで10000ケースでかつ状態管理用に数パターン必要なためVRCURLを管理するだけの数万行のU#が生まれてしまいました。

正直受け入れられるかも含めてプロトタイピングみたいなところもあり細かいところが雑なのですが
まぁこの時はこれでうまくいくと思っておりました。
がリリース後ちょっと色々問題が発生して・・・


アクシデント1 リソース枯渇

これは見積もりと設計が甘い話。

ワールドBでの解除コードは容易に復号できる形でURLに辿り着きたかったのでそのまま入力値≒固有コードでやってたのですが
想定を大幅に超える方々にワールドを遊んでいただいたことで用意された固有コードをほとんど食い潰す自体に陥りました。

固有コードを食い潰した状態だと当人としては間違った解除コードを入力しているにもかかわらず
他インスタンスの解除コードと一致してしまった結果、ワールドBを間違えた解除コードで突破可能になってしまってました。
(他人に解かれる可能性はあるとは思ってましたがまぁ1000コード程度なら偶然当たることも無いだろうとタカを括ってましたが、誕生日のパラドクスじゃないですが母数が増えるほど当たってしまうのでそもそもそれ自体が間違い)

さらに二次被害として解除コードの利用回数に制限を設けていたため
偶然にも解かれてしまったインスタンスが進行不能になってしまうということにも。

誰かが総当たり試みると終わってしまうので、そもそも仕組みがよくなかったです。

問題発生後に急遽解除コードのパターンを1万通りから100万通りに拡張しました。
(ギミックの根幹を変えるわけにはいかないので、偶然の一致の抑制+総当たりに挑む気力を無くさせるの効果を期待)

固有コードは前述のURLの関係で拡張できなかったため多少無理やりですが、現在遊んでいるワールドに影響がないようにワールドA,ワールドB,StringLoader受付サーバを更新する必要があり少し大変でした。。。
(気づいた方もいるとは思いますが、途中でDescriptionが変わったり、問題の数字の桁が増えたのはそのためです)


アクシデント2 VRChat外部よりアクセス

こちらは設計とセキュリティが甘い話。

今回用意していたURLはクエリパラメータで固有コードを判別し処理していたのですが
固有コードに特に暗号化も設けず、またサーバサイドもリクエストのチェックなど実施せず全て受け付けるようにしていました。

なので・・・といいますか、VRChat外部より固有コード発行および固有コードを使った解除を網羅的に呼び出されてしまう事態が発生しました。
サーバは受け付けたもの全て処理するので、不正なレコードをたくさん作られてしまいました。(テヘペロ

これは前述のリソース枯渇問題にも影響し、固有コード枯渇や進行不能インスタンスを作り出すことになってしまいました。

当然といえば当然ですがStringLoaderで状態を管理する場合はパラメータの難読化やアクセスを弾くような仕組みが必須ですね。
後付けでやりましたが最初からやっとけよという話。


順位システム

初日クリアインスタンスにて撮影、なんかバグって鍋の順位が出てない

5日目クリアインスタンスにて撮影、本当に滅茶苦茶解かれたと思う

謎ではないですが自分が担当した部分なので。

遊んでくださった方は体感したかと思います。このワールドには「順位」があります。
各問題ごとのクリア順位と全体のクリア順位のやつです。

神社が「ワールドを跨いだ」ギミックだとするとこちらは「インスタンスを跨いだ」ギミックのようにイメージしてます。
単純に数字をインクリメントしるだけですが……

インスタンスをチームのような概念と考え他のチームと「リアルタイムで競い合える」もしくは「1位を目指す!!!」のようなモチベーションになってほしくて作りました。
(いい感じのUIにしてくれたのは円フツさんです。マジで感謝)

元々「インスタンスを跨いでゲームをする」みたいな概念はたまに考えてて ワールド間対戦リバーシ のようなものを作ったこともありました。
(まぁこれは対戦相手がリアルタイムでいないと遊ぶことすらできないのでよっぽど流行らないと成り立たない)

順位を表示すること自体は結構成功だったかなと思ってます。
Twitter(現X)に順位のスクショを載せてくれる方も結構いて嬉しかったですね。
(なんかバグって表示されて無かったりも多々あったようですが……)


その他

実はキューリストにURL入れるとタイトルに変換される

URLを入れてしばらくするとこんな感じに、ちょっと便利
自作ギミックとかではなく Iwasync のキューリストと YTTL を繋げてるだけです。



おわりに

制作物の8割くらいは円フツさんなのでだいぶ何もやってないですが
やってて楽しいワールド制作でした。

色々問題もありましたが、たくさんに人に遊んで頂いてよかったです。

あと鍋は俺もこんなん解けるわけねえだろってずっと思ってます。

readroom2とかは多分ないです。

おわり