实现金币飞入钱包的动画

效果

金币从初始位置散开后逐个飞向指定位置,这是游戏中很常用的一个动画,效果如下:

本文使用Cocos Creator 2.4.11版本。

思路

这个效果中,分成两个阶段:

  • 一定数量的金币从一个起点散开
  • 这些金币逐一飞向终点

计算金币的初始散开位置

生成圆周上的等分点

金币散开的位置看似随机,但实际上是围绕起点形成一个圆。对于圆上的等分点,我们可以利用基本的三角函数来计算。例如,若要将圆分成8等分,每个点之间的夹角就是45度(360度/8)。已知圆心坐标和半径,就可以计算出每个等分点的坐标,如下图

随机偏移

为了让金币的位置看起来更自然,我们对每个点的位置进行随机偏移。这可以通过在计算出的坐标上加上一个随机向量来实现,从而让金币围绕起点呈现随机分布的效果。

位置代码如下:

typescript 复制代码
/**
   * 以某点为圆心,生成圆周上等分点的坐标
   * @param {number} radius 半径
   * @param {cc.Vec2} pos 圆心坐标
   * @param {number} count 等分点数量
   * @param {number} randomScope 等分点的随机波动范围
   * @returns {cc.Vec2[]} 返回等分点坐标
   */
  getCirclePosition(radius: number, pos: cc.Vec2, count: number, randomScope: number = 60): cc.Vec2[] {
    let positions = [];
    let radians = (Math.PI / 180) * Math.round(360 / count);
    for (let i = 0; i < count; i++) {
      let x = pos.x + radius * Math.sin(radians * i);
      let y = pos.y + radius * Math.cos(radians * i);
      positions.unshift(cc.v3(x + Math.random() * randomScope, y + Math.random() * randomScope, 0));
    }
    return positions;
  }

金币是一直在旋转,还需要在Cocos Creator编辑器中为预制体节点添加旋转动画

金币飞向目标位置

计算金币到目标位置的距离

在金币飞向钱包的过程中,我们希望金币按照距离钱包的远近顺序进入。因此,需要先计算每个金币到钱包的距离。这可以通过计算每个金币位置和钱包位置之间的向量距离来实现。

typescript 复制代码
let points = this.getCirclePosition(r, stPos, count);
let coinNodeList = points.map(pos => {
  let coin = this.getCoinNode();
  coin.setPosition(stPos);
  this.node.addChild(coin);
  return {
    node: coin,
    stPos: stPos,
    mdPos: pos,
    edPos: edPos,
    dis: (pos as any).sub(edPos).mag()
  };
});

排序和动画执行

根据计算出的距离对金币进行排序,使距离近的金币先飞入钱包。

typescript 复制代码
coinNodeList = coinNodeList.sort((a, b) => {
  if (a.dis - b.dis > 0) return 1;
  if (a.dis - b.dis < 0) return -1;
  return 0;
});

通过缓动动画系统播放金币飞向目标位置的动画

typescript 复制代码
// 执行金币落袋的动画
coinNodeList.forEach((item, idx) => {
  cc.tween(item.node)
      .to(0.3, {position: item.mdPos})
      .delay(idx * 0.01)
      .to(0.5, {position: item.edPos})
      .call(() => {
        // 金币落袋后,将金币节点放入节点池中,并更新金币数值
        this.coinNum += 20;
        this.coinNumLabel.string = this.coinNum.toString();
        this.coinPool.put(item.node);
      })
      .start();
});

这里使用节点池来重复利用金币节点,以防性能紧张

完整代码如下:

typescript 复制代码
const { ccclass, property } = cc._decorator;

@ccclass
export default class CoinRewardEffect extends cc.Component {
  /** 金币动画启动 */
  @property(cc.Node)
  startNode: cc.Node = null;

  /** 金币动画终点 */
  @property(cc.Node)
  endNode: cc.Node = null;

  /** 金币数值Label */
  @property(cc.Label)
  coinNumLabel: cc.Label = null;

  /** 金币预制节点 */
  @property(cc.Prefab)
  coinPrefab: cc.Prefab = null;

  /** 金币节点池 */
  coinPool: cc.NodePool = null;
  
  /** 金币数 */
  coinNum: number = 1000;

  onLoad() {
    this.coinPool = new cc.NodePool();
    this.coinNumLabel.string = this.coinNum.toString();
    this.initCoinPool();
  }

  /** 先预先创建几个节点放入节点池中 */
  initCoinPool(count: number = 20) {
    for (let i = 0; i < count; i++) {
      let coin = cc.instantiate(this.coinPrefab);
      this.coinPool.put(coin);
    }
  }

  /** 从节点池中取出节点 */
  getCoinNode() {
    let coin = null;
    if (this.coinPool.size() > 0) {
      coin = this.coinPool.get();
    } else {
      coin = cc.instantiate(this.coinPrefab);
    }
    return coin;
  }

  playAnim() {
    let randomCount = 20;//Math.random() * 10 + 10;
    let stPos = this.startNode.getPosition();
    let edPos = this.endNode.getPosition();
    this.playCoinRewardAnim(randomCount, stPos, edPos);
  }

  /**
   * 金币飞向钱包的动画
   *
   * @param {number} count 金币数量
   * @param {cc.Vec2} stPos 金币起始位置
   * @param {cc.Vec2} edPos 金币终点位置
   * @param {number} [r=130] 金币飞行的半径
   */
  playCoinRewardAnim(count: number, stPos: cc.Vec2, edPos: cc.Vec2, r: number = 130) {
    // 生成圆,并且对圆上的点进行排序
    let points = this.getCirclePosition(r, stPos, count);
    let coinNodeList = points.map(pos => {
      let coin = this.getCoinNode();
      coin.setPosition(stPos);
      this.node.addChild(coin);
      return {
        node: coin,
        stPos: stPos,
        mdPos: pos,
        edPos: edPos,
        dis: (pos as any).sub(edPos).mag()
      };
    });
    coinNodeList = coinNodeList.sort((a, b) => {
      if (a.dis - b.dis > 0) return 1;
      if (a.dis - b.dis < 0) return -1;
      return 0;
    });
    
    // 执行金币落袋的动画
    coinNodeList.forEach((item, idx) => {
      cc.tween(item.node)
          .to(0.3, {position: item.mdPos})
          .delay(idx * 0.01)
          .to(0.5, {position: item.edPos})
          .call(() => {
            // 金币落袋后,将金币节点放入节点池中,并更新金币数值
            this.coinNum += 20;
            this.coinNumLabel.string = this.coinNum.toString();
            this.coinPool.put(item.node);
          })
          .start();
    });
  }

  /**
   * 以某点为圆心,生成圆周上等分点的坐标
   * @param {number} radius 半径
   * @param {cc.Vec2} pos 圆心坐标
   * @param {number} count 等分点数量
   * @param {number} randomScope 等分点的随机波动范围
   * @returns {cc.Vec2[]} 返回等分点坐标
   */
  getCirclePosition(radius: number, pos: cc.Vec2, count: number, randomScope: number = 60): cc.Vec2[] {
    let positions = [];
    let radians = (Math.PI / 180) * Math.round(360 / count);
    for (let i = 0; i < count; i++) {
      let x = pos.x + radius * Math.sin(radians * i);
      let y = pos.y + radius * Math.cos(radians * i);
      positions.unshift(cc.v3(x + Math.random() * randomScope, y + Math.random() * randomScope, 0));
    }
    return positions;
  }
}

到此就实现了开头的效果。

相关推荐
VaJoy16 天前
Cocos Creator Shader 入门 ⑺ —— 图层混合样式的实现与 Render Texture
cocos creator
VaJoy18 天前
Cocos Creator Shader 入门 ⑹ —— 灰阶、反色等滤镜的实现
cocos creator
VaJoy20 天前
Cocos Creator Shader 入门 ⑸ —— 代码复用与绿幕抠图技术
cocos creator
VaJoy22 天前
Cocos Creator Shader 入门 ⑷ —— 纹理采样与受击闪白的实现
cocos creator
VaJoy23 天前
Cocos Creator Shader 入门 ⑶ —— 给节点设置透明度
cocos creator
VaJoy25 天前
Cocos Creator Shader 入门 (2) —— 给节点染色
cocos creator
VaJoy1 个月前
Cocos Creator Shader —— 附录
cocos creator
成长ing121381 个月前
多层背景视差滚动Parallax Scrolling
cocos creator
似水流年wxk1 个月前
cocos creator使用jenkins打包微信小游戏,自动上传资源到cdn,windows版运行jenkins
运维·jenkins·cocos creator
成长ing121383 个月前
点击音效系统
前端·cocos creator