CocosCreator只谈实战系列1——成语游戏篇
上一篇主要分享了 成语游戏的关卡编辑器实现,经过了关卡编辑器的开发,
我们大致理清了成语游戏关键的数据结构和对象关系:
1, 词条基本数据 (对应 IdiomData.js)
描述成语词库里的一条原始数据,被成语对象引用
2, 成语对象 (对应 Idiom.js)
描述关卡中编辑好的每一条成语, 同时会记录成语所占用的格子
3, 格子对象 (对应 Grid.js)
描述编辑区每个格子的状态与行为,如格子被使用,可从格子引用到成语对象
4, 关卡对象 (对应 Level.js)
关卡对象组织了格子和成语对象,并且负责对刷成语和换成语/删成语/去字/保存加载等
在编辑器开发过程中,我们已经实现了上面4个核心对象,而这些源码文件都可以直接复用到游戏项目,
因为它们的数据结构和算法是完全一致的,只是游戏项目中,对象的表现需要做更精细化的处理。
接下来,我们主要分享游戏部分的实现重点。
一. 显示成语区域和填字区域
成语区域是 9X9 的格子布局,我们制作了一个格子Prefab, 一次性创建81个实例将背景区域铺满。
这样的好处是我们不需要在每次关卡加载时去创建新实例和销毁,只需要根据每个关卡的数据去复用格子对象,
控制和刷新每个格子的状态即可, 如果我们将显示控制条件关闭,看下面这张图就清楚了:

格子Prefab:


下方填字区域也同样复用Grid 预设,遍历关卡中的成语对象,将带空格属性的字创建为填字对象

for (let j = 0; j < idiom.grids.length; j++)
{
//创建下方可选格子
if (idiom.grids[j].isSpaceGrid && this.isInBottomSelGrids(idiom.grids[j].gridId) === false)
{
let selGrid = cc.instantiate(this.bottomGridPrefab);
let gridComponent = selGrid.getComponent("Grid");
//格子设置为填字模式
gridComponent.setSelectMode(true);
gridComponent.setChar(idiom.grids[j].char);
gridComponent.gridId = idiom.grids[j].gridId;
this.bottomSelGrids.push(selGrid);
}
}
//填字格子需打乱顺序再addChild
this.bottomSelGrids.sort(() => {
return Math.random() > 0.5 ? -1 : 1;
});
二. 填词
填词逻辑触发放在Grid的touchEvent上
registTouchEvent: function ()
{
this.node.on(cc.Node.EventType.TOUCH_START, (event) => {
if(this.node.scale!==1 || Grid.disappearGrid!==null)
return;
if (this.isSelectMode) {
Audio.instance.playFillChar();
Game.instance.level.fillGrid(this);
}
fillGrid方法具体实现:
//填字逻辑
fillGrid: function (grid)
{
//设置charLabel显示
this.charLabel.node.active = true;
this.setCharLabel(grid.char);
//设置其它显示属性...
//填字格子消失动画
grid.bottomGridDisappear();
//检查格子引用的成语
let fillError = false;
for (let i = 0; i < this.idioms.length; i++)
{
if (this.idioms[i].checkIdiomFillResult())
{
//填词成功动画播放
this.idioms[i].onFillRight();
}
else
{
//填词有错误
if (this.idioms[i].getFilledCharNum() === 4)
{
//填错动画播放
this.idioms[i].onFillError();
fillError = true;
}
}
onFillRight() 和onFillError() 中主要实现 填词正确和错误的动画播放, 动画对象添加在单个格子上,成语对象
持有所占用格的引用,在正确和错误情况下都做全部格子的整体动画播放


fillGrid 剩下的流程,主要是做 自动选中下一个填字格 和 过关条件判断
if (!fillError)
{
//自动跳下一格
let nextGrid = this.getNextSpaceGrid();
if (nextGrid)
Game.instance.level.selectGrid(nextGrid);
else
{
//当前无可选格,取消选择状态
Game.instance.level.selectGrid(null);
}
}
else
{
//停在错词的格子不作处理
}
let levelPass = true;
for (let i = 0; i < Game.instance.level.idioms.length; i++)
{
if (!Game.instance.level.idioms[i].filled)
{
//如果还有成语未完成则break
levelPass = false;
break;
}
}
if (levelPass)
{
//过关,显示结算界面...
成语游戏主体的实现差不多就是以上内容,编辑器部分的代码复用省了不少代码工作。
这里也顺便分享一个心得,休闲小游戏开发周期较短,一般我们会将编辑器投入占比作为是否单独开发编辑器的判断依据,如果编辑器开发不超过项目整体周期30%,通常都单独开发编辑器,如果大于这个占比,则需要考虑项目类型和编辑器在以后被复用的可能性,满足这一条件的话,也能摊薄编辑器所投入的开发成本。
成语项目则比较特殊,由于关卡数量太多,立项时直接先制作了编辑器,其实益智类游戏很多都是这样的情况。

