Redpoll's 60
 Home / 3Dプログラミング入門 / 第4章 $§$4-20
第4章 3D空間におけるオブジェクトの運動

$§$4-20 指定方向へのオブジェクトの移動 1


4-13節では3D空間内に配置されたオブジェクトから弾を発射することについて扱ったが、本節ではその応用として3D空間内を移動するオブジェクトから弾を連射する処理を実装する。
しかし、本節で作成するプログラムは実質的には第2章で扱った連射プログラムと同じものである。

(なお 本節のプログラムはすべて連射であるためプログラム中における Shell は配列を表す変数である)



# Code1
最初のプログラムではShell(弾)を発射するオブジェクトとして図1の Navy を使用する。Navyは初期状態では z軸プラス方向を向いており、図2に示されるように2つの発射口を持っている (初期状態においては2つの発射口はともに z軸プラス方向を向いている)。
図3は今回使用するShellの初期状態であるが、このShellもNavyと同じく初期状態における向きは z軸プラス方向である。図4は2つのShellを発射開始位置 $S_0$、$S_1$ に移動させたときのものであり、Navyは初期状態であるため発射方向は z軸プラス方向である。

図1 Navy 初期状態
図2  2つの発射口 (初期状態においては z軸プラス方向を向いている)

図3 Shell 初期状態 (z軸プラス方向を向いている)
図4  2つの発射開始位置にShellを配置 (Navyは初期状態であるため発射方向は z軸プラス方向)

以下 4-13節の内容を簡単に復習する。下図はNavyを3D空間内の適当な位置に適当な方向に向けて配置したときのものであり、この状態におけるShellの発射方向を $\boldsymbol{\mathsf{v}}$ とする。
このときShellの向きを変えないまま発射開始位置に移動だけさせたものが図5であるが、図5のShellは初期状態と同じ z軸プラス方向を向いてる。この図から明らかなように本来の発射方向 $\boldsymbol{\mathsf{v}}$ とShellの向いている方向が異なるために、Shellをこの向きで発射することは明らかに不自然である。

図5
図6

今回のShellのように(球体ではなく)向きがあるShellを使う場合、オブジェクトからShellを発射する際にはオブジェクトの向いている方向へShellが発射されるようにすることが必要である。そして今回のNavyとShellは初期状態における向きが同じであるため、ShellをNavyと同じ方向に向けるようにするためにはNavyに実行されている回転と同じ回転をShellに対しても行えばよい。
図6はShellに対してNavyと同じ回転を実行し、発射開始位置に移動させたときのものであるが、図に示されるようにShellが発射方向 $\boldsymbol{\mathsf{v}}$ を向いた状態になっている。

以下のプログラムは3D空間内を飛行するNavyからのShellの連射処理を実装したものである。
使用するキー操作は以下のとおり。
    A  :  (押し続けることで)Shellを連射する。
    S  :  飛行開始あるいは停止のスイッチ。
    H, L  :  Navyに対して水平方向の回転を実行する。
    J, K  :  Navyに対して垂直方向の回転を実行する。

[Code1]  (実行結果 図)
if (!i_INITIALIZED)
{
    Navy.lastShootFrame = 0;
    Navy.shootInterval = 8;
    Navy.shellSpeed = 1.0f;
    Navy.initShootDirection = Vector3.forward;
    Navy.initShootPositions = new Vector4[]
    {
        new Vector4( 0.358f, -0.13965f ,0.2371f, 1.0f),  
        new Vector4(-0.358f, -0.13965f ,0.2371f, 1.0f)        
    };

    i_INITIALIZED = true;
}

NavyMotion();


i_frameCount++;

Matrix4x4 M = Matrix4x4.identity;
Matrix4x4 R = Matrix4x4.identity;
int numShooter = 0;
if (Input.GetKey(KeyCode.A))
{
    if (i_frameCount >= Navy.lastShootFrame + Navy.shootInterval)
    {
        Navy.lastShootFrame = i_frameCount;
        numShooter = 2;

        M = Navy.GetMatrix();
        R = M;
        R.SetColumn(3, new Vector4(0, 0, 0, 1));
    }
}


Vector3 posNavy = Navy.GetWorldPosition();
foreach (var shell in Shell)
{
    if (shell.active)
    {
        Vector3 newShellPos = shell.position + shell.speed * shell.direction;
        if ((posNavy - newShellPos).magnitude > 80.0f)
        {
            shell.active = false;
            shell.position = new Vector3(-1000, 0, 0);
        }
        else
        {
            Matrix4x4 mtx = TH3DMath.GetTranslation4x4(newShellPos) * shell.rotMatrix;
            shell.SetMatrix(mtx);
        }

    }
    else
    {
        if (numShooter > 0)
        {
            Vector3 startPos = M * Navy.initShootPositions[2 - numShooter];
            Vector3 shootDir = M * Navy.initShootDirection;

            shell.active = true;
            shell.direction = shootDir;
            shell.speed = Navy.shellSpeed;
            shell.rotMatrix = R;

            Matrix4x4 mtx = TH3DMath.GetTranslation4x4(startPos) * shell.rotMatrix;
            shell.SetMatrix(mtx);

            numShooter--;
        }
    }
}


