Unity-2017.3官方實例教程Space-Shooter(二)


由於初學Unity,寫下此文作為筆記,文中難免會有疏漏,不當之處還望指正。

Unity-2017.3官方實例教程Space-Shooter(一)

章節列表:

一、創建小行星Prefab

二、創建敵機和敵機子彈Prefab

三、創建游戲控制器

四、添加音效

五、對象回收

六、創建計分板

 

一、創建小行星Prefab

a、創建一個空對象,重命名為Asteroid_01,在Models文件夾下找到prop_asteroid_01模型,將模型拖拽到Asteroid_01上,使其成為Asteroid_01的一個子對象。

b、為prop_asteroid_01添加膠囊碰撞器(Capsule Collider),調整碰撞器的檢測范圍:

c、為Asteroid_01添加剛體組件(Rigidbody),去掉使用重力選項(Use Gravity):

d、為Asteroid_01添加隨機旋轉腳本RandomRotator,編輯如下內容:

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

public class RandomRotator : MonoBehaviour
{
    public float tumble;
    private Rigidbody rg;
    // Use this for initialization
    void Start ()
    {
        rg = GetComponent<Rigidbody>();
        rg.angularVelocity = Random.insideUnitSphere * tumble;
    }
    
    // Update is called once per frame
    void Update ()
    {
        
    }
}

首先聲明一個公有類型的變量tumble來設置翻滾速度,聲明一個私有類型變量rg便於類內部使用剛體組件,angularVelocity是剛體的角速度向量,在大多數情況下不應該直接修改它,因為這會導致不真實的行為。Random.insideUnitSphere變量隨機返回一個單位球體內的一點(Vector3類型變量,只讀)。

e、為Asteroid_01添加移動腳本。在上一篇中我們創建了一個Mover腳本,在這里我們把Mover腳本拖拽到Asteroid_01上,然后把速度設置為-5,讓Asteroid_01沿Z軸負方向移動。

f、當我們的飛船發射子彈擊中小行星,小行星會被擊毀並產生一個爆炸的特效;當我們的飛船撞向小行星時,飛船會和小行星同歸於盡,並產生另外一個爆炸效果。為了區分是子彈擊中了小行星還是我們的飛船撞向了小行星,我們可以給我們的飛船增加一個tag,通過判斷這個tag就可以知道是不是飛船撞向了小行星。點擊Player,我們在Tag屬性處選擇Player,也可以新建另外的tag:

我們給Asteroid_01添加一個腳本組件,命名為DestroyByContact,編輯內容如下:

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

public class DestroyByContact : MonoBehaviour
{
    public GameObject explosion;
    public GameObject explosion_player;

    // Use this for initialization
    void Start ()
    {
        
    }
    
    // Update is called once per frame
    void Update ()
    {
        
    }

    private void OnTriggerEnter(Collider other)
    {
        if(explosion)
        {
            Instantiate(explosion, transform.position, transform.rotation);
        }
        if(other.tag=="Player" && explosion_player)
        {
            Instantiate(explosion_player, transform.position, transform.rotation);//產生一個特效
        }
        
        Destroy(other.gameObject);
        Destroy(gameObject);
    }
}

聲明兩個公有變量explosion和explosion_player,分別綁定兩種特效對象,用於在小行星和我方飛船爆炸時產生特效。

void OnTriggerEnter(Collider other)函數是碰撞檢測的回調函數,當開始碰撞時調用,要想碰撞函數被調用,需要碰撞器中勾選Is Trigger選項。

Instantiate的函數原型為static Object Instantiate(Object original, Vector3 position, Quaternion rotation)或static Object Instantiate(Object original),函數作用為克隆原始物體並返回克隆物體,original為要拷貝的已經存在的對象,position和rotation分別為拷貝新對象的位置和方向。Instantiate更多通常用於實例投射物(如子彈、榴彈、破片、飛行的鐵球等),AI敵人,粒子爆炸或破壞物體的替代品等。

Destroy的函數原型為static void Destroy(Object obj, float t = 0.0F),在時間t后刪除一個游戲對象,組件或者資源,如果obj是組件,它將從GameObject銷毀組件component。如果obj是GameObject它將銷毀GameObject全部的組件和GameObject全部的transform子物體。實際物體的銷毀總是延遲到當前更新循環后,但總是渲染之前完成。

