还没学到设计模式,所以自己和同学捣鼓了一个,请各位大神多多指教!
源码已经添加到我的github,跳转至https://github.com/HMY777/GameProject.git下载
整个项目的压缩包下载:
链接:https://pan.baidu.com/s/1pMB82nCCOSgLOP_UL8wxmA
提取码:w8a1
展示卡牌
卡牌展示效果
打出卡牌
敌人释放技能
需求
制作一款卡牌游戏最核心的地方就是卡牌,游戏中有生成卡牌,获得卡牌,销毁卡牌,查看卡牌,打出卡牌,卡牌音效等几大功能,但是最核心的可以分成三个:卡牌的生成与销毁,卡牌的特效,卡牌的查看,下面我们就遵循这三个核心来设计卡牌类
制作
- 将卡牌做成Prefab,这里面包括卡牌名称,信息,卡牌框图片,卡牌图片
- 我们设计了一个Card类来存放所有卡牌的基础信息,这个类不继承MonoBehaviour,因为它不需要挂载在某个物体上,这里面包括:名称,介绍,类型,效果,卡牌框图片,卡牌图片,卡牌ID
- 需要一个脚本用来对卡牌实例化的操作,因为每张卡牌都不一样,所以生成时候都需要再设置一下它的信息,这个脚本我们叫CardInstance
- 需要一个脚本来管理卡牌的抽取,丢弃,销毁等,同时它还有 当前使用了多少张卡牌,卡牌总数,当前卡牌数等等所有游戏运行时候卡牌的信息,这个脚本我们叫CardManager,它是个单例脚本
- 需要一个脚本来对卡牌的展示进行操作,如鼠标放上去卡牌会放大,打出卡牌时会有音效等等,这个卡组我们叫做CardListen
上述脚本编写完以后,把CardInstance和CardListen添加到卡牌的Prefab上,设置卡牌信息。
流程
整个大体流程是这样的,玩家选择英雄,CardManager设置初始化卡组,进入游戏,玩家抽牌,CardManager生成一张卡牌,并且传递Card类的信息给CardInstance,然后CardInstance在Start()方法中设置自己的所有信息,就有一张卡牌生成了,接着打出一张卡牌的时候,就播放动画移到弃牌堆,然后摧毁这张卡牌,这样就能实现卡牌的抽取和打出了。
- 选择英雄并初始化不同卡组
- 手动初始化卡组
抽牌:
- CardManager实例化一个卡牌Prefab
- 卡牌Prefab上的CardInstance被调用,然后设置自身的信息
弃牌:
- 卡牌打出后,通知CardManager,然后CardManager将这种卡牌的信息放入弃牌堆,播放卡牌动画,然后摧毁这张卡牌
一些问题
我们根据选择的职业生成了不同的初始卡组,然后把它存放在一个Card类型的数组里面,以后增加删除都可以用它来操作,注意它的属性是Card,就是之前说的静态脚本
public Card[] CardGroup;
怎么实现抽牌和弃牌呢?
我们先想到,用Random.Range来随机获得一张卡牌,但是随机又有新的问题,如果运气不好,获取到了已经抽过,或者已经打出的卡牌怎么办?这里我们用了一个List来解决这个问题
抽牌
public List<int> CardToDrugList;
int类型的List,里面存放的是待抽取的牌的数组序号
想象一下你现在有6张牌,那么这个list的Index和value就为
index 0 1 2 3 4 5
value 0 1 2 3 4 5
假设我们抽到2号牌,那么list的变化就为
index 0 1 2 3 4
value 0 1 3 4 5
其中2号牌从list中删除了,从上面我们可以看出,真正要传给CardGroup的是list的value,使用这个方法就能避免抽到重复的牌了。
3. 初始化这个List,从0开始到卡牌总数,
for (int i = 0; i < HowManyCard; i++) { CardToDrugList.Add(i); }
这里要注意,Unity数组和List似乎必须要在方法里面初始化,如果在Start里面初始化的话,接下来使用的过程中会报空指针错误。
-
现在假设有15张牌,我们从中抽取一张,那么先用Random.Range来获得一个随机数,假设是9,
int j = Random.Range(0, CardToDrugList.Count);
-
然后初始化CardGroup里面的第CardToDrugList中的第j个元素,这么说可能有点绕,要好好想一下
SetCard((CardGroup[CardToDrugList[j]]));
前面说了,因为List里面存放的是数组序号,而且经过几次抽牌后,List的下标会改变,导致不能跟List下标对应的值一一对应,但是不管它改变多少次,它里面存放的值始终是不变的,我们只要取得它里面的值(即为卡组中的第几张牌),然后再传递给卡组就行了 -
如果抽完牌了,就初始化List就行了,因为CardGroup是始终不变的
##弃牌
我们每打出一张牌,就把这张牌的添加到弃牌堆里面,这里不用用到int类型的LIst,直接用Card类型的List,每次打出一张牌就添加进这个List里面就行了
public List<Card> CardToLoseList;
查看卡牌
战斗中我们常常需要查看卡组和弃牌堆,这里面怎么操作呢?
查看抽牌堆
用我们之前提到的抽牌List,获取LIst的长度,然后按顺序实例化数组中的List序号的卡牌,接着设置它的位置和父物体就行了。由于是用生成的方法,所以每次都要先清空已经生成的卡牌,很简单,直接Destroy就行了。
/// <summary>
/// 生成预制卡片,设置信息
/// </summary>
public void CreatCardToSee()
{
DestroyCardToSee();
//获得待抽牌长度
CardGroupLength = CardManager.Instance.CardToDrugList.Count;
for (int i = 0; i < CardGroupLength; i++)
{
//实例化生成一个物体
GameObject go = GameObject.Instantiate(CardToSeePrefab, Vector3.zero, Quaternion.identity);
//设置Card属性
go.GetComponent<CardToSeeInstance>().card = CardManager.Instance.CardGroup[CardManager.Instance.CardToDrugList[i]];
go.transform.GetChild(1).transform.position = new Vector3(0, -45, 0);
//TextTitle.transform.position = new Vector3(0, -45, 0);
go.GetComponent<CardToSeeInstance>().SetAllInfomation();
//设置到CardGroupPanel上
go.transform.SetParent(CardPanel.GetComponent<Transform>());
// CardManager.Instance.CardGroup[CardManager.Instance.CardToDrugList[i]];
}
}
/// <summary>
/// 清空已有的卡组信息
/// </summary>
public void DestroyCardToSee()
{
GameObject go = CardPanel;
for (int i = 0; i < go.transform.childCount; i++)
{
Destroy(go.transform.GetChild(i).gameObject);
}
}
查看弃牌堆
弃牌堆要用到之前的弃牌LIst,这个简单,因为它是Card类型的List,我们直接生成就行了
/// <summary>
/// 生成预制卡片,设置信息
/// </summary>
public void CreatLoseCardToSee()
{
DestroyLoseCardToSee();
//获得弃牌List长度
CardGroupLength = CardManager.Instance.CardToLoseList.Count;
if(CardGroupLength>0)
{
for (int i = 0; i < CardGroupLength; i++)
{
//实例化生成一个物体
//GameObject go = GameObject.Instantiate(CardToSeePrefab, Vector3.zero, Quaternion.identity);
//go.GetComponent<CardToSeeInstance>().card = CardManager.Instance.CardGroup[CardManager.Instance.CardToLoseList[i]];
//go.GetComponent<CardToSeeInstance>().SetAllInfomation();
//go.transform.SetParent(GameObject.Find("Lose_CardPanel").GetComponent<Transform>());
GameObject go = GameObject.Instantiate(CardToSeePrefab, Vector3.zero, Quaternion.identity);
go.GetComponent<CardToSeeInstance>().card = CardManager.Instance.CardToLoseList[i];
go.GetComponent<CardToSeeInstance>().SetAllInfomation();
go.transform.SetParent(Lose_CardPanel.GetComponent<Transform>());
}
}
}
卡牌效果的实现
这里是我们没做好的地方,我们原来想的是即将打出的卡牌和卡牌指向的目标之间需要一个消息脚本来给他们发消息,传递当前卡牌效果,于是就能把所有的卡牌信息写在那个脚本里面,然后每次监听鼠标,每次鼠标选中一张卡牌指向一个目标的时候,我们就发消息给这个消息脚本,传递选中的卡牌和目标,然后根据卡牌的ID发动效果。
但是当时没想好,直接把每张卡牌的效果写在了人物目标身上,导致这个脚本有点逻辑不通(运行时没问题的,但是自己感觉很不舒服),过程还是一样,直接传卡牌ID到人物身上就行了