Unityでの開発を支えるアプリケーション基盤

はじめに

Unityでゲームを開発していると、プロジェクトが変わっても繰り返し必要になる機能があります。デバッグツール、多言語対応、セーブデータの管理、大量データを表示するスクロールリスト――どれも一から作ると相応の工数がかかりますし、プロジェクトごとに品質がバラつくリスクもあります。
そこでサムザップでは、こうした共通基盤を UPM パッケージ として整備し、プロジェクト横断で再利用できる仕組みを構築しました。
この記事では、その基盤パッケージ群「Spica」の全体像と、各パッケージの機能・設計方針について紹介します。

Spica とは

名前の由来

Spica という名前は、SumzapPicatrix を組み合わせた造語です。
Picatrix とは、中世ヨーロッパに伝わる伝説の魔導書で、天体魔術や錬金術の秘奥が記されているとされる書物です。「開けば開発の悩みが解決する、サムザップにとっての伝説の魔導書を作ろう」――そんな想いを込めて命名しました。
……とはいえ、実際のところ中身は魔術ではなくC#です。呪文の代わりにコードを唱えて開発課題を解決していきます。

構成

Spica は独立した UPM パッケージ の集合として構成されており、現在は以下の4つのパッケージが提供されています。今後も必要に応じてパッケージが追加されていく予定です。

パッケージ名 役割
Spica.Debugger ランタイムデバッグツール
Spica.TextLocalization テキストのローカライズ
Spica.Persistence データの永続化
Spica.ReusableScroller 高性能スクロールリスト

各パッケージは独立しているため、必要なものだけをプロジェクトに導入できます。「デバッグツールだけ欲しい」「ローカライズだけ使いたい」といった柔軟な運用が可能です。

【1】Spica.Debugger

ゲーム開発中のデバッグやQA作業を効率化するための、UI Toolkit ベースのランタイムデバッグツールです。ランタイムとエディタの両方で動作します。

Spica.Debugger

開発の背景

従来、ランタイムデバッグツールとしては SRDebugger を利用していましたが、開発開始当時は更新が長期間停止しており、新しい Unity バージョンへの追従や機能拡張が難しい状況でした。そこで、プロジェクトの要件に柔軟に対応できる自前のデバッグツールを開発することにしました。
UIの構築には UI Toolkit を採用し、独自のデバッグメニュー定義の仕組みは UnityDebugSheet を参考に設計しています。

主な機能

Console

Unity のログをリアルタイムに表示するログビューアです。フィルタリングや検索、スタックトレースの確認が可能です。同一ログの折りたたみ(Collapse)にも対応しており、大量のログが出力される場面でも効率的に確認できます。

Profiler

FPS やメモリ使用量をリアルタイムに監視できるプロファイラです。常時表示するオーバーレイモードも備えており、ゲームプレイ中のパフォーマンスを視覚的に把握できます。

System Info

デバイス情報・アプリケーション情報・グラフィックス情報・メモリ情報を一覧で確認できます。プロジェクト固有の情報を追加表示するカスタム情報の登録にも対応しています。

Report

スクリーンショット付きのバグレポートをその場で作成・送信できる機能です。送信先として Slack と Wrike に対応しているほか、基底クラスの継承で独自の送信先も追加できます。例外発生時にスクリーンショットやログを自動で送信する自動例外レポート機能も備えています。

Options(カスタムデバッグメニュー)

プロジェクト固有のデバッグ設定画面を自由に定義・追加できる拡張ポイントです。トグル・スライダー・ドロップダウン・入力フィールドなど、さまざまなUIコンポーネントをビルダーパターンで簡単に構築できます。ページのネスト(サブページへの遷移)にも対応しており、パスを指定して特定のページを直接開くことも可能です。

トリガー(起動方法)

デバッガの表示切り替えには複数のトリガーが用意されており、開発環境やデバイスに応じて使い分けができます。

トリガー 操作
Floating Button 画面上のフローティングボタンをタップ
Keyboard キーボードショートカット(カスタマイズ可能)
Triple Touch 3本指同時タッチ
Pinch ピンチ操作
Corner Long Press 画面の角を長押し
Corner Triple Tap 画面の角を3回タップ

