Unity快速上手系列之4:《塔防》終

作者:四五二十
大家好。都特么快把這個(gè)小項(xiàng)目忘了,趕緊把坑填上。
今天我們來為塔防做經(jīng)濟(jì)系統(tǒng)和升級(jí)功能,還是先看一個(gè)視頻:
為塔防做簡化的經(jīng)濟(jì)系統(tǒng)和升級(jí)功能
簡化的經(jīng)濟(jì)和升級(jí)系統(tǒng)如下:
我們?cè)诮ㄔ旌蜕?jí)防御塔時(shí)會(huì)消耗金錢;
每次消滅敵人時(shí)和出售防御塔會(huì)獲得金錢;
隨著防御塔級(jí)別的提升,升級(jí)所要消耗的金錢與出售時(shí)獲得的金錢也會(huì)逐步提高,且升級(jí)后的防御塔會(huì)獲得攻擊力等技能上的提升;
玩家進(jìn)入游戲時(shí)有一定數(shù)量的初始金錢。
根據(jù)上面的思路,本篇文章分為兩部分:經(jīng)濟(jì)系統(tǒng)的實(shí)現(xiàn)與防御塔升級(jí)系統(tǒng)的實(shí)現(xiàn)。
創(chuàng)建一個(gè)游戲數(shù)據(jù)類,主要存放金錢數(shù)據(jù):
public class GameData
{
??? private static GameData _Instance;
??? public static GameData Instance
??? {
??????? get
??????? {
??????????? if (_Instance == null)
??????????????? _Instance = new GameData();
??????????? return _Instance;
??????? }
??? }
??? //游戲金錢
??? public int gold;
}
?然后在主調(diào)類GameMain中初始化金錢:
??? public int InitGold;
??? //初始化游戲數(shù)據(jù)
??? void InitGameData()
??? {
??????? GameData.Instance.gold = InitGold;
??? }
?
在場景中搭建一個(gè)金錢顯示窗口:

為它創(chuàng)建一個(gè)腳本:
public class GoldPanel : MonoBehaviour
{
??? public static GoldPanel Instance;
??? public Text goldText; //把顯示數(shù)量的Text拖進(jìn)去
??? //初始化
??? public void Init()
??? {
??????? Instance = this;
??????? //將當(dāng)前金錢顯示在界面上
??????? goldText.text = GameData.Instance.gold.ToString();
??? }
}
?
該初始化方法也放到GameMain中調(diào)用,但要放在初始化金錢后調(diào)用。
然后來到GameData類中,添加增加金錢和減少金錢的方法:
//增加金錢
public void AddGold(int _gold)
{
??? gold += _gold;
??? GoldPanel.Instance.goldText.text = gold.ToString();
}
//減去金錢
public void SubGold(int _gold)
{
??? gold -= _gold;
??? GoldPanel.Instance.goldText.text = gold.ToString();
}
?
由于我們會(huì)把升級(jí)系統(tǒng)放后面,所以現(xiàn)在我們就可以在建造防御塔時(shí)和消滅敵人時(shí)分別調(diào)用減少金錢和增加金錢,具體數(shù)量自定設(shè)定:

敵人腳本中添加增加金錢屬性:
public int gold; //金錢獎(jiǎng)勵(lì)
?
消滅敵人時(shí)調(diào)用:
GameData.Instance.AddGold(gold);
防御塔腳本中添加相應(yīng)屬性:
public int gold; //修建價(jià)格
在創(chuàng)建防御塔時(shí)調(diào)用:
GameData.Instance.SubGold(gold);
稍微修改一下防御塔的菜單界面:

