したかみ ぶろぐ

Unity成分多め

【Unity】Edit Mode 時に設定したテクスチャをマテリアルに反映するコンポーネントを作る

始めに

uGUIを使わないUIを作るため、自作でテクスチャをマテリアルに設定するコンポーネントを作ります。

今回は備忘録的な内容なので結構あっさり目にまとめます。


やりたいこと

一番の希望はuGUIの Image コンポーネントのようなものになります。挙動としては次の通り。

  • 1つのマテリアルを複数のオブジェクトで共有できる
  • マテリアルの削除漏れが発生しない
  • Edit Mode でもInspectorのテクスチャの変更が反映される



マテリアルの注意点

上記のやりたいことに関しての簡単な注意点をまとめます。

マテリアルを共有した場合、変更は別オブジェクトでも適用される

マテリアルを共有している場合、一つのオブジェクトでマテリアルに画像を設定しても別オブジェクトにも反映されます。(同じマテリアルなので当たり前っちゃ当たり前)


また、スクリプトRenderer.sharedMaterialからマテリアルを変更した場合も同様になります。(sharedMaterial は共有しているマテリアルを参照するため


では、同じマテリアルを使って異なるテクスチャを設定する方法で Renderer.material を使う方法があります。 こちらからは設定されたマテリアルをインスタンス化したものを変更します。



Renderer.material は破棄をする

先程出てきた Renderer.material は自動的に破棄されるものではないので、Destory しない場合そのまま残り続けます。

なので、OnDestory などで生成した material を破棄する必要があります。こちらの内容については以下の記事がとても参考になります。

light11.hatenadiary.com



Edit Mode でもInspectorの変更を反映する

基本的にMonoBehaviourはPlay Mode時にしか動きませんが、いくつかの方法で Edit Mode からでも動作させられます。

今回調べて見つけた記事でこちらが網羅的に解説されていて参考になりました。

qiita.com


上記の記事から、今回選択した方法は2つ。

OnValidate() を使う

Unity公式では次のように説明されています。

この関数はスクリプトがロードされた時やインスペクターの値が変更されたときに呼び出されます(この呼出はエディター上のみ)

なので、Inspectorでテクスチャを変更するたびに呼ばれるので、ここで更新処理を書けばマテリアルに変更が適用されそうです。

[ExecuteAlways] を使う

OnValidate() だけですと、Material の生成と破棄が行えません。なので、[ExecuteAlways] を使って Awake()OnDestory() も呼ばれるようにします。



Inspectorの変更を反映するコンポーネントを作る

以上のことを注意して、Inspectorの変更を反映するコンポーネントを作りました。

このコンポーネントを作るに当たって、こちらの記事のコードを参考にしました。 (こちらの記事ではOnValidate() を使わずに Update() を使用、new Material(_renderer.sharedMaterial)でマテリアルを作れることを知った)

qiita.com

主に EditorApplication.IsPlaying を使って Edit Mode なのかを判別をしてマテリアル生成方法を分岐しています。

using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteAlways]
public class TextureSetting : MonoBehaviour
{
    [SerializeField] private Texture2D _texture;
    [SerializeField] private Renderer _renderer;

    private Material _material;
    private static readonly int MainTex = Shader.PropertyToID("_MainTex");

    private void Awake()
    {
#if UNITY_EDITOR
        _material = EditorApplication.isPlaying
            ? _renderer.material
            : new Material(_renderer.sharedMaterial);
#else
            _material = _renderer.material;
#endif

        _material.SetTexture(MainTex, _texture);
        _renderer.material = _material;
    }

#if UNITY_EDITOR
    private void OnValidate()
    {
        if (EditorApplication.isPlaying || _material == null) // Material生成前に呼ばれることがある
        {
            return;
        }

        _material.SetTexture(MainTex, _texture);
        _renderer.material = _material;
    }
#endif

    private void OnDestroy()
    {
        if (_material != null)
        {
#if UNITY_EDITOR
            if (EditorApplication.isPlaying)
            {
                Destroy(_material);
            }
            else
            {
                DestroyImmediate(_material);
            }
#else
            Destroy(_material);
#endif
        }
    }
}


こちらの結果は次のようになります。(コンポーネントの名前が異なるのは気にしないでください) Inspectorを変更したら反映されることが確認できました。



まとめ

このコンポーネントによって、オブジェクトごとにマテリアルを作る必要がなくなりました。

また、変更が Edit Mode でも反映されるので、作ろうとしているオブジェクト群やPrefabの構成をすぐに確認できます。

注意として、まだ完全に動作確認ができたわけではないので、もし不具合などがあればコードを修正する予定です。


使用した画像

今回しようした画像はいらすとやのものになります。

www.irasutoya.com