EasyAR實(shí)踐開(kāi)發(fā)案列之太空戰(zhàn)機(jī)游戲


原創(chuàng)文章,請(qǐng)勿轉(zhuǎn)載。感謝??!

適合人群

必須會(huì)Unity3D引擎基礎(chǔ),對(duì)C#腳本編程有一定經(jīng)驗(yàn)


使用插件

本案列使用前還必須導(dǎo)入以下這些插件
DoTween
優(yōu)秀的動(dòng)畫(huà)插件
FairyGUI
本案例用的是FairyGUI系統(tǒng),基礎(chǔ)知識(shí)可以到FairyGUI官網(wǎng)學(xué)習(xí),當(dāng)然你可以將UI改造為UGUI系統(tǒng)。


游戲介紹

太空戰(zhàn)機(jī)是一款通過(guò)識(shí)別對(duì)應(yīng)的AR游戲卡片結(jié)合實(shí)景的敏捷躲避AR游戲,游戲采用UNITY3D引擎和EasyAR聯(lián)合開(kāi)發(fā)。游戲主要操作方法是利用手機(jī)重力感應(yīng)來(lái)控制飛機(jī)左右移動(dòng)躲避障礙物,并且有各種各樣的道具輔助玩家獲得高分?jǐn)?shù)排名。


游戲前期策劃

游戲初期策劃是占一個(gè)重要成分,前期策劃做得好,后期游戲的制作流程會(huì)更加明了,我們可以先坐下來(lái),用筆或紙記錄自己的一點(diǎn)一點(diǎn)的創(chuàng)意,最后整理集合成一個(gè)游戲策劃方案。
對(duì)于太空戰(zhàn)機(jī)這個(gè)游戲,由于玩法比較簡(jiǎn)單,所以策劃方案也不會(huì)太復(fù)雜,游戲策劃大概列出下面這幾點(diǎn):

  • 利用EasyAR識(shí)別圖片后,出現(xiàn)開(kāi)始界面,并且出現(xiàn)飛機(jī),此時(shí)飛機(jī)不可以被玩家控制
  • 點(diǎn)擊開(kāi)始按鈕后進(jìn)入倒數(shù)界面,倒數(shù)5秒,出現(xiàn)游戲界面,此時(shí)飛機(jī)可以被玩家控制
  • 用重力感應(yīng)控制飛機(jī)左右移動(dòng)
  • 圓環(huán)和道具從遠(yuǎn)處不斷向飛機(jī)直線移動(dòng),碰到圓環(huán)損失1血,從圓環(huán)中心通過(guò),獎(jiǎng)勵(lì)1分
  • 道具分別是能量包和隕石,能量包用于恢復(fù)生命,隕石碰撞到后損失2血
  • 當(dāng)血量為0時(shí),游戲結(jié)束,出現(xiàn)排行版

當(dāng)然這個(gè)初期策劃方案的功能并不是十分完善,例如后期會(huì)增加飛機(jī)選擇界面,增加購(gòu)買飛機(jī)商城,增加更多的道具,這些需由你自己去完4善策劃內(nèi)容,這樣你會(huì)從中學(xué)習(xí)到更多的策劃經(jīng)驗(yàn)。


游戲素材準(zhǔn)備

通常我們做游戲前,都需要準(zhǔn)備制作該游戲的一些美術(shù)資源,音效資源,如果平時(shí)有收集整理自己的素材庫(kù),那素材的問(wèn)題很快可以解決,如果平時(shí)沒(méi)有這個(gè)習(xí)慣,可以到UNITY官方資源商店購(gòu)買下載合適的素材,本案例中的資源包都會(huì)通過(guò)百度盤共享,可以直接拿來(lái)使用。


申請(qǐng)AppKey

我們首先要到EasyAR官網(wǎng)注冊(cè)賬號(hào),然后進(jìn)入開(kāi)發(fā)中心,創(chuàng)建一個(gè)新應(yīng)用,應(yīng)用名為ARSpaceShip,注意Bundle ID(iOS) Package Name(Android)這一列,其格式通常為com.公司名.項(xiàng)目名,所以我們這里按照自己的實(shí)際需求來(lái)填寫(xiě),本案例填寫(xiě)為com.game4u.arspaceship,最后點(diǎn)擊顯示按鈕,出現(xiàn)AppKey。

