【Unity】捕鱼达人开发流程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_39574690/article/details/89317307

核心功能:炮台的控制、炮台发射子弹、鱼群孵化器和鱼群行为。

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个左右,而且地图周围(外边)也有设置一些碰撞器用于消除没有被玩家打掉的鱼,而鱼群你可以优化下使用资源池,不然太卡了,实例化很耗费时间的!

猜你喜欢

转载自blog.csdn.net/qq_39574690/article/details/89317307