查看所有代码请点击 2048-typescript-cocoscreator
先放上游戏体验的链接 Saber2pr/2048-typescript-cocoscreator
算法来自2048 游戏实现原理
算法看链接里就好,里面提供了最最核心的数学原理,就是不知道是哪位大佬想出来的,那位博主看起来也是转的。
言归正传,我们该怎么把它做成游戏。
先搭建一个这样子的场景
中间的正方形节点就是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/2048-typescript-cocoscreator