【アプリ開発】二本指で開閉する洗濯バサミを作る 〜完結編〜【Unity】

前回に引き続き、二本指タッチで動く洗濯バサミ(使い道は謎)を作っていきます!
(読んでいない方はぜひ前回の記事↓からご覧くださいm(_ _)m)

sympathy.hatenablog.com



今回で洗濯バサミは完成となります!
(↓の動画と同じものができ上がります。)
 



【アプリ開発】洗濯バサミ作ってみた!笑(完成品)【Unity】
 


スクリプトで実装したい機能は以下の3つです。
 

  1. 洗濯バサミを二本指でドラッグできるようにする
  2. タッチしている二点の角度に合わせて洗濯バサミを回転させる
  3. ピンチイン・アウトで洗濯バサミを開閉する

 
これらの機能を一つずつ記述していきます。



※以下の内容は前回(part1)でできあがったものを前提としています。

※また、プログラミング初心者であるゆえ不適切なプログラムの書き方などをしている恐れがありますので、参考にする程度で見ていただきたいです。



では行きましょう!
 
 

①洗濯バサミをドラッグ移動させる機能

 
「ドラッグ移動」とは、言い換えるとすなわち、
→「タッチしている位置を取得し、その位置に移動する」
 

ということですね。
しかし今回の洗濯バサミは「二本指」でドラッグしたいので、


→「タッチしている二点のちょうど真ん中の位置を算出し、その位置に移動する」
 

ということにします。

なのでまず、タッチを取得していきます。

// まずUnityEngine.UIをusingしておいてください

// PanelとそれぞれのパーツのImageを宣言しておきます
public GameObject pinPanel;
public Image ringImage;
public Image leftPartImage;
public Image rightPartImage;

// Updateの中に書きます
void Update () 
{
    // 二本以上の指でタッチしているとき
    if (Input.touchCount >= 2)
    {
        // Touchを取得する
        Touch touch1 = Input.GetTouch (0);
        Touch touch2 = Input.GetTouch (1);
 
         /****************************

        ここにtouch1とtouch2を使って機能を記述していきます

        *****************************/
    }
}


Touchは↑このように「Input.GetTouch」で取得できます。
さらにその位置は「.position」(Vector3型)で取得できます。(簡単ですね!)


それを使って記述するとこうなります↓(完成形)。

// 二点の座標の差を求めます
float dx = touch2.position.x - touch1.position.x;
float dy = touch2.position.y - touch1.position.y;

// パネルの位置を移動します
pinPanel.GetComponent<Image> ().transform.position = new Vector3(touch1.position.x + dx / 2, touch1.position.y + dy / 2, 0);


ちょうど真ん中の点なので、二点の座標の値を2分の1してtouch1の座標に加えればいいですね。
パネルごと移動させるので、それに乗っかったパーツが全て一緒に移動します。



これで洗濯バサミをドラッグ移動させる機能は完成しました!




②二本指ドラッグで洗濯バサミを回転させる機能

ここでの考え方は、①とあまり変わりません。
①は「位置」を計算しましたが、②では「角度」を計算します。


二点を結んだ線と水平とがみたす角度を求め、その角度にパネルを回転させればいいわけです。


では、二点のタッチから角度を求めていきます。

そこで、「Mathf.Atan2」という関数を使います。
この関数は、引数に直角三角形の高さ底辺を渡すと角度を(ラジアン単位で)返す関数です。
文系なりに図を描いてみました↓。

f:id:sympacifica:20170628212635p:plain


この関数に、さきほど①で求めた二点の座標の差(dyとdx)を渡せばいいですね!


そうして記述するとこうなります(完成形)↓

// 回転すべき角度を求めます(ラジアン単位をオイラー単位に直すのを忘れずに!)
float angle = (float)(Mathf.Atan2 (dy, dx) * 180 / Math.PI);

// パネルを回転します
pinPanel.GetComponent<Image> ().transform.eulerAngles = new Vector3 (0, 0, angle);


