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");//小括號裡可以填寫場景的名字 } } }
新手上路可以一起交流哦!
&n