讓物體動起來,Unity的幾種移動方式

清風扶雲霧發表於2023-10-27

# 一、前言 #

在大部分的Unity遊戲開發中,移動是極其重要的一部分,移動的手感決定著遊戲的成敗,一個優秀的移動手感無疑可以給遊戲帶來非常舒服的體驗。而Unity中有多種移動方法,使用Transform,使用剛體Rigidbody,使用CharacterController,使用NavMesh導航系統等等等等。當然,對於新手來說,最常見的莫過於使用Transform和Rigidbody這兩種元件的移動方案。所以,這篇文章將就這兩種移動方案進行分析講解。

注意!!!以下程式碼均為2D場景,3D同理

二、Transform

Transform元件是GameObject的變換元件,可以操縱GameObject的位置(Position),大小(Scale),旋轉(Rotation)等等。所以,使用Transform元件進行物體移動是一個非常不錯的選擇,以下是透過Transform元件實現的幾種移動方式,以及對應的場景。

1.Translate

使用Transform的Translate函式可以在GameObject的本地座標系下進行平移。可以傳入一個位移向量作為引數,指定平移的方向和距離。

[SerializeField] private float moveSpeed;
    private void Update()
    {
        //自動向右移動
        transform.Translate(Vector2.right * moveSpeed * Time.deltaTime);
    }
//物體沿向量指向方向移動
//Vector2.right 向右移動向量,也可以寫成自己定義的
//moveSpeed 移動速度,通常為float型

Translate方法在遊戲中可以用作物體移動,適用簡單的移動方式,例如箱子在平面上自動移動等等。

2.MoveTowards

使用Transform的MoveTowards函式可以實現直線移動到目標位置。可以傳入當前位置、目標位置和移動速度來控制移動的速度和到達目標位置。

MoveTowards函式對應的三個引數分別為(當前位置,目標位置,移動速度),前兩個為Vector型別,最後一個為float型別,也可以寫成整型等。

例如:將物體移動到(5,5)的位置

[SerializeField] private float moveSpeed;
    private void Update()
    {
        transform.position = Vector2.MoveTowards(transform.position,new Vector2(5,5),moveSpeed);
    }

3.Lerp

使用Transform的Lerp函式可以實現平滑插值移動。可以傳入起始位置、目標位置和插值比例來控制移動的過渡效果。

Lerp函式對應的三個引數分別為(當前位置,目標位置,插值比例),前兩個為Vector型別,最後一個為float型=型別,插值比例範圍是[0,1],當lerp取0時,物體不移動,lerp取1時,物體直接移動到目標位置,lerp取值越大,物體移動越快。

例如:將物體移動到(5,5)

[SerializeField] private float moveSpeed;
[SerializeField] private float lerp;
private void Update()
{
    transform.position = Vector3.Lerp(transform.position, new Vector2(5,5), lerp); 
}

接下來,將講解一下Lerp函式的移動原理:

插值係數lerp本質上是物體每次移動距離與物體當前位置到目標位置的比值,物體每次移動後,都會重新重置下一步移動距離,但是比例不變,也就是說,物體朝目標點移動,每次移動的距離都會變短。聽起來非常繞口對吧,下面我們用一幅圖來講解一下這個原理。

紅色豎線為第一次移動到的位置,那麼它的移動距離L1=S1lerp,藍色豎線第二次移動到的位置,那麼第二次移動的距離L2=S2lerp,同理,L3=S3lerp。由圖可知,物體每次移動的距離都在縮短,但是,它們每次移動的距離與當前位置到目標位置的距離的比值不變。並且,我們也可以發現,lerp值越大,單次移動距離越大,即速度越快,相反,lerp越小,單詞移動距離也就越小。最後,我們不難發現,在Lerp函式中,物體移動的距離永遠是當前位置到目標位置的距離lerp,也就是說,物體永遠不可能到達目標位置,只會無限接近目標位置。所以,為了使物體可以到達目標位置,我們可以新增一個if條件,當物體的目標位置的距離小於某一值時,物體位置變為目標位置。

if (Vector2.Distance(transform.position, new Vector2(5, 5))<0.1f)
{
     transform.position = new Vector2(5, 5);
}

以上便是使用Transform移動物體的幾種方案,當然使用Transform元件移動物體的方案有很多種形式,具體可以自行探索。