どのトリガーが最適かはプロジェクトの好みや要件によって異なるため、複数の選択肢を用意しています。また、独自のトリガーを定義して追加することもできます。

設計方針

UI Toolkit の採用

デバッグツールのUI構築に UI Toolkit を選んだのは、USS によるスタイル管理のしやすさと、ListView の仮想化をはじめとする高機能なコンポーネントが利用できる点が大きな理由です。たとえばコンソールのログ一覧では ListView の仮想化を活用しており、大量のログがあっても描画負荷を抑えられています。また、ゲーム本体の uGUI とは独立したUI層として動作するため、既存のUIと干渉しません。

ページベースのアーキテクチャ

デバッガのUIは「ページ」を単位として構成されています。Console・Profiler・System Info・Report・Options がそれぞれ独立したページとして実装されており、Options 内ではスタックベースのナビゲーションでサブページに遷移できます。新しいページは基底クラスの継承で追加でき、拡張が容易です。

ビルド制御

Define Symbol によってデバッガの有効・無効を制御できます。リリースビルドではシンボルを除外することで、デバッグコードが一切含まれないビルドを生成できます。

【2】Spica.TextLocalization

TextMeshPro と連携するローカライズシステムです。CSV からの自動変換やゼロアロケーションを意識した設計で、パフォーマンスと運用のしやすさを両立しています。

主な機能

テキストデータの管理

ローカライズテキストは CSV ファイルで管理し、ScriptableObject に自動変換して使用します。言語ごとに ScriptableObject が生成され、登録するだけでアプリケーション全体からキーベースでテキストを取得できます。

アプリ組み込みテキストと追加ダウンロードテキストの分離

テキストデータを「アプリに組み込むもの」と「追加ダウンロードで取得するもの」に分けて管理できます。こうした分離を行ったのは、テキストの修正や追加をアプリのアップデートなしで配信したいという運用上の要件があったためです。
内部ではマージモデルを採用しており、後から登録されたデータが既存のキーを上書きする仕組みになっています。アプリ本体には最低限のテキストを組み込んでおき、修正が必要になった場合はダウンロードで差分を配信するだけで対応できます。

プロジェクト固有の特殊タグ

テキスト内に特殊タグを埋め込むことで、プロジェクト固有のスタイルを適用できます。

タグ 用途
<key=xxx> 別のローカライズキーを参照して埋め込み
<w=xxx> 登録済みの単語に置換
<c=xxx> プロジェクト定義のカラーを TextMeshPro タグとして適用
<s=xxx> プロジェクト定義のフォントサイズを TextMeshPro タグとして適用

この仕組みを導入した背景には、テキスト全体で色やフォントサイズを統一し、変更が必要になった際には定義を一箇所変えるだけで全テキストに反映できるようにしたいという意図があります。TextMeshPro 標準のリッチテキストタグ<color=#RRGGBBAA> など)を直接埋め込む方式では、CSV 上での管理が煩雑になり、色を変えたいときにすべてのテキストを修正する必要がありました。
名前付きタグにすることで、テキストデータ上では <c=gold> のような直感的な記述でスタイルを適用でき、デザイナーとの協業においても「gold はこの色」という共通認識のもとで運用できます。

ローカライズコンポーネント

TextMeshPro のオブジェクトにアタッチすると、言語切り替え時に自動でテキストが更新される専用コンポーネントを提供しています。Inspector からキーを指定するだけで使えるため、コードを書かずにローカライズ対応が可能です。

設計方針

パフォーマンスへの配慮

テキストの検索にはバイナリサーチを採用し、大量のテキストデータがあっても高速にアクセスできます。また、オプションで ZString と連携することで、文字列フォーマット時の GC アロケーションを大幅に削減できます。タグ処理ではテキスト内にタグ候補が含まれない場合は正規表現の処理をスキップするなど、不要な計算を避ける工夫もしています。

マージベースのデータ登録

テキストデータは登録順に蓄積され、同一キーは後から登録されたもので上書きされます。このシンプルなマージモデルにより、「アプリ組み込み」→「追加ダウンロード」→「ホットフィックス」といった段階的なテキスト配信を、特別な仕組みなしに実現できます。

