まゆたまガジェット開発逆引き辞典

電子工作やプログラミングのHowtoを逆引き形式で掲載しています。作りたいモノを決めて学んでいくスタイル。プログラマではないので、コードの汚さはお許しを。参照していないものに関しては、コピペ改変まったく問いません

UnityでMIDI機器を使ってオブジェクトと照明の色をコントロールする

VR上でのライブ演出の作業が必要となり、PCキーボードやマウス制御では事故ると思ってMIDI機器を引っぱり出しました。
いろいろ大変だったので、Keijiro Takahashiさんが作成された「MidiJack」をお借りして制御します。
今回はKORGの「nanoPAD1(初代)」を使いました。「nanoPAD2」や10月下旬に販売される新しいnanoPAD2でもいけると思います。

f:id:prince9:20181015231629g:plain

プロジェクトファイルはこちらです。
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.適当にオブジェクトと照明をを配置して、名前をつける
f:id:prince9:20181015211143p:plain

2.「GameObject」メニュー→「Create Empty」で空のオブジェクトを作成する
3.2.に名前をつける(ここでは「Cubes」にしました)
4.オブジェクトと照明を3.にドラッグ&ドロップして、下記のような構成にする
f:id:prince9:20181015211554p:plain

スクリプトを書く

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);」にしたほうがよかったかもしれません。