查看所有代码请点击 2048-typescript-cocoscreator
<https://github.com/Saber2pr/2048-typescript-cocoscreator>

先放上游戏体验的链接  Saber2pr/2048-typescript-cocoscreator
<https://saber2pr.github.io/MyWeb/build/2048/>

算法来自2048 游戏实现原理 <https://www.cnblogs.com/pipu-qiao/p/6012697.html>

算法看链接里就好,里面提供了最最核心的数学原理,就是不知道是哪位大佬想出来的,那位博主看起来也是转的。

言归正传,我们该怎么把它做成游戏。

先搭建一个这样子的场景



中间的正方形节点就是2048卡片的容器

然后我们写个接口,处理这个容器里节点的逻辑
/* * @Author: AK-12 * @Date: 2018-11-02 13:06:06 * @Last Modified by: AK-12 *
@Last Modified time: 2018-11-03 19:03:38 */ export default interface ILayout {
initEdge(size: { width: { start: number; end: number } height: { start: number;
end: number } }): void draw(step?: number): void log(): void }
分别是初始化边界方法,绘图方法,输出调试信息方法

然后实现接口
/* * @Author: AK-12 * @Date: 2018-11-01 20:07:29 * @Last Modified by: AK-12 *
@Last Modified time: 2018-11-03 20:14:09 */ import ILayout from './ILayout'
import Model from './Model' import { visitArray, computed, judgePos } from
'./MathVec' import Data from './Data' /** *Block节点视图的逻辑 * * @export * @class
Layout * @implements {ILayout} */ export default class Layout implements
ILayout { private background: cc.Node private edge: { width: { start: number;
end: number } height: { start: number; end: number } } private start: cc.Vec2
private 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) } /** *Creates an instance of Layout. * @param {cc.Node} background *
@param {number} offset * @param {number} [speed=0.2] * @memberof Layout */
constructor(background: cc.Node) { this.background = background return this }
/** *初始化边界 * * @memberof Layout */ public initEdge = (size: { width: { start:
number; end: number } height: { start: number; end: number } }): Layout => {
this.edge = size this.start = cc.v2(size.width.start, -size.height.start)
return this } /** *根据矩阵绘制block组 * * @param {number} [step=100] * @memberof
Layout */ public draw(step: number = 100): void {
Model.getInstance().clearNodeList() let data = Data.getInstance().data //
遍历block组 visitArray(data, (raw, col) => { if (data[raw][col] !== 0) { // 映射锚点位置
let pos = cc.v2(this.start.x + step * col, this.start.y - step * raw) // 取对象池节点
let block = Model.getInstance().getBlock() block.setParent(this.background)
block.setPosition(pos)
block.getChildByName('label').getComponent(cc.Label).string = String(
data[raw][col] ) block.color = this.color[String(data[raw][col])]
Model.getInstance().saveNode(block) } }) } /** *打印调试信息 * * @memberof Layout */
public log(): void { cc.log('nodelist:', Model.getInstance().NodeList.length) }
}
这里我只说一下其中的draw方法
public draw(step: number = 100): void { // Model是个单例,封装了对象池的操作 //
这里表示每次绘图前清楚掉上次的所有节点 // 因为使用了对象池, 所以性能不会受太大影响
Model.getInstance().clearNodeList() // Data也是个单例,内部封装了对矩阵(二维数组)的操作 // 获取到当前的矩阵
let data = Data.getInstance().data // 遍历block组 //
visitArray方法是一个反转控制的方法,内部是两层for的遍历,拿到矩阵的每个元素的位置(raw,col)注入到回调函数中
visitArray(data, (raw, col) => { // 如果矩阵在(raw, col)处的值不为0,
则映射到layout容器的对应位置,生成一个节点 if (data[raw][col] !== 0) { // 映射锚点位置 let pos =
cc.v2(this.start.x + step * col, this.start.y - step * raw) // 取对象池节点 let block
= Model.getInstance().getBlock() // 设置父节点为layout,加入节点树
block.setParent(this.background) // 设置映射好的位置 block.setPosition(pos) //
设置卡片显示的数字为矩阵(raw, col)处的值
block.getChildByName('label').getComponent(cc.Label).string = String(
data[raw][col] ) // 根据矩阵(raw, col)处的值设置卡片的颜色 block.color =
this.color[String(data[raw][col])] // 保存一下节点的引用,用于下次draw时的重绘
Model.getInstance().saveNode(block) } }) }
接下来要解决一个问题,也是另一个核心的问题,那就是怎么判断触摸的手势!以及对矩阵的操作!

 这里还是先大致写一个接口
export default interface ITouchFront { submit( callbackLeft?: Function,
callbackRight?: Function, callbackUp?: Function, callbackDown?: Function ):
ITouchFront listen(): void }
分别是注册触摸回调的方法,启动监听的方法。