回到Unity編輯器,點擊Asteroid_01對象,將Prefabs文件夾下的explosion和explosion_player分別拖拽到Explosion和Explosion_player屬性上:

為了后續的使用,我們將Asteroid_01的tag屬性選擇為Enemy:

g、把Asteroid_01制作成一個Prefab。在Prefabs的DonePrefabs文件夾下創建一個Prefab,重命名為Asteroid_01,把Hierarchy視圖中的Asteroid_01拖拽到Asteroid_01預制體上,完成Prefab的制作。

同樣的我們把另外兩個小行星模型也制作成Prefab,分別為Asteroid_02和Asteroid_03。

二、創建敵機和敵機子彈Prefab

和創建Player一樣,敵機也具有船體、引擎特效、子彈掛載點,發射子彈,移動等屬性。

(1)、創建敵機模型。新建一個空對象,重命名為EnemyShip,將Models文件夾下的敵人飛船模型vehicle_playerShip拖到EnemyShip上,調整vehicle_playerShip模型的方向,繞Y軸旋轉180度,使機頭朝下。將Prefabs->VFX->Engines文件夾下的敵機引擎特效engines_enemy拖拽到EnemyShip。再在EnemyShip下建立一個空對象,命名為ShotSpawn,調整ShotSpawn的位置在機頭位置。

 

(2)、為EnemyShip添加剛體組件和膠囊碰撞器。

(3)、給EnemyShip添加Mover腳本使能向下移動。把Mover腳本拖拽到EnemyShip上,並把Speed屬性值設置為-5:

(4)、給EnemyShip添加DestroyByContact腳本,使其能被我方飛船發射的子彈擊毀,或者和我方飛船同歸於盡,但是不會被敵方物體撞毀(敵方子彈,敵方飛船,小行星),修改DestroyByContact腳本內容:

    private void OnTriggerEnter(Collider other)
    {
        if(other.tag=="Enemy")
        {
            return;
        }
        if(explosion)
        {
            Instantiate(explosion, transform.position, transform.rotation);
        }
        if(other.tag=="Player" && explosion_player)
        {
            Instantiate(explosion_player, transform.position, transform.rotation);//產生一個特效
        }
        
        Destroy(other.gameObject);
        Destroy(gameObject);
    }        

回到Unity編輯器,點擊EnemyShip對象,將Prefabs文件夾下的explosion_enemy和explosion_player分別拖拽到Explosion和Explosion_player屬性上:

(5)、讓敵機發射子彈。為EnemyShip添加一個新腳本,命名為WeaponController,編輯腳本內容如下:

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

public class WeaponController : MonoBehaviour
{
    public GameObject shot;
    public Transform shotSpawn;
    public float shotRate;
    public float delay;
    // Use this for initialization
    void Start ()
    {
        InvokeRepeating("Fire", delay, shotRate);
    }
    
    // Update is called once per frame
    void Update ()
    {
        
    }

    private void Fire()
    {
        Instantiate(shot,shotSpawn.position,shotSpawn.rotation);
    }
}

a、shot變量用來實例化子彈,shotSpawn為子彈射出的位置,shotRate記錄敵機發射子彈的頻率,delay為實例化敵機后多少秒敵機開始發射子彈。

b、InvokeRepeat的函數原型為 void InvokeRepeating(string methodName, float time, float repeatRate),在time秒調用methodName方法,然后每repeatRate秒重復調用methodName方法。Fire函數實例化一個子彈對象。

c、創建子彈預制體。

在Assets下新建一個文件夾命名為MyMaterials,在MyMaterials下新建一個材質球,右鍵->Create->Material,命名為mat_bolt,在Inspector視圖中設置Shader屬性為Particles/Additive,選擇紋理為fx_lazer_cyan_dff:

新建一個空對象,重命名為Bolt,在Bolt下新建一個Quat對象,重命名為VFX,刪除VFX上的Mesh Collider。把材質球mat_bolt拖拽到VFX上面,並將VFX繞X軸旋轉90°:

點擊Bolt,設置Bolt的tag屬性為Enemy,添加剛體組件和膠囊碰撞器,為Bolt添加Mover腳本,並設置Speed屬性為-20,為Bolt添加DestroyByContent腳本,設置完成如下:

在Prefabs/DonePrefabs文件夾下新建一個Prefab,重命名為Bolt,將Hierarchy視圖中的Bolt對象拖拽到Prefab Bolt上,完成子彈預制體的制作。然后刪除Hierarchy視圖中的Bolt對象。

