CORETECH ENGINEER BLOG

株式会社サイバーエージェント SGEコア技術本部 技術ブログ

Addressables のアドレスを定数として出力できるライブラリを公開しました

こんにちは、SGE コア技術本部(コアテク)基盤チーム所属の袴田です。 Unity の Addressables を使ったプロジェクトで、こんな経験はありませんか?

  • アドレスが character_icon_001 なのか character_icon_0001 なのか分からなくなる
  • タイポに気づかず実機確認時にロード失敗でエラーになる
  • キャラクター ID からアドレスを構築する関数が複数箇所に散在している

パスやファイル名に依存しないアドレスを定義して扱えるのは便利な反面、文字列を直に扱うと問題も起きがちです。

そんな問題を解決するために、Addressables のアドレスを定数として自動生成し、型安全に扱えるパッケージ「Address Definition Generator」を開発し、OSS として公開しました。

このツールを使えば、文字列のタイポを防ぎ、コード補完を活用してアドレスを安全に扱えます。具体的には以下のような課題を解決します。

1. タイポによるバグを防止

タイポがあっても、目視やコードレビューではなかなか気づけず、特にアプリ内の導線としてマイナーな箇所では、ロード失敗に気づくのも遅れがちです。

定数化することで以下のようなメリットがあります。

2. 命名規則に基づくアドレス構築を一元管理

img_001img_002のように連番やパラメータを含むアドレスは、多くのプロジェクトでアドレス構築関数を作っていると思います。

しかし、複数人で開発していると以下のような問題が起きがちです。

  • 同じような関数があちこちに重複して作られる
  • 仕様変更があったとき、一部の関数は更新されたが別の関数は追随漏れになる
  • どの関数が最新なのか分からなくなる

Address Definition Generator では、書式ルールを一箇所で定義すると、そのルールにマッチするアドレスを構築する関数が自動生成されます。

ルールを更新すれば生成される関数も一斉に更新されるため、追随漏れの心配もありません。

また、連番で数千個のアセットがあっても定数は増えないため、IDE の補完リストが膨大にならずに済みます。

基本的な使い方

Address Definition Generator を使うと、例えば以下のような Addressables のグループとアドレスから

Group0

  • img_001
  • img_002
  • img_003

Group1

  • black
  • card001
  • card002

次のような C#コードが自動生成されます。

public partial class AddressDefinition
{
    public static string GetImage(long n)
    {
        return $"img_{n:D3}";
    }
    public const string black = "black";
    public static string GetCard(long id)
    {
        return $"card{id:D3}";
    }
}

書式ルールに基づいて関数が生成されるので、以下のようにタイプセーフにアドレスを扱えます。

// 定数として生成されたアドレス
var handle = Addressables.LoadAssetAsync<Sprite>(AddressDefinition.black);

// 関数として生成されたアドレス
var handle = Addressables.LoadAssetAsync<Sprite>(AddressDefinition.GetImage(1));

インストール

Unity Package Manager の「Add package from git URL...」から以下の URL を指定してインストールできます。

https://github.com/CyberAgentGameEntertainment/AddressDefinitionGenerator.git?path=/Packages/AddressDefinitionGenerator

詳しい設定方法は README を参照してください。

主な機能

1. 書式ルールによる関数生成

アドレスのパターンを示す書式ルールを定義すると、そのルールにマッチするアドレスを構築する関数を生成できます。

例えば、icon_{type}_chr[characterId:5] というルールを定義すると

  • icon_small_chr00001
  • icon_medium_chr01001
  • icon_large_chr20100

これらのアドレスにマッチし、以下のような関数が生成されます。

public static string GetCharacterIcon(string type, long characterId)
{
    return $"icon_{type}_chr{characterId:D5}";
}

