unity官方案例精講(第三章)–星際航行遊戲Space Shooter
案例中實現的功能包括:
(1)鍵盤控制飛船的移動;
(2)發射子彈射擊目標
(3)隨機生成大量障礙物
(4)計分
(5)實現遊戲對象的生命周期管理
導入的工程包中,包含著一個完整的 _scene—Main場景,創建一個全新場景,會在其中實現大部分功能
素材:
鏈接:https://pan.baidu.com/s/1-qFUYMjrvhfeOWThawJ-Hw 提取碼:bhr8
一、場景準備
1、創建飛船對象:
(1)從project面板中Assets/models/vechicle_playerShip到Hierarchy視圖,重命名player。reset它的Transform組件。
(2)添加Rigidbody組件:用途是通過腳本來為飛船添加作用力,此外不希望飛船受重力影響而下墜,取消Use Gravity選項。
(3)添加Mesh Collider組件:目的是使飛船能夠和隨機出現的障礙物發生隨機碰撞,並在碰撞後觸發銷毀飛船和障礙物的事件。此時Mesh Collider組件的Mesh屬性為模型vehicle_playerShip的網格,選中該網格模型,你可以看到在網格模型中包含了很多非常小的細小的三角面片。
由於上面的網格模型過於複雜,在進行碰撞檢測時可能需要消耗大量的計算資源,降低遊戲的執行效率,因此,沒有必要進行這麼精確的碰撞檢測,可以通過建模建立一個簡化的模型,減少不必要的碰撞計算。
為此選中同目錄下的vehicle_playerShip_colloder,展開後選擇對應的網格模型,將它拖動到Mesh Collider組件的Mesh屬性上。還需要勾選Convex和Is Trigger選項框,設置為觸發器。(Convex勾選複選框以啟用凸面。如果啟用,此網格碰撞器將與其他網格碰撞器碰撞。凸網格碰撞器限制為255個三角形)
其中勾選Convex(凸面)是unity新要求,否則運行會出現:Non-convex MeshCollider with non-kinematic Rigidbody is no longer supported since Unity 5.在前面添加剛體的時候,沒有勾選Is Kinematic選項,unity5中不再支援非Kinematic剛體的非Convex網格碰撞體)
(4)添加飛船尾部火焰粒子效果:在project面板中,Assets/Perfabs/VFX/Engines目錄下,將預製體engines_player拖動到Hierarchy視圖的Player對象上,成為player的子對象。
2、設置攝像機的參數
攝像機的投影方式(projection)為Orthography(正交投影),size為10,Clear Flags為Solid Color,background為黑色,其他設置為保留值。
(Clear Flags: 每個攝影機在渲染其視圖時存儲的顏色和深度資訊。螢幕中未繪製的部分為空,默認情況下將顯示skybox。使用多個攝影機時,每個攝影機在緩衝區中存儲自己的顏色和深度資訊,在每個攝影機渲染時累積更多數據。當場景中的任何特定攝影機渲染其視圖時,可以設置清除標誌以清除緩衝區資訊的不同集合。
skybox:這是默認設置。螢幕的任何空白部分都將顯示當前相機的天空盒。如果當前攝影機沒有設置「天空盒」(skybox)
solid color:螢幕的任何空白部分都將顯示當前相機的背景色。
Depth only:如果要繪製玩家的槍而不讓其在環境中被剪輯,請將一個攝影機設置為深度0以繪製環境,並將另一個攝影機設置為深度1以單獨繪製武器。
Don’t clear:此模式不清除顏色或深度緩衝區. 結果是,每個幀都會在下一幀上繪製,從而產生塗抹效果。這通常不用於遊戲,而且更可能與自定義著色器一起使用
注意,在某些GPU(主要是移動GPU)上,如果不清除螢幕,可能會導致下一幀中未定義螢幕內容。在某些系統中,螢幕可以包含前一幀影像、實心黑螢幕或隨機彩色像素
)
3、添加背景圖片
(1)創建一個Quad面片,重命名為background,移除Mesh Collider組件,在Assets/Textures中選擇tile_nubula_green_dff,將其拖動到background上,(此圖片的尺寸是1024*2048,寬高比為1:2,為了防止圖片被拉伸失真,在放大是需要遵循這個比例。)設置其Transform組件。紋理的shader設置為Unlit/Textures。
4、添加粒子背景效果
在真實的是空中應該是繁星點點,所以要添加粒子背景效果,讓星空背景更貼近逼真
(1)在Assets/Prefabs/VFX/Starfield目錄下,拖動預製體StarField到Hierarchy面板上,保留Transform組件屬性的默認值,由於Y值為-5,高於background的(-10),所以不會被background擋住。
(2)展開StarField可以看到兩個子對象,其中part_StarFied用於生成較大的粒子效果,另外一個生成較小的粒子效果。在子對象中,你會發現一個粒子系統組件(Particle System)
二、編寫腳本程式碼
1、鍵盤控制飛船移動的操作
(1)在Assets中創建文件夾Scripts,在Scripts中創建PlayerController.cs腳本,由於需要處理剛體組件的物體特效,我們在此重載事件函數FixedUpdate,並且在其中添加如下程式碼:
void FixedUpdate() { //得到水平和豎直方向的輸入 float moveHorizontal = Input.GetAxis("Horizontal"); float moveVertical = Input.GetAxis("Vertical"); //利用上面得到的水平和豎直方向的輸入創建一個vector3,作為剛體速度 Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical); G
(2)綁定腳本到player對象,直接選中腳本,將其拖動到player上
(3)運行遊戲,有三個問題:
- 飛船的移動速度過慢
- 沒有對player做範圍限制,飛船可以移動到螢幕外
- 左右移動飛船的時候,飛船沒有側翻效果
(4)解決上面問題,添加一個控制速度變數,創建一個public類型的變數speed
(5)添加限制對象運動範圍的程式碼:
由於此場景飛機的活動範圍是在xz平面上的,需要限制player的位置在有效的活動範圍內,由background決定其xz的坐標值
- 在腳本中創建一個Boundary類用於管理飛船活動的範圍,在PlayerController類中添加一個Boundary的實例。訪問許可權是public
public class Boundary { public float xMin, xMax, zMin, zMax; } public class Player_Control : MonoBehaviour { public float speed;//速度 public Boundary1 boundary;
- 要將一個物體限制在一個範圍內,可以使用unity提供的Mathf.Clamp函數來實現:該函數若value的值小於min,則返回min;若value大於max,則返回max。於是可以在FixedUpdate中限定
static float Clamp(float value,float min,float max);
- 在player面板上,並沒有看到boundary變數出現,需要為Boundary類添加可序列化的屬性
[System.Serializable] public class Boundary1 { public float xMin, xMax, zMin, zMax; }
- 運行遊戲,尋找臨界值。此時FixedUpdate函數的程式碼
void FixedUpdate() { //得到水平和豎直方向的輸入 float moveHorizontal = Input.GetAxis("Horizontal"); float moveVertical = Input.GetAxis("Vertical"); //利用上面得到的水平和豎直方向的輸入創建一個vector3,作為剛體速度 Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical); Rigidbody rb = GetComponent<Rigidbody>(); if(rb != null) { rb.velocity = movement * speed; rb.position = new Vector3(Mathf.Clamp(rb.position.x, boundary1.xMin, boundary1.xMax), 0.0f, Mathf.Clamp(rb.position.z, boundary1.zMin, boundary1.zMax)); } }
(6)添加移動時旋轉的效果
- 要是想飛船左右移動時,以一定的角度傾斜,需要在改變飛船位置的同時更新飛船的Rotation屬性:在PlayerController類中添加一個傾斜係數tilt,設置默認值為4.0f.
- 在FixedUpdate函數中添加下面的語句
rb.rotation = Quaternion.Euler(0.0f, 0.0f, rb.velocity.x * -tilt);
- 函數Euler()是Quaternion的一個靜態方法,接收繞XYZ軸的旋轉角度為參數,並返回一個Quaternion對象。若飛船左右傾斜,則需要繞z軸旋轉,往左移動的時候,x軸方向上速度為負值,而此時旋轉角度(逆時針)應該為正值,所以需要乘以一個負數。
此時完整的PlayerController腳本程式碼:
using System.Collections; using System.Collections.Generic; using UnityEngine; [System.Serializable] public class Boundary { public float xMin, xMax, zMin, zMax; } public class Player_Control : MonoBehaviour { public float speed;//速度 public Boundary boundary; public float tilt = 4.0f; void FixedUpdate() { //得到水平和豎直方向的輸入 float moveHorizontal = Input.GetAxis("Horizontal"); float moveVertical = Input.GetAxis("Vertical"); //利用上面得到的水平和豎直方向的輸入創建一個vector3,作為剛體速度 Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical); Rigidbody rb = GetComponent<Rigidbody>(); if(rb != null) { rb.velocity = movement * speed; rb.position = new Vector3(Mathf.Clamp(rb.position.x, boundary.xMin, boundary.xMax), 0.0f, Mathf.Clamp(rb.position.z, boundary.zMin, boundary.zMax)); rb.rotation = Quaternion.Euler(0.0f, 0.0f, rb.velocity.x * -tilt); } } }
三、實現射擊行為
1、創建電光子彈
(1)新建一個空遊戲對象,命名為Bolt,重置其Transform組件,為了防止Player遮擋Bolt,可暫時將player隱藏,然後為Bolt添加一個Rigidbody組件,並取消勾選Use Gravity。
(2)創建一個Quad,命名為VFX,將其設為Bolt的子對象,重置Transform組件,Rotation的屬性值(90,0,0),移除Mesh collider組件
(3)將Assets/Materials目錄下的fx_bolt_orange拖動到VFX上
(4)為Bolt添加一個Capsule Collider組件,勾選Is Trigger選項框,設置為一個觸發器(注意這裡的Capsule Collider組件只能放到Bolt上,不能放到子對象上,不然無法銷毀Bolt對象,然後設置Capsule Collider的direction屬性值為Y-Aixs,並設置radius為0.04,Height為0.65)
(5)新建一個Mover.cs綁定到Bolt上
public float speed=20.0f; // Start is called before the first frame update void Start() { GetComponent<Rigidbody>().velocity = Vector3.forward * speed; }
(6)建立目錄Perfabs,用來存儲預製體,將Blot製作成一個預製體,建好之後,刪除Hierarchy視圖中的Bolt
(7)兩個問題:不能通過鍵盤和滑鼠發射,子彈不會自己消失或者銷毀,數量巨大的子彈必定消耗非常多的系統資源,嚴重影響遊戲的性能
2、用腳本控制發射子彈
(1)為player建立一個空的子對象shot spawn ,這是發射子彈的位置,position的值為(0,0,0.7),位置可以自己調整
(2)為了實現fire1觸發後即刻實例化Bolt預製體,需要:
- 存儲傳入的Bolt遊戲對象,作為Instantiate的第一個參數
- 存儲發射器的位置,作為實例化Bolt的位置
- 設置一定的發射頻率,只有間隔時間到了之後才能繼續發射
(3)在PlayerController中書寫程式碼
public float fireRate = 0.5f;//發射的間隔時間,默認是0.5秒 public GameObject shot; //shot表示的是Bolt預製體 public Transform shotSpawn;//子彈發射的位置 private float nextFire = 0.0f;//表示下次可以發射的最早時間(發射時間應該大於此值)從0開始 private void Update() { if(Input.GetButton("Fire1") && Time.time > nextFire){ nextFire = Time.time + fireRate; Instantiate(shot, shotSpawn.position,Quaternion.identity); } }
3、管理子彈的聲明周期
我們想要子彈飛出有效的遊戲區域後自行銷毀,因此可以為遊戲區域增加觸發器,當飛出的時候,在事件響應中調用Destroy方法
(1)創建一個Cube,重命名Boundary,重置Transform組件,設置數值,由於不用顯示移除Mesh Renderer組件,
(2)創建腳本DestroyByBoundary.cs在其中添加響應的處理事件,OnTriggerExit,將其拖動到Boundary對象上。
private void OnTriggerExit(Collider other) { Destroy(other.gameObject); }
四、添加小行星(Asteroid)
接下來可以在場景中添加小行星對象,實現的目標是:
- 小行星隨機產生,且應該以隨機的角度旋轉
- 當飛船發射子彈擊中小行星時,小行星會爆照並且銷毀
- 若飛船碰撞到小行星,則飛船爆炸,遊戲結束
1、創建小行星對象
(1)創建空對象,重命名為Asteroid,重置其Transform組件,設置position(0,0,10),添加Rigidbody組件,取消Use Gravity選項,將Angular Drag 設置為0;添加capsule collider組件,勾選Is Trigger選項。
(2)從Assets/Models拖動prop_asteroid_01到Asteroid對象上。成為Asteroid的子對象
(3)為了使碰撞體更接近模型的幾何體形狀,選中設置碰撞體的屬性值Radius的值為0.5,Height的值為1.6,Direction為Z軸
2、添加控制小行星隨機旋轉的功能
(1)創建腳本RandomRotator.cs並且綁定到Asteroid對象上。
public float tumble = 10.0f;//小行星的旋轉係數 // Start is called before the first frame update void Start() { //設置剛體的角速度,角速度是描述做圓周運動的物體,單位時間旋轉的角度 //Random.insideUnitSphere表示單位長度半徑球體內的一個隨機點(向量) //記住將剛體的角阻力設置為0,不然會越轉越慢(物體旋轉是所受到的空氣阻力) GetComponent<Rigidbody>().angularVelocity = Random.insideUnitSphere * tumble; }
3、添加控制射擊小行星的功能
子彈射中小行星,二者會消失;飛船與小行星發生碰撞,二者會消失
(1)新建一個腳本DestroyByContact.cs,並且綁定的Asteroid對象上
(2)小行星在Boundary中,如果寫直接寫銷毀程式碼,遊戲一開始就會把小行星和Boundary銷毀,所以要進行碰撞體檢測,若是與Boundary碰撞不銷毀,與其他的對象則執行銷毀程式碼,方法之一是比較對象的Tag屬性,設置Boundary的Tag為Boundary。
(3)添加程式碼
public class DestroyBy_Contact : MonoBehaviour { private void OnTriggerEnter(Collider other) { if(other.tag == "Boundary") { return; } Destroy(other.gameObject); Destroy(gameObject); } }
4、添加小行星爆炸效果
(1)在腳本DestroyByContact中添加兩個變數
public GameObject explosion;//小行星的爆炸粒子效果對象 public GameObject playerExplosion;//飛船爆炸的粒子效果對象
(2)在碰撞函數中添加實例化粒子效果的程式碼
//實例化爆炸效果 Instantiate(explosion, transform.position, transform.rotation); if(other.tag == "Player") { Instantiate(playerExplosion, other.transform.position, other.transform.rotation); }
(3)在Assets/prefabs/VFX目錄下拖動explosion_asteroid到變數explosion上,explosion_player到變數playerExplosion上
5、添加小行星移動的功能
(1)將Mover.cs腳本拖動到Asteroid上,設置Speed的值為-5,使小行星向與子彈運動方向相反的方向運行
6、添加小行星隨機產生的邏輯功能
在添加隨機產生小行星的邏輯功能之前,需要先製作Asteroid預製體
(1)將Asteroid拖動到Prefabs中,然後在hierarchy面板中刪除
(2)創建一個空對象,重命名為GameController,重置其Transform組件,設置Tag為GameController
(3)創建GameController.cs腳本,並且拖動到GameController上
public GameObject hazard;//準備實例化的障礙物對象 public Vector3 spawnValues;//設置為(6,0,14.5) private Vector3 spawnPosition = Vector3.zero;//實例化時的位置 private Quaternion spawnRotation;//實例化時的旋轉 //用於在 void SpawnWaves() { //x在這個範圍之間 spawnPosition.x = Random.Range(-spawnValues.x, spawnValues.x); spawnPosition.z = spawnValues.z; spawnRotation = Quaternion.identity; Instantiate(hazard, spawnPosition, spawnRotation); } // Start is called before the first frame update void Start() { SpawnWaves(); }
(4)將小行星預製體拖拽給hazard,spawnValues設置為(6,0,14.5)
(5)運行會發現隨機位置生成
7、添加小行星批量產生的功能
(1)在GameController腳本中添加變數hazardCount,表示障礙物的數量
(2)修改SpawnWaves中的程式碼
public int spawnCount;//生成小行星的數量 //用於生成小行星 void SpawnWaves() { for (int i = 0; i < spawnCount; i++) { //x在這個範圍之間 spawnPosition.x = Random.Range(-spawnValues.x, spawnValues.x); spawnPosition.z = spawnValues.z; spawnRotation = Quaternion.identity; Instantiate(hazard, spawnPosition, spawnRotation); } }
(3)設置數量為10,這樣的話,,生成的小行星之間會互相碰撞銷毀,為了解決這個問題,可以在每次生成一個小行星後等待一段時間,unity中提供協程類WaitForSeconds可以實現這樣的功能
(4)再添加一個變數spawnWait,使用協程方法,修改函數。並且修改調用方法,設置變數的是為0.5
(5)由於不想一開始就生成小行星,可以在設置一個變數startWait,在for循環的上面添加一段程式碼,保存,設置startwait為1
(6)如果想不斷的產生多波小行星,可以添加一個變數waveWait,表示兩波之間的時間間隔,寫個無限循環,將for包進去,並且加上延遲waveWait
public GameObject hazard;//準備實例化的障礙物對象 public Vector3 spawnValues;// private Vector3 spawnPosition = Vector3.zero;//實例化時的位置 private Quaternion spawnRotation;//實例化時的旋轉 public int spawnCount;//生成小行星的數量 public float spawnWait;//設置產生小行星的時間間隔 public float startWait;//設置等待時間,之後產生小行星 public float waveWait;//兩波小行星之間的時間間隔 //用於生成小行星 IEnumerator SpawnWaves() { //等待startWait秒之後生成行星 yield return new WaitForSeconds(startWait); //不斷產生行星 while (true) { for (int i = 0; i < spawnCount; i++) { //x在這個範圍之間 spawnPosition.x = Random.Range(-spawnValues.x, spawnValues.x); spawnPosition.z = spawnValues.z; spawnRotation = Quaternion.identity; Instantiate(hazard, spawnPosition, spawnRotation); //生成每個行星的時間間隔 yield return new WaitForSeconds(spawnWait); } //兩波波行星生成的時間間隔 yield return new WaitForSeconds(waveWait); } } // Start is called before the first frame update void Start() { StartCoroutine(SpawnWaves()); }
(7)設置waveWait的值為2,運行遊戲,發現可以不斷的生成小行星,但是發現擊中小行星幾次後,爆炸粒子效果explosion_asteroid沒有自動銷毀,隨著遊戲的進行,嚴重的影響了遊戲的美觀和效率。
(8)新建一個腳本DestroyByTime.cs並且綁定到粒子效果上面。
public class DestrtroyByTime : MonoBehaviour { //表示的是粒子的聲明周期默認2秒 public float lifeTime = 2.0f; // Start is called before the first frame update void Start() { //在lifeTime秒之後銷毀物體 Destroy(gameObject, lifeTime); } }
(9)運行遊戲,已經ok了
五、添加遊戲音頻
1、添加碰撞爆炸音頻
(1)將project視圖變成單列布局,兩列的不好弄
(2)將Assets/Audio中將對應的音頻文件拖動到Assets/VFX/Explosions中預製體對象上。確保Play On Awake選項勾選
2、添加飛船射擊音效
(1)將音頻文件拖動到player上,取消勾選Play On Awake選項,不然一開始就會響
(2)在PlayerController腳本中添加以下程式碼,運行發射子彈就可以聽到聲音
if(Input.GetButton("Fire1") && Time.time > nextFire){ ...............//調用audiosource類中成員函數Play來播放聲音 GetComponent<AudioSource>().Play(); }
3、添加背景音效
理論上,背景音樂可以放到場景中任意一個處於活動狀態的遊戲對象上,這裡選擇的是在GameController上
上面講直接拖動音頻文件到目標對象的方法添加音頻,簡介高效。但不利於讀者理解unity管理音頻的過程,下面採用另外一種方法來添加音頻。
(1)在GameController上添加一個AudioSource組件,此時Audio Clip屬性為空。
(2)講背景音樂拖動到Audio Clip中,這樣就可以綁定到GameController上了
(3)由於背景音樂從遊戲開始連續不斷的播放,所以Play On Awake和Loop都要勾選上
六、添加計分文本
(1)創建Text,會自動添加一個 Canvas父對象和EventSystem對象,重命名Text為Score Text,Text組件中的Text屬性輸入:得分
(2)將其放到場景的左上角:
(3)添加計分功能;在GameController中添加兩個變數:之後再創建函數並進行初始化
public Text scoreText;//Text組件 private int score;//分數
void Start() { //初始化分數和Text組件 score = 0; updateScore(); StartCoroutine(SpawnWaves()); } //創建一個增加和更新分數的組件 public void AddScore(int newScoreValue) { score += newScoreValue; updateScore(); } private void updateScore() { scoreText.text = "得分:" + score; } }
(4)在DestroyByContact腳本中加入變數
public int scoreValue;//設置小行星的分數 private GameController gameController;//創建一個GameController類的變數
(5)在小行星碰撞事件函數中OnTriggerEnter中添加分值更新語句
//增加分數 gameController.AddScore(scoreValue);
(6)在函數start中初始化變gameController,我們不能直接得到GameController腳本,需要找到GameController對象,在得到綁定在上面的GameController腳本
private void Start() { GameObject go = GameObject.FindWithTag("GameController"); if(go != null) { gameController = go.GetComponent<GameController>(); } else { Debug.Log("找不到tag為GameController的對象"); } if(gameController == null) { Debug.Log("找不到為GameController腳本"); } }
(7)在GameController對象中將Score Text拖進去,在Asteroid預製體中設置分數為10
七、遊戲結束與重新開始
當飛船銷毀後,遊戲應該結束,並且用戶能夠選擇重新開始遊戲
1、設置遊戲結束的文本,創建Text 設置遊戲結束的字體,居中顯示
2、添加遊戲結束的功能
(1)打開腳本GameController腳本,添加變數
public Text gameOverText;//遊戲結束顯示的文本 public bool gameOver;//遊戲是否結束的標誌
(2)在Start中賦值,遊戲開始時應該清除文本
//遊戲剛開始,文本清除,同時設置gameOver為false gameOverText.text = ""; gameOver = false;
(3)在腳本中添加一個GameOver函數,用來表示遊戲的結束
public void GameOver() { gameOver = true; gameOverText.text = "遊戲結束"; }
(4)在SpawnWaves中,當gameOver為true時,應該跳出while 循環
//不斷產生行星 while (true) { //如果遊戲結束,跳出循環 if (gameOver) { break; }
(5)將場景中的遊戲結束的文本,拖拽給gameOverText變數,unity會自動的賦值
(6)打開腳本DestroyByContact,當小行星碰撞的是player對象的時候,遊戲結束(注意檢查player的Tag是不是設置成了Player)
if (other.tag == "Player") {
............. //調用遊戲結束的函數 gameController.GameOver(); }
(7)運行遊戲,當飛船與小行星碰撞後,遊戲結束
3、重新開始遊戲
1、創建一個Text,重命名restartText,拖動選擇好合適的位置,Text屬性寫: 按下【R】鍵重新開始,調整好大小
2、添加重新開始的程式碼
(1)打開腳本GameController腳本,添加變數
public Text restartText;//重新開始的文本 private bool restart;//遊戲是否從新開始的標誌
(2)在Start中賦值,遊戲開始時應該清除文本
//遊戲開始,文本清除,同時設置restart為false
restartText.text = "";
restart = false;
(3)在SpawnWaves函數中,當遊戲結束時,添加程式碼
//如果遊戲結束,跳出循環 if (gameOver) { restartText.text = "按下【R】鍵重新開始"; restart = true; break;
(4)在Update函數中,添加程式碼
private void Update() { if (restart) { if (Input.GetKeyDown(KeyCode.R)) { //Application.LoadLevel(Application.loadedLevel);已經棄用 SceneManager.LoadScene("Space_Shooter");//小括弧里可以填寫場景的名字 } } }
新手上路可以一起交流哦!