0
0

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 1 year has passed since last update.

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

0
Last updated at Posted at 2024-09-08

Unity+MIDIで音ゲーを作るならコレ!(3)の続きです。

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

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

分離した部分は以下のようになります。

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


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


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

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?