added fly animation, refactoring, scale cards.
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import type { Container, Sprite, Ticker, TickerCallback } from "pixi.js";
|
||||
import type { Container, Point, Sprite, Ticker, TickerCallback } from "pixi.js";
|
||||
import { CardStatus, Hand } from "./Hand";
|
||||
import { getSpriteSheet } from "./spritesheet";
|
||||
import { CARD_HEIGHT, CARD_WIDTH } from "./constants";
|
||||
@ -40,7 +40,7 @@ export abstract class Animation {
|
||||
}
|
||||
}
|
||||
|
||||
export class FlipCard extends Animation {
|
||||
export class Flip extends Animation {
|
||||
card: Sprite;
|
||||
cardWidth: number;
|
||||
status: CardStatus;
|
||||
@ -91,9 +91,35 @@ export class FlipCard extends Animation {
|
||||
}
|
||||
}
|
||||
|
||||
export class Fly extends Animation {
|
||||
#card: Sprite;
|
||||
#to: Point;
|
||||
#cardDelta: number;
|
||||
|
||||
constructor(hand: Hand, index: number, to: Point) {
|
||||
super(hand);
|
||||
|
||||
this.#card = hand.getCard(index).sprite;
|
||||
this.#to = to;
|
||||
this.#cardDelta = distanceBetween(this.#card.position, to);
|
||||
}
|
||||
|
||||
onTick(ticker: Ticker) {
|
||||
const [xTravelled, yTravelled] = moveToward(
|
||||
this.#card,
|
||||
this.#to,
|
||||
this.#cardDelta * 0.04 * ticker.deltaTime,
|
||||
);
|
||||
|
||||
if (!xTravelled && !yTravelled) {
|
||||
this.done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ToHorizontal extends Animation {
|
||||
#cardDeltas: [number, number][]; // change in x and y of each card
|
||||
#finalPositions: [number, number][]; // final position of each card (relative to container)
|
||||
#cardDeltas: number[]; // total distance travelled by each card
|
||||
#finalPositions: Point[]; // final position of each card (relative to container)
|
||||
#hand: Hand;
|
||||
#cards: Sprite[];
|
||||
#container: Container;
|
||||
@ -121,18 +147,10 @@ export class ToHorizontal extends Animation {
|
||||
this.#hand.size(),
|
||||
);
|
||||
|
||||
// calculate the x and y amounts that each card will move over the
|
||||
// course of the animation
|
||||
const deltas = [];
|
||||
// calculate the total distances between each starting and ending point
|
||||
const deltas: number[] = [];
|
||||
for (let i = 0; i < currentPositions.length; i++) {
|
||||
const [currentX, currentY] = currentPositions[i];
|
||||
const [finalX, finalY] = finalPositions[i];
|
||||
|
||||
const adjustment: [number, number] = [
|
||||
finalX - currentX,
|
||||
finalY - currentY,
|
||||
];
|
||||
deltas.push(adjustment);
|
||||
deltas.push(distanceBetween(currentPositions[i], finalPositions[i]));
|
||||
}
|
||||
|
||||
this.#finalPositions = finalPositions;
|
||||
@ -154,8 +172,8 @@ export class ToHorizontal extends Animation {
|
||||
// of the container after the animation
|
||||
const initialWidth = this.#cards[count - 1].x + CARD_WIDTH;
|
||||
const initialHeight = this.#cards[count - 1].y + CARD_HEIGHT;
|
||||
const finalWidth = finalPositions[count - 1][0] + CARD_WIDTH;
|
||||
const finalHeight = finalPositions[count - 1][1] + CARD_HEIGHT;
|
||||
const finalWidth = finalPositions[count - 1].x + CARD_WIDTH;
|
||||
const finalHeight = finalPositions[count - 1].y + CARD_HEIGHT;
|
||||
|
||||
// the containers change in width and height over the course of the
|
||||
// animation
|
||||
@ -183,58 +201,22 @@ export class ToHorizontal extends Animation {
|
||||
// https://pixijs.download/release/docs/ticker.Ticker.html#deltatime
|
||||
const rate = 0.04;
|
||||
|
||||
for (let i = 0; i < this.#cardDeltas.length; i++) {
|
||||
for (let i = 0; i < this.#cards.length; i++) {
|
||||
const card = this.#cards[i];
|
||||
|
||||
// x, y coordinates where the card will end up
|
||||
const [finalX, finalY] = this.#finalPositions[i];
|
||||
const to = this.#finalPositions[i];
|
||||
|
||||
// x, y distances travelled by the end of the animation
|
||||
const [deltaX, deltaY] = this.#cardDeltas[i];
|
||||
|
||||
// These are the x, y distances remaining between where the
|
||||
// card is in the animation, and where it will end up. They are
|
||||
// going to be used to determine if the current adjustment
|
||||
// should be the final adjustment.
|
||||
const remainingX = this.#cards[i].x - this.#finalPositions[i][0];
|
||||
const remainingY = this.#cards[i].y - this.#finalPositions[i][1];
|
||||
|
||||
// These are the numbers of pixels that should be travelled this
|
||||
// tick. They are calculated by taking the total travel
|
||||
// distance (deltaX), taking a percentage of that journey that
|
||||
// should be taken in a given target frame (rate), and then
|
||||
// multiplying that by ticker.deltaTime to get the fraction of
|
||||
// the target framerate that has actually elapsed (ideally, 1.0).
|
||||
//
|
||||
// For more information about ticker.deltaTime, see:
|
||||
// https://pixijs.download/release/docs/ticker.Ticker.html#deltatime
|
||||
const offsetX = deltaX * rate * ticker.deltaTime;
|
||||
const offsetY = deltaY * rate * ticker.deltaTime;
|
||||
|
||||
// If the remaining x distance is less than, or equal to the offset,
|
||||
// then this is the last adjustment and, instead of offsetting, the
|
||||
// card should just be set to its desired final position...
|
||||
if (Math.abs(remainingX) <= Math.abs(offsetX)) {
|
||||
card.x = finalX;
|
||||
const [xTravelled, yTravelled] = moveToward(
|
||||
card,
|
||||
to,
|
||||
this.#cardDeltas[i] * rate * ticker.deltaTime,
|
||||
);
|
||||
isXAnimating = !!xTravelled;
|
||||
isYAnimating = !!yTravelled;
|
||||
}
|
||||
|
||||
// ...otherwise, apply the offset and set isXAnimating to true since
|
||||
// there is yet more animating along x to do!
|
||||
else {
|
||||
card.x += offsetX;
|
||||
isXAnimating = true;
|
||||
}
|
||||
|
||||
// and for y
|
||||
if (Math.abs(remainingY) <= Math.abs(offsetY)) {
|
||||
card.y = finalY;
|
||||
} else {
|
||||
card.y += offsetY;
|
||||
isYAnimating = true;
|
||||
}
|
||||
}
|
||||
|
||||
// When neither x nor y still need to be animated, the animation is .complete
|
||||
// When neither x nor y still need to be animated, the animation is complete
|
||||
if (!isXAnimating && !isYAnimating) {
|
||||
this.#hand.setCardLayout("horizontal"); // the hand layout has changed
|
||||
|
||||
@ -259,3 +241,49 @@ export class ToHorizontal extends Animation {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function moveToward(
|
||||
sprite: Sprite,
|
||||
to: Point,
|
||||
distance: number,
|
||||
): [number, number] {
|
||||
// x, y coordinates of the current and destination points.
|
||||
const { x: toX, y: toY } = to;
|
||||
const { x: fromX, y: fromY } = sprite.position;
|
||||
|
||||
const rise = toY - fromY;
|
||||
const run = toX - fromX;
|
||||
|
||||
// If we imagine that rise and run are sides of a right triangle, then
|
||||
// the total travel distance is the hypotenuse of that triangle. Boom.
|
||||
const totalDistance = distanceBetween(sprite.position, to);
|
||||
|
||||
if (totalDistance <= distance) {
|
||||
const travelled: [number, number] = [toX - sprite.x, toY - sprite.y];
|
||||
sprite.x = toX;
|
||||
sprite.y = toY;
|
||||
|
||||
return travelled;
|
||||
}
|
||||
|
||||
// The proportion of the total distance travelled.
|
||||
const distanceProportion = distance / totalDistance;
|
||||
|
||||
const yDist = rise * distanceProportion;
|
||||
const xDist = run * distanceProportion;
|
||||
|
||||
sprite.x += xDist;
|
||||
sprite.y += yDist;
|
||||
|
||||
return [xDist, yDist];
|
||||
}
|
||||
|
||||
function distanceBetween(a: Point, b: Point) {
|
||||
// If we imagine that rise and run are sides of a right triangle, then
|
||||
// the total travel distance is the hypotenuse of that triangle. Boom.
|
||||
const rise = b.y - a.y;
|
||||
const run = b.y - a.y;
|
||||
const totalDistance = Math.sqrt(Math.pow(rise, 2) + Math.pow(run, 2));
|
||||
|
||||
return totalDistance;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Assets, Container, Sprite, Spritesheet, Ticker } from "pixi.js";
|
||||
import { Assets, Container, Point, Sprite, Spritesheet, Ticker } from "pixi.js";
|
||||
import { Card } from "./Card";
|
||||
import { CARD_HOVER_DIST, CARD_WIDTH } from "./constants";
|
||||
import { FlipCard, ToHorizontal } from "./Animation";
|
||||
import { CARD_HEIGHT, CARD_HOVER_DIST, CARD_WIDTH } from "./constants";
|
||||
import { Flip, Fly, ToHorizontal } from "./Animation";
|
||||
|
||||
type Pivot = "left" | "right" | "center";
|
||||
type Layout = "horizontal" | "ascending" | "stacked";
|
||||
@ -19,18 +19,16 @@ export class Hand {
|
||||
#hover: boolean;
|
||||
#layout: Layout;
|
||||
#maxWidth: number;
|
||||
#maxHeight: number;
|
||||
#pivot: Pivot;
|
||||
ticker: Ticker | null;
|
||||
#gap: number;
|
||||
|
||||
constructor(maxWidth: number, cards: Card[] = []) {
|
||||
if (maxWidth < CARD_WIDTH) {
|
||||
throw new Error("hand cannot be narrower than a single card");
|
||||
}
|
||||
|
||||
constructor(cards: Card[] = []) {
|
||||
this.#container = new Container();
|
||||
this.#hover = false;
|
||||
this.#maxWidth = maxWidth;
|
||||
this.#maxWidth = Infinity;
|
||||
this.#maxHeight = Infinity;
|
||||
this.#pivot = "left";
|
||||
this.#gap = 0;
|
||||
this.#layout = "horizontal";
|
||||
@ -55,9 +53,11 @@ export class Hand {
|
||||
}
|
||||
}
|
||||
|
||||
add(card: Card, faceDown = false) {
|
||||
async add(card: Card, faceDown = false, from: Point | null = null) {
|
||||
const spriteName = faceDown ? "unknown" : card;
|
||||
const sprite = new Sprite(spritesheet.textures[spriteName]);
|
||||
sprite.width = CARD_WIDTH;
|
||||
sprite.height = CARD_HEIGHT;
|
||||
|
||||
this.#cardSprites.push(sprite);
|
||||
this.#statuses.push({
|
||||
@ -68,6 +68,19 @@ export class Hand {
|
||||
this.#setCardHover(sprite);
|
||||
this.fanCards();
|
||||
this.setPivot(this.#pivot);
|
||||
|
||||
if (from) {
|
||||
const to = sprite.position.clone();
|
||||
const globalPosition = sprite.toGlobal(new Point());
|
||||
const { x: fromX, y: fromY } = from;
|
||||
sprite.x -= globalPosition.x;
|
||||
sprite.y -= globalPosition.y;
|
||||
sprite.y += fromY;
|
||||
sprite.x += fromX - CARD_WIDTH / 2;
|
||||
|
||||
const animation = new Fly(this, this.#cardSprites.length - 1, to);
|
||||
await animation.start();
|
||||
}
|
||||
}
|
||||
|
||||
animationTicker(ticker: Ticker) {
|
||||
@ -85,7 +98,7 @@ export class Hand {
|
||||
if (!this.ticker)
|
||||
throw new Error("can't animate hand before passing it a ticker");
|
||||
|
||||
const animation = new FlipCard(this, cardIdx);
|
||||
const animation = new Flip(this, cardIdx);
|
||||
await animation.start();
|
||||
}
|
||||
|
||||
@ -134,6 +147,14 @@ export class Hand {
|
||||
return this.#layout;
|
||||
}
|
||||
|
||||
setMaxWidth(maxWidth: number) {
|
||||
this.#maxWidth = maxWidth;
|
||||
}
|
||||
|
||||
setMaxHeight(maxHeight: number) {
|
||||
this.#maxHeight = maxHeight;
|
||||
}
|
||||
|
||||
hover(hasHover: boolean) {
|
||||
if (this.#hover === hasHover) return;
|
||||
|
||||
@ -171,28 +192,41 @@ export class Hand {
|
||||
}
|
||||
|
||||
getCardPositions(layout: Layout, cardCount: number) {
|
||||
const positions: [number, number][] = [];
|
||||
const max = this.#maxWidth;
|
||||
let offset = CARD_WIDTH / 3;
|
||||
const positions: Point[] = [];
|
||||
let horizontalOffset = CARD_WIDTH / 3;
|
||||
let verticalOffset = horizontalOffset;
|
||||
|
||||
if (layout === "horizontal") {
|
||||
offset =
|
||||
cardCount * (CARD_WIDTH + this.#gap) > max
|
||||
? max / cardCount
|
||||
: CARD_WIDTH + this.#gap;
|
||||
const fullCardWidth = CARD_WIDTH + this.#gap;
|
||||
const fullHandWidth = cardCount * (CARD_WIDTH + this.#gap);
|
||||
|
||||
// the sum of all the parts of card that peek out of a hand and are visible
|
||||
// to the player
|
||||
const cardPeek = (this.#maxWidth - CARD_WIDTH) / (cardCount - 1);
|
||||
|
||||
horizontalOffset =
|
||||
fullHandWidth > this.#maxWidth ? cardPeek : fullCardWidth;
|
||||
}
|
||||
|
||||
if (layout === "ascending") {
|
||||
const fullHandHeight = CARD_HEIGHT + (cardCount - 1) * verticalOffset;
|
||||
if (fullHandHeight > this.#maxHeight) {
|
||||
verticalOffset = (this.#maxHeight - CARD_HEIGHT) / (cardCount - 1);
|
||||
console.log("max height", this.#maxHeight, "offset", verticalOffset);
|
||||
}
|
||||
}
|
||||
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
for (let i = 0; i < cardCount; i++) {
|
||||
positions.push([x, y]);
|
||||
positions.push(new Point(x, y));
|
||||
switch (layout) {
|
||||
case "ascending":
|
||||
x += offset;
|
||||
y -= offset;
|
||||
x += horizontalOffset;
|
||||
y -= verticalOffset;
|
||||
break;
|
||||
case "horizontal":
|
||||
x += offset;
|
||||
x += horizontalOffset;
|
||||
break;
|
||||
case "stacked":
|
||||
x += CARD_WIDTH / 20;
|
||||
@ -210,7 +244,7 @@ export class Hand {
|
||||
);
|
||||
|
||||
for (let i = 0; i < positions.length; i++) {
|
||||
const [x, y] = positions[i];
|
||||
const { x, y } = positions[i];
|
||||
const card = this.#cardSprites[i];
|
||||
card.x = x;
|
||||
card.y = y;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Application, Graphics, Text, TextStyleOptions } from "pixi.js";
|
||||
import { Application, Graphics, Point, Text, TextStyleOptions } from "pixi.js";
|
||||
import { Hand } from "./Hand";
|
||||
import { Card } from "./Card";
|
||||
import { CARD_HEIGHT, CARD_HOVER_DIST, CARD_WIDTH } from "./constants";
|
||||
@ -8,6 +8,8 @@ const playerCards: Card[] = [
|
||||
"sixOfClubs",
|
||||
"kingOfClubs",
|
||||
"nineOfDiamonds",
|
||||
"sixOfHearts",
|
||||
"sevenOfHearts",
|
||||
];
|
||||
|
||||
const dealerCards: Card[] = ["aceOfClubs", "aceOfHearts"];
|
||||
@ -66,8 +68,11 @@ const dealerCards: Card[] = ["aceOfClubs", "aceOfHearts"];
|
||||
style: insuranceStyle,
|
||||
});
|
||||
|
||||
const playerHand = new Hand(350, []);
|
||||
const dealerHand = new Hand(350, []);
|
||||
const playerHand = new Hand();
|
||||
playerHand.setMaxWidth(CARD_WIDTH * 3);
|
||||
playerHand.setMaxHeight(CARD_HEIGHT * 1.5);
|
||||
|
||||
const dealerHand = new Hand();
|
||||
|
||||
const positionElements = () => {
|
||||
markTable(app, tableMarkings);
|
||||
@ -101,24 +106,26 @@ const dealerCards: Card[] = ["aceOfClubs", "aceOfHearts"];
|
||||
positionElements();
|
||||
});
|
||||
|
||||
for (const card of playerCards) {
|
||||
playerHand.add(card, false);
|
||||
}
|
||||
|
||||
let firstCard = true;
|
||||
for (const card of dealerCards) {
|
||||
dealerHand.add(card, firstCard);
|
||||
firstCard = false;
|
||||
}
|
||||
|
||||
dealerHand.setGap(CARD_WIDTH * 0.1);
|
||||
dealerHand.animationTicker(app.ticker);
|
||||
|
||||
playerHand.setGap(CARD_WIDTH * 0.1);
|
||||
playerHand.animationTicker(app.ticker);
|
||||
|
||||
await dealerHand.spread();
|
||||
await dealerHand.flip(0);
|
||||
for (const card of playerCards) {
|
||||
await playerHand.add(card, false, new Point(app.screen.width / 2, 0));
|
||||
}
|
||||
|
||||
let firstCard = true;
|
||||
for (const card of dealerCards) {
|
||||
await dealerHand.add(card, firstCard, new Point(app.screen.width / 2, 0));
|
||||
firstCard = false;
|
||||
}
|
||||
|
||||
// await dealerHand.spread();
|
||||
// await dealerHand.flip(0);
|
||||
|
||||
// await playerHand.spread();
|
||||
|
||||
// let ms = 0;
|
||||
// let elapsed = 0;
|
||||
|
||||
Reference in New Issue
Block a user