天天看點

【Unity】捕魚達人開發流程

核心功能:炮台的控制、炮台發射子彈、魚群孵化器和魚群行為。

GunFollow.cs腳本:(炮台的控制)

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

public class GunFollow : MonoBehaviour {

    public RectTransform UGUICanvas;//
    public Camera mainCamera;
	
	void Update () {
        //用于儲存遊戲中的滑鼠坐标
        Vector3 mousePosition;
        //擷取滑鼠坐标 通過RectTransformUtility.ScreenPointToWorldPointInRectangle(UGUI區域(某層),遊戲界面滑鼠的二維坐标,所在錄影機,輸出遊戲世界中的滑鼠位置
        RectTransformUtility.ScreenPointToWorldPointInRectangle(UGUICanvas, new Vector2(Input.mousePosition.x, Input.mousePosition.y), mainCamera, out mousePosition);
        float z;
        //以y軸為軸轉 負數角度 因為Angle函數傳回的永遠是0~180°(正數),故Vector3.Angle()前面加負号!
        if (mousePosition.x > transform.position.x)
        {
            //炮台到滑鼠的向量與炮台的↑up向量之間的角度(這裡是正數還是負數自己測試就OK)
            z = -Vector3.Angle(Vector3.up, mousePosition - transform.position);
        }
        else//左轉 反之
        {
            z = Vector3.Angle(Vector3.up, mousePosition - transform.position);
        }
        //改變武器的角度,使用localRotation,在2/3D混合使用的遊戲場景下,必須用這個,因為會受Canvas的影響。
        //使用四元數的一個方法Euler(x,y,z)擷取旋轉角度
        transform.localRotation = Quaternion.Euler(0, 0, z);
    }
}
           

GameController.cs(遊戲控制器如:炮台射擊、UI更新、背景更新)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using System.Collections;
public class GameController : MonoBehaviour {
    //采用單例模式 直接通路腳本             
    private static GameController _instance;
    public static GameController Instance
    {
        get
        {
            return _instance;
        }
    }
    public Text oneShootCostText;

    public Text goldText;
    public Text lvText;
    public Text lvNameText;
    public Text smallCountdownText;
    public Text bigCountdownText;
    public Button bigCountdownButton;
    public Button backButton;
    public Button settingButton;
    public Slider expSlider;

    public int lv = 0;
    public int exp = 0;
    public int gold = 500;
    
    public const int bigCountdown = 240;//獎金倒計時
    public const int smallCountdown = 60;//定時自動獎勵獎金倒計時
    public float bigTimer = bigCountdown;
    public float smallTimer = smallCountdown;
    public Color goldColor;
    public int bgIndex=0;

    public Image bgImage;//渲染背景的元件
    public GameObject seaWaveEffect;//浪花效果 每20級出現一次出現浪花
    public GameObject lvUpTips;
    public GameObject fireEffect;
    public GameObject changeEffect;
    public GameObject lvEffect;
    public GameObject goldEffect;
    

    public Transform bulletHolder;
    public Sprite[] bgSprite;//背景
    public GameObject[] bullet1Gos;
    public GameObject[] bullet2Gos;
    public GameObject[] bullet3Gos;
    public GameObject[] bullet4Gos;
    public GameObject[] bullet5Gos;

    public GameObject[] gunGos;

    //使用的是第幾檔炮彈
    private int costIndex=0;
    //每一炮所需的金币數和造成的傷害值 20 種: 每4種傷害值決定一個類型的武器和一組子彈類型
    private int[] oneShootCost = { 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000 };//炮彈花費金額和炮彈傷害值是一樣的
    private string[] lvName = { "新手","入門","鋼鐵","青銅","白銀","黃金","白金","鑽石","大師","宗師"};

    void Awake()
    {
        _instance = this;
    }
    void Start()
    {
        gold = PlayerPrefs.GetInt("gold", gold);
        lv = PlayerPrefs.GetInt("lv", lv);
        exp = PlayerPrefs.GetInt("exp", exp);
        smallTimer = PlayerPrefs.GetFloat("scd", smallCountdown);
        bigTimer = PlayerPrefs.GetFloat("bcd", bigCountdown);
        UpdateUI();
    }
    void Update()
    {
        ChangeBulletCost();
        Fire();
        UpdateUI();
        ChangeBg();
    }
    /// <summary>
    /// 根據等級來換背景圖
    /// </summary>
    void ChangeBg()
    {
        if (bgIndex!=lv/20)
        {
            bgIndex = lv / 20;
            AudioManager.Instance.PlayEffectSound(AudioManager.Instance.seaWaveClip);
            Instantiate(seaWaveEffect);
            if (bgIndex >= 3)
            {
                bgImage.sprite = bgSprite[3];
            }
            else
            {
                bgImage.sprite = bgSprite[bgIndex];
            }
        }
    }
    /// <summary>
    /// 更新UI方面的東西(可以選擇不看)
    /// </summary>
    void UpdateUI()
    {
       //大金币獎勵時間倒計時
        bigTimer -= Time.deltaTime;
       //小金币獎勵時間倒計時(smallCountdown秒給一次獎勵)
        smallTimer -= Time.deltaTime;
        if (smallTimer <= 0)
        {
            smallTimer = smallCountdown;
            gold += 50;
        }
        //大金币獎勵時間已到,并且獎勵按鈕是隐藏狀态的,就顯示其獎勵按鈕和隐藏倒計時按鈕。
        if (bigTimer <= 0&&bigCountdownButton.gameObject.activeSelf==false)//沒有顯示出來擷取金币按鈕才顯示
        {
            bigTimer = bigCountdown;
            bigCountdownText.gameObject.SetActive(false);//倒計時關閉
            bigCountdownButton.gameObject.SetActive(true);//擷取金币按鈕顯示
        }
       //經驗等級換算公式:更新所需經驗=1000+200*目前等級
        while (exp>=1000+200*lv)
        {
            exp = exp- (1000 + 200 * lv);
            lv++;
            //更新特效(其實是一個文本UI 顯示0.6秒 然後漸隐消失)
            lvUpTips.SetActive(true);
            lvUpTips.transform.Find("Text").GetComponent<Text>().text = lv.ToString();
            StartCoroutine(lvUpTips.GetComponent<EF_HideSelf>().HideSelf(0.6f));
            AudioManager.Instance.PlayEffectSound(AudioManager.Instance.lvUpClip);
            Instantiate(lvEffect);
        }
        //改文本資訊
        goldText.text = "$"+gold;
        lvText.text = lv.ToString();
        if (lv / 10 <= 9)
        {
            lvNameText.text = lvName[lv / 10];
        }
        else
        {
            lvNameText.text = lvName[9];
        }
        //小金币獎勵倒計時文本
        smallCountdownText.text = " " + (int)smallTimer / 10 + "  " + (int)smallTimer % 10;
        //大金币獎勵倒計時文本
        bigCountdownText.text = (int)bigTimer + "s";
        //經驗條結束
        expSlider.value = ((float)exp) / (1000 + 200 * lv);
    }

