Unity+MIDIで音ゲーを作るならコレ！(4)

[Unity+MIDIで音ゲーを作るならコレ！(3)](https://qiita.com/ELIXIR/items/336cd37e99b90929af40)の続きです。

前回のコードを汎用化しました。

```MidiDelayCall.cs
using MidiPlayerTK;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

namespace Elix
{
    public class MidiDelayCall : MonoBehaviour
    {
        [System.Serializable]
        public class MidiMinMaxFilter
        {
            public int min = 0;
            public int max = 0;
        }
        [System.Serializable]
        public class MidiEvFi
        {
            public MidiMinMaxFilter[] trackFilter;
            public MidiMinMaxFilter[] channelFilter;
            public MidiMinMaxFilter[] velocityFilter;
            public MidiMinMaxFilter[] noteFilter;

            private bool contains(int value, MidiMinMaxFilter[] filters)
            {
                if ((filters == null) || (filters.Length == 0))
                    return true;
                foreach (MidiMinMaxFilter filter in filters)
                {
                    if (value >= filter.min && value <= filter.max)
                        return true;
                }
                return false;
            }
            public bool Contains(MPTKEvent ev)
            {
                return Contains((int)ev.Track, ev.Channel, ev.Velocity, ev.Value);
            }
            public bool Contains(int track, int channel, int velocity, int note)
            {
                if (!contains(track, trackFilter)) return false;
                if (!contains(channel, channelFilter)) return false;
                if (!contains(velocity, velocityFilter)) return false;
                if (!contains(note, noteFilter)) return false;
                return true;
            }
        }
        [System.Serializable]
        public class MidiEvFiArr
        {
            public MidiEvFi[] filters;
            public bool Contains(MPTKEvent ev)
            {
                return Contains((int)ev.Track, ev.Channel, ev.Velocity, ev.Value);
            }
            public bool Contains(int track, int channel, int velocity, int note)
            {
                if ((filters == null) || (filters.Length == 0))
                    return true;
                foreach (MidiEvFi evfi in filters)
                {
                    if (evfi.Contains(track, channel, velocity, note))
                        return true;
                }
                return false;
            }
        }

        [System.Serializable]
        public class MidiEventFilter
        {
            public MidiEvFiArr prebuild;
            public MidiEvFiArr midi;
        }

        public enum FilterType
        {
            Prebuild,
            Midi,
        }

        [SerializeField] private MidiFilePlayer m_midiFilePlayer;
        [SerializeField] private float m_delaySec = 2.0f;
        [SerializeField] private UnityEvent<MPTKEvent> m_prebuildEvent;
        [SerializeField] private UnityEvent<MPTKEvent> m_midiPlayEvent;
        [SerializeField] private MidiEventFilter m_eventFilter;

        float m_currentTime = 0.0f;
        bool m_loaded = false;
        int m_eventIdx = 0;

        private void Awake()
        {
            m_midiFilePlayer.MPTK_PlayOnStart = false; // Start時に再生しない
            m_midiFilePlayer.MPTK_LogEvents = false; // イベントログを出力しない


        }
        // Start is called once before the first execution of Update after the MonoBehaviour is created
        void Start()
        {
            m_midiFilePlayer.MPTK_Play(); // Load and Start
        }

        // 音が出る前にあらかじめイベントを調べる部分
        // 前回呼ばれてから今回までの間に起こったイベントを処理する
        void Update()
        {
            if (!m_loaded)
                return;

            m_currentTime += Time.deltaTime;
            List<MPTKEvent> events = m_midiFilePlayer.MPTK_MidiEvents;
            for (int i = m_eventIdx; i < events.Count; i++)
            {
                MPTKEvent ev = events[i];
                float evTimeSec = ev.RealTime * 0.001f;
                if (m_currentTime < evTimeSec)
                {
                    m_eventIdx = i;
                    break;
                }

                // フィルターに含まれるイベントのみ処理する
                if (m_eventFilter.prebuild.Contains(ev))
                {
                    m_prebuildEvent.Invoke(ev);
                }
            }

        }

        // Loadが終わり再生が始まる前に呼ばれる
        public void OnMIDIStart(string str)
        {
            m_loaded = true;
            m_currentTime = 0f;
            m_eventIdx = 0;
            m_midiFilePlayer.MPTK_Pause(); // 一旦停止
            StartCoroutine(waitStartCo(m_delaySec));
            Debug.Log("MIDI Start:" + Time.time);
        }

        // 再生時に定期的に呼ばれる処理
        // 再生中、前回呼ばれてから今回までの間に起こったイベントを処理する
        public void OnMIDITick(List<MPTKEvent> midievents)
        {
            foreach (MPTKEvent ev in midievents)
            {
                // フィルターに含まれるイベントのみ処理する
                if (m_eventFilter.midi.Contains(ev))
                {
                    m_midiPlayEvent.Invoke(ev);
                }
            }
        }

        // 再生終了時に呼ばれる処理
        public void OnMIDIEnd(string str, EventEndMidiEnum endEnum)
        {
            Debug.Log("MIDI End");
        }

        /// <summary>
        /// フィルターに含まれるかどうかを返す
        /// </summary>
        /// <param name="ev">イベント</param>
        /// <param name="type">フィルタータイプ</param>
        /// <param name="idx">フィルター要素</param>
        /// <returns></returns>
        public bool FilterContains(MPTKEvent ev, FilterType type, int idx)
        {
            MidiEvFiArr arr = (type == FilterType.Midi) ? m_eventFilter.midi : m_eventFilter.prebuild;
            if (arr.filters.Length > idx)
            {
                return arr.filters[idx].Contains(ev);
            }
            return false;
        }

        // MIDIの再生を遅延させる
        IEnumerator waitStartCo(float _delaySec)
        {
            yield return new WaitForSeconds(_delaySec);
            m_midiFilePlayer.MPTK_Play();
        }
    }
}
```
イベントから呼ばれる関数を分離しました。
```PrebuiddEvent```および```MidiPlayEvent```にイベントから呼ばれる関数をバインドしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/ccea0101-3cd5-99b4-03db-47f224da4a06.png)

