From 9a42e0eaf8df2a1f6e1a1fb19cf5cee72e07e583 Mon Sep 17 00:00:00 2001 From: type-two Date: Tue, 14 Jul 2026 23:39:40 +1000 Subject: [PATCH] Step 7: wire FLUX sprites into the game Chroma-key magenta -> alpha, autocrop, downscale (assets/process_sprites.py -> public/sprites/). GameScene loads image textures instead of drawing primitives: tiled darkened floor, crate walls, digger/poser/nerd/sound-guy/booth/pickup sprites. Bodies sized under the 32px sprites so corridors still fit. Mechanics verified unchanged (combat, trainspot, level flow). Co-Authored-By: Claude Fable 5 --- assets/collect.py | 33 ++++++++++++++++++ assets/process_sprites.py | 62 ++++++++++++++++++++++++++++++++++ public/sprites/airhorn.png | Bin 0 -> 823 bytes public/sprites/door.png | Bin 0 -> 969 bytes public/sprites/espresso.png | Bin 0 -> 1488 bytes public/sprites/exit.png | Bin 0 -> 2191 bytes public/sprites/floor.png | Bin 0 -> 8238 bytes public/sprites/generator.png | Bin 0 -> 2567 bytes public/sprites/lanyard.png | Bin 0 -> 912 bytes public/sprites/nerd.png | Bin 0 -> 1858 bytes public/sprites/player.png | Bin 0 -> 2341 bytes public/sprites/poser.png | Bin 0 -> 2155 bytes public/sprites/soundguy.png | Bin 0 -> 1986 bytes public/sprites/vinyl.png | Bin 0 -> 632 bytes public/sprites/wall.png | Bin 0 -> 1868 bytes src/GameScene.js | 63 +++++++++++------------------------ 16 files changed, 115 insertions(+), 43 deletions(-) create mode 100644 assets/collect.py create mode 100644 assets/process_sprites.py create mode 100644 public/sprites/airhorn.png create mode 100644 public/sprites/door.png create mode 100644 public/sprites/espresso.png create mode 100644 public/sprites/exit.png create mode 100644 public/sprites/floor.png create mode 100644 public/sprites/generator.png create mode 100644 public/sprites/lanyard.png create mode 100644 public/sprites/nerd.png create mode 100644 public/sprites/player.png create mode 100644 public/sprites/poser.png create mode 100644 public/sprites/soundguy.png create mode 100644 public/sprites/vinyl.png create mode 100644 public/sprites/wall.png diff --git a/assets/collect.py b/assets/collect.py new file mode 100644 index 0000000..b8dfd77 --- /dev/null +++ b/assets/collect.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +"""Copy finished FLUX assets out of MODELBEAST into assets/generated/.png. + +Reads queue.lock.json (job_id/name/seed written by enqueue.py), finds each +generated image in the MB asset store by its seed-based filename, and copies it +in under the friendly asset name. Run after the MB jobs finish. + + /opt/homebrew/bin/uv run python assets/collect.py +""" +import json, sys, shutil +from pathlib import Path + +MB = Path.home() / "MODELBEAST" +sys.path.insert(0, str(MB)) +import server.db as db # noqa: E402 + +HERE = Path(__file__).resolve().parent +lock = json.loads((HERE / "queue.lock.json").read_text()) +outdir = HERE / "generated" +outdir.mkdir(exist_ok=True) + +con = db.connect() +done = missing = 0 +for it in lock: + fname = f"flux_flux2-klein-4b_s{it['seed']}.png" + row = con.execute("SELECT path FROM assets WHERE name=? ORDER BY created_at DESC LIMIT 1", (fname,)).fetchone() + if row and Path(row["path"]).exists(): + shutil.copy(row["path"], outdir / f"{it['name']}.png") + done += 1 + else: + print(f" not ready: {it['name']}") + missing += 1 +print(f"collected {done}/{len(lock)} into {outdir}" + (f" ({missing} not ready)" if missing else "")) diff --git a/assets/process_sprites.py b/assets/process_sprites.py new file mode 100644 index 0000000..a2fdcf1 --- /dev/null +++ b/assets/process_sprites.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Turn the raw FLUX assets into game-ready textures in public/sprites/. + +Sprites (magenta #ff00ff background) get chroma-keyed to transparency, autocropped, +and centered on a square canvas. Tiles (full-bleed textures) are just resized square. +Output filename = the texture key GameScene.js loads. + + /opt/homebrew/bin/uv run --with pillow --with numpy python assets/process_sprites.py +""" +from pathlib import Path +from PIL import Image +import numpy as np + +HERE = Path(__file__).resolve().parent +SRC = HERE / "generated" +OUT = HERE.parent / "public" / "sprites" +OUT.mkdir(parents=True, exist_ok=True) + +# game texture key -> (source file, size px, is_tile) +TABLE = { + "floor": ("floor_parquet.png", 64, True), + "wall": ("wall_crate.png", 32, True), + "player": ("player_digger.png", 32, False), + "poser": ("enemy_poser.png", 32, False), + "nerd": ("enemy_gearnerd.png", 32, False), + "soundguy": ("enemy_soundguy.png", 32, False), + "generator": ("gen_discount_bin.png", 32, False), + "exit": ("prop_djbooth.png", 32, False), + "door": ("prop_velvet_rope.png", 32, False), + "espresso": ("pickup_espresso.png", 26, False), + "lanyard": ("pickup_lanyard.png", 26, False), + "airhorn": ("pickup_airhorn.png", 26, False), + "vinyl": ("pickup_vinyl.png", 18, False), +} + + +def chroma_key(im): + arr = np.array(im.convert("RGBA")) + r, g, b = arr[..., 0].astype(int), arr[..., 1].astype(int), arr[..., 2].astype(int) + # magenta: high red+blue, low green, red~=blue (catches the darker shadow halo too) + mask = (r > 135) & (b > 135) & (g < 100) & (np.abs(r - b) < 70) + arr[..., 3][mask] = 0 + return Image.fromarray(arr, "RGBA") + + +for key, (fname, size, is_tile) in TABLE.items(): + im = Image.open(SRC / fname).convert("RGBA") + if is_tile: + im = im.resize((size, size), Image.LANCZOS) + else: + im = chroma_key(im) + bbox = im.getbbox() + if bbox: + im = im.crop(bbox) + im.thumbnail((size, size), Image.LANCZOS) + canvas = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + canvas.paste(im, ((size - im.width) // 2, (size - im.height) // 2), im) + im = canvas + im.save(OUT / f"{key}.png") + print(f"{key:10s} <- {fname:24s} {size}px {'tile' if is_tile else 'keyed'}") + +print(f"\nwrote {len(TABLE)} textures to {OUT}") diff --git a/public/sprites/airhorn.png b/public/sprites/airhorn.png new file mode 100644 index 0000000000000000000000000000000000000000..57dd4e6439eea8d3cd723f2c08a531626a6c2ad9 GIT binary patch literal 823 zcmV-71IYY|P)Chsgd{DJsvvHqMeIftK}Ah>jo5`kHwFCzic4|fqJp?{ z;X*|rxUyYz(+Co%KS&gR3<+XPV%kh*=DmB*DfeZjiD~A|_>KPW;toZ8<7bP67(FFVwpZ7AKq{MyUww9VtGc?BrOVG++!T4| zd}Xg6Z`w-z3V_#Cjd-$w)0h0pcw_qVn7>hoHE>Xe!x z)@0OEGs=x$hgsh)MK6|1!!yI-z+bsS*DEsH8wH}zzQBd~SKxp8B`^da0Mr6PfC$N; zZOjUL->No7a)AuNSg23ib!ZR*JsuD-U=n~_Yyp9?VI-TT8bhpPfwUR$^}Y)Os#JQu?*)|o-G1q^*R?&5h&XMm8pnIh z(;XLZbL!3^0YQvQBZEj>LUXpDhTUC>5(Oe(0#Q&Rpf~}?0^cb6Mhm`Cn1fuhTVR<;jW8*yc&E+@eYLy@7vp|`FEr!RQn3$Yd zR7ng&Lt_P7y8+=OmdJfm2v<8002ovPDHLkV1gL> Bi3k7y literal 0 HcmV?d00001 diff --git a/public/sprites/door.png b/public/sprites/door.png new file mode 100644 index 0000000000000000000000000000000000000000..5484e5f44e15a95c91169cfbe6509dbcbb0e3eda GIT binary patch literal 969 zcmV;)12+7LP)Wt%(^<`N9?q#x$U-wDGSy{*=lWMV*RWjA`~^HA0?FNkDxHI&=4Xl ziIAWq2&^9x{ZPm$Xlq;B%wnr0gJ^@@W!-IO=YE{?#+jYTnVl)9RV0`P4)=23_kGTD z&V9~%fEunf7}&LzP(uwh)KCe|03iT&6?zoAirjMt%d28&Pe)hodO+@PSrTorAK4X< zYdP##w%A#|Wuw^FyfoTeJrN@M>N5>Qzurc>ACbRb{yvAvOI}~vmGJ1tRkZg}hZt0p zS0r=jkG`rO+5dVx+xJU-s{78B$50;^1VH=;6}HCKBDO%>(Y5;Ur$4yPb4MGqr@wZ7 z;|zw+HlHy%xHvR&IH7;48ycKCmR@mp=h55O4#b$4uk%bmh3YCO=GdA9SB-CUb_^`< z=-D`Ue(dd(GgLP^7QU=0@XOT}sy5srpPc-%jXK+~4Twe##^m7nn4on|v>Q-m`yc?w z!qbj&DAwb+n=T|I9g53-z;W(f!=Jd6Nz$4d`3{>QtIKC%%1Dr8DIn)Pn^o<7itjt( zhNlM zg*kqA`oD;qEce^_qDz{xPHgIggL0_;k1o@HS$TDnC3Uu~00 zNN2>@e6KWA9xDwLxV_MFb9f#+BD#E{>?GO%sm9pS*6ZCJ*=e1-A#EOPO4YX*Q{KC& zu&Pq>e~J~kuS>?7+nd$a>1py?o5aqAiTcH}TMK*l#YMxu7p8J<(x3Qw&}mFW#v z76SGM&IOX6Jdn6FI+>kE?)=b<4@(u9=g;aX0{sIMW?0G!dpZQ?(^tR2hUROn`UmE`cMeaIBruS}L r)SG#Kz4vfW^sEwYdam3x{QvPMB$rN*+nq@s00000NkvXXu0mjfC*<9f literal 0 HcmV?d00001 diff --git a/public/sprites/espresso.png b/public/sprites/espresso.png new file mode 100644 index 0000000000000000000000000000000000000000..5d1d7abc1c45d1e92c34bbd69b086cced689d0cd GIT binary patch literal 1488 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 literal 0 HcmV?d00001 diff --git a/public/sprites/generator.png b/public/sprites/generator.png new file mode 100644 index 0000000000000000000000000000000000000000..10211065afafe17963c1f31df30ac81c3dcda92f GIT binary patch literal 2567 zcmV+i3i$PjP)45875?sh%gmec%y`Dzc;6j6c4Eg#APa#s4uk+9fGCt8sx}HONL4{?r3#^nN>x$+ z6r>hZ+EOAY1%5!-X<|eu2@P>w?AXqN<1BVO9xvnBm-pr^_f>Dk6C6mZ{^yMDymRk8 z@1EtJbMNyrtyo29Vt3FZXQuSBKxZKCWn} zE^{NR7+6fCAqge~(GJz5;Guznfg=Q|jGBAj>yi*kD3C-EE~g0708%L#WhHK$S;$;` z^tpr0gb;JN0sL2$m${3ow{%vm|9tGCVhW<&Cd!yi2f>%RF+5X&!NC(~>FR>ZZU;{c zcti$XaG}yhFck>F)7Fh}DgXpRXex6cr5Q*DqnYbg)i>6adpj;IL{Be|0xcLomtt|l zs+tFFJO$GkqOmkWdIseyY7rOuAO|bpaCsovbi`v3n1UTC(}~%ThI1hffrx;bhFaWm z=S~!LHQ;R2Kq{>Ogo!3&xcB{Hh9|_@j>QR zTsj>`N0)|s_uhlf@+!>Fr?3!~p@O2QvJ5`I3tq2;fuW-ao;PvJs_U@k=3c~#+=!;K z{M>v*-@c~q4jV7HE$_*41E`gK09MwNt=ZgOReb7VP_c`Dwtw(b!aIzn-kW;rj0FNi3M1aKR75nJpMwpCZ7x6FgLXQD9e z0!oX3whExG-ihXxVvLTBAs8Yk@i*X|@d5nyh4e476gdMM2ipUgdL)i#=XU}sCU=lnA?wm@o|*OQ7G#>vAekvtyMnI zA|D!CT5;%|58-iAbhVb^Q?(Jd?!6!9KX?mM7ZR9VSVVMkl8;rk=^Hz$H#qI~lC+|P zEx&my{Dx^_!;0#ijTIhza$!~{l<=HLAS89nrd0Hg1h7~>f>KXC+#T)sL474$4hdmX zLe1hOOnr77PrYyuS~iK!dOuEA)k0~y5u1AZ@#(9_F>m}EHl`z%$?C1m62Ez6?d|V@IYV; zH*iV(oq92~%?|$-0o!&r;+EcX*s=FI93KzjO3VBlxXNAcHiBnrmEK;ehKE;_rTijMxzScH8+N6R>xy! zDE8Yz_*UOKh~Xw=D-v+IM$vF~7=4@8;@#mi2EH)h_t!w>L#oXnj1g+xC43~FrdwAv z{3uri9!c_F(_Xdd!fb@`0>_&f0s{tgO<;rR8G1xdQ1|^eVb#iJn6G_?p@Av<4K?^s z4na8@!S0G8T+`Ez&sYpxg$~oL;NhCZT?<@s~sRH2gYRTP}f|2qQ6PV9Kr^0Z!oI{LOoiJ{iZ{iZb|Dw}6^y z%!T8)Zo>xb+T%y3vcz8gdra1id{eNP9dJ+kRK@de0 z7ddEmbJ_3t7yHLH3LO1p@6J`Cs+;QZlQUv^MGeH&^$?VxiHegJZ|6PMtPtXMt=lh8ws+x${t*nFoWQo$b?E7+ z)r%!jeE#6b?Ee0fJC_b&>gik4@aKnib$@$pYo#tnW&WUQBA_{N@#ri-?cjRKv9-4t z>dFQr-#Ce(Lx=J3gDue3@K6RAT3<;bLq&{y&xF(~Vc-v8lpJ0_#{*mN@_~Nbb!`*Y zwpFsR$q;?+-H|^YAD?~{fI4vms+_hQ65^e9 z*sF@cr#0x2FdRAuwVc3Z@X&)ARNjQ{0#clbWH1W}3@HGsYKtutrOt%n{9DH-pP3FP z9w{I&Qn-sO8$^zie!8nWQ|fl;8BOP%JcT6kpd1CqQ5d=b!8E}*3UEBqstVp9a5?Re zVIZ5;Aesb*&QRpCAug++j3JXXKwzpOA>tEn4AsYEv*ry3U1wkbbgR!8G?pJ{$Um(y zT{qRFqT$HtISUb4Hh&!vnM=4ZnN=o-&M%y>3%tlo!bm~f60rDX&HCls&BbLeA>e0(rRme^cJr-1190m}#m4NBO~t zg#*kqQn{?ZS_CXA?8|gG`Qi9XEMv2AV!_%k|F72aWbOau-jW`UP?l13oR|zhi>na* z+5jP7VA*3|%>Bvg8wL~$Ffi|#tq(elX(0f%v8m|q^3MHt d!|{!!{{WBv!v1N$Cz${M002ovPDHLkV1nBw-C_U$ literal 0 HcmV?d00001 diff --git a/public/sprites/lanyard.png b/public/sprites/lanyard.png new file mode 100644 index 0000000000000000000000000000000000000000..a04818aa2db40b1bd952d4b2e12f42bc519cfc92 GIT binary patch literal 912 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 literal 0 HcmV?d00001 diff --git a/public/sprites/player.png b/public/sprites/player.png new file mode 100644 index 0000000000000000000000000000000000000000..d0605c728ff99758039e9794daf721db93d08c66 GIT binary patch literal 2341 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 literal 0 HcmV?d00001 diff --git a/public/sprites/soundguy.png b/public/sprites/soundguy.png new file mode 100644 index 0000000000000000000000000000000000000000..62020748a1b892fa3f57f1b5056e3576704f1fef GIT binary patch literal 1986 zcmV;z2R-1SFsfH3TELP!xlUAOsO?w_*?p1QJBW zfE&S$U?2;@WyggyWZ1Y7lW3A2-tfQoPj{+YgVgPg-JQI=eyurhr=sedQ|Fxe&Ub-Z z6ajubkMpy~CMVuJxp}AETszTZarO*>?!^Bf>{ccUiXI=1C(jPYV{k66uJ&-&qAUvR zZeMu3*7Ywxa0dtsoT(r7hR2>7tq(L2BY24GRvRL2%A$<7F8urZ+Sb4MKpo(W#z{Py z4kwc`ibTrTyOwp|-dW@I?&YnxET#hsw>aA7jv&Ou*Mv-*clOGB4|sp$9L5-hR0y6p zLGqEK=LJ!C3P*BhlWM6p1xpTSrI zMxioT7k=k0oUw2gSj}p*W{G9Fs{ph&*MEov1CpXgC6D1@HCKc^-N2DRtJ`kl# zF|8@lHeNW}B8p?28252%v5(nw7jNHj3YYe`5T|{dsFpa!fY(MF*xlPh-PGV*AxeZ9 zudTV|Y_9fqx9)3f^Xg3_Ks^0@Ht3n&FmWmpB1zM(>Wy)DYdXbWbc_8@oQ4;`c)X55 zp26I8H_mxrYxgp&cks?bst7WY!Z~k}US^2XnIomZxbiu!yzm|_iV}cY3Fvhk;Z+@=Svi;edN6{Ha7a$JboOlG2lda4hIPw*1+F z5#Wq(X6fY8Xk)B7huYtp1CCG>dk{ilvbhOoJ?8s!=yruTiMm`fMli;7#`8grtQTQ1 zpJBGU=akCGu@-BSbqFr?&ZQk)lshLK{Qi4tfHC^0R0_laB0>~NESFO_*Sc8sV2y=} z1xDi$(s&Hh2)H&}5|aAJ2N~i_UR6eDOhMX|$g&}njKMh}N|Ky`P99i<5lkTj;$b=R%mTv_Khg48}QngFf=!07;f2>nBL_DAY`+%e77m#$Qq*b75$;nqjfn z7eWe1o<4bC4RE0zb*{zk?q#ypcj1TgH3R#*arU$|&UFrZ4yCE9kElInxe%18Va`N7I2W4Pi)XnN?sgzrv9MhY}Z#P^Bv~BV9fhlO(>Iafy zy*}BXG>=rC_e9*!PlE9WwJBh2AS3Kb>kqd86wbqkz;xO1UH5EtYjj(|G5;Zue=6_w z>C(kFRBLJ%$4S7YXAgA1!Os{mZ9*EhFP__e`^{%m9Dm1p^KdXmBm%0vn~mHEB@D2} z3+EgdlaFx{dE4l%rmkLU>XMAEiG>P1$N|6`W9mQERYkUSf7V7nv6{`!1*fFwV7aU0 z>jLo3?V5%Tf~62bYDV-`@7(Rv?TdGDE<*hX+XfEd-#X9%-;s;vd*y2W5~t{ipxs%_ z(`c+X2}7K{XUFgOaLt7)`%^Lv7y$3tSFE>1ER&B^MX{>t)!hbGAKDFB;Gm3h@BKoM zI4(ud|)g^2oeqpDKz!-O|BZmwj?`65f{@#wR>bWqcc{&&m zI<2<>m=9z|;-JAB9Af?j&iGG^wwP{j1I$7Qv54ceAB+b=+BEgT8r%0a#3cN*gplB0 z_3Oj7KQhV03uTN#!>!euRm%8S1$`DK6cm|!FZ&YdNNH1ZIbj`#pHLxf9zrMABW4g UlfwsA!TGAme*gdg literal 0 HcmV?d00001 diff --git a/public/sprites/vinyl.png b/public/sprites/vinyl.png new file mode 100644 index 0000000000000000000000000000000000000000..9e4f03fc4efa5a54d9c46c9cce62c06d915fbc28 GIT binary patch literal 632 zcmV-;0*C#HP))R=Qh4d8g1^p<_bckaA6^9UQfU z5|jFnpaEDa1aqO@Ca5bwv?G)+wZjxZUTd5AH?B0zY1~huEL{Xigz~+17*;pK9AG-5 z8&HvGu(sZ@Uhm|GI7+0#0K?fMZ~g>;5rFcR;8E1iXms!(9g?ioT5GQ#Kyc2v1ya4ek_h_{?(BABz$R`*L4<^NA68oa)LaFzg_(M2S zhw?I_SqAeNra`PNcCWi>$c|Ze>$s&^+f<{mh?-DuhDz->vAbID3d|DNAcgr9mS?a$ zh1phWXId=-#hkiO!Lk?vz87!)^kM}sN%=${0r&)Y`_uDtnf=koU!D#D^T4!#l>wBg zK3{LDWsPn&4B6-J0vI$k^M{9Xo*smJZFtgzc7-sDV1oo^DQuEd5^{D{Y6|@0mI6J1F?pngx(2cJT>#POhu5ezbdfj4)#Q07v^7b2o%~$ zq1y^>^p#?lF(bg9zq9Wh3xO7}qN=3lZLPXR`zd4V$;^lUP z0sxS5K^FpQEnE>Xc-`Z`HLxY|zAaFy1>zm=XY?^x0HuPT;Q9ZGY02ojPC=!d;l0CQ zN*WirkiJ|^(N^@n;I>P6JS^w|biu<_qEX>WL4?EQro-!!aoGnv?Ps`JF=TJY5~3A{ zDdT3-;dU4CeIk+!x*~dy%@A=~G9phP1iaHEgUSYPIwsTr{$!`Q)$s3LBEr_;wRVtlWEs4OPZ^YxD z5J)gf4Np#zRh}>y33=sQjHE=irS))J5TnDUspTBNSxNwcci7JvUw`hg6bp_JGoYm@ zLZk%ncsOC6vrdLKcU^}l_L>Jg?q}pOYB8zmyglF>qJS=@^`c8 z#byS^3EY5_i3%YWUr=!|MC^tRrpZ3*YW#{hgZTBjH>ow-x7rPh{ILgCh{fQv&=5mJ6zd_xUjyHXiV_ z-bXx7Ck!tW4og9tl5%gRWVt2FneT`F3EZ2{ECBNle&~`kDt?Zq2{h7vFl2~1QE*Nv zFw%&GjXcck%S7wtO*`TGEVbNGl7~5IgCTfHjU3<{Be}zVG7DaEMhIPVK}KNcsbs&^ zew)M`f4=N-IR@PCXB8B}N}>@uPC70bpRPy5=<)Gl#LICJIt+>{i{+r9CEdHby1?J} z6ZW*?W~x)k=h=fp3aj6x?e+KTFSY*+4NDrzucc>Qml;Jgd4NUQIcv-ICkjp_34Cf z&!=;RWm=w&)RX<@3AkT$p7cg+e$cuetJO3gLe0GLR6NbiX#UUB5GkS{yKR;#gi^-s zCbm==TD-2ufWtI{Mb=8P9h8AL4%krUktZQ4;;JNGCAHfwm{n7*xiouTWoETqh;{ax zV+kUIB@gyE?C+yr%a&=BNyNLmFwz1*| z7pcIMvWgVDpy%TP>V;aWr3k6wtvE$8S+QGhEVkbtCM+{8)1-Hx+^rl40cPsu$Z;n% zseK4a0}I=^;aJT@h;hT6`UP4X5$hs5X2>D`SByxy?YO~B4?G-ZwOK14L?QsMQ^MWV zfME#uupO|Ul1eWFQ9W&ii*hAezg_L{#{-bgo?~QGuW_!1O#zYyE20m$-uCDk9~iZ) zc(u;f^zT1f&tfv@yJYo)k2H~Z^x zHpqfM%kX(J&tTq5b+{NK9;S?c_Q^WlU=n|vD8skwoF1$#nKdfPgSwTb3kEl5BhANM zv(i@l)E)O!hBc#-VXn9y;~B|SMHhX*4`-#A8?B9vWN2M2Z&O`<%+xi3*A2yp_Z_z}_MTa8J6df$OpeB|d`8o{+ciZD_Whf*2q(=K486x7(!PH$0{#QLbdjuL`v^|}0000.png (see assets/process_sprites.py) +const TEXTURE_KEYS = [ + 'floor', 'wall', 'player', 'poser', 'nerd', 'soundguy', 'generator', + 'exit', 'door', 'espresso', 'lanyard', 'airhorn', 'vinyl', +]; + export default class GameScene extends Phaser.Scene { constructor() { super('game'); } + preload() { + for (const key of TEXTURE_KEYS) this.load.image(key, `sprites/${key}.png`); + } + create() { this.cameras.main.setBackgroundColor(0x1a1a1a); - this.makeTextures(); // Persistent groups — created once, refilled each level so colliders survive. this.walls = this.physics.add.staticGroup(); @@ -42,6 +51,7 @@ export default class GameScene extends Phaser.Scene { this.player = this.physics.add.sprite(100, 100, 'player'); this.player.setCollideWorldBounds(true); + this.player.body.setSize(22, 22, true); // body smaller than 32px sprite -> fits corridors // Colliders / overlaps (persistent group refs). this.physics.add.collider(this.player, this.walls); @@ -109,48 +119,6 @@ export default class GameScene extends Phaser.Scene { this.showOverlay('VINYL GAUNTLET\n\npress any key'); } - makeTextures() { - const tex = (key, w, h, draw) => { - const g = this.make.graphics({ x: 0, y: 0, add: false }); - draw(g); - g.generateTexture(key, w, h); - g.destroy(); - }; - tex('wall', TILE, TILE, (g) => { - g.fillStyle(0x6b4423).fillRect(0, 0, TILE, TILE); - g.lineStyle(2, 0x3d2814).strokeRect(1, 1, TILE - 2, TILE - 2); - }); - tex('door', TILE, TILE, (g) => { - g.fillStyle(0xb02020).fillRect(0, 0, TILE, TILE); - g.lineStyle(2, 0x600).strokeRect(1, 1, TILE - 2, TILE - 2); - }); - tex('espresso', 24, 24, (g) => g.fillStyle(0x3ad13a).fillRect(0, 0, 24, 24)); - tex('lanyard', 24, 24, (g) => g.fillStyle(0xffd11a).fillRect(0, 0, 24, 24)); - tex('airhorn', 24, 24, (g) => { - g.fillStyle(0xffffff).fillRect(0, 0, 24, 24); - g.lineStyle(2, 0x888888).strokeRect(1, 1, 22, 22); - }); - tex('generator', 30, 30, (g) => { - g.fillStyle(0x9b30ff).fillRect(0, 0, 30, 30); - g.lineStyle(2, 0x4b0082).strokeRect(1, 1, 28, 28); - }); - tex('exit', TILE, TILE, (g) => { - g.fillStyle(0xff00ff).fillRect(0, 0, TILE, TILE); - g.lineStyle(2, 0xffffff).strokeRect(1, 1, TILE - 2, TILE - 2); - }); - tex('soundguy', 24, 24, (g) => { - g.fillStyle(0x000000).fillRect(0, 0, 24, 24); - g.lineStyle(2, 0xffffff).strokeRect(1, 1, 22, 22); - }); - tex('vinyl', 12, 12, (g) => { - g.fillStyle(0x000000).fillCircle(6, 6, 6); - g.fillStyle(0xffffff).fillCircle(6, 6, 2); - }); - tex('poser', 24, 24, (g) => g.fillStyle(0xff3030).fillTriangle(12, 0, 24, 24, 0, 24)); - tex('nerd', 24, 24, (g) => g.fillStyle(0xff8c1a).fillRect(0, 0, 24, 24)); - tex('player', 26, 26, (g) => g.fillStyle(0x00e5ff).fillCircle(13, 13, 13)); - } - resetStats() { this.vibe = VIBE_MAX; this.lanyards = 0; @@ -185,6 +153,14 @@ export default class GameScene extends Phaser.Scene { this.cameras.main.setBounds(0, 0, worldW, worldH); this.cameras.main.startFollow(this.player, true, 0.15, 0.15); + // Tiled floor behind everything. + if (this.floor) this.floor.destroy(); + this.floor = this.add + .tileSprite(0, 0, worldW, worldH, 'floor') + .setOrigin(0) + .setDepth(-10) + .setTint(0x555555); // darken so the brown crate walls read as raised + for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { const ch = map[r][c]; @@ -325,6 +301,7 @@ export default class GameScene extends Phaser.Scene { e.setTexture(poser ? 'poser' : 'nerd'); e.setActive(true).setVisible(true); e.body.enable = true; + e.body.setSize(22, 22, true); // body smaller than 32px sprite e.setPosition(g.x, g.y); e.setData('touch', poser ? POSER_TOUCH_DAMAGE : NERD_TOUCH_DAMAGE); e.setData('speed', poser ? POSER_SPEED : NERD_SPEED);