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

作者:四五二十
大家好。都特么快把这个小项目忘了,赶紧把坑填上。
今天我们来为塔防做经济系统和升级功能,还是先看一个视频:
简化的经济和升级系统如下:
我们在建造和升级防御塔时会消耗金钱;
每次消灭敌人时和出售防御塔会获得金钱;
随着防御塔级别的提升,升级所要消耗的金钱与出售时获得的金钱也会逐步提高,且升级后的防御塔会获得攻击力等技能上的提升;
玩家进入游戏时有一定数量的初始金钱。
根据上面的思路,本篇文章分为两部分:经济系统的实现与防御塔升级系统的实现。
创建一个游戏数据类,主要存放金钱数据:
public class GameData
{
private static GameData _Instance;
public static GameData Instance
{
get
{
if (_Instance == null)
_Instance = new GameData();
return _Instance;
}
}
//游戏金钱
public int gold;
}
然后在主调类GameMain中初始化金钱:
public int InitGold;
//初始化游戏数据
void InitGameData()
{
GameData.Instance.gold = InitGold;
}
在场景中搭建一个金钱显示窗口:

为它创建一个脚本:
public class GoldPanel : MonoBehaviour
{
public static GoldPanel Instance;
public Text goldText; //把显示数量的Text拖进去
//初始化
public void Init()
{
Instance = this;
//将当前金钱显示在界面上
goldText.text = GameData.Instance.gold.ToString();
}
}
该初始化方法也放到GameMain中调用,但要放在初始化金钱后调用。
然后来到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();
}
由于我们会把升级系统放后面,所以现在我们就可以在建造防御塔时和消灭敌人时分别调用减少金钱和增加金钱,具体数量自定设定:

敌人脚本中添加增加金钱属性:
public int gold; //金钱奖励
消灭敌人时调用:
GameData.Instance.AddGold(gold);
防御塔脚本中添加相应属性:
public int gold; //修建价格
在创建防御塔时调用:
GameData.Instance.SubGold(gold);
稍微修改一下防御塔的菜单界面:

IconElement增加新属性:
[SerializeField]
private Text goldTxet; //将购买价格Text拖进去
有了经济系统后,防御塔就不能无限建造了,如果金钱不够则不能购买防御塔,我们的防御塔是通过拖拽Icon来购买的,在IconElement脚本中增加一个判断方法:
//隐藏切换,发现金币不足时头像置灰,隐藏脚本(将不能点击)
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统一管理的,所以在PagodaMenu脚本中进行统一判断:
//刷新菜单,发现金钱不够的就隐藏该菜单选项
public void RefreshMenu()
{
for (int i = 0; i < icons.Length; i++)
{
icons[i].HideSwitch(GameData.Instance.gold >= icons[i].gold);
}
}
刷新菜单的方法放在金钱数量改变时调用即可:
PagodaMenu.Instanc.RefreshMenu();
之前弓箭手的脚本Pagoda是防御塔类的基类,现在我们把基类单独提出来,弓箭手重新挂上一个Pagoda1脚本来继承Pagoda,把独属于弓箭手的属性和方法放入Pagoda1中。
防御塔共设定了4个等级,初始的0级到3级,我们现在为Pagoda脚本增加几个属性:
public int grade; //当前等级(随等级变动)
[HideInInspector]
public int upgradeGold; //升级价格(随等级变动)
[HideInInspector]
public int sellGold; //出售价格(随等级变动)
不同的防御塔根据功能不同,升级的属性也稍有不同:

这些升级属性会在每次等级发生变动时根据等级刷新,从上面表中总结出几个相同的升级项:升级价格,出售价格,攻击力,我们在Pagoda中创建刷新数据的方法,并在等级发生变动时调用:
//根据等级更新数值
public virtual void UpdateByGrade()
{
}
//升级属性
public void Refresh(int _upgradeGold, int _sellGold, float _damage)
{
upgradeGold = _upgradeGold; //升级升级价格
sellGold = _sellGold; //升级出售价格
damage = _damage; //升级攻击力
}
在弓箭中脚本Pagoda1中实现:
//根据等级更新数值
public override void UpdateByGrade()
{
switch (grade)
{
case 0:
Refresh(10, 10, 30);
attactRange = 30; //升级攻击范围
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中实现:
float hammerRange; //击飞范围
//根据等级更新数值
public override void UpdateByGrade()
{
switch (grade)
{
case 0:
Refresh(15, 15, 10);
hammerRange = 6; //升级击飞范围
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中实现:
//根据等级更新数值
public override void UpdateByGrade()
{
switch (grade)
{
case 0:
Refresh(20, 20, 50);
critChance = 0.2f; //升级暴击率
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;
}
}
防御塔有了出售功能,意味着防御塔的模型可以重复利用,我们可以将防御塔也用对象池管理,在Pagoda中声明对象池:
[HideInInspector]
public Transform pagodaPool; //对象池
在IconElement脚本中根据模型名字进行初始化,创建过程中取消创建或出售时就返回对象池,并将该防御塔的等级清0,刷新数据。
(敌人那边也可以加上对象池功能)
为Pagoda创建出售方法和升级方法:
//出售防御塔
public void SellPagoda()
{
GameData.Instance.AddGold(sellGold); //获取金钱
//返回对象池,等级清0,刷新数据
transform.SetParent(pagodaPool);
grade = 0;
UpdateByGrade();
}
//升级方法
public void Upgrade()
{
//未满级且金钱足够才能升级
if (grade < 3 && GameData.Instance.gold >= upgradeGold)
{
GameData.Instance.SubGold(upgradeGold); //减少金钱
grade++;
UpdateByGrade(); //升级后更新数值
}
}
使用UI搭建一个升级界面,加入出售和升级两个按钮,在添加两个Text显示出售价格和升级价格,并用三颗星星表示当前等级:

为它挂上一个脚本UpgradePanel:
public class UpgradePanel : MonoBehaviour
{
public static UpgradePanel Instanc; //单例
public Transform attRange; //攻击范围显示器
[SerializeField]
private GameObject[] stars; //3颗星星
[SerializeField]
private Button gradebut; //升级按钮
[SerializeField]
private Text sellGold; //显示出售价格
[SerializeField]
private Text upgradeGold; //显示出售价格
}
所要用到的元素在编辑器界面直接拖入:

创建初始化方法在GameMain中调用:
//初始化
public void Init()
{
Instanc = this;
gameObject.SetActive(false);
}
添加一个显示防御塔信息的方法和刷新信息的方法:
Pagoda pagoda; //当前防御塔,点击防御塔时赋值
//显示防御塔信息
public void ShowPagodaInfo(Pagoda _pagoda)
{
gameObject.SetActive(true);
pagoda = _pagoda;
RefreshInfo();
ShowattRange();
}
//根据等级刷新信息
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(); //显示出售数字
upgradeGold.text = pagoda.upgradeGold.ToString(); //显示升级数字
Text butDes = gradebut.GetComponentInChildren<Text>();
//如果满级或钱不够
if (pagoda.grade >= 3 || GameData.Instance.gold < pagoda.upgradeGold)
{
//按钮为红色,不能点击
butDes.color = Color.red;
gradebut.enabled = false;
if (pagoda.grade >= 3) //如果是满级
{
//数字隐藏
upgradeGold.enabled = false;
}
else //不是满级,那就是钱不够
{
//数字显示,为红色
upgradeGold.enabled = true;
upgradeGold.color = Color.red;
}
}
else //未满级且钱够
{
//数字显示,为黄色
upgradeGold.enabled = true;
upgradeGold.color = Color.yellow;
//按钮为绿色,可以点击
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方法可以点击防御塔时调用,并获取该防御塔信息,而RefreshInfo方法除了在ShowPagodaInfo中调用外,等级增加时和金钱增加时也要刷新一次。
接下来就是怎样点击防御塔显示信息了。实现原理是从摄像机位置发射射线,根据物体层检测防御塔地形,如果该地形的子物体不会空,说明已经安置防御塔,那就获取该防御塔信息然后显示。创建一个主控脚本PlayerController:
public class PlayerController : MonoBehaviour
{
public UpgradePanel upgradePanel;
Transform hitTerrain; //当前地形
//显示升级信息
public void ShowUpgradePanel()
{
if (PagodaMenu.Instanc.readyToPlace == false)
{
//点击一定范围除关闭
if (upgradePanel.gameObject.activeInHierarchy &&
Vector3.Distance(upgradePanel.transform.position, Input.mousePosition) >= 300)
CloseUpgradePanel();
//从摄像机发射射线,检测防御塔地形
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);
}
//关闭升级信息和范围显示器
public void CloseUpgradePanel()
{
upgradePanel.gameObject.SetActive(false);
upgradePanel.attRange.gameObject.SetActive(false);
}
}
在Update中调用:
private void Update()
{
if (Input.GetMouseButtonDown(0))
ShowUpgradePanel();
//启用后一直跟随之前的刚才的地形,不受屏幕拖动影响
if (upgradePanel.gameObject.activeInHierarchy)
upgradePanel.transform.position = mainCamera.WorldToScreenPoint(hitTerrain.position + Vector3.up * 5);
}
在演示视频中镜头的移动与缩放也可以放入这个脚本中,该功能比较简单,这里不啰嗦。
升级界面还有两个按钮,分别为出售和升级,来到UpgradePanel脚本中创建相应按钮事件,挂到按钮上:
//出售防御塔(按钮事件)
public void PagodaSell()
{
pagoda.SellPagoda(); //调用防御塔初始方法
gameObject.SetActive(false);
attRange.gameObject.SetActive(false);
}
//升级防御塔(按钮事件)
public void PagodaGrade()
{
pagoda.Upgrade(); //调用防御塔升级方法
RefreshInfo(); //刷新数据
//升级攻击范围
attRange.localScale = new Vector3(pagoda.attactRange * 2, 1, pagoda.attactRange * 2);
}
这些都完成后,我们的升级功能就可以正常运行了。附上github:
https://github.com/wushupei/TowerDefenseGame
那么这个系列就此结束,有更多其他想法的读者欢迎自由发挥,添加更多更有趣的功能。
有意向参与线下游戏开发学习的童鞋,欢迎访问http://levelpp.com/
皮皮关的游戏开发QQ群也欢迎各位强势插入:869551769