Skip to content

Commit 6f3b0e7

Browse files
committed
feat: adjust player hair dynamics for improved facing alignment
1 parent 6ac535a commit 6f3b0e7

3 files changed

Lines changed: 44 additions & 10 deletions

File tree

src/view/PlayerView.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,7 @@ export class PlayerView {
756756
this.respawnSprite.container
757757
.setVisible(true)
758758
.setAlpha(sample.ghostAlpha)
759-
.setScale(sample.ghostScaleX * snapshot.facing, sample.ghostScaleY);
759+
.setScale(sample.ghostScaleX, sample.ghostScaleY);
760760

761761
this.respawnAura
762762
.setVisible(sample.auraAlpha > 0.001)
@@ -854,7 +854,7 @@ export class PlayerView {
854854
}
855855

856856
private squash(scaleX: number, scaleY: number): void {
857-
this.playerSprite.container.setScale(Math.abs(scaleX) * this.facing, scaleY);
857+
this.playerSprite.container.setScale(Math.abs(scaleX), scaleY);
858858
}
859859

860860
private applyFastFallScale(snapshot: PlayerSnapshot): void {
@@ -899,7 +899,7 @@ export class PlayerView {
899899
const delta = PLAYER_VISUALS.scaleRelaxRate * dt;
900900
this.playerSprite.container.scaleX = this.approach(
901901
this.playerSprite.container.scaleX,
902-
this.facing,
902+
1,
903903
delta,
904904
);
905905
this.playerSprite.container.scaleY = this.approach(
@@ -1199,7 +1199,6 @@ export class PlayerView {
11991199

12001200
private syncFacing(facing: PlayerSnapshot["facing"]): void {
12011201
this.facing = facing;
1202-
this.playerSprite.container.scaleX = Math.abs(this.playerSprite.container.scaleX) * facing;
12031202
}
12041203

12051204
private isDashActive(snapshot: PlayerSnapshot): boolean {
@@ -1223,6 +1222,7 @@ export class PlayerView {
12231222
): void {
12241223
sprite.container.setPosition(x, y);
12251224
this.setGlyphSpritePose(sprite, pose, w, h);
1225+
this.setGlyphSpriteFacing(sprite, this.facing);
12261226
this.setGlyphSpriteColors(sprite, bodyColor, hairColor);
12271227
this.setGlyphHairPositions(sprite);
12281228
}
@@ -1250,6 +1250,13 @@ export class PlayerView {
12501250
}
12511251
}
12521252

1253+
private setGlyphSpriteFacing(sprite: GlyphSprite, facing: PlayerSnapshot["facing"]): void {
1254+
const flipped = facing < 0;
1255+
sprite.body.setFlipX(flipped);
1256+
sprite.bangs.setFlipX(flipped);
1257+
sprite.legacyHair.setFlipX(flipped);
1258+
}
1259+
12531260
private updateGlyphHairState(
12541261
sprite: GlyphSprite,
12551262
snapshot: PlayerSnapshot,

src/view/playerHair.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ const HAIR_LAYOUTS: Record<Sqrt11Pose, { anchor: HairPoint; segmentOffset: HairP
3939
};
4040

4141
const DEFAULT_HAIR_NODE_COUNT: number = SQRT11_HAIR_RADII.length;
42-
const DEFAULT_FOLLOW_RATE = 40;
42+
const DEFAULT_FOLLOW_RATE = 20;
4343

4444
export function resolveHairLayout(snapshot: HairSnapshotLike): HairLayout {
4545
const pose = resolveHairPose(snapshot);
4646
const base = HAIR_LAYOUTS[pose];
4747
const localVx = snapshot.vx * snapshot.facing;
48-
let offsetX = base.segmentOffset.x + clamp(-localVx / 140, -0.6, 0.7);
48+
let localOffsetX = base.segmentOffset.x + clamp(-localVx / 140, -0.6, 0.7);
4949
let offsetY = base.segmentOffset.y;
5050

5151
if (!snapshot.onGround) {
@@ -59,17 +59,20 @@ export function resolveHairLayout(snapshot: HairSnapshotLike): HairLayout {
5959
}
6060

6161
if (snapshot.state === "dash") {
62-
offsetX -= 0.4;
62+
localOffsetX -= 0.4;
6363
offsetY -= 0.45;
6464
} else if (snapshot.state === "climb") {
65-
offsetX += 0.15;
65+
localOffsetX += 0.15;
6666
offsetY -= 0.1;
6767
}
6868

6969
return {
70-
anchor: base.anchor,
70+
anchor: {
71+
x: base.anchor.x * snapshot.facing,
72+
y: base.anchor.y,
73+
},
7174
segmentOffset: {
72-
x: clamp(offsetX, -1.9, 0.35),
75+
x: clamp(localOffsetX, -1.9, 0.35) * snapshot.facing,
7376
y: clamp(offsetY, 0.45, 2.15),
7477
},
7578
maxDistance: base.maxDistance,

tests/model/player-hair.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,30 @@ describe("sqrt(11) player hair", () => {
5151
expect(dash.segmentOffset.y).toBeLessThan(run.segmentOffset.y);
5252
});
5353

54+
test("facing mirrors the hair anchor and segment direction instead of reusing the same side", () => {
55+
const right = resolveHairLayout({
56+
facing: 1,
57+
isCrouched: false,
58+
onGround: true,
59+
state: "normal",
60+
vx: 0,
61+
vy: 0,
62+
});
63+
const left = resolveHairLayout({
64+
facing: -1,
65+
isCrouched: false,
66+
onGround: true,
67+
state: "normal",
68+
vx: 0,
69+
vy: 0,
70+
});
71+
72+
expect(left.anchor.x).toBe(-right.anchor.x);
73+
expect(left.anchor.y).toBe(right.anchor.y);
74+
expect(left.segmentOffset.x).toBe(-right.segmentOffset.x);
75+
expect(left.segmentOffset.y).toBe(right.segmentOffset.y);
76+
});
77+
5478
test("step keeps each node within the configured max distance of its target", () => {
5579
const layout = {
5680
anchor: { x: 0, y: 0 },

0 commit comments

Comments
 (0)