你的生命是人类的货币——用Unity实现《Seed》(二)

作者:Yumir
哈喽大家好我是Yumir。
上次画了一个世界树动画【封面图】,这次我画了一个歌唱家被攻击时对攻击者进行迷惑的动画,非常的鬼畜,调动画的时候正好在听Doubt & Trust,兴致上来顺手做了个视频。
这么鬼畜的场景我一定要和大家一起分享。
【魔性歌唱家】做游戏的时候一定要听歌 ‐ Made with Clipchamp
咳,安静安静,我们是正经的游戏复刻分享。
今天我们继续Seed的制作,在上一篇文章中我们对该游戏做了简单的分析,以及部分素材的制作,现在我们继续实现更多的功能。
上一篇的传送门:你的生命是人类的货币——用Unity实现《Seed》(一)
--------------------------------肃静分割线-------------------------------------------
首先我们今天的目标有:
1、种树,并且树之间要有层级关系。


3、植物预制体的动画状态机以及各种逻辑等等...
总之我们今天要把植物相关的功能都做完就是了- -
------------------------------------开始制作!-----------------------------------------
游戏的开始我们需要种下第一颗树,那么我们需要实现种下第一个树的逻辑。在上一篇文章中已经制作了初始树的预制体,初始树可以直接在沙地(反正就是不能种树的地)上种植,之后的树则只能在绿地上种植,所以首先我们要添加三个tag用于区分:沙地、绿地和树荫。

种树的逻辑基本上就是发射射线,然后在碰撞点生成一个对应的预制体(这部分游戏逻辑惯例是在游戏管理器(GameManager)上实现)。
//射线碰撞检测 hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);//生成预制体Instantiate(treePrefab, new Vector3(hit.point.x, hit.point.y, hit.point.y), Quaternion.identity);
是我们在场景中种下了我们的第一棵树,此时我们需要将UI中世界树种子的按钮隐藏,显示普通树的种子,这当然很简单,GameObject.SetActive嘛,但是我们除了世界树之外还有很多其他树哦,所以我用了一个数组将所有的种子按钮按顺序存储了起来,方便管理。

想要快速实现像我这样全场动作整齐划一的乖乖Button布局的同学可以用“Horizontal Layout Group”组件快速布局,再将组件去掉哦。
UI是做好了,怎么控制不同按钮种不同的树呢,而且还要实现变异!面对问题我们要学会简化,按钮的目的是种树,那么我们首先要让按钮”有树可依“!
为了使目前测试的UI按钮“有树可依”,我们先定一个小目标:制作四个预制体。
-----------------------------------树的预制体----------------------------------------
考虑到有同学可能会对“状态机”充满疑惑,这里先放一个传送门,便于同学们学习:
【Unity教程】和十四一起做游戏——斜45度仿黑魂作品(2)人物移动控制
这里因为我不想每颗树都做一个动画状态机,所以把网格部分的动画用协程实现了,这时候又会发现树只剩下一个图片切换的动画,而且不方便调整树发芽的时间,所以我们其实可以干脆把这个状态机取消使用,用协程来实现。
实现效果不如动画来得流畅,但问题不大,同学们也可以试试通过延时触发实现。
public IEnumerator TreeAnima()
{
treeGO.SetActive(false);
budGO.SetActive(true);
yield return new WaitForSeconds(sproutTime);
treeGO.SetActive(true);
budGO.SetActive(false);
float v = maskMaxScale / (maskTime / 0.1f);
while (greenMaskGO.transform.localScale.x < maskMaxScale && greenMaskGO.transform.localScale.y < maskMaxScale)
{
yield return new WaitForSeconds(0.1f);
greenMaskGO.transform.localScale = new Vector3(greenMaskGO.transform.localScale.x + v, greenMaskGO.transform.localScale.y + v);
}
}
由于“歌唱家”异于常树,需要多一个攻击攻击者的动画,所以在制作歌唱家预制体的时候需要新建一个歌唱家独享动画状态机、写一个新的脚本,该脚本继承原本的树通用的脚本,并重写被攻击方法。
//歌唱家的被攻击方法,其实差别只有多了一个概率攻击
public override void BeAttack(float attackData)
{
if (treeLife > attackData)
{
if (Random.Range(0, 2) == 0)
{
animator.SetTrigger("attack");
//调用攻击者的被攻击
}
else
{
animator.SetTrigger("beAttack");
}
treeLife -= attackData;
}
else
{
animator.SetBool("die", true);
Destroy(greenMaskGO);
Destroy(shadowGO);
}
}
不要忘了树被砍掉之后的小树桩哦~