ラジアン角をオイラー角に変換するには、「ラジアン * 180 / π」でできます。
こちらもパネルごと回転させるので、それに乗っかったパーツが全て一緒に回転します。



これで洗濯バサミをドラッグで回転させる機能は完成しました!




③ピンチイン・アウトで洗濯バサミを開閉する機能

ここでの考え方は、①や②とは違います。



「ピンチイン・アウトで洗濯バサミを開閉する」を再現するには、
二点間距離を洗濯バサミのパーツの回転角度に反映すればいい訳です。



洗濯バサミの左パーツの動きを基準に考えると、二点間距離が狭まると(つまむと)、洗濯バサミの左パーツはz軸正の方向に回転し、二点間距離が広くなると(開くと)、洗濯バサミの左パーツはz軸負の方向に回転します。(右パーツはその逆の方向に同じだけ回転するようにすればいいですね。)



では、二点間距離を算出して、それに対して一体どれぐらいの回転角度に対応させればいいのか。



私が何度も数値をいじりながら実験を繰り返した結果、

「左パーツの回転角度 = -0.03 * 二点間距離 + 30」

↑この関係式が満たされれば最も自然な洗濯バサミの開き具合が再現できる、という結論に至りました。
(少なくとも私のテストデバイス上では最適でした。)



ではこれをスクリプトに書いて行きます。

// 二点間距離をピタゴラスの定理で算出します
float width = (float)(Math.Sqrt (Math.Pow (dx, 2) + Math.Pow (dy, 2)));

// z軸の回転角度を計算します
float leftPartAngle = -0.03f * width + 30;

// パーツを回転させます(localEulerAnglesは親のパネルからみた相対的な回転角度です)
leftPartImage.GetComponent<Image> ().transform.localEulerAngles = new Vector3 (0, 0, leftPartAngle);
rightPartImage.GetComponent<Image> ().transform.localEulerAngles = new Vector3 (0, 0, -leftPartAngle);


これで、ピンチイン・アウトで洗濯バサミを開閉する機能は完成しました!





と言う訳で、以上で①〜③の機能は全て記述することができました!

では、全ての機能を記述したスクリプト全体を見てみましょう↓。
(仕上げとして、オマケ機能①〜③が追加されています。)

public GameObject pinPanel;
public Image ringImage;
public Image leftPartImage;
public Image rightPartImage;

void Update () 
{
	// 二本以上の指でタッチしているとき
	if (Input.touchCount >= 2) {
		Touch touch1 = Input.GetTouch (0);
		Touch touch2 = Input.GetTouch (1);
		
		// オマケ機能①:タッチした順序で洗濯バサミの向きが180度変わってしまうのを防ぐ機能
		if (touch1.position.y > touch2.position.y) {
			Touch t = touch1;
			touch1 = touch2;
			touch2 = t;
		}


		// 位置の差を計算しておく
		float dx = touch2.position.x - touch1.position.x;
		float dy = touch2.position.y - touch1.position.y;


		// ①パネルの位置移動
		pinPanel.GetComponent<Image> ().transform.position = new Vector3(touch1.position.x + dx / 2, touch1.position.y + dy / 2, 0);


		// ②パネルの回転
		float angle = (float)(Mathf.Atan2 (dy, dx) * 180 / Math.PI);
		pinPanel.GetComponent<Image> ().transform.eulerAngles = new Vector3 (0, 0, angle);


		// ③洗濯バサミの開閉
		float width = (float)(Math.Sqrt (Math.Pow (dx, 2) + Math.Pow (dy, 2)));
		float leftPartAngle = -0.03f * width + 30f;

		leftPartImage.GetComponent<Image> ().transform.localEulerAngles = new Vector3 (0, 0, leftPartAngle);
		rightPartImage.GetComponent<Image> ().transform.localEulerAngles = new Vector3 (0, 0, -leftPartAngle);


		// オマケ機能②:バネの輪っかの見た目を自然にする機能
		ringImage.GetComponent<Image> ().fillAmount = 1 - leftPartAngle / 180;
		ringImage.GetComponent<Image> ().transform.localEulerAngles = new Vector3 (0, 0, -leftPartAngle);
	}
	// オマケ機能③:指を離しているとき、洗濯バサミを閉じる機能
	else {
		leftPartImage.GetComponent<Image> ().transform.localEulerAngles = new Vector3 (0, 0, 0);
		rightPartImage.GetComponent<Image> ().transform.localEulerAngles = new Vector3 (0, 0, 0);
		ringImage.GetComponent<Image> ().fillAmount = 1;
	}
}


