2024 虛擬實境 x 人因設計 13Unity 保齡球遊戲拆解&製作

閱讀時間約 11 分鐘

我們完成了保齡球發射,再來來處理球瓶囉!!

在這之前,我們在學一個程式語言中也算蠻重要的語法....,然後看看怎麼運用到遊戲裡面 :)

For迴圈(loop)

(好像會很麻煩,但我們還是痛苦一下才會進化><)


這看的出來是操場嘛,迴圈的概念就是重複執行,像是繞著操場跑步一直做一樣的事情,通常用於對集合(例如清單、字串、元組等)中的每個元素進行迭代操作。

raw-image

蛤~迭代?

#迭代(iteration)是重複回饋過程的活動,其目的通常是為了接近並且到達所需的目標或結果。每一次對過程的重複被稱為一次「迭代」,而每一次迭代得到的結果會被用來作為下一次迭代的初始值。


常常看到的迴圈有兩種 While & For,這兩個差在哪邊?

觀察兩種迴圈的結構如下:

While迴圈:

while (執行條件) {
重複執行的程式們}

For迴圈:

for (宣告索引變數; 執行條件; 每次迭代索引變數的變化) {
重複執行的程式們}

可以發現最大的差異就在於有沒有宣告索引變數,而for loop因為宣告了索引變數,所以可以記錄目前跑了幾次迴圈,迴圈次數到了就可以跳出迴圈了。相對的While不須宣告索引變數,只要執行條件仍然滿足,就可以一直迴圈下去。因此,當迭代次數已知,也就是當你知道要跑幾次迴圈時,可使用for迴圈;而當只知道迴圈執行條件,而不清楚總共需跑幾次迴圈時,就只能使用While迴圈

就好像狗狗繞圈圈 :

如果要讓小狗繞10圈,可用for迴圈;如果要讓小狗一直跑到主人回到家,但不知道會跑幾圈時,就只能用while迴圈。

程式碼示意如下(在這裡暫且以文字代替真正的程式碼,以著重在兩種迴圈的比較):

  • 跑十圈:
for (let i = 1; i <= 10; i++) 
{
小狗繞一圈
}
  • 一直跑到主人回到家:
while (主人還沒到家)
{
小狗繞一圈
}


While迴圈也可完成for迴圈的任務

但其實while迴圈也可達成for迴圈的任務,只要自行在「迴圈外」宣告索引變數」,並在「迴圈內」寫入每次迭代時索引變數的變化,就可達到同樣的效果,如下:

let i = 1
while ( i <= 10)
{
小狗繞一圈
i++
}

兩種寫法主要的差異在於:

  1. 索引變數的有效範圍: for迴圈的索引變數只在迴圈內有效,而while的則在圈外也有效。
  2. 索引變數變化的位置: for迴圈一律在進入迴圈前;而while則可任意選擇其在迴圈內的位置。

在舉個例:

for 迴圈適合以下情境:

    • 當你知道需要迭代的確切次數時,for 迴圈是最適合的選擇。
    • 例如:瀏覽列表、字典、元組或範圍內的數值。
fruits = ['apple', 'banana', 'cherry']
for fruit in fruits:
print(fruit)


while 迴圈適合以下情境:

迭代次數未知但需要根據條件迭代的情況

  • 當迭代次數不確定,並且需要根據某個條件來決定迭代是否繼續時,while 迴圈是最佳選擇。
  • 例如:等待用戶輸入正確的數據或處理數據直到特定條件滿足。
user_input = ''
while user_input.lower() != 'exit':
user_input = input('Enter something (type "exit" to quit): ')
print('You entered:', user_input)


這樣看來,while迴圈好像有更多的彈性,但相對的也可考量: 索引變數在迴圈外還會需要使用嗎? 若在迴圈外不再需要使用,但在迴圈外仍有效的話,是否只是佔用了多餘的儲存空間,並讓變數的管理更為複雜呢 ?



來練習一下

for (初始變數; 判斷式; 遞增式)

for (i = 0; i<j; i++)

新增一個程式碼,可命名ForLoopMath ,先把他賦予在某物件上,這樣按Play鍵他才會執行。

宣告兩個變數-開始整數 & 結束整數

public int StartNumber = 0;

public int EndNumber = 0;

我們只執行一次,先把它寫在Void Start 裡面

for (StartNumber = 0; StartNumber < EndNumber; StartNumber++)

{

print(StartNumber);

}

raw-image

這是數列的練習,再試個字串(String)的試試~~

宣告一個字串變數

public string[] fruits = { "apple", "orange", "banana" };

一樣只須執行一次所以寫在Start

void Start()