--------------------------叮,您的按钮有树可依啦---------------------------------
有了树的预制体之后我们就可以开始我们的造树大业啦,首先我们要知道接下来要种的是什么树,这里设计了一个树的枚举类型,因为树还有变异机制,所以我们的做法需要稍微复杂一点点,我的做法是这样的:
1、声明一个用于标识按钮是否显示的数组和一个存储预制体名称的数组,以及一个树的枚举类型:

2、按钮脚本中持有一个树的枚举类型,当按钮按下(并且CD结束)的时候调用GameManager对应的方法,对GameManager持有的当前输入的树枚举值进行赋值。
3、当GameManager中的树枚举值不为None的时候(GameManager中的树枚举值为None时代表没有持有树种),玩家点击地面可以种树,但是长出来的树不一定是此时持有的树的类型,这里就需要进行“变异”
树的变异:
树的变异设计在上一篇文章中提及过,原版游戏的树的设定如下:

这里我将树的类型和变异都进行了简化,去掉了最后一级变异,我的变异逻辑是:
1、当树的变异目标已经全部存在,则直接不变异,否则进入变异方法;
2、利用随机数变异,如果随机到的物种已经存在,也返回不变异,反之返回变异结果;
这里我对所有树的变异抽象成了一个方法,根据每个会变异的树声明一个枚举类型数组,数组的第一位是该树自己的枚举值,其他位置是树可以变异成的类型。
private SeedType Variation(SeedType[] vars)
{
int num = Random.Range(0, vars.Length);
if (!seedIsGet[(int)(vars[num])])
{
return vars[num];
}
return vars[0];
}
也有更加适合新手阅读的分别变异的方法,但是在文章中展示的话篇幅就太长了,这段看不懂的同学可以下载我Github上的源代码配合理解。
现在你拿到了树的种子的枚举类型,只需要通过“Resources.Load<GameObject>("Prefabs/" + treePrefabName[(int)seedType])”就可以得到树的预制体啦。
那么我们要把树种在哪里呢?如果直接种在射线碰撞的位置就会出现种在后面的树却挡住了前面的树的情况,这就是我们的目标之一啦。
最后的解决方法很简单,就是改变树的z轴,这里我直接使用了树的y值,另一个办法是修改树的layer,但是这样要涉及到链表操作,非常的费力不讨好,当然我的源代码也是有链表实现方法的,下面是种树的方法。
因为逻辑大致一致,所以我将种世界树和其他树的方法都集合到了同一个方法里,在调用的时候根据是否种下过世界树输入tag标签的名字,为了知道是否已经种下世界树,我又添加了一个布尔值。
public void TreePlanting(string tag)
{
hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);
if (hit.transform != null && hit.transform.tag == tag)
{
if (seedType == SeedType.FristSeed)
{
isStart = true;
fristTreeItem.SetActive(false);
seedIsGet[0] = true;
buttonGO[0].SetActive(true);
buttonGO[0].GetComponent<SeedItemBtn>().StartCD();
}
else
{
buttonGO[(int)seedType].GetComponent<SeedItemBtn>().StartCD();
if (IsVariation(ref seedType))
{
//变异音效//变异UI提示//显示对应Btn,开始CD
buttonGO[(int)seedType].SetActive(true);
seedIsGet[(int)seedType] = true;
}
buttonGO[(int)seedType].GetComponent<SeedItemBtn>().StartCD();
}
index.SetActive(false);
GameObject go = Instantiate(Resources.Load<GameObject>("Prefabs/" + treePrefabName[(int)seedType]), new Vector3(hit.point.x, hit.point.y, hit.point.y), Quaternion.identity);
seedType = SeedType.None;
}
}
这样操作的结果种树的3D结构就是下图这个样子的:

那么我们今天的大目标小目标就都实现啦~
什么?你说我还没说歌唱家的动画是怎么做的?
就是Sprite动画啊,上上篇文章教过了,快去补哇!

考虑到同学们会跟着做所以决定还是把目前使用的素材上传到百度网盘,只能作为学习用途哦。

https://pan.baidu.com/s/1epj7TiJam46LA5FOHn3h4w#list/path=%2F
------最后的分割线------
欢迎加入游戏开发群欢乐搅基:869551769
有意向参与线下游戏开发学习的读者可戳这里进一步了解:http://levelpp.com/