申請(qǐng)AppKey


如何制作識(shí)別度高的識(shí)別圖

識(shí)別圖如果識(shí)別度比較高,那么識(shí)別后,模型相對(duì)會(huì)穩(wěn)定,并且不會(huì)出現(xiàn)抖動(dòng)現(xiàn)象,那么我們應(yīng)該怎樣制作識(shí)別度高得識(shí)別圖呢?以下我總結(jié)出來(lái)幾點(diǎn)經(jīng)驗(yàn):

  • 避免重復(fù)元素大量出現(xiàn):大量圓圈、方形、菱形或者其他相同的形狀,容易出現(xiàn)識(shí)別錯(cuò)誤
  • 避免圖案元素分布不均勻:圖案有區(qū)域出現(xiàn)大片空白、單一顏色或者分布不均勻,會(huì)導(dǎo)致識(shí)別錯(cuò)誤
  • 避免圖案元素過(guò)于簡(jiǎn)單:識(shí)別跟蹤更喜歡“雜亂無(wú)章”的圖案,太簡(jiǎn)單的圖案,有可能會(huì)導(dǎo)致掃描錯(cuò)誤
  • 避免圖案全是文字:圖案全部都是文字,掃描識(shí)別圖有可能會(huì)出現(xiàn)識(shí)別錯(cuò)誤
  • 避免圖案明暗對(duì)比度不明顯:如果圖案全是亮色或者暗色,有可能會(huì)導(dǎo)致識(shí)別錯(cuò)誤
  • 避免圖案模糊:圖案需要清晰的細(xì)節(jié)才能掃描,如果細(xì)節(jié)很模糊,識(shí)別圖有可能掃描出錯(cuò)
  • 避免圖案高度反光: :如果圖案反光嚴(yán)重,很容易導(dǎo)致識(shí)別圖掃描出錯(cuò)
  • 避免圖案上印有二維碼:請(qǐng)將二維碼區(qū)域設(shè)置為空白。當(dāng)識(shí)別圖提供給用戶掃描的時(shí)候,可以在空白區(qū)域補(bǔ)充二維碼,不影響識(shí)別結(jié)果
  • 避免使用png格式圖

同樣,本案例的識(shí)別圖可以在資源包里找到。


制作識(shí)別圖數(shù)據(jù)

我們先來(lái)制作一下數(shù)據(jù)文件。這個(gè)數(shù)據(jù)文件是一個(gè)json數(shù)據(jù),我們輸入以下內(nèi)容,并保存文件為targets.json。