    void Fire()
    {
        //臨時數組儲存一組子彈
        GameObject[] useBullets = bullet5Gos;
        //一組子彈中的某一種子彈
        int bulletIndex;     
        //按的是滑鼠左擊 并且 按的區域不是UI的話 發射子彈
        if (Input.GetMouseButtonDown(0)&&EventSystem.current.IsPointerOverGameObject()==false)
        {
            //計算下夠不夠錢買子彈
            if (gold - oneShootCost[costIndex] >= 0)
            {
                //根據目前選擇的子彈金額索引号(0~19),确定用哪一組子彈(0~9)
                switch (costIndex / 4)
                {
                    case 0: useBullets = bullet1Gos; break;//第一組
                    case 1: useBullets = bullet2Gos; break;//第二組
                    case 2: useBullets = bullet3Gos; break;//第三組
                    case 3: useBullets = bullet4Gos; break;//第四組
                    case 4: useBullets = bullet5Gos; break;//第五組與第四組相同
                }
                //根據等級決定選取一組子彈中的哪種子彈 0~9
                bulletIndex = lv % 10;
                //執行個體化一組子彈中的其中一種子彈 選哪一種是根據等級選的!!這裡應該是設定為根據使用者花費的錢而選的才對啊!
                //例如:你99級,但是你花費的是5元的子彈,那麼你還是射出了最高等級子彈的模型,傷害卻是最低的!
                //很糾結的一個問題出現了:遊戲中有50種子彈類型,但隻有20種傷害類型!無法一一比對!遊戲改進應該設定一一比對,提高玩家體驗感。
                //那麼可以修改每組子彈隻用4種!然後将上面一行改為:bulletIndex=costIndex%4;即可!(否則,子彈模型與子彈傷害是沒有關系的)
                gold -= oneShootCost[costIndex];//扣錢
                Instantiate(fireEffect);
                AudioManager.Instance.PlayEffectSound(AudioManager.Instance.fireClip); 
                GameObject bullet = Instantiate(useBullets[bulletIndex]);//例如:costIndex是19,就會使用bullet5Gos的第十種子彈
                bullet.transform.SetParent(bulletHolder, false);
                //設定子彈出現位置:在哪個武器身上的FirePos出現(主要有5種武器)
                bullet.transform.position = gunGos[costIndex / 4].transform.Find("FirePos").transform.position;
                //設定子彈方向朝向遊戲内部,也就是與武器朝向保持一緻
                bullet.transform.rotation = gunGos[costIndex / 4].transform.rotation;
                //添加腳本,使得子彈可以向前移動,前是正y軸 是以是up
                bullet.AddComponent<EF_AutoMove>().Dir = Vector3.up;
                //根據子彈身上帶的屬性腳本擷取子彈相應的飛行速度
                bullet.GetComponent<EF_AutoMove>().speed = bullet.GetComponent<BulletAttr>().speed;
                //設定子彈屬性腳本裡的傷害值為子彈應有的傷害(就是買子彈花費的錢)
                bullet.GetComponent<BulletAttr>().damage = oneShootCost[costIndex];
            }
            else
            {
                //TODO Flash The Text
                StartCoroutine(GoldNotEnough());
            }
        }
    }
    /// <summary>
    /// 通過滑鼠滾動滑輪 設定子彈代價
    /// </summary>
    void ChangeBulletCost()
    {
        //向下滑動
        if (Input.GetAxis("Mouse ScrollWheel")<0)
        {
            OnButtonMDown();
        }
        //向上滑動
        if (Input.GetAxis("Mouse ScrollWheel")>0)
        {
            OnButtonPDown();
        }
    }
    //按炮台右邊的按鈕會更新槍支、子彈等級(即處理costIndex關鍵)
    public void OnButtonPDown()
    {
        gunGos[costIndex / 4].SetActive(false);
        costIndex++;
        AudioManager.Instance.PlayEffectSound(AudioManager.Instance.changeClip);
        Instantiate(changeEffect);
        costIndex = costIndex % oneShootCost.Length;  // costIndex % 20 =[0,19] 不會産生越界問題
        gunGos[costIndex / 4].SetActive(true);
        oneShootCostText.text = "$"+oneShootCost[costIndex];
    }
    //按炮台左邊的按鈕會降低槍支等級、子彈等級(即處理costIndex)
    public void OnButtonMDown()
    {
        gunGos[costIndex / 4].SetActive(false);
        costIndex--;
        AudioManager.Instance.PlayEffectSound(AudioManager.Instance.changeClip);
        Instantiate(changeEffect);
        costIndex = (costIndex < 0) ? oneShootCost.Length-1 : costIndex;  // 同理 防止出現越界問題
        gunGos[costIndex / 4].SetActive(true);
        oneShootCostText.text = "$" + oneShootCost[costIndex];
    }
    //獎勵按鈕事件-擷取金币(計時到一定時候才會有該按鈕顯示)
    public void OnBigCountdownButtonDown()
    {
        gold += 500;
        AudioManager.Instance.PlayEffectSound(AudioManager.Instance.rewardClip);
        Instantiate(goldEffect);
        bigCountdownButton.gameObject.SetActive(false);
        bigCountdownText.gameObject.SetActive(true);
    }

    /// <summary>
    /// 金币不足 變紅 然後 變原來顔色
    /// </summary>
    IEnumerator GoldNotEnough()
    {
        goldText.color = Color.red;//變紅
        yield return new WaitForSeconds(0.5f);//等待0.5s
        goldText.color = goldColor;//恢複回來
    }

    
}
           

魚群孵化器、魚群行為(直線走、拐彎走):

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

public class FishMaker : MonoBehaviour {

    public Transform fishHolder;//魚存放位置(父物體)
    public Transform[] genPosition;//魚生成地點
    public GameObject[] fishPrefabs;//魚預制體數組

    public float fishGenWaitTime = 0.5f;//魚生成時間間隔
    public float waveGenWaitTime = 0.3f;//波時間間隔

