Не могу корректно интегрировать Phaser игру на React страницу

Проблема заключается в том что при открытии страницы, игра написанная на Phaser перекрывает все возможные модули которые должны отображаться. И занимает весь экран, не взирая на стили указанные для блока .

Паралельно в консоль выдавая такую ошибку

     Phaser v3.85.2 (WebGL | Web Audio)  https://phaser.io/v385/ phaser.js:16679:20
TypeError: (void 0) is undefined
    fh GameWindow.js:569
    React 3
    T scheduler.production.min.js:13
    M scheduler.production.min.js:14
    122 scheduler.production.min.js:14
    Webpack 12
react-dom.production.min.js:188:119
Uncaught TypeError: (void 0) is undefined
    fh GameWindow.js:569
    React 3
    T scheduler.production.min.js:13
    M scheduler.production.min.js:14
    122 scheduler.production.min.js:14
    Webpack 12
GameWindow.js:569:8
WebGL warning: texImage: Alpha-premult and y-flip are deprecated for non-DOM-Element uploads.
WebGL warning: generateMipmap: Tex image TEXTURE_2D level 0 is incurring lazy initialization.
WebGL warning: generateMipmap: Tex image TEXTURE_2D level 0 is incurring lazy initialization.
WebGL warning: generateMipmap: Tex image TEXTURE_2D level 0 is incurring lazy initialization.
WebGL warning: generateMipmap: Tex image TEXTURE_2D level 0 is incurring lazy initialization.
WebGL warning: generateMipmap: Tex image TEXTURE_2D level 0 is incurring lazy initialization.
WebGL warning: generateMipmap: Tex image TEXTURE_2D level 0 is incurring lazy initialization.
WebGL warning: generateMipmap: Tex image TEXTURE_2D level 0 is incurring lazy initialization.
WebGL warning: generateMipmap: Tex image TEXTURE_2D level 0 is incurring lazy initialization.
WebGL warning: generateMipmap: Tex image TEXTURE_2D level 0 is incurring lazy initialization.
WebGL warning: generateMipmap: Tex image TEXTURE_2D level 0 is incurring lazy initialization.
WebGL warning: generateMipmap: Tex image TEXTURE_2D level 0 is incurring lazy initialization.
WebGL warning: generateMipmap: Tex image TEXTURE_2D level 0 is incurring lazy initialization. 

Вот как это выглядит

Скриншот некорректной работы

Логика поведения должна быть такова

  1. Подгружаем информацию о пользовател
  2. Если информация о пользователе получена проверяем кол-во энергии
  3. Если у пользователя более или равно 700 энергии ты мы отнимаем 700 энергии и даём пользователю право играть
  4. Окно игры занимает 80% от высоты экрана, нижние оставшиеся 20% процентов занимает меню навигации(Оно отображается автоматически, это прописано в файле App.js)
  5. По окончанию игры пополняем баланс пользователя на количество заработанных очков

К сожалению у меня нет возможности прикрепить укороченную, исполняемую версию кода. Потому что я столкнулся с этой проблемой на позднем этапе разработки. И я не совсем понимаю как сделать это корректно, чтобы передать проблему. Но я надеюсь оставленные комментарии, обилие консоль логов поможет вам понять мой код правильно, и помочь мне....

Вот мой код.

import React, { useContext, useEffect, useState } from "react";
import { observer } from "mobx-react-lite";
import {Context} from "../../../index";
import Phaser from 'phaser';
import { updateUser, check } from "../../../http/userApi";


import coin1 from '../../../img/game/1.svg';
import coin2 from '../../../img/game/2.svg';
import coin3 from '../../../img/game/3.svg';
import coin4 from '../../../img/game/4.svg';
import coin5 from '../../../img/game/5.svg';
import coin6 from '../../../img/game/6.svg';
import coin7 from '../../../img/game/7.svg';
import coin8 from '../../../img/game/8.svg';
import coin9 from '../../../img/game/9.svg';
import coin10 from '../../../img/game/10.svg';
import coin11 from '../../../img/game/11.svg';

import "./GameWindowStyle.css";
 

