piclets / src /lib /components /Battle /BattleEffects.svelte
Fraser's picture
style
e64944b
raw
history blame
14.3 kB
<script lang="ts">
import { fade } from 'svelte/transition';
import { onMount } from 'svelte';
export let effects: Array<{type: string, emoji: string, duration: number}> = [];
export let flash: boolean = false;
// GBA-style flicker animation parameters
const flickerCount = 19;
const frameDelay = 2;
const flickerDuration = 600; // milliseconds
// Flicker state management
let isFlickering = false;
let flickerVisible = true;
let flickerFrame = 0;
let flickerInterval: number;
// Particle system configuration
const PARTICLES_PER_EFFECT = 5; // Number of emoji particles per effect
const SPAWN_RADIUS = 60; // Radius around piclet where particles spawn
// Generate multiple particles for each effect
$: particleList = effects.flatMap((effect, effectIndex) => {
const particles = [];
for (let i = 0; i < PARTICLES_PER_EFFECT; i++) {
// Random spawn position around the piclet
const angle = (Math.PI * 2 * i) / PARTICLES_PER_EFFECT + (Math.random() - 0.5) * 0.5;
const distance = SPAWN_RADIUS * (0.7 + Math.random() * 0.6); // Vary distance
const x = Math.cos(angle) * distance;
const y = Math.sin(angle) * distance;
// Random animation properties
const scale = 0.8 + Math.random() * 0.4; // 0.8x to 1.2x size
const rotation = Math.random() * 360; // Random initial rotation
const duration = effect.duration + (Math.random() - 0.5) * 200; // Vary duration slightly
const delay = Math.random() * 100; // Stagger animation starts
particles.push({
id: `${effectIndex}-${i}`,
type: effect.type,
emoji: effect.emoji,
x,
y,
scale,
rotation,
duration,
delay
});
}
return particles;
});
// Watch for flash changes to trigger flicker animation
$: if (flash && !isFlickering) {
startFlickerAnimation();
}
function startFlickerAnimation() {
isFlickering = true;
flickerFrame = 0;
// Calculate frame duration based on total duration and frame count
const totalFrames = flickerCount * (frameDelay + 1);
const frameDuration = flickerDuration / totalFrames;
flickerInterval = setInterval(() => {
if (flickerFrame >= totalFrames) {
// Animation finished, always visible
clearInterval(flickerInterval);
isFlickering = false;
flickerVisible = true;
return;
}
// Toggle visibility every frameDelay frames
const flickerCycle = Math.floor(flickerFrame / (frameDelay + 1));
flickerVisible = flickerCycle % 2 === 0;
flickerFrame++;
}, frameDuration);
}
onMount(() => {
return () => {
if (flickerInterval) {
clearInterval(flickerInterval);
}
};
});
</script>
<!-- Effects wrapper with relative positioning for particles -->
<div class="effects-wrapper">
<!-- GBA-style flicker effect -->
<div class="effects-container" style="opacity: {(flash && isFlickering) ? (flickerVisible ? 1 : 0) : 1};">
<slot />
</div>
<!-- Multi-particle effects -->
{#each particleList as particle (particle.id)}
<div
class="effect-particle {particle.type}"
style="
left: {particle.x}px;
top: {particle.y}px;
animation-duration: {particle.duration}ms;
animation-delay: {particle.delay}ms;
--initial-scale: {particle.scale};
--initial-rotation: {particle.rotation}deg;
"
>
<span class="effect-emoji">{particle.emoji}</span>
</div>
{/each}
</div>
<style>
.effects-wrapper {
position: relative;
display: inline-block;
}
.effects-container {
position: relative;
display: inline-block;
transition: opacity 0.05s ease;
}
.effect-particle {
position: absolute;
pointer-events: none;
z-index: 5;
animation-fill-mode: forwards;
transform-origin: center center;
}
.effect-emoji {
font-size: 20px;
display: block;
filter: drop-shadow(0 0 4px rgba(0, 0, 0, 0.4));
transform: scale(var(--initial-scale, 1)) rotate(var(--initial-rotation, 0deg));
}
/* Status effects - floating with rotation */
.effect-particle.burn {
animation: statusBurn ease-in-out;
}
.effect-particle.poison {
animation: statusPoison ease-in-out;
}
.effect-particle.paralyze {
animation: statusParalyze linear;
}
.effect-particle.sleep {
animation: statusSleep ease-in-out;
}
.effect-particle.freeze {
animation: statusFreeze ease-out;
}
/* Stat increases - rising with spin */
.effect-particle.attackUp,
.effect-particle.defenseUp,
.effect-particle.speedUp,
.effect-particle.accuracyUp {
animation: statIncrease ease-out;
}
/* Stat decreases - falling with wobble */
.effect-particle.attackDown,
.effect-particle.defenseDown,
.effect-particle.speedDown,
.effect-particle.accuracyDown {
animation: statDecrease ease-in;
}
/* Special effects */
.effect-particle.critical,
.effect-particle.superEffective {
animation: criticalBurst ease-out;
}
.effect-particle.notVeryEffective,
.effect-particle.miss {
animation: missSwirl ease-in-out;
}
.effect-particle.heal {
animation: healRise ease-out;
}
/* Complex multi-property animations */
@keyframes statusBurn {
0% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(var(--initial-rotation)) scale(0.3);
opacity: 0;
}
15% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 45deg)) scale(1.2);
opacity: 1;
}
50% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 180deg)) scale(1.0);
opacity: 0.9;
}
85% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 315deg)) scale(0.8);
opacity: 0.4;
}
100% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 360deg)) scale(0.5);
opacity: 0;
}
}
@keyframes statusPoison {
0% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(var(--initial-rotation)) scale(0.4);
opacity: 0;
}
20% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) - 30deg)) scale(1.1);
opacity: 1;
}
40% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 30deg)) scale(0.9);
opacity: 0.8;
}
60% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) - 20deg)) scale(1.0);
opacity: 0.6;
}
80% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 15deg)) scale(0.7);
opacity: 0.3;
}
100% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(var(--initial-rotation)) scale(0.5);
opacity: 0;
}
}
@keyframes statusParalyze {
0% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(var(--initial-rotation)) scale(0.2);
opacity: 0;
}
10% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 10deg)) scale(1.3);
opacity: 1;
}
20% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) - 10deg)) scale(1.1);
opacity: 0.9;
}
30% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 8deg)) scale(1.2);
opacity: 0.8;
}
40% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) - 8deg)) scale(1.0);
opacity: 0.7;
}
50% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 5deg)) scale(0.9);
opacity: 0.6;
}
100% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(var(--initial-rotation)) scale(0.3);
opacity: 0;
}
}
@keyframes statusSleep {
0% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(var(--initial-rotation)) scale(0.5);
opacity: 0;
}
25% {
transform: translate(-50%, -55%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 15deg)) scale(1.1);
opacity: 1;
}
50% {
transform: translate(-50%, -45%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) - 15deg)) scale(1.0);
opacity: 0.9;
}
75% {
transform: translate(-50%, -55%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 10deg)) scale(0.9);
opacity: 0.5;
}
100% {
transform: translate(-50%, -60%) scale(var(--initial-scale)) rotate(var(--initial-rotation)) scale(0.4);
opacity: 0;
}
}
@keyframes statusFreeze {
0% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(var(--initial-rotation)) scale(0.3);
opacity: 0;
}
30% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 5deg)) scale(1.4);
opacity: 1;
}
60% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) - 3deg)) scale(1.2);
opacity: 0.8;
}
90% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 2deg)) scale(0.8);
opacity: 0.3;
}
100% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(var(--initial-rotation)) scale(0.6);
opacity: 0;
}
}
@keyframes statIncrease {
0% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(var(--initial-rotation)) scale(0.4);
opacity: 0;
}
25% {
transform: translate(-50%, -70%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 90deg)) scale(1.3);
opacity: 1;
}
50% {
transform: translate(-50%, -90%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 180deg)) scale(1.1);
opacity: 0.9;
}
75% {
transform: translate(-50%, -110%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 270deg)) scale(0.9);
opacity: 0.6;
}
100% {
transform: translate(-50%, -130%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 360deg)) scale(0.5);
opacity: 0;
}
}
@keyframes statDecrease {
0% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(var(--initial-rotation)) scale(0.4);
opacity: 0;
}
25% {
transform: translate(-50%, -30%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) - 30deg)) scale(1.2);
opacity: 1;
}
50% {
transform: translate(-50%, -10%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 30deg)) scale(1.0);
opacity: 0.8;
}
75% {
transform: translate(-50%, 10%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) - 20deg)) scale(0.8);
opacity: 0.4;
}
100% {
transform: translate(-50%, 30%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 10deg)) scale(0.6);
opacity: 0;
}
}
@keyframes criticalBurst {
0% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(var(--initial-rotation)) scale(0.2);
opacity: 0;
}
15% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 45deg)) scale(1.8);
opacity: 1;
}
30% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) - 30deg)) scale(1.4);
opacity: 0.9;
}
50% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 60deg)) scale(1.6);
opacity: 0.8;
}
70% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) - 45deg)) scale(1.2);
opacity: 0.5;
}
100% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 720deg)) scale(0.3);
opacity: 0;
}
}
@keyframes missSwirl {
0% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(var(--initial-rotation)) scale(0.6);
opacity: 0;
}
25% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 90deg)) scale(1.2);
opacity: 0.7;
}
50% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 180deg)) scale(1.0);
opacity: 0.5;
}
75% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 270deg)) scale(0.8);
opacity: 0.3;
}
100% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 360deg)) scale(0.4);
opacity: 0;
}
}
@keyframes healRise {
0% {
transform: translate(-50%, -30%) scale(var(--initial-scale)) rotate(var(--initial-rotation)) scale(0.5);
opacity: 0;
}
20% {
transform: translate(-50%, -50%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 30deg)) scale(1.3);
opacity: 1;
}
40% {
transform: translate(-50%, -70%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) - 20deg)) scale(1.1);
opacity: 0.9;
}
60% {
transform: translate(-50%, -90%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) + 15deg)) scale(1.0);
opacity: 0.7;
}
80% {
transform: translate(-50%, -110%) scale(var(--initial-scale)) rotate(calc(var(--initial-rotation) - 10deg)) scale(0.8);
opacity: 0.4;
}
100% {
transform: translate(-50%, -130%) scale(var(--initial-scale)) rotate(var(--initial-rotation)) scale(0.5);
opacity: 0;
}
}
</style>