IconElement增加新屬性:
[SerializeField]
?private Text goldTxet; //將購買價(jià)格Text拖進(jìn)去
有了經(jīng)濟(jì)系統(tǒng)后,防御塔就不能無限建造了,如果金錢不夠則不能購買防御塔,我們的防御塔是通過拖拽Icon來購買的,在IconElement腳本中增加一個(gè)判斷方法:
?
//隱藏切換,發(fā)現(xiàn)金幣不足時(shí)頭像置灰,隱藏腳本(將不能點(diǎn)擊)
public void HideSwitch(bool enough)
{
??? if (enough)
??? {
??????? enabled = true;
??????? image.color = new Color(1, 1, 1); //頭像顏色正常
??????? goldTxet.color = new Color(1, 1, 1); //字體顏色正常
??? }
??? else
??? {
??????? enabled = false;
??????? image.color = new Color(0.5f, 0.5f, 0.5f); //頭像置灰
??????? goldTxet.color = new Color(1, 0, 0); //字體變紅
??? }
}
?
而Icon由PagodaMenu統(tǒng)一管理的,所以在PagodaMenu腳本中進(jìn)行統(tǒng)一判斷:
??? //刷新菜單,發(fā)現(xiàn)金錢不夠的就隱藏該菜單選項(xiàng)
??? public void RefreshMenu()
??? {
??????? for (int i = 0; i < icons.Length; i++)
??????? {
??????????? icons[i].HideSwitch(GameData.Instance.gold >= icons[i].gold);
??????? }
??? }
?
刷新菜單的方法放在金錢數(shù)量改變時(shí)調(diào)用即可:
PagodaMenu.Instanc.RefreshMenu();????
之前弓箭手的腳本Pagoda是防御塔類的基類,現(xiàn)在我們把基類單獨(dú)提出來,弓箭手重新掛上一個(gè)Pagoda1腳本來繼承Pagoda,把獨(dú)屬于弓箭手的屬性和方法放入Pagoda1中。
防御塔共設(shè)定了4個(gè)等級(jí),初始的0級(jí)到3級(jí),我們現(xiàn)在為Pagoda腳本增加幾個(gè)屬性:
public int grade; //當(dāng)前等級(jí)(隨等級(jí)變動(dòng))
[HideInInspector]
public int upgradeGold; //升級(jí)價(jià)格(隨等級(jí)變動(dòng))
[HideInInspector]
public int sellGold; //出售價(jià)格(隨等級(jí)變動(dòng))
?
不同的防御塔根據(jù)功能不同,升級(jí)的屬性也稍有不同:

這些升級(jí)屬性會(huì)在每次等級(jí)發(fā)生變動(dòng)時(shí)根據(jù)等級(jí)刷新,從上面表中總結(jié)出幾個(gè)相同的升級(jí)項(xiàng):升級(jí)價(jià)格,出售價(jià)格,攻擊力,我們?cè)赑agoda中創(chuàng)建刷新數(shù)據(jù)的方法,并在等級(jí)發(fā)生變動(dòng)時(shí)調(diào)用:
//根據(jù)等級(jí)更新數(shù)值
public virtual void UpdateByGrade()
{
}
//升級(jí)屬性
public void Refresh(int _upgradeGold, int _sellGold, float _damage)
{
??? upgradeGold = _upgradeGold; //升級(jí)升級(jí)價(jià)格
??? sellGold = _sellGold; //升級(jí)出售價(jià)格???????
??? damage = _damage; //升級(jí)攻擊力
}
?
在弓箭中腳本Pagoda1中實(shí)現(xiàn):
//根據(jù)等級(jí)更新數(shù)值
public override void UpdateByGrade()
{
??? switch (grade)
??? {
??????? case 0:
??????????? Refresh(10, 10, 30);
??????????? attactRange = 30; //升級(jí)攻擊范圍
??????????? break;
??????? case 1:
??????????? Refresh(20, 15, 40);
??????????? attactRange = 35;
??????????? break;
??????? case 2:
??????????? Refresh(30, 25, 50);
??????????? attactRange = 40;
??????????? break;
??????? case 3:
??????????? Refresh(0, 40, 60);
??????????? attactRange = 45;
??????????? break;
??? }
}
在錘子兵腳本Pagoda2中實(shí)現(xiàn):
float hammerRange; //擊飛范圍
//根據(jù)等級(jí)更新數(shù)值
public override void UpdateByGrade()
{
??? switch (grade)
??? {
??????? case 0:
??????????? Refresh(15, 15, 10);
??????????? hammerRange = 6; //升級(jí)擊飛范圍
??????????? break;
??????? case 1:
??????????? Refresh(30, 22, 12);
??????????? hammerRange = 8;
??????????? break;
??????? case 2:
??????????? Refresh(45, 37, 15);
??????????? hammerRange = 10;
??????????? break;
??????? case 3:
??????????? Refresh(0, 60, 20);
??????????? hammerRange = 12;
??????????? break;
??? }
}
?
在劍士腳本Pagoda3中實(shí)現(xiàn):
//根據(jù)等級(jí)更新數(shù)值
public override void UpdateByGrade()
{
??? switch (grade)
??? {
??????? case 0:
??????????? Refresh(20, 20, 50);
??????????? critChance = 0.2f; //升級(jí)暴擊率
??????????? break;
??????? case 1:
??????????? Refresh(40, 30, 65);
??????????? critChance = 0.3f;
??????????? break;
??????? case 2:
??????????? Refresh(60, 50, 80);
??????????? critChance = 0.4f;
??????????? break;
??????? case 3:
??????????? Refresh(0, 80, 100);
? ??????????critChance = 0.5f;
??????????? break;
??? }
}
?防御塔有了出售功能,意味著防御塔的模型可以重復(fù)利用,我們可以將防御塔也用對(duì)象池管理,在Pagoda中聲明對(duì)象池:
??? [HideInInspector]
??? public Transform pagodaPool; //對(duì)象池
?
在IconElement腳本中根據(jù)模型名字進(jìn)行初始化,創(chuàng)建過程中取消創(chuàng)建或出售時(shí)就返回對(duì)象池,并將該防御塔的等級(jí)清0,刷新數(shù)據(jù)。
(敵人那邊也可以加上對(duì)象池功能)
為Pagoda創(chuàng)建出售方法和升級(jí)方法:
//出售防御塔
public void SellPagoda()
{
??? GameData.Instance.AddGold(sellGold); //獲取金錢
??? //返回對(duì)象池,等級(jí)清0,刷新數(shù)據(jù)
??? transform.SetParent(pagodaPool);???????
??? grade = 0;
??? UpdateByGrade();
}
//升級(jí)方法
public void Upgrade()
{
??? //未滿級(jí)且金錢足夠才能升級(jí)
??? if (grade < 3 && GameData.Instance.gold >= upgradeGold)
??? {
??????? GameData.Instance.SubGold(upgradeGold); //減少金錢
??????? grade++;
??????? UpdateByGrade(); //升級(jí)后更新數(shù)值
??? }
}
?
使用UI搭建一個(gè)升級(jí)界面,加入出售和升級(jí)兩個(gè)按鈕,在添加兩個(gè)Text顯示出售價(jià)格和升級(jí)價(jià)格,并用三顆星星表示當(dāng)前等級(jí):