	void Start () 
    {
        InvokeRepeating("MakeFishes", 0, waveGenWaitTime);
	}
    void MakeFishes()
    {
        int genPosIndex = Random.Range(0, genPosition.Length);
        int fishPreIndex = Random.Range(0, fishPrefabs.Length);
        int maxNum = fishPrefabs[fishPreIndex].GetComponent<FishAttr>().maxNum;
        int maxSpeed = fishPrefabs[fishPreIndex].GetComponent<FishAttr>().maxSpeed;
        int num = Random.Range((maxNum / 2) + 1, maxNum);//保證最小生成數量也有一個
        int speed = Random.Range(maxSpeed / 2, maxSpeed);//速度保證是最大Speed的一半以上
        int moveType = Random.Range(0, 2);               //0:直走 1:轉彎 标志位
        int angOffset;                                   //僅直走生效,直走的傾斜角
        int angSpeed;                                    //僅轉彎生效,轉彎的角速度

        if (moveType == 0)
        {
            //TODO 直走魚群的生成  
            angOffset = Random.Range(-22, 22);//45°範圍 沿着z軸旋轉 反正就是一定會投放在遊戲界面内
            StartCoroutine(GenStraightFish(genPosIndex, fishPreIndex, num, speed, angOffset));
        }
        else
        {
            //TODO 轉彎魚群的生成 是否取負的角速度 右轉是負數 左轉是正數
            if (Random.Range(0, 2) == 0)
            {
                angSpeed = Random.Range(-15, -9);
            }
            else
            {
                angSpeed = Random.Range(9, 15);
            }
            StartCoroutine(GenTrunFish( genPosIndex,  fishPreIndex,  num,  speed,  angSpeed));
        }
    }
    IEnumerator GenStraightFish(int genPosIndex, int fishPreIndex, int num, int speed, int angOffset)
    {
        for (int i = 0; i < num; i++)
        {
            GameObject fish= Instantiate(fishPrefabs[fishPreIndex]);
            fish.transform.SetParent(fishHolder,false);
            fish.transform.localPosition = genPosition[genPosIndex].localPosition;
            fish.transform.localRotation = genPosition[genPosIndex].localRotation;//關鍵:設定魚在哪之後,魚的朝向也要設定
            fish.transform.Rotate(0, 0, angOffset);//這裡是在原有基礎上 繞着Z軸旋轉angOffset°(負數向右,正數向左)
            fish.GetComponent<SpriteRenderer>().sortingOrder += i;//解決渲染問題,新生成的魚會疊在舊的魚之上
            fish.AddComponent<EF_AutoMove>().speed = speed;//給魚添加新的腳本元件,并且設定Speed,這樣魚就會向正X方向移動
            yield return new WaitForSeconds(fishGenWaitTime);//暫停0.5s 間隔魚群中的個體
        }
    }
    IEnumerator GenTrunFish(int genPosIndex, int fishPreIndex, int num, int speed, int angSpeed)
    {
        for (int i = 0; i < num; i++)
        {
            GameObject fish = Instantiate(fishPrefabs[fishPreIndex]);
            fish.transform.SetParent(fishHolder, false);
            fish.transform.localPosition = genPosition[genPosIndex].localPosition;
            fish.transform.localRotation = genPosition[genPosIndex].localRotation;//關鍵:設定魚在哪之後,魚的朝向也要設定
            fish.GetComponent<SpriteRenderer>().sortingOrder += i;//解決渲染問題,新生成的魚會疊在舊的魚之上
            fish.AddComponent<EF_AutoRotate>().speed = angSpeed;//添加新的腳本元件,并且設定Speed 角速度,這樣魚就會沿着Z軸轉彎
            fish.AddComponent<EF_AutoMove>().speed = speed;//給魚添加新的腳本元件,并且設定Speed,這樣魚就會向正X方向移動
            yield return new WaitForSeconds(fishGenWaitTime);//暫停0.5s 間隔魚群中的個體
        }
    }
}
           

至于魚群怎麼拐彎的,其實很簡單就直接在Update用transform.Rotate(z軸,旋轉量)就好了,然後給魚一個速度(平均速度就好),魚的孵化地點是在地圖上(外邊)設定了貌似有12個左右,而且地圖周圍(外邊)也有設定一些碰撞器用于消除沒有被玩家打掉的魚,而魚群你可以優化下使用資源池,不然太卡了,執行個體化很耗費時間的!

繼續閱讀