new animation, refactor, fixes

This commit is contained in:
Vasyl Kazakov
2026-06-18 12:45:48 +03:00
parent 930ed00c95
commit 852e1ec963
22 changed files with 519 additions and 116 deletions
+65
View File
@@ -1,3 +1,4 @@
import { UpdateController } from "@24tools/playable_template";
import { Color, Material, Mesh, Object3D, Vector3 } from "three";
import { ResourceScreenFly } from "../Resources/ResourceScreenFly";
import {
@@ -9,10 +10,19 @@ import {
const FILL_TARGET = 10;
const FILL_COLOR = new Color(0x2db83a);
/** Амплітуда «поглинаючого» імпульсу (частка від базового масштабу). 0.05 = 5%. */
const PULSE_AMPLITUDE = 0.05;
/** Тривалість одного імпульсу, сек: базовий розмір → пік → базовий. */
const PULSE_DURATION = 0.18;
/** Значення таймера, коли імпульс неактивний. */
const PULSE_IDLE = Infinity;
export class InteractiveZoneC {
private static zoneRoot: Object3D | null = null;
private static deposited = 0;
private static baseColors = new WeakMap<Material, Color>();
private static baseScale = new Vector3(1, 1, 1);
private static pulseTime = PULSE_IDLE;
static init(mapObject: Object3D) {
const zone = findInteractiveZoneFromMap(mapObject);
@@ -22,7 +32,41 @@ export class InteractiveZoneC {
}
this.zoneRoot = zone;
this.baseScale.copy(zone.scale);
this.setupMaterials(zone);
UpdateController.Instance.onUpdate.addDelegate((delta: number) => {
this.updatePulse(delta);
});
}
/** Один «поглинаючий» імпульс зони — викликати на приліт кожного ресурсу. */
static pulse() {
this.pulseTime = 0;
}
/** Повертає зону до базового розміру і гасить імпульс (teardown/зупинка). */
static resetPulse() {
this.pulseTime = PULSE_IDLE;
if (this.zoneRoot) this.zoneRoot.scale.copy(this.baseScale);
}
private static updatePulse(delta: number) {
if (!this.zoneRoot || this.pulseTime === PULSE_IDLE) return;
this.pulseTime += delta;
if (this.pulseTime >= PULSE_DURATION) {
this.resetPulse();
return;
}
const env = Math.sin(Math.PI * (this.pulseTime / PULSE_DURATION));
const k = 1 + PULSE_AMPLITUDE * env;
this.zoneRoot.scale.set(
this.baseScale.x * k,
this.baseScale.y * k,
this.baseScale.z * k,
);
}
static getWorldCenter(out = new Vector3()) {
@@ -39,6 +83,27 @@ export class InteractiveZoneC {
this.refreshFillVisual();
}
/** Звільняє клоновані матеріали зони (виклик на teardown playable). */
static dispose() {
if (!this.zoneRoot) return;
this.resetPulse();
this.zoneRoot.traverse((child) => {
const mesh = child as Mesh;
if (!mesh.isMesh) return;
const material = mesh.material;
if (Array.isArray(material)) {
material.forEach((entry) => entry.dispose());
} else if (material) {
material.dispose();
}
});
this.baseColors = new WeakMap<Material, Color>();
}
static getFillRatio() {
return Math.min(this.deposited / FILL_TARGET, 1);
}