实现金币飞入钱包的动画

效果

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

本文使用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;
  }
}

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

相关推荐
布鲁克零三四四18 天前
Cocos Creator导出obj文件用于后端寻路
cocos creator
烧仙草奶茶21 天前
【cocos creator】输入框滑动条联动小组建
cocos creator·cocos-creator
烧仙草奶茶3 个月前
【cocos creator】2.x里,使用3D射线碰撞检测
3d·cocos creator·cocos-creator·2.x
仅此而已7293 个月前
Cocos Creator倒计时
游戏引擎·cocos creator
仅此而已7293 个月前
cocosUI多分辨率适配
游戏引擎·cocos creator·多分辩率适配
SilenceJude3 个月前
cocos creator 3学习记录01——如何替换图片
前端·cocos creator
GrimRaider4 个月前
[Cocos Creator] v3.8开发知识点记录(持续更新)
开发·cocos creator
S_clifftop5 个月前
cocos creator如何使用cryptojs加解密(及引入方法)
cocos creator·加密解密·cryptojs
平淡风云5 个月前
cocosCreator获取手机剪切板内容
java·智能手机·typescript·android studio·cocos creator
zhenmu5 个月前
【cocos creator 3.x】 修改builtin-unlit 加了一个类似流光显示的mask参数
cocos creator·shader·effect