然后实现接口
/* * @Author: AK-12 * @Date: 2018-11-01 13:31:42 * @Last Modified by: AK-12 *
@Last Modified time: 2018-11-03 19:25:20 */ import ITouchFront from
'./ITouchFront' /** *触摸方向执行对应回调 * * @export * @class TouchFront * @implements
{ITouchFront} */ export default class TouchFront implements ITouchFront {
private node: cc.Node private offset: number private delta: number private
_lock: number private callbackLeft: Function private callbackRight: Function
private callbackUp: Function private callbackDown: Function /** *Creates an
instance of TouchFront. * @param {cc.Node} node 监听节点 * @param {number}
[offset=100] 触摸偏移 ? 100 * @param {number} [delta=200] 灵敏度 ? 200 * @memberof
TouchFront */ constructor(node: cc.Node, offset: number = 100, delta: number =
200) { this.node = node this.offset = offset this._lock = 0 this.delta = delta
} /** *注册手势回调 * * @memberof TouchFront */ public submit = ( callbackLeft?:
Function, callbackRight?: Function, callbackUp?: Function, callbackDown?:
Function ): TouchFront => { this.callbackLeft = callbackLeft this.callbackRight
= callbackRight this.callbackUp = callbackUp this.callbackDown = callbackDown
return this } /** *监听触摸 * * @memberof TouchFront */ public listen = (): void =>
{ let originPos: cc.Vec2 this.node.on( cc.Node.EventType.TOUCH_START, touch =>
(originPos = touch.getLocation()) ) this.node.on(cc.Node.EventType.TOUCH_MOVE,
touch => { this._lock++ }) this.node.on(cc.Node.EventType.TOUCH_END, touch => {
this._lock < this.delta ? this.testPos(originPos, touch.getLocation()) : null
this._lock = 0 }) this.node.on(cc.Node.EventType.TOUCH_CANCEL, touch => {
this._lock < this.delta ? this.testPos(originPos, touch.getLocation()) : null
this._lock = 0 }) } /** *检测偏移执行回调 * * @private * @memberof TouchFront */
private testPos = (originPos: cc.Vec2, touchPos: cc.Vec2): void => { if (
Math.abs(touchPos.x - originPos.x) < this.offset && Math.abs(touchPos.y -
originPos.y) < this.offset ) { return } if ( Math.abs(touchPos.x - originPos.x)
> Math.abs(touchPos.y - originPos.y) ) { if (touchPos.x - originPos.x >
this.offset) { !!this.callbackRight ? this.callbackRight() : null } else if
(touchPos.x - originPos.x < -this.offset) { !!this.callbackLeft ?
this.callbackLeft() : null } } else { if (touchPos.y - originPos.y >
this.offset) { !!this.callbackUp ? this.callbackUp() : null } else if
(touchPos.y - originPos.y < -this.offset) { !!this.callbackDown ?
this.callbackDown() : null } } } }
看一下listen方法
/** *监听触摸 * * @memberof TouchFront */ // 由于不是纯函数,内联了大量回调函数,所以定义listen为箭头函数
public listen = (): void => { // 记录触摸开始的点 let originPos: cc.Vec2 this.node.on(
cc.Node.EventType.TOUCH_START, touch => (originPos = touch.getLocation()) ) //
this._lock是一个标记值,用来防止手抖动产生的连续回调 this.node.on(cc.Node.EventType.TOUCH_MOVE,
touch => { this._lock++ }) // 触摸结束和取消的逻辑处理一样
this.node.on(cc.Node.EventType.TOUCH_END, touch => { // 判断触摸滑动距离,太短暂不予处理 //
回调testPos方法,传入触摸结束的点 this._lock < this.delta ? this.testPos(originPos,
touch.getLocation()) : null this._lock = 0 })
this.node.on(cc.Node.EventType.TOUCH_CANCEL, touch => { this._lock < this.delta
? this.testPos(originPos, touch.getLocation()) : null this._lock = 0 }) }
然后解释一下核心的testPos检测触摸方向方法
/** *检测偏移执行回调 * * @private * @memberof TouchFront */ // 有大量回调,所以也是箭头函数 private
testPos = (originPos: cc.Vec2, touchPos: cc.Vec2): void => { //
this.offset是一个偏移量,如果触摸偏移小于偏移量则跳过此次回调 if ( Math.abs(touchPos.x - originPos.x) <
this.offset && Math.abs(touchPos.y - originPos.y) < this.offset ) { return } //
判断触摸偏移在x和y方向上的优先级 if ( Math.abs(touchPos.x - originPos.x) > Math.abs(touchPos.y
- originPos.y) ) { // 执行x方向的回调 // 如果是正偏移,则执行callbackRight,否则callbackLeft if
(touchPos.x - originPos.x > this.offset) { //判断callbackRight 是否定义
!!this.callbackRight ? this.callbackRight() : null } else if (touchPos.x -
originPos.x < -this.offset) { !!this.callbackLeft ? this.callbackLeft() : null
} } else { // 执行y方向的回调 // 如果是正偏移,则执行callbackUp,否则callbackDown if (touchPos.y -
originPos.y > this.offset) { !!this.callbackUp ? this.callbackUp() : null }
else if (touchPos.y - originPos.y < -this.offset) { !!this.callbackDown ?
this.callbackDown() : null } } }
基本最难的部分就是这些了

剩下其他的类和方法请访问我的github吧

Saber2pr <https://github.com/Saber2pr>/2048-typescript-cocoscreator
<https://github.com/Saber2pr/2048-typescript-cocoscreator>