{

// 使用 for 迴圈輸出字串列表的每個元素

for (int i = 0; i < fruits.Length; i++)

{

print(fruits[i]);

}

}

raw-image

好,準備一個GameObject(保齡球球瓶),一個空物件(定位用),還有一個新腳本BowlingPinCreate

打開新腳本後

宣告三個公開變數:

一個是球瓶數量 , 一個是創建球瓶的, 另一個是創建球瓶的起始位置

public int totalBowlingBalls = 5;

public GameObject bowlingBall;

public Transform initialBallPosition;

raw-image


再來試著用for迴圈創建N個球瓶
for (int i = 0; i < totalBowlingBalls; i++)

{

Vector3 ballPosition = Vector3.zero;

// 如果有設置初始位置,則使用初始位置,否則使用計算的位置

if (initialBallPosition != null)

{

ballPosition = initialBallPosition.position + new Vector3(i * 6, 0.5f, 0);

}

else

{

ballPosition = new Vector3(i * 6, 0.5f, 0);

}

Instantiate(bowlingBall, ballPosition, Quaternion.identity);

}

}

raw-image

之後這個腳本附加在地板或其他GameObject上,就是不要附在創建球瓶的上面

(不然會很恐怖,Unity 會直接當掉)

生完球瓶,那就來遊戲的重點啦,讓球瓶倒掉~~

那我希望全部的球瓶倒掉我就贏了,那換句話說就是

「我擊倒的球瓶數達到原本數量就贏了!」

來囉~


先為每個保齡球物件添加了一個名為 PinCollider 的腳本,用於追蹤保齡球的狀態。

GameObject ball = Instantiate(bowlingBall, ballPosition, Quaternion.identity);

// 添加 PinCollider 腳本以追蹤保齡球狀態

ball.AddComponent<PinCollider>();

raw-image

不對阿,我們沒有PinCollider的腳本,所以要再建立一個,這個的內容是我們要看球瓶有沒有碰到地板。

PinCollider腳本這樣寫

private void OnCollisionEnter(Collision collision)
{
// 碰撞到地板時通知 Finish類別已擊倒了
if (collision.gameObject.CompareTag("Finish"))
{
BowlingPinCreate game = FindObjectOfType<BowlingPinCreate>();
if (game != null)
{
game.UpdateKnockedDownBalls();
}
}
}
raw-image

找場景中的第一個 BowlingPinCreate 類別實例


之後回去BowlingPinCreate腳本 新建 ​UpdateKnockedDownBalls()的方法

新增一個私有變數 private int knockedDownBalls = 0; // 已擊倒的保齡球數量

raw-image
 public void UpdateKnockedDownBalls()
{
knockedDownBalls++;

// 檢查是否達到贏得遊戲的條件
if (knockedDownBalls >= totalBowlingBalls)
{
Debug.Log("Congratulations! You win the game!");
}
else
{
Debug.Log("Knocked down balls: " + knockedDownBalls);
}
}

就可以試著射球瓶看看~~


ㄟ等等,他A球瓶如果碰地板兩次,會+2,這不是我們要的結果吧!正常是一球瓶倒只會+1,所以要回去改球瓶碰撞器的腳本PinCollider,讓他碰到地板一次後就不會再偵測了


private bool hasCollided = false; // 標記是否已經計算過碰撞

public class PinCollider : MonoBehaviour
{
private bool hasCollided = false; // 標記是否已經計算過碰撞

private void OnCollisionEnter(Collision collision)
{
if (!hasCollided && collision.gameObject.CompareTag("Finish"))
{
BowlingPinCreate game = FindObjectOfType<BowlingPinCreate>();
if (game != null)
{
game.UpdateKnockedDownBalls();
}
hasCollided = true; // 設置已經計算過碰撞
}
}
}
    4會員
    20內容數
    Gavin Hsieh ㄉ基地
    留言0
    查看全部
    發表第一個留言支持創作者!
    你可能也想看
    Google News 追蹤
    Thumbnail
    本專欄將提供給您最新的市場資訊、產業研究、交易心法、精選公司介紹,以上內容並非個股分析,還請各位依據自身狀況作出交易決策。歡迎訂閱支持我,獲得相關內容,也祝您的投資之路順遂! 每年 $990 訂閱方案👉 https://reurl.cc/VNYVxZ 每月 $99 訂閱方案👉https://re
    Thumbnail
    本專欄將提供給您最新的市場資訊、產業研究、交易心法、精選公司介紹,以上內容並非個股分析,還請各位依據自身狀況作出交易決策。歡迎訂閱支持我,獲得相關內容,也祝您的投資之路順遂! 每年 $990 訂閱方案👉 https://reurl.cc/VNYVxZ 每月 $99 訂閱方案👉https://re