您的位置:首页 > 其它

物體在二維空間中的移動

2013-11-09 20:25 309 查看


遊戲裡移動的原理很簡單,就是經圖從舊的地方刪除,重新畫在新的地方,只要速度夠快,看起來就會像是在移動。而
XNA 在每一次呼叫繪圖函數時都會有一行 GraphicsDevice.Clear (Color.CornflowerBlue);就是負責把畫面清空用的,所以我們只要改變圖的位置就可以了,話雖如此,但是不斷地改變位置對於需要不斷移動或是移動路徑複雜的物體,實在也很難知道下一次要出現在哪裡,所以必須用算的。於是乎,數學就重要啦!

相信大家在國中的時候就學過幾何數學了,當時或許覺得有趣,也可能認為不知所云!不過現在可能需要稍微回想一下了。在二維空間中物體的移動會有速度,而速度有方向性,在 xna 中可以用 Vector2 記錄,Vector2 支援許多向量的運算。

假設有一顆紅色的球往右上角移動,他的速度是 V,此速度可以分解成 x 和 y 的分量,分別是 Vx 以及 Vy,如下圖:



此時我們可以用 Vector2 中的 X 與 Y 來記錄 Vx 和 Vy。若我們要讓物體往定點移動,假設由 A 點到 B 點速率為 s(速率 s 是純量),速度可以由下面的算式得出:



而當物體在向著目的地移動時,BA向量與速度的內積會是正數(如下圖左上),當物體到達目的地時,BA向量與速度的內積會是零,當物體超過目的地時,BA向量與速度的內積會是負數,(如下圖右下):



因此我們可以由此關係知道是否已經到達目的地,該停止移動了!

知道以上簡單的幾何數學後,就可以開始設計我們的程式了。首先設計一個飛機物件,他是我們的主角,程式碼如下:

public class Airplane{
private Vector2 _Origin;
private Vector2 _Velocity;
private Vector2 _Position;
private Vector2 _Destination;
private float _Speed;
private bool _IsArrive;

public Texture2D Image { get; private set; }

public Vector2 Origin {
get { return _Origin; }
}

//速度
public Vector2 Velocity {
get { return _Velocity; }
}

//目前位置
public Vector2 Position {
get { return _Position; }
}

//目標位置
public Vector2 Destination {
get { return _Destination; }
}

//速率
public float Speed {
get { return _Speed; }
set { _Speed = value; }
}

//是否已經到達
public bool IsArrive {
get { return _IsArrive; }
}

public Airplane(Texture2D image, Vector2 defaultPosition) {
Image = image;
_Origin = new Vector2(image.Width / 2, image.Height / 2);
_Velocity = Vector2.Zero;
_Position = defaultPosition;
_Destination = defaultPosition;
_IsArrive = true;
_Speed = 200;
}

/// <summary>
/// 計算前往目標位置所需要的速度
/// </summary>
/// <param name="destination">目標位置</param>
public void MoveTo(Vector2 destination) {
_Destination = destination;
Vector2 vector = Vector2.Subtract(destination, Position);
float length = vector.Length();
if (length == 0) {
_IsArrive = true;
} else {
float percent = _Speed / length;
_Velocity = Vector2.Multiply(vector, percent);
_Velocity /= 1000f;
_IsArrive = false;
}
}

public void Update(float time) {
if (_IsArrive == false) {
_Position += _Velocity * time;
if (Vector2.Dot(Vector2.Subtract(_Destination, _Position), _Velocity) <= 0) {
_IsArrive = true;
_Position = _Destination;
}
}
}

public void Draw(SpriteBatch sprite) {
sprite.Draw(Image, _Position, null, Color.White, 0, _Origin, 1, SpriteEffects.None, 0);
}
}


讓我稍微介紹一下 MoveTo 函式,此函式的參數是要到達的目標位置。在寫之前,應該先決定「單位」,一般生活中常用的速率單位是公尺每秒,但我想螢幕可以用公尺算的人應該不多!這裡我們就用像素每秒來當速率的單位!表示每一秒物體移動多少像素。因此 MoveTo 的內容就是一些簡單的幾何數數學了~先求出我與目標的向量,在乘以縮放比率,最後除以 1000 是因為我們輸入的速率是以秒為單位,但遊戲時間是以毫秒為單位,所以除以一千,這樣算出來的速度就是像素每毫秒。 Update 函式傳入毫秒,然後速度乘以時間就是距離,用來計算飛機下一個應該出現在哪裡。再來是利用向量內積來計算有沒有跑過頭,如果過頭了就表示已經到了,就要把飛機的位置設定成目標位置,如果不這樣做,飛機會因為速度太快而跑過頭,越快越明顯。Draw
函式就是讓飛機畫出自己。 在 Game 裡面加入 Airplane 物件,然後當使用者點畫面後,飛機就會移動到那個地方,關鍵程式碼如下:

Airplane Airplane;

protected override void Initialize() {
TouchPanel.EnabledGestures = GestureType.Tap;
base.Initialize();
}