d、點擊EnemyShip對象,為Shot、Shot Spawn、Shot Rate和Delay參數賦值:

(6)、讓敵機具有規避機動。為EnemyShip新添加一個腳本,命名為EvasiveManeuver,編輯腳本內容如下:

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

public class EvasiveManeuver : MonoBehaviour
{
    public Boundray boundray;
    public float tilt;
    public float dodge;
    public float smoothing;
    public Vector2 startWait;
    public Vector2 maneuverTime;
    public Vector2 maneuverWait;

    private float currentSpeed;
    private float targetManeuver;
    private Rigidbody rg;
    // Use this for initialization
    void Start ()
    {
        rg = GetComponent<Rigidbody>();
        currentSpeed = rg.velocity.z;
        StartCoroutine(Evade());
    }
    
    // Update is called once per frame
    void Update ()
    {
        
    }

    IEnumerator Evade()
    {
        yield return new WaitForSeconds(Random.Range(startWait.x,startWait.y));
        while(true)
        {
            targetManeuver = Random.Range(1, dodge) * -Mathf.Sign(transform.position.x);
            yield return new WaitForSeconds(Random.Range(maneuverTime.x,maneuverTime.y));
            targetManeuver = 0;
            yield return new WaitForSeconds(Random.Range(maneuverWait.x,maneuverWait.y));
        }
    }

    private void FixedUpdate()
    {
        float newManeuver = Mathf.MoveTowards(rg.velocity.x,targetManeuver,smoothing*Time.deltaTime);
        rg.velocity = new Vector3(newManeuver,0.0f, currentSpeed);
        rg.position = new Vector3(
            Mathf.Clamp(rg.position.x, boundray.xMin, boundray.xMax),
            0.0f,
            Mathf.Clamp(rg.position.z,boundray.zMin,boundray.zMax));
        rg.rotation = Quaternion.Euler(0.0f,0.0f,rg.velocity.x*-tilt);
    }
}

設置參數如下所示:

 

(7)、把敵機對象制作成Prefab。為了后續的使用,我們把EnemyShip的tag屬性宣威Enemy,然后在Prefabs->DonePrefabs文件夾下新建一個Prefab,重命名為EnemyShip,把EnemyShip拖拽到新建的Prefab上,完成Prefab的制作。

 

三、創建游戲控制器

完成了上述內容我們運行游戲發現小行星和敵機不會自動出現,所以我們需要建立一個腳本來控制敵人不間斷的出現。我們在Scripts文件夾下新建一個腳本文件,命名為GameController,把GameController腳本綁定到Main Camera上,編輯腳本內容如下:

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

public class GameController : MonoBehaviour
{
    public GameObject[] hazards;
    public Vector3 spawnValue;
    public long hazardCnt;
    public float spawnWait;
    public float startWait;
    public float waveWait;
    // Use this for initialization
    void Start ()
    {
        StartCoroutine(SpawnWaves());
    }
    
    // Update is called once per frame
    void Update ()
    {
        
    }

    IEnumerator SpawnWaves()
    {
        yield return new WaitForSeconds(startWait);
        while(true)
        {
            for (long i=0;i<hazardCnt;++i)
            {
                GameObject hazard = hazards[Random.Range(0,hazards.Length)];
                Vector3 spawnPosition = new Vector3(
                    Random.Range(-spawnValue.x,spawnValue.x),
                    spawnValue.y,
                    spawnValue.z);
                Quaternion spawnRotation = Quaternion.identity;
                Instantiate(hazard,spawnPosition,spawnRotation);
                yield return new WaitForSeconds(spawnWait);
            }
            yield return new WaitForSeconds(waveWait);
        }
    }
}

 

四、添加音效

 1、給我方飛船增加發射子彈音效。在Prefabs/DonePrefabs文件夾下找到Player預制體,選中Player預制體,在Inspector視圖底部點擊Add Component->Audio->Audio Source,將Audio文件夾下的weapon_player拖拽到Audio Source組件中的Audio Clip屬性上,去掉Play On Awake選項:

打開PlayController腳本文件,編輯腳本內容,使飛船在發射子彈時產生音效,修改內容如下:

