GAME
ベクトルによる移動処理(中級編)


HomeProgramming TipsGame Tips[GAME005]

 ダウンロード : ベクトル移動サンプル(2005/04/07) 

本編では理解を最優先するため、基本的には2次元ベクトルを中心に解説します。
ベクトルの世界では2次元も3次元もあまり変わりませんので、2次元が分かれば徐々に3次元にトライしてください。
まず、ベクトルを理解する上で、最低限、次の用語だけは覚えてください。

これら三つの単位をうまく使うことで斜め移動を簡単に実現しようというのが本解説の主旨です。

さて、ベクトルをまともに扱おうと思ったら、やはり構造体(できればクラス)で、その要素を一括して管理したいですね。
とりあえず、最も簡単な解説のため、2×2行列と2次元ベクトルを定義してみましょう。

行列は各要素を名称でも、要素番号でも指定できるようにするため、union を使って定義しました。
こちらのほうが何かと使いやすいはずです。


// 行列定義
struct TMatrix2 {
    union {
        struct {
            float   _11, _12;
            float   _21, _22;
        };
        float m[2][2];
    };
};

// ベクトル定義
struct TVector2 {
    float   x, y;
};

さて、基本となる値をこの構造体に代入する関数を用意しましょう。
算数で n×1 は n で代わりがありませんね。
これと同じように掛けても値が変わらない基本的な大きさのベクトルを単位ベクトルと呼びます。
同様に大きさが1の行列を単位行列と呼びます。

単位ベクトルを取得する簡単な方法は三角関数の cosθ, sinθ の値をそれぞれ x,y とすれば OK です。


// -------------------------------------------------------------------
// 単位ベクトルを取得する
// -------------------------------------------------------------------
inline TVector2 GetUnitVector2(
    float fAngle            // 角度(ラジアン角)
)
{
    const TVector2 c_vec2 = { cosf(fAngle), sinf(fAngle) };
    return c_vec2;
}



角度すら不要の場合はこんなに簡単です。


// -------------------------------------------------------------------
// 単位ベクトルを取得する
// -------------------------------------------------------------------
inline TVector2 GetUnitVector2()
{
    const TVector2 c_vec2 = { 1.0f, 0.0f };
    return c_vec2;
}



さて、このベクトルの長さを変化させましょう。
基本的にはスカラ値を各要素に乗除算してやります。
加減算はダメです。各要素の比率が変わると方向が変化してしまいます。


// -------------------------------------------------------------------
// ベクトルにスカラ値を掛ける
// -------------------------------------------------------------------
inline TVector2 MultiVec2Scalar(TVector2 vec2, float fNum)
{
    const TVector2 c_vec2 = { vec2.x * fNum, vec2.y * fNum };
    return c_vec2;
}


// -------------------------------------------------------------------
// ベクトルをスカラ値で割る
// -------------------------------------------------------------------
inline TVector2 DivVec2Scalar(TVector2 vec2, float fNum)
{
    const TVector2 c_vec2 = MultiVec2Scalar(vec2, 1.0f / fNum);
    return c_vec2;
}



いろんな長さになると扱いにくいので、一旦、単位ベクトルに直す関数も用意します。
長さが n を 1 にするには n で割ります。例えば、長さ7の値を1にするには7で割りますよね。
7÷7→1 となります。これとまったく同じ事をベクトルでも出来るわけです。
ベクトルの長さはピタゴラスの定理を使うことで計算できます。
ピタゴラスの定理とは直角三角形で、直角を成す a,b と斜面 c の長さは a2 + b2 = c2 というアレです。
これを応用します。


// -------------------------------------------------------------------
// ベクトルの長さを取得する
// -------------------------------------------------------------------
inline float GetVec2Length(TVector2 vec2)
{
    const float c_fScalar = sqrtf(vec2.x * vec2.x + vec2.y * vec2.y);
    return c_fScalar;
}

// -------------------------------------------------------------------
// 単位ベクトルを取得する
// -------------------------------------------------------------------
inline TVector2 CalcUnitVector2(TVector2 vec2)
{
    const TVector2 c_vec2 = DivVec2Scalar(vec2, GetVec2Length(vec2));
    return c_vec2;
}



ベクトルの加減算で移動を表現できます。
加減算は対応する各要素を単純に加減算するだけです。


// -------------------------------------------------------------------
// 加算する
// -------------------------------------------------------------------
inline TVector2 AddVec2(TVector2 vec2_1, TVector2 vec2_2)
{
    const TVector2 c_vec2 = { vec2_1.x + vec2_2.x, vec2_1.y + vec2_2.y };
    return c_vec2;
}

// -------------------------------------------------------------------
// 減算する
// -------------------------------------------------------------------
inline TVector2 SubVec2(TVector2 vec2_1, TVector2 vec2_2)
{
    const TVector2 c_vec2 = { vec2_1.x - vec2_2.x, vec2_1.y - vec2_2.y };
    return c_vec2;
}



