2Dゲームメモ 〜ユニットの移動
2Dゲームメモ。ユニットの移動方法について。
ユニットの移動ロジック
目標に向かって、一定のスピードで直進
点Aから点Bに向かって、一定のスピードで進む
- ユニットの移動方法
ABベクトルの単位ベクトルにユニットのスピード(1フレームあたりに進む距離)をかけたものが、移動ベクトルになります。
(1)ABベクトルの距離を求め、一定以下の場合Bに到達。そうでなければ(2)へ
(2)ABベクトルの単位ベクトルを求める
(3)求めた単位ベクトルにユニットのスピードをかける
(4)点Aに1フレームあたりに進むベクトルを加算して移動する
(5)(1)へ
- サンプル
以下のようなベクトルを扱うクラスを用意すると計算がらくです。
Vector2D.java
import android.graphics.PointF; public class Vector2D extends PointF { public static final Vector2D LEFT = new Vector2D(-1f, 0); public static final Vector2D UP = new Vector2D(0, -1f); public static final Vector2D RIGHT = new Vector2D(1f, 0); public static final Vector2D DOWN = new Vector2D(0, 1f); public Vector2D() { super(); } public Vector2D(float x, float y) { super(x, y); } public Vector2D(PointF vec) { super(vec.x, vec.y); } public Vector2D(Vector2D vec) { super(vec.x, vec.y); } public Vector2D(PointF from, PointF to) { super(to.x - from.x, to.y - from.y); } public Vector2D set(PointF from, PointF to) { this.set(to.x - from.x, to.y - from.y); return this; } public Vector2D normalize() { float len = length(); x = x / len; y = y / len; return this; } public void offset(PointF offset) { offset(offset.x, offset.y); } public float dot(PointF vec) { return x * vec.x + y * vec.y; } public Vector2D times(float pow) { x = x * pow; y = y * pow; return this; } public float distance(PointF to) { return length(to.x - x, to.y - y); } public static float distance(PointF from, PointF to) { return length(to.x - from.x, to.y - from.y); } public Vector2D add(float ax, float ay) { x += ax; y += ay; return this; } public Vector2D dec(float ax, float ay) { x -= ax; y -= ay; return this; } public Vector2D add(PointF add) { return this.add(add.x, add.y); } public Vector2D dec(PointF dec) { return this.dec(dec.x, dec.y); } public boolean isEquals(Vector2D vec) { if(vec == null) return false; if(x == vec.x && y == vec.y) return true; return false; } }
- sample
PointF targetA・・・点A
PointF targetB・・・点B
float unitSpeed・・・ユニットのスピード
// ABベクトル Vector2D vectorAB = new Vector2D(targetA, targetB); // 長さが閾値(unitSpeed)以下のとき if(vectorAB.length() < unitSpeed) { targetA.set(targetB.x, targetB.y); // 到達状態の処理を行う .... } // 単位ベクトルを求め、スピードをかける vectorAB.normalize().times(unitSpeed); // targetAを移動する targetA.offset(vectorAB.x, vectorAB.y);
回転しながらターゲットの方向を向く
移動ではありませんが、ターゲットを追跡するときの基本になるので・・・。回転砲台などで使います。
ここでは内積の性質を利用します。
(1)ABベクトルとACベクトルの角度 < 90
→ 内積 > 0
ABベクトルとACベクトルの長さが等しく、内積 == 1のとき、ABベクトルとACベクトルが一致
(2)ABベクトルとACベクトルが垂直
→ 内積 == 0
(3)ABベクトルとACベクトルの角度 > 90
→ 内積 < 0
ABベクトルとACベクトルの長さが等しく、内積 == -1のとき、ABベクトルとACベクトルが逆向きで一致
- ユニットの回転方法
uベクトル・・・ユニットの向き
vベクトル・・・uベクトルを右に90度回転させたベクトル
vベクトルとABベクトルが垂直(内積==0)となるようにuベクトルを回転させます。
uベクトルの向きがABベクトルと間逆になる場合もありうるので、uベクトルとABベクトルの内積>0のチェックも行います。
(1)ABベクトル、ユニットAの向きuベクトルとuベクトルに垂直なvベクトルを取得
右に90度回転させた垂直なベクトルは回転行列より、u(X, Y) → v(-Y, X)のように求められます。
(2)uベクトルとABベクトルの内積(uAB)、vベクトルとABベクトルの内積(vAB)を求める
(3)uAB > 0かつvAB == 0のとき、終了(実際にはvABが閾値以下のときuベクトルをABベクトルに重ねる)。それ以外のとき、(4)へ
(4)uAB、vABに応じて回転
回転行列についてはhttp://ja.wikipedia.org/wiki/%E5%9B%9E%E8%BB%A2%E8%A1%8C%E5%88%97を参考にしてください。
(5)(1)へ
- サンプル
Vector2D vectorAB・・・ABベクトル
Vector2D uVector・・・ユニットの向き。単位ベクトル
double degree・・・回転角
public void lockOnTarget(Vector2D vectorAB, Vector2D uVector) { // vベクトル(uVectorを右に90°回転) Vector2D vVector = new Vector2D(-uVector.y, uVector.x); // 内積 float uAB = uVector.dot(vectorAB); float vAB = vVector.dot(vectorrAB); if(uAB > 0) { // vAB == 0だとなかなか重ならないので、閾値以下で重ならせる if(Math.abs(vAB) < 閾値) { // ユニットの向きが一致 uVector.set(vectorAB).normalize(); } else if(vAB > 0) { // 右回転 rotateVector(uVector, degree); uVector.normalize(); } else { // 左回転 rotateVector(uVector, -degree); uVector.normalize(); } } else { if(vAB > 0) { // 左回転 rotateVector(uVector, -degree); uVector.normalize(); } else { // 右回転 rotateVector(uVector, degree); uVector.normalize(); } } } private void rotateVector(Vector2D vec, double degree) { double radian = Math.toRadians(degree); float x = vec.x; float y = vec.y; vec.x = (float)(Math.cos(radian)*x - Math.sin(radian)*y); vec.y = (float)(Math.sin(radian)*x + Math.cos(radian)*y); }
Canvasで描画を行うような場合、Y軸が反転している点に注意してください。
また、こんな感じにするとif文が少なくなります。
rotateVector(uVector, uAB*vAB/Math.abs(uAB*vAB)*degree); uVector.normalize();
ターゲットをホーミングする
ターゲットを追いかける移動方法
「回転しながらターゲットの方向を向く」動作に「目標に向かって、一定のスピードで直進」を組み合わせるだけです。
- サンプル
PointF targetA・・・点A
PointF targetB・・・点B
float unitSpeed・・・ユニットのスピード
Vector2D uVector・・・ユニットの向き
// ABベクトル Vector2D vectorAB = new Vector2D(targetA, targetB); // 長さが閾値(unitSpeed)以下のとき if(vectorAB.length() < unitSpeed) { targetA.set(targetB.x, targetB.y); // 到達状態の処理を行う .... } // ユニットの向きを調整 lockOnTarget(vectorAB, uVector); // ユニットの向きに進むベクトルを求める float dx = uVector.x * unitSpeed; float dy = uVector.y * unitSpeed; // targetAを移動する targetA.offset(dx, dy);
実際に使用する場合は、インスタンスを極力生成しないようにする必要があります。work変数を用意して、ガーベジコレクタが動作を抑えるようにしてください。