151 lines
6.3 KiB
Diff
151 lines
6.3 KiB
Diff
diff --git a/src/Game.js b/src/Game.js
|
||
--- a/src/Game.js
|
||
+++ b/src/Game.js
|
||
@@ -28,9 +28,12 @@ function Game({ avatarUrl, gender }) {
|
||
// 2) реф для группы «города»
|
||
const cityGroupRef = useRef(null);
|
||
|
||
// 3) реф для группы «интерьера»
|
||
const interiorGroupRef = useRef(null);
|
||
- const cleanupTimerRef = useRef(null);
|
||
+ const cleanupTimerRef = useRef(null);
|
||
+ // Глобальный менеджер прогресса загрузки (используем в GLTFLoader)
|
||
+ const loadingManagerRef = useRef(null);
|
||
+
|
||
// камеры
|
||
const orthoCamRef = useRef(null);
|
||
const fpCamRef = useRef(null);
|
||
const cameraRef = useRef(null);
|
||
const rendererRef = useRef(null);
|
||
@@ -347,6 +350,7 @@ function Game({ avatarUrl, gender }) {
|
||
}));
|
||
}, []);
|
||
|
||
//Телефон
|
||
+
|
||
const scene = new THREE.Scene();
|
||
const playerRef = useRef(null);
|
||
const cityMeshesRef = useRef([]);
|
||
const cityObjectsDataRef = useRef([]);
|
||
const loadedCityObjectsRef = useRef({});
|
||
@@ -744,6 +748,59 @@ function Game({ avatarUrl, gender }) {
|
||
if (!mount) {
|
||
console.log('[DEBUG] mountRef.current не определён!');
|
||
return;
|
||
}
|
||
|
||
+ // ─────────────────────────────────────────────
|
||
+ // Красивый загрузочный оверлей + LoadingManager
|
||
+ // ─────────────────────────────────────────────
|
||
+ let overlayEl = null, barEl = null, textEl = null;
|
||
+ function createLoadingOverlay() {
|
||
+ if (overlayEl) return;
|
||
+ overlayEl = document.createElement('div');
|
||
+ Object.assign(overlayEl.style, {
|
||
+ position: 'fixed', inset: '0', zIndex: 2000,
|
||
+ display: 'flex', flexDirection: 'column',
|
||
+ alignItems: 'center', justifyContent: 'center',
|
||
+ background: 'linear-gradient(135deg,#0f172a,#1e293b)',
|
||
+ color: '#fff', fontFamily: 'system-ui, Arial, sans-serif'
|
||
+ });
|
||
+ textEl = document.createElement('div');
|
||
+ Object.assign(textEl.style, {
|
||
+ fontSize: '24px', fontWeight: 700, opacity: 0.9, marginBottom: '16px'
|
||
+ });
|
||
+ textEl.textContent = 'Загрузка ресурсов...';
|
||
+ overlayEl.appendChild(textEl);
|
||
+ const barWrap = document.createElement('div');
|
||
+ Object.assign(barWrap.style, {
|
||
+ width: '320px', height: '10px',
|
||
+ background: 'rgba(255,255,255,0.15)',
|
||
+ borderRadius: '999px', overflow: 'hidden',
|
||
+ boxShadow: '0 6px 20px rgba(0,0,0,0.35)'
|
||
+ });
|
||
+ barEl = document.createElement('div');
|
||
+ Object.assign(barEl.style, {
|
||
+ width: '0%', height: '100%',
|
||
+ transition: 'width .15s ease',
|
||
+ background: 'linear-gradient(90deg,#22d3ee,#38bdf8,#60a5fa)'
|
||
+ });
|
||
+ barWrap.appendChild(barEl);
|
||
+ overlayEl.appendChild(barWrap);
|
||
+ const pct = document.createElement('div');
|
||
+ Object.assign(pct.style, { marginTop: '12px', fontSize: '14px', opacity: 0.8 });
|
||
+ pct.id = 'loadingPct';
|
||
+ pct.textContent = '0%';
|
||
+ overlayEl.appendChild(pct);
|
||
+ document.body.appendChild(overlayEl);
|
||
+ }
|
||
+ function updateLoadingOverlay(percent, text) {
|
||
+ if (!overlayEl) return;
|
||
+ const p = Math.max(0, Math.min(100, Math.round(percent || 0)));
|
||
+ if (barEl) barEl.style.width = p + '%';
|
||
+ const pct = overlayEl.querySelector('#loadingPct');
|
||
+ if (pct) pct.textContent = p + '%';
|
||
+ if (text && textEl) textEl.textContent = text;
|
||
+ }
|
||
+ function removeLoadingOverlay() {
|
||
+ if (!overlayEl) return;
|
||
+ overlayEl.style.transition = 'opacity .2s ease';
|
||
+ overlayEl.style.opacity = '0';
|
||
+ setTimeout(() => {
|
||
+ overlayEl && overlayEl.remove();
|
||
+ overlayEl = barEl = textEl = null;
|
||
+ }, 220);
|
||
+ }
|
||
+ // Общий менеджер загрузки (для GLTF/Texture и т.п.)
|
||
+ const loadingManager = new THREE.LoadingManager();
|
||
+ loadingManagerRef.current = loadingManager;
|
||
+ loadingManager.onStart = (_url, loaded, total) => {
|
||
+ createLoadingOverlay();
|
||
+ updateLoadingOverlay(total ? (loaded / total) * 100 : 5, 'Загрузка ресурсов...');
|
||
+ };
|
||
+ loadingManager.onProgress = (_url, loaded, total) => {
|
||
+ updateLoadingOverlay(total ? (loaded / total) * 100 : 50);
|
||
+ };
|
||
+ loadingManager.onLoad = () => {
|
||
+ updateLoadingOverlay(100, 'Инициализация сцены...');
|
||
+ setTimeout(removeLoadingOverlay, 150);
|
||
+ };
|
||
+
|
||
console.log('–– useEffect начало');
|
||
|
||
const baseOffset = new THREE.Vector3(-200, 150, -200);
|
||
const planarDist = Math.hypot(baseOffset.x, baseOffset.z);
|
||
const radius = Math.hypot(planarDist, baseOffset.y);
|
||
@@ -825,8 +882,9 @@ function Game({ avatarUrl, gender }) {
|
||
socket.on('economy:inventory', setInventory);
|
||
socket.on('gameTime:update', ({ time }) => setGameTime(time));
|
||
- const gltfLoader = new GLTFLoader();
|
||
- const animLoader = new GLTFLoader();
|
||
+ // Лоадеры, учитывающиеся в прогрессе через loadingManagerRef
|
||
+ const gltfLoader = new GLTFLoader(loadingManagerRef.current || undefined);
|
||
+ const animLoader = new GLTFLoader(loadingManagerRef.current || undefined);
|
||
|
||
async function loadPlayerModel(avatarUrl) {
|
||
return new Promise((resolve, reject) => {
|
||
gltfLoader.load(avatarUrl, (gltf) => {
|
||
if (!gltf.scene) return reject('GLTF.scene отсутствует');
|
||
@@ -1168,6 +1226,18 @@ function Game({ avatarUrl, gender }) {
|
||
setSelectedHouse(null);
|
||
}
|
||
|
||
+ // Мини-лоадер при загрузке интерьеров (обёртка поверх loadInteriorScene)
|
||
+ const _origLoadInteriorScene = loadInteriorScene;
|
||
+ loadInteriorScene = async (interiorId) => {
|
||
+ try {
|
||
+ // показываем мини-оверлей на время подзагрузки интерьера
|
||
+ createLoadingOverlay();
|
||
+ updateLoadingOverlay(30, 'Загрузка интерьера...');
|
||
+ await _origLoadInteriorScene(interiorId);
|
||
+ } finally {
|
||
+ setTimeout(removeLoadingOverlay, 120);
|
||
+ }
|
||
+ };
|
||
+
|
||
function onMouseWheel(e) {
|
||
e.preventDefault();
|
||
const delta = -e.deltaY * 0.001;
|
||
|
||
if (e.ctrlKey) {
|