{"images" :
  [
    {
      "image" : "arspaceship.jpg",
      "name" : "arspaceship"
    }
  ]
}```
****
#游戲框架初步搭建
素材準(zhǔn)備好了后,我們開(kāi)始制作太空戰(zhàn)機(jī)AR游戲,打開(kāi)Unity引擎并創(chuàng)建一個(gè)新項(xiàng)目,首先將下載好的EasyAR的unity package導(dǎo)入到項(xiàng)目里,如圖
![導(dǎo)入EasyAR unity package](http://upload-images.jianshu.io/upload_images/3353184-8b068541ebb29051.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

導(dǎo)入完成后,我們右鍵Asset文件夾,新建一個(gè)自己的游戲文件夾,命名為ARSpaceShip,在這個(gè)文件夾里再分別建立Scene(用于放置場(chǎng)景文件),Scripts(用于放置腳本),Modles(用于放置模型),Textures(用于放置貼圖),Materials(用于放置材質(zhì)),Prefabs(用于放置預(yù)置物)。
如果項(xiàng)目里沒(méi)有StreamingAssets和Resources文件夾,我們也應(yīng)該右鍵新建此文件夾,如圖
![建立文件夾](http://upload-images.jianshu.io/upload_images/3353184-335eb2b33ecb7823.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

現(xiàn)在我們?cè)赟cene文件夾里新建一個(gè)場(chǎng)景,命名為GameScene,雙擊打開(kāi)該場(chǎng)景,刪除掉里面的MainCamera和Diretorly Light,找到EasyAR的Prefab目錄,將EasyAR_Startup組件拖到場(chǎng)景里,然后填寫(xiě)在官網(wǎng)里注冊(cè)的AppKey,如圖
![填寫(xiě)AppKey](http://upload-images.jianshu.io/upload_images/3353184-cf736ee08088efdf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
此時(shí)我們還需修改一下EasyAR_Startup里面的RenderCamera相機(jī)組件的視野屬性,將Clipping Planes的Near和Far分別修改為 0.01和2000,如下圖:

![RenderCamera屬性](http://upload-images.jianshu.io/upload_images/3353184-2f4eba3c7c6da899.png)

我們將識(shí)別圖和target.json一起放到StreamingAssets文件夾里,EasyAR會(huì)通過(guò)腳本訪問(wèn)此文件夾獲得資源和數(shù)據(jù)。接下來(lái)同樣在EasyAR的Prefab目錄下的Primitives里將ImageTarget組件拖到場(chǎng)景里,并且將ImageTargetBehaviour腳本移除。
![5.png](http://upload-images.jianshu.io/upload_images/3353184-f72b8da11a91a2c9.png)

然后在我們的項(xiàng)目文件夾的Scripts目錄右鍵,新建一個(gè)EasyImageTargetBehaviour腳本,輸入以下代碼:

using UnityEngine;

namespace EasyAR
{
public class EasyImageTargetBehaviour : ImageTargetBehaviour
{
protected override void Awake()
{
base.Awake();
TargetFound += OnTargetFound;
TargetLost += OnTargetLost;
}

    protected override void Start()
    {
        base.Start();
        HideObjects(transform);
    }

    void HideObjects(Transform trans)
    {
        for (int i = 0; i < trans.childCount; ++i)
            HideObjects(trans.GetChild(i));
        if (transform != trans)
            gameObject.SetActive(false);
    }

    void ShowObjects(Transform trans)
    {
        for (int i = 0; i < trans.childCount; ++i)
            ShowObjects(trans.GetChild(i));
        if (transform != trans)
            gameObject.SetActive(true);
    }

    void OnTargetFound(ImageTargetBaseBehaviour behaviour)
    {
        ShowObjects(transform);
    }

    void OnTargetLost(ImageTargetBaseBehaviour behaviour)
    {
        HideObjects(transform);
    }
}

}

把這段代碼拖到ImageTarget中,這段代碼的作用是EasyAR如果識(shí)別到圖片后,控制ImageTarget里面的所有子物體的顯示和隱藏。然后填寫(xiě)一下ImageTarget屬性面版里面的一些屬性,注意Storage需要選擇為Asset模式,如圖:
![輸入識(shí)別圖信息](http://upload-images.jianshu.io/upload_images/3353184-d059e9bd65273eb6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

**Storages說(shuō)明**
>**App**
app路徑,對(duì)于不同設(shè)備會(huì)有不同路徑。
Android: 程序持久化數(shù)據(jù)目錄(注意,這個(gè)目錄與Unity的Application.persistentDataPath可能不同)
iOS: 程序沙盒目錄
Windows: 可執(zhí)行文件(exe)目錄
Mac: 可執(zhí)行文件目錄(如果app是一個(gè)bundle,這個(gè)目錄在bundle內(nèi)部)

>**Assets**
StreamingAssets路徑

>**Absolute**
絕對(duì)路徑(json/圖片路徑或視頻文件路徑)或url(僅視頻文件)

>**Json**
表示json string的標(biāo)志位,在Target.Load中使用

大家有沒(méi)發(fā)現(xiàn)上面的ImageTarget沒(méi)有材質(zhì),不太好看,那么我們可以在自己項(xiàng)目文件夾里的Matertials文件夾右鍵新建一個(gè)材質(zhì)ImageTargetMat,然后拖到ImageTarget的MeshRenderer組件的Materials里面,改變材質(zhì)為L(zhǎng)egacy Shaders/Self-Illumin/Diffuse,把識(shí)別圖復(fù)制一份到Textures文件夾里拖到材質(zhì)里面,如圖:
![改變ImageTarget材質(zhì)](http://upload-images.jianshu.io/upload_images/3353184-389f13a88fbbbad5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

最后,我們?cè)贗mageTarget里建立一個(gè)空的GameObject,命名為GameWorld,此時(shí)游戲初步框架完成。

![建立GameWorld](http://upload-images.jianshu.io/upload_images/3353184-4ccb4e200f0173b5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
****
#制作游戲地面
在GameWorld里新建一個(gè)GameObject命名為Map,然后在里面新建一個(gè)Quad物體命名為Ground,調(diào)整他的Rotation X軸為90,目的是讓它平放。我們?cè)僭贛aterials文件夾里新建一個(gè)GroundMat材質(zhì),將這個(gè)材質(zhì)拖動(dòng)到Ground物體,最后將識(shí)別圖作為貼圖賦值給材質(zhì),并調(diào)整合適大小,如圖:
![制作游戲地面](http://upload-images.jianshu.io/upload_images/3353184-88b533980c89fcf0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
****
#制作太空戰(zhàn)機(jī)
在GameWorld里新建一個(gè)GameObject命名為SpaceShipCotroller,并設(shè)置tag為Player,然后在素材包里面找到太空戰(zhàn)機(jī)的模型的文件夾,將它復(fù)制到Modles文件夾里,將模型文件拖動(dòng)到SpaceShipCotroller里作為子物體并將模型的Scale調(diào)為0.008,0.008,0.008,如圖:

![飛機(jī)模型](http://upload-images.jianshu.io/upload_images/3353184-f4a1dfde696b34c9.png)
此時(shí)如果沒(méi)有材質(zhì),可以將材質(zhì)重新拖到飛機(jī)模型上關(guān)聯(lián)即可。我們?cè)僬{(diào)整一下SpaceShipController的物體的Y軸位置為0.13,Z軸位置為-0.28,如圖:
![調(diào)整飛機(jī)大小位置](http://upload-images.jianshu.io/upload_images/3353184-bc747c92e1faf963.png)

在Scripts文件夾里新建一個(gè)腳本,命名為SpaceShipController,拖動(dòng)到SpaceShipController物體處,輸入以下代碼:

using System;
using UnityEngine;

public class SpaceShipController : MonoBehaviour
{
//移動(dòng)速度
public float Speed = 20f;
//方向
private Vector3 _dir = Vector3.zero;

void Start()
{
}

void Update()
{
    Move();
}

/// <summary>
/// 飛機(jī)移動(dòng)邏輯
/// </summary>
void Move()
{
    _dir.x = Input.acceleration.x;
    if (_dir.sqrMagnitude > 1) _dir.Normalize();
    transform.Translate(Time.deltaTime * _dir * Speed);
}

}


這段代碼的作用是用來(lái)控制太空戰(zhàn)機(jī)。我們策劃的時(shí)候是要通過(guò)手機(jī)重力感應(yīng)來(lái)控制戰(zhàn)機(jī)左右移動(dòng),UnityAPI提供了移動(dòng)設(shè)備重力感應(yīng)方法`Input.acceleration`,只需要通過(guò)x值來(lái)判斷左右移動(dòng)。我們還需要在SpaceShipController的屬性面板中輸入Speed的值,并且調(diào)試一下是否是自己想要的戰(zhàn)機(jī)移動(dòng)速度,本案例的速度填寫(xiě)為500,然后在手機(jī)上發(fā)布看看實(shí)際效果。此時(shí)你掃描識(shí)別圖后,左右搖動(dòng)手機(jī),戰(zhàn)機(jī)也跟著左右移動(dòng)了。
現(xiàn)在看起來(lái)戰(zhàn)機(jī)移動(dòng)沒(méi)問(wèn)題,但是給人感覺(jué)有點(diǎn)生硬,需要加一個(gè)左右移動(dòng)的時(shí)候機(jī)身會(huì)跟著側(cè)飛,那么我們選中場(chǎng)景中的SpaceShipController物體,右鍵新建一個(gè)GameObject命名為RotateObject,然后把我們的模型拖到RotateObject里作為子物體,如下圖:
![RotateObject](http://upload-images.jianshu.io/upload_images/3353184-374aab3aee1d63b1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

雙擊SpaceController腳本,給它增加兩個(gè)屬性:

//旋轉(zhuǎn)速度
public float RotateSpeed = 20f;
//需要旋轉(zhuǎn)的物體
public Transform RotateObject;

然后增加多一個(gè)側(cè)飛的方法:

/// <summary>
/// 側(cè)飛
/// </summary>
void Rotate()
{
//根據(jù)方向判斷是左旋轉(zhuǎn)還是右旋轉(zhuǎn)
if (_dir.x < 0)
{
RotateObject.localRotation = Quaternion.Lerp(RotateObject.localRotation,Quaternion.Euler(new Vector3(0, 0, 30f)), Time.deltaTime * RotateSpeed);
}
else
{
RotateObject.localRotation = Quaternion.Lerp(RotateObject.localRotation,Quaternion.Euler(new Vector3(0, 0, -30f)),Time.deltaTime * RotateSpeed);
}
}

然后在Update方法處,添加Rotate方法:

void Update()
{
Move();
Rotate();
}


最終的SpaceController腳本應(yīng)該如下:

using System;
using UnityEngine;

public class SpaceShipController : MonoBehaviour
{
//移動(dòng)速度
public float Speed = 20f;
//方向
private Vector3 _dir = Vector3.zero;
//旋轉(zhuǎn)速度
public float RotateSpeed = 20f;
//需要旋轉(zhuǎn)的物體
public Transform RotateObject;

void Start()
{
}

void Update()
{
    Move();
    Rotate();
}

/// <summary>
/// 飛機(jī)移動(dòng)邏輯
/// </summary>
void Move()
{
    _dir.x = Input.acceleration.x;
    if (_dir.sqrMagnitude > 1) _dir.Normalize();
    transform.Translate(Time.deltaTime * _dir * Speed);
}

/// <summary>
/// 側(cè)飛
/// </summary>
void Rotate()
{
    if (_dir.x < 0)
    {
        RotateObject.localRotation = Quaternion.Lerp(RotateObject.localRotation, Quaternion.Euler(new Vector3(0, 0, 30f)),
            Time.deltaTime * RotateSpeed);
    }
    else
    {
        RotateObject.localRotation = Quaternion.Lerp(RotateObject.localRotation, Quaternion.Euler(new Vector3(0, 0, -30f)),
            Time.deltaTime * RotateSpeed);
    }
}

}

寫(xiě)完代碼后,再次點(diǎn)擊場(chǎng)景中的SpaceShipController物體,會(huì)發(fā)現(xiàn)多了RotateSpeed和RotateObject屬性,先設(shè)置RotateSpeed為2,再將場(chǎng)景中的RotateObject拖動(dòng)到RotateObject屬性l里,如圖:
![設(shè)置屬性](http://upload-images.jianshu.io/upload_images/3353184-6d8e6e4a247ff9a9.png)

現(xiàn)在飛機(jī)看起來(lái)沒(méi)有倒影顯得不是那么真實(shí),我們可以再素材庫(kù)將SpaceShipShadow.png復(fù)制到Textures文件夾下,在場(chǎng)景中的SpaceShipController物體右鍵新建一個(gè)Quad物體命名為Shadow,去掉組件MeshCollider,在Materials文件夾中新一個(gè)SpaceShipShadow材質(zhì),將SpaceShipShadow.png拖到材質(zhì)里然后再設(shè)置為透明材質(zhì),設(shè)置完成后將材質(zhì)關(guān)聯(lián)給場(chǎng)景中Shadow物體,最后調(diào)整Shadow物體的Position Y軸為-0.2, Rotation X軸為90度,Scale為0.16,0.16,0.16,效果如下圖:
![調(diào)整效果](http://upload-images.jianshu.io/upload_images/3353184-f3d41697a4b26de0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

飛機(jī)飛行的時(shí)候還需要有個(gè)噴射效果,在Modles文件夾的SpaceShip文件夾找到SpaceShipEngines的模型,拖動(dòng)到場(chǎng)景中的SpaceShip模型里面,設(shè)置好大小和材質(zhì)關(guān)聯(lián),效果如圖:
![噴射效果制作](http://upload-images.jianshu.io/upload_images/3353184-4de7afc8fe8b0c6e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

現(xiàn)在讓我們用DoTween來(lái)模擬飛行噴射粒子動(dòng)畫(huà),在場(chǎng)景中的SpaceShip里找到Ship_1_Engines 1物體,添加DoTweenAnimation組件,輸入合適的參數(shù),如下圖:
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/3353184-803953d748f6bdcd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

最后我們?cè)赑refab文件夾下新建一個(gè)Prefab命名為SpaceShipController,將場(chǎng)景里的SpaceShipController拖動(dòng)到這個(gè)預(yù)置物完成飛機(jī)的制作。
****
#制作圓環(huán)敵人
在GameWorld這個(gè)GameObject里,新建一個(gè)GameObject命名為RingController,然后在素材包里面找到圓環(huán)的模型,拖動(dòng)到RingEnemy里作為子物體調(diào)整好合適的大小,將Rotation的Y值變?yōu)?80度,然后在Scripts文件夾里新建一個(gè)腳本命名為RingController,拖動(dòng)到RingController物體處,輸入以下代碼:

using UnityEngine;
using System.Collections;

public class RingController : MonoBehaviour
{
//移動(dòng)速度
public float Speed = 20f;

void Start()
{
}

void Update()
{
    Move();
}

/// <summary>
/// 移動(dòng)邏輯
/// </summary>
void Move()
{
    transform.Translate(Vector3.forward * Speed * Time.deltaTime);
    if (transform.localPosition.z < -1f)
    {
        Destroy(gameObject);
    }
}

}```