分離した部分は以下のようになります。
```MidiEventParts.cs
using MidiPlayerTK;
using UnityEngine;
using Elix;

public class MidiEventParts : MonoBehaviour
{
    [SerializeField] GameObject m_effectPrefab;
    [SerializeField] MidiDelayCall m_mdc;
    [SerializeField] bool m_logMessage = false;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
    }

    public void OnCreateObj(MPTKEvent ev)
    {
        if (ev.Command == MPTKCommand.NoteOn)
        {
            PrimitiveType type = ((ev.Value & 1) == 0) ? PrimitiveType.Sphere : PrimitiveType.Cube;
            GameObject go = GameObject.CreatePrimitive(type);
            go.transform.position = transform.position;
            go.transform.rotation = transform.rotation;
            go.transform.localScale = Vector3.one * ev.Velocity * 0.001f;

            Rigidbody rb = go.AddComponent<Rigidbody>();
            rb.useGravity = false;
            rb.AddForce(go.transform.forward * 4.0f, ForceMode.Impulse);
            Destroy(go, 10.0f); // 10秒後に自動消滅

            if (m_logMessage)
            {
                string msg = $"tr:{ev.Track} ch:{ev.Channel} note:{ev.Value} vol:{ev.Velocity}";
                Debug.LogWarning(msg);
            }
        }
    }

    public void OnCreateEfc(MPTKEvent ev)
    {
        if (ev.Command == MPTKCommand.NoteOn)
        {
            if (m_effectPrefab != null)
            {
                GameObject efcGo = Instantiate(m_effectPrefab);
                efcGo.transform.position = transform.position + Random.onUnitSphere * 3f;
                efcGo.transform.localScale = Vector3.one * ev.Velocity * 0.01f;
                if (m_mdc.FilterContains(ev,MidiDelayCall.FilterType.Midi,1))
                { // フィルターMidi[1]にマッチした場合
                    efcGo.transform.localScale *= 0.1f;
                }
                Destroy(efcGo, 2.0f); // 2秒後に自動消滅
            }
            else
            {
                Debug.LogWarning("Effect Prefab is not set");
            }
            if(m_logMessage)
            {
                string msg = $"tr:{ev.Track} ch:{ev.Channel} note:{ev.Value} vol:{ev.Velocity}";
                Debug.Log(msg);
            }
        }
    }
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/a7f7d145-8169-a50e-1bf8-7cd80261308f.png)

<br>

これで音ゲーの基礎部分ができました。
いかがでしたでしょうか？お役に立てましたら幸いです。
<hr>

[Unity+MIDIで音ゲーを作るならコレ！(1)](https://qiita.com/ELIXIR/items/e822a6fd9663d1750a81)