見出し画像

Spatial Messageプロジェクトで取り組んだスピード重視の設計

MESONのCTOのえどです。(CTO就任エントリーもあるので気になる方はぜひ読んでみてください!)今回は、そんなCTOになって初のプロジェクトとなった、Spatial Messageの開発の裏側について書こうと思います。(ちょっと間が空いてしまいましたが、色々と知見がたまったプロジェクトになったので公開することにしました)


プロジェクト概要

今回紹介するコンテンツは、2022年11月20日(日)にフクダ電子アリーナで行われたオシム元監督の追悼試合。そこで展示した「Spatial Message」と呼んでいるARコンテンツです。

Spatial Messageはオシム元監督の追悼試合のためだけに作られたものではなく、「空間コミュニケーション」をテーマに博報堂DYホールディングス様と共同研究として取り組んできました。2019年には東京ミッドタウンで初の展示を行い、多くの一般の方に体験していただきました。

今回は2019年の展示内容のうち、テキストを投稿すると文字が柱のように蓄積されていく「絵馬的コミュニケーション」をベースにオシム元監督へメッセージを届けるという体験を制作しました。

デザイナー目線での記事も公開されているので、よかったらそちらもご覧ください。

設計の指針

Spatial Message自体は概要でも書いた通り、2019年に制作されたものの中から演出部分を抜き出して再構築したものです。主に絵馬的コミュニケーションの中枢を担う「文字柱」エフェクト部分を再利用しています。

文字柱が立ち上っていく様子

全体の体験の流れ・設計についてはまったく異なるものになるためイチから設計し直しました。今回はエンジニア2名体制での開発です。自分はリードエンジニアとして参加しました。

そして今回のプロジェクトはイベント当日まで1ヶ月を切っているところからのスタートだったため、以下の部分を指針として設計しました。


  • エンジニアそれぞれが分担して実装が行えるように

  • とにかくイテレーションを早く回せるように

  • データの状態によって遷移する、宣言的フロー(データ駆動)


上でも書いたように特に今回は時間がない中での開発だったため、どこが一番ボトルネックになるか、それをどう解消していくかを中心に据えて設計を行いました。

体験の粒度をエピソードという単位に分割

とにかく時間がない中でのエンジニア2名での開発だったため、作業を完全に分担する必要を感じ、実装のイメージを共通化するために全体の設計を意識しました。まず最初に行ったのが、全体を俯瞰してどういう単位に分解するかという点です。

今回は「ショートムービーのような体験」というのが全体のコンセプトで、ストーリーには分岐がありませんでした。そこで考えたのが「それぞれの体験の単位をエピソードという単位に分割する」ことでした。

エピソードは大まかに以下のような構成になっています。


  • SetupEpisode

  • UserNameInputEpisode

  • PositioningEpisode

  • OpeningEpisode

  • SpatialMessageEpisode

  • MessagePostEpisode

  • SublimationEpisode

  • EndingEpisode


エピソードの処理をScriptableObjectに実装する

Unityには ScriptableObject という、クラスをシリアライズしアセットとして保存できるようにする仕組みがあります。アセットにできるということで、ともするとパラメータやコンフィグなどの、いわゆる「値を保持しておく場所」という見方をするかもしれません。

しかし、Unityの公式ブログで以下のような言及があります。

ざっくり概要を書くと、

  • 変数を ScriptableObject で定義する

  • イベントも ScriptableObject で定義する

  • これらを用いてモジュール化を促し依存を減らし疎結合を実現する

というような内容です。

また記事内で以下のように言及があります。

ゲームをできる限りデータ駆動型にしましょう。データを命令として処理する機械のようにゲームシステムを設計すれば、ゲーム実行中にも、ゲームに効率的に変更を加えられるようになります。

unity.com

今回の設計では特にこの「データ駆動型にしましょう」という点を参考にしました。

この記事を参考に各エピソードの処理は ScriptableObject にロジックとして実装し、必要なパラメータがある場合はアセット化したものに保持するようになっています。

ロジックに対する調整用パラメータを保持できるメリット

エピソードロジックを ScriptableObject にしたことで、ロジックに必要なパラメータをインスペクタから簡単に設定、保持することができるようになりました。

普段はシリアライズしたい値は MonoBehaviour を継承したクラスの SerializeField に保存していますが、これだと GameObject としてシーン内に配置するか、あるいはPrefab化してなにかしら生成する処理を書かないとなりません。