這段代碼的作用是用來(lái)控制圓環(huán)的,程序會(huì)控制它不斷向前移動(dòng),如圖:


圓環(huán)屬性

最后在Prefab文件夾下,新建一個(gè)Prefab,命名為RingController,將場(chǎng)景里的RingController拖動(dòng)到這個(gè)預(yù)置物,刪掉場(chǎng)景中的RingController,后面我們要?jiǎng)討B(tài)創(chuàng)建圓環(huán)。


完善游戲框架

制作好游戲模型的預(yù)制物后,我們開(kāi)始通過(guò)程序動(dòng)態(tài)創(chuàng)建圓環(huán)。在Scripts文件夾右鍵,新建SimpleSpawn腳本,輸入以下代碼:

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

public class SimpleSpwan : MonoBehaviour
{
    //敵人Prefab列表
    public List<GameObject> monsterList = new List<GameObject>();
    //敵人生成的Delegate事件
    public delegate void CreateObjectDelegate(GameObject spawnObject);
    public CreateObjectDelegate CreateObjectEvent;
    //生成的間隔時(shí)間
    public float spawnTimer;
    //生成的最大數(shù)量
    public int limitSpawn = 1;

    //記錄復(fù)制出來(lái)的敵人列表
    private GameObject[] spawnList = new GameObject[] { };
    //計(jì)數(shù)生成數(shù)量
    private int coutnSpawn = 0;
    //容器
    private Transform _container;

