おもちゃラボ

Unityで遊びを作ってます

【Arduino】マウスホイール(ロータリーエンコーダ)の回転量を取得する

マウスホイール(ロータリーエンコーダ)がどれだけ回転したかをArduinoで調べる方法を紹介します。

f:id:nn_hokuson:20170326102120j:plain

まずはマウスを分解して、ホイール部分だけ取り出しました。

f:id:nn_hokuson:20170326082121p:plain:w350

取り出したホイールとロータリーエンコーダはこんな感じです。ロータリーエンコーダの足にはワイヤをつけています。

f:id:nn_hokuson:20170326083121j:plain:w350

ロータリーエンコーダの仕組み

ロータリーエンコーダには3本の足があり、1本はGNDで、残り2本からパルス(A相とB相)が出力されます。出力される2相の信号は位相が90度がずれるように作られています。
2相の信号の出力を見ることで、正回転と逆回転を判別することができます。

正回転の場合は下図のように、A相がB相に対して位相が90度進みます。
そこで前回と今回のA相B相の値が(1110、0100、0010、1011)になっている場合は正回転しているとみなすことができます。

f:id:nn_hokuson:20170326091440p:plain:w300

一方、逆回転の場合はA相がB相に対して90度遅れるので下図のようになります。そこで、前回と今回のA相B相の値が(1110、1000、0001、0111)になっている場合には逆回転とみなします。

f:id:nn_hokuson:20170326091446p:plain:w300

マウスホイールとArduinoをつなぐ回路図

マウスホイールのロータリーエンコーダとArduinoをつなぐ回路図は次のようになります。

f:id:nn_hokuson:20170326093205j:plain:w300

ここではロータリーエンコーダのA相とB相をArduinoの2番ポートと3番ポートに接続しています。ロータリーエンコーダから出ているもう一本はGNDに接続します。

Aruduinoのプログラム

ロータリーエンコーダの値を検出するArduinoのプログラムは次のとおりです。

volatile int value = 0;
volatile uint8_t prev = 0;

void setup() 
{
  pinMode(2, INPUT); 
  pinMode(3, INPUT);
  
  attachInterrupt(0, updateEncoder, CHANGE);
  attachInterrupt(1, updateEncoder, CHANGE);
  digitalWrite(2, HIGH);
  digitalWrite(3, HIGH);
  
  Serial.begin(9600);
}

void updateEncoder()
{
  uint8_t a = digitalRead(2);
  uint8_t b = digitalRead(3);
 
  uint8_t ab = (a << 1) | b;
  uint8_t encoded  = (prev << 2) | ab;

  if(encoded == 0b1101 || encoded == 0b0100 || encoded == 0b0010 || encoded == 0b1011){
    value ++;
  } else if(encoded == 0b1110 || encoded == 0b0111 || encoded == 0b0001 || encoded == 0b1000) {
    value --;
  }

  prev = ab;
}

void loop()
{
   Serial.println(value);
}

今回は波形が変化したタイミングでロータリーエンコーダからの値を読み出したいので、割り込みの仕組みを使っています。割り込みを使うことで、ピンに入力されている値が変化したタイミングで、指定した関数を実行することができます。

Arduinoで割り込みを使うにはattachInterrupt関数を使います。第一引数には使用するポートのインデックス(2番ポートならインデックスは0、3番ポートならインデックスは1になります・・・ややこしい!)を指定します。

第二引数に呼び出す割り込み用の関数名、第三引数には値がどのように変化(LOW, RISING, FALLING, CHANGE)したときに割り込みの関数を呼び出すかを指定します。

  • LOW ピンがLOWのとき発生
  • CHANGE ピンの状態が変化したときに発生
  • RISING ピンの状態がLOWからHIGHに変わったときに発生
  • FALLING ピンの状態がHIGHからLOWに変わったときに発生

f:id:nn_hokuson:20170326094506p:plain:w200

ここでは2番ポートと3番ポートを割り込みに使用して、波形が変化(CHANGE)した場合にupdateEncoder関数を呼びだしています。

