ゴマちゃんフロンティア

アザラシが大好きなエンジニアの開発日記です

【Unity】パラメータ変更するマテリアルをアセットから実行中のインスタンスに変えたお話

time 2020/08/30

今回はUnityでマテリアルなお話です。
マテリアルのパラメータを変えるとき、アセットを参照しているマテリアルを弄った場合、加えた変更が再生後も保持されてしまいます。
当然と言えば当然の挙動なのですが、私のゲームの場合はテクスチャを通常時と被ダメージ時で切り替えているので、ダメージ中に再生を止めたりすると面倒です。

オブジェクトに設定されたマテリアルが1つだけであればRenderer.materialを直接弄れば済む話ですが、複数のマテリアルを設定している場合はそうもいきません。
ということで、実行時にRenderer.materialsから必要なマテリアルの参照を取得し、それを変える方式にしてみました。

以下は顔のテクスチャを切り替えるスクリプトです。キャラクターの顔のオブジェクトにアタッチして使用します。

using System.Linq;
using UnityEngine;

public class FaceTextureController : MonoBehaviour
{
    /// <summary>
    /// 変更対象のマテリアル
    /// </summary>
    [SerializeField]
    private Material targetMaterial;

    /// <summary>
    /// ランタイム中に切り替える用のマテリアル
    /// </summary>
    private Material runtimeMaterial;

    /// <summary>
    /// 通常時のテクスチャ
    /// </summary>
    [SerializeField]
    private Texture normalTexutre;

    /// <summary>
    /// ダメージ時のテクスチャ
    /// </summary>
    [SerializeField]
    private Texture damagedTexture;

    private void Awake()
    {
        var renderer = GetComponent<Renderer>();
        var originMaterials = renderer.materials;

        // 顔のマテリアルの参照を取得
        runtimeMaterial = originMaterials.FirstOrDefault(x => x.name.StartsWith(targetMaterial.name));
    }

    private void Start()
    {
        // 通常状態にしておく
        SwapTexture(FaceTextureType.Normal);
    }

    /// <summary>
    /// テクスチャを切り替える
    /// </summary>
    /// <param name="faceTextureType"></param>
    public void SwapTexture(FaceTextureType faceTextureType)
    {
        Texture targetTexture = null;

        switch (faceTextureType) {
            case FaceTextureType.Normal:
                targetTexture = normalTexutre;
                break;
            case FaceTextureType.Damaged:
                targetTexture = damagedTexture;
                break;                
        }

        if (targetTexture == null) {
            Debug.LogWarning($"未実装か想定されていないテクスチャ種別です。({faceTextureType})");
            return;
        }

        runtimeMaterial.SetTexture("_MainTex", targetTexture);
    }
}

FaceTextureTypeはこちら。単なる列挙型ですね。

public enum FaceTextureType
{
    Normal,
    Damaged
}

Awake()で自身のRendererの参照を取得し、マテリアルの配列を取得します。
インデックスを直接指定して取得すると楽ですが、モデル側のマテリアルブロックに影響されるので避けたいところです。なのでFirstOrDefault()で入れ替える対象となるマテリアルを探します。

var originTargetMaterial = originMaterials.FirstOrDefault(x => x.name.StartsWith(targetMaterial.name));

探すための目安としてMaterial型のフィールドを用意しておき、インスペクターから対象のマテリアルを設定しておきます。

`targetMaterial`はRendererで使っているのと同じマテリアルを設定しているから、オブジェクト同士の比較じゃだめなの?

マテリアルは「実行時にインスタンス化される」ため、アセットから設定したマテリアルとは参照が異なります。

それぞれのマテリアルを`GetHashCode()`して見比べてみると分かりやすいよ!

また、インスタンス化されたマテリアルのnameは後ろに「 (Instance)」が付くため、名称での完全一致もNGです。
ということでStartsWith()を使い前方一致で探しました。

必要なマテリアルの参照を取れれば、あとはテクスチャを切り替えるだけです。
SwapTexture()というメソッドを設け、引数の列挙型に応じてSetTexture()のテクスチャを切り替えます。

使い方はこのコンポーネントの参照を取ってSwapTexture()を呼び出すだけなので、割愛させていただきます。
おそらく親オブジェクトにPlayerなどのコンポーネントの付けると思うので、そこからGetComponentInChildren()してあげるのが一番楽かと。

被ダメージ中に再生を止めたりして動作を確認してみます。アセット側のマテリアルのテクスチャが変わっていなければOKです。

down

コメントする