0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Timelineでshaderの値を変更した際にMaterialPropertyBlockで値が制御できなくなるのを解消したい

Last updated at Posted at 2022-01-05

初めに

Timelineの処理shaderの値を変更する
「MaterialPropertyBlock is used to modify these values」
と言うメッセージが出る。
image.png

これ自体は、下記のブログで説明してあるように

Materialのオブジェクト作らず簡単にスクリプトで変更できるって言うとても便利なものです。

下記で画像で見てもらいたい箇所を赤く囲ってあります。
image.png

しかし今回は、スクリプトでshaderの値を変更してるのですが、Timelineの処理とMaterialPropertyBlock の処理が挟まると
意図したことが出来ないのでいろいろ調べました。
Animation00.gif

Timeline単体の機能としては優秀なのだが、今回は邪魔になった

Animation01.gif

初めにの所で書いたMaterialPropertyBlockで値が変更が出来なくなり、スクリプトで値が変更できなくなります。。
色々調べて、悩んだ末に。。

限定的に変更可能にした!

今回は、Timelineで値を変更したものをいつでも確認できその値をTimelineでキーを打ちたかったので
Timelineがレコーディングモードにした際にMaterialPropertyBlockで値が変更できなくなるのを解除したかったので
Animation02.gif

スクリプト

スクリプトはMaterialTsetのスクリプトのエディタ拡張で書きました。

MaterialTsetは何も処理をしてなく、TimelinenのPlayableDirectorとスクリプトで変更したかったMaterialを指定するだけ

using UnityEngine;
using UnityEngine.Playables;

public class MaterialTset : MonoBehaviour
{
    [SerializeField]
    PlayableDirector _playableDirector;

    [SerializeField]
    private Material _mat;
}

エディタ拡張の部分はこんな感じ

using UnityEditor;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
using UnityEditor.Timeline;

[CustomEditor(typeof(MaterialTset), true)]
public class MaterialTsetEditor : Editor
{
    private Vector2 v;
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        serializedObject.Update();

        var mat = serializedObject.FindProperty("_mat").objectReferenceValue as Material;


        v = mat.mainTextureOffset;


        if (GUILayout.Button("+"))
        {
            v.x += 0.1f;
        }
        if (GUILayout.Button("-"))
        {
            v.x -= 0.1f;
        }

        mat.mainTextureOffset = v;

        TimelineRecordingCheckFunc(serializedObject);

        serializedObject.ApplyModifiedProperties();

    }
    public  void TimelineRecordingCheckFunc(SerializedObject serobj)
    {
        var playableDirector = serobj.FindProperty("_playableDirector").objectReferenceValue as PlayableDirector;
        if (playableDirector == null)
        {
            return;
        }
        var timelineAsset = playableDirector.playableAsset as TimelineAsset;
        foreach (var timelineClip in timelineAsset.GetRootTracks())
        {
            // タイムラインのそれぞれのタイプを取得
            var type = timelineClip.GetType();
            // アニメーショントラックか判定
            if (typeof(UnityEngine.Timeline.AnimationTrack) == type)
            {
                // TrackAssetをAnimationTrackで取得
                var animationTrack = timelineClip as AnimationTrack;
                // playableDirectorから各トラックを取得する
                var binding = playableDirector.GetGenericBinding(animationTrack) as Animator;
                // ちゃんとオブジェクトがセットされているか
                //  レコーディング状態をチェック
                if (binding && animationTrack.IsRecording())
                {
                    // トラックにセットされいる、オブジェクトの子供に
                    //  MaterialPropertyBlock がセットされいるかチェック
                    var hasPropertyBlock = false;
                    var renderers = binding.GetComponentsInChildren<MeshRenderer>();
                    foreach (var renderer in renderers)
                    {
                        if (renderer)
                        {
                            if (renderer.HasPropertyBlock())
                            {
                                hasPropertyBlock = true;
                            }
                        }
                    }

                    // チェックの親オブジェクトをアクティブを一瞬とく
                    if (hasPropertyBlock)
                    {
                        // ここが重要
                        binding.gameObject.SetActive(false);
                        binding.gameObject.SetActive(true);
                    }
                }
            }
        }
    }
}

大事なところは、
レコーディング中かみてる所

                var binding = playableDirector.GetGenericBinding(animationTrack) as Animator;
                // ちゃんとオブジェクトがセットされているか
                //  レコーディング状態をチェック
                if (binding && animationTrack.IsRecording())

今回のしたかった事を実際に実現して箇所をとチェックに使った関数

MeshRendererに登録されているMaterialにMaterialPropertyBlockで値が変更されているかチェックするのに

Renderer.HasPropertyBlockを使います

チェックして使用されていたら親のオブジェクトをSetActiveでOn Offするだけ

                    // チェックの親オブジェクトをアクティブを一瞬とく
                    if (hasPropertyBlock)
                    {
                        // ここが重要
                        binding.gameObject.SetActive(false);
                        binding.gameObject.SetActive(true);
                    }

実現はできたがこれでいいのか。。

たぶん、親のオブジェクトがTimelineでAnimationTrackで登録された際にAnimatorが自動で登録されるのだが、
このAnimatorがMaterialPropertyBlockを設定して言うのかとおもわれそれを
SetActiveでOn Offにすると、MaterialPropertyBlockが外れるのだと思います。

image.png

注意

今現在Unityのバージョンの2020 LTSのバージョンは2020.3.25f1ではTimelineのバージョンは1.4.8
image.png

それですと下記のエラーが出てしまいます。
image.png

これは、レコーディング状態を確認するIsRecording関数1.5.0以降に追加された機能だからです。
なので、Timelineを1.5.0以降にしてください。

最後に

同じように悩むことなどしないと思いますが
もし、同じように悩んでたどり着いた方で、もっといい方法あったらおしえてください。。
いつもならUnity のフォーラムあたりで書いてあったりし自信がもてるのだけど。。
今回ばかりは、少しづつ調べて実現したので自信がない。。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?