    void Start()
    {
        spawnList = new GameObject[limitSpawn];
        _container = transform;
    }

    public void DoSpawn()
    {
        InvokeRepeating("Spawn", 0.5f, spawnTimer);
    }

    /// <summary>
    /// 生成怪物
    /// </summary>
    public void Spawn()
    {
        for (int i = 0; i < spawnList.Length; i++)
        {
            if (spawnList[i] == null)
            {
                GameObject monsterSpawn = (GameObject)GameObject.Instantiate(monsterList[Random.Range(0, monsterList.Count)]);
                monsterSpawn.transform.SetParent(_container, false);
                SpwanObject ss = monsterSpawn.AddComponent<SpwanObject>();
                ss.spawnerOwner = gameObject;
                spawnList[i] = monsterSpawn;
                coutnSpawn++;
                if (coutnSpawn >= limitSpawn)
                {
                    //生成敵人超過(guò)指定數(shù)量,停止執(zhí)行函數(shù)
                    CancelInvoke("Spawn");
                }

                if (CreateObjectEvent != null)
                {
                    CreateObjectEvent(monsterSpawn);
                }
                return;
            }

        }
    }

    /// <summary>
    /// 偵聽(tīng)物體Destroy事件
    /// </summary>
    /// <param name="spwanObject"></param>
    public void OnSpawnerObjectDestory(GameObject spwanObject)
    {
        for (int i = 0; i < spawnList.Length; i++)
        {
            if (spawnList[i] == spwanObject)
            {
                spawnList[i] = null;
                //敵人死亡后,通知事件,如果已經(jīng)是最大值,那么就回復(fù)時(shí)間繼續(xù)生成
                if (coutnSpawn >= limitSpawn)
                {
                    Debug.Log("less then limitspawn , restart Invoke");
                    InvokeRepeating("Spawn", spawnTimer, spawnTimer);
                }
                coutnSpawn--;
                return;
            }
        }
    }