【3】Spica.Persistence

型ベースの API でデータを保存・読み込みできる永続化ライブラリです。シリアライザや暗号化の選択肢が用意されており、用途に応じた柔軟な構成が可能です。

主な機能

型ベースの API

保存・読み込みの操作は型を指定するだけで行えます。ファイルパスは型のフルネームからハッシュを自動生成して管理されるため、利用者がパスを意識する必要がありません。
型ベースの設計にしたのは、APIをシンプルに保ち使う側の負担を減らすことと、パスやキーの文字列管理によるミスを防ぐことが目的です。型を指定するだけで保存先が一意に決まるため、パスの重複や指定ミスといった問題が構造的に発生しません。

シリアライザの選択

シリアライズ方式は差し替え可能な設計になっており、Unity 標準の JsonUtility を使った JSON 形式と、MemoryPack を使った高速・省メモリな形式を選択できます。

暗号化

暗号化方式も差し替え可能です。暗号化なし(開発時向け)と AES 暗号化を切り替えられ、暗号化キーは ScriptableObject で管理できます。

キャッシュ機構

読み込んだデータは型ごとに参照カウント付きのキャッシュで管理されます。同じ型のデータを複数箇所でロードしても、2回目以降はキャッシュから同一インスタンスが返されます。参照カウントが0になるとキャッシュから自動で解放されるため、メモリの無駄も抑えられます。
このキャッシュ機構を導入した主な理由は、同一データの複数インスタンスが生まれることによる不整合の防止です。キャッシュがなければ、ロードするたびに別のインスタンスが生成され、一方で行った変更がもう一方に反映されないといった問題が起こりえます。キャッシュにより常に同じインスタンスが返されることで、こうした不整合を構造的に防いでいます。

非同期対応

async/await および UniTask による非同期の保存・読み込みにも対応しています。

設計方針

パイプライン構造

データの保存は「シリアライズ → 暗号化 → ファイル書き込み」、読み込みは「ファイル読み込み → 復号 → デシリアライズ」というパイプラインで処理されます。各段階が分離されているため、シリアライザや暗号化方式を自由に組み合わせられます。

スレッドセーフなファイルアクセス

ファイルの読み書きはファイルごとのロックで排他制御されており、非同期処理やマルチスレッド環境でも安全に動作します。書き込み時は一時ファイルに出力してからリネームする方式を採用しており、書き込み中のクラッシュによるデータ破損を防止しています。

コンパイルスイッチによる構成

MemoryPackUniTask といった外部ライブラリとの連携は Define Symbol で制御されており、プロジェクトに導入済みのライブラリに応じて最適な実装が自動的に選択されます。

【4】Spica.ReusableScroller

セルのリサイクル機構を備えた高性能なスクロールリストです。スクロール機能を1つのコンポーネントに統合し、方向を1軸に固定することで、数千件のデータでも少数のセルインスタンスでスムーズにスクロールできます。

Spica.ReusableScroller

主な機能

セルリサイクル

画面外に出たセルを回収して再利用する仕組みにより、メモリ使用量と描画コストを最小限に抑えます。10,000件のリストでも、実際に生成されるセルのインスタンスは画面に表示される分+αの数だけです。プレハブインデックスごとにリサイクルプールが管理されるため、複数のセルプレハブを使い分ける場合でも効率的にリサイクルされます。

表示領域
+---------------------------+
|  [Cell A]  ← 再利用      |
|  [Cell B]                 |
|  [Cell C]                 |
|  [Cell D]                 |
|  [Cell E]  ← 再利用      |
+---------------------------+

↓ スクロール ↓

+---------------------------+
|  [Cell B]                 |
|  [Cell C]                 |
|  [Cell D]                 |
|  [Cell A → F] ← 上のAを再利用 |
|  [Cell E → G] ← 上のEを再利用 |
+---------------------------+

スナップ

ドラッグ終了時やフリック後に、最も近いセルにスナップする機能です。ドラッグ中のスナップとフリック後の慣性スナップの両方に対応しています。

