feat(hearts): enhance card sorting, drag interaction, and pass animations
- Sort player's hand by suit and value for consistent layout - Implement drag-to-sort functionality for the local hand with visual feedback (slot indicators, card displacement, play zone highlighting) - Refactor pass confirmation to include multi-phase animation: outgoing cards fly to target, incoming cards animate in from source, flip to reveal, and settle into sorted positions - Add background to hearts broken indicator for better visibility - Update card interaction handlers to support both click and drag events
This commit is contained in:
parent
3cea4f10b6
commit
4b0551c3cd
Binary file not shown.
Binary file not shown.
|
|
@ -24,6 +24,16 @@
|
|||
"file": "track05.mp3",
|
||||
"artist": "Guns 'n Roses",
|
||||
"title": "Thunderbird"
|
||||
},
|
||||
{
|
||||
"file": "track06.mp3",
|
||||
"artist": "Simon Stalenhag",
|
||||
"title": "Behind the Reactor"
|
||||
},
|
||||
{
|
||||
"file": "track07.mp3",
|
||||
"artist": "Jean Michel Jarre",
|
||||
"title": "Oblivion"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import {
|
|||
playCard,
|
||||
legalPlays,
|
||||
startNextHand,
|
||||
passTargetSeat,
|
||||
PASS_COUNT,
|
||||
} from './HeartsLogic.js';
|
||||
import { choosePass, choosePlay } from './HeartsAI.js';
|
||||
|
|
@ -84,6 +85,10 @@ export default class HeartsGame extends Phaser.Scene {
|
|||
this.passButton = null;
|
||||
this.scoreTexts = [];
|
||||
this.turnGlow = null;
|
||||
|
||||
this.localHandCards = []; // player 0 card containers in hand order
|
||||
this.dragState = null;
|
||||
this.potentialDrag = null;
|
||||
}
|
||||
|
||||
create() {
|
||||
|
|
@ -92,6 +97,7 @@ export default class HeartsGame extends Phaser.Scene {
|
|||
this.buildSeats();
|
||||
this.buildCenter();
|
||||
this.buildHUD();
|
||||
this.setupDragHandlers();
|
||||
this.startNewMatch();
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +135,8 @@ export default class HeartsGame extends Phaser.Scene {
|
|||
}
|
||||
|
||||
buildCenter() {
|
||||
this.heartsText = this.add.text(CX, CY - 170, '', {
|
||||
this.heartsBg = this.add.graphics().setDepth(D.ui - 1);
|
||||
this.heartsText = this.add.text(CX, CY - 195, '', {
|
||||
fontFamily: 'Righteous', fontSize: '18px', color: COLORS.dangerHex,
|
||||
}).setOrigin(0.5).setDepth(D.ui);
|
||||
|
||||
|
|
@ -181,6 +188,11 @@ export default class HeartsGame extends Phaser.Scene {
|
|||
this.updateHeartsIndicator();
|
||||
playSound(this, SFX.CARD_SHUFFLE);
|
||||
await this.dealAnimation();
|
||||
const SUIT_ORDER = { c: 0, d: 1, s: 2, h: 3 };
|
||||
this.gs.players[0].hand.sort((a, b) => {
|
||||
const sd = SUIT_ORDER[a.suit] - SUIT_ORDER[b.suit];
|
||||
return sd !== 0 ? sd : a.value - b.value;
|
||||
});
|
||||
this.renderHands();
|
||||
this.updateScores();
|
||||
this.busy = false;
|
||||
|
|
@ -222,18 +234,111 @@ export default class HeartsGame extends Phaser.Scene {
|
|||
|
||||
async confirmPass() {
|
||||
if (this.passSelection.size !== PASS_COUNT) return;
|
||||
const ids = [...this.passSelection];
|
||||
const outgoingIds = [...this.passSelection];
|
||||
this.passButton?.destroy();
|
||||
this.passButton = null;
|
||||
this.passSelection.clear();
|
||||
this.busy = true;
|
||||
|
||||
const dir = DIRECTION_LABEL[this.gs.passDirection];
|
||||
this.gs = selectPass(this.gs, 0, ids); // all four are ready → pass resolves
|
||||
this.showBanner(`Cards passed ${dir}.`);
|
||||
const dir = this.gs.passDirection;
|
||||
const targetSeat = passTargetSeat(0, dir);
|
||||
|
||||
// Find which opponent passes TO player 0.
|
||||
let sourceSeat = 1;
|
||||
for (let s = 1; s < 4; s++) {
|
||||
if (passTargetSeat(s, dir) === 0) { sourceSeat = s; break; }
|
||||
}
|
||||
|
||||
// Capture incoming card data before state is applied.
|
||||
const incomingIds = new Set(this.gs.pendingPass[sourceSeat]);
|
||||
const incomingCards = this.gs.players[sourceSeat].hand.filter((c) => incomingIds.has(c.id));
|
||||
|
||||
const targetLay = slotLayout(SLOTS[targetSeat]);
|
||||
const sourceLay = slotLayout(SLOTS[sourceSeat]);
|
||||
|
||||
// ── Phase 1: Player's cards fly to the target opponent ───────────────────
|
||||
const outgoingSprites = outgoingIds
|
||||
.map((id) => this.handCardObjs.get(`0-${id}`))
|
||||
.filter(Boolean);
|
||||
for (const s of outgoingSprites) s.setDepth(D.ui + 5);
|
||||
playSound(this, SFX.CARD_DEAL);
|
||||
await Promise.all(outgoingSprites.map((s) =>
|
||||
this.tweenTo(s, targetLay.portrait.x, targetLay.portrait.y, 380)
|
||||
));
|
||||
|
||||
// Apply state and sort the final hand.
|
||||
this.gs = selectPass(this.gs, 0, outgoingIds);
|
||||
const SUIT_ORDER = { c: 0, d: 1, s: 2, h: 3 };
|
||||
this.gs.players[0].hand.sort((a, b) => {
|
||||
const sd = SUIT_ORDER[a.suit] - SUIT_ORDER[b.suit];
|
||||
return sd !== 0 ? sd : a.value - b.value;
|
||||
});
|
||||
|
||||
for (const s of outgoingSprites) s.destroy();
|
||||
this.renderHands();
|
||||
await this.delay(900);
|
||||
this.hideBanner();
|
||||
// Keep incoming card positions reserved but invisible until they animate in.
|
||||
for (const card of incomingCards) {
|
||||
this.handCardObjs.get(`0-${card.id}`)?.setAlpha(0);
|
||||
}
|
||||
|
||||
// ── Phase 2: Face-down cards fly in from source opponent ─────────────────
|
||||
const stagingY = slotLayout('bottom').handCenter.y - 180;
|
||||
const stagingSprites = incomingCards.map((card) => {
|
||||
const s = this.makeCardSprite(card, sourceLay.portrait.x, sourceLay.portrait.y, { faceUp: false });
|
||||
s.setDepth(D.ui + 5);
|
||||
return s;
|
||||
});
|
||||
|
||||
playSound(this, SFX.CARD_DEAL);
|
||||
await Promise.all(stagingSprites.map((s, i) =>
|
||||
new Promise((resolve) => {
|
||||
this.tweens.add({
|
||||
targets: s,
|
||||
x: CX + (i - 1) * (HAND_SPREAD + 12),
|
||||
y: stagingY,
|
||||
delay: i * 90,
|
||||
duration: 380,
|
||||
ease: 'Cubic.easeOut',
|
||||
onComplete: resolve,
|
||||
});
|
||||
})
|
||||
));
|
||||
|
||||
// ── Phase 3: Flip each card to reveal face-up ─────────────────────────────
|
||||
for (let i = 0; i < stagingSprites.length; i++) {
|
||||
const sprite = stagingSprites[i];
|
||||
const card = incomingCards[i];
|
||||
await new Promise((resolve) => {
|
||||
this.tweens.add({
|
||||
targets: sprite, scaleX: 0, duration: 140, ease: 'Linear',
|
||||
onComplete: () => {
|
||||
this.renderCardFace(sprite, card, true);
|
||||
this.tweens.add({ targets: sprite, scaleX: 1, duration: 140, ease: 'Linear', onComplete: resolve });
|
||||
},
|
||||
});
|
||||
});
|
||||
playSound(this, SFX.CARD_PLACE);
|
||||
if (i < stagingSprites.length - 1) await this.delay(70);
|
||||
}
|
||||
|
||||
await this.delay(420);
|
||||
|
||||
// ── Phase 4: Cards slide into their sorted positions in the hand ──────────
|
||||
await Promise.all(stagingSprites.map((sprite, i) => {
|
||||
const target = this.handCardObjs.get(`0-${incomingCards[i].id}`);
|
||||
if (!target) { sprite.destroy(); return Promise.resolve(); }
|
||||
return new Promise((resolve) => {
|
||||
this.tweens.add({
|
||||
targets: sprite, x: target.x, y: target.y, duration: 320, ease: 'Cubic.easeOut',
|
||||
onComplete: () => {
|
||||
target.setAlpha(1);
|
||||
sprite.destroy();
|
||||
resolve();
|
||||
},
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
this.busy = false;
|
||||
await this.advance();
|
||||
}
|
||||
|
|
@ -423,6 +528,7 @@ export default class HeartsGame extends Phaser.Scene {
|
|||
}
|
||||
|
||||
renderSeatHand(seat) {
|
||||
if (seat === 0) this.localHandCards = [];
|
||||
const player = this.gs.players[seat];
|
||||
const lay = slotLayout(SLOTS[seat]);
|
||||
const n = player.hand.length;
|
||||
|
|
@ -447,13 +553,21 @@ export default class HeartsGame extends Phaser.Scene {
|
|||
const interactive = selecting || (playable && this.legalIds.has(card.id));
|
||||
const dimmed = playable && !this.legalIds.has(card.id);
|
||||
if (dimmed) sprite.setAlpha(0.45);
|
||||
if (interactive) {
|
||||
sprite.setInteractive(new Phaser.Geom.Rectangle(-CARD_W / 2, -CARD_H / 2, CARD_W, CARD_H), Phaser.Geom.Rectangle.Contains);
|
||||
sprite.input.cursor = 'pointer';
|
||||
sprite.on('pointerover', () => { if (!this.busy) { sprite.y -= 10; } });
|
||||
sprite.on('pointerout', () => { if (!this.busy) { sprite.y += 10; } });
|
||||
sprite.on('pointerdown', () => this.onCardClick(card.id));
|
||||
}
|
||||
|
||||
sprite.setInteractive(new Phaser.Geom.Rectangle(-CARD_W / 2, -CARD_H / 2, CARD_W, CARD_H), Phaser.Geom.Rectangle.Contains);
|
||||
sprite.input.cursor = interactive ? 'pointer' : 'default';
|
||||
sprite.on('pointerover', () => { if (!this.busy && !this.dragState) sprite.y -= 10; });
|
||||
sprite.on('pointerout', () => { if (!this.busy && !this.dragState) sprite.y += 10; });
|
||||
sprite.on('pointerdown', (pointer) => {
|
||||
if (this.busy || this.dragState) return;
|
||||
this.potentialDrag = {
|
||||
handIdx: i, cardId: card.id, isInteractive: interactive,
|
||||
startX: pointer.x, startY: pointer.y,
|
||||
offsetX: pointer.x - sprite.x, offsetY: pointer.y - sprite.y,
|
||||
};
|
||||
});
|
||||
|
||||
this.localHandCards.push(sprite);
|
||||
if (selected) this.ringCard(x, y);
|
||||
}
|
||||
}
|
||||
|
|
@ -516,7 +630,15 @@ export default class HeartsGame extends Phaser.Scene {
|
|||
}
|
||||
|
||||
updateHeartsIndicator() {
|
||||
this.heartsText?.setText(this.gs.heartsBroken ? '♥ Hearts broken' : '');
|
||||
const broken = this.gs.heartsBroken;
|
||||
this.heartsText?.setText(broken ? '♥ Hearts broken' : '');
|
||||
this.heartsBg?.clear();
|
||||
if (broken) {
|
||||
const t = this.heartsText;
|
||||
const pad = 8;
|
||||
this.heartsBg.fillStyle(0x000000, 0.55);
|
||||
this.heartsBg.fillRoundedRect(t.x - t.width / 2 - pad, t.y - t.height / 2 - pad, t.width + pad * 2, t.height + pad * 2, 6);
|
||||
}
|
||||
}
|
||||
|
||||
updateTurnIndicator() {
|
||||
|
|
@ -592,6 +714,9 @@ export default class HeartsGame extends Phaser.Scene {
|
|||
// ── Cleanup helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
clearHandObjs() {
|
||||
this.dragState = null;
|
||||
this.potentialDrag = null;
|
||||
this.localHandCards = [];
|
||||
for (const c of this.handCardObjs.values()) c.destroy();
|
||||
this.handCardObjs.clear();
|
||||
}
|
||||
|
|
@ -606,6 +731,204 @@ export default class HeartsGame extends Phaser.Scene {
|
|||
this.trickSprites.clear();
|
||||
}
|
||||
|
||||
// ── Hand drag-to-sort ─────────────────────────────────────────────────────
|
||||
|
||||
setupDragHandlers() {
|
||||
this.input.on('pointermove', (pointer) => {
|
||||
if (this.potentialDrag && !this.dragState) {
|
||||
const dx = pointer.x - this.potentialDrag.startX;
|
||||
const dy = pointer.y - this.potentialDrag.startY;
|
||||
if (Math.sqrt(dx * dx + dy * dy) >= 8) {
|
||||
const pd = this.potentialDrag;
|
||||
this.potentialDrag = null;
|
||||
this.startCardDrag(pd.handIdx, pd.offsetX, pd.offsetY);
|
||||
}
|
||||
}
|
||||
if (this.dragState) this.updateCardDrag(pointer);
|
||||
});
|
||||
|
||||
this.input.on('pointerup', () => {
|
||||
if (this.potentialDrag) {
|
||||
const { cardId, isInteractive } = this.potentialDrag;
|
||||
this.potentialDrag = null;
|
||||
if (isInteractive) this.onCardClick(cardId);
|
||||
return;
|
||||
}
|
||||
if (this.dragState) this.endCardDrag();
|
||||
});
|
||||
}
|
||||
|
||||
startCardDrag(handIdx, offsetX, offsetY) {
|
||||
const card = this.localHandCards[handIdx];
|
||||
if (!card) return;
|
||||
const hand = this.gs.players[0].hand;
|
||||
const n = hand.length;
|
||||
const lay = slotLayout('bottom');
|
||||
const handStartX = lay.handCenter.x - (n - 1) / 2 * HAND_SPREAD;
|
||||
const handCenterY = lay.handCenter.y;
|
||||
const insertIdx = Phaser.Math.Clamp(Math.round((card.x - handStartX) / HAND_SPREAD), 0, n - 1);
|
||||
const isLegal = this.gs.phase === 'playing' && this.awaitingHuman && this.legalIds.has(hand[handIdx].id);
|
||||
|
||||
this.tweens.killTweensOf(card);
|
||||
|
||||
const shadow = this.add.graphics();
|
||||
shadow.fillStyle(0x000000, 0.35);
|
||||
shadow.fillEllipse(0, 0, CARD_W * 1.1, 28);
|
||||
shadow.setPosition(card.x + 6, card.y + 16);
|
||||
shadow.setDepth(D.card + 9);
|
||||
this.transient.push(shadow);
|
||||
|
||||
const slotIndicator = this.add.graphics();
|
||||
slotIndicator.lineStyle(3, COLORS.accent, 0.75);
|
||||
slotIndicator.strokeRoundedRect(-CARD_W / 2 - 4, -CARD_H / 2 - 4, CARD_W + 8, CARD_H + 8, 10);
|
||||
slotIndicator.fillStyle(COLORS.accent, 0.1);
|
||||
slotIndicator.fillRoundedRect(-CARD_W / 2 - 4, -CARD_H / 2 - 4, CARD_W + 8, CARD_H + 8, 10);
|
||||
slotIndicator.setPosition(handStartX + insertIdx * HAND_SPREAD, handCenterY);
|
||||
slotIndicator.setDepth(D.card - 1);
|
||||
this.transient.push(slotIndicator);
|
||||
|
||||
let playZoneHighlight = null;
|
||||
if (isLegal) {
|
||||
const pt = slotLayout('bottom').trick;
|
||||
playZoneHighlight = this.add.graphics();
|
||||
playZoneHighlight.lineStyle(4, COLORS.accent, 0.9);
|
||||
playZoneHighlight.strokeRoundedRect(-CARD_W / 2 - 6, -CARD_H / 2 - 6, CARD_W + 12, CARD_H + 12, 12);
|
||||
playZoneHighlight.fillStyle(COLORS.accent, 0.18);
|
||||
playZoneHighlight.fillRoundedRect(-CARD_W / 2 - 6, -CARD_H / 2 - 6, CARD_W + 12, CARD_H + 12, 12);
|
||||
playZoneHighlight.setPosition(pt.x, pt.y);
|
||||
playZoneHighlight.setDepth(D.card + 1);
|
||||
playZoneHighlight.setAlpha(0);
|
||||
this.transient.push(playZoneHighlight);
|
||||
}
|
||||
|
||||
this.dragState = { cardIdx: handIdx, offsetX, offsetY, shadow, slotIndicator, insertIdx, handStartX, handCenterY, isLegal, playZoneHighlight, overPlayZone: false };
|
||||
|
||||
card.setDepth(D.card + 10);
|
||||
this.tweens.add({ targets: card, scaleX: 1.08, scaleY: 1.08, duration: 120, ease: 'Cubic.easeOut' });
|
||||
}
|
||||
|
||||
_isPlayZone(x, y) {
|
||||
const pt = slotLayout('bottom').trick;
|
||||
return Math.abs(x - pt.x) < CARD_W * 1.5 && Math.abs(y - pt.y) < CARD_H * 1.5;
|
||||
}
|
||||
|
||||
updateCardDrag(pointer) {
|
||||
const ds = this.dragState;
|
||||
const card = this.localHandCards[ds.cardIdx];
|
||||
const n = this.gs.players[0].hand.length;
|
||||
|
||||
card.x = pointer.x - ds.offsetX;
|
||||
card.y = pointer.y - ds.offsetY;
|
||||
ds.shadow.setPosition(card.x + 6, card.y + 16);
|
||||
|
||||
const overPlayZone = ds.isLegal && this._isPlayZone(card.x, card.y);
|
||||
if (overPlayZone !== ds.overPlayZone) {
|
||||
ds.overPlayZone = overPlayZone;
|
||||
if (overPlayZone) {
|
||||
this.tweens.killTweensOf(ds.slotIndicator);
|
||||
ds.slotIndicator.setAlpha(0);
|
||||
this._settleNonDraggedCards();
|
||||
this.tweens.add({ targets: ds.playZoneHighlight, alpha: 1, duration: 150 });
|
||||
} else {
|
||||
ds.slotIndicator.setAlpha(1);
|
||||
this._updateNonDraggedCards();
|
||||
this.tweens.add({ targets: ds.playZoneHighlight, alpha: 0, duration: 150 });
|
||||
}
|
||||
}
|
||||
|
||||
if (!overPlayZone) {
|
||||
const newInsertIdx = Phaser.Math.Clamp(Math.round((card.x - ds.handStartX) / HAND_SPREAD), 0, Math.max(0, n - 1));
|
||||
if (newInsertIdx !== ds.insertIdx) {
|
||||
ds.insertIdx = newInsertIdx;
|
||||
this.tweens.killTweensOf(ds.slotIndicator);
|
||||
this.tweens.add({ targets: ds.slotIndicator, x: ds.handStartX + newInsertIdx * HAND_SPREAD, duration: 100, ease: 'Cubic.easeOut' });
|
||||
this._updateNonDraggedCards();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_updateNonDraggedCards() {
|
||||
const ds = this.dragState;
|
||||
const n = this.gs.players[0].hand.length;
|
||||
const others = [];
|
||||
for (let i = 0; i < n; i++) { if (i !== ds.cardIdx) others.push(i); }
|
||||
|
||||
for (let k = 0; k < others.length; k++) {
|
||||
const j = others[k];
|
||||
const finalPos = k < ds.insertIdx ? k : k + 1;
|
||||
const targetX = ds.handStartX + finalPos * HAND_SPREAD;
|
||||
const distFromGap = Math.abs(finalPos - ds.insertIdx);
|
||||
const leanDir = finalPos < ds.insertIdx ? -1 : 1;
|
||||
const targetRot = leanDir * Math.max(0, 2 - distFromGap) * 0.04;
|
||||
const targetScale = distFromGap <= 1 ? 0.95 : 1.0;
|
||||
const cardObj = this.localHandCards[j];
|
||||
this.tweens.killTweensOf(cardObj);
|
||||
this.tweens.add({ targets: cardObj, x: targetX, rotation: targetRot, scaleX: targetScale, scaleY: targetScale, duration: 100, ease: 'Cubic.easeOut' });
|
||||
}
|
||||
}
|
||||
|
||||
_settleNonDraggedCards() {
|
||||
const ds = this.dragState;
|
||||
const hand = this.gs.players[0].hand;
|
||||
const n = hand.length;
|
||||
const remaining = n - 1;
|
||||
const settleStartX = slotLayout('bottom').handCenter.x - (remaining - 1) / 2 * HAND_SPREAD;
|
||||
let k = 0;
|
||||
for (let j = 0; j < n; j++) {
|
||||
if (j === ds.cardIdx) continue;
|
||||
const cardObj = this.localHandCards[j];
|
||||
this.tweens.killTweensOf(cardObj);
|
||||
this.tweens.add({ targets: cardObj, x: settleStartX + k * HAND_SPREAD, rotation: 0, scaleX: 1, scaleY: 1, duration: 150, ease: 'Cubic.easeOut' });
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
endCardDrag() {
|
||||
const ds = this.dragState;
|
||||
const card = this.localHandCards[ds.cardIdx];
|
||||
const hand = this.gs.players[0].hand;
|
||||
const n = hand.length;
|
||||
|
||||
// ── Play path: dropped on the play zone ───────────────────────────────────
|
||||
if (ds.overPlayZone) {
|
||||
const handCard = hand[ds.cardIdx];
|
||||
this.tweens.killTweensOf(card);
|
||||
card.setScale(1);
|
||||
card.setRotation(0);
|
||||
this.dragState = null;
|
||||
this.playHuman(handCard.id);
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Reorder path: dropped back in hand ────────────────────────────────────
|
||||
const movedCardId = hand[ds.cardIdx].id;
|
||||
const finalIdx = Phaser.Math.Clamp(Math.round((card.x - ds.handStartX) / HAND_SPREAD), 0, n - 1);
|
||||
const isSelected = this.gs.phase === 'passing' && this.passSelection.has(movedCardId);
|
||||
const finalY = ds.handCenterY - (isSelected ? 26 : 0);
|
||||
|
||||
this.tweens.killTweensOf(card);
|
||||
this.tweens.add({ targets: card, x: ds.handStartX + finalIdx * HAND_SPREAD, y: finalY, rotation: 0, scaleX: 1, scaleY: 1, duration: 220, ease: 'Back.easeOut' });
|
||||
|
||||
const others = [];
|
||||
for (let i = 0; i < n; i++) { if (i !== ds.cardIdx) others.push(i); }
|
||||
for (let k = 0; k < others.length; k++) {
|
||||
const j = others[k];
|
||||
const finalPos = k < finalIdx ? k : k + 1;
|
||||
const otherIsSelected = this.gs.phase === 'passing' && this.passSelection.has(hand[j].id);
|
||||
const cardObj = this.localHandCards[j];
|
||||
this.tweens.killTweensOf(cardObj);
|
||||
this.tweens.add({ targets: cardObj, x: ds.handStartX + finalPos * HAND_SPREAD, y: ds.handCenterY - (otherIsSelected ? 26 : 0), rotation: 0, scaleX: 1, scaleY: 1, duration: 120, ease: 'Cubic.easeOut' });
|
||||
}
|
||||
|
||||
const [moved] = hand.splice(ds.cardIdx, 1);
|
||||
hand.splice(finalIdx, 0, moved);
|
||||
const [movedCard] = this.localHandCards.splice(ds.cardIdx, 1);
|
||||
this.localHandCards.splice(finalIdx, 0, movedCard);
|
||||
|
||||
this.dragState = null;
|
||||
this.time.delayedCall(240, () => { if (!this.dragState) this.renderHands(); });
|
||||
}
|
||||
|
||||
tweenTo(target, x, y, duration) {
|
||||
return new Promise((resolve) => this.tweens.add({ targets: target, x, y, duration, ease: 'Cubic.easeOut', onComplete: resolve }));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue