feat: 30 showboat/taunt animations during question & answer phases
Fighters now perform random showboating micro-animations while idle during question reading and opponent's answer — shadow boxing, backflips, flex poses, moonwalks, ground pounds, air guitar, sumo stomps, and more. Each showboat uses existing sprite anims (attack/kick/special/win) combined with Kaplay tweens for position, scale, rotation, sparks, and emote text. Showboating loops with random variety and pauses between moves. Flow: both showboat during question → stop the answering bot during their turn → stop all before fight animation begins. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
6561ef5d15
commit
2534cbc4cf
@@ -7502,6 +7502,307 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
// === SHOWBOATING / TAUNTING SYSTEM ===
|
||||
// 30 idle animations fighters perform during question/answer phases
|
||||
const _showboatActive: Record<string, boolean> = { a: false, b: false }
|
||||
|
||||
type ShowboatFn = (fighter: any, homeX: number, homeY: number, sx: number, sy: number) => Promise<void>
|
||||
|
||||
const showboats: ShowboatFn[] = [
|
||||
// 0: Shadow boxing — quick attack/kick combo in place
|
||||
async (f, _hx, _hy, _sx, _sy) => {
|
||||
f.play('attack'); sfxDodge(); await k.wait(0.18)
|
||||
f.play('kick'); await k.wait(0.18)
|
||||
f.play('attack'); await k.wait(0.18)
|
||||
},
|
||||
// 1: Flex pose — scale up, hold, shrink back
|
||||
async (f, _hx, _hy, sx, sy) => {
|
||||
f.play('win')
|
||||
await k.tween(1, 1.25, 0.2, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }, k.easings.easeOutBack)
|
||||
await k.wait(0.4)
|
||||
await k.tween(1.25, 1, 0.2, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }, k.easings.easeInQuad)
|
||||
},
|
||||
// 2: Bouncy taunt — rapid small hops
|
||||
async (f, _hx, hy) => {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
await k.tween(f.pos.y, hy - 15, 0.06, (v) => { f.pos.y = v }, k.easings.easeOutQuad)
|
||||
await k.tween(f.pos.y, hy, 0.06, (v) => { f.pos.y = v }, k.easings.easeInQuad)
|
||||
}
|
||||
},
|
||||
// 3: Spin move — full 360 rotation
|
||||
async (f) => {
|
||||
f.play('special')
|
||||
const startAngle = f.angle || 0
|
||||
await k.tween(startAngle, startAngle + 360, 0.4, (v) => { f.angle = v }, k.easings.easeInOutQuad)
|
||||
f.angle = startAngle
|
||||
},
|
||||
// 4: Moonwalk — slide backward while idle
|
||||
async (f, hx) => {
|
||||
const dir = f.pos.x < W / 2 ? -1 : 1
|
||||
await k.tween(f.pos.x, hx + dir * 40, 0.3, (v) => { f.pos.x = v }, k.easings.easeInOutQuad)
|
||||
await k.tween(f.pos.x, hx, 0.3, (v) => { f.pos.x = v }, k.easings.easeInOutQuad)
|
||||
},
|
||||
// 5: Jump rope — rapid tiny bounces with kick anim
|
||||
async (f, _hx, hy) => {
|
||||
f.play('kick')
|
||||
for (let i = 0; i < 6; i++) {
|
||||
await k.tween(f.pos.y, hy - 8, 0.05, (v) => { f.pos.y = v }, k.easings.easeOutQuad)
|
||||
await k.tween(f.pos.y, hy, 0.05, (v) => { f.pos.y = v }, k.easings.easeInQuad)
|
||||
}
|
||||
},
|
||||
// 6: Intimidate stare — zoom in slightly, hold, zoom out
|
||||
async (f, _hx, _hy, sx, sy) => {
|
||||
f.play('special')
|
||||
await k.tween(1, 1.35, 0.25, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.5)
|
||||
await k.tween(1.35, 1, 0.2, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }, k.easings.easeInQuad)
|
||||
},
|
||||
// 7: Cocky lean — tilt to one side
|
||||
async (f) => {
|
||||
const start = f.angle || 0
|
||||
await k.tween(start, start + 12, 0.2, (v) => { f.angle = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.4)
|
||||
await k.tween(start + 12, start, 0.2, (v) => { f.angle = v }, k.easings.easeInQuad)
|
||||
},
|
||||
// 8: Fist pump — jump + attack anim
|
||||
async (f, _hx, hy) => {
|
||||
f.play('attack'); sfxPunch()
|
||||
await k.tween(f.pos.y, hy - 40, 0.12, (v) => { f.pos.y = v }, k.easings.easeOutQuad)
|
||||
await k.tween(f.pos.y, hy, 0.12, (v) => { f.pos.y = v }, k.easings.easeInQuad)
|
||||
},
|
||||
// 9: Shuffle dance — rapid left-right shimmy
|
||||
async (f, hx) => {
|
||||
f.play('win')
|
||||
for (let i = 0; i < 4; i++) {
|
||||
await k.tween(f.pos.x, hx + 12, 0.06, (v) => { f.pos.x = v })
|
||||
await k.tween(f.pos.x, hx - 12, 0.06, (v) => { f.pos.x = v })
|
||||
}
|
||||
f.pos.x = hx
|
||||
},
|
||||
// 10: Push-ups — squish vertically
|
||||
async (f, _hx, _hy, sx, sy) => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await k.tween(1, 0.6, 0.12, (v) => { f.scale.y = sy * v; f.scale.x = sx * (2 - v) }, k.easings.easeOutQuad)
|
||||
await k.tween(0.6, 1, 0.12, (v) => { f.scale.y = sy * v; f.scale.x = sx * (2 - v) }, k.easings.easeInQuad)
|
||||
}
|
||||
},
|
||||
// 11: Victory lap — dash to center and back
|
||||
async (f, hx, hy) => {
|
||||
f.play('kick'); sfxZoomWhoosh()
|
||||
const center = W / 2
|
||||
await k.tween(f.pos.x, center, 0.2, (v) => { f.pos.x = v }, k.easings.easeInQuad)
|
||||
f.play('win')
|
||||
spawnSparks(center, hy - 20, 5, theme.accent)
|
||||
await k.wait(0.2)
|
||||
await k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeOutQuad)
|
||||
},
|
||||
// 12: Yawn stretch — slow scale up, hold, shrink
|
||||
async (f, _hx, hy, sx, sy) => {
|
||||
await k.tween(1, 1.15, 0.4, (v) => { f.scale.x = sx * v; f.scale.y = sy * v; f.pos.y = hy - (v - 1) * 30 }, k.easings.easeInOutQuad)
|
||||
spawnEmoteText(f.pos.x, f.pos.y - 40, '*yawn*', '#aaaaaa')
|
||||
await k.wait(0.3)
|
||||
await k.tween(1.15, 1, 0.3, (v) => { f.scale.x = sx * v; f.scale.y = sy * v; f.pos.y = hy - (v - 1) * 30 }, k.easings.easeInOutQuad)
|
||||
},
|
||||
// 13: Backflip — jump high + rotate 360
|
||||
async (f, _hx, hy) => {
|
||||
sfxBoing()
|
||||
const startAngle = f.angle || 0
|
||||
await Promise.all([
|
||||
k.tween(f.pos.y, hy - 80, 0.2, (v) => { f.pos.y = v }, k.easings.easeOutQuad),
|
||||
k.tween(startAngle, startAngle - 360, 0.4, (v) => { f.angle = v }, k.easings.easeInOutQuad),
|
||||
])
|
||||
await k.tween(f.pos.y, hy, 0.2, (v) => { f.pos.y = v }, k.easings.easeInQuad)
|
||||
f.angle = startAngle
|
||||
},
|
||||
// 14: Chest thump — attack anim + shake
|
||||
async (f) => {
|
||||
f.play('attack'); sfxBlock()
|
||||
k.shake(3)
|
||||
spawnEmoteText(f.pos.x, f.pos.y - 40, 'COME ON!', '#ff4444')
|
||||
await k.wait(0.3)
|
||||
f.play('attack')
|
||||
k.shake(3)
|
||||
await k.wait(0.2)
|
||||
},
|
||||
// 15: Hover float — rise slowly, hold, descend
|
||||
async (f, _hx, hy) => {
|
||||
f.play('special')
|
||||
await k.tween(f.pos.y, hy - 50, 0.4, (v) => { f.pos.y = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3)
|
||||
await k.tween(f.pos.y, hy, 0.3, (v) => { f.pos.y = v }, k.easings.easeInQuad)
|
||||
},
|
||||
// 16: Dizzy spin — multiple fast spins
|
||||
async (f) => {
|
||||
f.play('hit')
|
||||
const startAngle = f.angle || 0
|
||||
await k.tween(startAngle, startAngle + 720, 0.5, (v) => { f.angle = v }, k.easings.easeOutQuad)
|
||||
spawnEmoteText(f.pos.x, f.pos.y - 40, '@_@', '#ffcc00')
|
||||
f.angle = startAngle
|
||||
await k.wait(0.2)
|
||||
},
|
||||
// 17: Ground pound — jump high, slam down with shake
|
||||
async (f, _hx, hy) => {
|
||||
f.play('kick'); sfxBoing()
|
||||
await k.tween(f.pos.y, hy - 70, 0.15, (v) => { f.pos.y = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.05)
|
||||
f.play('attack')
|
||||
await k.tween(f.pos.y, hy, 0.08, (v) => { f.pos.y = v }, k.easings.easeInQuad)
|
||||
sfxExplosion(); k.shake(8)
|
||||
spawnSparks(f.pos.x, hy, 8, '#ff6600')
|
||||
},
|
||||
// 18: Feint charge — dash toward opponent, brake, dash back
|
||||
async (f, hx) => {
|
||||
const dir = f.pos.x < W / 2 ? 1 : -1
|
||||
f.play('attack'); sfxZoomWhoosh()
|
||||
await k.tween(f.pos.x, hx + dir * 80, 0.12, (v) => { f.pos.x = v }, k.easings.easeOutQuad)
|
||||
sfxBlock()
|
||||
await k.wait(0.1)
|
||||
f.play('idle')
|
||||
await k.tween(f.pos.x, hx, 0.25, (v) => { f.pos.x = v }, k.easings.easeInOutQuad)
|
||||
},
|
||||
// 19: Butt wiggle — oscillating rotation
|
||||
async (f) => {
|
||||
f.play('win')
|
||||
const start = f.angle || 0
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await k.tween(f.angle, start + 8, 0.05, (v) => { f.angle = v })
|
||||
await k.tween(f.angle, start - 8, 0.05, (v) => { f.angle = v })
|
||||
}
|
||||
f.angle = start
|
||||
},
|
||||
// 20: Jumping jacks — alternating stretch X/Y
|
||||
async (f, _hx, hy, sx, sy) => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
f.play(i % 2 === 0 ? 'kick' : 'attack')
|
||||
await k.tween(0, 1, 0.12, (t) => {
|
||||
f.scale.x = sx * (1 + 0.2 * Math.sin(t * Math.PI))
|
||||
f.scale.y = sy * (1 - 0.1 * Math.sin(t * Math.PI))
|
||||
f.pos.y = hy - 10 * Math.sin(t * Math.PI)
|
||||
})
|
||||
}
|
||||
f.scale.x = sx; f.scale.y = sy; f.pos.y = hy
|
||||
},
|
||||
// 21: Sumo stomp — stomp left then right with mini shakes
|
||||
async (f, hx, hy, sx, sy) => {
|
||||
f.play('attack')
|
||||
// Left stomp
|
||||
await k.tween(f.pos.x, hx - 15, 0.08, (v) => { f.pos.x = v })
|
||||
f.scale.y = sy * 0.85; k.shake(3); sfxBonk()
|
||||
await k.wait(0.1); f.scale.y = sy
|
||||
// Right stomp
|
||||
await k.tween(f.pos.x, hx + 15, 0.08, (v) => { f.pos.x = v })
|
||||
f.scale.y = sy * 0.85; k.shake(3); sfxBonk()
|
||||
await k.wait(0.1); f.scale.y = sy
|
||||
f.pos.x = hx
|
||||
},
|
||||
// 22: Crane stance — rise on one leg, arms out
|
||||
async (f, _hx, hy, sx, sy) => {
|
||||
f.play('kick')
|
||||
await Promise.all([
|
||||
k.tween(f.pos.y, hy - 30, 0.3, (v) => { f.pos.y = v }, k.easings.easeOutQuad),
|
||||
k.tween(1, 1.15, 0.3, (v) => { f.scale.y = sy * v }, k.easings.easeOutQuad),
|
||||
])
|
||||
await k.wait(0.5)
|
||||
await Promise.all([
|
||||
k.tween(f.pos.y, hy, 0.2, (v) => { f.pos.y = v }, k.easings.easeInQuad),
|
||||
k.tween(1.15, 1, 0.2, (v) => { f.scale.y = sy * v }, k.easings.easeInQuad),
|
||||
])
|
||||
},
|
||||
// 23: Matrix dodge — lean way back
|
||||
async (f) => {
|
||||
const start = f.angle || 0
|
||||
f.play('special')
|
||||
await k.tween(start, start - 35, 0.2, (v) => { f.angle = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3)
|
||||
await k.tween(start - 35, start, 0.15, (v) => { f.angle = v }, k.easings.easeInQuad)
|
||||
},
|
||||
// 24: Air guitar — special anim + rapid bounces
|
||||
async (f, _hx, hy) => {
|
||||
f.play('special'); sfxSlideUp()
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await k.tween(f.pos.y, hy - 6, 0.04, (v) => { f.pos.y = v })
|
||||
await k.tween(f.pos.y, hy, 0.04, (v) => { f.pos.y = v })
|
||||
}
|
||||
spawnEmoteText(f.pos.x, f.pos.y - 45, '~♪~', '#ff44ff')
|
||||
},
|
||||
// 25: Cracking knuckles — shake + attack burst
|
||||
async (f, hx) => {
|
||||
const origX = f.pos.x
|
||||
for (let i = 0; i < 4; i++) {
|
||||
await k.tween(f.pos.x, hx + 4, 0.025, (v) => { f.pos.x = v })
|
||||
await k.tween(f.pos.x, hx - 4, 0.025, (v) => { f.pos.x = v })
|
||||
}
|
||||
f.pos.x = origX
|
||||
f.play('attack'); sfxBlock()
|
||||
await k.wait(0.15)
|
||||
},
|
||||
// 26: Windmill arms — rapid attack/kick cycling
|
||||
async (f) => {
|
||||
const anims = ['attack', 'kick', 'special', 'attack', 'kick', 'special'] as const
|
||||
for (const a of anims) {
|
||||
f.play(a)
|
||||
await k.wait(0.07)
|
||||
}
|
||||
},
|
||||
// 27: Confident strut — walk forward, pose, walk back
|
||||
async (f, hx) => {
|
||||
const dir = f.pos.x < W / 2 ? 1 : -1
|
||||
f.play('win')
|
||||
await k.tween(f.pos.x, hx + dir * 35, 0.3, (v) => { f.pos.x = v }, k.easings.easeInOutQuad)
|
||||
spawnEmoteText(f.pos.x, f.pos.y - 45, '😎', '#ffcc00')
|
||||
await k.wait(0.2)
|
||||
await k.tween(f.pos.x, hx, 0.25, (v) => { f.pos.x = v }, k.easings.easeInOutQuad)
|
||||
},
|
||||
// 28: Earthquake stomp — big jump, land with major shake
|
||||
async (f, _hx, hy) => {
|
||||
sfxBoing()
|
||||
await k.tween(f.pos.y, hy - 90, 0.2, (v) => { f.pos.y = v }, k.easings.easeOutQuad)
|
||||
f.play('attack')
|
||||
await k.tween(f.pos.y, hy, 0.1, (v) => { f.pos.y = v }, k.easings.easeInQuad)
|
||||
sfxExplosion(); k.shake(12)
|
||||
spawnSparks(f.pos.x, hy, 12, '#ffcc00')
|
||||
spawnEmoteText(f.pos.x, f.pos.y - 50, 'BOOM!', '#ff2d2d')
|
||||
await k.wait(0.15)
|
||||
},
|
||||
// 29: Meditation float — rise, pulse glow, descend
|
||||
async (f, _hx, hy, sx, sy) => {
|
||||
f.play('special')
|
||||
await k.tween(f.pos.y, hy - 35, 0.3, (v) => { f.pos.y = v }, k.easings.easeOutQuad)
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await k.tween(1, 1.08, 0.15, (v) => { f.scale.x = sx * v; f.scale.y = sy * v })
|
||||
await k.tween(1.08, 1, 0.15, (v) => { f.scale.x = sx * v; f.scale.y = sy * v })
|
||||
}
|
||||
await k.tween(f.pos.y, hy, 0.2, (v) => { f.pos.y = v }, k.easings.easeInQuad)
|
||||
},
|
||||
]
|
||||
|
||||
// Showboat loop: picks random showboats in sequence while active
|
||||
async function _showboatLoop(side: 'a' | 'b') {
|
||||
const tag = side === 'a' ? 'fighterA' : 'fighterB'
|
||||
const homeX = side === 'a' ? HOME_A : HOME_B
|
||||
const homeY = GROUND_Y - 6
|
||||
while (_showboatActive[side]) {
|
||||
const f = k.get(tag)[0]
|
||||
if (!f) break
|
||||
const sx = f.scale.x
|
||||
const sy = f.scale.y
|
||||
const idx = Math.floor(Math.random() * showboats.length)
|
||||
try {
|
||||
await showboats[idx](f, homeX, homeY, sx, sy)
|
||||
} catch { break }
|
||||
// Restore state after each showboat
|
||||
const ff = k.get(tag)[0]
|
||||
if (ff) {
|
||||
ff.pos.x = homeX; ff.pos.y = homeY
|
||||
ff.scale.x = sx; ff.scale.y = sy
|
||||
ff.angle = 0
|
||||
ff.play('idle')
|
||||
}
|
||||
// Brief pause between showboats
|
||||
if (_showboatActive[side]) await k.wait(0.3 + Math.random() * 0.6)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
k,
|
||||
|
||||
@@ -7514,6 +7815,15 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
startTalking(side: 'a' | 'b') { startTalking(side) },
|
||||
stopTalking(side: 'a' | 'b') { stopTalking(side) },
|
||||
|
||||
startShowboating(side: 'a' | 'b') {
|
||||
if (_showboatActive[side]) return
|
||||
_showboatActive[side] = true
|
||||
_showboatLoop(side)
|
||||
},
|
||||
stopShowboating(side: 'a' | 'b') {
|
||||
_showboatActive[side] = false
|
||||
},
|
||||
|
||||
async playEntrance() {
|
||||
const fA = k.get('fighterA')[0]
|
||||
const fB = k.get('fighterB')[0]
|
||||
|
||||
Reference in New Issue
Block a user