しかし、 ScriptableObject であれば自然とシリアライズ、アセット化することができ、またインスタンスをシーン内に配置する必要がなくなるためとても重宝します。

エピソードアセットの例

エピソードの管理は「EpisodeTeller」

エピソードを単位として構築することが決まり、これらを順番に進めていく役割のクラスが必要でした。最初は Controller や Manager などありがちな名前を考えていましたが、設計コンセプトとマッチせず、どうにもピンと来ない状態だったのですが、エピソードを「話している」というふうに捉えたときに EpisodeTeller という管理クラス名が思い浮かびました。

意味が明確になるし、なにをやるかも明確。マネージャ的クラスが陥りがちな「余分なタスク」も入り込まなそうないい名前が決まり、それをもうひとりのエンジニアにシェアするととても共感してもらえてイメージもばっちり伝えることができました。

「名は体を表す」の言葉通り、クラス名に付ける名前はピンとくるまでしっかり考えることが重要だと考えています。

エピソードの順番はリストで管理

イテレーションを早くする

全体の設計方針・コンセプトが決まったものの、解決しないとならない課題はまだありました。それが「いかにしてイテレーションを早く回すか」という点です。

XR開発においてどうしてもネックになるのが「実機確認で印象がまったく異なる」ということが上げられます。モバイルアプリなどでも、パフォーマンスや、ユーザの手による画面の遮蔽など「実機で見ないと分からない」ことはあると思います。

しかし、ことXR開発においてはその影響が顕著です。そもそもディスプレイが2Dではなく360度どの角度でも見ることができますし、奥行きもあります。PC画面で見ている開発時点では気にならなかったものが、実機で見たら「近すぎる」あるいは「遠すぎる」という感想を抱くことも少なくありません。

また別の視点でむずかしいところと言えば「ユーザがどこを向いているか分からない」というものもあります。現状のARグラスは光学シースルー型のものが主であるため、視野角が比較的狭いです。つまり「視野の外のエリア」、言い換えると死角がすぐにできてしまうのです。

このため、体験の中に見せ場を作っても「見逃した・・・」となってしまうユーザがいる可能性が否定できません。もちろん、それをさせないような視線誘導だったりを入れるわけですが、こればっかりは実機で見ないことには確認できないものです。

つまり総じて、XR開発は「実機で見ることが重要」となるわけです。そうしたわけで、実機での確認時にすばやく見れること、繰り返し見れることを達成しなくてはなりません。

いかに早くデプロイするかだけでなく、体験自体も「実機ですばやく見れる状態」を作る必要があるわけです。

早送りと宣言的フロー

イテレーションを早くする上で今回のプロジェクトで工夫した点は「早送り」と「宣言的フロー」です。それぞれ詳しく解説します。

分岐を減らす目的の早送り

冒頭でも書いたように、各体験は「エピソード」として区切られています。「早送り」とは、いわば曲の頭出しのような仕組みです。つまり「観たいエピソードまですばやく到達する機能」です。

ショートカットではなく早送りにした理由は、体験の流れ自体を見ることができることと「スキップ時にしか起きないバグを避けるため」です。特に今回は開発期間が短かったこともあり、できるだけ分岐処理を少なくしたいという思いがありました。

もし仮に、スキップしたときだけ起きる問題が発生してしまうと、その部分の調査に時間が取られることになってしまいます。しかし、ただの早送りであれば全体的なフローは人間が感じる時間が短いだけで、システム的にはまったく同じことを実行していることになります。

「早送り」自体はUnityの Time.scale の値を変更することで実現するシンプルな方法です。

インスペクタから簡単にスキップが行えるようになっている
実機でもスキップが行えるように、デバッグビルドの場合はメニューが表示される

状態を遷移させない宣言的フロー

早送りと対で設計したのがこの「宣言的フロー」です。いわゆる「宣言的UI」や「宣言的プログラミング」のイメージに近いですが、その一部の考え方を応用したような仕組みです。(参考にしたUnityブログでは「データ駆動」と呼んでいました)

今回の体験はショートムービー的と書きましたが、一部、ユーザの手によって入力されるデータがあります。それが「名前」と「メッセージの種類」そして「メッセージそのもの」です。
以下が実際に作成したデータ構造体です。

[Serializable]
public class MessageData
{
    public string name;
    public string message;
    public int message_type;

    private static readonly int s_limitLength = 30;

    public bool ValidateMessageData()
    {
        if (string.IsNullOrEmpty(message)) return false;\
        // 選択カテゴリは1~3として初期値0のままであればカテゴリを未選択とみなす
        if (message_type == 0) return false;
        if (!IsValidMessageLimitLength(message)) return false;

        return true;
    }

