fix(rpg): 자매 sprite 를 실제 일러스트와 닮게 재작성
자기야 발견: 1차 sprite 색상이 실제 자매 일러스트와 거리가 멀었음. 4 자매
실제 avatar.png (각 LXC 의 ~/.openclaw/avatar.png) 를 직접 확인 후 시그니처
정정:
하랑 — 흰/은 머리 + 흰 cat ears + 파란 리본 + 파란 눈 (이전: 빨강 — 틀림)
나랑 — 검은 short bob + 검은 cat ears + 청록 보타이 + 청록 눈 (그나마 비슷)
다랑 — 흰 머리 + 흰 cat ears + 빨간 chinese 옷 + 빨간 눈 (이전: 노랑 — 틀림)
이랑 — 흰 long hair + 흰 cat ears + 흰 옷 + 파란 눈 + 파란 나비 (이전: 보라)
핵심 변경:
- spriteFactory.ts SISTER_PALETTES 전면 재작성: hair / hairShadow / outfit /
outfit2 / eye / earInner 등 분리. 자매별로 정확한 시그니처 색상.
- 4 자매 모두 catgirl 이라 sprite 위쪽에 cat ears 추가 (행 0-2 에 cat ear
outline + hair fill + earInner pink).
- CHAR_H 24 → 28 로 키워서 cat ears 와 더 긴 머리/몸 표현 공간 확보.
- 자매별 추가 디테일:
darang → 이마에 빨간 보석 점
erang → 머리 옆에 작은 파란 나비
harang → 머리 위에 파란 리본
narang → 옆머리 청록 헤어핀
- narang 만 short bob (옆 bangs 다른 자매보다 짧음). 나머지 3 명은 long hair.
- OfficeScene.ts: sprite 28px 으로 커졌으니 origin 을 (0.5, 1) 로 바꾸고
발끝을 타일 바닥 위에 정렬해 일관된 standing height.
이전: sprite 가 식별 불가능한 색깔 박스. 이번: 4 자매 각각 일러스트 시그
니처 색 + cat ears + 머리스타일이 보임.
This commit is contained in:
@@ -115,9 +115,12 @@ export class OfficeScene extends Phaser.Scene {
|
||||
if (ch >= '1' && ch <= '4') {
|
||||
const idx = parseInt(ch, 10) - 1;
|
||||
const name = SISTER_KEYS[idx]!;
|
||||
// Sprite is 28px tall — anchor at the feet (origin 0.5, 1) and
|
||||
// place feet at the tile floor (y + half tile) for consistent
|
||||
// standing height.
|
||||
const sprite = this.add
|
||||
.sprite(x, y - 4, `sister-${name}`, 0)
|
||||
.setOrigin(0.5, 0.5);
|
||||
.sprite(x, y + TILE_SIZE / 2 - 1, `sister-${name}`, 0)
|
||||
.setOrigin(0.5, 1);
|
||||
sprite.setInteractive({ useHandCursor: true });
|
||||
sprite.on('pointerdown', () => {
|
||||
this.events_.onSisterClick?.(name);
|
||||
|
||||
@@ -3,68 +3,92 @@ import type * as Phaser from 'phaser';
|
||||
/**
|
||||
* Procedural pixel art sprite factory.
|
||||
*
|
||||
* Why procedural: pre-baked sprite sheets would mean shipping art assets,
|
||||
* downloading from external sources, and committing copyrighted-by-others
|
||||
* material. Drawing every pixel in code keeps the dashboard self-contained
|
||||
* and lets us match the 4-sister color identity exactly.
|
||||
* Goal: each sister sprite should look like a tiny pixel-art version of
|
||||
* her real anime illustration. All four sisters are catgirls in the source
|
||||
* art, so every sprite has cat ears. Sister-specific signatures:
|
||||
*
|
||||
* Style: 1-bit-ish pixel art with 2-3 colors per sprite, 16px tiles, 16x24
|
||||
* characters. Inspired by GBC Pokemon, deskrpg, openclaw-office.
|
||||
* harang — white hair, blue ribbon, white cat ears, blue eyes
|
||||
* narang — black short bob, teal bowtie, black cat ears, teal eyes
|
||||
* darang — white hair, red chinese-style outfit, white cat ears, red eyes
|
||||
* erang — white hair, white outfit + soft blue trim, white cat ears, blue eyes
|
||||
*
|
||||
* The 16x28 grid maps roughly to:
|
||||
* row 0-1 : cat ear tips
|
||||
* row 2-7 : hair top + face
|
||||
* row 8-9 : eyes / mouth
|
||||
* row 10-11 : neck + collar
|
||||
* row 12-19 : torso + arms
|
||||
* row 20-25 : legs
|
||||
* row 26-27 : feet / shadow
|
||||
*/
|
||||
|
||||
export const TILE_SIZE = 16;
|
||||
export const CHAR_W = 16;
|
||||
export const CHAR_H = 24;
|
||||
export const CHAR_H = 28;
|
||||
|
||||
export interface SisterPalette {
|
||||
hair: number;
|
||||
outfit: number;
|
||||
outfit2: number;
|
||||
skin: number;
|
||||
outline: number;
|
||||
hair: number; // primary hair color
|
||||
hairShadow: number; // hair shadow band
|
||||
outfit: number; // primary garment color
|
||||
outfit2: number; // accent (ribbon, bowtie, trim)
|
||||
eye: number; // eye color
|
||||
skin: number; // face/hand
|
||||
outline: number; // dark outline (black-tinted with hair)
|
||||
earInner?: number; // cat ear inner (optional pink)
|
||||
accent?: number; // bonus accent (e.g. erang's butterfly)
|
||||
}
|
||||
|
||||
export const SISTER_PALETTES: Record<string, SisterPalette> = {
|
||||
// 하랑이 — 흰/은 머리, 파란 리본, 흰 cat ears, 파란 눈
|
||||
harang: {
|
||||
// 따뜻한 빨강 (planner / 부장 vibe)
|
||||
hair: 0x6b2424,
|
||||
outfit: 0xd84444,
|
||||
outfit2: 0x9c2828,
|
||||
skin: 0xf4cfa1,
|
||||
outline: 0x1a0808,
|
||||
hair: 0xe6ebf0,
|
||||
hairShadow: 0xa9b1ba,
|
||||
outfit: 0xf0f3f7,
|
||||
outfit2: 0x3a7bc8,
|
||||
eye: 0x2a5fb0,
|
||||
skin: 0xf4d4ad,
|
||||
outline: 0x1a1d24,
|
||||
earInner: 0xd99fb0,
|
||||
},
|
||||
// 나랑이 — 검은 short bob, 청록 보타이, 검은 cat ears, 청록 눈
|
||||
narang: {
|
||||
// 차가운 청록 (developer / 손에 코드)
|
||||
hair: 0x1f3a4a,
|
||||
outfit: 0x37b8c7,
|
||||
outfit2: 0x1d6f7c,
|
||||
skin: 0xf4cfa1,
|
||||
outline: 0x081a1f,
|
||||
hair: 0x1c1c28,
|
||||
hairShadow: 0x0a0a14,
|
||||
outfit: 0x232330,
|
||||
outfit2: 0x37b8c7,
|
||||
eye: 0x37b8c7,
|
||||
skin: 0xf4d4ad,
|
||||
outline: 0x050510,
|
||||
earInner: 0x6a3848,
|
||||
},
|
||||
// 다랑이 — 흰 머리, 빨간 chinese 옷, 흰 cat ears, 빨간 눈
|
||||
darang: {
|
||||
// 노랑 (qa / 검사관)
|
||||
hair: 0x4a3a18,
|
||||
outfit: 0xeac34a,
|
||||
outfit2: 0xa07020,
|
||||
skin: 0xf4cfa1,
|
||||
outline: 0x1f1808,
|
||||
hair: 0xe6ebf0,
|
||||
hairShadow: 0xa9b1ba,
|
||||
outfit: 0xc62a2a,
|
||||
outfit2: 0x6a1818,
|
||||
eye: 0xd83434,
|
||||
skin: 0xf4d4ad,
|
||||
outline: 0x180404,
|
||||
earInner: 0xd99fb0,
|
||||
},
|
||||
// 이랑이 — 흰 long hair, 흰 옷 + 파란 trim, 흰 cat ears, 파란 눈, 파란 나비
|
||||
erang: {
|
||||
// 보라 (infra / 시스템)
|
||||
hair: 0x2c1a3e,
|
||||
outfit: 0x9858d8,
|
||||
outfit2: 0x5c2c8a,
|
||||
skin: 0xf4cfa1,
|
||||
outline: 0x10081f,
|
||||
hair: 0xeef2f7,
|
||||
hairShadow: 0xc4cdd6,
|
||||
outfit: 0xf2f5fa,
|
||||
outfit2: 0x6e94c4,
|
||||
eye: 0x4a7bbf,
|
||||
skin: 0xf4d4ad,
|
||||
outline: 0x1a1d24,
|
||||
earInner: 0xd99fb0,
|
||||
accent: 0x5fa8e6, // 파란 나비
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper: write a 2D pixel grid into a Phaser Graphics object so it can be
|
||||
* generated as a texture. Each cell of `grid` is a hex color or -1 for
|
||||
* transparent.
|
||||
*
|
||||
* The grid is row-major: grid[y][x].
|
||||
* Helper: write a 2D pixel grid into a Phaser Graphics object.
|
||||
* grid[y][x] is the hex color or -1 for transparent.
|
||||
*/
|
||||
function paintGrid(
|
||||
g: Phaser.GameObjects.Graphics,
|
||||
@@ -82,93 +106,142 @@ function paintGrid(
|
||||
}
|
||||
}
|
||||
|
||||
interface FrameOpts {
|
||||
sister: keyof typeof SISTER_PALETTES;
|
||||
pal: SisterPalette;
|
||||
/** 0 = neutral, 1 = breath (1px down) */
|
||||
bob: 0 | 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a simple character sprite (16x24) for one sister, looking down,
|
||||
* 2 frames of idle (slight bob).
|
||||
*
|
||||
* Layout (16x24, frame 0):
|
||||
* row 0-1 : empty
|
||||
* row 2-7 : hair / head
|
||||
* row 8-9 : face row
|
||||
* row 10-15 : body / outfit
|
||||
* row 16-19 : legs
|
||||
* row 20-23 : feet / shadow base
|
||||
* Generate a single 16x28 sister frame.
|
||||
*/
|
||||
function makeSisterFrame(
|
||||
scene: Phaser.Scene,
|
||||
pal: SisterPalette,
|
||||
frameIdx: 0 | 1,
|
||||
): number[][] {
|
||||
const _ = -1; // transparent
|
||||
function makeSisterFrame({ sister, pal, bob }: FrameOpts): number[][] {
|
||||
const _ = -1;
|
||||
const O = pal.outline;
|
||||
const H = pal.hair;
|
||||
const HS = pal.hairShadow;
|
||||
const S = pal.skin;
|
||||
const C = pal.outfit;
|
||||
const C2 = pal.outfit2;
|
||||
const F = 0x000000;
|
||||
const E = pal.eye;
|
||||
const EI = pal.earInner ?? 0xd99fb0;
|
||||
const A = pal.accent ?? -1;
|
||||
|
||||
// Subtle vertical bob: frame 1 shifts the entire body 1px down by adding
|
||||
// an extra blank row at the top and dropping the last row. This is the
|
||||
// simplest "breathing" idle.
|
||||
const offset = frameIdx === 1 ? 1 : 0;
|
||||
const totalH = CHAR_H;
|
||||
|
||||
// Build a base grid (16x22 for the actual character — leaves 2 rows for
|
||||
// shadow / breathing slack)
|
||||
const grid: number[][] = Array.from({ length: totalH }, () =>
|
||||
// Initial empty grid
|
||||
const grid: number[][] = Array.from({ length: CHAR_H }, () =>
|
||||
Array.from({ length: CHAR_W }, () => _),
|
||||
);
|
||||
|
||||
function row(y: number, cells: number[]): void {
|
||||
if (y >= 0 && y < totalH) {
|
||||
for (let x = 0; x < CHAR_W && x < cells.length; x++) {
|
||||
grid[y]![x] = cells[x]!;
|
||||
}
|
||||
const yOffset = bob === 1 ? 1 : 0;
|
||||
const set = (x: number, y: number, c: number) => {
|
||||
const yy = y + yOffset;
|
||||
if (yy < 0 || yy >= CHAR_H || x < 0 || x >= CHAR_W) return;
|
||||
grid[yy]![x] = c;
|
||||
};
|
||||
const setRow = (y: number, cells: Array<number | -1>) => {
|
||||
for (let x = 0; x < cells.length && x < CHAR_W; x++) {
|
||||
const c = cells[x]!;
|
||||
if (c !== -1) set(x, y, c);
|
||||
}
|
||||
};
|
||||
|
||||
// ── row 0-1: cat ear tips (left ear cols 4-5, right ear cols 10-11) ──
|
||||
// Ear outline + hair fill
|
||||
set(4, 0, O); set(5, 0, O);
|
||||
set(10, 0, O); set(11, 0, O);
|
||||
set(3, 1, O); set(4, 1, H); set(5, 1, EI); set(6, 1, O);
|
||||
set(9, 1, O); set(10, 1, EI); set(11, 1, H); set(12, 1, O);
|
||||
set(4, 2, O); set(5, 2, H); set(6, 2, H);
|
||||
set(9, 2, H); set(10, 2, H); set(11, 2, O);
|
||||
|
||||
// ── row 2-6: hair top + face dome ──
|
||||
setRow(2, [_, _, _, O, H, H, H, O, O, H, H, H, O, _, _, _]);
|
||||
setRow(3, [_, _, O, H, H, H, H, H, H, H, H, H, H, O, _, _]);
|
||||
setRow(4, [_, _, O, H, HS, HS, H, H, H, H, HS, HS, H, O, _, _]);
|
||||
setRow(5, [_, _, O, H, HS, S, S, S, S, S, S, HS, H, O, _, _]);
|
||||
// ── row 6-9: face (eyes) ──
|
||||
setRow(6, [_, _, O, H, S, S, S, S, S, S, S, S, H, O, _, _]);
|
||||
setRow(7, [_, _, O, H, S, E, S, S, S, S, E, S, H, O, _, _]);
|
||||
setRow(8, [_, _, O, H, S, S, S, S, S, S, S, S, H, O, _, _]);
|
||||
setRow(9, [_, _, _, O, S, S, S, O, O, S, S, S, O, _, _, _]);
|
||||
// hair side bangs (long hair) — overrides for narang's short bob handled below
|
||||
setRow(10, [_, _, O, H, _, _, _, _, _, _, _, _, H, O, _, _]);
|
||||
|
||||
// narang has short hair → no side bangs reaching the shoulders
|
||||
if (sister !== 'narang') {
|
||||
setRow(11, [_, O, H, H, _, _, _, _, _, _, _, _, H, H, O, _]);
|
||||
} else {
|
||||
setRow(11, [_, _, O, _, _, _, _, _, _, _, _, _, _, O, _, _]);
|
||||
}
|
||||
|
||||
void scene; // not used directly here — Phaser texture is created separately
|
||||
void F;
|
||||
// ── row 11-12: neck + collar ──
|
||||
setRow(12, [_, _, _, _, O, S, S, S, S, S, S, O, _, _, _, _]);
|
||||
|
||||
// Apply offset
|
||||
const r = (n: number) => n + offset;
|
||||
// ── row 13-18: torso (outfit) ──
|
||||
setRow(13, [_, _, _, O, C, C, C, C, C, C, C, C, O, _, _, _]);
|
||||
setRow(14, [_, _, O, C, C, C2, C2, C2, C2, C2, C2, C, C, O, _, _]);
|
||||
setRow(15, [_, _, O, C, C, C2, C2, C2, C2, C2, C2, C, C, O, _, _]);
|
||||
setRow(16, [_, _, O, C, C, C, C, C, C, C, C, C, C, O, _, _]);
|
||||
setRow(17, [_, O, C, C, C, C, C, C, C, C, C, C, C, C, O, _]);
|
||||
setRow(18, [_, O, C, S, C, C, C, C, C, C, C, C, S, C, O, _]);
|
||||
|
||||
// hair top (5 rows of hair, row 1-5)
|
||||
row(r(1), [_, _, _, _, O, O, O, O, O, O, O, O, _, _, _, _]);
|
||||
row(r(2), [_, _, _, O, H, H, H, H, H, H, H, H, O, _, _, _]);
|
||||
row(r(3), [_, _, O, H, H, H, H, H, H, H, H, H, H, O, _, _]);
|
||||
row(r(4), [_, _, O, H, H, H, H, H, H, H, H, H, H, O, _, _]);
|
||||
row(r(5), [_, _, O, H, H, S, S, S, S, S, S, H, H, O, _, _]);
|
||||
// face (eyes/mouth)
|
||||
row(r(6), [_, _, O, H, S, S, O, S, S, O, S, S, H, O, _, _]);
|
||||
row(r(7), [_, _, O, H, S, S, S, S, S, S, S, S, H, O, _, _]);
|
||||
row(r(8), [_, _, O, H, S, S, S, O, O, S, S, S, H, O, _, _]);
|
||||
row(r(9), [_, _, _, O, S, S, S, S, S, S, S, S, O, _, _, _]);
|
||||
// neck
|
||||
row(r(10), [_, _, _, _, O, S, S, S, S, S, S, O, _, _, _, _]);
|
||||
// outfit upper
|
||||
row(r(11), [_, _, O, C, C, C, C, C, C, C, C, C, C, O, _, _]);
|
||||
row(r(12), [_, O, C, C, C, C2, C2, C2, C2, C2, C2, C, C, C, O, _]);
|
||||
row(r(13), [_, O, C, C, C, C2, C2, C2, C2, C2, C2, C, C, C, O, _]);
|
||||
row(r(14), [_, O, C, C, C, C, C, C, C, C, C, C, C, C, O, _]);
|
||||
row(r(15), [_, O, C, C, C, C, C, C, C, C, C, C, C, C, O, _]);
|
||||
// arms
|
||||
row(r(16), [_, O, C, S, S, C2, C2, C2, C2, C2, C2, S, S, C, O, _]);
|
||||
row(r(17), [_, O, S, S, S, C2, C2, C2, C2, C2, C2, S, S, S, O, _]);
|
||||
// belt
|
||||
row(r(18), [_, _, O, C2, C2, C2, C2, C2, C2, C2, C2, C2, C2, O, _, _]);
|
||||
// legs
|
||||
row(r(19), [_, _, _, O, C2, C2, _, _, _, _, C2, C2, O, _, _, _]);
|
||||
row(r(20), [_, _, _, O, C2, C2, _, _, _, _, C2, C2, O, _, _, _]);
|
||||
row(r(21), [_, _, _, O, F, F, _, _, _, _, F, F, O, _, _, _]);
|
||||
row(r(22), [_, _, _, _, O, O, _, _, _, _, O, O, _, _, _, _]);
|
||||
// ── row 19-20: arms / hands ──
|
||||
setRow(19, [_, O, S, S, C, C, C, C, C, C, C, C, S, S, O, _]);
|
||||
setRow(20, [_, _, O, _, O, C2, C2, C2, C2, C2, C2, O, _, O, _, _]);
|
||||
|
||||
// ── row 21-24: legs (dark) ──
|
||||
const L = pal.outfit2;
|
||||
const LD = pal.outline;
|
||||
setRow(21, [_, _, _, O, L, L, _, _, _, _, L, L, O, _, _, _]);
|
||||
setRow(22, [_, _, _, O, L, L, _, _, _, _, L, L, O, _, _, _]);
|
||||
setRow(23, [_, _, _, O, L, L, _, _, _, _, L, L, O, _, _, _]);
|
||||
setRow(24, [_, _, _, O, L, L, _, _, _, _, L, L, O, _, _, _]);
|
||||
|
||||
// ── row 25-26: shoes ──
|
||||
setRow(25, [_, _, _, O, LD, LD, _, _, _, _, LD, LD, O, _, _, _]);
|
||||
setRow(26, [_, _, _, _, O, O, _, _, _, _, O, O, _, _, _, _]);
|
||||
|
||||
// ── row 27: ground shadow ──
|
||||
setRow(27, [_, _, _, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, _, _, _]);
|
||||
|
||||
// ── per-sister extras ──
|
||||
|
||||
// darang: 이마 빨간 점 (forehead gem)
|
||||
if (sister === 'darang') {
|
||||
set(7, 5, 0xff5050);
|
||||
set(8, 5, 0xff5050);
|
||||
}
|
||||
|
||||
// erang: 옆에 작은 파란 나비
|
||||
if (sister === 'erang' && A !== -1) {
|
||||
// small butterfly to the right of head
|
||||
set(13, 3, A);
|
||||
set(14, 3, A);
|
||||
set(13, 4, A);
|
||||
set(14, 2, A);
|
||||
}
|
||||
|
||||
// harang: 작은 파란 리본 머리에 (옅은)
|
||||
if (sister === 'harang') {
|
||||
set(2, 3, 0x4a8fd4);
|
||||
set(3, 3, 0x4a8fd4);
|
||||
set(2, 4, 0x6ba0d8);
|
||||
}
|
||||
|
||||
// narang: 옆머리에 청록 헤어핀
|
||||
if (sister === 'narang') {
|
||||
set(13, 4, 0x37b8c7);
|
||||
set(13, 5, 0x37b8c7);
|
||||
}
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a tile texture (16x16) representing a wood floor plank.
|
||||
* Tile generators (16x16 each)
|
||||
*/
|
||||
|
||||
function makeFloorTile(): number[][] {
|
||||
const _ = 0xc4955a;
|
||||
const D = 0x8a6438;
|
||||
@@ -176,12 +249,10 @@ function makeFloorTile(): number[][] {
|
||||
const grid: number[][] = Array.from({ length: TILE_SIZE }, () =>
|
||||
Array.from({ length: TILE_SIZE }, () => _),
|
||||
);
|
||||
// horizontal plank lines
|
||||
for (let x = 0; x < TILE_SIZE; x++) {
|
||||
grid[3]![x] = D;
|
||||
grid[10]![x] = D;
|
||||
}
|
||||
// light highlights
|
||||
for (let x = 0; x < TILE_SIZE; x++) {
|
||||
if ((x + 1) % 4 === 0) {
|
||||
grid[1]![x] = L;
|
||||
@@ -199,7 +270,6 @@ function makeWallTile(): number[][] {
|
||||
const grid: number[][] = Array.from({ length: TILE_SIZE }, () =>
|
||||
Array.from({ length: TILE_SIZE }, () => _),
|
||||
);
|
||||
// brick pattern
|
||||
for (let y = 0; y < TILE_SIZE; y++) {
|
||||
grid[y]![TILE_SIZE - 1] = D;
|
||||
}
|
||||
@@ -207,12 +277,10 @@ function makeWallTile(): number[][] {
|
||||
grid[TILE_SIZE - 1]![x] = D;
|
||||
grid[0]![x] = L;
|
||||
}
|
||||
// brick seam every 4 rows
|
||||
for (let x = 0; x < TILE_SIZE; x++) {
|
||||
grid[7]![x] = D;
|
||||
grid[15]![x] = D;
|
||||
}
|
||||
// staggered vertical seams
|
||||
for (let y = 0; y < 7; y++) grid[y]![7] = D;
|
||||
for (let y = 8; y < 16; y++) grid[y]![3] = D;
|
||||
for (let y = 8; y < 16; y++) grid[y]![11] = D;
|
||||
@@ -236,7 +304,6 @@ function makeCarpetTile(): number[][] {
|
||||
}
|
||||
|
||||
function makeDeskTile(): number[][] {
|
||||
// 32x16 desk drawn into a 16x16 cell — top half table, bottom half legs
|
||||
const T = 0x6b4628;
|
||||
const D = 0x3a2614;
|
||||
const L = 0x8a5e36;
|
||||
@@ -244,7 +311,6 @@ function makeDeskTile(): number[][] {
|
||||
const grid: number[][] = Array.from({ length: TILE_SIZE }, () =>
|
||||
Array.from({ length: TILE_SIZE }, () => _),
|
||||
);
|
||||
// tabletop
|
||||
for (let x = 0; x < TILE_SIZE; x++) {
|
||||
grid[2]![x] = D;
|
||||
grid[3]![x] = T;
|
||||
@@ -252,12 +318,10 @@ function makeDeskTile(): number[][] {
|
||||
grid[5]![x] = L;
|
||||
grid[6]![x] = D;
|
||||
}
|
||||
// monitor (small screen on top, lighter)
|
||||
for (let y = 7; y < 11; y++) {
|
||||
for (let x = 5; x < 11; x++) grid[y]![x] = 0x1a3a5a;
|
||||
}
|
||||
for (let x = 5; x < 11; x++) grid[11]![x] = 0x6e6e7c;
|
||||
// legs (bottom)
|
||||
for (let y = 12; y < TILE_SIZE; y++) {
|
||||
grid[y]![1] = D;
|
||||
grid[y]![14] = D;
|
||||
@@ -272,7 +336,6 @@ function makeChairTile(): number[][] {
|
||||
const grid: number[][] = Array.from({ length: TILE_SIZE }, () =>
|
||||
Array.from({ length: TILE_SIZE }, () => _),
|
||||
);
|
||||
// backrest
|
||||
for (let y = 2; y < 8; y++) {
|
||||
for (let x = 5; x < 11; x++) grid[y]![x] = C;
|
||||
}
|
||||
@@ -280,12 +343,10 @@ function makeChairTile(): number[][] {
|
||||
grid[y]![5] = D;
|
||||
grid[y]![10] = D;
|
||||
}
|
||||
// seat
|
||||
for (let x = 4; x < 12; x++) {
|
||||
grid[8]![x] = D;
|
||||
grid[9]![x] = C;
|
||||
}
|
||||
// legs
|
||||
grid[10]![5] = D;
|
||||
grid[10]![10] = D;
|
||||
grid[11]![5] = D;
|
||||
@@ -293,9 +354,6 @@ function makeChairTile(): number[][] {
|
||||
return grid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a grid into a Phaser texture and register it under `key`.
|
||||
*/
|
||||
export function registerGridTexture(
|
||||
scene: Phaser.Scene,
|
||||
key: string,
|
||||
@@ -311,8 +369,7 @@ export function registerGridTexture(
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a 2-frame spritesheet texture for one sister.
|
||||
* The two frames are stacked vertically into one image.
|
||||
* Build a 2-frame spritesheet for one sister.
|
||||
*/
|
||||
export function registerSisterTexture(
|
||||
scene: Phaser.Scene,
|
||||
@@ -321,30 +378,22 @@ export function registerSisterTexture(
|
||||
const key = `sister-${sister}`;
|
||||
if (scene.textures.exists(key)) return;
|
||||
const pal = SISTER_PALETTES[sister];
|
||||
const f0 = makeSisterFrame(scene, pal, 0);
|
||||
const f1 = makeSisterFrame(scene, pal, 1);
|
||||
// stack vertically
|
||||
|
||||
const f0 = makeSisterFrame({ sister, pal, bob: 0 });
|
||||
const f1 = makeSisterFrame({ sister, pal, bob: 1 });
|
||||
|
||||
// Stack vertically
|
||||
const combined: number[][] = [...f0, ...f1];
|
||||
const g = scene.add.graphics({ x: 0, y: 0 });
|
||||
paintGrid(g, combined, 1);
|
||||
g.generateTexture(key, CHAR_W, CHAR_H * 2);
|
||||
g.destroy();
|
||||
|
||||
// register as a spritesheet so we can play it as an animation
|
||||
const tex = scene.textures.get(key);
|
||||
tex.add(0, 0, 0, 0, CHAR_W, CHAR_H);
|
||||
tex.add(1, 0, 0, CHAR_H, CHAR_W, CHAR_H);
|
||||
|
||||
if (!scene.anims.exists(`${key}-idle`)) {
|
||||
// We didn't actually create proper frames via spritesheet — instead,
|
||||
// create a manual animation by swapping textures using `addKey`. Since
|
||||
// Phaser anims need spritesheet/atlas frames, we re-register the
|
||||
// texture as a spritesheet:
|
||||
scene.textures.remove(key);
|
||||
const g2 = scene.add.graphics({ x: 0, y: 0 });
|
||||
paintGrid(g2, combined, 1);
|
||||
g2.generateTexture(key, CHAR_W, CHAR_H * 2);
|
||||
g2.destroy();
|
||||
// Configure the texture frames manually
|
||||
const tex = scene.textures.get(key);
|
||||
tex.add(0, 0, 0, 0, CHAR_W, CHAR_H);
|
||||
tex.add(1, 0, 0, CHAR_H, CHAR_W, CHAR_H);
|
||||
scene.anims.create({
|
||||
key: `${key}-idle`,
|
||||
frames: [
|
||||
|
||||
Reference in New Issue
Block a user