章国雁
(安徽工商职业学院 信息工程学院,安徽 合肥 231131)
随着游戏行业的发展,中国游戏用户的数量呈现爆发式的增长.新型冠状病毒肺炎疫情虽然影响了一些行业的发展,却促进了游戏行业的进一步发展.随着游戏行业的发展,出现了各类型的游戏,如射击类、休闲类、动作类、RPG类、解密类等.在各类型的游戏中,休闲娱乐类的轻度游戏一直受到用户的欢迎,因为现代社会工作节奏快、压力大,人们希望在工作间隙能够有个适度的放松休闲.休闲类游戏由于较好利用了用户的碎片化时间,其游戏玩法简单、快速上手,因此拥有庞大的用户群体.
近些年,不少学者对使用游戏引擎进行游戏开发做了较多的研究,李昊宇[1]在基于Unity3D的横版过关游戏一文中介绍了经典的过关游戏“马里奥”,并在之前的传统玩法基础上加入了新的玩法和创意;孟子权等[2]在基于Unity3D的环境保护类游戏研究与实现一文中提出了游戏与环境保护主题相结合的创意设计,让用户在玩游戏的同时更多地关注环境保护;张胜男等[3]在基于Unity3D的寻宝游戏的设计与制作一文中实现了以各类生活知识为线索、以拓展知识为目的的寻宝游戏,兼顾了娱乐和教育的内容;陈丽梅等[4]分析了基于Unreal Engine的第三人称动作游戏的设计与实现;郭建军等[5]、岳书丹等[6]、付梦远等[7]对使用Unity3D引擎制作塔防类游戏进行了研究与实现.以上学者的研究成果给游戏开发从业人员提供了新的创意和思路.
本文以Unity3D游戏引擎为开发平台,使用C#程序设计语言,介绍一款三消类休闲游戏“宝石迷阵”的算法设计及实现步骤.游戏以宝石为主题,通过移动交换位置相邻的2个宝石,当相邻的3个或3个以上宝石类型匹配相同时,系统自动消除宝石并给予相应奖励,当奖励达到满足条件时给予通关.
为了增加游戏的趣味性,“宝石迷阵”游戏提供9种不同类型的宝石样式,刚开始运行时,初始界面随机产生7行10列不同类型的宝石矩阵,如图1所示.设计思路:(1)使用普通数组gemstoneBgs[]存储9种不同类型的宝石样式,当宝石被生成时,从数组中随机产生一种宝石样式;(2)使用动态数组gemstoneList来存储游戏运行时产生的7行10列共70个宝石,可以通过一个双重循环语句把宝石顺序存储在动态数组中,由于动态数组可以动态增加或减少数组的大小,可以很好地适应游戏运行过程中宝石被消除和生成时的数量动态变化,具有较好的扩展性.
图1 随机产生不同类型宝石矩阵
要实现宝石的交换,需要同时满足以下条件:(1)第一次点击的宝石和第二次点击的宝石进行交换,第二次点击的宝石不能和第三次点击的宝石交换,即以2次点击为一个交换循环,交换过后重新开始;(2)相互交换的宝石必须在位置上属于水平方向上相邻或者垂直方向上相邻.针对第一个条件,本文通过设定一个临时变量currentGemstone来保存第一次被点击的宝石.当宝石第一次被点击时,判断currentGemstone的值,如果为空,则存储当前被点击的宝石;如果不为空,则进入第二个条件判断.针对第二个条件的解决思路为:当第一次被点击的宝石,即currentGemstone里存储的宝石和第二次被点击的宝石进行交换前,需要计算出两个宝石的行坐标差值和列坐标差值的和;当行差值和列差值二者和为1时,表示两个宝石处于行相邻或者列相邻,可以进行交换操作,否则不满足第二个条件,不能进行宝石交换.
当宝石被初始化生成或者被点击交换后,程序需要检测是否有相同类型的宝石,发现有相同的宝石则进行消除.相同类型宝石的判断条件为:同一行里面的宝石超过3个相邻的宝石为相同类型;或者同一列里面的宝石超过3个相邻的宝石为相同类型.针对以上条件,程序实现的思路为:(1)首先判断每一行的宝石.通过使用双重循环语句,外循环为遍历每一行,内循环为遍历当前行的每一列,取出当前列的宝石和当前列后一位(即当前列的值+1)的宝石、当前列的宝石和当前列后两位(即当前列的值+2)的宝石俩俩进行比较,如果宝石的类型相同,则判断参与比较的相邻3个宝石为当前行相同类型的宝石,暂时保存相同类型的宝石到matchesGemstone动态数组中.(2)接着判断每一列的宝石.再次通过使用双重循环语句,外循环为遍历每一列,内循环为遍历当前列的每一行,取出当前行的宝石和当前行上面一位(即当前行的值+1)的宝石、当前行的宝石和当前行上面两位(即当前行的值+2)的宝石俩俩进行比较,如果宝石的类型相同,则判断参与比较的相邻3个宝石为当前列相同类型的宝石,暂时保存相同类型的宝石到matchesGemstone动态数组中.
通过程序检测到相同类型的宝石后,不能马上消除宝石,因为可能会存在3个以上相邻的宝石或者行列交叉都相同的宝石情况,程序需要先暂时保存所有相同的宝石到matchesGemstone动态数组,待所有行和所有列全部检测结束后,统一进行消除.当相同的宝石被消除后,游戏需要及时进行宝石的增补,便于玩家进行下一次的操作.宝石的消除和增补功能实现思路为:(1)循环遍历保存着相同类型宝石的matchesGemstone动态数组,消除遍历到的当前宝石;(2)读取当前被消除的宝石所在列,循环遍历该列的每一个宝石,从当前宝石的上一行开始,向下一行移动一个位置,从下往上依次对该列的所有宝石做位移操作后,在该列的最上方空出一个位置等待宝石的增补;(3)实例化一个新的宝石物体,从宝石样式数组gemstoneBgs[]中随机取出一个宝石,放在空出的位置上.通过以上步骤实现了宝石消除和增补的算法.
基于上述的程序算法和实现思路,在Unity3D引擎中实现宝石迷阵游戏案例的制作,其关键节点的实现步骤如下:
在Unity3D引擎的Project窗口中,导入本案例的资源包,包含有9张不同效果的宝石图片和1张游戏背景图片,背景音乐和宝石交换、匹配、销毁、生成时的各种音效.将9张宝石图片类型从普通Texture修改为精灵Sprite(2D and UI)类型.为了增强游戏界面中宝石的显示效果,适当放大每个宝石的大小,依次修改每个宝石图片X轴和Y轴的缩放项数值为1.2,保存每个宝石图片为预制体,如图2所示.
图2 9种类型的宝石预制体
游戏初始界面需要生成一个7行10列的宝石矩阵,如图1所示.通过一个双重循环语句保存生成的宝石,外循环语句保存每一行的宝石,内循环语问保存当前行中遍历的每一列宝石,其部分核心代码如下:
public Gemstone gemstone;//宝石变量
public int rowNum = 7;//行数
public int columnNum = 10;//列数
public ArrayList gemstoneList;//动态数组,用来保存生成的宝石
void Start () {
gemstoneList = new ArrayList ();//数组的初始化
//双重循环语句顺序存储每一个生成的宝石
for (int rowIndex = 0;rowIndex < rowNum;rowIndex++) {
//临时动态数组,用来存储当前行的每一列宝石
ArrayList temp = new ArrayList();
for(int columnIndex=0;columnIndex < columnNum;columnIndex++){
Gemstone gemstone = AddGemstone(rowIndex,columnIndex);
temp.Add (gemstone);
}
gemstoneList.Add (temp);
}
}
每个宝石在生成时需要随机产生一个类型.通过实例化函数Instantiate()生成宝石,通过随机读取宝石类型数组gemstoneBgs[]的值产生不同的宝石.其部分核心代码如下:
//生成宝石函数
public Gemstone AddGemstone(int rowIndex,int columnIndex){
Gemstone gemstone = Instantiate (gemstone) as Gemstone;//实例化生成宝石
gemstone.transform.parent = this.transform;//指定位置
gemstone.RandomCreateGemstoneBg();//生成随机的宝石类型
return gemstone;
}
//生成随机的宝石类型函数
public void RandomCreateGemstoneBg(){
gemstoneType = Random.Range (0,gemstoneBgs.Length);//随机产生一个0至数组长度之间的整数值
gemstoneBg = Instantiate(gemstoneBgs[gemstoneType]) as GameObject;//产生一种宝石的样式
gemstoneBg.transform.parent = this.transform;//指定位置
}
当宝石第一次被点击时,需要存储到currentGemstone变量中,等待和下一次被点击的宝石进行交换,同时在交换之前需要判断两者是否在同一行相邻或者同一列相邻,其部分核心代码如下:
//点击宝石函数
public void Select(Gemstone gemstone){
if (currentGemstone == null) {//第一次点击
currentGemstone = gemstone;//保存当前点击的宝石
currentGemstone.isSelected = true;//标记该宝石为已选中
return;
}else{//第二次点击
if( Mathf.Abs(currentGemstone.rowIndex - gemstone.rowIndex)+Mathf.Abs(currentGemstone.columnIndex - gemstone.columnIndex) == 1 ){
//如果宝石相邻,则进行交换
StartCoroutine (ExangeAndMatches(currentGemstone,gemstone));
}else{
audio.PlayOneShot(errorClip);//播放交换失败的音效
}
currentGemstone.isSelected = false;//重置标志位
currentGemstone = null;//清空
}
}
根据上述2.3节的判断条件,程序先后检测行方向和列方向的所有宝石类型,即宝石的gemstoneType字段值,该字段为整型,如果参与比较的宝石gemstoneType相同,则为相同类型的宝石.以检测每一行的宝石为例,其部分代码如下:
bool CheckHorizontalMatches(){//实现检测水平方向的宝石类型
bool isMatches = false;
for (int rowIndex = 0;rowIndex < rowNum;rowIndex++) {//遍历行
for(int columnIndex =0;columnIndex < columnNum - 2;columnIndex++){//遍历列
//判断相邻宝石的gemstoneType值
if((GetGemstone (rowIndex,columnIndex).gemstoneType == GetGemstone (rowIndex,columnIndex+1).gemstoneType ) && (GetGemstone (rowIndex,columnIndex).gemstoneType == GetGemstone (rowIndex,columnIndex+2).gemstoneType )){
AddMatches (GetGemstone (rowIndex,columnIndex));//存储相同宝石
AddMatches (GetGemstone (rowIndex,columnIndex+1));//存储相同宝石
AddMatches (GetGemstone (rowIndex,columnIndex+2));//存储相同宝石
isMatches = true;
}
}
}
return isMatches;
}
程序检测完成,如果发现相同类型的宝石,则需要进行消除操作,使用循环语句遍历matchesGemstone动态数组,删除该数组保存的每一个元素(宝石).宝石删除后,需要移动宝石的位置进行填充,并生成新的宝石.以消除为例其部分核心代码如下:
//遍历matchesGemstone动态数组消除宝石
void RemoveMatches(){
for(int i=0;i< matchesGemstone.Count;i++){
Gemstone gemstone = matchesGemstone[i] as Gemstone;
RemoveGemstone(gemstone);//调用消除宝石函数
}
matchesGemstone = new ArrayList ();
}
宝石迷阵游戏案例注重三消类游戏的算法设计与实现过程,使用Unity3D游戏引擎作为开发平台,后期可以发布到普通PC电脑、Mac电脑、安卓、IOS、平板、游戏主机等多种平台,使本游戏案例具备较好的跨平台特性.本案例游戏算法的设计思路对于游戏开发的教学和行业应用有积极的促进作用.