Fraser commited on
Commit
5207761
·
1 Parent(s): 81b3c3b

GBA style flash

Browse files
src/lib/components/Battle/BattleEffects.svelte CHANGED
@@ -1,32 +1,85 @@
1
  <script lang="ts">
2
  import { fade } from 'svelte/transition';
 
3
 
4
  export let effects: Array<{type: string, emoji: string, duration: number}> = [];
5
  export let flash: boolean = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  </script>
7
 
8
- <!-- Flash overlay -->
9
- {#if flash}
10
- <div class="flash-overlay" transition:fade={{ duration: 200 }}></div>
11
- {/if}
12
-
13
- <!-- Particle effects -->
14
- {#each effects as effect (effect)}
15
- <div class="effect-particle {effect.type}" style="animation-duration: {effect.duration}ms">
16
- <span class="effect-emoji">{effect.emoji}</span>
17
  </div>
18
- {/each}
 
 
 
 
 
 
 
19
 
20
  <style>
21
- .flash-overlay {
22
- position: absolute;
23
- top: 0;
24
- left: 0;
25
- right: 0;
26
- bottom: 0;
27
- background: rgba(255, 255, 255, 0.8);
28
- z-index: 10;
29
- pointer-events: none;
30
  }
31
 
32
  .effect-particle {
 
1
  <script lang="ts">
2
  import { fade } from 'svelte/transition';
3
+ import { onMount } from 'svelte';
4
 
5
  export let effects: Array<{type: string, emoji: string, duration: number}> = [];
6
  export let flash: boolean = false;
7
+
8
+ // GBA-style flicker animation parameters
9
+ const flickerCount = 19;
10
+ const frameDelay = 2;
11
+ const flickerDuration = 600; // milliseconds
12
+
13
+ // Flicker state management
14
+ let isFlickering = false;
15
+ let flickerVisible = true;
16
+ let flickerFrame = 0;
17
+ let flickerInterval: number;
18
+
19
+ // Watch for flash changes to trigger flicker animation
20
+ $: if (flash && !isFlickering) {
21
+ startFlickerAnimation();
22
+ }
23
+
24
+ function startFlickerAnimation() {
25
+ isFlickering = true;
26
+ flickerFrame = 0;
27
+
28
+ // Calculate frame duration based on total duration and frame count
29
+ const totalFrames = flickerCount * (frameDelay + 1);
30
+ const frameDuration = flickerDuration / totalFrames;
31
+
32
+ flickerInterval = setInterval(() => {
33
+ if (flickerFrame >= totalFrames) {
34
+ // Animation finished, always visible
35
+ clearInterval(flickerInterval);
36
+ isFlickering = false;
37
+ flickerVisible = true;
38
+ return;
39
+ }
40
+
41
+ // Toggle visibility every frameDelay frames
42
+ const flickerCycle = Math.floor(flickerFrame / (frameDelay + 1));
43
+ flickerVisible = flickerCycle % 2 === 0;
44
+
45
+ flickerFrame++;
46
+ }, frameDuration);
47
+ }
48
+
49
+ onMount(() => {
50
+ return () => {
51
+ if (flickerInterval) {
52
+ clearInterval(flickerInterval);
53
+ }
54
+ };
55
+ });
56
  </script>
57
 
58
+ <!-- Effects wrapper with relative positioning for particles -->
59
+ <div class="effects-wrapper">
60
+ <!-- GBA-style flicker effect -->
61
+ <div class="effects-container" style="opacity: {(flash && isFlickering) ? (flickerVisible ? 1 : 0) : 1};">
62
+ <slot />
 
 
 
 
63
  </div>
64
+
65
+ <!-- Particle effects -->
66
+ {#each effects as effect (effect)}
67
+ <div class="effect-particle {effect.type}" style="animation-duration: {effect.duration}ms">
68
+ <span class="effect-emoji">{effect.emoji}</span>
69
+ </div>
70
+ {/each}
71
+ </div>
72
 
73
  <style>
74
+ .effects-wrapper {
75
+ position: relative;
76
+ display: inline-block;
77
+ }
78
+
79
+ .effects-container {
80
+ position: relative;
81
+ display: inline-block;
82
+ transition: opacity 0.05s ease;
83
  }
84
 
85
  .effect-particle {
src/lib/components/Battle/BattleField.svelte CHANGED
@@ -73,15 +73,19 @@
73
 
74
  {#if enemyVisible}
75
  <div class="enemy-piclet-wrapper" class:animate-in={showIntro}>
76
- <img
77
- class="piclet-image enemy-image"
78
- src={enemyPiclet.imageData || enemyPiclet.imageUrl}
79
- alt={enemyPiclet.nickname}
80
- on:error={(e) => {
81
- const target = e.currentTarget as HTMLImageElement;
82
- target.src = 'https://via.placeholder.com/120x120?text=Piclet';
83
- }}
84
- />
 
 
 
 
85
  <img
86
  class="platform enemy-platform"
87
  src="/assets/grass.PNG"
@@ -101,9 +105,6 @@
101
  <StatusEffectIndicator statusEffects={battleState.opponentPiclet.statusEffects.map(effect => ({ type: effect, turnsLeft: 3 }))} />
102
  </div>
103
  {/if}
104
-
105
- <!-- Enemy Battle Effects -->
106
- <BattleEffects effects={enemyEffects} flash={enemyFlash} />
107
  </div>
108
  {/if}
109
  </div>
@@ -116,15 +117,19 @@
116
  <div class="player-stack" class:intro-animations={showIntro}>
117
  {#if playerVisible}
118
  <div class="player-piclet-wrapper" class:animate-in={showIntro}>
119
- <img
120
- class="piclet-image player-image"
121
- src={playerPiclet.imageData || playerPiclet.imageUrl}
122
- alt={playerPiclet.nickname}
123
- on:error={(e) => {
124
- const target = e.currentTarget as HTMLImageElement;
125
- target.src = 'https://via.placeholder.com/120x120?text=Piclet';
126
- }}
127
- />
 
 
 
 
128
  <img
129
  class="platform player-platform"
130
  src="/assets/grass.PNG"
@@ -144,9 +149,6 @@
144
  <StatusEffectIndicator statusEffects={battleState.playerPiclet.statusEffects.map(effect => ({ type: effect, turnsLeft: 3 }))} />
145
  </div>
146
  {/if}
147
-
148
- <!-- Player Battle Effects -->
149
- <BattleEffects effects={playerEffects} flash={playerFlash} />
150
  </div>
151
  {/if}
152
 
 
73
 
74
  {#if enemyVisible}
75
  <div class="enemy-piclet-wrapper" class:animate-in={showIntro}>
76
+ <!-- Enemy Battle Effects wrap the image for flicker animation -->
77
+ <BattleEffects effects={enemyEffects} flash={enemyFlash}>
78
+ <img
79
+ class="piclet-image enemy-image"
80
+ src={enemyPiclet.imageData || enemyPiclet.imageUrl}
81
+ alt={enemyPiclet.nickname}
82
+ on:error={(e) => {
83
+ const target = e.currentTarget as HTMLImageElement;
84
+ target.src = 'https://via.placeholder.com/120x120?text=Piclet';
85
+ }}
86
+ />
87
+ </BattleEffects>
88
+
89
  <img
90
  class="platform enemy-platform"
91
  src="/assets/grass.PNG"
 
105
  <StatusEffectIndicator statusEffects={battleState.opponentPiclet.statusEffects.map(effect => ({ type: effect, turnsLeft: 3 }))} />
106
  </div>
107
  {/if}
 
 
 
108
  </div>
109
  {/if}
110
  </div>
 
117
  <div class="player-stack" class:intro-animations={showIntro}>
118
  {#if playerVisible}
119
  <div class="player-piclet-wrapper" class:animate-in={showIntro}>
120
+ <!-- Player Battle Effects wrap the image for flicker animation -->
121
+ <BattleEffects effects={playerEffects} flash={playerFlash}>
122
+ <img
123
+ class="piclet-image player-image"
124
+ src={playerPiclet.imageData || playerPiclet.imageUrl}
125
+ alt={playerPiclet.nickname}
126
+ on:error={(e) => {
127
+ const target = e.currentTarget as HTMLImageElement;
128
+ target.src = 'https://via.placeholder.com/120x120?text=Piclet';
129
+ }}
130
+ />
131
+ </BattleEffects>
132
+
133
  <img
134
  class="platform player-platform"
135
  src="/assets/grass.PNG"
 
149
  <StatusEffectIndicator statusEffects={battleState.playerPiclet.statusEffects.map(effect => ({ type: effect, turnsLeft: 3 }))} />
150
  </div>
151
  {/if}
 
 
 
152
  </div>
153
  {/if}
154
 
src/lib/components/Pages/Battle.svelte CHANGED
@@ -252,10 +252,10 @@
252
  function triggerDamageFlash(target: 'player' | 'enemy') {
253
  if (target === 'player') {
254
  playerFlash = true;
255
- setTimeout(() => playerFlash = false, 200);
256
  } else {
257
  enemyFlash = true;
258
- setTimeout(() => enemyFlash = false, 200);
259
  }
260
  }
261
 
 
252
  function triggerDamageFlash(target: 'player' | 'enemy') {
253
  if (target === 'player') {
254
  playerFlash = true;
255
+ setTimeout(() => playerFlash = false, 600); // Match GBA flicker duration
256
  } else {
257
  enemyFlash = true;
258
+ setTimeout(() => enemyFlash = false, 600); // Match GBA flicker duration
259
  }
260
  }
261