對 Unity 太空射擊游戲的實踐


寫在前面 #

本次 Space Shooter 實踐通過實現以下功能達到加深對 U3D 游戲開發的認知.

  • 鍵盤控制飛船移動;
  • 發射子彈設計目標;
  • 隨機生成大量障礙物;
  • 計分;
  • 實現游戲對象的生命周期管理;

同時進一步練習場景元素的編輯, 腳本文件的創建和 GUI 的處理, 以及音頻文件的添加等方法.

最終效果:

1. 導入模型,貼圖和材質 #

步驟要注意的幾點 :

導入的資源包中有可以正確運行已做好的 Done_Main 場景, 將其刪除, 創建一個全新的空場景文件 Main, 實踐復原 Done_Main 的功能;

將 File>>Build Settings>>Player Settings>>Default Is Full Screen 取消勾選, 寬高設置為 400x600;

飛船模型拖至 Hierarchy 命名為 Player, Reset Transform 組件;

添加 Rigidbody, 不希望飛船受重力影響而下墜, 取消勾選 Use Gravity 選項;

添加碰撞體組件 Mesh Collider, 這是一個網格碰撞體, 使飛船能夠與隨機出現的障礙物發生碰撞, 並在碰撞后觸發銷毀飛船和障礙物的事件, Mesh Collider 的 Mesh 屬性為模型 vehicle_playerShip 的網格, 該網格模型包含許多細小的三角形面片

為了提高游戲的執行效率, 飛船網格模型不應該過於復雜, 不必進行如此精確的碰撞檢測, 應該建立一個簡化的模型, 減少不必要的碰撞計算;

最后還要勾選 Convex 和 Is Trigger 選項框, 將 Mesh Collider 設置為觸發器, 如圖;

添加飛船尾部的火焰粒子效果, 要是 Player 的子對象;

使攝像機正對着飛船, Rotation(90,0,0). 使飛船處於 Viewport 視圖窗口的下半部分, Position(0,10,4). 攝像機為正交投影;

添加背景圖片, GameObject>>3D Object>>Quad 創建一個平面命名為 Background, 移除 Mesh Collider, 此時垂直於飛船;(Quad 默認情況下為背向剔除模式, 因此可能需要調整視角才能看到 Quad 平面) Quad 的 Position(90,0,0);

設置 Background 的紋理圖片 Shader 模式為 Unlit/Texture;

為背景添加粒子效果繁星點點;

至此動圖效果:

2. 編寫腳本代碼 #

2.1 控制飛船移動 ##

PlayerController.cs 實現方向鍵控制飛船移動的功能;

 using UnityEngine;
 using System.Collections;

 public class PlayerController : MonoBehaviour
 {
 	// 想在 Inspector 視圖顯示, 就需要為 Boundary 類添加可序列化的屬性 [System.Serializable]
 	[System.Serializable]
 	public class Boundary
 	{
 		// 用於管理飛船活動的邊界值, XZ 平面
 		public float xMin, xMax, zMin, zMax;
 	}

 	// 速度控制變量
 	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;
 			// Mathf.Clamp 限定剛體的活動范圍
 			rb.position = new Vector3 (
 				Mathf.Clamp (rb.position.x, boundary.xMin, boundary.xMax),
 				0.0f,
 				Mathf.Clamp (rb.position.z, boundary.zMin, boundary.zMax)
 			);
 			// 飛船左右移動時有一定的傾斜效果,
 			// 繞 Z 軸旋轉, 往左運動 X 軸上速度為負值, 旋轉的角度為逆時針正值, 所以要乘以一個負系數
 			rb.rotation = Quaternion.Euler (0.0f, 0.0f, rb.velocity.x * -tilt);
 		}

 	}
 }

至此動圖效果為

2.2 實現射擊行為 ##

步驟需要注意的幾點

新建立一個空的游戲對象 Bolt, 添加 Rigidbody 取消勾選 Use Gravity 選項框.

為 Bolt 新建一個子對象 Quad 命名為 VFX, Rotation(90,0,0), 移除 Mesh Collider, 添加材質 fx_bolt_orange.

為 Bolt 添加一個膠囊碰撞體, 勾選 Is Trigger 設為觸發器, 設置 Capsule Collider 的 Direction 屬性值為 Z-Axis, 設置半徑和高度.

為 Bolt 添加一個腳本 Mover.cs. 此段代碼放在 Start() 函數里, 因為在腳本的生命周期中只需要調用一次, 不需要每一幀都調用.

將 Bolt 拖至 Prefabs 文件夾成為預制體, 預制體做好后將原本的 Bolt 刪除.

using UnityEngine;
using System.Collections;

