最近我制作了一个Boss,并实现了花园场景的存档。
首先是Boss的制作,最麻烦的是找素材,因为在网上难以找到所有的原版PVZ的素材,所以开始我解包了原版PVZ的游戏资源,但是得到的是僵尸肢体的图片,所以想要完全再现原版需要我们手动拼装僵尸并制作动画,所以我解包了另一个游戏,制作了简单的几个动画。
上面两张是动画原图,下面是Boss在游戏中释放了技能。在Boss关卡中,只要Boss存活就会无限产生僵尸,这个很好实现,在无尽模式中已有僵尸管理器用来产生和销毁僵尸,那么只要定时调用方法增加场景中的僵尸即可。
private IEnumerator CreatZombie()
{
iscreateZombie = true;
animator.Play("bossCall");
yield return new WaitForSeconds(1.5f);
switch (Random.Range(0, 3))
{
case 0:
ZombieManager.Instance.UpdateZombieByBoss(7, ZombieType.Zombie);
break;
case 1:
ZombieManager.Instance.UpdateZombieByBoss(5, ZombieType.ConeheadZombie);
break;
case 2:
ZombieManager.Instance.UpdateZombieByBoss(2, ZombieType.BucketheadZombie);
break;
}
yield return new WaitForSeconds(2f);
animator.Play("BossStay");
yield return new WaitForSeconds(15f);
iscreateZombie = false;
}
这里我使用了协程的方式,便于播放动画以及冷却召唤间隔,同时为了凸显与普通刷新不同,我做了简单的特效以表明这是由Boss召唤出来的僵尸。
在原版中僵王会召唤火球清除一路的植物,关于火球的实现我取巧将其视为僵尸以便它可以被樱桃炸弹和火爆辣椒消除。
原版的僵王作为一个Boss只有很简单的技能,但是因为庞大的身躯造成强烈的视觉效果而不觉得有什么违和,而我制作的Boss在失去了巨大的身躯后只能通过技能上的优势来凸显难度,所以我还做了两个技能。一个是场地会定时在随机位置产生爆炸对爆炸点周围8格产生高额伤害。当然这个爆炸点是随机的。
void ReleaseMissile()
{
StartCoroutine(DoReleaseMissile());
}
private IEnumerator DoReleaseMissile()
{
isReleaseMissile = true;
yield return new WaitForSeconds(3.5f);
animator.Play("bossReleaseSkill");
yield return new WaitForSeconds(1.5f);
//产生一个导弹
GameObject prefab = GameManager.Instance.GameConf.Missile;
Missile missile = PoolManager.Instance.GetObj(prefab).GetComponent<Missile>();
missile.transform.SetParent(transform);
missile.transform.position = GridManager.Instance.GetGrid().Position + new Vector2(0,1.9f);
missile.StartBoom();
yield return new WaitForSeconds(2f);
animator.Play("BossStay");
yield return new WaitForSeconds(80f);
isReleaseMissile = false;
}
public void StartBoom()
{
StartCoroutine(CheckBoom());
}
/// <summary>
/// 检测爆炸
/// </summary>
/// <returns></returns>
IEnumerator CheckBoom()
{
while (true)
{
yield return new WaitForSeconds(2.2f);
Boom();
}
}
private void Boom()
{
// 播放爆炸音效
AudioManager.Instance.PlayEFAudio(GameManager.Instance.GameConf.Boom);
// 找到可以被我攻击的敌人,并且附加伤害
Vector2 pos = new Vector2(transform.position.x, transform.position.y-1.9f);
List<Grid> grids = GridManager.Instance.GetGrids(pos, 2.25f);
if (grids == null) return;
for (int i = 0; i < grids.Count; i++)
{
if (!grids[i].HavePlant)
{
continue;
}
else
{
grids[i].CurrPlantBase.Hurt(500);
}
}
// 放入缓冲池
if (animator.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1)
{
Dead();
}
}
然后是一个大范围伤害技能,必定清除向日葵等功能性植物,豌豆等普通攻击植物会残余小部分生命值。考虑到难度问题我没有再设计新的技能。
//技能,全场攻击
private IEnumerator DoReleaseFlame()
{
isReleaseFlame = true;
yield return new WaitForSeconds(1.5f);
animator.Play("bossReleaseSkill");
yield return new WaitForSeconds(1.5f);
//飞鸟
GameObject prefab = GameManager.Instance.GameConf.Bird;
for(int i = 0; i < 3; i++)
{
Bird bird = PoolManager.Instance.GetObj(prefab).GetComponent<Bird>();
bird.transform.SetParent(transform);
bird.transform.position = new Vector2(3.8f, 2.8f*Random.Range(-1,2));
bird.StartFly();
yield return new WaitForSeconds(0.8f);
}
yield return new WaitForSeconds(2f);
animator.Play("BossStay");
yield return new WaitForSeconds(50f);
isReleaseFlame = false;
}
public void StartFly()
{
StartCoroutine(CheckFly());
}
/// <summary>
/// 检测爆炸
/// </summary>
/// <returns></returns>
IEnumerator CheckFly()
{
while (transform.position.x > -24)
{
Fly();
yield return new WaitForSeconds(0.04f);
transform.Translate((new Vector2(-15f, 0) * (Time.deltaTime / 1)));
}
Dead();
}
private void Fly()
{
// 找到可以被我攻击的敌人,并且附加伤害
Vector2 pos = new Vector2(transform.position.x, transform.position.y);
List<Grid> grids = GridManager.Instance.GetGrids(pos, 3.25f);
if (grids == null) return;
for (int i = 0; i < grids.Count; i++)
{
if (!grids[i].HavePlant)
{
continue;
}
else
{
grids[i].CurrPlantBase.Hurt(8);
}
}
}
然后是关于花园系统的完善,之前制作的时候只要点击种子就可以种植植物。但是在原版中情况是当僵尸死亡后会有几率掉落植物种子,然后在花园中的物品栏出现植物种子,种植后种子消失。那么为了实现不同场景间的数据交流,本地的数据存储与读取是必须的,我的实现方式是,当僵尸死亡时会掉落卡片,点击卡片会向本地文件写入一个种子的类型。
/// <summary>
/// 死亡,
/// </summary>
public void Dead(bool playOndead=true)
{
if (playOndead)
{
OnDead();
}
switch (Random.Range(0, 10))
{
case 0:case 1:case 2:
//掉落种子卡片
store seed = PoolManager.Instance.GetObj(GameManager.Instance.GameConf.seedCard).GetComponent<store>();
seed.transform.position = transform.position;
seed.Init();
break;
}
// 告诉僵尸管理器,我死了
isSpecialDie = false;
isSpeedUp = false;//不加速
spriteRenderer.color = Color.white; //把颜色恢复正常
ZombieManager.Instance.RemoveZombie(this);
StopAllCoroutines();
currGrid = null;
PoolManager.Instance.PushObj(Prefab, gameObject);
}
当进入花园场景时会读取本地文件获取种子信息,如果有物品栏中就会出现种子,每次点击更新本地文件,当所有种子都被种下后,物品栏中的种子就会消失。
然后我通过读取本地文件的方式保存了花园的当前场景的种植信息,之前只是实现了种植,但是一旦切换了场景之后所有的场景就会重置,所以我在本地文件中保存了植物的种类以及种植的位置。最初我是想将自己写的Grid类序列化,因为整个场景是通过Grid的集合来管理的,每一个植物的信息都保存在Grid中,这样的话读取文件的时候就不需要再现种植操作了,但是在序列化的过车各种报了错,经过测试最终我只能序列化一些比较基本的数据类型,保存的数据如下:
[System.Serializable]
public class Save
{
//我需要存储的数据
public List<PlantType> type = new List<PlantType>();//种子代表的植物种类
public int goldNum;//金币数量
public List<PlantType> GardenGrids = new List<PlantType>();//目前已经中了哪些植物
public List<float> PositionsX = new List<float>();//这些植物在什么位置
public List<float> PositionsY = new List<float>();//这些植物在什么位置
}
最终在加载存档时,我通过植物类型与种植位置将其再次种植,实现了简单读档功能。大体操作如下,特殊场景特殊处理:
//写入文件
public void Save()
{
/*seed sed = GameObject.Instantiate<GameObject>(GameManager.Instance.GameConf.seed, new Vector3(-2.57f, 4.78f, 0), Quaternion.identity, transform).GetComponent<seed>();
sed.CardType = type;
*/
// 如果文件存在,则显示保存成功
if (File.Exists(path))
{
BinaryFormatter bf = new BinaryFormatter();
// 打开一个文件流
FileStream fileStream = File.Open(path, FileMode.Open);
// 调用格式化程序的反序列化方法,将文件流转换为Save 对象
Save save = (Save)bf.Deserialize(fileStream);
for (int i = 0; i < save.type.Count; i++)
{
Debug.Log(save.type[i]);
}
fileStream.Close();
save.type.Add(type);
//想将数据序列化需要将文件create而不是open
fileStream = File.Create(path);
bf.Serialize(fileStream, save);
fileStream.Close();
Debug.Log("保存成功");
}
else
{
Save s = new Save();
s.type.Add(type);
BinaryFormatter bf = new BinaryFormatter();
// 创建一个文件流
FileStream fileStream = File.Create(path);
// 用二进制格式化程序的序列化方法 来 序列化Save对象
// 参数:创建的文件流和需要序列化的对象
bf.Serialize(fileStream, s);
// 关闭流
fileStream.Close();
if (File.Exists(path))
{
Debug.Log("保存成功");
}
}
}
//读取文件
public void Load()
{
if (File.Exists(path))
{
// 反序列化过程
// 创建一个二进制格式化程序
BinaryFormatter bf = new BinaryFormatter();
// 打开一个文件流
FileStream fileStream = File.Open(path, FileMode.Open);
// 调用格式化程序的反序列化方法,将文件流转换为Save 对象
Save save = (Save)bf.Deserialize(fileStream);
// 关闭文件流
fileStream.Close();
for(int i = 0; i < save.type.Count; i++)
{
Debug.Log(save.type[i]);
}
Debug.Log("加载成功");
}
else
{
Debug.Log("文件不存在");
}
}