2、給敵機增加發射子彈音效。在Prefabs/DonePrefabs文件夾下找到EnemyShip預制體,選中EnemyShip預制體,在Inspector視圖底部點擊Add Component->Audio->Audio Source,將Audio文件夾下的weapon_enemy拖拽到Audio Source組件中的Audio Clip屬性上,去掉Play On Awake選項:

打開WeaponController腳本文件,編輯腳本內容,使敵方飛船在發射子彈時產生音效,修改內容如下:

3、給小行星、我方飛船、敵方飛船添加爆炸音效。這三種音效不能像上述兩種那樣直接綁定到對象上,因為發生爆炸的時候對象直接被銷毀了,這個時候即使調用了Play()函數也無法播放聲音(因為對象已經銷毀,所以綁定在對象上的AudioSource組件也一起銷毀了)。我們需要把這個音效綁定在爆炸特效上,點擊explosion_asteroid預制體,為預制體添加AudioSource組件,把Audio文件夾下的explosion_asteroid音效文件拖拽到Audio Clip屬性上,注意Play On Awake屬性為勾選狀態:

我方飛船和敵方飛船爆炸音效按照同樣的方法制作。

4、加上背景音效。背景音效我們可以綁定到主相機上,也可以綁定到背景上面,這里我們選擇綁定到背景上。點擊BackGround,為BackGround添加Audio Source組件,並把music_background音效拖拽到Audio Clip屬性上,記得勾選Play On Awake和Loop選項:

 

五、對象回收

我們發現在我們的游戲運行的過程中,如果小行星和敵機沒有被擊毀並跑到了我們的視野之外,他們就會一直存在,另外子彈和爆炸效果產生的對象也不會自動銷毀,這樣我們的內存消耗就會越來越大。所以我們需要有對象回收機制。

對於跑出視野的小行星和敵機、發射的子彈我們采用的方法是創建一個Cube對象,當這些對象和Cube碰撞時銷毀這些對象。新建一個Cube,重命名為Boundray,去掉Cube(Mesh Filter)和Mesh Renderer組件,在Box Collider組件中選中Is Trigger選項,新建一個Tag,命名為Boundray,在Tag屬性中選中Boundray,最后為Boundray對象添加一個腳本,命名為DestroyByBoundray:

編輯DestroyByBoundray腳本內容如下:

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

public class DestroyByBoundray : MonoBehaviour
{

    // Use this for initialization
    void Start ()
    {
        
    }
    
    // Update is called once per frame
    void Update ()
    {
        
    }

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

    }
}

讓與Boundray對象發生碰撞的游戲對象直接銷毀。(這里有個小錯誤:在我們制做小行星預制體時,我們只給prop_asteroid_01、prop_asteroid_02和prop_asteroid_03模型綁定了膠囊碰撞器,而沒有給它們的父對象Asteroid_01、Asteroid_02和Asteroid_03綁定碰撞器,那么在這里我們直接Destroy(other.gameObject),只是把子對象銷毀了,父對象還一直存在,所以我們還要給父對象綁定一個碰撞器。其實我們只需要給父對象綁定一個碰撞器就可以了,如果兩者都綁定了碰撞器,而prop_asteroid_01、prop_asteroid_02和prop_asteroid_03的Tag屬性又沒有選擇為Enemy,那么小行星會被敵機的子彈打中,原因是當發生碰撞時,DestroyByContent腳本中的OnTriggerEnter會觸發兩次,分別是父對象觸發一次,子對象觸發一次,這樣子對象就會被銷毀,而只剩下一個父對象。)

在DestroyByContent腳本中我們編寫了與小行星或敵方飛船發生碰撞的對象都會被銷毀,但是Boundray對象是不能銷毀的,所以我們需要修改DestroyByContent腳本的OnTriggerEnter函數:

    private void OnTriggerEnter(Collider other)
    {
        if(other.tag=="Enemy" || other.tag == "Boundray")
        {
            return;
        }
        if(explosion)
        {
            Instantiate(explosion, transform.position, transform.rotation);
        }
        if(other.tag=="Player" && explosion_player)
        {
            Instantiate(explosion_player, transform.position, transform.rotation);//產生一個特效
        }
        
        Destroy(other.gameObject);
        Destroy(gameObject);
    }

對於爆炸效果的銷毀,我們采取延時一段時間后銷毀對象。首先我們新建一個腳本,命名為DestroyByTime,編輯內容如下:

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