public class Mover : MonoBehaviour
{
	// 子彈的速度
	public float speed;

	void Start ()
	{
		GetComponent<Rigidbody> ().velocity = transform.forward * speed;
	}

}

腳本控制發射子彈, 為 Player 新建空的子對象 Shot Spawn, Position(0,0,0.7), 在此位置發射子彈

管理光電子彈的生命周期, 子彈在飛出有效區域之后自行銷毀, 為游戲區域添加觸發器, 當電光子彈飛出區域時觸發事件, 在實踐響應函數中調用 Destroy.

設置 Boundary 為觸發器, 由於不需要在場景中顯示 Boundary 對象, 移除 Mesh Renderer 組件.

為 Boundary 添加腳本 DestoryByBoundary.cs

using UnityEngine;
using System.Collections;

public class DestoryByBoundary : MonoBehaviour {

	void OnTriggerExit(Collider other){
		Destroy (other.gameObject);
	}
}

注意的 :

  • 若要處理游戲對象移出觸發器時的事件, 應該重載事件函數 OnTriggerExit;
  • OnTriggerExit 的參數 Collider 表示移出觸發器的對象, 這里就是飛出邊界的子彈對象上的碰撞體;

2.3 添加小行星障礙物 ##

要注意的幾點

小行星隨機生成, 隨機的角度旋轉;

射擊擊中小行星時, 小行星爆炸並銷毀;

飛船碰上小行星, 飛船爆炸, 游戲結束;

新建空對象 Asteroid Position(0,0,9) Rigidbody 取消 Use Gravity 添加 Capsule Collider 勾選 Is Trigger.

模型 prop_asteroid_01 添加為 Asteroid 的子對象.

Capsule Collider 屬性 Radius = 0.5, Height = 1.6, Direction 為 Z-Axis

為 Asteroid 添加腳本 RandomRotator.cs;

using UnityEngine;
using System.Collections;

public class RandomRotator : MonoBehaviour
{
	// tumble 是旋轉系數
	public float tumble;
	void Start ()
	{
		// angularVelocity 表示剛體的角速度;  insideUnitSphere 表示單位長度半徑球體內的一個隨機點(向量)
		// 乘積結果描述了在半徑長度為 tumble 的球體中的隨機點
		// 由此就可以實現剛體以一個隨機的角速度旋轉
		GetComponent<Rigidbody> ().angularVelocity = Random.insideUnitSphere * tumble;
	}
}

設定 Asteroid 對象的角阻力為0;

添加控制射擊小行星的功能, 為小行星 Asteroid 添加一個腳本來控制碰撞事件 DestroyByContact.cs

using UnityEngine;
using System.Collections;

public class DestoryByContact : MonoBehaviour
{
	// 小行星爆炸時的粒子對象
	public GameObject explosion;
	// 飛船與小行星碰撞飛船爆炸的粒子對象
	public GameObject playerExplosion;

	void OnTriggerEnter (Collider other)
	{

		if (other.tag == "Boundary" || other.tag == "Enemy") {
			return;
		}
		if (explosion != null) {
			// 在小行星銷毀的位置生成一個爆炸效果, explosion 是小行星的位置
			Instantiate (explosion, transform.position, transform.rotation);  
		}

		if (other.tag == "Player") {
			// 在玩家飛機銷毀的位置生成一個爆炸效果, playerExplosion 是飛船的位置
			Instantiate (playerExplosion, other.transform.position, other.transform.rotation);  
		}
		// 銷毀跟小行星碰撞的物體
		Destroy (other.gameObject);  
		// 銷毀小行星
		Destroy (this.gameObject);   
	}
}

Boundary 的 Tag 設為 Boundary; Player 的 Tag 設為 Player

至此動圖效果為

2.4 控制小行星運動和隨機生成 ##

讓小行星以一定的速度飛向飛船, 為 Asteroid 添加腳本 Mover.cs 設置 speed 屬性值為 -5; 速度設為負值, 因為小行星與子彈的運動方向相反

需要先制作 Asteroid 預制體, 創建 Project>>GameController 空游戲對象, Tag 為 GameController, 並為之創建腳本 GameController.cs

using UnityEngine;
using System.Collections;

public class GameController : MonoBehaviour
{
	// 小行星數組
	public GameObject[] hazards;
	// 隨機生成小行星的位置
	public Vector3 spawnValues;
	// 每一波小行星生成的數量
	public int hazardCount;
	// 每次生成小行星對象后延遲的時間, 單位秒
	public float spawnWait;
	// 表示開始生成小行星對象前等待的時間
	public float startWait;
	// 表示兩批小行星陣列間的時間間隔
	public float waveWait;