protected override void Update(GameTime gameTime) {
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();

float time = (float)gameTime.ElapsedGameTime.TotalMilliseconds;

while (TouchPanel.IsGestureAvailable) {
var gs = TouchPanel.ReadGesture();
Airplane.MoveTo(gs.Position);
}

Airplane.Update(time);

base.Update(gameTime);
}

protected override void Draw(GameTime gameTime) {
GraphicsDevice.Clear(Color.CornflowerBlue);

// TODO: Add your drawing code here
spriteBatch.Begin();
Airplane.Draw(spriteBatch);
spriteBatch.End();
base.Draw(gameTime);
}

_Position = defaultPosition;
_Destination = defaultPosition;
_IsArrive = true;
_Speed = 200;
}

/// <summary>
/// 計算前往目標位置所需要的速度
/// </summary>
/// <param name="destination">目標位置</param>
public void MoveTo(Vector2 destination) {
_Destination = destination;
Vector2 vector = Vector2.Subtract(destination, Position);
float length = vector.Length();
if (length == 0) {
_IsArrive = true;
} else {
float percent = _Speed / length;
_Velocity = Vector2.Multiply(vector, percent);
_Velocity /= 1000f;
_IsArrive = false;
}
}

public void Update(float time) {
if (_IsArrive == false) {
_Position += _Velocity * time;
if (Vector2.Dot(Vector2.Subtract(_Destination, _Position), _Velocity) <= 0) {
_IsArrive = true;
_Position = _Destination;
}
}
}

public void Draw(SpriteBatch sprite) {
sprite.Draw(Image, _Position, null, Color.White, 0, _Origin, 1, SpriteEffects.None, 0);
}
}


讓飛機隨著點選的位置移動,但是飛機卻不會轉頭,看起來怪怪的。接著就來試著讓它跟著轉向吧!程式裡的角度轉向和我們以前念的數學也有點差異,主要是起始位置不同,以前數學通常都是由 X 軸逆時針方向旋轉 (下圖左),但是程式裡是 Y 軸順時針方向旋轉 (下圖右),這點要特別注意。另外一個要注意的就是遊戲內的角度是弧度,介於 0 到 2π 之間。



要轉向,就要先知道角度,大家是不是又把高中數學還給老師了呢?讓我們複習一下。



用這公式就可以求出兩個向量的夾角。由於我們要計算 Y 軸和目前速度 (V) 的夾角,而Y軸向量是 (0,1),其長度是 1,所以公式可以簡化如下



看一下程式碼:

public void Update(float time) {
if (_IsArrive == false) {
_Position += _Velocity * time;
_Rotation = (float)Math.Acos((double)(-_Velocity.Y / _Velocity.Length()));
if (_Velocity.X < 0) _Rotation = (float)MathHelper.TwoPi - _Rotation;
System.Diagnostics.Debug.WriteLine(string.Format("{0}:{1}", _Velocity.Y / _Velocity.Length(), _Rotation));
if (Vector2.Dot(Vector2.Subtract(_Destination, _Position), _Velocity) <= 0) {
_IsArrive = true;
_Position = _Destination;
}
}
}

/// </summary>
/// <param name="destination">目標位置</param>
public void MoveTo(Vector2 destination) {
_Destination = destination;
Vector2 vector = Vector2.Subtract(destination, Position);
float length = vector.Length();
if (length == 0) {
_IsArrive = true;
} else {
float percent = _Speed / length;
_Velocity = Vector2.Multiply(vector, percent);
_Velocity /= 1000f;
_IsArrive = false;
}
}

public void Update(float time) {
if (_IsArrive == false) {
_Position += _Velocity * time;
if (Vector2.Dot(Vector2.Subtract(_Destination, _Position), _Velocity) <= 0) {
_IsArrive = true;
_Position = _Destination;
}
}
}

public void Draw(SpriteBatch sprite) {
sprite.Draw(Image, _Position, null, Color.White, 0, _Origin, 1, SpriteEffects.None, 0);
}
}


其中重要的兩行需要稍微講解一下

VB

_Rotation = (float)Math.Acos((double)(-_Velocity.Y / _Velocity.Length()));


注意這裡是負的 _Velocity.Y,這跟坐標系有關,根據上面角度的算法是由 Y 軸順時針起算,這裡 Y 軸是往上為正的笛卡兒座標系,但是螢幕的 Y 軸卻是往下為正,因此當我們的速度是正的,在螢幕上是往下跑,轉換成笛卡兒座標系就必須加個負號。

再來是下面一行

VB

if (_Velocity.X < 0) _Rotation = (float)MathHelper.TwoPi - _Rotation;


當我們用Math.Acos計算出來的角度,會是最小夾角,也就是不論我們的方向是右上還是左上,如下圖,得到的角度都是πD4 (45度),所以我們要分辨速度的X軸,如果是負的,就要用計算補角,也就是 2π-π/4=7π/4 (315度)



這樣計算的角度才會是我們要的,程式執行後,小飛機就會往我們點擊的地方跑,也會正常地轉頭了!

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  XNA