當然,使用Transform元件移動物體有時會出現一個小小的bug,我們將在Rigidbody中說明。

三、Rigidbody

Rigidbody,剛體元件,在這個元件中,我們可以使用物理學的定義進行物體移動等操作。並且,這也是最經常用的操控玩家移動的元件,。當然剛體元件不僅僅只用來移動GameObject,還有很多操作,在這裡,我們只講移動方面的使用。

上文說了,Transform有一個小小的bug,那就是會引起穿模,也就是說,物體在進行移動時,碰到障礙物繼續移動,會導致穿過障礙物,這是一個致命的bug。但是,剛體元件就可以很好的解決這個bug。在這裡,我查閱了一些資料,大致便是,Transform元件是位置的改變,也就是一次一次的發生位置變化,也就是相當於每次移動都是一段瞬移閃現,第一時間在一個位置,下一時間又瞬移到下一個位置,這樣的話,在和障礙物進行擠壓時,就極其容易導致物體和障礙物發生交叉,導致碰撞體檢測出現異常,從而導致穿模,而剛體元件相當於拉著物體移動,就不存在這樣的bug。對這方面感興趣可以查閱相關資料。

下面繼續講解Rigidbody元件控制GameObject移動。

1.AddForce

使用AddForce函式給剛體施加力來移動物體,想要朝哪個方向移動,就在哪個方向新增力。

AddForce函式的引數為AddForce(方向向量 * 力的大小);

[SerializeField] private float force;
private Rigidbody2D rigidbody2D;
private void Start()
{
    //獲取掛載指令碼的物體的剛體元件
    rigidbody2D = GetComponent<Rigidbody2D>();
}
private void Update()
{
    //向上施加一個大小為force的力
    rigidbody2D.AddForce(Vector2.up * force);
}

2.MovePosition

MovePosition函式可以直接設定物體的位置。

MovePosition函式的引數為MovePosition(位置(例如tramsform.position))

下面的程式碼是物體每次向右閃現/瞬移speed的長度,注意,這個方法也有可能導致穿模

[SerializeField] private float speed;
private Rigidbody2D rigidbody2D;
private void Start()
{
    //獲取掛載指令碼的物體的剛體元件
    rigidbody2D = GetComponent<Rigidbody2D>();
}
private void Update()
{
    //向右移動,2D中為向右/前,X軸正方向
    rigidbody2D.MovePosition(transform.position + Vector3.right * speed * Time.deltaTime);
}

當然,也可以直接只填入目標位置,使得物體閃現到指定目標位置

[SerializeField] private Transform targetTransform;
private Rigidbody2D rigidbody2D;
private void Start()
{
    //獲取掛載指令碼的物體的剛體元件
    rigidbody2D = GetComponent<Rigidbody2D>();
}
private void Update()
{
    //傳送到targetTransform的位置
    rigidbody2D.MovePosition(targetTransform.position);
}

3.velocity

首先說明的是velocity不是函式,而是一個引數,也就是物體的速度。

所以我們透過對物體將要移動的方向上新增速度,也就可使物體超指定方向以固定的速度進行移動。

[SerializeField] private float moveSpeed_X;
[SerializeField] private float moveSpeed_Y;
private Rigidbody2D rigidbody2D;
private void Start()
{
    //獲取掛載指令碼的物體的剛體元件
    rigidbody2D = GetComponent<Rigidbody2D>();
}
private void Update()
{
    //水平方向
    float horizontal = Input.GetAxis("Horizontal");
    //豎直方向
    float vertical = Input.GetAxis("Vertical");
    rigidbody2D.velocity=new Vector2 (horizontal*moveSpeed_X*Time.deltaTime, vertical* moveSpeed_Y * Time.deltaTime);
    //也可以只改變x或y的值
    rigidbody2D.velocity = new Vector2(horizontal * moveSpeed_X * Time.deltaTime, rigidbody2D.velocity.y);
    rigidbody2D.velocity = new Vector2(rigidbody2D.velocity.x, vertical * moveSpeed_Y * Time.deltaTime);
}

四、結尾

以上便是幾種簡單的物體移動方式,當然使物體移動的方法有很多種,這裡只列舉了幾種,感興趣的小夥伴可以深究一下。

相關文章