public class DestroyByTime : MonoBehaviour
{
    public float lifeTime;
    // Use this for initialization
    void Start ()
    {
        Destroy(gameObject,lifeTime);
    }
    
    // Update is called once per frame
    void Update ()
    {
        
    }
}

在Prefabs/VFX/Explosions文件夾下找到explosion_asteroid、explosion_enemy和explosion_player三個爆炸特效預制體,把這三個預制體都綁定DestroyByTime腳本,並把lifeTime設置為2秒,這樣爆炸產生的特效對象會在2秒后銷毀。

 

六、創建計分板

建立一個Text用來記錄分數。在Hierarchy視圖中右鍵->UI->Text,創建一個Text,重命名為ScoreText,顏色選擇為紅色。在創建Text的時候系統會自動建立一個畫布,按照同樣的辦法我們再創建兩個Text用來提示游戲結束和重新開始,調整Text的位置,如下所示:

為了實現計分功能,我們需要修改GameController腳本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class GameController : MonoBehaviour
{
    public GameObject[] hazards;
    public Vector3 spawnValue;
    public long hazardCnt;
    public float spawnWait;
    public float startWait;
    public float waveWait;

    public Text scoreText;
    public Text gameOverText;
    public Text restartText;
    private int score;
    private bool gameOver;
    private bool restart;
    // Use this for initialization
    void Start ()
    {
        StartCoroutine(SpawnWaves());
        gameOverText.text = "";
        restartText.text = "";
        UpdateScore();
        score = 0;
        gameOver = false;
        restart = false;
    }
    
    // Update is called once per frame
    void Update ()
    {
        if(restart)
        {
            if(Input.GetKeyDown(KeyCode.R))
            {
                SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
            }
        }
    }

    IEnumerator SpawnWaves()
    {
        yield return new WaitForSeconds(startWait);
        while(true)
        {
            for (long i=0;i<hazardCnt;++i)
            {
                GameObject hazard = hazards[Random.Range(0,hazards.Length)];
                Vector3 spawnPosition = new Vector3(
                    Random.Range(-spawnValue.x,spawnValue.x),
                    spawnValue.y,
                    spawnValue.z);
                Quaternion spawnRotation = Quaternion.identity;
                Instantiate(hazard,spawnPosition,spawnRotation);
                yield return new WaitForSeconds(spawnWait);
            }
            yield return new WaitForSeconds(waveWait);

            if(gameOver)
            {
                restart = true;
                restartText.text = "Press 'R' for Restart.";
                break;
            }
        }
    }

    private void UpdateScore()
    {
        scoreText.text = "Score:" + score.ToString();
    }

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

    public void GameOver()
    {
        gameOver = true;
        gameOverText.text = "Game Over!";
    }
}

修改DestroyByContent腳本內容如下:

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

public class DestroyByContact : MonoBehaviour
{
    public GameObject explosion;
    public GameObject explosion_player;

    public int scoreValue;
    private GameController gameController;
    // Use this for initialization
    void Start ()
    {
        GameObject gameControllerObj = GameObject.FindGameObjectWithTag("GameController");
        if(gameControllerObj)
        {
            gameController = gameControllerObj.GetComponent<GameController>();
        }
        if(!gameController)
        {
            Debug.Log("Can not Find 'GameController' script.");
        }
    }
    
    // Update is called once per frame
    void Update ()
    {
        
    }

    private void OnTriggerEnter(Collider other)
    {
        if(other.tag=="Enemy" || other.tag == "Boundray")
        {
            return;
        }
        if(explosion)
        {
            Instantiate(explosion, transform.position, transform.rotation);
        }
        if(other.tag=="Player" && explosion_player)
        {
            Instantiate(explosion_player, transform.position, transform.rotation);//產生一個特效
            gameController.GameOver();
        }
        gameController.AddScore(scoreValue);
        Destroy(other.gameObject);
        Destroy(gameObject);
    }
}

把預制體Asteroid_01、Asteroid_02和Asteroid_03的Score Value屬性值設置為10,把預制體EnemyShip的Score Value屬性值設置為20,點擊Main Camera找到GameController腳本組件,分別將Hierarchy視圖中的ScoreText、GameOverText和RestartText拖拽到Score Text、Game Over Text和Restart Text屬性上,並把Main Camera的Tag屬性設置為GameController:

至此游戲制作基本完成。


免責聲明!

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



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