書式ルールでは以下の構文が使用できます。

  • {xxx} : 任意の文字列(string 型の引数)
  • [xxx:N] : N 桁の整数(long 型の引数、ゼロパディング
  • [xxx] : 任意の整数(long 型の引数)

書式ルールの構文エラーはコード生成前に検証されます。

例えば{category:2}(文字列型でコロン構文は使えない)や[id:0](桁数は 1 以上)などの無効な構文はエラーとして報告されるため、ルールの定義ミスによって不正なコードが生成されることはありません。

2. プロジェクトに合わせたカスタマイズ

カスタムアドレス型のサポート

IAddressインターフェースを実装した独自の型でアドレスを表現できます。例えば型安全性を高めたり、バリデーション機能を追加したりできます。

public class SampleAddress : IAddress
{
    private readonly string address;

    public SampleAddress(string address)
    {
        this.address = address;
    }
}

この SampleAddress クラスを設定から指定すると、このようなコードとして生成されます。

public static readonly SampleAddress black = new SampleAddress("black");
public static SampleAddress GetCard(long id)
{
    return new SampleAddress($"card{id:D3}");
}

グループ単位での管理

Addressables のグループごとにクラスを生成するか、フラットな構造にするかを選択できます。

グループごとにクラスを生成する場合

public partial class AddressDefinition
{
    public partial class Group0
    {
        public static string GetImage(long n)
        {
            return $"img_{n:D3}";
        }
    }
    public partial class Group1
    {
        public const string black = "black";
        public static string GetCard(long id)
        {
            return $"card{id:D3}";
        }
    }
}

// 使用例
var handle = Addressables.LoadAssetAsync<Sprite>(AddressDefinition.Group1.black);

フラットな構造の場合は、基本的な使い方 の例のように、全てのアドレスが同じクラス直下に配置されます。

大規模プロジェクトではグループ単位で整理すると管理しやすくなるでしょう。

3. 開発フローへの統合

CLI サポート

CI/CD 環境での自動生成にも対応しています。

Unity -quit -batchmode -projectPath "$PROJECT_PATH" \
  -executeMethod AddressDefinitionGenerator.Editor.CLI.AddressDefinitionGeneratorCLI.Execute \
  -all

エラー時は終了コード 1 で終了するため、ビルドパイプラインに組み込みやすくなっています。

便利に使うための Tips

パーシャルクラスで独自拡張

生成されるクラスには全てpartial修飾子が付いているため、手動で追加の関数やプロパティを同じクラスに実装できます。

例えば、Enum やマスターデータを直接渡せるようにした中継関数など、使いやすさを向上させる拡張が可能です。

// 自動生成されたコード
public partial class AddressDefinition
{
    public static string GetItemThumbnail(string category, long itemId)
    {
        return $"item_{category}_thumb_{itemId:D4}";
    }

    public static string GetCharacterIcon(long characterId)
    {
        return $"character_icon_{characterId:D5}";
    }
}

// 手動で追加した拡張
public partial class AddressDefinition
{
    // Enum から変換(プロジェクト独自の拡張メソッドを使用)
    public static string GetItemThumbnail(ItemType type, long itemId)
    {
        string category = type.ToCategory();
        return GetItemThumbnail(category, itemId);
    }

    // マスターデータから変換
    public static string GetCharacterIcon(CharacterMasterData data)
    {
        return GetCharacterIcon(data.Id);
    }
}

パフォーマンス重視なら ZString 対応

パフォーマンスが重要な場合、ZString を使った文字列補間にも対応しています。プロジェクトに ZString が導入されていれば、設定から有効化できます。

public static string GetCard(long id)
{
    return ZString.Format("card{0:D3}", id);
}

まとめ

Addressables は非常に強力なアセット管理システムですが、文字列ベースのアドレス指定にはタイポのリスクがあり、大規模プロジェクトでは保守性の課題になりがちです。

Address Definition Generator を使うことで、型安全性を保ちながら Addressables の利便性を最大限に活用できるようになります。

以下のようなプロジェクトで特に効果を発揮します:

  • 連番アセット(キャラクターやアイテムなど)が多い
  • タイポによる事故を防ぎたい
  • アドレス構築関数の散在や追随漏れをなくしたい
  • CI/CD パイプラインに自動生成を組み込みたい

もし興味を持たれたら、ぜひ以下からプロジェクトに導入してみてください。

機能要望やバグ報告は Issues でお待ちしています。実際に使ってみた感想なども歓迎しています!