fix(player): порядок чтения скина + управление камерой 02 + авто-меню

1. Стартовый скин не ставился: блок чтения scene.skins/playerModelType стоял
   НИЖЕ предзагрузки модели и enterPlayMode. Перенёс ВЫШЕ — теперь
   PlayerController при старте видит корректный _playerModelType.
2. Меню открывалось каждые ~4с: onPointerLockChange звал _onExitRequest при
   любой потере lock. В third/front потеря lock = отпустили ПКМ (orbit), это
   НЕ выход. Меню (Esc) только из perma-режимов (first/lockfirst/sideview/
   shift-lock).
3. Управление 02: start() лочит только в perma-режимах; onCanvasClick не лочит
   в third (курсор свободен для GUI/3D-табличек); ПКМ-orbit (onRmbDown/Up);
   onWheel авто-переход third<->first; _isPermaLockMode/_applyCursorVisibility.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
МИН 2026-05-30 09:17:19 +03:00
parent 6a2aeefee5
commit 85f8198c7c

View File

@ -7154,6 +7154,35 @@ export class BabylonScene {
this._syncUserModelColliders(); this._syncUserModelColliders();
} }
// === Тип модели персонажа — РЕШАЕМ ДО предзагрузки/плеера ===
// ВАЖНО: должно стоять ВЫШЕ _loadPrototype и до enterPlayMode, иначе
// PlayerController прочитает старый _playerModelType (баг: пончик 2046
// не ставился — skins.default применялся ниже, после предзагрузки).
// Миграция: старые проекты сохраняли Kenney-модель ('character-a..g');
// форсим R15 bacon-hair. Явно выбранные 'skin_*' не трогаем.
if (state.scene.playerModelType) {
const pmt = state.scene.playerModelType;
this._playerModelType = pmt.startsWith('character-') ? 'skin_bacon-hair' : pmt;
}
// Задача 07: конфиг скинов { default, unlocked, shopVisible, coins, customGlbs }.
if (state.scene.skins && typeof state.scene.skins === 'object') {
this._skinsConfig = {
default: state.scene.skins.default || null,
unlocked: Array.isArray(state.scene.skins.unlocked) ? state.scene.skins.unlocked.slice() : [],
shopVisible: state.scene.skins.shopVisible !== false,
coins: Number.isFinite(state.scene.skins.coins) ? state.scene.skins.coins : 0,
customGlbs: Array.isArray(state.scene.skins.customGlbs) ? state.scene.skins.customGlbs.slice() : [],
};
// Стартовый скин из skins.default имеет приоритет над playerModelType.
if (this._skinsConfig.default) {
const d = this._skinsConfig.default;
this._playerModelType = (d.startsWith('character-') || d.startsWith('skin_') || d.startsWith('customskin:'))
? d : ('skin_' + d);
}
} else {
this._skinsConfig = null;
}
// ОПТИМИЗАЦИЯ: предзагружаем модель ИГРОКА (character) тоже — // ОПТИМИЗАЦИЯ: предзагружаем модель ИГРОКА (character) тоже —
// PlayerController.start() её ждёт, но если предзагрузить сейчас, // PlayerController.start() её ждёт, но если предзагрузить сейчас,
// на enterPlayMode она будет в кэше Babylon и стартует мгновенно. // на enterPlayMode она будет в кэше Babylon и стартует мгновенно.
@ -7251,36 +7280,7 @@ export class BabylonScene {
} }
} }
} catch (e) { console.warn('[BabylonScene] spawn auto-lift failed:', e); } } catch (e) { console.warn('[BabylonScene] spawn auto-lift failed:', e); }
// Тип модели персонажа. // (Тип модели персонажа и skins решены выше — до предзагрузки модели.)
// Миграция: старые проекты сохраняли Kenney-модель ('character-a..g').
// Теперь стандарт — R15-скин bacon-hair. Если в проекте старая
// Kenney-модель — форсим bacon-hair. Явно выбранные 'skin_*' не трогаем.
if (state.scene.playerModelType) {
const pmt = state.scene.playerModelType;
if (pmt.startsWith('character-')) {
this._playerModelType = 'skin_bacon-hair';
} else {
this._playerModelType = pmt;
}
}
// Задача 07: конфиг скинов проекта { default, unlocked, shopVisible, coins, customGlbs }.
if (state.scene.skins && typeof state.scene.skins === 'object') {
this._skinsConfig = {
default: state.scene.skins.default || null,
unlocked: Array.isArray(state.scene.skins.unlocked) ? state.scene.skins.unlocked.slice() : [],
shopVisible: state.scene.skins.shopVisible !== false,
coins: Number.isFinite(state.scene.skins.coins) ? state.scene.skins.coins : 0,
customGlbs: Array.isArray(state.scene.skins.customGlbs) ? state.scene.skins.customGlbs.slice() : [],
};
// Стартовый скин из skins.default имеет приоритет над playerModelType.
if (this._skinsConfig.default) {
const d = this._skinsConfig.default;
this._playerModelType = d.startsWith('character-') || d.startsWith('skin_') || d.startsWith('customskin:')
? d : ('skin_' + d);
}
} else {
this._skinsConfig = null;
}
// Пользовательские скрипты // Пользовательские скрипты
if (Array.isArray(state.scene.scripts)) { if (Array.isArray(state.scene.scripts)) {
this._scripts = state.scene.scripts this._scripts = state.scene.scripts