tetu式

ゲームと音楽・作曲の自己満足と悩みどころの多いプログラムのブログ。

Unity2D フリック操作:8方向編 角度の計算と判定

今回はちょっと数学的なお話。

以前フリック操作の話をした時は上下左右のみの4方向を判定するフリック操作でした。

何となくこのフリック操作のファイルは他のプロジェクトでも使いそうな気がするので機能拡張、8方向にも対応できるようにしてみます。


前回のおさらい。

以前は4方向のみで良かったため、単に 終点X - 始点X と 終点Y - 始点Y の計算をし、それぞれの絶対値を出します。
Xの絶対値がYの絶対値より大きければ横方向、逆の場合は縦方向のフリックと判断。
同じだった場合(45度、135度、225度、315度)はどちらかに寄るようにします。

Xの絶対値が大きかった場合はさらに絶対値にする前の数値の符号を確認。
+だったら右方向、-だったら左方向のフリックと判断。

イメージとしては☒という記号の上下左右の三角形部分を見ている感じ。
二つの斜線が交わってるところが始点。
実際は□で囲まずに斜線がスクリーンサイズいっぱいまで伸びてると考えてもらえれば良いです。


で、今回は8方向にしてみます。
流石に8方向となるとYとXどちらの絶対値が大きいかでの判断は出来なくなります。
なので今回はまず始点と終点の角度を求めます。

Vector3 startPos;    //始点
Vector3 endPos;      //終点
if(Input.GetKeyDown (KeyCode.Mouse0))    //マウス左クリック時に始点座標を代入
{
    startPos = new Vector3(Input.mousePosition.x , Input.mousePosition.y , Input.mousePosition.z);
}
if(Input.GetKeyUp (KeyCode.Mouse0))    //マウスのボタン解放時に終点座標を代入
{
    endPos = new Vector3(Input.mousePosition.x , Input.mousePosition.y , Input.mousePosition.z);
    float directionX = endPos.x - startPos.x;
    float directionY = endPos.y - startPos.y;
    float radian = Mathf.Atan2 ( directionY , directionX ) * Mathf.Rad2Deg;
    Debug.Log("radian : " + radian);    //フリック角度をConsoleに表示
}

真っ直ぐ右方向にフリックした時に0度になります。
radian変数の代入に使っている Mathf.Atan2 というのはYとXの長さを見てラジアンを求める関数。
ちなみにラジアンというのは扇形の半径と弧の長さが一致する長さの時の角度を1とする単位のことだそうです。
度に直すとおよそ57.3度くらい。
円周率が絡む単位であるため小数点以下に数字が延々と続き、正確な数値は出ません。
その後ろのMathf.Rad2Degはラジアンを度に変換する定数です。逆もあります。

これで一応フリックの角度が表示されますが、この計算式では-180~180で返してきます。
真っ直ぐ左方向にフリックすると180が返ってきますが、少しでも下方向に入ると-170度台の数値が返ってきます。
右向きのX軸を中心に反時計回りに+、時計回りに-の角度が返ると考えればいいでしょうか。
例をあげると反時計周りに225度と時計回りに-135度は同じ、ということです。
で、返す値は絶対値が小さい方。

なのでマイナス表記の場合、360を加算してあげることで右向きのX軸を0度とした反時計回りの360度の角度を求められます。

Vector3 startPos;    //始点
Vector3 endPos;      //終点
if(Input.GetKeyDown (KeyCode.Mouse0))    //マウス左クリック時に始点座標を代入
{
    startPos = new Vector3(Input.mousePosition.x , Input.mousePosition.y , Input.mousePosition.z);
}
if(Input.GetKeyUp (KeyCode.Mouse0))    //マウスのボタン解放時に終点座標を代入
{
    endPos = new Vector3(Input.mousePosition.x , Input.mousePosition.y , Input.mousePosition.z);
    float directionX = endPos.x - startPos.x;
    float directionY = endPos.y - startPos.y;
    float radian = Mathf.Atan2 ( directionY , directionX ) * Mathf.Rad2Deg;
    if(radian < 0)
    {
        Debug.Log("radian : " + (radian + 360));    //フリック角度をConsoleに表示
    }
    else
    {
        Debug.Log("radian : " + radian);    //フリック角度をConsoleに表示
    }
}

表示する際にif文で分けることで360度まで表示ができます。
右向きX軸を中心に反時計周りに角度を決めるようになります。

さて、角度を求めることができたので続いて上下左右と斜め部分の判定を判断したいと思います。

まずは360度を8分割。45度になりますね。

きれいに8等分してるはずですが何か違和感・・・
実際に判定したい範囲にアルファベットを振ってみると

こんな感じ。

例えば右方向の判定をしたい時 a の範囲か h の範囲かどちらかになると思います。
では右上方向の判定は? これは a か b のどちらかになります。

すると右と右上では a の判定が混在しちゃうことになります。これではいけません・・・
なので実際の判定はこれをさらに45度の半分、22.5度傾けたもの。

こんなイメージになります。
これなら右方向は a のみ、右上は b のみで迷う必要がなくなります。

そしたらいよいよ判定。
今回は素直に0~360度で得られる角度の判定を行うのみ。

基本的には境目から境目の間を見ていく形になります。
ではプログラムの続き。

{
    //省略
    float radian = Mathf.Atan2 ( directionY , directionX ) * Mathf.Rad2Deg;    //radianに角度を代入
    String direction = "";
    if(radian < 0)
    {
        radian += 360;    //マイナスのものは360を加算
    }

    //方向判定
    if(radian <= 22.5f || radian > 337.5f)
    {
        direction = "a";
    }
    else if(radian <= 67.5f && radian > 22.5f)
    {
        direction = "b";
    }
    else if(radian <= 112.5f && radian > 67.5f)
    {
        direction = "c";
    }
    else if(radian <= 157.5f && radian > 112.5f)
    {
        direction = "d";
    }
    else if(radian <= 202.5f && radian > 157.5f)
    {
        direction = "e";
    }
    else if(radian <= 247.5f && radian > 202.5f)
    {
        direction = "f";
    }
    else if(radian <= 292.5f && radian > 247.5f)
    {
        direction = "g";
    }
    else if(radian <= 337.5f && radian > 292.5f)
    {
        direction = "h";
    }
    Debug.Log("direction : " + direction);
}

if文の片方は境目を含める形にしておきます。
そうしないとぴったりこの角度のフリックになった時に判定してくれないので・・・
aの判定だけは360と0の境目が存在し、論理積の判定だと通らなくなるので論理和にしています。

今回は中身が分りやすいように角度を直値で判断する方法をとりましたが、プログラムとしてはあまり魅力的でない書き方です。
他に判定する実現も様々な方法があるので考えてみてもいいでしょう。
hの判定にたどり着くまでのif文の判定を減らすもよし、if文すら使わないもよし。