ある座標に向かって移動させる処理

前回の記事で「画面の中央に向かって移動する処理も追加してみました。」とさらりと言いましたが、実はこれが結構難しい処理だったりします。

ある座標に向かって移動させるということは、今いる座標と目標となる座標の角度を求めて、その方向にX座標とY座標を加算してあげる必要があります。

そこで角度を求めるための処理を書いてみました。

float getRadian(float X1,float Y1,float X2,float Y2){
    float w = X2 - X1; // cosθ
    float h = Y2 - Y1; // sinθ

    if( w != 0 ){
        float t_Work = h / w; // tanθ
        if (X1 < X2) {
            // ターゲットX座標が大きい場合はそのまま
            return atanf(t_Work);
        }
        // ターゲットX座標が小さい場合は反対に行ってしまうため180度足す
        return atanf(t_Work) + 3.1415f;
    }

    // 以下ターゲットが同一X座標の場合の処理

    if (Y1 < Y2){
        // ターゲットY座標が大きい場合は真上なので90度
        return 3.1415f / 2.0f;
    }

    // ターゲットY座標が小さい場合は真下なので-90度(もしくは270度)
    return -3.1415f / 2.0f;
}

この処理を実装するには三角関数について良く知っておく必要があります。


自分の理解度を明確にするために、処理にコメントを書くようにしてあります。

まずは座標からcosθとsinθを求めます。と言って処も理だけをみれば単に幅と高さを求めているだけです。

次に幅が0の場合にはちょっとした最適化をしておきます。別のこの処理はなくても問題ないですが、一応です。

幅が0ということはX座標が同じだということになります。

つまりターゲットの座標が真上か真下にあることになります。つまり90度上にいるか、-90度下にいるかの二択になります。

ちなみに真下の場合、-90度でも270度でもどちらでも構いません。270度にする場合は90度+180度と考えると以下のような計算式になりますね。

// 90度 + 180度
return 3.1415f / 2.0f +  3.1415f;

次に幅が0じゃなかった時の処理は少し複雑です。

ターゲットのX座標が大きいか小さいかによって処理ワケしないといけません。

理由は簡単です。

例えば、ターゲット座標TのX座標が大きい場合、

座標A (x_{\small 1},y_{\small 1}) = (1,1)

座標T (x_{\small 2},y_{\small 2}) = (5,5)

cosθ、sinθともに4になり、4/4でtanθは1になります。

次に、ターゲット座標TのX座標が小さい場合、

座標A (x_{\small 1},y_{\small 1}) = (9,9)

座標T (x_{\small 2},y_{\small 2}) = (5,5)

この場合もcosθ、sinθともに-4になり、-4/-4でtanθは1になります。

つまり求められた角度が同じになるので進む方向が同じになるということなります。

明らかにおかしいですよね。なので座標Aが座標Tよりも大きい場合は180度逆の方向へ進んでもらわないと困るというわけなのです。

// ターゲットX座標が小さい場合は反対に行ってしまうため180度足す
return atanf(t_Work) + 3.1415f;

これで無事進むべき方向の角度が取得できるようになりました。

あとはこれを使って

// 角度を求める
float radian = tlib::getRadian(
    m_x + m_width/2,
    m_y + m_height/2,
    static_cast<float>(tlib::SCREEN_WIDTH/2),
    static_cast<float>(tlib::SCREEN_HEIGHT/2)
);

// ターゲットの方向へ進む
m_x += cosf(radian);
m_y += sinf(radian);

このように角度から、実際のcosとsinの値を取得し、加算すれば完成です。


さて、ここまで角度を取得するための処理を実装しておきながら、あとで気付いた衝撃の事実があります。

この自作したgetRadian関数とまったく同じことをしてくれるatan2fという関数が既にmath.hに存在してましたorz

float getRadian(float X1,float Y1,float X2,float Y2){
    float w = X2 - X1; // cosθ
    float h = Y2 - Y1; // sinθ

    // atan2f呼ぶだけでおしまい
    return atan2f(h,w);
}

昨日数時間かけた実装が徒労となりました。とほほ。