Skip to content

Commit bcc3486

Browse files
committed
feat: refactor player rendering to use sprite for body and hair and use tint for tired state
1 parent 50f7915 commit bcc3486

1 file changed

Lines changed: 105 additions & 45 deletions

File tree

src/view/PlayerView.ts

Lines changed: 105 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,23 @@ const SQRT11_GLYPHS: Record<Sqrt11Pose, PixelGlyph> = {
6767
};
6868

6969
interface 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+
7581
export 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

Comments
 (0)