為它掛上一個(gè)腳本UpgradePanel:
public class UpgradePanel : MonoBehaviour
{
??? public static UpgradePanel Instanc; //單例
?
??? public Transform attRange; //攻擊范圍顯示器
??? [SerializeField]
??? private GameObject[] stars; //3顆星星
??? [SerializeField]
??? private Button gradebut; //升級(jí)按鈕
??? [SerializeField]
??? private Text sellGold; //顯示出售價(jià)格
??? [SerializeField]
??? private Text upgradeGold; //顯示出售價(jià)格
}
?
所要用到的元素在編輯器界面直接拖入:

創(chuàng)建初始化方法在GameMain中調(diào)用:
//初始化
public void Init()
{
??? Instanc = this;
??? gameObject.SetActive(false);
}
?
添加一個(gè)顯示防御塔信息的方法和刷新信息的方法:
Pagoda pagoda; //當(dāng)前防御塔,點(diǎn)擊防御塔時(shí)賦值
??? //顯示防御塔信息
??? public void ShowPagodaInfo(Pagoda _pagoda)
??? {
??????? gameObject.SetActive(true);
??????? pagoda = _pagoda;
??????? RefreshInfo();
??????? ShowattRange();
??? }
??? //根據(jù)等級(jí)刷新信息??
??? public void RefreshInfo()
??? {
??????? if (pagoda != null)
??????? {??? //顯示星星
??????????? for (int i = 0; i < stars.Length; i++)
??????????? {
??????????????? stars[i].SetActive(i < pagoda.grade);
??????????? }
??????????? sellGold.text = pagoda.sellGold.ToString(); //顯示出售數(shù)字
??????????? upgradeGold.text = pagoda.upgradeGold.ToString(); //顯示升級(jí)數(shù)字
?????????
??????????? Text butDes = gradebut.GetComponentInChildren<Text>();
?????????? //如果滿級(jí)或錢不夠
??????????? if (pagoda.grade >= 3 || GameData.Instance.gold < pagoda.upgradeGold)
??????????? {
??????????????? //按鈕為紅色,不能點(diǎn)擊
??????????????? butDes.color = Color.red;
??????????????? gradebut.enabled = false;
?
??????????????? if (pagoda.grade >= 3) //如果是滿級(jí)
??????????????? {
??????????????????? //數(shù)字隱藏
???????????????? ???upgradeGold.enabled = false;
??????????????? }
??????????????? else //不是滿級(jí),那就是錢不夠
??????????????? {
??????????????????? //數(shù)字顯示,為紅色
??????????????????? upgradeGold.enabled = true;
??????????????????? upgradeGold.color = Color.red;
??????????????? }
???? ???????}
??????????? else //未滿級(jí)且錢夠
??????????? {
??????????????? //數(shù)字顯示,為黃色
??????????????? upgradeGold.enabled = true;
??????????????? upgradeGold.color = Color.yellow;
??????????????? //按鈕為綠色,可以點(diǎn)擊
??????????????? butDes.color = Color.green;
???????????? ???gradebut.enabled = true;
??????????? }
??????? }
??? }
??? //顯示攻擊范圍
??? void ShowattRange()
??? {
??????? attRange.gameObject.SetActive(true);
??????? attRange.localScale = new Vector3(pagoda.attactRange * 2, 1, pagoda.attactRange * 2);
??????? attRange.position = pagoda.transform.position;
??? }
?
ShowPagodaInfo方法可以點(diǎn)擊防御塔時(shí)調(diào)用,并獲取該防御塔信息,而RefreshInfo方法除了在ShowPagodaInfo中調(diào)用外,等級(jí)增加時(shí)和金錢增加時(shí)也要刷新一次。
接下來就是怎樣點(diǎn)擊防御塔顯示信息了。實(shí)現(xiàn)原理是從攝像機(jī)位置發(fā)射射線,根據(jù)物體層檢測防御塔地形,如果該地形的子物體不會(huì)空,說明已經(jīng)安置防御塔,那就獲取該防御塔信息然后顯示。創(chuàng)建一個(gè)主控腳本PlayerController:
public class PlayerController : MonoBehaviour
{
??? public UpgradePanel upgradePanel;
Transform hitTerrain; //當(dāng)前地形
??? //顯示升級(jí)信息
??? public void ShowUpgradePanel()
??? {
??????? if (PagodaMenu.Instanc.readyToPlace == false)
??????? {
??????????? //點(diǎn)擊一定范圍除關(guān)閉
??????????? if (upgradePanel.gameObject.activeInHierarchy &&
??????????????? Vector3.Distance(upgradePanel.transform.position, Input.mousePosition) >= 300)
??????????????? CloseUpgradePanel();
?
???? ???????//從攝像機(jī)發(fā)射射線,檢測防御塔地形
??????????? Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
??????????? RaycastHit hit;
??????????? if (Physics.Raycast(ray, out hit, 500, LayerMask.GetMask("Pagoda")))
??????????? {
??????????????? //得到防御塔信息
???????? ???????Pagoda pagoda = hit.collider.GetComponentInChildren<Pagoda>();
??????????????? if (pagoda != null)
??????????????? {
??????????????????? hitTerrain = hit.collider.transform;
??????????????????? upgradePanel.ShowPagodaInfo(pagoda);
??????????????? }
??????????? }
??????? }
??????? else
??????????? upgradePanel.gameObject.SetActive(false);
??? }
??? //關(guān)閉升級(jí)信息和范圍顯示器
??? public void CloseUpgradePanel()
??? {
??????? upgradePanel.gameObject.SetActive(false);
??????? upgradePanel.attRange.gameObject.SetActive(false);
}
}
?
在Update中調(diào)用:
??? private void Update()
??? {
??????? if (Input.GetMouseButtonDown(0))
??????????? ShowUpgradePanel();
??????? //啟用后一直跟隨之前的剛才的地形,不受屏幕拖動(dòng)影響
??????? if (upgradePanel.gameObject.activeInHierarchy)
??????????? upgradePanel.transform.position = mainCamera.WorldToScreenPoint(hitTerrain.position + Vector3.up * 5);
??? }
?
在演示視頻中鏡頭的移動(dòng)與縮放也可以放入這個(gè)腳本中,該功能比較簡單,這里不啰嗦。
升級(jí)界面還有兩個(gè)按鈕,分別為出售和升級(jí),來到UpgradePanel腳本中創(chuàng)建相應(yīng)按鈕事件,掛到按鈕上:
//出售防御塔(按鈕事件)
public void PagodaSell()
{
??? pagoda.SellPagoda(); //調(diào)用防御塔初始方法
??? gameObject.SetActive(false);
??? attRange.gameObject.SetActive(false);
}
//升級(jí)防御塔(按鈕事件)
public void PagodaGrade()
{
??? pagoda.Upgrade(); //調(diào)用防御塔升級(jí)方法
??? RefreshInfo(); //刷新數(shù)據(jù)
??? //升級(jí)攻擊范圍
??? attRange.localScale = new Vector3(pagoda.attactRange * 2, 1, pagoda.attactRange * 2);
}
?
這些都完成后,我們的升級(jí)功能就可以正常運(yùn)行了。附上github:
https://github.com/wushupei/TowerDefenseGame
那么這個(gè)系列就此結(jié)束,有更多其他想法的讀者歡迎自由發(fā)揮,添加更多更有趣的功能。
有意向參與線下游戲開發(fā)學(xué)習(xí)的童鞋,歡迎訪問http://levelpp.com/
皮皮關(guān)的游戲開發(fā)QQ群也歡迎各位強(qiáng)勢插入:869551769