Newer
Older
rpg / v2 / packages / engine / src / core / controls.ts
/**
 * Ввод как данные (решение v2): состояние управления — простой объект,
 * его заполняют клавиатура демки и виртуальный стик агентного моста.
 * Менеджера ввода с DOM-событиями в ядре нет (урок №4: не делать путь
 * без потребителя) — кнопки/указатель появятся вместе с потребителями.
 *
 * Направление движения — в ЭКРАННЫХ осях ([x] вправо, [y] к зрителю):
 * игрок думает «вправо относительно экрана». Перевод в мировые оси —
 * граница (урок v1 №1: единое мировое пространство, проекция на границе).
 */

/** Состояние управления на шаг логики. */
export interface ControlState {
    /** Желаемое движение в экранных осях, компоненты −1..1 (вправо, к зрителю). */
    move: readonly [number, number];
}

/**
 * Экранное направление → мировая горизонталь при изо-камере с yaw θ
 * (θ — угол «target → камера» в плоскости XZ). Экранный «вправо» ложится
 * на (sinθ, 0, −cosθ), «к зрителю» — на (cosθ, 0, sinθ); длина результата
 * обрезается до 1 (диагональ WASD не быстрее).
 */
export function screenMoveToWorld(
    move: readonly [number, number],
    yaw: number,
): [number, number] {
    const [sx, sy] = move;
    const x = sx * Math.sin(yaw) + sy * Math.cos(yaw);
    const z = -sx * Math.cos(yaw) + sy * Math.sin(yaw);
    const len = Math.hypot(x, z);
    if (len <= 1 || len === 0) return [x, z];
    return [x / len, z / len];
}