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) {
|