游戏引擎

- cocos creator 3.6

项目地址

three-tiles · GitCode

核心逻辑代码

- assets/script/game/GameScene.ts

import { _decorator, Component, Node, Prefab, assetManager, NodePool, instantiate, v3, Vec3, UITransform, game, tween, Animation, PageView, Label } from 'cc';
import { Fw } from '../../framework/manager/Fw';
import { ArrayUtil } from '../../framework/util/ArrayUtil';
import { LoadUtil } from '../../framework/util/LoadUtil';
import { GameStep, TileStatus, TileUnit } from '../config/BaseConf';
import { PageConf } from '../config/PageConf';
import { GameManager } from '../manager/GameManager';
import { Tile } from './Tile';
const { ccclass, property } = _decorator;@ccclass('GameScene')
export class GameScene extends Component {public static I: GameScene@property(Node)Map: Node = null;@property(Node)Put: Node = null;@property(Prefab)TilePrefab: Prefab = null;@property(Prefab)FxTishiPrefab: Prefab = null;@property(Label)NumRefresh: Label = null;@property(Label)NumTips: Label = null;@property(Label)NumBack: Label = null;lvd: { type: number, cell: number[][][], idList: number[], total: number } = null;tilePool: NodePool = new NodePool();fxTishiPool: NodePool = new NodePool();start() {GameScene.I = this;this.preLoadLevel(() => {this.initLevel();})this.refreshUI();//刷新UI}update(deltaTime: number) {}//预加载关卡配置与对应的预制体preLoadLevel(complete: Function, progress: Function = null): void {let skinChildIdList: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19,];let finished: number = 0;let total: number = 2;LoadUtil.load(`level:${GameManager.I.level}`, (err0, res0) => {console.log(err0, res0)let lvd: any = res0.json;this.lvd = lvd;this.lvd.idList = [];this.lvd.total = 0;for (let row of this.lvd.cell)this.lvd.total += row.length;total = lvd.type + 1 + 1;finished++;progress && progress(finished, total);this.lvd.idList = ArrayUtil.randomElementList(skinChildIdList, lvd.type);let arr = [];for (let id of this.lvd.idList) {arr.push(`icon:skin/skin${GameManager.I.curSkinId}/${id}/spriteFrame`);}arr.push(`icon:skin/kuai${GameManager.I.curSkinId}/spriteFrame`);LoadUtil.loadList(arr, complete, () => {finished++;progress && progress(finished, total);})})}//初始化关卡initLevel(): void {//移除Tilefor (let i: number = this.Map.children.length - 1; i >= 0; i--) {for (let j: number = this.Map.children[i].children.length - 1; j >= 0; j--) {this.removeTile(this.Map.children[i].children[j]);}}//配置let groupTotal: number = this.lvd.total / 3;let idList: number[] = [];for (let i: number = 0; i < groupTotal; i++) {let id = ArrayUtil.randomElement(this.lvd.idList);for (let j: number = 0; j < 3; j++)idList.push(id);}idList = ArrayUtil.randomSort(idList);let tileList = [];for (let i: number = 0; i < this.lvd.cell.length; i++) {this.lvd.cell[i] = this.lvd.cell[i].sort((a, b) => { return a[1] !== b[1] ? b[1] - a[1] : a[0] - b[0] })for (let cell of this.lvd.cell[i])tileList.push(v3(...cell, i));}//生成let index: number = 0;this.schedule(() => {let id = idList[index];let pos = tileList[index];let node: Node = this.createTile();let sc: Tile = node.getComponent(Tile);sc.id = id;// sc.index = index;sc.pos = pos;sc.status = TileStatus.Show;node.active = true;index++;//向上检测是否被覆盖 所有显示的块this.checkUpAll();}, 0.03, this.lvd.total - 1);}//创建块createTile(): Node {let node: Node = this.tilePool.get();if (node) return node;node = instantiate(this.TilePrefab);let sc: Tile = node.getComponent(Tile);sc.status = TileStatus.Recover;node['TileId'] = -1;node['IsMask'] = false;return node;}//移除块removeTile(node: Node): void {node.setParent(this.node);node.active = false;let sc: Tile = node.getComponent(Tile);sc.status = TileStatus.Recover;// sc.index = -1;this.tilePool.put(node);}//创建提示粒子createFxTishi(): Node {let node: Node = this.fxTishiPool.get();if (node) return node;node = instantiate(this.FxTishiPrefab);return node;}//移除提示粒子removeFxTishi(node: Node): void {node.setParent(this.node);node.active = false;this.fxTishiPool.put(node);}//加入队列的顺序putSortList: Node[] = [];//加入待消除的队列putTile(node: Node): void {this.putSortList.push(node);//加入队列的顺序Fw.Audio.playEffect("tile_click");let sc: Tile = node.getComponent(Tile);sc.status = TileStatus.Put;let index = 0;let len: number = this.Put.children.length;if (len) {for (let i: number = 0; i < len; i++) {if (this.Put.children[i]['TileId'] === node['TileId']) {index = i + 1;}}if (index === 0) index = len;}let position: Vec3 = node.worldPosition;this.Put.insertChild(node, index);node.worldPosition = position;//向上检测是否被覆盖 所有显示的块this.checkUpAll();//检测消除列表this.scheduleOnce(this.checkPutTile, 0.3);}PutMax: number = 7;//最大待消除数量PutAniDuration: number = 0.3;//每一次动画持续时长(单位:秒)nextPutAniTime: number = 0;putAniList: [string, Node, any][][] = [];//检测消除列表checkPutTile(): void {let len: number = this.Put.children.length;if (len < 3) return;let preTileId: number = this.Put.children[0]['TileId'];let total: number = 1;let index: number = -1;for (let i: number = 1; i < len; i++) {if (preTileId === this.Put.children[i]['TileId']) {total++;if (total >= 3) {index = i;break;}}else {preTileId = this.Put.children[i]['TileId'];total = 1;}}if (index === -1) return this.checkLevelFail();Fw.Audio.playEffect("tile_match");for (let i: number = index; i >= index - 2; i--) {let node: Node = this.Put.children[i];//播放粒子let fx: Node = this.createFxTishi();this.node.addChild(fx);fx.worldPosition = node.worldPosition;fx.active = true;fx.getComponent(Animation).play('fxtishi');this.scheduleOnce(() => {this.removeFxTishi(fx);}, 1)//移除块this.removeTile(node);}//检测是否通关this.checkLevelWin();}//检测是否失败checkLevelFail(): void {if (this.Put.children.length < this.PutMax) return;console.log("失败")GameManager.I.gameStep = GameStep.End;GameManager.I.levelRes = false;Fw.View.open(PageConf.EndView);}//检测是否胜利checkLevelWin(): void {for (let child of this.Map.children)if (child.children.length) return;console.log("胜利");GameManager.I.gameStep = GameStep.End;GameManager.I.levelRes = true;Fw.View.open(PageConf.EndView);}//向上检测是否被覆盖 所有显示的块checkUpAll(): void {for (let row of this.Map.children) {for (let node of row.children) {let sc: Tile = node.getComponent(Tile);sc.checkUp();}}}//刷新道具onBtnRefresh(): void {if (GameManager.I.refreshTool < 1) {GameManager.I.refreshTool += 3;this.refreshUI();//刷新UIFw.msg('数量不足');return;}GameManager.I.refreshTool--;this.refreshUI();//刷新UI//配置let idList: number[] = [];let tileList = [];for (let row of this.Map.children) {for (let node of row.children) {let sc: Tile = node.getComponent(Tile);idList.push(sc.id);tileList.push(sc.pos);}}idList = ArrayUtil.randomSort(idList);//移除之前的Tile节点for (let i: number = this.Map.children.length - 1; i >= 0; i--) {for (let j: number = this.Map.children[i].children.length - 1; j >= 0; j--) {this.removeTile(this.Map.children[i].children[j]);}}//生成let index: number = 0;this.schedule(() => {let id = idList[index];let pos = tileList[index];let node: Node = this.createTile();let sc: Tile = node.getComponent(Tile);sc.id = id;// sc.index = index;sc.pos = pos;sc.status = TileStatus.Show;node.active = true;index++;//向上检测是否被覆盖 所有显示的块this.checkUpAll();}, 0.017, idList.length - 1);}//回退道具onBtnBack(): void {if (GameManager.I.backTool < 1) {GameManager.I.backTool += 3;this.refreshUI();//刷新UIFw.msg('数量不足');return;}for (let i: number = this.putSortList.length - 1; i >= 0; i--) {let sc: Tile = this.putSortList[i].getComponent(Tile);if (sc.status !== TileStatus.Put) continue;GameManager.I.backTool--;this.refreshUI();//刷新UIsc.reset();return;}Fw.msg("暂无可回退的对象");}//提示道具onBtnTips(): void {if (GameManager.I.tipsTool < 1) {GameManager.I.tipsTool += 3;this.refreshUI();//刷新UIFw.msg('数量不足');return;}GameManager.I.tipsTool--;this.refreshUI();//刷新UI//PS:当前的推荐方法还有很大的优化空间,不支持多层堆叠推荐//显示的块let showMap = {};let showIdList = [];for (let i: number = this.Map.children.length - 1; i >= 0; i--)for (let node of this.Map.children[i].children) {if (node['IsMask']) continue;let id = node['TileId'];if (!showMap[id]) showMap[id] = [];showMap[id].push(node);if (showIdList.indexOf(id) === -1) showIdList.push(id);}showIdList = showIdList.sort((a, b) => { return showMap[b].length - showMap[a].length })//已推的块let putIdList = [];let putMap = {}for (let node of this.Put.children) {let id = node['TileId'];if (!putMap[id]) putMap[id] = [];putMap[id].push(node);if (putIdList.indexOf(id) === -1) putIdList.push(id);}putIdList = putIdList.sort((a, b) => { return putMap[b].length - putMap[a].length })//剩余的格子数let disPutNum: number = this.PutMax - this.Put.children.length;//情况一:已推的块+显示的块>=3for (let id of putIdList) {if (putMap[id].length + disPutNum < 3) continue;//剩余的格子数无法满足消除的需求if (!showMap[id]) continue;if (putMap[id].length + showMap[id].length < 3) continue;for (let i: number = 0, len: number = 3 - putMap[id].length; i < len; i++)this.scheduleOnce(() => { this.putTile(showMap[id][i]) }, 0.3 * i);return;}//情况二:显示的块>=3if (disPutNum >= 3 && showMap[showIdList[0]].length >= 3) {for (let i: number = 0; i < 3; i++)this.scheduleOnce(() => { this.putTile(showMap[showIdList[0]][i]) }, 0.3 * i);return;}//没有满足3个待消除的情况if (disPutNum <= 1) {GameManager.I.tipsTool++;this.refreshUI();//刷新UIFw.msg("没有可提示的选项");return;}//情况三:推已推中有相同的块for (let id of putIdList) {if (!showMap[id]) continue;this.putTile(showMap[id][0]);return;}//情况四:推显示的第一个this.putTile(showMap[showIdList[0]][0]);}//刷新UIrefreshUI(): void {this.NumBack.string = GameManager.I.backTool + '';this.NumRefresh.string = GameManager.I.refreshTool + '';this.NumTips.string = GameManager.I.tipsTool + '';}
}

- assets/script/game/Tile.ts

import { _decorator, Component, Node, Sprite, v3, Vec3, game, director, tween, UITransform } from 'cc';
import { LoadUtil } from '../../framework/util/LoadUtil';
import { GameStep, TileStatus, TileUnit } from '../config/BaseConf';
import { GameManager } from '../manager/GameManager';
import { GameScene } from './GameScene';
const { ccclass, property } = _decorator;@ccclass('Tile')
export class Tile extends Component {v3_0: Vec3 = v3();v3_1: Vec3 = v3();@property(Sprite)Bg: Sprite = null;@property(Sprite)Icon: Sprite = null;@property(Node)Mask: Node = null;start() {}onClick() {if (GameManager.I.gameStep !== GameStep.Game) return;switch (this.status) {case TileStatus.Show: {if (this.Mask.active) return;GameScene.I.putTile(this.node);break;}case TileStatus.Put: {break;}}}update(deltaTime: number) {switch (this.status) {case TileStatus.Put: {let siblingIndex: number = this.node.getSiblingIndex();this.v3_0.y = 0;this.v3_0.x = TileUnit * 2 * siblingIndex;Vec3.lerp(this.v3_1, this.node.position, this.v3_0, 0.2);this.node.position = this.v3_1;break;}case TileStatus.Show: {//向上检测是否被覆盖// if (director.getTotalFrames() % GameScene.I.lvd.total !== this.index) return;// this.checkUp();break;}}}//向上检测是否被覆盖checkUp(): void {for (let z: number = this.z + 1; z < GameScene.I.Map.children.length; z++) {for (let node of GameScene.I.Map.children[z].children) {if (Math.abs(node.position.x - this.node.position.x) <= TileUnit && Math.abs(node.position.y - this.node.position.y) <= TileUnit) {this.Mask.active = this.node['IsMask'] = true;return;}}}this.Mask.active = this.node['IsMask'] = false;}//-----------------------------------状态-----------------------------------status: TileStatus = TileStatus.Recover;//-----------------------------------编号-----------------------------------get id(): number { return this.node['TileId'] }set id(val: number) {this.node['TileId'] = val;this.Bg.spriteFrame = LoadUtil.get(`icon:skin/kuai${GameManager.I.curSkinId}/spriteFrame`);this.Icon.spriteFrame = LoadUtil.get(`icon:skin/skin${GameManager.I.curSkinId}/${this.id}/spriteFrame`);}// index: number = -1;//-----------------------------------位置-----------------------------------_x: number = 0;get x(): number { return this._x }set x(val: number) {this._x = val;this.node.position = v3(this.x * TileUnit, this.y * TileUnit);}_y: number = 0;get y(): number { return this._y }set y(val: number) { this._y = val; this.node.position = v3(this.x * TileUnit, this.y * TileUnit); }_z: number = 0;get z(): number { return this._z }set z(val: number) {this._z = val;this.node.setParent(GameScene.I.Map.children[val]);this.node.position = v3(this.x * TileUnit, this.y * TileUnit);}get pos(): Vec3 { return v3(this.x, this.y, this.z) }set pos(val: Vec3) {this._x = val.x;this._y = val.y;this.z = val.z;}reset(): void {let position: Vec3 = this.node.worldPosition;let parent: Node = GameScene.I.Map.children[this.z];let index: number = 0;for (let i: number = 0; i < parent.children.length; i++) {let sc: Tile = parent.children[i].getComponent(Tile);if (this.y < sc.y) continue;if (this.y === sc.y && this.x > sc.x) continue;index = i;break;}parent.insertChild(this.node, index);this.node.worldPosition = position;tween(this.node).to(0.6, { position: v3(this.x * TileUnit, this.y * TileUnit) }, { easing: 'circInOut' }).start();this.status = TileStatus.Show;}
}

内容说明

- (非业务)基础框架

- 核心玩法

- 道具(刷新、回退、提示)

提示:本项目如标题所示仅提供核心玩法的逻辑,不计划补充周边功能

页面效果

实现流程

1、根据配置(配置中包含种类数量、块位置列表)实例化关卡

2、每步操作后,【检测】所有块的被遮挡状态

3、点击(未遮挡的)块,将其移动到待消除的列表中

- 【检测】满3个执行消除

- 【检测】待消除的列表长度>=7 游戏失败

- 【检测】消除所有的块 游戏通关

比较麻烦的点:

1、块的排列(没有什么捷径,addChild前优先做好前后排序)

2、块的移动(采用lerp的方式)

经典三消游戏核心玩法相关推荐