const GameWindow = observer(() => {

  const [userInfo, setUserInfo] = useState(null);
  const [isUserInfoLoaded, setIsUserInfoLoaded] = useState(false);
  const [gamePass, setGamePass] = useState(false);


   // Первый useEffect: получение данных и обновление состояния
   useEffect(() => {
    const fetchData = async () => {
        try {
            const data = await check(); // Замени check() на свою функцию получения данных
            setUserInfo(data);
            setIsUserInfoLoaded(true); // Устанавливаем флаг, что данные загружены
        } catch (error) {
            console.error("Ошибка при получении данных:", error);
        }
    };

    fetchData();
}, []); // Пустой массив зависимостей для запуска эффекта только один раз при монтировании компонента

    useEffect(() => {
      if (isUserInfoLoaded) {
          const gameStart = async () =>{
        if(userInfo.energy >= 700){

      let newData;
      newData = await updateUser(userInfo.telegramID, userInfo.username, userInfo.role, userInfo.balance, userInfo.level, userInfo.energy - 700);
      setGamePass(true);
    }
    }
    gameStart()
    
    }

    }, [isUserInfoLoaded])

    useEffect( () => {
      if (isUserInfoLoaded) {
        if(gamePass){

            const config = {
                type: Phaser.AUTO,
                width: window.innerWidth < 480 ? window.innerWidth : 480,
                height: window.innerHeight < 854 ? window.innerHeight : 854,
                parent: 'gameContainer',  // Используем id div-а для встраивания игры
                physics: {
                    default: 'matter',
                    matter: {
                        gravity: { y: 1 },
                        debug: {
                            showBody: false,
                            showStaticBody: false,
                            color: 0xff0000,
                            alpha: .5
                        }
                    }
                },
                fps: { min: 60, target: 60 },
                scene: { preload, create, update }
            };
            
        
        const game = new Phaser.Game(config);
        let coins = [];
        let score = 0;
        let scoreText;
        let nextCoinsText;
        let nextCoins = [];
        let activeCoin = null;
        let isCoinFollowing = true;
        let isCoinSpawning = false; // Флаг, указывающий на процесс появления монеты
        
        
        
        let gameActive = true; // Флаг, указывающий, активна ли игра
        let idleTimer; // Таймер простоя
        
        
        const lineY = 110 + 0.25 * config.height; // высота
        
        // Базовый процентный размер монеты (например, 25% от ширины экрана)
        const baseSizePercentage = 0.15;
        
        
        let crossingTimer = 0; // Таймер для отслеживания времени пересечения
        const CROSSING_TIMEOUT = 5000; // Время, после которого игра заканчивается, если монета пересекает линию
        let crossingActive = false; // Флаг, указывающий на пересечение линией
        
        const IDLE_TIMEOUT = 20000; // Время простоя в миллисекундах
        
        
        
        // Массив ассетов монет для каждого уровня
        const coinAssets = [
            'coin1', 'coin2', 'coin3', 'coin4', 'coin5', 
            'coin6', 'coin7', 'coin8', 'coin9', 'coin10', 'coin11'
        ];
        
        // Базовый размер монеты
        const baseSize = 60; // Увеличенный размер монеты
        
        function preload() {
                this.load.svg("coin1", coin1);
                this.load.svg("coin2", coin2);
                this.load.svg("coin3", coin3);
                this.load.svg("coin4", coin4);
                this.load.svg("coin5", coin5);
                this.load.svg("coin6", coin6);
                this.load.svg("coin7", coin7);
                this.load.svg("coin8", coin8);
                this.load.svg("coin9", coin9);
                this.load.svg("coin10", coin10);
                this.load.svg("coin11", coin11);
        }
        
        async function endGame() {
            if (!gameActive) return; // Игнорируем, если игра уже завершена
        
            gameActive = false; // Устанавливаем флаг в false
            console.log('Game ended.'); // Логирование окончания игры
        
            // Удаляем все монеты, счетчик очков и табло
            if (activeCoin) {
                activeCoin.destroy();
                activeCoin = null; // Сбрасываем ссылку на активную монету
                console.log('Active coin destroyed.'); // Логирование удаления активной монеты
            }
            // Удаляем все ранее упавшие монеты
            coins.forEach(coin => coin.destroy());
            coins = []; // Очищаем массив монет
        
            nextCoinsText.removeAll(true); // Удаляем табло с будущими монетами
            scoreText.destroy(); // Удаляем счетчик очков
        
            // Отображаем сообщение о завершении игры
            const finalMessage = this.add.text(config.width / 2, config.height / 2, `Good job,\nyou got ${score} coins`, {
                fontSize: '5vw',
                fontFamily: 'Atkinson Hyperlegible',
                fontWeight: 'bold',
                fill: '#fff'
            }).setOrigin(0.5);
            let newData;
                newData = await updateUser(userInfo.telegramID, userInfo.username, userInfo.role, userInfo.balance + score, userInfo.level, userInfo.energy);
            console.log(`Final score: ${score}`); // Логирование итогового счета
        }
        
        
        function create() {
            // Устанавливаем цвет фона
            this.cameras.main.setBackgroundColor('#1C1C1C');
        
            const groundHeight = 40;
            const groundY = config.height - (config.height * 0.05) - groundHeight;
        
            // Пол
            const groundColor = 0x444444; // Чуть темнее
            this.add.rectangle(config.width / 2, groundY, config.width, groundHeight, groundColor);
            this.matter.add.rectangle(config.width / 2, groundY, config.width, groundHeight, { isStatic: true });
        
            // Создаем текст для следующих монет
            nextCoinsText = this.add.container(config.width / 2, 60); // Панель для следующих монет
            initializeNextCoins.call(this); // Инициализация
            updateNextCoins.call(this); // Генерация монет на табло сразу при запуске игры
        
            // Создаем текст счетчика очков
            scoreText = this.add.text(config.width / 2, 110, 'SCORE: 0', { 
                fontSize: '5vw', // Адаптивный размер шрифта
                fontFamily: 'Atkinson Hyperlegible',
                fontWeight: 'bold', 
                fill: '#fff' 
            }).setOrigin(0.5);
        
            // Добавляем пунктирную линию на 25% ниже счетчика очков
            const dashLength = 10; // Длина штриха
            const spaceLength = 5; // Длина пробела
            for (let i = 0; i < config.width; i += dashLength + spaceLength) {
                this.add.line(0, lineY, i, 0, i + dashLength, 0, 0xffffff, 1); // Создаём отдельные линии
            }
        
            // Стены
            const wallColor = 0x444444; // Чуть темнее
            const wallThickness = 20;
        
            // Создаем визуальные стены
            this.add.rectangle(wallThickness / 2, config.height / 2, wallThickness, config.height, wallColor); // Левое
            this.add.rectangle(config.width - wallThickness / 2, config.height / 2, wallThickness, config.height, wallColor); // Правое
        
            // Добавляем физические стены
            this.matter.add.rectangle(wallThickness / 2, config.height / 2, wallThickness, config.height, { isStatic: true });
            this.matter.add.rectangle(config.width - wallThickness / 2, config.height / 2, wallThickness, config.height, { isStatic: true });
        
            // Блок с возможными монетами, касающийся игрового пола
            const allCoinsContainer = this.add.container(config.width / 2, groundY + groundHeight * 1.25); // Поднимаем блок на половину высоты пола
            updateAllCoins.call(this, allCoinsContainer); // Инициализация блока с монетами
        
            spawnCoin.call(this); // Спавн первой монеты
        
            resetIdleTimer.call(this);
        
            
            let isCoinSpawning = false; // Флаг, указывающий на процесс появления монеты
            
            // Флаг для отслеживания состояния активной монеты
            let activeCoinIsReady = false;
            
            this.input.on('pointerdown', (pointer) => {
                // Проверяем, не идет ли процесс спавна и не готова ли монета
                if (pointer.button === 0 && gameActive && !isCoinSpawning) {
                    resetIdleTimer.call(this); // Сбрасываем таймер при клике
            
                    if (activeCoin) {
                        isCoinFollowing = false; // Прекращаем следование указателя
                        const targetX = pointer.x; // Целевая позиция по X
                        
                        // Учитываем ширину стен и радиус монеты
                        const coinRadius = activeCoin.displayWidth / 2;
                        const leftBoundary = 20 + coinRadius; // Левая граница с учетом радиуса
                        const rightBoundary = config.width - 20 - coinRadius; // Правая граница с учетом радиуса
            
                        // Ограничиваем целевую позицию
                        const constrainedTargetX = Phaser.Math.Clamp(targetX, leftBoundary, rightBoundary);
            
                        const speedFactor = 0.1; // Фактор скорости перемещения
            
                        // Плавное перемещение монеты к целевой позиции
                        const moveToTarget = () => {
                            activeCoin.x += (constrainedTargetX - activeCoin.x) * speedFactor;
            
                            // Проверяем, достигла ли монета целевой точки
                            if (Math.abs(activeCoin.x - constrainedTargetX) > 1) {
                                requestAnimationFrame(moveToTarget); // Продолжаем движение
                            } else {
                                // Создаем физическую монету, когда целевая позиция достигнута
                                createPhysicalCoin.call(this, activeCoin.x);
                            }
                        };
            
                        moveToTarget(); // Запускаем движение к цели
                    }
                }
            });
            
            
            
            
        
            function resetIdleTimer() {
                clearTimeout(idleTimer); // Очищаем предыдущий таймер
            
                idleTimer = setTimeout(() => {
                    endGame.call(this); // Завершаем игру по истечении времени
                }, IDLE_TIMEOUT);
            }
        
        
            
            
            this.matter.world.on('collisionstart', (event) => {
                if (!gameActive) return; // Игнорируем столкновения, если игра завершена
                event.pairs.forEach((pair) => {
                    const bodyA = pair.bodyA;
                    const bodyB = pair.bodyB;
            
                    // Проверяем, что оба объекта являются монетами
                    if (bodyA.gameObject && bodyB.gameObject) {
                        // Проверяем, что монеты одного уровня
                        if (bodyA.gameObject.level === bodyB.gameObject.level && bodyA.gameObject !== bodyB.gameObject) {
                            mergeCoins.call(this, bodyA.gameObject, bodyB.gameObject);
                        }
                    }
                });
            });
            
        }
        
        // Функция для расчета размера монеты на основе размеров экрана
        function getCoinSize(level) {
            const baseSize = config.width * baseSizePercentage; // Базовый размер монеты
            return baseSize * (1 + (level - 1) * 0.15); // Увеличиваем размер в зависимости от уровня монеты
        }
        
        function update() {
            if (!gameActive || coins.length === 0) return; // Игнорируем обновление, если игра завершена или нет монет
        
            scoreText.setText(`SCORE: ${score}`);
        
            // Если активная монета должна следовать за курсором
            if (activeCoin && isCoinFollowing) {
                const pointerX = this.input.activePointer.x;
                const speedFactor = 0.1; // Чем меньше значение, тем медленнее будет следование
        
                const leftBoundary = 20 + activeCoin.displayWidth / 2; // Левая граница
                const rightBoundary = config.width - 20 - activeCoin.displayWidth / 2; // Правая граница
        
                // Плавное перемещение по оси X
                activeCoin.x = Phaser.Math.Clamp(activeCoin.x + (pointerX - activeCoin.x) * speedFactor, leftBoundary, rightBoundary);
        
                // Рассчитываем угол наклона
                const maxTilt = Phaser.Math.DegToRad(25); // Максимальный угол наклона
                const tiltAmount = Phaser.Math.Clamp((pointerX - activeCoin.x) * 0.01, -maxTilt, maxTilt); // Наклон
                activeCoin.setRotation(tiltAmount); // Устанавливаем наклон
            }
        
            // Проверка пересечения с пунктирной линией для всех монет
            let isAnyCoinCrossing = false;
        
            coins.forEach((coin) => {
                const coinRadius = coin.displayWidth / 2; // Радиус монеты
                const isCoinOnLine = (coin.y + coinRadius >= lineY && coin.y - coinRadius <= lineY); // Проверка пересечения линии
        
                if (isCoinOnLine) {
                    console.log(`Coin with level ${coin.level} is on the dashed line. X: ${coin.x}, Y: ${coin.y}`); // Логирование
                    isAnyCoinCrossing = true;
                }
            });
        
            if (isAnyCoinCrossing) {
                if (!crossingActive) {
                    crossingActive = true;
                    crossingTimer = 0;
                    console.log('A coin is on the dashed line. Starting the crossing timer.');
                }
            } else {
                if (crossingActive) {
                    console.log('No coins are on the dashed line. Resetting the crossing timer.');
                    crossingActive = false;
                    crossingTimer = 0;
                }
            }
        
            // Увеличиваем таймер пересечения, если активен
            if (crossingActive) {
                crossingTimer += this.game.loop.delta; // Увеличиваем таймер
                console.log(`Crossing timer: ${crossingTimer.toFixed(2)} ms`);
        
                if (crossingTimer >= CROSSING_TIMEOUT) {
                    endGame.call(this); // Завершаем игру при превышении таймаута
                }
            }
        }
        
        
        
        const COIN_SPAWN_INTERVAL = 500; // Время респавна монеты в миллисекундах
        
        
        function createPhysicalCoin(x) {
            const coinSize = getCoinSize(activeCoin.level); // Рассчитываем размер монеты
            const newCoin = this.matter.add.image(x, activeCoin.y, coinAssets[activeCoin.level - 1]);
        
            newCoin.setCircle();
            newCoin.setDisplaySize(coinSize, coinSize); // Устанавливаем адаптивный размер монеты
            newCoin.setBounce(0.6);
            newCoin.level = activeCoin.level;
            newCoin.setIgnoreGravity(false);
            newCoin.setVelocityY(5); // Даем толчок вниз
        
            coins.push(newCoin); // Добавляем монету в массив coins
        
            activeCoin.destroy(); // Уничтожаем активную монету
            activeCoin = null;
        
            // Таймер перед созданием нового указателя
            isCoinSpawning = true;
            setTimeout(() => {
                if (!gameActive) return;
                spawnCoin.call(this);
                isCoinSpawning = false;
            }, COIN_SPAWN_INTERVAL); // Таймер в 500 миллисекунд
        }
        
        
        
        // Функция для проверки, пересекает ли монета пунктирную линию
        function isCrossingDashedLine(coin) {
            const lineY = 110 + 0.25 * config.height; // Высота пунктирной линии
            return coin.y >= lineY; // Проверяем, ниже ли монета линии
        }
        
        // Функция для обработки пересечения
        function handleCrossing() {
            if (!crossingActive) {
                crossingActive = true; // Активируем отслеживание пересечения
                crossingTimer = 0; // Сбрасываем таймер
                console.log('Coin is crossing the dashed line. Timer started.'); // Логирование
            } else {
                console.log('Coin continues to cross the dashed line.'); // Логирование продолжающегося пересечения
            }
        }
        
        // Обновление состояния пересечения при выходе из области пересечения
        this.matter.world.on('collisionend', (event) => {
            event.pairs.forEach((pair) => {
                const bodyA = pair.bodyA;
                const bodyB = pair.bodyB;
        
                if (bodyA.gameObject && bodyB.gameObject) {
                    if (isCrossingDashedLine(bodyA.gameObject) || isCrossingDashedLine(bodyB.gameObject)) {
                        console.log('Coin stopped crossing the dashed line. Resetting timer.'); // Логирование
                        crossingActive = false; // Отключаем отслеживание
                        crossingTimer = 0; // Сбрасываем таймер
                    }
                }
            });
        });
        
        
        
        function initializeNextCoins() {
            // Инициализируем массив с тремя случайными уровнями монет
            nextCoins = [Phaser.Math.Between(1, 3), Phaser.Math.Between(1, 3), Phaser.Math.Between(1, 3)];
        }
        
        function addRandomCoin() {
            // Добавляем новую случайную монету в конец массива
            const randomLevel = Phaser.Math.Between(1, 3); // Уровень от 1 до 3
            nextCoins.push(randomLevel);
        }
        
        function spawnCoin() {
            const coinY = 0.25 * config.height; // Высота спавна монеты
            const nextCoinLevel = nextCoins[0]; // Берем первый уровень монеты из массива
            const asset = coinAssets[nextCoinLevel - 1];
        
            // Спавним активную монету по центру
            const coinX = config.width / 2;
        
            activeCoin = this.add.image(coinX, coinY, asset); // Создаем обычное изображение монеты, без физики
        
            // Устанавливаем адаптивный размер монеты
            let coinSize = getCoinSize(nextCoinLevel);
            activeCoin.setDisplaySize(coinSize, coinSize);
        
            // Устанавливаем уровень монеты
            activeCoin.level = nextCoinLevel;
            console.log(`Spawned coin with level: ${activeCoin.level}`); // Логирование уровня монеты
        
            isCoinFollowing = true; // Монета будет следовать за указателем
        
            // Удаляем первую монету из массива и добавляем новую случайную монету
            nextCoins.shift();
            addRandomCoin.call(this);
            updateNextCoins.call(this);
        }
        
        
        
        
        function updateNextCoins() {
            // Удаляем старые монеты из контейнера
            nextCoinsText.removeAll(true);
            
            // Обновляем текст с монетами
            nextCoins.forEach((level, index) => {
                const asset = coinAssets[level - 1];
                const coinImage = this.add.image(0, 0, asset);
                coinImage.setDisplaySize(50, 50); // Уменьшенный размер для монет в табло
        
                // Позиционируем монеты в контейнере с небольшим расстоянием между ними
                coinImage.x = (index - 1) * 70; // Устанавливаем расстояние в 70 пикселей между монетами
                nextCoinsText.add(coinImage);
            });
        }
        
        function updateAllCoins(container) {
            const wallThickness = 20; // Толщина стен
            const groundHeight = 40; // Высота пола
            const groundY = config.height - (config.height * 0.05) - groundHeight; // Положение пола
        
            // Определяем доступную ширину контейнера (между боковыми стенами)
            const availableWidth = config.width - 2 * wallThickness;
        
            // Количество монет
            const totalCoins = coinAssets.length;
        
            // Рассчитываем адаптивный размер монеты
            const maxCoinSize = availableWidth * 0.9 / totalCoins;
            const coinSize = Math.min(maxCoinSize, 60); // Максимальный размер монеты 60px, адаптивный - меньше
        
            // Очищаем контейнер от предыдущих монет
            container.removeAll(true);
        
            // Вычисление отступов между монетами
            const spacing = (availableWidth - totalCoins * coinSize) / (totalCoins - 1);
        
            // Добавляем монеты в контейнер
            coinAssets.forEach((asset, index) => {
                const xPosition = -availableWidth / 2 + index * (coinSize + spacing) + coinSize / 2;
                const coinImage = this.add.image(xPosition, 0, asset);
                coinImage.setDisplaySize(coinSize, coinSize);
        
                container.add(coinImage);
            });
        
            // Рассчитываем отступ от нижней границы игрового поля на радиус монеты
            const containerY = config.height - groundHeight / 1.25; // Учитываем радиус монеты
        
            // Устанавливаем позицию контейнера
            container.setPosition(config.width / 2, containerY);
        }
        
        
        
        
        
        function mergeCoins(coin1, coin2) {
            console.log(`Attempting to merge coins: ${coin1.level} + ${coin2.level}`); // Логируем попытку объединения
        
            if (!coin1 || !coin2 || !coin1.body || !coin2.body) {
                console.error('Attempt to merge with undefined coin objects or one of the coins is destroyed.', coin1, coin2);
                return null;
            }
        
            if (coin1.level < 11) {
                const newLevel = coin1.level + 1;
                const x = (coin1.x + coin2.x) / 2;
                const y = (coin1.y + coin2.y) / 2;
        
                // Создаем новую монету
                const newCoin = this.matter.add.image(x, y, coinAssets[newLevel - 1]);
                const newCoinSize = getCoinSize(newLevel); // Вычисляем адаптивный размер новой монеты
                newCoin.setCircle();
                newCoin.setDisplaySize(newCoinSize, newCoinSize);
                newCoin.setBounce(0.6);
                newCoin.level = newLevel;
        
                // Увеличиваем счетчик очков
                score += 10;
                console.log(`Merged coins into new coin with level: ${newLevel}. Score: ${score}`); // Логирование
        
                // Удаляем старые монеты из массива
                coins = coins.filter(c => c !== coin1 && c !== coin2);
        
                // Уничтожаем старые монеты
                coin1.destroy();
                coin2.destroy();
        
                coins.push(newCoin); // Добавляем новую монету в массив coins
        
                return newCoin;
            }
        
            console.log(`Merge failed: one or both coins at max level.`);
            return null;
        }
        
        
        

      }}
      }, [gamePass])

    return (
            
        <div className="GameWindow">
                < div  id = "root" > </ div > 
                < div  id = "phaser-container" > </ div > 
            <script src="https://cdn.jsdelivr.net/npm/phaser@3/dist/phaser.js"></script>
        </div>
    );
});

export default GameWindow;


Заранее спасибо


Ответы (0 шт):