物體在二維空間中的移動

来源:互联网 发布:bindvalue数组 编辑:程序博客网 时间:2024/06/06 10:45

遊戲裡移動的原理很簡單,就是經圖從舊的地方刪除,重新畫在新的地方,只要速度夠快,看起來就會像是在移動。而 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度)

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

原创粉丝点击