さて、ここらでまとめましょう。
以下は今までの関数を使用した簡単なサンプルです。

■グローバル変数/マクロ定義

#define RADIAN(x)   (3.1415926f * 2 * (x) / 360)

    TVector2    vec2MyPos;      // 現在位置
    TVector2    vec2MyDirec;    // 移動方向


■初期化する

    // 自分の最初の位置を指定する
    RECT rcClient;
    ::GetClientRect(hWnd, &rcClient);
    vec2MyPos.x = (rcClient.right  - rcClient.left) / 2.0f;
    vec2MyPos.y = (rcClient.bottom - rcClient.top)  / 2.0f;

    // 自分の移動方向を決定する
    vec2MyDirec = GetUnitVector2(RADIAN(rand() % 360));

    // 初期移動速度を設定する
    vec2MyDirec = MultiVec2Scalar(vec2MyDirec, DEFAULT_SPEED);


■ ゲームループで処理する

    // 1% 加速させるか
    if (::GetKeyState(VK_UP) < 0)
        vec2MyDirec = MultiVec2Scalar(vec2MyDirec, 1.01f);

    // 1% 減速させるか
    if (::GetKeyState(VK_DOWN) < 0)
        vec2MyDirec = MultiVec2Scalar(vec2MyDirec, 0.99f);

    // いきなり初期速度に戻すか
    if (::GetKeyState(VK_RETURN) < 0)
        vec2MyDirec = MultiVec2Scalar(CalcUnitVector2(vec2MyDirec), DEFAULT_SPEED);

    // 移動方向を反転させるか
    if (::GetKeyState(VK_SPACE) < 0)
        vec2MyDirec = MultiVec2Scalar(vec2MyDirec, -1.0f);

    // 移動する
    vec2MyPos = AddVec2(vec2MyPos, vec2MyDirec);

    // 範囲外は引き戻す
    RECT rcClient;
    ::GetClientRect(hWnd, &rcClient);
    if (vec2MyPos.x <  float(rcClient.left))   vec2MyPos.x = float(rcClient.left);
    if (vec2MyPos.x >= float(rcClient.right))  vec2MyPos.x = float(rcClient.right);
    if (vec2MyPos.y <  float(rcClient.top))    vec2MyPos.y = float(rcClient.top);
    if (vec2MyPos.y >= float(rcClient.bottom)) vec2MyPos.y = float(rcClient.bottom);

    // 描画する
    ::InvalidateRect(hWnd, NULL, FALSE);



ここまでで、まだ一度も行列が使われていませんね。
行列を使うときは、ベクトルの向きを変えるときです。

まず、回転行列を取得しましょう。
ここでは、公式を使います。意味を理解したい人は別のサイトで勉強してください。
ここはあくまでも使うことに徹します。


// -------------------------------------------------------------------
// 回転行列を取得する
// -------------------------------------------------------------------
inline TMatrix2 Matrix2Rotation(float fAngle)
{
    const float c_fCos = cosf(RADIAN(fAngle));
    const float c_fSin = sinf(RADIAN(fAngle));
    TMatrix2 c_mtx = { c_fCos, -c_fSin, c_fSin, c_fCos };
    return c_mtx;
}



さて、ベクトルを回転させるには、先に得られた回転行列を掛けてやれば OK です。
掛け算にも行列演算の基礎が必要ですが、とりあえず、下記の関数を使えば問題はないです。


// -------------------------------------------------------------------
// ベクトルに行列を掛ける
// -------------------------------------------------------------------
inline TVector2 CalcVec2TransformCoord(TVector2 vec2, TMatrix2 mtx)
{
    const TVector2 c_vec2 = {
        vec2.x * mtx._11 + vec2.y * mtx._21,
        vec2.x * mtx._12 + vec2.y * mtx._22
    };
    return c_vec2;
}



さて、さきほどのサンプルに左右の回転移動を加えてみましょう。
回転性能を毎ループ±5度とします。
毎回、三角関数を計算すると実行速度に影響が出ますので、最初に作っておきます。


    // 右回転用行列の初期化
    mx2RightTurn = Matrix2Rotation(-DEFAULT_CURVE);

    // 左回転用行列の初期化
    mx2LeftTurn = Matrix2Rotation(+DEFAULT_CURVE);


あとは、この行列を使ってループ内で左右のボタンが押されていれば移動方向に回転行列を掛けてやれば OK です。


    // 右に移動するか
    if (::GetKeyState(VK_RIGHT) < 0)
        vec2MyDirec = CalcVec2TransformCoord(vec2MyDirec, mx2RightTurn);

    // 左に移動するか
    if (::GetKeyState(VK_LEFT) < 0)
        vec2MyDirec = CalcVec2TransformCoord(vec2MyDirec, mx2LeftTurn);


< 初級編に戻る  > 上級編に進む 



 Copyright 2005 VALGUS. All Rights Reserved.