CocosCreator | 2048使用Typescript实现及原理

查看所有代码请点击 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

猜你喜欢

转载自blog.csdn.net/u011607490/article/details/83691416