@@ -67,17 +67,23 @@ const SQRT11_GLYPHS: Record<Sqrt11Pose, PixelGlyph> = {
6767} ;
6868
6969interface Afterimage {
70- rect : Phaser . GameObjects . Graphics ;
70+ sprite : GlyphSprite ;
7171 life : number ;
7272 maxLife : number ;
7373}
7474
75+ interface GlyphSprite {
76+ container : Phaser . GameObjects . Container ;
77+ body : Phaser . GameObjects . Image ;
78+ hair : Phaser . GameObjects . Image ;
79+ }
80+
7581export class PlayerView {
7682 private scene : Phaser . Scene ;
77- private body : Phaser . GameObjects . Graphics ;
83+ private playerSprite : GlyphSprite ;
7884 private dashSlash : Phaser . GameObjects . Rectangle ;
7985 private afterimages : Afterimage [ ] = [ ] ;
80- private afterimagePool : Phaser . GameObjects . Graphics [ ] = [ ] ;
86+ private afterimagePool : GlyphSprite [ ] = [ ] ;
8187 private trailTimer = 0 ;
8288 private dashParticleTimer = 0 ;
8389 private dashSlashTimer = 0 ;
@@ -99,10 +105,9 @@ export class PlayerView {
99105 constructor ( scene : Phaser . Scene ) {
100106 this . scene = scene ;
101107 this . ensurePixelTexture ( ) ;
108+ this . ensureGlyphTextures ( ) ;
102109
103- this . body = this . scene . add
104- . graphics ( )
105- . setDepth ( 5 ) ;
110+ this . playerSprite = this . createGlyphSprite ( 5 ) ;
106111
107112 this . dashSlash = this . scene . add
108113 . rectangle (
@@ -160,10 +165,11 @@ export class PlayerView {
160165 const drawY = snapshot . y + snapshot . hitboxH ;
161166 const pose = this . resolveSqrt11Pose ( snapshot ) ;
162167
163- this . body . setPosition ( drawX , drawY ) ;
164- this . drawSqrt11 (
165- this . body ,
168+ this . applyGlyphSprite (
169+ this . playerSprite ,
166170 pose ,
171+ drawX ,
172+ drawY ,
167173 snapshot . drawW ,
168174 snapshot . drawH ,
169175 this . resolveBodyColor ( snapshot ) ,
@@ -176,16 +182,16 @@ export class PlayerView {
176182 }
177183
178184 destroy ( ) : void {
179- this . body . destroy ( ) ;
185+ this . destroyGlyphSprite ( this . playerSprite ) ;
180186 this . dashSlash . destroy ( ) ;
181187 this . dashEmitter . destroy ( ) ;
182188 this . wallEmitter . destroy ( ) ;
183189
184190 for ( const a of this . afterimages ) {
185- a . rect . destroy ( ) ;
191+ this . destroyGlyphSprite ( a . sprite ) ;
186192 }
187- for ( const rect of this . afterimagePool ) {
188- rect . destroy ( ) ;
193+ for ( const sprite of this . afterimagePool ) {
194+ this . destroyGlyphSprite ( sprite ) ;
189195 }
190196
191197 this . afterimages = [ ] ;
@@ -307,14 +313,14 @@ export class PlayerView {
307313 const a = this . afterimages [ i ] ;
308314 a . life -= dt ;
309315 if ( a . life <= 0 ) {
310- a . rect . setVisible ( false ) ;
311- this . afterimagePool . push ( a . rect ) ;
316+ a . sprite . container . setVisible ( false ) ;
317+ this . afterimagePool . push ( a . sprite ) ;
312318 this . afterimages [ i ] = this . afterimages [ this . afterimages . length - 1 ] ;
313319 this . afterimages . pop ( ) ;
314320 continue ;
315321 }
316322
317- a . rect . alpha = a . life / a . maxLife ;
323+ a . sprite . container . alpha = a . life / a . maxLife ;
318324 }
319325 }
320326
@@ -336,38 +342,37 @@ export class PlayerView {
336342 private spawnAfterimage ( snapshot : PlayerSnapshot , color : number ) : void {
337343 const drawX = snapshot . x + PLAYER_GEOMETRY . hitboxW / 2 ;
338344 const drawY = snapshot . y + snapshot . hitboxH ;
339- const rect = this . getAfterimageRect ( ) ;
345+ const sprite = this . getAfterimageSprite ( ) ;
340346 const pose = this . resolveSqrt11Pose ( snapshot ) ;
341347
342- rect
348+ sprite . container
343349 . setPosition ( drawX , drawY )
344350 . setAlpha ( 0.55 )
345351 . setVisible ( true )
346- . setScale ( this . body . scaleX , this . body . scaleY ) ;
347-
348- this . drawSqrt11 ( rect , pose , snapshot . drawW , snapshot . drawH , COLORS . playerBody , color , 1 ) ;
352+ . setScale ( this . playerSprite . container . scaleX , this . playerSprite . container . scaleY ) ;
353+ this . setGlyphSpritePose ( sprite , pose , snapshot . drawW , snapshot . drawH ) ;
354+ this . setGlyphSpriteColors ( sprite , COLORS . playerBody , color ) ;
349355
350356 this . afterimages . push ( {
351- rect ,
357+ sprite ,
352358 life : 0.14 ,
353359 maxLife : 0.14 ,
354360 } ) ;
355361 }
356362
357- private getAfterimageRect ( ) : Phaser . GameObjects . Graphics {
358- const rect = this . afterimagePool . pop ( ) ;
359- if ( rect ) {
360- return rect ;
363+ private getAfterimageSprite ( ) : GlyphSprite {
364+ const sprite = this . afterimagePool . pop ( ) ;
365+ if ( sprite ) {
366+ return sprite ;
361367 }
362368
363- return this . scene . add
364- . graphics ( )
365- . setDepth ( 4 )
366- . setVisible ( false ) ;
369+ const created = this . createGlyphSprite ( 4 ) ;
370+ created . container . setVisible ( false ) ;
371+ return created ;
367372 }
368373
369374 private squash ( scaleX : number , scaleY : number ) : void {
370- this . body . setScale ( Math . abs ( scaleX ) * this . facing , scaleY ) ;
375+ this . playerSprite . container . setScale ( Math . abs ( scaleX ) * this . facing , scaleY ) ;
371376 }
372377
373378 private applyFastFallScale ( snapshot : PlayerSnapshot ) : void {
@@ -412,8 +417,16 @@ export class PlayerView {
412417
413418 private relaxScale ( dt : number ) : void {
414419 const delta = PLAYER_VISUALS . scaleRelaxRate * dt ;
415- this . body . scaleX = this . approach ( this . body . scaleX , this . facing , delta ) ;
416- this . body . scaleY = this . approach ( this . body . scaleY , 1 , delta ) ;
420+ this . playerSprite . container . scaleX = this . approach (
421+ this . playerSprite . container . scaleX ,
422+ this . facing ,
423+ delta ,
424+ ) ;
425+ this . playerSprite . container . scaleY = this . approach (
426+ this . playerSprite . container . scaleY ,
427+ 1 ,
428+ delta ,
429+ ) ;
417430 }
418431
419432 private approach ( current : number , target : number , maxDelta : number ) : number {
@@ -554,34 +567,81 @@ export class PlayerView {
554567 g . destroy ( ) ;
555568 }
556569
570+ private ensureGlyphTextures ( ) : void {
571+ for ( const pose of Object . keys ( SQRT11_GLYPHS ) as Sqrt11Pose [ ] ) {
572+ this . ensureGlyphTexture ( pose , "body" , SQRT11_GLYPHS [ pose ] . bodyRows ) ;
573+ this . ensureGlyphTexture ( pose , "hair" , SQRT11_GLYPHS [ pose ] . hairRows ) ;
574+ }
575+ }
576+
577+ private ensureGlyphTexture (
578+ pose : Sqrt11Pose ,
579+ layer : "body" | "hair" ,
580+ rows : readonly ( readonly PixelRun [ ] ) [ ] ,
581+ ) : void {
582+ const key = this . glyphTextureKey ( pose , layer ) ;
583+ if ( this . scene . textures . exists ( key ) ) return ;
584+
585+ const glyph = SQRT11_GLYPHS [ pose ] ;
586+ const g = this . scene . add . graphics ( ) ;
587+ this . drawGlyphRows ( g , rows , 0 , 0 , 1 , 1 , 0xffffff , 1 ) ;
588+ g . generateTexture ( key , glyph . width , glyph . height ) ;
589+ g . destroy ( ) ;
590+ }
591+
592+ private createGlyphSprite ( depth : number ) : GlyphSprite {
593+ const body = this . scene . add . image ( 0 , 0 , this . glyphTextureKey ( "idle" , "body" ) ) . setOrigin ( 0.5 , 1 ) ;
594+ const hair = this . scene . add . image ( 0 , 0 , this . glyphTextureKey ( "idle" , "hair" ) ) . setOrigin ( 0.5 , 1 ) ;
595+ const container = this . scene . add . container ( 0 , 0 , [ body , hair ] ) . setDepth ( depth ) ;
596+ return { container, body, hair } ;
597+ }
598+
599+ private destroyGlyphSprite ( sprite : GlyphSprite ) : void {
600+ sprite . body . destroy ( ) ;
601+ sprite . hair . destroy ( ) ;
602+ sprite . container . destroy ( ) ;
603+ }
604+
557605 private syncFacing ( facing : PlayerSnapshot [ "facing" ] ) : void {
558606 this . facing = facing ;
559- this . body . scaleX = Math . abs ( this . body . scaleX ) * facing ;
607+ this . playerSprite . container . scaleX = Math . abs ( this . playerSprite . container . scaleX ) * facing ;
560608 }
561609
562610 private resolveSqrt11Pose ( snapshot : PlayerSnapshot ) : Sqrt11Pose {
563611 return snapshot . state === "duck" || snapshot . isCrouched ? "duck" : "idle" ;
564612 }
565613
566- private drawSqrt11 (
567- g : Phaser . GameObjects . Graphics ,
614+ private applyGlyphSprite (
615+ sprite : GlyphSprite ,
568616 pose : Sqrt11Pose ,
617+ x : number ,
618+ y : number ,
569619 w : number ,
570620 h : number ,
571621 bodyColor : number ,
572622 hairColor : number ,
573- alpha : number = 1 ,
574623 ) : void {
575- g . clear ( ) ;
624+ sprite . container . setPosition ( x , y ) ;
625+ this . setGlyphSpritePose ( sprite , pose , w , h ) ;
626+ this . setGlyphSpriteColors ( sprite , bodyColor , hairColor ) ;
627+ }
576628
577- const glyph = SQRT11_GLYPHS [ pose ] ;
578- const pixelW = Math . max ( w , 1 ) / glyph . width ;
579- const pixelH = Math . max ( h , 1 ) / glyph . height ;
580- const left = - ( glyph . width * pixelW ) / 2 ;
581- const top = - glyph . height * pixelH ;
629+ private setGlyphSpritePose ( sprite : GlyphSprite , pose : Sqrt11Pose , w : number , h : number ) : void {
630+ sprite . body
631+ . setTexture ( this . glyphTextureKey ( pose , "body" ) )
632+ . setDisplaySize ( w , h ) ;
633+ sprite . hair
634+ . setTexture ( this . glyphTextureKey ( pose , "hair" ) )
635+ . setDisplaySize ( w , h ) ;
636+ }
637+
638+ private setGlyphSpriteColors ( sprite : GlyphSprite , bodyColor : number , hairColor : number ) : void {
639+ sprite . body . setTint ( bodyColor ) ;
640+ sprite . hair . setTint ( hairColor ) ;
641+ }
582642
583- this . drawGlyphRows ( g , glyph . bodyRows , left , top , pixelW , pixelH , bodyColor , alpha ) ;
584- this . drawGlyphRows ( g , glyph . hairRows , left , top , pixelW , pixelH , hairColor , alpha ) ;
643+ private glyphTextureKey ( pose : Sqrt11Pose , layer : "body" | "hair" ) : string {
644+ return `sqrt11- ${ pose } - ${ layer } ` ;
585645 }
586646
587647 private drawGlyphRows (
0 commit comments