昨天,我們加上了 UI 還有一些微調,讓遊戲畫面變得更精緻一點點。今天我們要來製作 Game Over 的機制,也就是當玩家撞到障礙物或者是從賽道外墜落,意味著遊戲結束,我們要讓玩家重新回到起始點。
我們需要再建立一個腳本來負責控制所有有關遊戲開始和遊戲結束,而這個腳本並沒有要套用到場景中的任何物體上,所以我們可以建立一個空物件,並將新的腳本套用到該空物件上,這樣程式才有辦法運作。
在 Hierarchy
中右鍵 -> Create Empty
,命名這個空物件為 GameManager
。並且再創立一個腳本,也把他命名為 GameManager.cs
,接著把這個腳本套用到空物件上,我們就可以來撰寫我們的程式了!
// GameManager.cs
using UnityEngine;
public class GameManager : MonoBehaviour
{
public void EndGame()
{
Debug.Log("Game Over");
}
}
當我們碰到物體時,只需要呼叫這個腳本裡面的 EndGame()
就可以了,我們先初步的輸出 Game Over 就好,接著要回到 PlayerCollision.cs
,並且加入呼叫的程式碼 FindObjectOfType<>
。
using UnityEngine;
public class PlayerCollision : MonoBehaviour
{
public PlayerMovement movement;
void OnCollisionEnter (Collision collisionInfo)
{
if(collisionInfo.collider.tag == "Obstacle")
{
movement.enabled = false;
FindObjectOfType<GameManager>().EndGame();
}
}
}
FindObjectOfType<GameManager>
會幫助我們找到 GameManager.cs
這個檔案,這種呼叫的方法比起我們之前在腳本中創立一另外一個腳本的 object 的方法更為安全。因為後者需要人們手動連結腳本到這個變數上,如果有遊戲的機制是玩家死亡之後腳色需要消失,在某個地方又重新出現,那麼這種連結腳本的方法就會出問題。例如我們先建立一個 Player
的 Prefab:將 Player
從 Hierarchy
面板中拖回 Prefab
資料夾
我們開始模擬這個過程
Player
之間的腳本都是我們手動連結上去的,所以一切工作正常Player
Player
拖回場景中Player
有建立 Main Camera
和 Text
之間的連結,但現在這些連結都因為原本的 Player
被刪除,使得相機和文字都無法獲取到正確的數值,新建立的 Plyaer
和原本的並沒有關聯。我們必須再重新把 Player
分別拖拉給 Main Camera
和 Text
的腳本,才能讓遊戲重新恢復正常。 這就是為什麼我們使用 FindObjectOfType<GameManager>
PlayerMovement.cs
加上檢測 y 軸方向的程式碼using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
public Rigidbody rb;
public float forwardForce = 2000f;
public float sideForce = 100f;
// Update is called once per frame
void FixedUpdate()
{
rb.AddForce(0, 0, forwardForce * Time.deltaTime);
if ( Input.GetKey("d"))
{
rb.AddForce(sideForce * Time.deltaTime, 0, 0, ForceMode.VelocityChange);
}
if ( Input.GetKey("a") )
{
rb.AddForce(-sideForce * Time.deltaTime, 0, 0, ForceMode.VelocityChange);
}
if (rb.position.y < -1f)
{
FindObjectOfType<GameManager>().EndGame();
}
}
}
當玩家的 y 軸座標小於 -1 的時候,我們也會呼叫這個 EndGame()
函式。
接著執行遊戲看看 Game Over 是不是都在正確的時間被呼叫:
當玩家掉出賽道時,系統一直不斷地輸出 Game Over ,這是因為每一幀都檢測到 y 軸座標小於 -1 ,這會導致遊戲被重新啟動很多次,所以必須加入判斷機制,使得遊戲最多只會被重啟一次
// GameManager.cs
using UnityEngine;
public class GameManager : MonoBehaviour
{
bool gameHasEnded = false;
public void EndGame()
{
if(gameHasEnded == false)
{
gameHasEnded = true;
Debug.Log("Game Over");
}
}
}
我們使用布林值來操控,最終不論如何,遊戲最多都只會重啟一次
不論是碰撞還是墜落,遊戲都最多都只會輸出一次的重啟訊息
接下來要來真正撰寫重新開啟遊戲的程式
// GameManager.cs
using UnityEngine;
using UnityEngine.SceneManageent;
public class GameManager : MonoBehaviour
{
bool gameHasEnded = false;
public void EndGame()
{
if(gameHasEnded == false)
{
gameHasEnded = true;
Debug.Log("Game Over");
Restart();
}
}
void Restart()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
}
我們獨立寫了一個 Restart
函式,裡面使用的 SceneManager
有需多的功能,但要記得在最上方寫上 using UnityEngine.SceneManageent;
,其中的SceneManager.LoadScene()
可以用來重新啟動場景,例如我們可以寫 SceneManager.LoadScene("level_01")
來啟動第一關的場景。而我們可能擁有很多關卡,我們需要啟動的是當前執行的場景,所以為了得到當前場景的名字,我們可以一樣使用 SceneManager
底下的功能 SceneManager.GetActiveScene().name
在實際遊玩的時候發現我們啟動場景有點太快了,一碰到東西馬上場景就重新啟動場景,所以我們要加入 Invoke
// GameManager.cs
using UnityEngine;
using UnityEngine.SceneManageent;
public class GameManager : MonoBehaviour
{
bool gameHasEnded = false;
public float restartDelay = 1f;
public void EndGame()
{
if(gameHasEnded == false)
{
gameHasEnded = true;
Debug.Log("Game Over");
Invoke("Restart", restartDelay);
}
}
void Restart()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
}
Invoke
有兩個傳入值,分別是要執行的東西,和延遲的時間,也就是說,當檢測到遊戲結束,會再等待一秒鐘,讓玩家看清楚我們究竟是跟什麼東西發生了碰撞,然後才重啟關卡,整個過程變得流暢多了!
最後,如果在重新啟動場景的時候發現光線使得地板變得怪怪的,只要到 Lighting
面板中把下方的 Auto 框框取消打勾就好了
選項如果打勾,表示 Unity 會自動偵測場景的光線變化,並且重新計算所有物體的光影效果並取代掉之前的。但是當我們重新啟動一個場景時, Unity 並沒有時間計算這些東西,所以我們可能會得到一個光線奇怪的場景,所以我們取消打勾,並按下旁邊的重新生成光影,問題就能夠得到解決!
重新執行遊戲就順暢的很多了!會自動重新開始的關卡省的每次都要按重新執行的按鈕。今天完成了遊戲重新啟動的偵測,並且修正了許多遊戲可能會有的問題。明天要來製作早該出現但消失很久的...勝利畫面!當玩家通過關卡時應該顯示的 UI 介面。