Sharpen sprites: TILE 32->48, higher-res textures + unsharp pass
Regenerate textures at 48px (chars/walls), 96px floor, with an UnsharpMask to counter the LANCZOS softening. Bump TILE to 48 so the whole world scales up and sprites render bigger/crisper; scale movement + range constants x1.5 to keep the same tiles-per-second feel, and collision bodies to 34px. Mechanics reverified. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ -8,7 +8,7 @@ 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
|
||||
from PIL import Image, ImageFilter
|
||||
import numpy as np
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
@ -18,19 +18,19 @@ 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),
|
||||
"floor": ("floor_parquet.png", 96, True),
|
||||
"wall": ("wall_crate.png", 48, True),
|
||||
"player": ("player_digger.png", 48, False),
|
||||
"poser": ("enemy_poser.png", 48, False),
|
||||
"nerd": ("enemy_gearnerd.png", 48, False),
|
||||
"soundguy": ("enemy_soundguy.png", 48, False),
|
||||
"generator": ("gen_discount_bin.png", 48, False),
|
||||
"exit": ("prop_djbooth.png", 48, False),
|
||||
"door": ("prop_velvet_rope.png", 48, False),
|
||||
"espresso": ("pickup_espresso.png", 38, False),
|
||||
"lanyard": ("pickup_lanyard.png", 38, False),
|
||||
"airhorn": ("pickup_airhorn.png", 38, False),
|
||||
"vinyl": ("pickup_vinyl.png", 24, False),
|
||||
}
|
||||
|
||||
|
||||
@ -56,6 +56,8 @@ for key, (fname, size, is_tile) in TABLE.items():
|
||||
canvas = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
||||
canvas.paste(im, ((size - im.width) // 2, (size - im.height) // 2), im)
|
||||
im = canvas
|
||||
# crisp up the LANCZOS-softened downscale
|
||||
im = im.filter(ImageFilter.UnsharpMask(radius=1.2, percent=150, threshold=1))
|
||||
im.save(OUT / f"{key}.png")
|
||||
print(f"{key:10s} <- {fname:24s} {size}px {'tile' if is_tile else 'keyed'}")
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 823 B After Width: | Height: | Size: 962 B |
|
Before Width: | Height: | Size: 969 B After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 912 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 632 B After Width: | Height: | Size: 688 B |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 5.0 KiB |
@ -2,7 +2,7 @@ import Phaser from 'phaser';
|
||||
import { LEVELS } from './levels.js';
|
||||
import { announce, LINES } from './announcer.js';
|
||||
|
||||
const TILE = 32;
|
||||
const TILE = 48;
|
||||
const VIBE_MAX = 2000;
|
||||
const VIBE_DRAIN_PER_SEC = 1; // the Gauntlet heartbeat
|
||||
const ESPRESSO_VIBE = 500;
|
||||
@ -11,12 +11,12 @@ const NERD_TOUCH_DAMAGE = 80;
|
||||
const SOUNDGUY_DRAIN = 10; // per 250ms tick while overlapping
|
||||
const GEN_HP = 3;
|
||||
const GEN_SPAWN_MS = 2500; // base; effective interval shrinks with density
|
||||
const GEN_RANGE = 700;
|
||||
const PLAYER_SPEED = 180;
|
||||
const POSER_SPEED = 120;
|
||||
const NERD_SPEED = 70;
|
||||
const SOUNDGUY_SPEED = 55;
|
||||
const VINYL_SPEED = 400;
|
||||
const GEN_RANGE = 1050; // px; scaled with TILE so range stays ~same in tiles
|
||||
const PLAYER_SPEED = 270;
|
||||
const POSER_SPEED = 180;
|
||||
const NERD_SPEED = 105;
|
||||
const SOUNDGUY_SPEED = 82;
|
||||
const VINYL_SPEED = 600;
|
||||
const VINYL_LIFE_MS = 1200;
|
||||
const FIRE_COOLDOWN_MS = 180;
|
||||
const TRAINSPOT_MS = 3000;
|
||||
@ -51,7 +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
|
||||
this.player.body.setSize(34, 34, true); // body smaller than 48px sprite -> fits corridors
|
||||
|
||||
// Colliders / overlaps (persistent group refs).
|
||||
this.physics.add.collider(this.player, this.walls);
|
||||
@ -301,7 +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.body.setSize(34, 34, true); // body smaller than 48px 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);
|
||||
|
||||