logo

Babylon.js Market

CardFan

tabletop-casino · poker

CardFan

1
Unlock

Install with the CLI:

bjs download CardFan

CardFan

A hand of cards doesn't just appear — it flies in. CardFan deals a row of cards in an eased arc to their seats, lands them, then flips them face-up or face-down with a lift-and-bounce, painting each quad with the right card-face or card-back as it turns.

What it does

CardFan is the dealt-hand renderer: it turns a list of card ids into the moving, flipping cards on the felt. On attach it builds one double-faced card quad per slot through the card API — createCardMesh({ width: 1.4, height: 2.0, subdivisions: 16, cornerRadius: 20, bow }). The parabolic bow and the rounded-corner alpha mask are baked inside that call, so the component never hand-bows vertices or paints a mask canvas.

A cardfan.deal arms the hand: it preloads the back image, loads each card's face, then the sim flies every card from the stack to its seat (eased move + a sine arc + a few spins), drops it, and fires cardfan.landed. A cardfan.flip raises the lift-and-bounce flip; the texture decision — showBack = |rotZ % 2π| < π/2 — decides whether the convex back or the concave face shows, and a per-slot skip-cache repaints only the card whose visible side actually changed.

The flight, the flip math, the texture decision, and the auto-flip schedule all live in CardFan.core.ts and run without a renderer. The System steps that core each frame and translates a cache miss into a single setMeshFaceTexture call.

Use it in a scene

Drop a CardFan on its own entity, then deal it a hand over the EventBus.

A scene is just data — a list of entities, each with its components. At startup the SceneLoader turns this JSON into a live world; edit the file and reload to rebuild it.

Fastest path

bjs download scene CardFan

Pulls this exact scene into your project and runs it — no copy-paste. Add --all to grab the components and assets it needs too.

Or build it from scratch with the bjs CLI:

  1. 1Scaffold a project
    npm create @babylonjsmarket/arcade@latest my-game

    World, renderer, and dev server — ready to run.

  2. 2Install dependencies
    cd my-game && npm install
  3. 3Add CardFan
    bjs download CardFan

    Copies its source into src/ so the scene resolves.

    First time? Run bjs login once.

  4. 4Paste the scene into src/scenes/arcade-room.ts and run
    npm run dev

    SceneLoader builds the world from the JSON; reload to rebuild.

JSON
{
  "Hand": {
    "components": {
      "CardFan": {
        "count": 2,
        "originZ": -2,
        "targetZ": -7,
        "spread": 1.8,
        "selectedBack": 1,
        "basePath": "/poker"
      }
    }
  }
}
TypeScript
// Deal the two hole cards, then flip them one after another.
world.getEventBus().emit('cardfan.deal', { cards: ['ace_of_spades', 'king_of_hearts'] })
world.getEventBus().emit('cardfan.flip', { indices: [0, 1], startDelay: 0.4, interval: 0.25 })

Pair it with DeckDealer for the shuffled card ids and PokerSeating for the table layout: DeckDealer emits deck.dealt, and a thin glue line forwards the dealt card ids into cardfan.deal.

Props

PropTypeDefaultMeaning
countnumber2Card slots in the hand (a Hold'em hole hand is 2).
originZnumber-2Z row the cards fly from.
targetZnumber-8Z row the cards land on.
spreadnumber1.8Horizontal spacing between adjacent card centers.
selectedBacknumber1Card-back design index → back-{n}.webp.
basePathstring"/poker"Asset root for the default CardTextureSource.

Faces and backs resolve through a CardTextureSource (basePath + faceUrl / backUrl). The default reads ${basePath}/textures/cards/${cardId}.webp and ${basePath}/textures/backs/back-${n}.webp; pass a custom source to new CardFanSystem(eventBus, source) to point at a CDN or a different layout. Faces load flipped horizontally so the print reads correctly on the concave side; backs load rotated π so the pattern is upright when face-down.

Events

EventDirectionPayloadWhen
cardfan.dealin{ cards: string[] }Arm a hand; one id per slot, in deal order.
cardfan.flipin{ index } or { indices, startDelay?, interval? }Flip one card, or auto-flip a group in sequence.
cardfan.resetin{}Clear the hand and dispose loaded textures.
cardfan.dealtout{ entityId, count }A deal was armed.
cardfan.landedout{ entityId, index }A card finished its flight.
cardfan.flippedout{ entityId, index, faceUp }A flip settled; faceUp is the resting side.

Dependencies

  • DeckDealer — the shuffled deck and round-robin deal queue that supplies the card ids.
  • PokerSeating — the table geometry and deal plan for placing multiple hands around the felt.
  • Shadow — each card quad is attached as a shadow caster, so a DirectionalLight + Shadow setup drops real card shadows on the felt.

Notes

  • Texture handles, card meshes, and the sim state live on the System in a WeakMap<CardFanComponent, …>, never on the Component — so serialize() round-trips cleanly.
  • loadTexture is deduped by key on the adapter: every card id and every back design loads once and reuses one handle across deals.
  • The deal handler awaits preloadTexture for the back before the first card flies, so a card never lands showing a blank convex face.
  • The skip-cache (CardTexCache) only repaints a card whose visible side crossed the showBack threshold — a flip costs one setMeshFaceTexture, not one per frame.

More like this

DeckDealer
PokerSeating
PokerTableDirector
Shadow
HealthBar
PokerAI
PokerAIBill
PokerAICali
PokerAIRandy
PokerAIShark
PokerAISurge
PokerAITilt
PokerBetting
PokerHUD
PokerHandEval
PokerInput
PokerMultiplayer
PokerTableCards
Score
Scoreboard
SpaceShooterScore
TurnPacer
Waypoint

Was this page helpful?

We read every note — tell us what's working and what isn't.

↑↓ NavigateEnter SelectEsc CloseCtrl+K Open Search