    public static bool IsValidMessageLimitLength(string message)
    {
        if (string.IsNullOrEmpty(message)) return false;
        if (message.Length > s_limitLength) return false;

        return true;
    }
}

ダミーデータでスキップを実現

早送りする場合、途中のエピソードは自動的に遷移する必要があり、こうしたデータはダミーデータで賄われます(ユーザの入力もスキップされるため)。しかし、インプットなどの処理を仲介したり、スキップ時だけ特別な処理、というような分岐を入れてしまうと、前述したようなバグが発生しないとも限りません。

そこで採用したのが先ほど話した宣言的フローです。これは、ユーザが入力するデータを表すデータクラスがあり、そのデータのバリデーションが済んでいたら処理を進めるというものです。こうしておくことでユーザがデータを入力したのか、あるいはあらかじめ設定されているダミーデータなのかはエピソード内の処理では感知しません。言い換えるなら「データが正常なら次に進める」という状態を作り出したわけです。

以下に実装のサンプルをお見せします。

await UniTask.WaitUntil(() => _model.IsValid);

この仕組みにしたおかげで早送りが検知されたら対象のデータクラスに対してダミーデータを設定するだけで済むようになりました。どこでそれらが入力されたかは関係がないため、データさえ正常な値が入っていればエピソードが進行します。そして早送り機能と協調して確認したいエピソードまで到達したら通常再生に戻します。

設計の副作用としてできたデモアプリ

XRの体験は、体験者のみが見ることができるため周りの人はなにをしているか分からない、という問題が常につきまといます。オブザーバビューなどと呼ばれる、外部のモニターなどで体験者が見ているシーンを合成して見せるなどの方法がありますが、今回は屋外の展示であることと期間が短いことから、ネットワークを利用した機能は見送りとなりました。

とはいえ、周りの人がなにをしているか見れないのは呼び込みにも影響しますし、なんとかしないとならない課題でした。そこで、iPad版アプリを作りその映像を外部モニターに映す方法を採用しました。弊社ではGAUGUINというXR開発向けのクラスプラットフォームフレームワークがあるため、iPad版のビルドは比較的すぐ対応することができました。(一部、Unityのバージョン問題などはありましたが・・・)

しかしひとつだけ問題がありました。というのは、ARグラスでの体験では名前入力やメッセージの入力があるのですが、それをデモアプリではスキップする必要があったのです。

スキップ・・・? そう、早送り機能で利用しているスキップ機能が使えたのです。もともとiPad版は最初の設計時点で考慮していたわけではなかったのですが、開発後半のぎりぎりのタイミングで最初の設計で決めたことが活き、iPad版のときだけダミーデータが最初から入っている状態にすることで無事にデモアプリを完成させることができました。

振り返ってみて

CTOとして初のプロジェクトということもあり、このプロジェクトでは今後のプロジェクト進行に対しての布石となるような形にしたいなという思いもありました。特に、もうひとりのエンジニアに設計の大事さを伝えたかったというのもあります。

MESONでは比較的開発期間が短いプロジェクトが多くありますが、今回はその中でもかなり短い部類に入ります。しかもイベントに向けて開発するため、絶対に日付を変更することができません。しかし蓋を開けてみれば、プロジェクト後半は演出のブラッシュアップの時間に当てることができ、大きな設計上の手戻りもなく開発を終えることができました。

実は全然別文脈で出てきた新機能の実装も、今回の設計が活きてほぼ手を加えることなく追加実装できていたりします。

最後に

設計は千差万別。人によって考え方が様々であり、そのため守破離的な要素も強くむずかしいものですが、逆を返せば自分なりの道筋が見えてくるととても楽しい領域でもあります。今回の実装をベースにさらに良い設計を目指し、MESONの開発に寄与できればと思っています。

今回ご紹介したSpatial MessageはMESONオフィスでいつでも体験可能です。XR開発に興味がある方がいましたらぜひ遊びに来てください。

特にエンジニアの方であれば、設計についての話もできますのでぜひ遊びにきていただきたいと思っています。

XRクリエイティブカンパニー「MESON」では、AR/VRを中心とした次世代コンピューティング領域でサービス化、事業成長を志すパートナー企業と共に、企画・デザイン・開発を一気通貫に進めています。XR領域で活躍したい! というエンジニアの方はぜひ以下からご連絡ください!

この記事が気に入ったらサポートをしてみませんか?