updateEncoder関数の中では2番ポートと3番ポートから、ロータリーエンコーダのA相とB相の値を読み出し、前回のA相B相の値と繋げて4bitの値にして、正回転か逆回転かを判定しています。

実行結果

マウスのホイールを回した結果がこちらです。分かりやすいように、エンコードした値が偶数のときにはLEDを点灯、奇数の場合には消灯しています。

www.youtube.com

今回はこちらの記事を参考にさせていただきました!
マウスホイールでトイレットペーパの使用量を調べるって・・・・斬新w
面白いなぁ〜^^/
eleclog.quitsq.com

おまけ

使うマイコンによっては割り込みを使えない場合もあります。その場合は次のようにloop関数の中で割り込みをエミュレートしましょう。

void loop()
{
    int a = (PIND & _BV(2))>>2;
    int b = (PIND & _BV(3))>>3;

    if( a != prevA || b != prevB ){
      updateEncoder(a, b);
    }

    prevA = a;
    prevB = b;
}

ポート2とポート3の入力を調べるときにdigitalRead関数を使うかわりに、PIND & _BV(N)を使って高速化しています。

また、これで得られる値は0x00010や0x00100なので、1/0に変換するためにビットシフトしています。詳しくは次のサイトが参考になりました。

ehbtj.com

【Unity】ビルボードで常にカメラの方に向く木を作る

ビルボードを使って、常にカメラの方を向く木のオブジェクトの作り方を紹介したいと思います。

最近ではビルボードを使う機会も減り、パーティクル以外ではあまり使われなくなりました。ただ、スマートフォン用など、ROM容量が厳しい環境では、依然として使われているようです。

ビルボードの歴史

そもそもどうしてビルボードというものがあるのか、というところから説明したいと思います。今でこそ記憶媒体は豊富になりましたが、ひと昔前までは今では想像できないぐらい記憶媒体の容量が少なかったのです。したがって、ゲームの容量(サイズ)を出来る限り減らすことを常に意識する必要がありました。

ドラクエのROMサイズが64KBだったというのは有名な話ですね。一体どうやって作ったのか、想像を絶するものがあります。

マリオの雲と草も同じテクスチャを使いまわしていたり、マリオとルイージは色違いだったりと、色々と容量を減らすための涙の努力が見え隠れします。

matome.naver.jp

そんな中で、リッチでハイポリな木の3Dモデルなんか使えるはずもありません。かといって、そのまま木を平面に貼り付けただけでは、見る方向によってものすごく不自然な見た目になってしまいます。

f:id:nn_hokuson:20170324203420p:plain

そのために考えられた技術がビルボードです。ビルボードの実体は平面に貼ったテクスチャですが、常にカメラの方向を向く特性を持っています。そのため、あたかも2Dオブジェクトを3Dオブジェクトのように見せることができるのです。

ビルボードの作り方

ではどのようにしてこのビルボードを作るのかを紹介します。Unityを使わずにビルボードを作る場合はカメラの位置とオブジェクトの位置から変換行列を自力で計算しなくてはいけません。

UnityではLookAtという超便利なメソッドが用意されているため、非常に簡単にビルボードを作ることができます。次のプログラムをビルボードにしたいオブジェクトにアタッチするだけです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BillBoard : MonoBehaviour {

	void Update () {
		Vector3 p = Camera.main.transform.position;
		p.y = transform.position.y;
		transform.LookAt (p);
	}
}

ただし、3Dモデル(Planeなど)にテクスチャを貼り付ける場合、平面を90度回転してカメラに向けた状態で使うことが多いと思います。
初期状態で回転した状態だとビルボードが正しく動かないため、回転していない空のオブジェクトを用意し、モデルをその子要素にして下さい。

f:id:nn_hokuson:20170324204623p:plain:w200

この場合、ビルボードのスクリプトは親のオブジェクトにアタッチします。

f:id:nn_hokuson:20170324211147p:plain

実行結果

ビルボードで作った木の間を車が走行するデモがこちらです。見た目に全然不自然ではありません。言われなければビルボードを使っていることは分からないのではないでしょうか。

