UnityでMIDI機器を使ってオブジェクトと照明の色をコントロールする
VR上でのライブ演出の作業が必要となり、PCキーボードやマウス制御では事故ると思ってMIDI機器を引っぱり出しました。
いろいろ大変だったので、Keijiro Takahashiさんが作成された「MidiJack」をお借りして制御します。
今回はKORGの「nanoPAD1(初代)」を使いました。「nanoPAD2」や10月下旬に販売される新しいnanoPAD2でもいけると思います。
プロジェクトファイルはこちらです。
www.dropbox.com
※照明のところで、MIDIナンバーのコメントが一部違っています。このサイトに掲載しているスクリプトは修正済みです。スクリプトの実行に影響はないですが、すみません
※2 三項演算子と「Input.GetKeyDown」「Input.GetKeyUp」の相性が極めて悪いようです。
これらを使う場合は、素直に公式の下記を使います。
docs.unity3d.com
下記はMIDI機器のドライバがすでにインストール済みとして進めます。
MidiJackを読み込む
1.下記のURLにアクセスする
github.com
2.緑色のボタンの「Clone or Download」をクリックし、「Download ZIP」をクリックしてダウンロードする
3.ダウンロードしたものを解凍する
4.プロジェクトを作成し、「MidiJack-master」フォルダ→「MidiJack.unitypackage」をUnityの「Assets」フォルダにドラッグ&ドロップする
5.「Import」ボタンを押して読み込む
オブジェクトと照明を作成する
1.適当にオブジェクトと照明をを配置して、名前をつける
2.「GameObject」メニュー→「Create Empty」で空のオブジェクトを作成する
3.2.に名前をつける(ここでは「Cubes」にしました)
4.オブジェクトと照明を3.にドラッグ&ドロップして、下記のような構成にする
スクリプトを書く
5.「Assets」を右クリックし、「Create」→「C# Script」を選択する
6.5.に「MIDITestScript」等名前をつける
7.Hierarchyにある「Cubes」に「MIDITestScript」をドラッグ&ドロップする
8.「MIDITestScript」をダブルクリックして、下記のスクリプトを入力する
using System.Collections; using System.Collections.Generic; using UnityEngine; using MidiJack; //https://github.com/keijiro/MidiJack //注意: 下記の「MIDITestScript」がファイル名と同じかチェックする。違う場合はファイル名に書き直す public class MIDITestScript : MonoBehaviour { //「Cubes」をクリックしてInspectorを開き、mycubeオブジェクト3つとSpot Lightオブジェクト3つをドラッグする public GameObject cube1,cube2,cube3,slight1,slight2,slight3; /* 色を配列に登録 UnityではRGB値で指定できないので、RGB値の後に「f/255f」をつける 例: RGB値 165,42,42(茶色) public Color Brown = new Color(165f/255f,42f/255f,42f/255f); */ //キュープ用の色の配列 Color[] collors = new Color[] { Color.white,Color.red, Color.green, new Color(0f/255f,206f/255f,209f/255f)}; //照明用の色の配列、照明を消すには「Color.clear」を使っているのでちょっと無理やり感 Color[] lcollors = new Color[] {Color.clear,Color.green, Color.yellow, new Color(188f/255f,6f/255f,12f/255f)}; void NoteOn(MidiChannel channel, int note, float velocity) { //Debug.Log("NoteOn: " + channel + "," + note + "," + velocity); Color c1 = cube1.GetComponent<Renderer>().material.color; Color c2 = cube2.GetComponent<Renderer>().material.color; Color c3 = cube3.GetComponent<Renderer>().material.color; Color lightC1 = slight1.GetComponent<Light>().color; Color lightC2 = slight2.GetComponent<Light>().color; Color lightC3 = slight3.GetComponent<Light>().color; //下記、三項演算子を使った条件分岐(キューブの色) c1 = (note == 39) ? collors[1] //nanoPadの1 : (note == 36) ? collors[0] //nanoPadの7 : cube1.GetComponent<Renderer>().material.color = c1; //その他のキーが押されたとき、状態を保持 c2 = (note == 48) ? collors[2] //nanoPadの2 : (note == 38) ? collors[0] //nanoPadの8 : cube2.GetComponent<Renderer>().material.color = c2; //その他のキーが押されたとき、状態を保持 c3 = (note == 45) ? collors[3] //nanoPadの3 : (note == 40) ? collors[0] //nanoPadの9 : cube3.GetComponent<Renderer>().material.color = c3; //その他のキーが押されたとき、状態を保持 cube1.GetComponent<Renderer>().material.color = c1; cube2.GetComponent<Renderer>().material.color = c2; cube3.GetComponent<Renderer>().material.color = c3; /* 上記を条件分岐の「if」で書き直すとこちら if(note == 39) { cube1.GetComponent<Renderer>().material.color = collors[1]; } else if(note == 36) { cube1.GetComponent<Renderer>().material.color = collors[0]; } 〜略 */ /* 上記を条件分岐の「switch〜case」で書き直すとこちら switch (note) { case 39: cube1.GetComponent<Renderer>().material.color = collors[1]; break; case 36: cube1.GetComponent<Renderer>().material.color = collors[0]; break; 〜略 default: Debug.Log("エラー"); break; } */ //下記、三項演算子を使った条件分岐(照明の色) lightC1 = (note == 43) ? lcollors[1] //nanoPadの4 : (note == 42) ? lcollors[0] //nanoPadの10 : slight1.GetComponent<Light>().color = lightC1; //その他のキーが押されたとき、状態を保持 lightC2 = (note == 51) ? lcollors[2] //nanoPadの5 : (note == 44) ? lcollors[0] //nanoPadの11 : slight2.GetComponent<Light>().color = lightC2; //その他のキーが押されたとき、状態を保持 lightC3 = (note == 49) ? lcollors[3] //nanoPadの6 : (note == 46) ? lcollors[0] //nanoPadの12 : slight3.GetComponent<Light>().color = lightC3; //その他のキーが押されたとき、状態を保持 slight1.GetComponent<Light>().color = lightC1; slight2.GetComponent<Light>().color = lightC2; slight3.GetComponent<Light>().color = lightC3; /* 長くなるが、こちらのほうが無理がなくていいかも if(note == 43) { slight1.SetActive(true); } else if(note == 42) { slight1.SetActive(flase); } */ } void NoteOff(MidiChannel channel, int note) { Debug.Log("NoteOff: " + note); } void OnEnable() { MidiMaster.noteOnDelegate += NoteOn; MidiMaster.noteOffDelegate += NoteOff; } void OnDisable() { MidiMaster.noteOnDelegate -= NoteOn; MidiMaster.noteOffDelegate -= NoteOff; } // Use this for initialization void Start () { } // Update is called once per frame void Update () { } }
スクリプト解説 三項演算子
nanoPadのボタンに割り振られている番号(MIDIナンバー)がバラバラだったため、やむなく条件分岐で処理を書くことにしました。
switch〜caseやif〜elseなどを使えば楽なのですが、スクリプトが長くなりがちです。
今回はこれら条件分岐を短めに書く「三項演算子」を使いました。
注意点としては、演算子なのでif〜elseの「else」に該当する部分が省略できないことです。
以下、例1-3すべて同じ動きをします。
例1: if文
string s; if (note == 39) { s = "No.1 0n"; } else if (note == 48) { s = "No.1 0ff"; } else { s = "Other 0ff"; }
例2: switch文
string s; switch (note) { case 39: s = "No.1 0n"; break; case 48: s = "No.1 0ff"; break; default: s = "Other 0ff"; break; }
例3: 三項演算子
string s; s = (note == 39) ? "No.1 0n" : (note == 48) ? "No.1 0ff" : "Other 0ff";
swtich文と比較すると分かりやすいかと思われます。
= (演算子の式1) ? 式1の条件が真のときの処理 : (演算子の式2) ? 式2の条件が真のときの処理 : すべてが当てはまらないときの処理;
となっています。
スクリプト解説 照明の色について
ifやswitchを書かないようにしていたので、「Color.clear」で無理やり照明を消しています。これに関してはifかswitchを使って、「slight1.SetActive(trueまたはfalse);」にしたほうがよかったかもしれません。