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にイベントから呼ばれる関数をバインドしてください。

分離した部分は以下のようになります。
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);
}
}
}
}
これで音ゲーの基礎部分ができました。
いかがでしたでしょうか?お役に立てましたら幸いです。
