логика выделена в отдельный набор классов
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import { makeAutoObservable } from 'mobx';
|
||||
import {
|
||||
DROP_COLLECT_ARC_HEIGHT,
|
||||
DROP_COLLECT_DURATION,
|
||||
DROP_EJECT_RADIUS,
|
||||
DROP_FALL_ARC_HEIGHT,
|
||||
DROP_FALL_DURATION,
|
||||
} from '../constants';
|
||||
import { randomId } from '../id';
|
||||
import type { DropPhase, Flight, ResourceDropData, ResourceType, Vec2 } from '../types';
|
||||
|
||||
/** A resource in flight — ejected from a chopped node or a killed rabbit, falling to a landing
|
||||
* spot, then (once the character gets close) flying into the inventory. Framework-free — no
|
||||
* React/DOM dependency. */
|
||||
export class ResourceDrop {
|
||||
readonly id: string;
|
||||
resource: ResourceType;
|
||||
x: number;
|
||||
y: number;
|
||||
/** Visual height above ground during a flight, for the ballistic arc. 0 once landed. */
|
||||
z: number;
|
||||
phase: DropPhase;
|
||||
flight?: Flight;
|
||||
|
||||
constructor(resource: ResourceType, x: number, y: number, z: number, phase: DropPhase, flight: Flight | undefined, id = randomId('drop')) {
|
||||
this.id = id;
|
||||
this.resource = resource;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.phase = phase;
|
||||
this.flight = flight;
|
||||
makeAutoObservable(this, {}, { autoBind: true });
|
||||
}
|
||||
|
||||
/** Ejects a resource from `origin` on a ballistic arc to a random spot within a circle around
|
||||
* it; it only becomes pickable once it lands (see updateFlight). */
|
||||
static eject(origin: Vec2, resource: ResourceType): ResourceDrop {
|
||||
const angle = Math.random() * Math.PI * 2;
|
||||
const landDistance = Math.random() * DROP_EJECT_RADIUS;
|
||||
const toX = origin.x + Math.cos(angle) * landDistance;
|
||||
const toY = origin.y + Math.sin(angle) * landDistance;
|
||||
return new ResourceDrop(resource, origin.x, origin.y, 0, 'falling', {
|
||||
fromX: origin.x,
|
||||
fromY: origin.y,
|
||||
toX,
|
||||
toY,
|
||||
elapsed: 0,
|
||||
duration: DROP_FALL_DURATION,
|
||||
arcHeight: DROP_FALL_ARC_HEIGHT,
|
||||
});
|
||||
}
|
||||
|
||||
/** Reconstructs a drop from saved data (a straight deep copy). */
|
||||
static restore(data: ResourceDropData): ResourceDrop {
|
||||
return new ResourceDrop(data.resource, data.x, data.y, data.z, data.phase, data.flight ? { ...data.flight } : undefined, data.id);
|
||||
}
|
||||
|
||||
/** Starts a landed drop flying into the inventory once the character gets close enough. */
|
||||
startCollecting(target: Vec2): void {
|
||||
this.phase = 'collecting';
|
||||
this.flight = {
|
||||
fromX: this.x,
|
||||
fromY: this.y,
|
||||
toX: target.x,
|
||||
toY: target.y,
|
||||
elapsed: 0,
|
||||
duration: DROP_COLLECT_DURATION,
|
||||
arcHeight: DROP_COLLECT_ARC_HEIGHT,
|
||||
};
|
||||
}
|
||||
|
||||
/** Advances an in-progress flight along its ballistic arc. Returns 'flying' while still en
|
||||
* route, 'landed' the instant a falling flight completes (or if there's no flight at all), or
|
||||
* 'collected' the instant a collecting flight reaches the character — the caller applies the
|
||||
* inventory gain and removes the drop only on 'collected'. */
|
||||
updateFlight(deltaTime: number): 'flying' | 'landed' | 'collected' {
|
||||
if (this.phase === 'landed' || !this.flight)
|
||||
return 'landed';
|
||||
|
||||
const flight = this.flight;
|
||||
flight.elapsed += deltaTime;
|
||||
const t = Math.min(1, flight.elapsed / flight.duration);
|
||||
this.x = flight.fromX + (flight.toX - flight.fromX) * t;
|
||||
this.y = flight.fromY + (flight.toY - flight.fromY) * t;
|
||||
this.z = flight.arcHeight * 4 * t * (1 - t);
|
||||
|
||||
if (t < 1)
|
||||
return 'flying';
|
||||
|
||||
if (this.phase === 'falling') {
|
||||
this.phase = 'landed';
|
||||
this.flight = undefined;
|
||||
this.z = 0;
|
||||
return 'landed';
|
||||
}
|
||||
|
||||
return 'collected';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user