成语json_cocos creator实战(2)成语小秀才ts版
生活随笔
收集整理的这篇文章主要介绍了
成语json_cocos creator实战(2)成语小秀才ts版
小编觉得挺不错的,现在分享给大家,帮大家做个参考.
1 分析
公司要求做h5小游戏之前,想要做的一款类似成语小秀才的小游戏。学了一段时间ccc后回头填坑,尝试仿制一波,刚好发现论坛有套开源的ui素材。花了两天做的demo。做完后发现最难的是生成随机关卡,由于益智类小游戏动辄几百上千关,编写较高质量的关卡随机算法还是很有难度的。
- 玩法:每个成语最多空缺两个位置,选择对应的词填入成语中,全对即可过关。
界面分三部分,顶部关卡信息、成语区、选词区。
- 成语区实际上是9*9的布局,共81的格子,只显示了有词的格子,开启调试模式可以显示所有的格子。
- 选词区显示成语中空缺的词
2 代码结构
2.1 关卡数据结构
正式上线的话,需要开发服务端用于返回关卡数据和保存用户信息。客户端通过http请求获取关卡数据,同时可以上传分数等信息。demo中没有用到服务端,直接本地挂载关卡数据,关卡数据保存在json文件中。
id ---> 成语id,对应成语库中的成语grids ---> 保存成语中四个字的位置
id ---> 所处的格子位置
space ---> 该字是否为空缺
{
"id": 4959,
"grids": [
{
"id": 18,
"space": false
},
{
"id": 27,
"space": true
},
{
"id": 36,
"space": false
},
{
"id": 45,
"space": true
}
]
}
2.2 词条数据对象
用一个类来描述成语词条的基本数据,它对应词库中的一条成语。
// IdiomData.tsexport class IdiomData {
public id: number = 0; // 词条id
public chars: string = null; // 完整成语,例如"一马当先"
public pinyin: string = null; // 词条拼音
public note: string = null; // 词条出处和释义
}
2.3 词条对象
用一个类来描述关卡中出现的每一条成语对象。
- 记录占用的格子
- 保存词条数据对象
export class Idiom {
public grids = []; // 记录占用的格子
public data = null; // 词条数据
public constructor(grids, idiomdataObj) {
this.data = idiomdataObj;
for (let i = 0; i this.grids.push(grids[i]);
}
}
}
2.4 格子对象
用一个类来描述每个格子的状态和行为。
- 记录格子id
- 该格子使用状态
- 保存完整的成语数据
- 格子上需要填写的字符
- 保存被哪些词条对象使用
- 格子是否为空缺状态
- 格子填词是否成功,用来判断填词是否成功
- 当前格子上的词id
public gridId: number = 0; // 格子id
public isUsed: boolean = false; // 是否是被使用的格子
public data: string = null; // 完整的成语
public char: string = ""; // 使用的成语字符,单个字
public idioms = []; // 反向保存idiom引用 这个是用来保存这个格子被哪些词条引用
public isSpaceGrid: boolean = false; // 是否是 被 去字 状态
public isSelectMode: boolean = false; // 是否是 填字 模式
public isSuccess: boolean = false; // 标注该位置词 是否正确
public currentId: number = 0; // 记录当前格子上 选词 的id,用来判断回退
// 重置 格子数据,关卡切换复用
public resetGrid() {
……
}
// 格子注册监听事件
public addListener(fn: Function, target) {
this.node.on(cc.Node.EventType.TOUCH_END, fn, target);
}
// 格子移除监听事件
public removeListener(fn: Function, target) {
this.node.off(cc.Node.EventType.TOUCH_END, fn, target);
}
2.5 关卡对象
用一个类来描述游戏关卡。关卡对象读取关卡json文件,返回词条对象数组。http请求获取关卡数据可以放在这里。
// Level.tspublic initLevelData() {
let idioms = this._levelData.json["idioms"];
let idiomArr = [];
for (let i = 0; i let _id = idioms[i].id
let idiomdataObj = new IdiomData(this._jsonData.json[_id]);
// 词条对象 存词条数据,占位
let idiomObj: Idiom = new Idiom(idioms[i].grids, idiomdataObj);
idiomArr.push(idiomObj);
}
return idiomArr;
}
3 界面设计
3.1 节点结构
节点结构3.2 结算界面
展示词条成语,提供下一关切换按钮。
节点结构3.3 词条详情界面
展示词条拼音、释义、出处。
词条详情游戏逻辑
4.1 成语区
- 制作一个格子的预制体,一次性创建81个格子对象。
private init() {
// 9*9=81块 rows col
for (let index = 0; index 81; index++) {
let node = cc.instantiate(this.piece_prefab);
node.parent = this.qipan;
let tile: Grid = node.getComponent(Grid);
tile.gridId = index;
if (!this.isDebug) {
tile.hide();
}
this.tiles.push(tile);
}
…… ……
}
- 遍历成语对象后,给指定格子位置添加对应的成语字符并绑定数据。从成语对象中拿到成语,将每个成语都分割成单独的字符存入数组中,将字符循环插入到格子中,如果该位置的格子为去字状态,就把该位置的字符隐藏,并记录空缺字符的id和字符用于初始化选词区。
for (let i = 0; i let chars = idiomArr[i].data.chars;
let grids = idiomArr[i].grids;
// 分割拿到单独的字符
let arr = chars.split("");
for (let j = 0; j let gid = grids[j].id;
let space = grids[j].space;
this.tiles[gid].bg.node.active = true;
this.tiles[gid].word.node.active = true;
// 设置 格子信息
this.tiles[gid].gridId = gid;
this.tiles[gid].char = arr[j];
this.tiles[gid].data = chars;
this.tiles[gid].idioms.push(idiomArr[i]);
// 判断是否为 去字 状态,如果是去字,那么就隐藏上面的word
if (space) {
this.tiles[gid].isSpaceGrid = true; // 空格子
if (!this.tiles[gid].isSelectMode) { // 判断 该空是否为选词
this.tiles[gid].isSelectMode = true;
this.tiles[gid].bg.spriteFrame = this.word_tile;
// 添加点击事件
this.tiles[gid].addListener(this.onSpaceGridClick, this);
this.selectWords.push({"id": gid, "char": arr[j]});
}
} else {
this.tiles[gid].word.string = arr[j];
this.tiles[gid].bg.spriteFrame = this.word_normal;
}
}
}
- 空位置触摸事件。空位置需要实现两个功能:显示字符和回退选词区。
if (grid.isUsed) {
let char = grid.word.string;
this.tiles[gridId].isUsed = false;
this.tiles[gridId].word.string = "";
this.tiles[gridId].isSuccess = false;
this.tiles[gridId].bg.spriteFrame = this.word_selected;
this.currentSelectGrid = gridId;
let currentid = grid.currentId;
for (let i = 0; i this.selectNode.length; i++) {
if (this.selectNode[i].gridId === currentid) {
this.selectNode[i].node.getComponent(cc.Animation).play("show_word");
}
}
}
4.2 选词区
- 复用格子预制体,遍历记录空缺字符的selectWordsArr数组里的数据。
private initSelectGroup() {
for (let s = 0; s this.selectWords.length; s++) {
let gid = this.selectWords[s].id;
let char = this.selectWords[s].char;
let node = cc.instantiate(this.piece_prefab);
node.parent = this.selectGroup;
let tile: Grid = node.getComponent(Grid);
tile.gridId = gid;
tile.char = char;
tile.word.string = char;
tile.addListener(this.onSelectGridClick, this);
this.selectNode.push(tile);
}
}
- 为了可玩性,随机打乱selectWordsArr里的数据,将选词打乱。
this.selectWords.sort(() => {
return Math.random() > 0.5 ? -1 : 1;
});
- 选词区触摸回调,判断选词与空缺位置是否匹配,如果匹配将该格子的isSuccess设为true,表明该位置字符正确。这里判断字符正确的依据不能通过比较id的方式,因为有可能选词区出现若干个相同字符的格子,所以选择任意一个都要能达到字符正确的效果。
private onSelectGridClick(e) {
…… ……
// 隐藏该选词
e.target.getComponent(cc.Animation).play("hide_word");
if (this.tiles[this.currentSelectGrid].isUsed) {
let char = this.tiles[this.currentSelectGrid].word.string;
let nowId = this.tiles[this.currentSelectGrid].currentId;
this.selectNode[nowId].node.active = true;
this.tiles[this.currentSelectGrid].word.string = grid.char;
} else {
this.tiles[this.currentSelectGrid].word.string = grid.char;
this.tiles[this.currentSelectGrid].isUsed = true;
this.tiles[this.currentSelectGrid].bg.spriteFrame = this.word_finished;
this.tiles[this.currentSelectGrid].currentId = gridId;
}
// 判断是否填词成功 通过比较字符的方式 gridId === this.currentSelectGrid
if (char === this.tiles[this.currentSelectGrid].char) {
// 记录该位置词语正确
this.tiles[this.currentSelectGrid].isSuccess = true;
}
this.judgeSuccess(this.currentSelectGrid);
}
4.3 填词逻辑
- 每填一个词都要判断格子上绑定的成语是否填词成功。找出空位置,然后判断空位置上所有的isSuccess是否为true,满足条件则播放填词动画,查找下一个空缺位置。
private judgeSuccess(gridId: number) {
…… ……
if (this.tiles[gridId].idioms.length > 0) {
for (let i = 0; i this.tiles[gridId].idioms.length; i++) {
let idiom = this.tiles[gridId].idioms[i];
let flag = true;
spaceArr = [];
// 遍历去字 位置
for (let j in idiom.grids) {
let id = idiom.grids[j].id;
if (idiom.grids[j].space === true) {
spaceArr.push(id);
}
}
// 判断成功
for (let s = 0; s if (this.tiles[spaceArr[s]].isSuccess !== true) {
flag = false;
}
}
// 填词动画后,找下一个 空位置
this.findNextSpaceGrid();
…… ……
}
4.4 过关逻辑
- 寻找下一个空位置。遍历格子对象,筛选isSelectMode为true同时isUsed为false,也就是未被占用的格子。满足条件的格子设置当前选中状态即可。
if (this.tiles[i].isSelectMode && this.tiles[i].isUsed === false) {
// 记录选中的格子位置
this.currentSelectGrid = i;
this.tiles[i].bg.spriteFrame = this.word_selected;
return;
}
}
- 如果寻找下一个空位置失败,那么说明不存在下一个空格子,就要判断过关。
let flag = true;
for (let sw = 0; sw this.selectWords.length; sw++) {
let id = this.selectWords[sw].id;
if (this.tiles[id].isSuccess !== true) {
flag = false;
}
}
if (flag) {
// 执行过关函数
cc.log("过关");
this.gameSuccess();
}
4.5 动画效果
- 选词触摸动画,对照小秀才发现当选词点击时只需要把scale执行从1到0的动画就可以达到同样的效果。如果直接将active置false,layout的格子布局就会自动调整可见选词区的位置,导致每个选词的位置出现变化。
- 填词动画。对比发现,填词正确动画拆分看,每个位置的动画都是一样的,将格子进行快速缩放并设置不同的延迟,看上去有跳动的效果。
private fillGrid(grids, arr) {
for (let i = 0; i let id = grids[i].id;
this.tiles[id].isSelectMode = false;
// 每个字延迟动画
setTimeout(() => {
this.tiles[id].getComponent(cc.Animation).play("word_success");
}, 100 * i);
}
for (let j = 0; j this.tiles[arr[j]].removeListener(null, this);
}
}
4.6 词条成功逻辑
- 判断词条中空位置格子是否都正确,如果正确就执行填词动画,查找下一个空位。如果没有空位置且填词全都正确,则说明过关,展示结算界面。
- 词条详情界面,显示拼音、释义、出处。
5 演示
demo完整演示6 总结
- 项目开始前把结构划分清除,每个模块之间的联系都确定好,可以提高开发效率。
- 第一次仿制游戏,得益于有完整的ui素材,论坛也有类似的帖子介绍。拿这类的小游戏练手对提高学习信心很有帮助。
- 关于随机关卡,跟坛友交流过,随机生成的成语质量无法保证,也就是有可能生僻词混搭,能实现但体验会变差。还有另一种手动标注成语,人工干预的方式去生成关卡。随机关卡思路是有了,但是具体没实现,暂时用不上。什么时候有心情做了再发出来。
- 使用cocos creator2.3.2版本,typescript语言
总结
以上是生活随笔为你收集整理的成语json_cocos creator实战(2)成语小秀才ts版的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: mpvue 微信小程序api_第三方框架
- 下一篇: vivo解bl锁_黔隆科技刷机教程酷派B