以下、簡単にオマケ機能の説明をします。




オマケ機能①:タッチした順序で洗濯バサミの向きが180度変わってしまうのを防ぐ機能

GetTouch(0)とGetTouch(1)の順序はタッチした順序で決まるので、タッチした位置が同じでもタッチの順序が変わるとtouch1とtouch2が逆になってしまい、見た目が180度回転した形になってしまいます。


なのでここでは、右手でのみ操作することを想定し、洗濯バサミが常に左を向くようにしました。

// touch1のposition.yが、touch2のposition.yより大きくなってしまっているとき
if (touch1.position.y > touch2.position.y) {
	Touch t = touch1;
	touch1 = touch2;
	touch2 = t;
}


常にposition.yの値が小さい方がtouch1、大きい方がtouch2となるようにしています。




オマケ機能②:バネの輪っかの見た目を自然にする機能

下準備として、Unityエディタの方でバネの輪っかの画像「ringImage」のImage Typeを「Fill」に設定しておく必要があります。「Fill」を使うと、画像を扇形のように描画することができます。(詳しくはUnityのレファレンスなどを見てください)


今回は、洗濯バサミのはさむ部分だけを描画「させない」ようにしたいので、「fillAmount」を求めると、


fillAmount = (360 - 描画しない角度) / 360(角度(0~360)をfillAmount(0~1)の値に直したいので360で割ります)
→fillAmount = (360 - leftPartAngle * 2) / 360
→fillAmount = 1 - leftPartAngle / 180

という計算式になります。


これだけでは洗濯バサミのはさむ部分と、バネの輪っかの描画されていない部分との位置が合わないので、合わせるようにバネの輪っかを回転させます。
(時計回りにfillされるので、右パーツと同じ回転角度にすればいいですね。)

ringImage.GetComponent<Image> ().transform.localEulerAngles = new Vector3 (0, 0, -leftPartAngle);


どうしても説明がわかりにくくなってすみません。(汗)




オマケ機能③:指を離しているとき、洗濯バサミを閉じる機能

これは簡単で、Input.touchCount(タッチの数)が1以下のときはパーツを閉じれば良いので、

// タッチの数が2以上のとき
if (Input.touchCount >= 2)
{
	// ほげほげ
}
// タッチの数が1以下のとき
else {
	leftPartImage.GetComponent<Image> ().transform.localEulerAngles = new Vector3 (0, 0, 0);
	rightPartImage.GetComponent<Image> ().transform.localEulerAngles = new Vector3 (0, 0, 0);
	ringImage.GetComponent<Image> ().fillAmount = 1;
}


となります。





ということで、オマケを含め全ての機能のスクリプトの記述を説明し終えました。
(言葉足らずな説明になってしまい、申し訳ありませんでしたm(_ _)m)



今回で目的の洗濯バサミを完成できた訳ですが、、、

次回からはこの洗濯バサミを使ったゲームを作っていきたいと思います!


まだどのようなゲームにするかははっきりしていませんが、完成したらGooglePlayやAppStoreに公開するところまでできればと思っています。


なので次回からは「文系大学生がアプリを作って公開するまで」というタイトルで開発の進捗状況をブログに書いていきます!笑


応援していただける方は、ぜひ「いいな★」またはSNSなどでのシェアをお願いします!


また、この記事に関してご質問やご指摘があればぜひコメントをお願いします。


最後まで読んでくださり、本当にありがとうございました。