  1. 基于魔兽RPG对当前游戏发展趋势的分析,以及尝试一个高游戏性玩法的设计

    游戏发展中有这么一束分支,玩家可以从魔兽争霸3:冰封王座基本游戏模式开始延伸,并通过地图编辑器降低游戏开发门槛,创造开发自己的游戏模式,即魔兽RPG. 如果客观的看,魔兽RPG符合一个优胜劣汰的进化守 ...

  2. 用Unity制作一个很火的找不同游戏,包括核心玩法与关卡编辑器工具的实现

    玩家们心里都清楚,其实游戏的品类超级多,有些人喜欢玩moba.射击竞技类的,有些人喜欢塔防.策略.回合制类的,而又有些人喜欢经营养成.休闲益智类的.休闲益智类游戏,虽没像moba类游戏的激烈操作带来的 ...

  3. 最近迷上了自走棋塔防游戏,玩法比较休闲,而且耐玩!

    梦塔防-自走棋 梦塔防手游是一款3V3即时对战类手机游戏,也是自走棋模式打造的游戏,游戏以策略元素为主,玩家在游戏中可以自由调配兵种,以及功防阵局,丰富的兵种以及精致的游戏界面,为你提供更好的游戏体验 ...

  4. 首席新媒体运营商学院创始人黎想:活动运营推广,如何策划核心玩法

