fix(studio): бластер от 3-го лица стреляет в точку клика, а не в центр камеры

При свободном курсоре (нет pointer-lock, 3-е лицо) выстрел шёл из getForwardRay
(фокус камеры). Теперь onDown берёт координаты клика → setAimScreenPoint → луч
через точку клика; onMove обновляет _holdAim для авто-огня при удержании. При
pointer-lock (1-е лицо, курсор в центре) — прежнее поведение (центр).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
min 2026-06-07 14:01:14 +03:00
parent e4fdd91b12
commit 931d53b4d9

View File

@ -90,6 +90,18 @@ export class WeaponSystem {
if (e.button !== 0) return; if (e.button !== 0) return;
// Если UI-режим курсора — не стреляем (мышь работает по GUI) // Если UI-режим курсора — не стреляем (мышь работает по GUI)
if (this.scene3d?.player?.isUiCursorMode?.()) return; if (this.scene3d?.player?.isUiCursorMode?.()) return;
// Если курсор СВОБОДЕН (нет pointer-lock — обычно 3-е лицо) — стреляем
// ТУДА, КУДА КЛИКНУЛИ, а не в центр камеры. При pointer-lock курсор в
// центре экрана → используем прицел камеры (aim не задаём).
if (document.pointerLockElement !== canvas) {
const rect = canvas.getBoundingClientRect();
const cx = (e.clientX != null ? e.clientX : 0) - rect.left;
const cy = (e.clientY != null ? e.clientY : 0) - rect.top;
if (cx >= 0 && cy >= 0 && cx <= rect.width && cy <= rect.height) {
this.setAimScreenPoint(cx * (canvas.width / rect.width),
cy * (canvas.height / rect.height));
}
}
this._mouseDown = true; this._mouseDown = true;
this._tryFire(); this._tryFire();
}; };
@ -97,14 +109,28 @@ export class WeaponSystem {
if (e.button !== 0) return; if (e.button !== 0) return;
this._mouseDown = false; this._mouseDown = false;
}; };
// При свободном курсоре (3-е лицо) запоминаем позицию мыши — чтобы
// авто-огонь при удержании ЛКМ продолжал стрелять в точку курсора.
const onMove = (e) => {
if (!this._mouseDown) return;
if (document.pointerLockElement === canvas) return;
const rect = canvas.getBoundingClientRect();
const cx = (e.clientX != null ? e.clientX : 0) - rect.left;
const cy = (e.clientY != null ? e.clientY : 0) - rect.top;
if (cx >= 0 && cy >= 0 && cx <= rect.width && cy <= rect.height) {
this._holdAim = { x: cx * (canvas.width / rect.width), y: cy * (canvas.height / rect.height) };
}
};
const onKey = (e) => { const onKey = (e) => {
if (e.code === 'KeyR') this.reload(); if (e.code === 'KeyR') this.reload();
}; };
canvas.addEventListener('mousedown', onDown); canvas.addEventListener('mousedown', onDown);
window.addEventListener('mouseup', onUp); window.addEventListener('mouseup', onUp);
window.addEventListener('mousemove', onMove);
window.addEventListener('keydown', onKey); window.addEventListener('keydown', onKey);
this._listeners.push({ target: canvas, type: 'mousedown', fn: onDown }); this._listeners.push({ target: canvas, type: 'mousedown', fn: onDown });
this._listeners.push({ target: window, type: 'mouseup', fn: onUp }); this._listeners.push({ target: window, type: 'mouseup', fn: onUp });
this._listeners.push({ target: window, type: 'mousemove', fn: onMove });
this._listeners.push({ target: window, type: 'keydown', fn: onKey }); this._listeners.push({ target: window, type: 'keydown', fn: onKey });
// Регистрируем перед-кадровый хук для авто-стрельбы (если auto=true) // Регистрируем перед-кадровый хук для авто-стрельбы (если auto=true)
@ -583,7 +609,12 @@ export class WeaponSystem {
// (для tap-to-shoot на мобиле). Точка применяется один раз. // (для tap-to-shoot на мобиле). Точка применяется один раз.
let hit = null; let hit = null;
let ray; let ray;
const aim = this._aimScreenPoint; // aim: разовый клик (_aimScreenPoint) или удержание по курсору (_holdAim,
// только когда курсор свободен — нет pointer-lock).
let aim = this._aimScreenPoint;
if (!aim && this._holdAim && document.pointerLockElement !== this.scene3d?.canvas) {
aim = this._holdAim;
}
try { try {
if (aim) { if (aim) {
ray = this.scene.createPickingRay(aim.x, aim.y, null, camera); ray = this.scene.createPickingRay(aim.x, aim.y, null, camera);