@@ -2,87 +2,7 @@ import React from 'react';
22import { View , Text , StyleSheet } from 'react-native' ;
33import type { GameState } from '../../engine/types' ;
44import { ELITE_EMOJIS , ELITE_COLORS } from '../../engine/data' ;
5- import { WATER_ZONES } from '../../engine/constants' ;
6-
7- const GRID_STEP = 160 ;
8- const GRID_LINES = Array . from ( { length : Math . floor ( 2000 / GRID_STEP ) + 1 } , ( _ , i ) => i * GRID_STEP ) ;
9- const STARS = Array . from ( { length : 46 } , ( _ , i ) => ( {
10- id : i ,
11- x : ( i * 173 ) % 1960 + 20 ,
12- y : ( i * 311 ) % 1960 + 20 ,
13- size : 1 + ( i % 3 ) ,
14- opacity : 0.12 + ( i % 5 ) * 0.045 ,
15- } ) ) ;
16- const CURRENTS = Array . from ( { length : 12 } , ( _ , i ) => ( {
17- id : i ,
18- x : ( i * 227 ) % 1850 + 60 ,
19- y : ( i * 149 ) % 1840 + 80 ,
20- width : 90 + ( i % 4 ) * 28 ,
21- angle : - 18 + ( i % 5 ) * 9 ,
22- opacity : 0.05 + ( i % 3 ) * 0.025 ,
23- } ) ) ;
24- const BUBBLES = Array . from ( { length : 24 } , ( _ , i ) => ( {
25- id : i ,
26- x : ( i * 131 ) % 1900 + 35 ,
27- y : ( i * 211 ) % 1900 + 35 ,
28- size : 3 + ( i % 4 ) ,
29- speed : 0.28 + ( i % 5 ) * 0.08 ,
30- } ) ) ;
31- const REEF_PROPS = [
32- { id : 1 , x : 170 , y : 260 , emoji : '🪸' , size : 30 , layer : 0 } ,
33- { id : 2 , x : 420 , y : 1560 , emoji : '🌿' , size : 24 , layer : 1 } ,
34- { id : 3 , x : 820 , y : 300 , emoji : '🪨' , size : 26 , layer : 0 } ,
35- { id : 4 , x : 1240 , y : 1680 , emoji : '🪸' , size : 34 , layer : 1 } ,
36- { id : 5 , x : 1620 , y : 520 , emoji : '🌿' , size : 26 , layer : 0 } ,
37- { id : 6 , x : 1760 , y : 1320 , emoji : '💠' , size : 22 , layer : 1 } ,
38- { id : 7 , x : 620 , y : 1020 , emoji : '🪸' , size : 24 , layer : 0 } ,
39- { id : 8 , x : 1420 , y : 940 , emoji : '🪨' , size : 28 , layer : 1 } ,
40- { id : 9 , x : 920 , y : 820 , emoji : '🌿' , size : 23 , layer : 0 } ,
41- { id : 10 , x : 1080 , y : 1180 , emoji : '🪸' , size : 26 , layer : 1 } ,
42- { id : 11 , x : 780 , y : 1220 , emoji : '💠' , size : 20 , layer : 0 } ,
43- { id : 12 , x : 1200 , y : 760 , emoji : '🪨' , size : 24 , layer : 1 } ,
44- { id : 13 , x : 300 , y : 800 , emoji : '🐚' , size : 20 , layer : 0 } ,
45- { id : 14 , x : 1550 , y : 200 , emoji : '🪸' , size : 28 , layer : 1 } ,
46- { id : 15 , x : 500 , y : 1350 , emoji : '🌊' , size : 22 , layer : 0 } ,
47- { id : 16 , x : 1700 , y : 900 , emoji : '🪸' , size : 32 , layer : 1 } ,
48- { id : 17 , x : 100 , y : 1700 , emoji : '🐚' , size : 24 , layer : 0 } ,
49- { id : 18 , x : 950 , y : 1600 , emoji : '🌿' , size : 28 , layer : 1 } ,
50- ] ;
51- const CAUSTIC_LIGHTS = Array . from ( { length : 8 } , ( _ , i ) => ( {
52- id : i ,
53- x : ( i * 293 ) % 1700 + 150 ,
54- y : ( i * 197 ) % 1700 + 150 ,
55- width : 140 + ( i % 3 ) * 60 ,
56- height : 80 + ( i % 4 ) * 30 ,
57- angle : ( i * 37 ) % 360 ,
58- speed : 0.006 + ( i % 3 ) * 0.003 ,
59- drift : 0.008 + ( i % 4 ) * 0.004 ,
60- } ) ) ;
61- const KELP_CLUSTERS = Array . from ( { length : 10 } , ( _ , i ) => ( {
62- id : i ,
63- x : ( i * 199 ) % 1800 + 100 ,
64- y : ( i * 263 ) % 1800 + 100 ,
65- height : 40 + ( i % 3 ) * 16 ,
66- blades : 3 + ( i % 3 ) ,
67- hue : i % 2 === 0 ? 'rgba(34,197,94,0.14)' : 'rgba(20,184,166,0.12)' ,
68- } ) ) ;
69- const DEPTH_MOTES = Array . from ( { length : 18 } , ( _ , i ) => ( {
70- id : i ,
71- x : ( i * 157 ) % 1920 + 30 ,
72- y : ( i * 239 ) % 1920 + 30 ,
73- size : 2 + ( i % 3 ) * 1.5 ,
74- driftX : 0.012 + ( i % 5 ) * 0.006 ,
75- driftY : 0.008 + ( i % 4 ) * 0.005 ,
76- opacity : 0.06 + ( i % 4 ) * 0.03 ,
77- color : i % 3 === 0 ? '#A78BFA' : i % 3 === 1 ? '#2DD4BF' : '#BAE6FD' ,
78- } ) ) ;
79- const SAND_BARS = [
80- { id : 1 , x : 170 , y : 180 , width : 520 , height : 200 , rot : - 9 } ,
81- { id : 2 , x : 1250 , y : 150 , width : 560 , height : 220 , rot : 11 } ,
82- { id : 3 , x : 210 , y : 1280 , width : 680 , height : 260 , rot : 7 } ,
83- { id : 4 , x : 1080 , y : 1220 , width : 700 , height : 280 , rot : - 6 } ,
84- { id : 5 , x : 730 , y : 760 , width : 460 , height : 180 , rot : 4 } ,
85- ] ;
5+ import ArenaBackground from './arena/ArenaBackground' ;
866
877interface Props {
888 gameState : React . RefObject < GameState | null > ;
@@ -114,206 +34,19 @@ export default function GameCanvas({ gameState, frame }: Props) {
11434 const vMinY = camera . y - sh / 2 - 60 ;
11535 const vMaxY = camera . y + sh / 2 + 60 ;
11636 const vis = ( x : number , y : number ) => x >= vMinX && x <= vMaxX && y >= vMinY && y <= vMaxY ;
117- const visRect = ( x : number , y : number , width : number , height : number ) => (
118- x + width >= vMinX && x <= vMaxX && y + height >= vMinY && y <= vMaxY
119- ) ;
120- const visibleGridX = GRID_LINES . filter ( x => x >= vMinX && x <= vMaxX ) ;
121- const visibleGridY = GRID_LINES . filter ( y => y >= vMinY && y <= vMaxY ) ;
122- const visibleCurrents = CURRENTS . filter ( current => visRect ( current . x - 30 , current . y - 30 , current . width + 60 , 60 ) ) ;
123- const visibleReefProps = REEF_PROPS . filter ( prop => visRect ( prop . x - 20 , prop . y - 20 , prop . size + 40 , prop . size + 40 ) ) ;
124- const visibleStars = STARS . filter ( star => vis ( star . x , star . y ) ) ;
125- const visibleBubbles = BUBBLES . filter ( bubble => {
126- const y = ( ( bubble . y - frame * bubble . speed ) % 1960 + 1960 ) % 1960 + 20 ;
127- return vis ( bubble . x , y ) ;
128- } ) ;
12937 const showCritFlash = state . hitStop > 0.06 ;
13038
13139 return (
13240 < View style = { s . viewport } pointerEvents = "none" >
13341 < View style = { [ s . world , { transform : [ { translateX : tx } , { translateY : ty } ] } ] } >
134- { /* Arena bg */ }
135- < View style = { [ s . arenaBg , { width : arena . width , height : arena . height } ] } >
136- < View style = { s . arenaGlowA } />
137- < View style = { s . arenaGlowB } />
138- { SAND_BARS . map ( bar => (
139- < View
140- key = { `sand-${ bar . id } ` }
141- style = { [
142- s . sandPatch ,
143- {
144- left : bar . x ,
145- top : bar . y ,
146- width : bar . width ,
147- height : bar . height ,
148- borderRadius : Math . round ( bar . height * 0.52 ) ,
149- transform : [ { rotate : `${ bar . rot } deg` } ] ,
150- } ,
151- ] }
152- />
153- ) ) }
154- { WATER_ZONES . map ( ( zone , i ) => (
155- < View
156- key = { `water-${ i } ` }
157- style = { [
158- s . waterZone ,
159- {
160- left : zone . x - zone . radius ,
161- top : zone . y - zone . radius ,
162- width : zone . radius * 2 ,
163- height : zone . radius * 2 ,
164- borderRadius : zone . radius ,
165- opacity : 0.2 + Math . sin ( frame * 0.035 + i ) * 0.04 ,
166- } ,
167- ] }
168- >
169- < View style = { s . waterZoneInner } />
170- </ View >
171- ) ) }
172- < View style = { [ s . arenaGlowC , {
173- left : 600 + Math . sin ( frame * 0.005 ) * 120 ,
174- top : 800 + Math . cos ( frame * 0.004 ) * 90 ,
175- } ] } />
176- { /* Caustic light rays */ }
177- { CAUSTIC_LIGHTS . map ( cl => (
178- < View
179- key = { `cl-${ cl . id } ` }
180- style = { [
181- s . causticLight ,
182- {
183- left : cl . x + Math . sin ( frame * cl . drift + cl . id ) * 40 ,
184- top : cl . y + Math . cos ( frame * cl . drift * 0.7 + cl . id * 3 ) * 30 ,
185- width : cl . width ,
186- height : cl . height ,
187- borderRadius : cl . height / 2 ,
188- opacity : 0.035 + Math . sin ( frame * cl . speed + cl . id * 2 ) * 0.02 ,
189- transform : [ { rotate : `${ cl . angle + Math . sin ( frame * 0.003 + cl . id ) * 12 } deg` } ] ,
190- } ,
191- ] }
192- />
193- ) ) }
194- { /* Kelp clusters */ }
195- { KELP_CLUSTERS . map ( kc => (
196- < View key = { `kc-${ kc . id } ` } style = { [ s . kelpCluster , { left : kc . x , top : kc . y } ] } >
197- { Array . from ( { length : kc . blades } , ( _ , bi ) => {
198- const sway = Math . sin ( ( frame + kc . id * 17 + bi * 11 ) * 0.025 ) * 8 ;
199- return (
200- < View
201- key = { bi }
202- style = { [
203- s . kelpBlade ,
204- {
205- height : kc . height + bi * 6 ,
206- backgroundColor : kc . hue ,
207- left : bi * 5 - ( kc . blades * 2.5 ) ,
208- transform : [ { rotate : `${ sway + ( bi - 1 ) * 3 } deg` } , { translateY : - kc . height / 2 } ] ,
209- } ,
210- ] }
211- />
212- ) ;
213- } ) }
214- </ View >
215- ) ) }
216- { visibleCurrents . map ( current => (
217- < View
218- key = { `cur-${ current . id } ` }
219- style = { [
220- s . currentLine ,
221- {
222- left : current . x ,
223- top : current . y + Math . sin ( ( frame + current . id * 17 ) * 0.018 ) * 14 ,
224- width : current . width ,
225- opacity : current . opacity ,
226- transform : [
227- { rotate : `${ current . angle } deg` } ,
228- { translateX : Math . sin ( ( frame + current . id * 11 ) * 0.022 ) * 18 } ,
229- ] ,
230- } ,
231- ] }
232- />
233- ) ) }
234- { visibleReefProps . map ( prop => (
235- < Text
236- key = { `reef-${ prop . id } ` }
237- style = { [
238- s . reefProp ,
239- {
240- left : prop . x ,
241- top : prop . y + Math . sin ( ( frame + prop . id * 19 ) * 0.025 ) * ( 2 + prop . layer ) ,
242- fontSize : prop . size ,
243- opacity : 0.18 + Math . sin ( ( frame + prop . id * 13 ) * 0.02 ) * 0.05 + prop . layer * 0.06 ,
244- } ,
245- ] }
246- >
247- { prop . emoji }
248- </ Text >
249- ) ) }
250- { BUBBLES . map ( bubble => {
251- const bx = bubble . x + Math . sin ( ( frame + bubble . id * 7 ) * 0.035 ) * 7 ;
252- const by = ( ( bubble . y - frame * bubble . speed ) % 1960 + 1960 ) % 1960 + 20 ;
253- if ( ! vis ( bx , by ) ) return null ;
254- return (
255- < View
256- key = { `bubble-${ bubble . id } ` }
257- style = { [
258- s . bubble ,
259- {
260- left : bx ,
261- top : by ,
262- width : bubble . size ,
263- height : bubble . size ,
264- borderRadius : bubble . size ,
265- opacity : 0.08 + ( bubble . id % 4 ) * 0.025 ,
266- } ,
267- ] }
268- />
269- ) ;
270- } ) }
271- { /* Depth motes */ }
272- { DEPTH_MOTES . map ( mote => {
273- const mx = mote . x + Math . sin ( frame * mote . driftX + mote . id * 5 ) * 30 ;
274- const my = mote . y + Math . cos ( frame * mote . driftY + mote . id * 3 ) * 25 ;
275- return (
276- < View
277- key = { `mote-${ mote . id } ` }
278- style = { [
279- s . depthMote ,
280- {
281- left : mx ,
282- top : my ,
283- width : mote . size ,
284- height : mote . size ,
285- borderRadius : mote . size ,
286- backgroundColor : mote . color ,
287- opacity : mote . opacity + Math . sin ( frame * 0.03 + mote . id ) * 0.02 ,
288- } ,
289- ] }
290- />
291- ) ;
292- } ) }
293- { visibleGridX . map ( x => < View key = { `vx-${ x } ` } style = { [ s . gridLineV , { left : x , height : arena . height } ] } /> ) }
294- { visibleGridY . map ( y => < View key = { `hy-${ y } ` } style = { [ s . gridLineH , { top : y , width : arena . width } ] } /> ) }
295- { visibleStars . map ( star => (
296- < View
297- key = { star . id }
298- style = { [
299- s . star ,
300- {
301- left : star . x ,
302- top : star . y ,
303- width : star . size ,
304- height : star . size ,
305- borderRadius : star . size ,
306- opacity : Math . min ( 0.55 , star . opacity + ( ( frame + star . id ) % 90 < 45 ? 0.08 : 0 ) ) ,
307- } ,
308- ] }
309- />
310- ) ) }
311- { /* Arena border pulse */ }
312- < View style = { [ s . arenaBorderPulse , {
313- width : arena . width , height : arena . height ,
314- borderColor : `rgba(45,212,191,${ 0.12 + Math . sin ( frame * 0.02 ) * 0.06 } )` ,
315- } ] } />
316- </ View >
42+ < ArenaBackground
43+ width = { arena . width }
44+ height = { arena . height }
45+ frame = { frame }
46+ camera = { camera }
47+ sw = { sw }
48+ sh = { sh }
49+ />
31750 { /* Hazards */ }
31851 { ( hazards ?? [ ] ) . filter ( h => vis ( h . x , h . y ) ) . map ( h => {
31952 const pulse = 0.5 + Math . sin ( h . pulse ) * 0.18 ;
@@ -794,6 +527,11 @@ export default function GameCanvas({ gameState, frame }: Props) {
794527 ) ;
795528 } ) }
796529 </ View >
530+ { state . inWater && (
531+ < View style = { s . submergeOverlay } pointerEvents = "none" >
532+ < View style = { s . submergeVignette } />
533+ </ View >
534+ ) }
797535 < OffScreenMarkers state = { state } />
798536 { ( p . hp / p . maxHp ) < 0.32 && < DangerVignette frame = { frame } /> }
799537 { showCritFlash && < CritFlash frame = { frame } /> }
@@ -867,24 +605,8 @@ function OffScreenMarkers({ state }: { state: GameState }) {
867605const s = StyleSheet . create ( {
868606 viewport : { ...StyleSheet . absoluteFillObject , overflow : 'hidden' , backgroundColor : '#030712' } ,
869607 world : { position : 'absolute' , left : 0 , top : 0 } ,
870- arenaBg : { position : 'absolute' , left : 0 , top : 0 , backgroundColor : '#050A15' , borderWidth : 2 , borderColor : 'rgba(45,212,191,0.24)' , overflow : 'hidden' } ,
871- arenaGlowA : { position : 'absolute' , width : 620 , height : 620 , borderRadius : 310 , left : 120 , top : 120 , backgroundColor : 'rgba(124,58,237,0.1)' } ,
872- arenaGlowB : { position : 'absolute' , width : 760 , height : 760 , borderRadius : 380 , right : 120 , bottom : 120 , backgroundColor : 'rgba(20,184,166,0.08)' } ,
873- sandPatch : { position : 'absolute' , backgroundColor : 'rgba(250,204,21,0.09)' , borderWidth : 1 , borderColor : 'rgba(234,179,8,0.16)' } ,
874- waterZone : { position : 'absolute' , borderWidth : 2 , borderColor : 'rgba(56,189,248,0.35)' , backgroundColor : 'rgba(14,116,144,0.18)' , alignItems : 'center' , justifyContent : 'center' } ,
875- waterZoneInner : { width : '72%' , height : '72%' , borderRadius : 999 , borderWidth : 1 , borderColor : 'rgba(125,211,252,0.22)' , backgroundColor : 'rgba(30,64,175,0.12)' } ,
876- arenaGlowC : { position : 'absolute' , width : 500 , height : 500 , borderRadius : 250 , backgroundColor : 'rgba(99,102,241,0.06)' } ,
877- causticLight : { position : 'absolute' , backgroundColor : 'rgba(186,230,253,0.06)' } ,
878- kelpCluster : { position : 'absolute' , width : 30 , height : 60 , alignItems : 'center' } ,
879- kelpBlade : { position : 'absolute' , width : 4 , borderRadius : 2 , bottom : 0 } ,
880- currentLine : { position : 'absolute' , height : 2 , borderRadius : 2 , backgroundColor : '#BAE6FD' } ,
881- reefProp : { position : 'absolute' , textShadowColor : 'rgba(0,0,0,0.5)' , textShadowOffset : { width : 0 , height : 2 } , textShadowRadius : 4 } ,
882- bubble : { position : 'absolute' , borderWidth : 1 , borderColor : 'rgba(186,230,253,0.55)' , backgroundColor : 'rgba(186,230,253,0.04)' } ,
883- depthMote : { position : 'absolute' } ,
884- gridLineV : { position : 'absolute' , top : 0 , width : 1 , backgroundColor : 'rgba(148,163,184,0.055)' } ,
885- gridLineH : { position : 'absolute' , left : 0 , height : 1 , backgroundColor : 'rgba(148,163,184,0.055)' } ,
886- star : { position : 'absolute' , backgroundColor : '#E0F2FE' } ,
887- arenaBorderPulse : { position : 'absolute' , left : 0 , top : 0 , borderWidth : 3 , backgroundColor : 'transparent' } ,
608+ submergeOverlay : { ...StyleSheet . absoluteFillObject , zIndex : 3 , backgroundColor : 'rgba(8,47,73,0.14)' } ,
609+ submergeVignette : { ...StyleSheet . absoluteFillObject , borderWidth : 22 , borderColor : 'rgba(14,116,144,0.22)' , backgroundColor : 'transparent' } ,
888610 entity : { position : 'absolute' , textAlign : 'center' } ,
889611 hazardWrap : { position : 'absolute' , alignItems : 'center' , justifyContent : 'center' , borderWidth : 2 , borderColor : 'rgba(251,146,60,0.38)' , backgroundColor : 'rgba(251,146,60,0.07)' } ,
890612 hazardPulse : { position : 'absolute' , borderWidth : 2 , borderColor : 'rgba(251,146,60,0.42)' , backgroundColor : 'rgba(251,146,60,0.08)' } ,
0 commit comments