    一听感觉很流弊的亚子,简单翻译一下,就是根据阶段目标的不同,通过策划落地一个短期的活动,来快速的提高产品某项指标的运营手段,我们称之为活动运营. 作为新媒体运营人,首席新媒体商学院创始人,资深新媒体运 ...

  5. unity制作qq炫舞的核心玩法

    她 文章目录 一. 介绍 二. 重点知识点和游戏逻辑 三. 箭头脚本 四. 管理箭头脚本 五. 管理全局脚本 六. 管理玩家输入脚本 七. ui脚本 八. 制作动画 九. 添加音乐节奏 十. 工程下载 ...

  6. 电商盲返模式的核心玩法

    寒冬已至,疫情还是在断断续续的复发,很多城市也受到严重的影响,封城的通告一出,无疑是给不少的实体企业增添了相当大的噩耗打击,这时候更为磨炼实体企业和创业人看待事情的立场,有些人会觉得疫情的袭来什么都会 ...

  7. 娱乐小游戏助力神器威信小程序源码下载多种游戏选择玩法多种

    大家好今天给大家带来另外一款小神器 好像记得小编之前也发过好几款这种小程序源码吧 但是每一款的UI或者功能什么的都还是会有所不一样的 大家也可以找找之前所发的那几款对比一下然后决定自己需要哪一款哟 这 ...

