feat(placeholder): add woodpecker tilemap

pull/5954/head
boojack 2 weeks ago
parent cf55f11072
commit 638e4f398e

@ -46,3 +46,4 @@ Avoid padding an animation with duplicate frames just to hit a standard count. A
- `OwlBlink.svg`: five-frame blink/idle strip with breathing wings, blink, and ear-feather settle.
- `FalconIdle.svg`: four-frame idle strip with breathing, blink, alert head shift, and tail flick.
- `WoodpeckerPeck.svg`: six-frame tree-trunk peck strip with red crest, chisel beak, barred wing, impact chips, recoil, and settle.

@ -3,9 +3,9 @@
<desc id="desc">A five-frame 32 by 32 pixel owl idle and blink animation strip.</desc>
<defs>
<style>
.outline { fill: #181512; }
.outline { fill: #2f2a24; }
.body { fill: #70685d; }
.body-dark { fill: #4f493f; }
.body-dark { fill: #585147; }
.wing { fill: #5f584f; }
.face { fill: #b8afa2; }
.face-dark { fill: #958c7f; }
@ -13,6 +13,7 @@
.eye-dark { fill: #2f2a24; }
.beak { fill: #bf9a53; }
.claw { fill: #d8d1c4; }
.contact { fill: #2f2a24; }
</style>
<symbol id="owl-open" viewBox="0 0 32 32">
@ -32,17 +33,17 @@
<rect class="beak" x="15" y="15" width="2" height="2" />
<rect class="beak" x="14" y="17" width="4" height="2" />
<rect class="outline" x="11" y="27" width="2" height="3" />
<rect class="outline" x="19" y="27" width="2" height="3" />
<rect class="contact" x="11" y="27" width="2" height="3" />
<rect class="contact" x="19" y="27" width="2" height="3" />
<rect class="claw" x="10" y="30" width="4" height="1" />
<rect class="claw" x="18" y="30" width="4" height="1" />
</symbol>
<symbol id="owl-breathe" viewBox="0 0 32 32">
<use href="#owl-open" />
<rect class="outline" x="3" y="16" width="1" height="6" />
<rect class="body-dark" x="3" y="16" width="1" height="6" />
<rect class="wing" x="4" y="16" width="6" height="10" />
<rect class="outline" x="28" y="16" width="1" height="6" />
<rect class="body-dark" x="28" y="16" width="1" height="6" />
<rect class="wing" x="22" y="16" width="6" height="10" />
<rect class="body" x="11" y="24" width="10" height="2" />
<rect class="body-dark" x="10" y="25" width="12" height="1" />
@ -68,8 +69,8 @@
<symbol id="owl-settle" viewBox="0 0 32 32">
<use href="#owl-open" />
<rect class="outline" x="6" y="1" width="2" height="3" />
<rect class="outline" x="24" y="1" width="2" height="3" />
<rect class="body-dark" x="6" y="1" width="2" height="3" />
<rect class="body-dark" x="24" y="1" width="2" height="3" />
<rect class="body-dark" x="4" y="21" width="1" height="2" />
<rect class="body-dark" x="27" y="21" width="1" height="2" />
<rect class="face-dark" x="11" y="20" width="10" height="1" />

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

@ -0,0 +1,162 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="32" viewBox="0 0 192 32" role="img" aria-labelledby="title desc" shape-rendering="crispEdges">
<title id="title">Woodpecker peck pixel tileset</title>
<desc id="desc">A six-frame 32 by 32 pixel woodpecker pecking a tree trunk.</desc>
<defs>
<style>
.outline { fill: #17110e; }
.bark { fill: #6c4b34; }
.bark-dark { fill: #3f2b20; }
.bark-light { fill: #9b6a45; }
.crest { fill: #c8442f; }
.crest-dark { fill: #7c261d; }
.head { fill: #f0d3a4; }
.cheek { fill: #f2efe7; }
.head-shadow { fill: #b78354; }
.beak { fill: #2f2a24; }
.body { fill: #835b3c; }
.belly { fill: #e6d7bf; }
.wing { fill: #27201d; }
.wing-light { fill: #f2efe7; }
.stripe { fill: #5b4d42; }
.tail { fill: #2f2a24; }
.claw { fill: #8e8376; }
.eye { fill: #120f0d; }
.eye-light { fill: #f4ead5; }
.chip { fill: #d8a55f; }
</style>
<symbol id="trunk" viewBox="0 0 32 32">
<path class="outline" d="M1 0h5v32H1V0z" />
<rect class="bark" x="2" y="0" width="3" height="32" />
<rect class="bark-dark" x="2" y="6" width="1" height="5" />
<rect class="bark-dark" x="4" y="17" width="1" height="6" />
<rect class="bark-light" x="4" y="2" width="1" height="4" />
<rect class="bark-light" x="3" y="24" width="1" height="5" />
</symbol>
<symbol id="woodpecker-rest" viewBox="0 0 32 32">
<use href="#trunk" />
<path class="outline" d="M14 3h4v2h2v3h2v4h2v2h2v12h-2v3h-4v2h-7v-2h-2v-3H9v-4h1v-5H8v-2H6v-2h3V8h2V5h3V3z" />
<path class="crest" d="M14 4h3v2h2v2h1v2h-5V8h-2V6h1V4z" />
<rect class="crest-dark" x="16" y="6" width="2" height="2" />
<path class="head" d="M11 8h8v2h2v4h-2v2h-7v-2h-2v-4h1V8z" />
<rect class="cheek" x="11" y="12" width="4" height="3" />
<rect class="head-shadow" x="16" y="14" width="3" height="2" />
<rect class="eye-light" x="14" y="9" width="2" height="2" />
<rect class="eye" x="14" y="9" width="1" height="1" />
<path class="beak" d="M5 11h7v1H5v-1z" />
<rect class="beak" x="6" y="12" width="6" height="1" />
<path class="body" d="M13 16h9v2h2v8h-2v2h-8v-2h-2v-8h1v-2z" />
<path class="belly" d="M13 17h4v8h-1v1h-2v-2h-1v-7z" />
<path class="wing" d="M17 16h7v3h-1v5h-2v2h-5v-9h1v-1z" />
<rect class="wing-light" x="18" y="17" width="3" height="1" />
<rect class="wing-light" x="20" y="20" width="3" height="1" />
<rect class="wing-light" x="17" y="23" width="3" height="1" />
<rect class="stripe" x="18" y="18" width="2" height="1" />
<rect class="stripe" x="21" y="21" width="2" height="1" />
<path class="tail" d="M22 24h3v2h2v3h-2v1h-4v-2h1v-4z" />
<rect class="claw" x="7" y="17" width="4" height="1" />
<rect class="claw" x="8" y="24" width="4" height="1" />
</symbol>
<symbol id="woodpecker-lean" viewBox="0 0 32 32">
<use href="#trunk" />
<path class="outline" d="M13 3h4v2h2v3h2v4h2v2h2v12h-2v3h-4v2h-7v-2h-2v-3H8v-4h1v-5H7v-2H5v-2h3V8h2V5h3V3z" />
<path class="crest" d="M13 4h3v2h2v2h1v2h-5V8h-2V6h1V4z" />
<rect class="crest-dark" x="15" y="6" width="2" height="2" />
<path class="head" d="M10 8h8v2h2v4h-2v2h-7v-2H9v-4h1V8z" />
<rect class="cheek" x="10" y="12" width="4" height="3" />
<rect class="head-shadow" x="15" y="14" width="3" height="2" />
<rect class="eye-light" x="13" y="9" width="2" height="2" />
<rect class="eye" x="13" y="9" width="1" height="1" />
<path class="beak" d="M4 11h7v1H4v-1z" />
<rect class="beak" x="5" y="12" width="6" height="1" />
<path class="body" d="M12 16h9v2h2v8h-2v2h-8v-2h-2v-8h1v-2z" />
<path class="belly" d="M12 17h4v8h-1v1h-2v-2h-1v-7z" />
<path class="wing" d="M16 16h7v3h-1v5h-2v2h-5v-9h1v-1z" />
<rect class="wing-light" x="17" y="17" width="3" height="1" />
<rect class="wing-light" x="19" y="20" width="3" height="1" />
<rect class="wing-light" x="16" y="23" width="3" height="1" />
<path class="tail" d="M21 24h3v2h2v3h-2v1h-4v-2h1v-4z" />
<rect class="claw" x="7" y="17" width="4" height="1" />
<rect class="claw" x="8" y="24" width="4" height="1" />
</symbol>
<symbol id="woodpecker-strike" viewBox="0 0 32 32">
<use href="#trunk" />
<path class="outline" d="M12 4h4v2h2v3h2v4h2v2h2v11h-2v3h-4v2h-7v-2H9v-3H7v-4h1v-5H6v-2H4v-3h4V9h2V6h2V4z" />
<path class="crest" d="M12 5h3v2h2v2h1v2h-5V9h-2V7h1V5z" />
<rect class="crest-dark" x="14" y="7" width="2" height="2" />
<path class="head" d="M9 9h8v2h2v4h-2v2h-7v-2H8v-4h1V9z" />
<rect class="cheek" x="9" y="13" width="4" height="3" />
<rect class="head-shadow" x="14" y="15" width="3" height="2" />
<rect class="eye-light" x="12" y="10" width="2" height="2" />
<rect class="eye" x="12" y="10" width="1" height="1" />
<path class="beak" d="M3 12h7v1H3v-1z" />
<rect class="beak" x="4" y="13" width="6" height="1" />
<path class="body" d="M11 17h9v2h2v7h-2v2h-8v-2h-2v-8h1v-1z" />
<path class="belly" d="M11 18h4v7h-1v1h-2v-2h-1v-6z" />
<path class="wing" d="M15 17h7v3h-1v4h-2v2h-5v-8h1v-1z" />
<rect class="wing-light" x="16" y="18" width="3" height="1" />
<rect class="wing-light" x="18" y="21" width="3" height="1" />
<rect class="wing-light" x="15" y="23" width="3" height="1" />
<path class="tail" d="M20 24h3v2h2v3h-2v1h-4v-2h1v-4z" />
<rect class="claw" x="7" y="17" width="4" height="1" />
<rect class="claw" x="8" y="24" width="4" height="1" />
</symbol>
<symbol id="woodpecker-impact" viewBox="0 0 32 32">
<use href="#woodpecker-strike" />
<rect class="chip" x="1" y="10" width="1" height="1" />
<rect class="chip" x="3" y="9" width="1" height="1" />
<rect class="chip" x="2" y="14" width="1" height="1" />
<rect class="bark-light" x="5" y="12" width="1" height="1" />
</symbol>
<symbol id="woodpecker-recoil" viewBox="0 0 32 32">
<use href="#trunk" />
<path class="outline" d="M15 2h4v2h2v3h2v4h2v2h2v13h-2v3h-4v2h-7v-2h-2v-3h-2v-4h1v-5H9v-2H7v-2h3V7h2V4h3V2z" />
<path class="crest" d="M15 3h3v2h2v2h1v2h-5V7h-2V5h1V3z" />
<rect class="crest-dark" x="17" y="5" width="2" height="2" />
<path class="head" d="M12 7h8v2h2v4h-2v2h-7v-2h-2V9h1V7z" />
<rect class="cheek" x="12" y="11" width="4" height="3" />
<rect class="eye-light" x="15" y="8" width="2" height="2" />
<rect class="eye" x="15" y="8" width="1" height="1" />
<path class="beak" d="M6 10h7v1H6v-1z" />
<rect class="beak" x="7" y="11" width="6" height="1" />
<path class="body" d="M14 16h9v2h2v8h-2v2h-8v-2h-2v-8h1v-2z" />
<path class="belly" d="M14 17h4v8h-1v1h-2v-2h-1v-7z" />
<path class="wing" d="M18 16h7v3h-1v5h-2v2h-5v-9h1v-1z" />
<rect class="wing-light" x="19" y="17" width="3" height="1" />
<rect class="wing-light" x="21" y="20" width="3" height="1" />
<rect class="wing-light" x="18" y="23" width="3" height="1" />
<path class="tail" d="M24 24h3v2h2v3h-2v1h-4v-2h1v-4z" />
<rect class="claw" x="8" y="17" width="4" height="1" />
<rect class="claw" x="9" y="24" width="4" height="1" />
</symbol>
<symbol id="woodpecker-settle" viewBox="0 0 32 32">
<use href="#woodpecker-rest" />
<rect class="crest-dark" x="14" y="5" width="3" height="1" />
<rect class="wing-light" x="21" y="19" width="2" height="1" />
<rect class="claw" x="8" y="18" width="3" height="1" />
<rect class="claw" x="9" y="25" width="3" height="1" />
</symbol>
</defs>
<use href="#woodpecker-rest" x="0" y="0" width="32" height="32" />
<use href="#woodpecker-lean" x="32" y="0" width="32" height="32" />
<use href="#woodpecker-strike" x="64" y="0" width="32" height="32" />
<use href="#woodpecker-impact" x="96" y="0" width="32" height="32" />
<use href="#woodpecker-recoil" x="128" y="0" width="32" height="32" />
<use href="#woodpecker-settle" x="160" y="0" width="32" height="32" />
</svg>

After

Width:  |  Height:  |  Size: 8.0 KiB

@ -1,5 +1,6 @@
import FalconIdle from "./pieces/FalconIdle.svg?url";
import OwlBlink from "./pieces/OwlBlink.svg?url";
import WoodpeckerPeck from "./pieces/WoodpeckerPeck.svg?url";
export interface TileSprite {
name: string;
@ -27,6 +28,14 @@ export const TILE_SPRITES: TileSprite[] = [
frames: 4,
duration: 960,
},
{
name: "WoodpeckerPeck",
src: WoodpeckerPeck,
frameWidth: 32,
frameHeight: 32,
frames: 6,
duration: 1080,
},
];
export function pickTileSprite(): TileSprite {

@ -4,14 +4,15 @@ import { DEFAULT_MESSAGES, type PlaceholderVariant } from "@/components/Placehol
describe("TILE_SPRITES integrity", () => {
it("registers 32px by 32px sprite strips with animation-specific frame counts", () => {
expect(TILE_SPRITES.map((sprite) => sprite.name)).toEqual(["OwlBlink", "FalconIdle"]);
expect(TILE_SPRITES.map((sprite) => sprite.name)).toEqual(["OwlBlink", "FalconIdle", "WoodpeckerPeck"]);
expect(TILE_SPRITES.map((sprite) => [sprite.name, sprite.frames])).toEqual([
["OwlBlink", 5],
["FalconIdle", 4],
["WoodpeckerPeck", 6],
]);
for (const sprite of TILE_SPRITES) {
expect(sprite.name).toMatch(/^[A-Z][A-Za-z]+(Idle|Hop|Blink|Drift|Flutter|Hover)$/);
expect(sprite.name).toMatch(/^[A-Z][A-Za-z]+(Idle|Hop|Blink|Drift|Flutter|Hover|Peck)$/);
expect(sprite.frameWidth).toBe(32);
expect(sprite.frameHeight).toBe(32);
expect(sprite.frames).toBeGreaterThanOrEqual(2);

Loading…
Cancel
Save