前言
此开发过程借鉴于 Prpr_Saber大佬的文章,
所以此文中会有大佬写的代码的影子,感兴趣的可以直接跳转至大佬的文章,而且大佬把原工程放在Git上了,可以直接拉取。
如果此文侵权,即刻删除。
1.State.ts
在开始之前,先分享一个实用的脚本
State.ts代码块
const { ccclass, property } = cc._decorator;
@ccclass
export default class State extends cc.Component
{
public nowState: number = 0;
//public allState: number = 0;
start()
{
//this.allState = this.node.childrenCount;
}
//将节点的子节点,v,设置active = true,其他为false
stateChange(v: number)
{
for (let i: number = 0; i < this.node.children.length; i++)
{
this.node.children[i].active = false;
}
if (v >= 0 && v < this.node.children.length)
{
this.nowState = v;
this.node.children[v].active = true;
return this.node.children[v];
}
}
}
将这个脚本挂在之前在场景中创建的labelRoot上
2.实现点击左右箭头切换中间数值
修改Select.ts
import CameraManager from "../camera/CameraManager";
import State from "../common/State";
const { ccclass, property } = cc._decorator;
@ccclass
export default class Select extends cc.Component
{
@property(cc.Node)
backBtn: cc.Node = null;//返回按钮
@property(cc.Node)
startBtn: cc.Node = null;//开始游戏按钮
@property(cc.Node)
rightBtn: cc.Node = null;//右按钮
@property(cc.Node)
leftBtn: cc.Node = null;//左按钮
@property(State)
state: State = null;//3*3 4*4 8*8 状态改变
private index: number = 0;//这个index代表挂State.ts的节点的当前子节点下标,初始默认值为0
onLoad()
{
this.state.stateChange(this.index);//这条语句的作用就是,将挂载State.ts的节点,第一个子节点设为真,其他都为false
//因为这里在引擎里直接创建的button组件,所以.on('click')而不是.on('touchend')
this.backBtn.on('click', () =>
{
CameraManager.getInstance().moveCamera(-1)
}, this)
this.rightBtn.on('click', () =>
{
if (this.index < 2)
{
this.index++;
this.state.stateChange(this.index)
}
}, this)
this.leftBtn.on('click', () =>
{
if (this.index > 0)
{
this.index--;
this.state.stateChange(this.index)
}
}, this)
}
}
这时候运行游戏点击左右箭头就可以切换中间的数值了
3.制作Play节点
因为play节点的绘制信息由select节点选择的几乘几的数组决定的,所以可以将play节点制成一个预制体,每次在select
点击开始按钮时,实例化一个预制体
- 修改Select.ts,在Select.ts脚本中加入以下代码块
在此之前,需对Play.ts加入Init()方法
const { ccclass, property } = cc._decorator;
@ccclass
export default class Play extends cc.Component
{
@property(cc.Node)
backBtn: cc.Node = null;//返回按钮
init(v: number)
{
//TODO:后续使用
}
onLoad()
{
this.backBtn.on('click', () =>
{
this.node.destroy();
CameraManager.getInstance().moveCamera(-1)
}, this)
}
}
- 修改场景节点,并制作一个playPre预制体。itemParent节点设置为600,600,居中,Anchor角度设置为(0,1),Anchor一定要设置好,不然会有问题
3.制作格子预制体,加入生成逻辑,对象池
- 制作展示格子
因为格子是一直在生成的,所以使用对象池。 - 编写对象池脚本PoolManager.ts
这里的ActionManager和DataManager随后介绍
PoolManager.ts代码块
export default class PoolManager
{
constructor()
{
this._blockPool = new cc.NodePool();
this._nodeList = new Array<cc.Node>();
}
_blockPool: cc.NodePool
_nodeList: cc.Node[]
prefab: cc.Prefab
static instance: PoolManager
static getInstance(): PoolManager
{
this.instance = this.instance ? this.instance : new PoolManager()
return this.instance
}
//初始化节点池大小,3*3大小就为9,4*4就为16,8*8就为64
initPool(prefab: cc.Prefab, size: number)
{
for (let i = 0; i < size; i++)
{
let block = cc.instantiate(prefab);
this._blockPool.put(block);
}
this.prefab = prefab;
}
//从节点池中拿取一个节点,节点池中没有,就重新生成一个
getBlock(): cc.Node
{
let block = null;
if (this._blockPool.size() > 0)
{
block = this._blockPool.get()
}
else
{
block = cc.instantiate(this.prefab)
}
return block;
}
//将节点保存在一个数组内
saveNode(node: cc.Node)
{
this._nodeList.push(node)
}
//清除节点数组,但不清除节点池
clearNodeList()
{
for (let node of this._nodeList)
{
this._blockPool.put(node)
this._nodeList = [];
}
}
//节点池和节点数组全部清除
clearPool()
{
this._nodeList = [];
this._blockPool.clear();
}
}
- 添加数据处理类DataManager.ts
/**
* 核心数据处理在这个脚本
*/
export default class DataManager
{
private map: number[][] = [];//地图数据
static instance: DataManager
static getInstance(): DataManager//实例化
{
this.instance = this.instance ? this.instance : new DataManager();
return this.instance
}
initMap(_size: { row: number, col: number }, value: number)
{
this.map = [];//每次初始化的时候将其置为空
for (let row = 0; row < _size.row; row++)
{
this.map.push([])
for (let col = 0; col < _size.col; col++)
{
this.map[row][col] = value;
}
}
console.log("初始化数据", this.map)//初始化完成后,查看地图数据
}
//添加一个格子信息,即将数组中不为0的其中一个数值改变
addRandom(): boolean
{
let points: cc.Vec2[] = [];//保存数组中不为0的横纵坐标
let hasNext: boolean = false;//是否能生成下一个
this.traversalTwoArray(this.map, (row, col) =>
{
if (this.map[row][col] == 0)
{
points.push(cc.v2(row, col))
hasNext = true;
}
})
if (hasNext)
{
let index = Math.floor(Math.random() * points.length)
let row = points[index].x;
let col = points[index].y
this.map[row][col] = 2;//TODO:这个值后续修改,添加限制,生成2或者4,而不是一味生成2
}
return hasNext
}
//获得地图数据
getMap(): number[][]
{
// console.log("getMap", this.map)
return this.map
}
//遍历二维数组,这个方法自认为挺好用的,可以认真看一下
traversalTwoArray<Type>(arr: Type[][], callback: (row: number, col: number) => void)
{
for (let row = 0; row < arr.length; row++)
{
for (let col = 0; col < arr[row].length; col++)
{
callback(row, col)
}
}
}
}
- 节点行为类ActionManager.ts
import DataManager from "./DataManager";
import PoolManager from "./PoolManager"
/**
* 此脚本绘制场景节点信息,包括展示,移动。。。
*/
const Color = {
2: cc.color(237, 241, 21, 255),
4: cc.color(241, 180, 21, 255),
8: cc.color(171, 241, 21, 255),
16: cc.color(149, 160, 216, 255),
32: cc.color(187, 149, 216, 255),
64: cc.color(216, 149, 209, 255),
128: cc.color(28, 118, 156, 255),
256: cc.color(16, 74, 99, 255),
512: cc.color(168, 85, 25, 255),
1024: cc.color(236, 122, 38, 255),
2048: cc.color(236, 86, 33, 255)
}
export default class ActionManager
{
constructor(itemParent: cc.Node)
{
this.itemParent = itemParent;
}
private itemParent: cc.Node;//格子的父节点
private itemWidth: number//格子节点宽
private itemHeight: number//格子节点高
//绘制地图
draw(_map: number[][])
{
PoolManager.getInstance().clearNodeList();
this.itemWidth = this.itemParent.width / _map.length
this.itemHeight = this.itemParent.height / _map.length
DataManager.getInstance().traversalTwoArray(_map, (row: number, col: number) =>
{
if (_map[row][col] !== 0)//初始地图数据为0
{
let item = PoolManager.getInstance().getBlock();
item.parent = this.itemParent;
//根据地图长度设置节点大小
item.setContentSize(this.itemWidth, this.itemHeight);
//这里的大小会有一个问题,假设父节点itemParent的width和height为600,
//传入的map长度为3,那么width和height为200,按照一定的位置排开,正好是
//铺满的状态,所以在这里可以在item预制体下加一个单色子节点,item本身大小就为200
//他的单色子节点大小我们定位190,或者其他一些更小的值,这样展示的时候就不会铺满
//整个itemParent,相互之间会有间隔
//这里一定要注意下,item在引擎中设置时,要把单色子节点放在第一个位置
item.children[0].setContentSize(this.itemWidth - 10, this.itemHeight - 10);
item.children[0].color = Color[String(_map[row][col])]
item.children[1].getComponent(cc.Label).string = String(_map[row][col]);
item.name = row + "" + col//这里作用是移动的时候能在itemParent下找到节点
item.x = this.itemWidth / 2 + this.itemWidth * col;//这里的位置要自己推一下
item.y = -this.itemHeight / 2 - this.itemHeight * row;
PoolManager.getInstance().saveNode(item)
}
})
}
}
- Play.ts脚本修改如下
在预制体playPre中将itemParent节点和item预制体分别拖入,这时候我们就可以看到游戏中生成格子了