物體在二維空間中的移動

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值