cocos官方文档:节点系统事件 | Cocos Creator
游戏界面展示
一、在cocos编译器随便画个页面 展示页面
二、连连看元素生成
2.1、准备单个方块元素,我这里就是直接使用一张图片,图片大小为100x100,锚点为(0,0),这图片命名animal,把这一张图片设置成Prefab(预制)
(作为后面用代码生成元素矩阵使用)
2.2、给命名animal的Prefab(预制)绑定一个ts文件,命名Animal.ts,这个类就是存放单个图片参数了
@property(cc.SpriteFrame)
sp1 = [];这里就是表示不同的图片资源,在上图的最右边就可以看的绑定了5张图片进去,为了id对应,[0]位置空了
Animal.ts文件代码如下:
//import { AnimalMgr } from "./AnimalMgr";
const { ccclass, property } = cc._decorator;
@ccclass
export default class Animal extends cc.Component {
// 存放不同图片,就是元素种类
@property(cc.SpriteFrame)
sp1 = [];
// LIFE-CYCLE CALLBACKS:
// onLoad () {}
//存放元素种类id,用于后面匹配消除同类元素
private _aid: number = 0;
public get aid(): number {
return this._aid;
}
public set aid(value: number) {
this._aid = value;
if (this._aid > 0 && this._aid < this.sp1.length) {
this.node.getComponent(cc.Sprite).spriteFrame = this.sp1[this._aid];
}
}
// 该元素位于矩阵第几行(x)
private _rowIndex: number = -1;
public get rowIndex(): number {
return this._rowIndex;
}
public set rowIndex(value: number) {
this._rowIndex = value;
}
// 该元素位于矩阵第几列(y)
private _colIndex: number = -1;
public get colIndex(): number {
return this._colIndex;
}
public set colIndex(value: number) {
this._colIndex = value;
}
// 该元素矩阵横着总长度
private _rowSum: number = -1;
public get rowSum(): number {
return this._rowSum;
}
public set rowSum(value: number) {
this._rowSum = value;
}
// 该元素矩阵竖着总长度
private _colSum: number = -1;
public get colSum(): number {
return this._colSum;
}
public set colSum(value: number) {
this._colSum = value;
}
start() {
// 点击该元素就会触发该方法,cocos固定点击事件写法
this.node.on(cc.Node.EventType.TOUCH_END, (xxx) => {
console.log(this.aid);
// AnimalMgr.addAnimal(this);
});
}
// update (dt) {}
}
2.3、给整个画布绑定一个ts文件,命名Mgr.ts,后面在这个Mgr.ts文件声明一个预制,引入上面的预置文件Animal
里面就是编写动态生成连连看矩阵元素的逻辑
Mgr.ts文件代码如下:
import Animal from "./Animal";
//import { AnimalMgr } from "./AnimalMgr";
const { ccclass, property } = cc._decorator;
@ccclass
export default class Mgr extends cc.Component {
@property(cc.Prefab)
T0 = null;
onLoad() {}
private _rows: number = 5; //矩阵的行数
private _cols: number = 6; //矩阵的列数
private _eleIdSum: number = 5; //可以展示图片的种类数量
start() {
// console.log("start");
// let _tmp = [
// [1,1,1,1,1,1,],
// [1,1,1,1,1,1,],
// [1,1,1,1,1,1,],
// [1,1,1,1,1,1,],
// [1,1,1,1,1,1,],
// ]
let _startx: number = -(this._cols * 100) >> 1;
let _starty: number = -(this._rows * 100) >> 1;
for (let index1 = 0; index1 < this._rows; index1++) {
for (let index2 = 0; index2 < this._cols; index2++) {
let _xxx: cc.Node = cc.instantiate(this.T0);//创建一个元素
this.node.addChild(_xxx); //把这元素添加到页面上
_xxx.x = _startx;
_xxx.y = _starty;
_startx += 100;
// 获取这个元素组件的元素
let _script: Animal = _xxx.getComponent(Animal); //获取这个元素的信息
_script.aid = this.randomIdFn(index1, index2); //给这个元素渲染的图片id 该方法并生成对称id种类个数
_script.rowIndex = index1; // 记录该图片在矩阵x轴位置
_script.colIndex = index2; // 记录该图片在矩阵y轴位置
_script.rowSum = this._rows; // 该元素矩阵横着总长度
_script.colSum = this._cols; // 该元素矩阵横着总长度
//AnimalMgr.set(index1, index2, 1); //记录矩阵元素是否消除或者存在,1表示存在,表示不存在
}
_startx = -(this._cols * 100) >> 1;
_starty += 100;
}
/*
伪节点,就是在连连看矩阵四周加多一层节点数据,
标识四周一圈的节点都消除成功了,为后面的链接算法做处理
*/
// for (let index = 0; index < this._cols; index++) {
// AnimalMgr.set(-1, index, 0);
// }
// for (let index = 0; index < this._cols; index++) {
// AnimalMgr.set(this._rows, index, 0);
// }
// for (let index = 0; index < this._rows; index++) {
// AnimalMgr.set(index, -1, 0);
// }
// for (let index = 0; index < this._rows; index++) {
// AnimalMgr.set(index, this._cols, 0);
// }
}
private idMap: Map<number, number> = new Map(); //收集每种元素id生成的个数,用于记录每种元素已经生成的个数
public randomIdFn(rowIndex: number, colIndex: number) {
// rowIndex: 记录元素生成到第几行, colIndex: 记录元素生成到第几列
//这里是末尾补偶法,前面元素随机生成,末尾再把基数种类元素补为偶数 此方法用于保证生成的元素都为偶数对
let randomId = 1 + Math.floor(Math.random() * this._eleIdSum); // 渲染的图片id--> 1~5
const eleSum = rowIndex * this._cols + colIndex; //遍历到第几个元素了
const replenish = this._cols * this._rows - this._eleIdSum; //需要开始补充奇数位的元素开始位置
if (eleSum > replenish) {
//开始补充基数位的元素-->这里有5类元素,我在格子最后的五位元素补充,确保每种元素都为偶数对
// let newCreateIdSum = this.idMap.get(randomId);
this.idMap.forEach((value, key) => {
if (value % 2 != 0) {
randomId = key; //不是偶数的元素种类,补充为偶数
}
});
}
//用于记录每种元素已经生成的个数
if (this.idMap.get(randomId) && this.idMap.get(randomId) > 0) {
let idSum = this.idMap.get(randomId) + 1;
this.idMap.set(randomId, idSum);
} else {
this.idMap.set(randomId, 1); //初始化
}
return randomId;
}
// protected onDestroy(): void {
// }
update(dt) {}
// protected lateUpdate(dt: number): void {
// }
}
这里总结一下做上面所有步骤都只是为了生成一个 连连看的矩阵画面,这里的元素生成主要逻辑是:
末尾补齐法,目的让每一种类元素都可成为偶数,避免消除完后,最后某种元素中有单个元素在页面上,玩家无法进行匹配,主要代码逻辑在 public randomIdFn(rowIndex: number, colIndex: number),方法不唯一,可以用自己想法编写
三、连连看算法逻辑编写
3.1、在项目新建ts文件,命名AnimalMgr.ts 连连看小游戏的主要逻辑都存放在该文件
在该文件下,AnimalMgr.ts初始化代码如下:
import Animal from "./Animal";
interface VC {
rowIndex: number;
colIndex: number;
}
class _AnimalMgr {
private _animals: Array<Animal> = []; //点击到的元素存进了,这里最大值2个元素
private _paths: Map<string, number> = new Map(); //用于记录矩阵那个元素的消除情况,1表示存在,0表示消除
public addAnimal(_Animal: Animal) {
if (_Animal) {
//
console.log(this._animals);
if (this._animals.length > 0) {
let _start: Animal = this._animals[0];
//是否是相同元素,是就不记录进去
if (
_start.colIndex == _Animal.colIndex &&
_start.rowIndex == _Animal.rowIndex
) {
return;
}
}
this._animals.push(_Animal);
//------------
if (this._animals.length == 2) {
//TODO:0拐点
let _isConnect: boolean = false;
let _start: Animal = this._animals[0];
let _stop: Animal = this._animals[1];
if (_start.aid != _stop.aid) {
//是否是相同元素
this._animals = [];
return;
}
if (_isConnect) {
// 符合连接条件的,在视图上销毁这俩连接上的节点,并记录下来这两节点已经消除
this.set(_start.rowIndex, _start.colIndex, 0);
this.set(_stop.rowIndex, _stop.colIndex, 0);
_start.node.destroy();
_stop.node.destroy();
}
// console.log("_isConnect",_isConnect);
this._animals = [];
}
}
}
//判断这个节点是否存在矩阵图形上
public isPass(_r: number, _c: number) {
let _key = `${_r}_${_c}`;
if (this._paths.has(_key)) {
return this._paths.get(_key) == 0;
} else {
return false;
}
}
/*
标识矩阵每个元素的位置,并且值为1 代表存在,0代表销毁
_r: number, //横坐标
_c: number, // 纵坐标
_v: number //值为1 代表存在,0代表销毁
*/
public set(_r: number, _c: number, _v: number) {
let _key = `${_r}_${_c}`;
this._paths.set(_key, _v);
console.log(this._paths);
}
}
export const AnimalMgr = new _AnimalMgr();
上面AnimalMgr.ts初始化代码主要一个目的,记录元素在矩阵中是否存在,存在的,就在矩阵的对应位置标识为1 不存在就标识为0
3.2、然后把上面Animal.ts和Mgr.ts文件,关于引用到AnimalMgr.ts的代码,注释回来,就可正常随意点击任意两个元素,这两个元素就会消失在页面上,如下图所示:
3.3、点击矩阵的两个节点,是否能连接成功,符合条件就把连接成功的节点消除
下面是主要逻辑,分三步走,
(第一步)0拐点:
意思是两个节点都是在同一条直线上,都在同一条x轴或者y轴上面,
比如A到B点,中间只是一条直线连接在同y轴上,或者C到D点也只一条直线连接,在同x轴上,
中间连接线不需要任何直角拐弯就可联通,就表示这两个节点0个拐点
在0拐点的两个节点,只要中间没有任何东西,就表示可连接成功
0拐点代码逻辑如下,思路才是重点,代码可以自己写,下面代码只做参考:
// 两点距离0个拐角(直角)
public _0c(start_: VC, stop_: VC): boolean {
// 同一条横直线上
if (start_.rowIndex == stop_.rowIndex) {
if (start_.colIndex < stop_.colIndex) {
//向右移动
let _startCol: number = start_.colIndex + 1;
//判断到下一个节点空就为true,有值就false
while (this.isPass(start_.rowIndex, _startCol)) {
_startCol++;
}
return _startCol == stop_.colIndex;
} else {
// 向左移动
let _startCol: number = start_.colIndex - 1;
while (this.isPass(start_.rowIndex, _startCol)) {
_startCol--;
}
return _startCol == stop_.colIndex;
}
} else if (start_.colIndex == stop_.colIndex) {
// 同一条竖直线上
if (start_.rowIndex < stop_.rowIndex) {
//向上移动
let _startRow: number = start_.rowIndex + 1;
while (this.isPass(_startRow, start_.colIndex)) {
_startRow++;
}
return _startRow == stop_.rowIndex;
} else {
//向下移动
let _startRow: number = start_.rowIndex - 1;
while (this.isPass(_startRow, start_.colIndex)) {
_startRow--;
}
return _startRow == stop_.rowIndex;
}
}
return;
}
(第二步:假设0个拐点条件不满足)1拐点:
意思是两个节点坐标x和y都不相同,但是,两个节点的连接线,只有一个直角
如下图:
下面需要点击A和B节点,A和B的直线距离都不能直接连接,现在需要连接只能两条路线,每条路线只能一个直角,就只有两条路线可走,每条路线都会有一个拐点,分别是 C和D拐点
A和B要想连接成功,路线一或者路线二,只要有一条能连接上:拐点的位置到起点和终点的直线连接都没有阻碍物,表示A和B就可以相连
1拐点代码逻辑如下,思路才是重点,代码可以自己写,下面代码只做参考:
// 两点距离1个拐角(直角)
public _1c(start_: VC, stop_: VC): boolean {
//找到两个节点的两个直角拐点
let _p1: VC = { rowIndex: start_.rowIndex, colIndex: stop_.colIndex }; //拐角点1
let _p2: VC = { rowIndex: stop_.rowIndex, colIndex: start_.colIndex }; //拐角点2
let _tmp: Array<VC> = [_p1, _p2];
// 判断每个拐角点到初始点和终点之间是否有阻碍节点,有就表示行不通
for (let index = 0; index < _tmp.length; index++) {
const pt = _tmp[index];
if (this.isPass(pt.rowIndex, pt.colIndex)) {
let _isOK = true;
_isOK = _isOK && this._0c(pt, start_);
_isOK = _isOK && this._0c(pt, stop_);
if (_isOK) {
return true;
}
}
}
return false;
}
(第三步:假设0个1拐点条件都不满足)2拐点:
意思是:A到B点的连通需要满足连接路线会出现两个直角的
如下图:
A点到B点要想连接成功,连接路线需要两个拐角,
C点到D点要想连接成功,连接路线也需要两个拐角,
两个节点出现两个拐角点要是想连接成功,代码的核心思路是,在起始点,通过上下左右移动的尝试,起始点移动到的位置,可以实现与终点连接出现一个拐点路线,就能连接成功了,就表示这两个节点可以连接成功
2拐点代码逻辑如下,思路才是重点,代码可以自己写,下面代码只做参考:
// 两点距离2个拐角(直角)
public _2c(start_: VC, stop_: VC): boolean {
// 向初始节点四面移动,判断受否可能找到 连接到终节点的一个拐角的路线
//TODO:左
let _startCol: number = start_.colIndex - 1;
//在初始起点向左移动,直到找到可连接到终节点的一个拐角的路线,遇到符合就终止,
// 如果都不满足,就退出向左移动的尝试,走下面的右移动逻辑
while (this.isPass(start_.rowIndex, _startCol)) {
// 判断这个节点是否存在矩阵图形上--this.isPass(start_.rowIndex, _startCol)
this.set(start_.rowIndex, _startCol, 100);//起始点左移动,标记该位置存在元素
let _isOk = this._1c(
{ rowIndex: start_.rowIndex, colIndex: _startCol },
{ rowIndex: stop_.rowIndex, colIndex: stop_.colIndex }
);
this.set(start_.rowIndex, _startCol, 0);
_startCol -= 1;
if (_isOk) {
return true;
}
}
//TODO:右
_startCol = start_.colIndex + 1;
while (this.isPass(start_.rowIndex, _startCol)) {
this.set(start_.rowIndex, _startCol, 100);
let _isOk = this._1c(
{ rowIndex: start_.rowIndex, colIndex: _startCol },
{ rowIndex: stop_.rowIndex, colIndex: stop_.colIndex }
);
this.set(start_.rowIndex, _startCol, 0);
_startCol += 1;
if (_isOk) {
return true;
}
}
//TODO:上
let _startRow = start_.rowIndex + 1;
while (this.isPass(_startRow, start_.colIndex)) {
this.set(_startRow, start_.colIndex, 100);
let _isOk = this._1c(
{ rowIndex: _startRow, colIndex: start_.colIndex },
{ rowIndex: stop_.rowIndex, colIndex: stop_.colIndex }
);
this.set(_startRow, start_.colIndex, 0);
_startRow += 1;
if (_isOk) {
return true;
}
}
//TODO:下
_startRow = start_.rowIndex - 1;
while (this.isPass(_startRow, start_.colIndex)) {
this.set(_startRow, start_.colIndex, 100);
let _isOk = this._1c(
{ rowIndex: _startRow, colIndex: start_.colIndex },
{ rowIndex: stop_.rowIndex, colIndex: stop_.colIndex }
);
this.set(_startRow, start_.colIndex, 0);
_startRow -= 1;
if (_isOk) {
return true;
}
}
return false;
}
AnimalMgr.ts完整代码如下代码如下:
import Animal from "./Animal";
interface VC {
rowIndex: number;
colIndex: number;
}
class _AnimalMgr {
private _animals: Array<Animal> = []; //点击到的元素存进了,这里最大值2个元素
private _paths: Map<string, number> = new Map(); //用于记录矩阵那个元素的消除情况,1表示存在,0表示消除
// AnimalMgr.set(index1,index2,0);
public addAnimal(_Animal: Animal) {
if (_Animal) {
//
console.log(this._animals);
if (this._animals.length > 0) {
let _start: Animal = this._animals[0];
//是否是相同元素,是就不记录进去
if (
_start.colIndex == _Animal.colIndex &&
_start.rowIndex == _Animal.rowIndex
) {
return;
}
}
this._animals.push(_Animal);
//------------
if (this._animals.length == 2) {
//TODO:0拐点
let _isConnect: boolean = false;
let _start: Animal = this._animals[0];
let _stop: Animal = this._animals[1];
if (_start.aid != _stop.aid) {
//是否是相同元素
this._animals = [];
return;
}
_isConnect = this._0c(
{ rowIndex: _start.rowIndex, colIndex: _start.colIndex },
{ rowIndex: _stop.rowIndex, colIndex: _stop.colIndex }
);
//TODO:1拐点
if (!_isConnect) {
_isConnect = this._1c(
{ rowIndex: _start.rowIndex, colIndex: _start.colIndex },
{ rowIndex: _stop.rowIndex, colIndex: _stop.colIndex }
);
}
//TODO:2拐点
if (!_isConnect) {
_isConnect = this._2c(
{ rowIndex: _start.rowIndex, colIndex: _start.colIndex },
{ rowIndex: _stop.rowIndex, colIndex: _stop.colIndex }
);
}
if (_isConnect) {
// 符合连接条件的,在视图上销毁这俩连接上的节点,并记录下来这两节点已经消除
this.set(_start.rowIndex, _start.colIndex, 0);
this.set(_stop.rowIndex, _stop.colIndex, 0);
_start.node.destroy();
_stop.node.destroy();
}
// console.log("_isConnect",_isConnect);
this._animals = [];
}
}
}
//判断这个节点是否存在矩阵图形上
public isPass(_r: number, _c: number) {
let _key = `${_r}_${_c}`;
if (this._paths.has(_key)) {
return this._paths.get(_key) == 0;
} else {
return false;
}
}
/*
标识矩阵每个元素的位置,并且值为1 代表存在,0代表销毁
_r: number, //横坐标
_c: number, // 纵坐标
_v: number //值为1 代表存在,0代表销毁
*/
public set(_r: number, _c: number, _v: number) {
let _key = `${_r}_${_c}`;
this._paths.set(_key, _v);
console.log(this._paths);
}
// 两点距离0个拐角(直角)
public _0c(start_: VC, stop_: VC): boolean {
// 同一条横直线上
if (start_.rowIndex == stop_.rowIndex) {
if (start_.colIndex < stop_.colIndex) {
//向右移动
let _startCol: number = start_.colIndex + 1;
//判断到下一个节点空就为true,有值就false
while (this.isPass(start_.rowIndex, _startCol)) {
_startCol++;
}
return _startCol == stop_.colIndex;
} else {
// 向左移动
let _startCol: number = start_.colIndex - 1;
while (this.isPass(start_.rowIndex, _startCol)) {
_startCol--;
}
return _startCol == stop_.colIndex;
}
} else if (start_.colIndex == stop_.colIndex) {
// 同一条竖直线上
if (start_.rowIndex < stop_.rowIndex) {
//向上移动
let _startRow: number = start_.rowIndex + 1;
while (this.isPass(_startRow, start_.colIndex)) {
_startRow++;
}
return _startRow == stop_.rowIndex;
} else {
//向下移动
let _startRow: number = start_.rowIndex - 1;
while (this.isPass(_startRow, start_.colIndex)) {
_startRow--;
}
return _startRow == stop_.rowIndex;
}
}
return;
}
// 两点距离1个拐角(直角)
public _1c(start_: VC, stop_: VC): boolean {
//找到两个节点的两个直角拐点
let _p1: VC = { rowIndex: start_.rowIndex, colIndex: stop_.colIndex }; //拐角点1
let _p2: VC = { rowIndex: stop_.rowIndex, colIndex: start_.colIndex }; //拐角点2
let _tmp: Array<VC> = [_p1, _p2];
// 判断每个拐角点到初始点和终点之间是否有阻碍节点,有就表示行不通
for (let index = 0; index < _tmp.length; index++) {
const pt = _tmp[index];
if (this.isPass(pt.rowIndex, pt.colIndex)) {
let _isOK = true;
_isOK = _isOK && this._0c(pt, start_);
_isOK = _isOK && this._0c(pt, stop_);
if (_isOK) {
return true;
}
}
}
return false;
}
// 两点距离2个拐角(直角)
public _2c(start_: VC, stop_: VC): boolean {
// 向初始节点四面移动,判断受否可能找到 连接到终节点的一个拐角的路线
//TODO:左
let _startCol: number = start_.colIndex - 1;
//在初始起点向左移动,直到找到可连接到终节点的一个拐角的路线,遇到符合就终止,
// 如果都不满足,就退出向左移动的尝试,走下面的右移动逻辑
while (this.isPass(start_.rowIndex, _startCol)) {
// 判断这个节点是否存在矩阵图形上--this.isPass(start_.rowIndex, _startCol)
this.set(start_.rowIndex, _startCol, 100); //起始点左移动,标记该位置存在元素
let _isOk = this._1c(
{ rowIndex: start_.rowIndex, colIndex: _startCol },
{ rowIndex: stop_.rowIndex, colIndex: stop_.colIndex }
);
this.set(start_.rowIndex, _startCol, 0);
_startCol -= 1;
if (_isOk) {
return true;
}
}
//TODO:右
_startCol = start_.colIndex + 1;
while (this.isPass(start_.rowIndex, _startCol)) {
this.set(start_.rowIndex, _startCol, 100);
let _isOk = this._1c(
{ rowIndex: start_.rowIndex, colIndex: _startCol },
{ rowIndex: stop_.rowIndex, colIndex: stop_.colIndex }
);
this.set(start_.rowIndex, _startCol, 0);
_startCol += 1;
if (_isOk) {
return true;
}
}
//TODO:上
let _startRow = start_.rowIndex + 1;
while (this.isPass(_startRow, start_.colIndex)) {
this.set(_startRow, start_.colIndex, 100);
let _isOk = this._1c(
{ rowIndex: _startRow, colIndex: start_.colIndex },
{ rowIndex: stop_.rowIndex, colIndex: stop_.colIndex }
);
this.set(_startRow, start_.colIndex, 0);
_startRow += 1;
if (_isOk) {
return true;
}
}
//TODO:下
_startRow = start_.rowIndex - 1;
while (this.isPass(_startRow, start_.colIndex)) {
this.set(_startRow, start_.colIndex, 100);
let _isOk = this._1c(
{ rowIndex: _startRow, colIndex: start_.colIndex },
{ rowIndex: stop_.rowIndex, colIndex: stop_.colIndex }
);
this.set(_startRow, start_.colIndex, 0);
_startRow -= 1;
if (_isOk) {
return true;
}
}
return false;
}
}
export const AnimalMgr = new _AnimalMgr();