f:id:nn_hokuson:20170324205001g:plain

ですが実際は、カメラの移動に合わせて結構壮絶な木の大回転が行われています(笑)バックヤードの動画がこちらです。
なんと、木がいっせいに動いている・・・ジブリの世界?ですね。

f:id:nn_hokuson:20170324205817g:plain

おまけ

ちなみに、スーパーマリオ64のボム兵は球体の部分(体)の部分だけ、ビルボードで作られているようです。昔は容量がなかった分、面白技術が色々と生まれたのですね。

【Unity】Animation Curveを使ってインスペクタからレベルデザインをする

Animation Curveとはベジェ曲線を使って物体の動きや挙動などをGUIで設定するための仕組みです。
Unityではスクリプトで定義したAnimation Curveをインスペクタに表示することができます。そこで、このアニメーションカーブを使ってレベルデザインをしちゃおう!というのがこの記事の内容です。

インスペクタからAnimation Curveを設定する

ここでは、簡単なシューティングゲームを考えてみます。敵が弾を打つ間隔をアニメーションカーブで制御してみましょう。

f:id:nn_hokuson:20170323200918p:plain:w400

早速ですが、敵クラスのプログラムは次のようになります。

public class EnemyController : MonoBehaviour 
{
    public GameObject bulletPrefab;
    public AnimationCurve waitTime;

    IEnumerator Generate()
    {
        while (true)
        {
            Instantiate (bulletPrefab);
            float wait = this.waitTime.Evaluate (Time.time);
            yield return new WaitForSeconds (wait);
        }
    }
            
    void Start () {
        StartCoroutine (Generate ());
    }
}

スクリプト中にpublic変数としてAnimation Curveを宣言しています。
Animation Curveクラスはグラフのようなもので、縦軸と横軸の定義は自由に決めてOKです。今回は縦軸に弾を打つ間隔、横軸にゲーム時間を表すグラフを作成します。

Animation Curve変数はpublic変数として宣言しているので、インスペクタを見るとアニメーションカーブの欄が表示されています。
この欄をダブルクリックしてAnimation Windowを開き、次のような曲線を作成しました。

f:id:nn_hokuson:20170323200251p:plain:w400

最初は弾を打つ間隔を大きく取り、時間が立つに連れて弾を打つ間隔を短くしています。最後はご褒美で少しだけ簡単にしています。今回は分かりやすいように、横軸は5秒で設定・・・(笑)

レベルデザインの方法については「Unity5の教科書」にも書いたので合わせて参考にしてください!

また、設定した時間外のグラフをどのように扱うのかは、カーブ終端の歯車ボタンで設定できます。
設定できる種類は次の3種類です。

  • Loop
  • PingPong
  • Clamp

[Loop]アニメーションカーブを1から繰り返します。
f:id:nn_hokuson:20170323200323p:plain:w300
[PingPong] グラフ終端で線対称になるようにグラフが生成されます
f:id:nn_hokuson:20170323200330p:plain:w300
[Clamp] グラフ終端の値が使われます。
f:id:nn_hokuson:20170323200336p:plain:w300

Animation Curveから値を取り出す

スクリプトに戻って、現在時刻から弾の時間間隔を取り出す部分を見ていきましょう。アニメーションカーブのX軸の値からY軸の値を得るためにはEvaluateメソッドを使います。

Evaluateメソッドの引数にX軸の値を渡すことで、Y軸の値が取得できます。ここではX軸の値としてゲーム開始からの時間を渡して、返り値として次の弾を発射するまでの待ち時間を取得しています。

f:id:nn_hokuson:20170323200407p:plain:w400

取り出した待ち時間ぶん、waitForSecondsメソッドを使って弾の生成を停止しています。

これでアニメーションカーブを使ったレベル設定ができました。
実行結果は次のようになりました。レベルデザインどおりに時間とともに弾を打つ間隔が変わっていますね〜^^

f:id:nn_hokuson:20170323200414g:plain