Menu
Menu
Blog
ブログ
About Site
サイトについて
Profile
プロフィール
Activity
活動
Works
制作
Contact
お問い合わせ
音ゲー好きなエンジニアの個人事務所。気ままにブログを更新しています。
Otogeworks
  • Blogブログ
  • About Siteサイトについて
  • Profileプロフィール
  • Activity活動
  • Works制作
  • Contactお問い合わせ

読み込んだフォントアセットのマテリアルを安全に差し替える方法

2023 6/30
ゲーム開発
Unity
2023年7月1日

UnityでTextMesh Proのフォントアセットを読み込み、スクリプトからフォントのマテリアルを差し替える処理を行いました。
1つのフォントに対して存在する複数のマテリアルをプログラム内で動的に差し替える必要があり、その上で外部アセットにフォントを渡す必要があったからです。

// フォントアセット取得
TMP_FontAsset font = FontMap["フォント名"]; // FontMap is Dictionary

// マテリアル差し替え
font.material = FontMaterialMap["マテリアル名"]; // FontMaterialMap is Dictionary

// 外部アセットの公開メソッドにフォントを投げる
ExternalAsset.PublicMethod(font);

すると次回以降、フォントアセットを読み込めなくなって起動に失敗する事態に陥ってしまいました。
仕方なく手動でフォントアセットを作り直すと初回は動作したものの、やはり2回目以降は同じ状況です。
※エラー内容を失念。残しておけばよかったな……

実はこの時、使用したフォントアセットとマテリアルはそれぞれ下記の状態でした。

  • フォントアセットは、リソースから直接読み込んだオブジェクト
    →具体的にはAddressables.LoadAssetAsync()で読み込んだもの
  • マテリアルは、リソースから直接読み込んだ後にnew Material();したオブジェクト

ここで使っているTMP_FontAssetインスタンスはnewしたオブジェクトではなくリソースから直接読み込んだオブジェクトのままなので、このオブジェクトに値をセットすることはリソースのフォントアセット(ファイル)を直接書き換えることを意味します。

そう、上記のコードではプログラム内で生成したマテリアルをフォントアセットに設定しています。
該当シーンをDestroyしたり、ゲームを終了するなどしてオブジェクトの参照を切った時点でマテリアルはメモリの藻屑となって消えるので、フォントアセットが指し示すマテリアルが意味のないものに変わり果ててしまったわけですね。

これではフォントアセットが壊れてしまうのも納得です。
とりあえずマテリアルの差し戻し処理を行うことで解決しました。

// フォントアセット取得
TMP_FontAsset font = FontMap["フォント名"]; // FontMap is Dictionary

// 元マテリアル取得
Material sourceMat = font.material;

// マテリアル差し替え
font.material = FontMaterialMap["マテリアル名"]; // FontMaterialMap is Dictionary

// 外部アセットの公開メソッドにフォントを投げる
ExternalAsset.PublicMethod(font);

// マテリアルを差し戻し
font.material = sourceMat;

しかしそもそもの原因は、フォントアセットを読み込んだままの姿で使っていることです。

この箇所のコーディングにミスがあるとフォントアセットが壊れます。
それ、バグってしまうと最悪の場合ゲームの動作に必要なファイルが破壊されるということです。
リスクが大きすぎて怖い。

こういう時はディープコピーすれば解決しますよね。
具体的にはフォントアセットとテクスチャとマテリアルをそれぞれ再生成して差し替えします。

/// <summary>
/// テクスチャとマテリアルを再生成したフォントアセットを生成
/// </summary>
public static TMP_FontAsset CopyFontAsset(TMP_FontAsset original)
{
    // フォントアセットのオブジェクトを再生成
    // なおInstantiateの実行に必要なので、このメソッドを記載する対象はMonoBehaviourもしくはObjectを継承したクラスであること
    TMP_FontAsset copy = Instantiate(original);

    // テクスチャをコピーして生成
    Texture2D origTexture = original.atlasTexture;
    Texture2D newTexture = new Texture2D(origTexture.width, origTexture.height, origTexture.format, origTexture.mipmapCount, !origTexture.isDataSRGB);
    Graphics.CopyTexture(origTexture, newTexture);

    // マテリアルをコピーして生成
    Material newMaterial = new Material(original.material);
    newMaterial.SetTexture("_MainTex", newTexture);

    // 生成したテクスチャとマテリアルをセット
    copy.atlasTextures = new[] { newTexture };
    copy.material = newMaterial;

    return copy;
}

※Texture2DクラスのisDataSRGB()メソッドはUnity 2022.2.0以降で使用できます。
 それより前のバージョンでは使用できないので、テクスチャがリニアならtrue、ガンマならfalseにしてください。

フォントアセットを読み込む処理で上記のメソッドにTMP_FontAssetインスタンスを渡して、返ってきたコピーをプログラム内で扱うようにしてください。

あと、必要なくなったらちゃんとDestroyしましょう。

foreach (Texture2D tex in copy.atlasTextures)
{
    Destroy(tex);
}
Destroy(copy.material);
Destroy(copy);

これを忘れるとメモリリークしてしまいます。

フォントのマテリアル(テクスチャ)を差し替えるのは割とニッチな利用方法なのか、この一連の話題は調べても出てきませんでした。
たったこれだけのメソッドですが躓きポイントが多くてハマりました。
誰かが車輪の再発明をしないように祈りつつ、電子の海にこのメモを放流。

ゲーム開発
Unity
ushui
宮城県生まれのエンジニアです。
都内でフリーランスを営んでいます。

カレー、技術、音楽、音ゲーが好き。
音楽面に関しては、特にBMSとVOCALOIDから生まれた同人音楽家たちに強く影響を受けました。
新着記事
  • ボカロPたちが戦った20年間のセカイ(弐) 音楽業界と二次創作
    ボカロPたちが戦った20年間のセカイ(弐) 音楽業界と二次創作
    2023年9月1日
    音楽
  • ボカロPたちが戦った20年間のセカイ(壱) ボカロ文化の誕生
    ボカロPたちが戦った20年間のセカイ(壱) ボカロ文化の誕生
    2023年9月1日
    音楽
  • ボカロPたちが戦った20年間のセカイ(零) 同人音楽文化の成立
    ボカロPたちが戦った20年間のセカイ(零) 同人音楽文化の成立
    2023年8月31日
    音楽
  • Windowsのペイントのァラーパレット
    Color型をuintにして12バイト節約したら2倍以上高速になった話
    2023年7月31日
    ゲーム開発
  • フォントアセット
    読み込んだフォントアセットのマテリアルを安全に差し替える方法
    2023年6月30日
    ゲーム開発
ジャンル
  • テクノロジー全般
    • Web
    • アプリ
    • ゲーム開発
    • ハードウェア
  • インターネット考古学
  • ゲーム
  • 音楽
  • WordPress
  • REVIVE USB
  • その他
アーカイブ
  • 2023 (12)
  • 2022 (24)
  • 2021 (7)
  • 2017 (7)
  • 2016 (5)
目次
  1. Top
  2. テクノロジー全般
  3. ゲーム開発
  4. 読み込んだフォントアセットのマテリアルを安全に差し替える方法
目次