feat(assets): загрузка PNG из src/assets с процедурным фолбэком

- assets.ts: текстуры грузятся из src/assets/<key>.png; если файла нет —
  откат на процедурный рисунок (игра не ломается, сборка не зависит от наличия PNG).
- dev.ts: отдаёт /assets/* прямо из src/assets/ (положил PNG → сразу подхватился).
- build.ts: копирует src/assets → dist/assets.
- Имена ключей = именам файлов из docs/ASSET_BRIEF.md (player-ranged.png и т.д.).
- Добавлен src/assets/tear.png как рабочий пример из дизайна.

Чтобы подключить полный арт: положить 21 PNG из проекта Claude Design в src/assets/.
Бинарные ассеты переносятся файлами (не через модель — это триггерит usage-policy).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-18 16:00:35 +03:00
co-authored by Claude Opus 4.8
parent 3bb46e2f6b
commit e2cb1ff855
4 changed files with 38 additions and 15 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

+22 -15
View File
@@ -23,16 +23,6 @@ function canvas(w: number, h: number): { cv: HTMLCanvasElement; ctx: CanvasRende
return { cv, ctx: cv.getContext('2d')! };
}
function texture(cv: HTMLCanvasElement, pixelated = true): THREE.CanvasTexture {
const t = new THREE.CanvasTexture(cv);
t.colorSpace = THREE.SRGBColorSpace;
if (pixelated) {
t.magFilter = THREE.NearestFilter;
t.minFilter = THREE.NearestFilter;
}
return t;
}
/** Скруглённый прямоугольник (хелпер рисования). */
function roundRect(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, r: number): void {
ctx.beginPath();
@@ -192,14 +182,31 @@ function drawShadow(): HTMLCanvasElement {
*/
export class Assets {
private cache = new Map<string, THREE.Texture>();
private readonly loader = new THREE.TextureLoader();
/**
* Возвращает текстуру по ключу. Сначала пытается загрузить PNG из
* `src/assets/<key>.png` (поставляется художником, см. docs/ASSET_BRIEF.md);
* если файла нет — рисует процедурный фолбэк, чтобы игра не ломалась.
* 404 в консоли для ещё не добавленных ассетов — это норма (сработал фолбэк).
*/
private get(key: string, build: () => HTMLCanvasElement, pixelated = true): THREE.Texture {
let t = this.cache.get(key);
if (!t) {
t = texture(build(), pixelated);
this.cache.set(key, t);
const cached = this.cache.get(key);
if (cached) return cached;
const tex = this.loader.load(
`assets/${key}.png`,
undefined,
undefined,
() => { tex.image = build() as unknown as HTMLImageElement; tex.needsUpdate = true; }, // PNG нет → процедурный фолбэк
);
tex.colorSpace = THREE.SRGBColorSpace;
if (pixelated) {
tex.magFilter = THREE.NearestFilter;
tex.minFilter = THREE.NearestFilter;
}
return t;
this.cache.set(key, tex);
return tex;
}
sprite(key: SpriteKey): THREE.Texture {