ループスクロール

先頭と末尾をつなげて無限にスクロールできるループモードを備えています。カルーセルUIの実装に適しています。

可変サイズ・複数プレハブ

セルごとに異なるサイズを指定でき、複数のプレハブも使い分けられます。データクラスのプロパティで制御するため、動的な切り替えも容易です。

入れ子 ScrollView

スクロール方向が異なる ScrollView を入れ子にした場合のドラッグイベント伝播に対応しています。縦スクロール内に横スクロールを配置するようなUIを自然な操作感で実現できます。

イージング

スナップやプログラムによるスクロールアニメーションには、33種類のイージングタイプ(Linear, OutQuad, InOutCubic, OutBounce など)が用意されています。

設計方針

ScrollRect / LayoutGroup を使わない独自実装

Unity 標準の ScrollRect を使わなかったのは、ScrollRect が縦横2軸方向のスクロールに対応した汎用的な設計になっており、1軸のスクロールリストには不要な計算コストが含まれているためです。
Spica.ReusableScroller はスクロールに必要な機能を1つのコンポーネントに統合し、方向を1軸に固定することで、これらの不要な処理を排除しています。入力処理・スクロール位置管理・セル配置をすべて自前で実装することで、必要最小限の処理だけが実行される構造になっています。

事前計算されたレイアウト

セルのサイズと位置はデータのリロード時に一度だけ計算され、以降はその結果を参照するだけです。フレームごとのレイアウト再計算が発生しないため、大量のセルがあってもパフォーマンスに影響しません。表示範囲内のセルインデックスの算出にはバイナリサーチを使用しており、O(log n) で高速に動作します。

データとビューの分離

セルのデータとビューが明確に分離されています。データ側でセルのサイズやプレハブの種類を指定し、ビュー側では表示の更新に集中するテンプレートメソッドパターンを採用しています。

共通の設計方針

モジュール分割

Spica の各パッケージは独立した UPM パッケージ として提供されます。パッケージ間に依存関係がないため、プロジェクトの要件に応じて必要なものだけを選択的に導入できます。

パフォーマンスへの意識

各パッケージは、モバイル環境でも快適に動作するよう設計されています。

  • TextLocalization: ZString によるゼロアロケーションフォーマット、バイナリサーチによる高速検索
  • Persistence: MemoryPack による高速シリアライズ、参照カウント付きキャッシュによるI/O削減
  • ReusableScroller: セルリサイクルによるインスタンス数の最小化、ScrollRect/LayoutGroup 不使用による計算コスト削減

拡張性

各パッケージは拡張ポイントを提供しています。

  • Debugger: レポート送信先の追加、カスタムデバッグページの作成、独自トリガーの追加
  • Persistence: シリアライザ・暗号化方式のカスタマイズ
  • ReusableScroller: セルの見た目と振る舞いの自由な定義

プロジェクト固有の要件に対応しつつ、共通基盤のメリットを享受できるバランスを意識しています。

まとめ

この記事では、サムザップの Unity 向けアプリケーション基盤「Spica」の概要と、各パッケージが提供する機能・設計方針について紹介しました。

  • Spica.DebuggerSRDebugger に代わる UI Toolkit ベースの自作デバッグツール
  • Spica.TextLocalization — プロジェクト固有タグとテキスト分離管理に対応したローカライズシステム
  • Spica.Persistence — 型安全でキャッシュ機構を備えたデータ永続化
  • Spica.ReusableScrollerScrollRect 不使用で高パフォーマンスを実現するスクロールリスト

ゲーム開発ではゲームそのものの面白さに直結する部分に集中したいものですが、こうした共通基盤を整備しておくことで、各プロジェクトが本質的な開発に注力できる環境を作れます。
Spica はまだまだ成長途中ですが、伝説の魔導書の名に恥じないよう、引き続き育てていきたいと考えています。


s07452
中北 龍秀

株式会社サムザップ クライアントエンジニア 2019年入社
『呪術廻戦 ファントムパレード』や『真 戦国炎舞 -KIZNA-』の開発を経て
現在はアプリケーション基盤と新規プロジェクトの開発を担当