Add Binding 2.0 asset pack

This commit is contained in:
2026-06-19 15:50:02 +03:00
parent db545ce80b
commit 3cdd3ac738
47 changed files with 472 additions and 23 deletions
+5 -3
View File
@@ -10,19 +10,21 @@ import type { Renderer } from './Renderer';
export class HudOverlay implements Renderer {
private readonly ctx: CanvasRenderingContext2D;
private readonly images = new Map<string, HTMLImageElement>();
private readonly assetBasePath: string;
/** Лениво грузит PNG из assets/<name>.png; возвращает картинку, только когда она готова. */
/** Лениво грузит PNG из выбранного пака; возвращает картинку, только когда она готова. */
private img(name: string): HTMLImageElement | null {
let im = this.images.get(name);
if (!im) {
im = new Image();
im.src = `assets/${name}.png`;
im.src = `${this.assetBasePath}/${name}.png`;
this.images.set(name, im);
}
return im.complete && im.naturalWidth > 0 ? im : null;
}
constructor(canvas: HTMLCanvasElement) {
constructor(canvas: HTMLCanvasElement, assetBasePath = 'assets') {
this.assetBasePath = assetBasePath;
// Буфер увеличиваем под плотность пикселей (чёткий текст на HiDPI),
// а рисуем по-прежнему в логических координатах CW×CH.
const dpr = Math.min(window.devicePixelRatio || 1, 2);
+5 -3
View File
@@ -40,7 +40,7 @@ export class ThreeRenderer implements Renderer {
private readonly renderer: THREE.WebGLRenderer;
private readonly scene = new THREE.Scene();
private readonly camera: THREE.PerspectiveCamera;
private readonly assets = new Assets();
private readonly assets: Assets;
private readonly theme: Theme;
// Общие геометрии.
@@ -75,8 +75,10 @@ export class ThreeRenderer implements Renderer {
private pickupMesh: THREE.Mesh | null = null;
private currentPickupWeapon: WeaponId | null = null;
constructor(canvas: HTMLCanvasElement, theme: Theme = DEFAULT_THEME) {
this.theme = theme;
constructor(canvas: HTMLCanvasElement, assetBasePathOrTheme: string | Theme = 'assets', theme: Theme = DEFAULT_THEME) {
const assetBasePath = typeof assetBasePathOrTheme === 'string' ? assetBasePathOrTheme : 'assets';
this.theme = typeof assetBasePathOrTheme === 'string' ? theme : assetBasePathOrTheme;
this.assets = new Assets(assetBasePath);
this.renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
this.renderer.setSize(CW, CH, false);
+15
View File
@@ -0,0 +1,15 @@
/** Наборы ассетов. Логика игры про них не знает — это чисто настройка рендера/HUD. */
export type AssetPackId = 'classic' | 'binding-2';
export interface AssetPack {
id: AssetPackId;
name: string;
path: string;
}
export const ASSET_PACKS: readonly AssetPack[] = [
{ id: 'classic', name: 'Классика', path: 'assets' },
{ id: 'binding-2', name: 'Биндинг 2.0', path: 'assets-binding-2' },
];
export const DEFAULT_ASSET_PACK = ASSET_PACKS[0];
+7 -5
View File
@@ -3,12 +3,12 @@ import type { WeaponId } from '../core/weapons';
/**
* assets.ts — поставщик текстур. Каждая текстура грузится из
* `src/assets/<ключ>.png` через THREE.TextureLoader; если PNG нет — рисуется
* `<пак>/<ключ>.png` через THREE.TextureLoader; если PNG нет — рисуется
* процедурный фолбэк на canvas (функции drawX ниже), чтобы игра работала без
* ассетов. Текстуры кэшируются и освобождаются в dispose().
*
* Как заменить/добавить графику: положи PNG с именем `<ключ>.png` в `src/assets/`
* (dev-сервер отдаёт их из src/assets, прод-сборка копирует в dist/assets). Код
* Как заменить/добавить графику: положи PNG с именем `<ключ>.png` в папку пака
* (dev-сервер отдаёт их из src/<asset-pack>, прод-сборка копирует в dist/). Код
* трогать не нужно. Функции drawX — это лишь плейсхолдер-фолбэк; правь их, только
* если хочешь другой запасной рисунок. Полный список ключей — в docs/ASSET_BRIEF.md.
*/
@@ -364,9 +364,11 @@ export class Assets {
private cache = new Map<string, THREE.Texture>();
private readonly loader = new THREE.TextureLoader();
constructor(private readonly basePath = 'assets') {}
/**
* Возвращает текстуру по ключу. Сначала пытается загрузить PNG из
* `src/assets/<key>.png` (поставляется художником, см. docs/ASSET_BRIEF.md);
* `<basePath>/<key>.png` (поставляется художником, см. docs/ASSET_BRIEF.md);
* если файла нет — рисует процедурный фолбэк, чтобы игра не ломалась.
* 404 в консоли для ещё не добавленных ассетов — это норма (сработал фолбэк).
*/
@@ -375,7 +377,7 @@ export class Assets {
if (cached) return cached;
const tex = this.loader.load(
`assets/${key}.png`,
`${this.basePath}/${key}.png`,
undefined,
undefined,
() => { tex.image = build() as unknown as HTMLImageElement; tex.needsUpdate = true; }, // PNG нет → процедурный фолбэк