このプログラムはいくつかの違いを除けば、2-11節や2-17節で扱った連射プログラムと同じである。初期化ブロックでは連射用のプロパティを設定しているがこれらも第2章で見てきたものと変わらない。ここで使用しているオブジェクトNavyはカスタムライブラリーの THAlphaObject3D クラスのインスタンスであり、このクラスには以下の連射用のプロパティが用意されている。同様にここで使用するShellもカスタムライブラリー THShell3D クラスのインスタンスであり、このクラスには連射用のプロパティが用意されている (そのほとんどは第2章で見てきたものと変わらない)。
3行目の lastShootFrame (int型)はShellを最後に発射したフレーム番号を表し、shootInterval (int型)はShellの発射間隔、shellSpeed (float型)は毎フレームのShellの進行速度、initShootDirection (Vector4型)は初期状態における発射方向を表す。
initShootPositions はShellの発射開始位置を表すVector4[]型配列で、今回のNavyは上図2に示されるように2つの発射口を持っているので発射開始位置は2つである。そのため、このプロパティには初期状態における2つの発射開始位置をセットしている (セットする際は $w=1$ の同次座標にする必要がある)。
16行目の NavyMotion() はNavyの運動が実装されている補助メソッドであり、上に述べたキー操作によってNavyを3D空間内で飛行させるものである (詳細については省略する)。
19行目以降は第2章の連射プログラム(例えば 2-11節 Code5)とほとんど同じであるから、異なる点だけを解説する。今回の設定では shootInterval の値が $8$ なので(4行目)、A キーを押し続けていると8フレームごとに26行目のifブロックに入るが、ここではNavyのプロパティ lastShootFrame の他に3つのローカル変数に値をセットする。
numShooter (int型 ; 29行目)にはそのフレームにおいてShellを発射する発射口の数をセットする。今回はNavyは2つの発射口を持っているのでここでは $2$ がセットされている。つまり そのフレームにおいてNavyから2つのShellが同時に発射されるようにするための設定である。
M (31行目)にはその時点でのNavyに実行されている変換行列(ワールド行列)、R (33行目)にはその変換行列の回転成分がセットされる (Matrix4x4はクラスではなく構造体である。そのため 32行目では M の内容が R にコピーされる)。
今回 Navyに対してはスケールは行われないので、Navyに実行される変換行列は平行移動行列と回転行列の積である。したがって、この変換行列から回転成分を取得するには第4列目を $(0, 0, 0, 1)$ にすればよいが(4-6節参照)、33行目がその処理である。
39行目のforeach文ではactiveなShellを指定の方向へ指定の量だけ移動させ、Shellを発射する際にはactiveでないShellのうち最初に見つかったものを発射開始位置に移動させる (2-11節と同様にここでもShellのプロパティ active の値がtrueのShellを「activeなShell」と表記する)。
activeでないShellを発射開始位置に移動させる処理は58行目のifブロックに記述されている。Navyは発射口が2つあるのでShellを発射するフレームでは2つのShellを同時に発射開始位置に移動させる必要がある。そのためShellを発射するフレームにおいてはこの58行目のifブロックに2回入る必要がある。A キーが押された際にローカル変数 numShooter に $2$ がセットされるのはそのためであり、このifブロックに入るたびに numShooter の値は $1$ ずつ減っていく (71行目)。
この58行目のifブロックではShellのプロパティ activetrueにし、Shellの進行方向、速度などを設定してShellを発射開始位置に移動させるが、第2章で見てきた連射プログラムとは異なりここではShellに対してNavyに実行された回転行列をセットしている (66行目 ; rotMatrix は回転行列をセットするためのプロパティ)。
上で述べたように向きがあるShellを発射する場合はShellをオブジェクトと同じ向きに向ける必要があり、その際にはオブジェクトに実行されている回転行列をShellに対して実行すればよい。68行目はShellを発射開始位置に移動させる記述であるが、平行移動の前にNavyと同じ向きにするための回転(shell.rotMatrix)を実行している。
Shellが発射されるとactiveなShellとなり次のフレームから41行目のifブロックに入ることになるが、ここでは発射したときの方向に一定の速度で進んでいく。しかし ここでも注意が必要である。Shellが進んでいく際にはShellは直線的にまっすぐ進むので、常に発射時点と同じ向きを向いていなければならない。
したがって 毎フレーム Shellを新しい位置に進めていく際にはまず先に、Shellを発射時点と同じ向きに向ける回転を行う。51行目はShellを新しい位置に移動させる行列の計算であるが、この計算において回転行列 shell.rotMatrix を先に行っているのはそのためである。
このプログラムではShellがその時点でのNavyの位置から距離にして $80$ 以上離れた場合に、(ほとんど見えなくなるために)ShellをactiveでないShellに戻している (44行目のifブロック)。

図7 Code1 実行結果
図8 Shellを同時に3つ発射する

なお 同時に発射するShellの数を増やすことも簡単にできる。例えば発射口を3つにする場合は、まず初期化ブロックの発射開始位置の設定(7行目)において以下のように第3の発射口を追加する。
Navy.initShootPositions = new Vector4[]
{
    new Vector4( 0.358f, -0.13965f ,0.2371f, 1.0f),  
    new Vector4(-0.358f, -0.13965f ,0.2371f, 1.0f),
    new Vector4( 0.0f, -0.14f ,0.42f, 1.0f)   // 第3の発射口
};

次に29行目でセットするローカル変数 numShooter の値を $3$ にし、
    numShooter = 3;

60行目の initShootPositions のインデックス [2 - numShooter] を以下のように [3 - numShooter] にする。
    Vector3 startPos = M * Navy.initShootPositions[3 - numShooter];

以上の変更を行うだけで図8に示されるように同時に発射されるShellの数が3つになる。


















© 2020-2024 Redpoll's 60 (All rights reserved)