记录--实现金币飞入钱包的动画
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
效果
金币从初始位置散开后逐个飞向指定位置,这是游戏中很常用的一个动画,效果如下:
思路
这个效果中,分成两个阶段:
- 一定数量的金币从一个起点散开
- 这些金币逐一飞向终点
计算金币的初始散开位置
生成圆周上的等分点
金币散开的位置看似随机,但实际上是围绕起点形成一个圆。对于圆上的等分点,我们可以利用基本的三角函数来计算。例如,若要将圆分成8等分,每个点之间的夹角就是45度(360度/8)。已知圆心坐标和半径,就可以计算出每个等分点的坐标,如下图
随机偏移
为了让金币的位置看起来更自然,我们对每个点的位置进行随机偏移。这可以通过在计算出的坐标上加上一个随机向量来实现,从而让金币围绕起点呈现随机分布的效果。
位置代码如下:
/** * 以某点为圆心,生成圆周上等分点的坐标 * @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编辑器中为预制体节点添加旋转动画
金币飞向目标位置
计算金币到目标位置的距离
在金币飞向钱包的过程中,我们希望金币按照距离钱包的远近顺序进入。因此,需要先计算每个金币到钱包的距离。这可以通过计算每个金币位置和钱包位置之间的向量距离来实现。
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(); });
这里使用节点池来重复利用金币节点,以防性能紧张
完整代码如下:
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; } }
到此就实现了开头的效果。