Art style

Tilework looks like a 2000s handheld RPG: a top-down 3/4 view, 16 px tiles, chunky characters with big heads, outlined props with three-tone shading, and white dialog-window menus. The goal is that style, not any existing game's assets.

Original work only. Every tile, prop, sprite, colour and UI frame is drawn by code in this repository. We do not use, trace, extract or re-colour assets from Pokémon or any other Nintendo, Game Freak or third-party game, and the project has no affiliation with them. A visual style (proportions, palette feel, pixel conventions) can be studied; assets and characters cannot be copied. See also Contributing.

#Rules of the look

AspectRule
Tile size16 x 16 px; the map is baked into one texture
Camerainteger zoom only (nearest-neighbour), reference view about 320 x 200 world px (20 x 12.5 tiles); a 240 x 160 handheld screen would show 15 x 10
Lightfrom the top-left: a light top/left edge, a darker bottom/right edge
Outlinesevery prop and character has a 1 px dark outline, never pure black; on characters it is a very dark version of the neighbouring colour, so hair, skin and clothes stay distinct
Shadingthree tones per material; highlights drift warm, shadows drift toward violet
Wallsbanded face: cream upper wall, a coloured stripe that matches the room's floor, a wooden wainscot and baseboard; a flat dark ceiling cap on thick walls; contact shadow on the floor below
Floorsbeveled pale plates with joint lines (reception), fine diagonal hatching on pale blue (desks), wide ochre planks with staggered joints (social area), dithered carpets with a dotted border band (meeting rooms); doorways get a doormat
Characters16 x 24 px content in an 18 x 26 cell; a 12 x 11 head (over half the sprite height), a 5-row torso and short legs; low-set eyes with a light blush; four directions, idle plus two walking frames; "right" is the mirrored "left"
UIwhite message frames with a coloured ring and dark lines on both sides; cream panels with an orange header tab; blue message strips; pill buttons with hard shadows; a black triangle cursor; teal scanline background on the join screen; a lavender name plate when entering an area
FontPixelify Sans (SIL Open Font License 1.1), bundled in web/src/assets/fonts with its licence

#Where the code lives

web/src/game/art/pixel.ts        helpers: hue-shifted shading, deterministic hash, outlined slabs and blobs
web/src/game/art/tiles.ts        floors, walls, doorways, furniture, rugs and the whole-map baker
web/src/game/art/characters.ts   character painter, walk frames, per-cell outline, sheet builder
web/src/game/palette.ts          skin, hair, shirt and trouser palettes
web/src/game/avatars.ts          turns a sheet into cached PixiJS textures
web/src/game/mapArt.ts           turns the baked canvas into a texture
web/src/theme-gba.css            the dialog-window UI skin (overrides styles.css)
web/dev/art-preview.*            server-free preview page and the export used by the website

The painters are plain canvas code with no PixiJS dependency, so they can be previewed and exported without running the game.

#Preview and iterate

cd web && pnpm exec vite --port 5180
# open http://127.0.0.1:5180/dev/art-preview.html?s=8   (sprites at 8x, map at 4x)

To refresh the images used by the README and the website:

cd e2e
node export-art.mjs   # sprite sheets and the baked map (needs only the Vite preview above)
node art.mjs          # cast, walking GIF and banner (needs Chrome and ffmpeg)
# in-game shots (needs a game server that serves the built client):
TILEWORK_APP=http://127.0.0.1:8091 TILEWORK_API=http://127.0.0.1:8091 node screens.mjs

web/dev/default-map.json is a copy of the starter map from the Go server (gamemap.Default()); refresh it if that map changes.

#Adding things

  • A prop: add its size to the dimensions table and a painter in tiles.ts, register the type in the Go map validator (server/internal/gamemap), and keep it solid or not in the Go table.
  • A hairstyle: add a case in each of the front, back and side branches of characters.ts and a label in palette.ts. The server keeps avatar fields within fixed ranges, so widen those too.
  • A floor: add an entry to the FLOORS table in tiles.ts and use its name in the map's areas.

Edit this page on GitHub