302 lines
11 KiB
TypeScript
302 lines
11 KiB
TypeScript
/** 只负责领取反馈、轨道移动和新卡出现动画。 */
|
|
export default class JungleAnimator {
|
|
constructor(
|
|
private host: cc.Component,
|
|
private contentNode: cc.Node,
|
|
private cardFrame: cc.SpriteFrame,
|
|
private unlockFrame: cc.SpriteFrame,
|
|
) { }
|
|
|
|
/** 档位从锁定变为当前可领取时,播放锁旋转下坠和按钮回弹。 */
|
|
public playUnlockEffect(card: cc.Node, onComplete: () => void): void {
|
|
let button = card && card.getChildByName("button");
|
|
let buttonVisual = button && button.getChildByName("button_content");
|
|
let lock = button && button.getChildByName("lock");
|
|
if (!button || !lock) {
|
|
onComplete();
|
|
return;
|
|
}
|
|
|
|
let buttonComponent = button.getComponent(cc.Button);
|
|
let lockSprite = lock.getComponent(cc.Sprite);
|
|
let originalLockFrame = lockSprite && lockSprite.spriteFrame;
|
|
let originalLockSize = lock.getContentSize();
|
|
let buttonLocalPosition = lock.getPosition();
|
|
let originZIndex = lock.zIndex;
|
|
let lockWorldPosition = lock.convertToWorldSpaceAR(cc.Vec2.ZERO);
|
|
let originPosition = this.contentNode.convertToNodeSpaceAR(lockWorldPosition);
|
|
let screenBottomY = cc.visibleRect && cc.visibleRect.bottom
|
|
? cc.visibleRect.bottom.y
|
|
: 0;
|
|
let dropWorldPosition = cc.v2(
|
|
lockWorldPosition.x,
|
|
screenBottomY - lock.height * 0.6,
|
|
);
|
|
let dropLocalPosition = this.contentNode.convertToNodeSpaceAR(dropWorldPosition);
|
|
let originX = originPosition.x;
|
|
let originY = originPosition.y;
|
|
let dropDistance = Math.max(0, originY - dropLocalPosition.y);
|
|
let dropDuration = Math.min(0.42, Math.max(0.22, dropDistance / 2800));
|
|
button.stopAllActions();
|
|
lock.stopAllActions();
|
|
if (buttonVisual) {
|
|
buttonVisual.stopAllActions();
|
|
buttonVisual.opacity = 0;
|
|
buttonVisual.scale = 0.92;
|
|
}
|
|
if (buttonComponent) {
|
|
buttonComponent.interactable = false;
|
|
}
|
|
|
|
button.scale = 1;
|
|
lock.active = true;
|
|
lock.opacity = 255;
|
|
lock.scale = 1;
|
|
lock.angle = 0;
|
|
if (lockSprite && this.unlockFrame) {
|
|
lockSprite.spriteFrame = this.unlockFrame;
|
|
lock.setContentSize(this.unlockFrame.getOriginalSize());
|
|
}
|
|
lock.parent = this.contentNode;
|
|
lock.zIndex = 60;
|
|
lock.setPosition(originX, originY);
|
|
|
|
cc.tween(button)
|
|
.to(0.08, { scale: 1.08 }, { easing: "quadOut" })
|
|
.to(0.1, { scale: 1 }, { easing: "backOut" })
|
|
.start();
|
|
|
|
if (buttonVisual) {
|
|
cc.tween(buttonVisual)
|
|
.to(0.08, { opacity: 255, scale: 1.06 }, { easing: "backOut" })
|
|
.to(0.1, { scale: 1 }, { easing: "sineOut" })
|
|
.start();
|
|
}
|
|
|
|
cc.tween(lock)
|
|
.to(0.04, { y: originY + 7, angle: -12 }, { easing: "quadOut" })
|
|
.to(0.04, { y: originY + 9, angle: 10 }, { easing: "sineInOut" })
|
|
.to(0.03, { y: originY + 4, angle: -6 }, { easing: "sineInOut" })
|
|
.parallel(
|
|
cc.tween().to(dropDuration, {
|
|
x: originX - 28,
|
|
y: dropLocalPosition.y,
|
|
angle: 220,
|
|
}, { easing: "quadIn" }),
|
|
cc.tween().to(dropDuration, { scale: 0.82 }, { easing: "quadIn" }),
|
|
)
|
|
.call(() => {
|
|
lock.active = false;
|
|
lock.opacity = 255;
|
|
lock.scale = 1;
|
|
lock.angle = 0;
|
|
if (lockSprite && originalLockFrame) {
|
|
lockSprite.spriteFrame = originalLockFrame;
|
|
lock.setContentSize(originalLockSize);
|
|
}
|
|
lock.parent = button;
|
|
lock.zIndex = originZIndex;
|
|
lock.setPosition(buttonLocalPosition);
|
|
|
|
if (buttonComponent) {
|
|
buttonComponent.interactable = true;
|
|
}
|
|
onComplete();
|
|
})
|
|
.start();
|
|
}
|
|
|
|
public playClaimEffect(card: cc.Node, onComplete: () => void): void {
|
|
const button = card.getChildByName("button");
|
|
const buttonVisual = button && button.getChildByName("button_content");
|
|
const lock = button && button.getChildByName("lock");
|
|
if (button) {
|
|
button.getComponent(cc.Button).interactable = false;
|
|
cc.tween(button)
|
|
.to(0.08, { scale: 1.08 }, { easing: "quadOut" })
|
|
.to(0.1, { scale: 1 })
|
|
.start();
|
|
}
|
|
if (buttonVisual) {
|
|
buttonVisual.opacity = 0;
|
|
}
|
|
if (lock) {
|
|
lock.active = false;
|
|
}
|
|
|
|
const flash = this.createSpriteNode("claim_flash", this.cardFrame);
|
|
flash.parent = card;
|
|
flash.zIndex = 8;
|
|
flash.color = cc.color(255, 244, 160);
|
|
flash.opacity = 0;
|
|
cc.tween(flash)
|
|
.to(0.1, { opacity: 150 })
|
|
.to(0.18, { opacity: 0 })
|
|
.call(() => flash.destroy())
|
|
.start();
|
|
|
|
const check = new cc.Node("claim_check");
|
|
check.parent = button;
|
|
check.zIndex = 20;
|
|
check.setContentSize(84, 84);
|
|
check.scale = 0;
|
|
const checkGraphics = check.addComponent(cc.Graphics);
|
|
checkGraphics.fillColor = cc.color(93, 205, 52, 255);
|
|
checkGraphics.strokeColor = cc.color(255, 255, 255, 255);
|
|
checkGraphics.lineWidth = 7;
|
|
checkGraphics.circle(0, 0, 35);
|
|
checkGraphics.fill();
|
|
checkGraphics.stroke();
|
|
checkGraphics.lineWidth = 9;
|
|
checkGraphics.moveTo(-19, 1);
|
|
checkGraphics.lineTo(-5, -14);
|
|
checkGraphics.lineTo(23, 18);
|
|
checkGraphics.stroke();
|
|
cc.tween(check)
|
|
.to(0.16, { scale: 1.15 }, { easing: "backOut" })
|
|
.to(0.08, { scale: 1 })
|
|
.delay(0.18)
|
|
.to(0.1, { opacity: 0 })
|
|
.call(() => check.destroy())
|
|
.start();
|
|
|
|
const rewardsNode = card.getChildByName("rewards");
|
|
const rewardGroups = rewardsNode ? rewardsNode.children.slice() : [];
|
|
rewardGroups.forEach((group, index) => {
|
|
const flyNode = cc.instantiate(group);
|
|
const worldPosition = group.convertToWorldSpaceAR(cc.Vec2.ZERO);
|
|
flyNode.parent = this.contentNode;
|
|
flyNode.setPosition(this.contentNode.convertToNodeSpaceAR(worldPosition));
|
|
flyNode.zIndex = 30;
|
|
const horizontalOffset = (index - (rewardGroups.length - 1) * 0.5) * 42;
|
|
cc.tween(flyNode)
|
|
.delay(index * 0.035)
|
|
.parallel(
|
|
cc.tween().by(0.42, { x: horizontalOffset, y: 190 + index % 2 * 24 }, { easing: "quadOut" }),
|
|
cc.tween().to(0.42, { opacity: 0, scale: 1.12 }),
|
|
)
|
|
.call(() => flyNode.destroy())
|
|
.start();
|
|
});
|
|
|
|
this.host.scheduleOnce(onComplete, 0.5);
|
|
}
|
|
|
|
public playCardExit(card: cc.Node, onComplete: () => void): void {
|
|
cc.tween(card)
|
|
.parallel(
|
|
cc.tween().to(0.2, { scale: 0.78, opacity: 0 }),
|
|
cc.tween().by(0.2, { y: 55 }),
|
|
)
|
|
.call(onComplete)
|
|
.start();
|
|
}
|
|
|
|
public moveCardsWithOverlap(
|
|
cards: cc.Node[],
|
|
slotPositions: cc.Vec2[],
|
|
cardIndex: number,
|
|
onNewCardReady: () => void,
|
|
): void {
|
|
if (cardIndex >= cards.length) {
|
|
onNewCardReady();
|
|
return;
|
|
}
|
|
|
|
const movingCard = cards[cardIndex];
|
|
if (!movingCard.active) {
|
|
this.moveCardsWithOverlap(cards, slotPositions, cardIndex + 1, onNewCardReady);
|
|
return;
|
|
}
|
|
|
|
let nextCardIndex = cardIndex + 1;
|
|
while (nextCardIndex < cards.length && !cards[nextCardIndex].active) {
|
|
nextCardIndex++;
|
|
}
|
|
|
|
const targetPosition = slotPositions[cardIndex - 1];
|
|
const startPosition = cc.v2(movingCard.x, movingCard.y);
|
|
const brakePosition = startPosition.lerp(targetPosition, 0.75);
|
|
cc.tween(movingCard)
|
|
.to(0.13, { x: brakePosition.x, y: brakePosition.y }, { easing: "linear" })
|
|
.call(() => {
|
|
if (nextCardIndex < cards.length) {
|
|
this.moveCardsWithOverlap(cards, slotPositions, nextCardIndex, onNewCardReady);
|
|
} else {
|
|
onNewCardReady();
|
|
}
|
|
})
|
|
.to(0.5, { x: targetPosition.x, y: targetPosition.y }, { easing: "cubicOut" })
|
|
.start();
|
|
}
|
|
|
|
public showNewCard(card: cc.Node, targetCenter: cc.Vec2, onComplete: () => void): void {
|
|
card.setAnchorPoint(0.5, 0.5);
|
|
card.scale = 0.06;
|
|
card.setPosition(targetCenter);
|
|
card.opacity = 0;
|
|
card.active = true;
|
|
|
|
cc.tween(card)
|
|
.parallel(
|
|
cc.tween().to(0.2, { opacity: 255 }, { easing: "sineOut" }),
|
|
cc.tween()
|
|
.to(0.28, { scale: 1.07 }, { easing: "cubicOut" })
|
|
.to(0.1, { scale: 0.97 }, { easing: "sineOut" })
|
|
.to(0.09, { scale: 1.02 }, { easing: "sineOut" })
|
|
.to(0.08, { scale: 1 }, { easing: "sineOut" }),
|
|
)
|
|
.call(onComplete)
|
|
.start();
|
|
}
|
|
|
|
/** 最前方礼包的短促放大回弹,用于吸引点击。 */
|
|
public playCardAttention(card: cc.Node, onComplete: () => void): void {
|
|
if (!card || !cc.isValid(card) || !card.activeInHierarchy) {
|
|
onComplete();
|
|
return;
|
|
}
|
|
|
|
card.scaleX = 1;
|
|
card.scaleY = 1;
|
|
cc.tween(card)
|
|
.to(0.14, { scaleX: 1.06, scaleY: 0.95 }, { easing: "sineInOut" })
|
|
.to(0.17, { scaleX: 0.97, scaleY: 1.06 }, { easing: "sineInOut" })
|
|
.to(0.18, { scaleX: 1.025, scaleY: 0.985 }, { easing: "sineInOut" })
|
|
.to(0.2, { scaleX: 1, scaleY: 1 }, { easing: "sineOut" })
|
|
.call(onComplete)
|
|
.start();
|
|
}
|
|
|
|
public stopCardAttention(card: cc.Node): void {
|
|
if (!card || !cc.isValid(card)) {
|
|
return;
|
|
}
|
|
cc.Tween.stopAllByTarget(card);
|
|
card.scaleX = 1;
|
|
card.scaleY = 1;
|
|
}
|
|
|
|
public playLockedFeedback(card: cc.Node): void {
|
|
card.stopAllActions();
|
|
const x = card.x;
|
|
cc.tween(card)
|
|
.to(0.05, { x: x - 9 })
|
|
.to(0.05, { x: x + 9 })
|
|
.to(0.05, { x: x - 5 })
|
|
.to(0.05, { x })
|
|
.start();
|
|
}
|
|
|
|
private createSpriteNode(name: string, frame: cc.SpriteFrame): cc.Node {
|
|
const node = new cc.Node(name);
|
|
const sprite = node.addComponent(cc.Sprite);
|
|
sprite.sizeMode = cc.Sprite.SizeMode.RAW;
|
|
sprite.spriteFrame = frame;
|
|
if (frame) {
|
|
node.setContentSize(frame.getOriginalSize());
|
|
}
|
|
return node;
|
|
}
|
|
}
|