	void Start ()
	{
		StartCoroutine (SpawnWave ());
	}

	// 一波一波地生成小行星
	IEnumerator SpawnWave ()
	{
		yield return new WaitForSeconds (startWait);

		while (true) {
			for (int i = 0; i < hazardCount; i++) {
				GameObject hazard = hazards [Random.Range (0, hazards.Length)];
				Vector3 spawnPosition = new Vector3 (Random.Range (-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
				Instantiate (hazard, spawnPosition, Quaternion.identity);  // 生成隨機的小行星
				yield return new WaitForSeconds (spawnWait);
			}

			yield return new WaitForSeconds (waveWait);
		}
	}
}

有一個要注意的地方, 對數組 Hazards 的內容不能拖成 model ,要是預制體, 否則生成的小行星無效導致不會運動, 如圖

防止小行星數量太多, 距離近以致小行星之間相互碰撞銷毀, 需要使用 協程類 WaitForSeconds

讓爆炸后的粒子實例 explosion_asteroid 自動銷毀, 建立腳本 DestroyByTime.cs 綁定到 explosion_asteroid 和 explosion_player 上

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DestroyByTime : MonoBehaviour
{

	public float lifeTime = 2.0f;

	void Start ()
	{
		Destroy (gameObject, lifeTime);
	}
}

3. 添加音頻 #

將音頻文件添加至預制體
是否勾選 Play On Awake 表明音頻文件在喚醒時自動播放;

4. 添加積分文本 #

新版 Text 組件的使用方法, GameObject>>UI>>Text 生成 Canvas>>Text 和 EventSystem. 調整 Text 位置, Anchor Presets 選擇 top-left.

積分功能包括以下作用 :

飛船發射子彈擊中小行星后分值增加;
分值增加后更新 Text 組件的顯示;

在 GameController.cs 腳本添加變量 scoreText 和 score

// 更新計分 Text 的組件
public Text scoreText;
// 保存當前分值
private int score;

void Start ()
{
  score = 0;
  UpdateScore ();
  StartCoroutine (SpawnWave ());
}
void UpdateScore ()
{
  scoreText.text = "Get Score : " + score;
}

public void AddScore (int newScoreValue)
{
  score += newScoreValue;
  UpdateScore ();
}

腳本 DestoryByContact.cs 可以調用 AddScore 函數.

// 表示小行星被擊中后玩家分值增加的數量
public int scoreValue;
// 表示在游戲對象 GameController 上綁定的腳本 GameController.cs
private GameController gameController;

void Start ()
{
  GameObject go = GameObject.FindWithTag ("GameController");
  if (go != null) {
    gameController = go.GetComponent<GameController> ();
  } else {
    Debug.Log ("Cannot Find a tag of GameController");
  }
  if (gameController == null) {
    Debug.Log ("Cannot Find the Script of GameController.cs");
  }
}
if (explosion != null) {
			// 在小行星銷毀的位置生成一個爆炸效果, explosion 是小行星的位置
			Instantiate (explosion, transform.position, transform.rotation);  
			gameController.AddScore (scoreValue);
		}

5. 游戲結束與重新開始 #

添加游戲結束的 Text 組件

添加游戲結束的腳本

GameController 添加變量

// 更新 Text 組件的顯示
public Text gameOverText;
// 游戲是否結束
private bool gameOver;
public void GameOver ()
{
  gameOver = true;
  gameOverText.text = "游戲結束";
}
while (true) {
			if (gameOver) {
				break;
			}
  // ... ...
}

在 DestroyByContact.cs 腳本加入對 GameOver() 函數的調用.

if (other.tag == "Player") {
	// 在玩家飛機銷毀的位置生成一個爆炸效果, playerExplosion 是飛船的位置
	Instantiate (playerExplosion, other.transform.position, other.transform.rotation);  
	gameController.GameOver ();
}

添加重新開始的 Text 組件, 按[R]鍵重新開始.

// 更新添加的 Text 組件
public Text restartText;
// 是否重新開始游戲, 只有游戲結束時重新開始
private bool restart;

void Start ()
{
  score = 0;
  UpdateScore ();
  gameOverText.text = "";
  gameOver = false;
  restartText.text = "";
  restart = false;
  StartCoroutine (SpawnWave ());
}

void Update ()
{
  if (restart) {
    if (Input.GetKeyDown (KeyCode.R)) {
      Application.LoadLevel (Application.loadedLevel);
    }
  }
}

Application.LoadLevel(Application.loadedLevel) 是 Unity 中重新加載場景的常用方法.

三個文本

至此完畢.

End.

學習自 Book《Unity 官方案例精講》

導出包 →GitHub


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM