import {
World,
IsometricTileMap,
findPath,
isoToScreen,
screenToIsoExact,
moveTo,
Container,
Graphics,
DEFAULT_ISO,
type Entity,
type Vec2
} from '@rpg/engine';
/**
* Управление героем: путь по клику (A*), плавное движение по маршруту.
* Спрайт — плейсхолдер из Graphics (пока нет арта); позиция — центр ромба тайла.
*/
export class PlayerController {
readonly entity: Entity;
readonly view: Container;
/** Позиция в виртуальных пикселях (источник истины, ноги героя). */
private pos: Vec2;
private path: { x: number; y: number }[] = [];
private waypoint: Vec2 | null = null;
/** Скорость в виртуальных пикселях/сек. */
private speed = 56;
constructor(world: World, private map: IsometricTileMap, startTile: { x: number; y: number }) {
this.entity = world.createEntity();
world.addComponent(this.entity, 'pos', { ...startTile });
this.view = new Container();
const body = new Graphics();
// Плейсхолдер героя: тёмный плащ и шляпа звонаря.
body.rect(-3, -9, 6, 9).fill(0x3b4a63);
body.rect(-2, -3, 4, 3).fill(0x2a3548);
body.rect(-3, -12, 6, 3).fill(0x8a6d4b);
body.rect(-1, -11, 2, 1).fill(0x1c1c22);
this.view.addChild(body);
const p = isoToScreen(startTile.x, startTile.y);
this.pos = { x: p.x, y: p.y + DEFAULT_ISO.tileH / 2 };
this.view.position.set(Math.round(this.pos.x), Math.round(this.pos.y));
}
/** Кликом по миру выбран тайл — строим путь A* и начинаем движение. */
onWorldClick(worldX: number, worldY: number): void {
const tile = screenToIsoExact(
worldX,
worldY,
this.map.data.width,
this.map.data.height,
DEFAULT_ISO
);
if (!tile) return;
const path = findPath(this.map, this.currentTile(), tile, false);
if (path && path.length > 0) {
this.path = path;
this.advanceWaypoint();
}
}
update(dt: number): void {
if (!this.waypoint) return;
this.pos = moveTo(this.pos, this.waypoint, this.speed * dt);
if (this.pos.x === this.waypoint.x && this.pos.y === this.waypoint.y) {
this.advanceWaypoint();
}
this.view.position.set(Math.round(this.pos.x), Math.round(this.pos.y));
}
get moving(): boolean {
return this.waypoint !== null;
}
/** Текущий тайл героя. */
currentTile(): { x: number; y: number } {
return (
screenToIsoExact(
this.pos.x,
this.pos.y,
this.map.data.width,
this.map.data.height,
DEFAULT_ISO
) ?? { x: 0, y: 0 }
);
}
private advanceWaypoint(): void {
const next = this.path.shift();
if (next) {
const p = isoToScreen(next.x, next.y);
this.waypoint = { x: p.x, y: p.y + DEFAULT_ISO.tileH / 2 };
} else {
this.waypoint = null;
}
}
}