/**
* Тени мира: блобы под актёрами (герой, NPC, враги) и направленные тени
* tall-объектов карты. Положение считает чистая математика sunShadow/
* blobPlacement по часам суток (сцена передаёт SunShadow каждый тик —
* тени ползут за солнцем плавно). Актёрский блоб — первый ребёнок вьюхи
* (ноги в (0,0)), статические тени — отдельный ground-слой под актёрами.
*/
import { Container, Sprite, Texture, blobPlacement, applyBlobShadow, makeShadowTexture, type Renderer, type SunShadow } from '@rpg/engine';
/** Актёрский блоб: вьюха + «толщина» объекта в px виртуального экрана. */
interface ActorShadow {
view: Container;
radiusPx: number;
heightPx: number;
sprite: Sprite;
}
/** Статическая тень (tall-тайл): база уже в координатах мира-экрана. */
interface StaticShadow {
sprite: Sprite;
base: { x: number; y: number };
radiusPx: number;
heightPx: number;
}
/** Узкий интерфейс для боевых вьюх — без связности с полной системой. */
export interface ShadowAttach {
attachActor(view: Container, radiusPx: number, heightPx: number): void;
}
export class GameShadows implements ShadowAttach {
private readonly tex: Texture;
/** Ground-слой: добавить в world между картой и актёрами. */
readonly ground = new Container();
private readonly actorShadows: ActorShadow[] = [];
private readonly statics: StaticShadow[] = [];
constructor(renderer: Renderer) {
this.tex = makeShadowTexture(renderer);
this.ground.eventMode = 'none';
}
/** Блоб под актёром: первым ребёнком — спрайты рисуются поверх тени. */
attachActor(view: Container, radiusPx: number, heightPx: number): void {
const sprite = new Sprite(this.tex);
sprite.anchor.set(0.5, 0.5);
view.addChildAt(sprite, 0);
this.actorShadows.push({ view, radiusPx, heightPx, sprite });
}
/** Направленная тень tall-объекта: база — центр основания (центр ромба). */
addStatic(base: { x: number; y: number }, radiusPx: number, heightPx: number): void {
const sprite = new Sprite(this.tex);
sprite.anchor.set(0.5, 0.5);
this.ground.addChild(sprite);
this.statics.push({ sprite, base, radiusPx, heightPx });
}
/** Пересчёт всех теней по солнцу (null — ночь: погасить). */
update(sun: SunShadow | null): void {
// Вьюхи погибших врагов уничтожаются CombatViews — блобы вычищаем сами.
for (let i = this.actorShadows.length - 1; i >= 0; i--) {
if (this.actorShadows[i]!.sprite.destroyed) {
this.actorShadows.splice(i, 1);
continue;
}
const rec = this.actorShadows[i]!;
this.place(rec.sprite, { x: 0, y: 0 }, rec, sun);
}
for (const rec of this.statics) this.place(rec.sprite, rec.base, rec, sun);
}
private place(
sprite: Sprite,
base: { x: number; y: number },
rec: { radiusPx: number; heightPx: number },
sun: SunShadow | null
): void {
if (!sun) {
sprite.alpha = 0;
return;
}
applyBlobShadow(sprite, base, blobPlacement(rec.radiusPx, rec.heightPx, sun));
}
destroy(): void {
this.ground.destroy({ children: true });
this.tex.destroy();
}
}