    /// <summary>
    /// 停止所有生成并清空
    /// </summary>
    public void DespawnAllAndStop()
    {
        for (int i = 0; i < spawnList.Length; i++)
        {
            if (spawnList[i] != null)
            {
                Destroy(spawnList[i]);
                spawnList[i] = null;

            }
            coutnSpawn = 0;
            CancelInvoke("Spawn");

        }
    }

    /// <summary>
    /// 暫停生成
    /// </summary>
    public void Stop()
    {
        coutnSpawn = 0;
        CancelInvoke("Spawn");
    }
}

這段代碼作用是通過(guò)計(jì)時(shí)器控制程序每隔指定秒數(shù)生成一個(gè)圓環(huán)物體。核心代碼在public void Spawn(),通過(guò)檢測(cè)spawnList的元素是否有數(shù)據(jù),如果沒(méi)有數(shù)據(jù)就開(kāi)始生成一個(gè)新的敵人。

接著繼續(xù)在Scripts文件夾右鍵,新建SimpleSpawnObject腳本,輸入以下代碼:

using UnityEngine;
using System.Collections;

public class SpwanObject : MonoBehaviour
{
    //物體所有者
    public GameObject spawnerOwner;

    void OnDestroy()
    {
        //物體Destroy后發(fā)送事件給SimpleSpwan
        if (spawnerOwner != null)
        {
            spawnerOwner.SendMessage("OnSpawnerObjectDestory", gameObject, SendMessageOptions.DontRequireReceiver);
        }
    }
}```

這段代碼是在物體Destory后,發(fā)送消息通知SimpleSpawn腳本刪除該物體的引用。

最后,我們將SimpleSpawn掛到ImageTarget里的GameWorld物體里,給SimpleSpawn的monsterList的size改為1,然后將RingController預(yù)置物拖到屬性里,將SpawnTimer設(shè)置為3秒,LimitSpawn設(shè)置為5,如圖:

![設(shè)置SimpleSpawn屬性](http://upload-images.jianshu.io/upload_images/3353184-a73a4c863a783077.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

那么我們?nèi)绾慰刂七@個(gè)SimpleSpawn生成物體呢?在場(chǎng)景里右鍵建立一個(gè)GameObject命名為Controllers,然后在Controllers右鍵再建立一個(gè)GameObject命名為GameController。然后再Scripts文件夾新建一個(gè)GameController腳本,拖動(dòng)到場(chǎng)景里的GameController物體處,輸入以下代碼:

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

public class GameController : MonoBehaviour
{
//飛機(jī)控制器
public SpaceShipController _ShipController;
//生成物體系統(tǒng)
public SimpleSpwan SpwanSystem;
//物體的線路位置
public List<float> ItemPostion = new List<float>();

void Start()
{
    Init();
}

/// <summary>
/// 游戲初始化
/// </summary>
void Init()
{
    SpwanSystem.DoSpawn();
    //SpwanSystem每生成一個(gè)物體,都會(huì)發(fā)送通知,并且返回這個(gè)物體
    SpwanSystem.CreateObjectEvent = (GameObject spawnObject) =>
    {
        Vector3 newpos = spawnObject.transform.localPosition;
        newpos.x = ItemPostion[Random.Range(0, ItemPostion.Count)];
        newpos.z = 1.2f;
        spawnObject.transform.localPosition = newpos;
    };
}

}

我們?cè)趯傩悦姘逄帉paceShipController拖到SpaceShipController屬性里,將GameWorld物體拖動(dòng)到SpawnSystem屬性里,設(shè)置ItemPostion的值為3,如圖:
![設(shè)置GameController屬性](http://upload-images.jianshu.io/upload_images/3353184-31c948c6ed52f382.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

發(fā)布到手機(jī)運(yùn)行測(cè)試后,你會(huì)發(fā)現(xiàn)圓環(huán)會(huì)在前方復(fù)制,并且不停移動(dòng),但是圓環(huán)并不會(huì)自動(dòng)消失,因?yàn)槲覀冞€沒(méi)有給圓環(huán)限制一個(gè)范圍。雙擊RingController腳本,我們改寫(xiě)一下Move方法,給Move方法增加一個(gè)移動(dòng)判斷,代碼如下:

/// <summary>
/// 移動(dòng)邏輯
/// </summary>
void Move()
{
transform.Translate(Vector3.forward * Speed * Time.deltaTime);
if (transform.localPosition.z < -1f)
{
Destroy(gameObject);
}
}

這段代碼的作用是當(dāng)圓環(huán)的Z軸到-1后Destory自身,在手機(jī)上發(fā)布我們會(huì)看到,圓環(huán)移動(dòng)到Z軸為-1的位置后消失了,并且SimpleSpawn會(huì)重新生成新的圓環(huán)。
****
#添加碰撞
**飛機(jī)增加剛體**
給場(chǎng)景中的SpaceShipController物體增加BoxCollider組件和Rigidbody組件,Rigidbody組件不需要使用重力,參數(shù)調(diào)整如下圖:
![設(shè)置碰撞屬性](http://upload-images.jianshu.io/upload_images/3353184-1e7e9b1e2bec1198.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)將剛體調(diào)整到只有機(jī)身部分點(diǎn)擊Apply按鈕即可,當(dāng)然你可以擴(kuò)大剛體范圍,不過(guò)這樣游戲會(huì)增大難度。

**圓環(huán)增加剛體**
將Prefabs文件夾的RingController物體拖動(dòng)到GameWorld里,給圓環(huán)整體增加一個(gè)BoxCollider組件,參數(shù)調(diào)整如下圖:
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/3353184-cb4663877502ed83.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

考慮到飛機(jī)可以從圓環(huán)中間穿過(guò),所以我們需要一點(diǎn)技巧來(lái)制作多一個(gè)剛體,在RingController右鍵建立一個(gè)新的GameObject命名為CrossCollider,增加一個(gè)BoxCollider組件,移動(dòng)這個(gè)BoxCollider的位置,讓這個(gè)BoxCollider比圓環(huán)的BoxCollider更加靠前,這樣做的目的是當(dāng)飛機(jī)碰撞到CrossCollider物體后,取消圓環(huán)的剛體碰撞,讓飛機(jī)可以順利穿越,具體參數(shù)調(diào)整如下圖:
![CrossCollider](http://upload-images.jianshu.io/upload_images/3353184-1826228c7539711a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

最終效果如下圖:

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/3353184-f67773ac880545be.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
****
#未完待續(xù)占坑
#
#
#
#
#
#
#
#
****
#制作游戲UI
#發(fā)布游戲
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容