  8. 老A:什么是抖音弹幕互动游戏,玩法以及如何参与

    今天老A主要为大家拆解弹幕互游三部分,一是弹幕互动游戏的形式及状态:二是弹幕互动游戏的玩法:三是如何参与这个到这个赛道上. 抖音弹幕游戏作为一种全新的娱乐直播形式,在传统娱乐直播中最头疼的问题莫过于: ...

  9. 小程序源码:修复图片音频全新升级带特效喝酒神器小游戏-多玩法安装简单

    这是一款全新升级带特效喝酒神器小游戏微信小程序源码 小编发现很多喝酒神器小程序都不带特效和音效的 感觉差了那么一点意思而且感觉也不炫酷 所以小编今天给大家带来一款带特效,音效炫酷的喝酒神器 该款神器由 ...

最新文章

  1. 50本程序员圣经级别书籍!包邮送到家!一书在手,天下我有!
  2. 自然语言处理发展历程自我总结
  3. 通过飞行CALL找到BT飞行偏移 和飞行状态偏移
  4. 精选文章 什么是跨域?怎么解决跨域问题?
  5. 织梦guestbook.php漏洞,DEDE:织梦漏洞修复(含任意文件上传漏洞与注入漏洞)
  6. DISCUZ网站DIY后,导致DIY功能失效,无法在前台删除已创建的DIY功能解决办法
  7. ABAP COMMIT WORK关键字在CRM content management应用里的使用场景
  8. php 图片 3d旋转图片,html5实现图片的3D旋转效果
  9. Android用按钮确定单选框,Android RadioButton单选框的使用方法
  10. Hrbust 1522 利用队列求最大子序列的和
  11. Linux下安装Redis讲解
  12. 数据库:增删改查操作
  13. linux换终端的背景颜色,linux 终端背景色修改
  14. 一个元素位于另一个元素之上,点击上面的元素引发下面元素事件操作
  15. IDEA显示树状目录结构
  16. 如何下载并安装Firebug插件
  17. 学习这东西确实得稳扎稳打,勿焦勿躁
  18. Ultra Librarian下载与安装使用教程
  19. 编写Java程序,使用单例模式,创建可以生成银联借记卡号的工具类,银联借记卡号是一个 19 位的数字,卡号以“62”开头,如图所示。
  20. LeetCode 情侣牵手

热门文章

  1. steam泰坦之旅dlc_《泰坦之旅十周年纪念版》新DLC上线Steam 售价50元
  2. 转 安卓控件属性大全
  3. VS2013项目加载失败解决方法
  4. 电路原理图 英语缩写 符号汇总
  5. EasyTouch中虚拟摇杆的使用EasyJoystick
  6. Unity实现读取Excel文件
  7. mysql decimal长度_mysql decimal类型与decimal长度用法详解
  8. 使用QTableWidget在Python界面中画表格
  9. Unity 自动化1.0(代码,预制体生成到指定模块)
  10. Unity——自动化代码生成