Add 3D dice roll animation for thrown rocks
Emit the individual rock-die faces (0/0/1/1/2/2) from the battle resolver so the client can render a real CSS 3D cube per die that tumbles and settles on the rolled face, replacing the flat random damage number. Falls back to the flying-rock cue for older battle logs without per-die data.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import type { BattleEvent, Card, ClientMessage, GameView } from '../types'
|
||||
import { CardView } from './CardView'
|
||||
import { DiceTray } from './Dice'
|
||||
import { artFor } from '../petArt'
|
||||
|
||||
interface Props {
|
||||
@@ -28,7 +29,7 @@ const EVENT_MS: Record<BattleEvent['type'], number> = {
|
||||
reveal: 800,
|
||||
summon: 1000,
|
||||
mill: 700,
|
||||
rock: 1200,
|
||||
rock: 1500,
|
||||
clash: 1400,
|
||||
shield: 900,
|
||||
strip: 1100,
|
||||
@@ -312,11 +313,15 @@ export function BattlePhase({ view, send }: Props) {
|
||||
<div className="battlefield">
|
||||
{renderSide(youSeat, 'left')}
|
||||
<div className="battle-center" aria-hidden>
|
||||
{!done && lastEvent?.type === 'rock' && (
|
||||
<div className={`rock-fly ${lastEvent.target === youSeat ? 'rock-fly-left' : 'rock-fly-right'}`}>
|
||||
🪨
|
||||
</div>
|
||||
)}
|
||||
{!done && lastEvent?.type === 'rock' &&
|
||||
(lastEvent.dice && lastEvent.dice.length > 0 ? (
|
||||
// Remount per rock event (keyed by step) so the tumble replays.
|
||||
<DiceTray key={step} dice={lastEvent.dice} />
|
||||
) : (
|
||||
<div className={`rock-fly ${lastEvent.target === youSeat ? 'rock-fly-left' : 'rock-fly-right'}`}>
|
||||
🪨
|
||||
</div>
|
||||
))}
|
||||
<span className="battle-center-bolt">⚡</span>
|
||||
</div>
|
||||
{renderSide(oppSeat, 'right')}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { CSSProperties } from 'react'
|
||||
|
||||
// The rock die has six faces: 0, 0, 1, 1, 2, 2. We lay them on a cube so a
|
||||
// real 3D tumble can settle with the rolled value facing the camera.
|
||||
// front/back = 0, right/left = 1, top/bottom = 2
|
||||
// To bring a value to the front we rotate the cube to whichever face carries
|
||||
// it: 0 -> identity, 1 -> show the right face, 2 -> show the top face.
|
||||
const LAND: Record<number, { rx: number; ry: number }> = {
|
||||
0: { rx: 0, ry: 0 },
|
||||
1: { rx: 0, ry: -90 },
|
||||
2: { rx: 90, ry: 0 },
|
||||
}
|
||||
|
||||
function pips(n: number) {
|
||||
return (
|
||||
<span className={`die-pips die-pips-${n}`} aria-hidden>
|
||||
{Array.from({ length: n }, (_, i) => (
|
||||
<span key={i} className="die-pip" />
|
||||
))}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function Die({ value, index }: { value: number; index: number }) {
|
||||
const land = LAND[value] ?? LAND[0]
|
||||
// Stagger each die so they don't tumble in lockstep, and give each its own
|
||||
// landing orientation. The keyframe adds the spins on top of these targets.
|
||||
const style = {
|
||||
'--rx': `${land.rx}deg`,
|
||||
'--ry': `${land.ry}deg`,
|
||||
'--delay': `${index * 90}ms`,
|
||||
} as CSSProperties
|
||||
|
||||
// The outer .die carries the drop-shadow; the shadow must live off the
|
||||
// preserve-3d element, since a `filter` collapses 3D children into a plane.
|
||||
return (
|
||||
<div className="die" style={style}>
|
||||
<div className="die-cube">
|
||||
<div className="die-face die-front">{pips(0)}</div>
|
||||
<div className="die-face die-back">{pips(0)}</div>
|
||||
<div className="die-face die-right">{pips(1)}</div>
|
||||
<div className="die-face die-left">{pips(1)}</div>
|
||||
<div className="die-face die-top">{pips(2)}</div>
|
||||
<div className="die-face die-bottom">{pips(2)}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// DiceTray rolls one cube per die face rolled. `key` should change per rock
|
||||
// event so React remounts and replays the tumble.
|
||||
export function DiceTray({ dice }: { dice: number[] }) {
|
||||
return (
|
||||
<div className="dice-tray" aria-hidden>
|
||||
{dice.map((v, i) => (
|
||||
<Die key={i} value={v} index={i} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user