Unity застревает на Application.EnterPlayMode() после использования сенсорных вводов.

Вопрос или проблема

У меня есть сложный мобильный проект в Unity под названием Cake Sort 3D, который был моей первой фриланс-работой. Я думаю, что мог сделать несколько ошибок в процессе.

Все работало безупречно до тех пор, пока я не адаптировал код движения тарелки для обработки ввода касанием экрана. Я не осознал проблемы, пока не начал сборку проекта. Трекер проблем Unity сообщил, что движок не может войти в игровой режим, и я подумал: “Как это возможно? Ранее все работало нормально.”

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

Вот классы PlateManager и input, а также другие классы, которые могут вызывать бесконечный цикл.

Скрипт движения тарелки

using System.Collections;
using UnityEngine;

public class DragAndDropMouse : MonoBehaviour
{
    private bool _isDragging;
    public GameObject[] plateHolders;
    private GameObject draggingObj;
    private GameObject holderToSnap;
    [SerializeField] public bool isMoveable = true;
    [SerializeField] private Plate plate;
    [SerializeField] private GameObject plane;
    [SerializeField] private PlateManager Manager;
    private ScoreManager scm;
    private SoundPlayer soundPlayer;
    SkillManager sm;
    SwitchController switchController;
    private bool isThereUnused = false;
    Vector3 StartPosition;
    private bool canCreate = true;

    private void Start()
    {
        plate = GetComponent<Plate>();
        sm = GameObject.FindGameObjectWithTag("SkillManager").GetComponent<SkillManager>();
        soundPlayer = GameObject.FindGameObjectWithTag("SoundPlayer").GetComponent<SoundPlayer>();
        plateHolders = GameObject.FindGameObjectsWithTag("PlateHolder");
        scm = GameObject.FindGameObjectWithTag("ScoreManager").GetComponent<ScoreManager>();
        Manager = GameObject.FindGameObjectWithTag("PlateManager").GetComponent<PlateManager>();
        plane = GameObject.FindGameObjectWithTag("Plane");
        if (Manager != null)
        {
            switchController = Manager.GetComponent<SwitchController>();
            Debug.Log(switchController);
        }
        plate.snappedHolder = null;
    }

    private void Update()
    {
        if (Input.touchCount > 0)
        {
            Touch touch = Input.GetTouch(0);

            switch (touch.phase)
            {
                case TouchPhase.Began:
                    OnTouchDown(touch);
                    break;

                case TouchPhase.Moved:
                    OnTouchDrag(touch);
                    break;

                case TouchPhase.Ended:
                    OnTouchUp();
                    break;
            }
        }
    }

    private void OnTouchDown(Touch touch)
    {
        if (switchController == null)
        {
            switchController = Manager?.GetComponent<SwitchController>();
            Debug.Log(switchController != null ? "SwitchController найден." : "SwitchController все еще null.");
        }

        if (switchController != null && switchController.SwitchSkillActive)
        {
            Ray ray2 = Camera.main.ScreenPointToRay(touch.position);
            if (Physics.Raycast(ray2, out RaycastHit hit2) && hit2.collider.tag.StartsWith("Cake"))
            {
                draggingObj = hit2.collider.gameObject;
            }
            Manager.GetComponent<SwitchController>().MouseDownSwitchSkill(hit2.collider.gameObject);
            return;
        }
        if (Manager == null)
        {
            Manager = GetComponent<Plate>().manager;
        }
        Ray ray = Camera.main.ScreenPointToRay(touch.position);
        if (Physics.Raycast(ray, out RaycastHit hit) && hit.collider.tag.StartsWith("Cake") && isMoveable)
        {
            draggingObj = hit.collider.gameObject;
            StartPosition = draggingObj.transform.position;
            draggingObj.GetComponent<BoxCollider>().enabled = false;
            _isDragging = true;
        }
        else if (sm.handSkill && plate.snappedHolder != null)
        {
            draggingObj = hit.collider.gameObject;
            draggingObj.GetComponent<BoxCollider>().enabled = false;
            _isDragging = true;
        }
        else if (sm.hammerSkill && plate.snappedHolder != null)
        {
            hit.collider.gameObject.GetComponent<Plate>().snappedHolder.GetComponent<PlateHolder>().isUsed = false;
            Destroy(hit.collider.gameObject);
            sm.hammerSkill = false;
            sm.ExecuteSkill();
        }
    }

    private void OnTouchDrag(Touch touch)
    {
        if (_isDragging && draggingObj != null)
        {
            Ray ray = Camera.main.ScreenPointToRay(touch.position);
            if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, LayerMask.GetMask("Ignore Raycast")) && hit.collider.gameObject == plane)
            {
                Vector3 newPos = hit.point;
                draggingObj.transform.position = new Vector3(newPos.x, transform.position.y, newPos.z);
            }
        }
    }

    private void OnTouchUp()
    {
        if (switchController.SwitchSkillActive) return;
        MouseUp(draggingObj);
    }

    public void SnapPlateToHolder(GameObject plateObj, GameObject targetHolder)
    {
        var plateComponent = plateObj.GetComponent<Plate>();
        var targetHolderComponent = targetHolder.GetComponent<PlateHolder>();

        if (plateComponent.snappedHolder != null)
        {
            plateComponent.snappedHolder.GetComponent<PlateHolder>().isUsed = false;
        }

        plateObj.transform.position = new Vector3(targetHolder.transform.position.x, plateObj.transform.position.y, targetHolder.transform.position.z);

        plateComponent.snappedHolder = targetHolder;
        targetHolderComponent.isUsed = true;
        plateComponent.GetComponent<DragAndDropMouse>().isMoveable = false;
        holderToSnap = targetHolder;

        if (sm.remainingSkills == 0)
        {
            StartCoroutine(finishController(plateHolders));
        }
        StartCoroutine(plateComponent.CheckSurroundingPlates(plateObj));
    }

    public void MouseUp(GameObject draggingObj)
    {
        if ((draggingObj != null && isMoveable) || sm.handSkill)
        {
            draggingObj.GetComponent<BoxCollider>().enabled = true;
            _isDragging = false;
            GameObject closestHolder = SnapToClosestHolder();

            if (closestHolder != null)
            {
                soundPlayer.PlaySound("Plate");
                SnapPlateToHolder(draggingObj, closestHolder);
            }
            else
            {
                canCreate = false;
                draggingObj.transform.position = StartPosition;
                Debug.Log("Не найдено использованной держалки для тарелки.");
            }

            draggingObj = null;
            sm.ExecuteSkill();
            sm.handSkill = false;
        }
    }

    private GameObject SnapToClosestHolder()
    {
        GameObject closestHolder = null;
        float closestDistance = Mathf.Infinity;

        foreach (GameObject holder in plateHolders)
        {
            if (!holder.GetComponent<PlateHolder>().isUsed)
            {
                float distance = Vector3.Distance(transform.position, holder.transform.position);
                if (distance < closestDistance)
                {
                    closestDistance = distance;
                    closestHolder = holder;
                }
            }
        }

        return closestHolder;
    }

    private IEnumerator finishController(GameObject[] holders) // контролирует, если в игре не осталось ходов
    {
        int counter = 0;

        while (counter <= 5)
        {
            isThereUnused = false;
            foreach (GameObject holder in holders)
            {
                if (!holder.GetComponent<PlateHolder>().isUsed)
                {
                    isThereUnused = true;
                    break; // Завершить контроль, если найдена хотя бы одна неиспользованная держалка
                }
            }

            if (!isThereUnused) 
            {
                yield return new WaitForSeconds(1); // Ждать определенное время
                counter++; // Увеличить счетчик
            }
            else
            {
                yield break;
            }
        }

        scm.ResetLevel();
        GameObject.FindGameObjectWithTag("Lost").SetActive(true);
    }

}

Менеджер тарелок

using System.Collections;
using UnityEngine;

public class PlateManager : MonoBehaviour
{
    [SerializeField] public GameObject[] cakePrefabs;
    [SerializeField] private GameObject platePrefab;
    [SerializeField] private Transform createReference;
    [SerializeField] private Transform[] lerpPositions;
    GameObject[] plates = new GameObject[3];
    public bool isProcessing = false;
    [SerializeField] private float moveSpeed = 2.0f;
    public int plateNumber = 0;

    void Start()
    {
        createNewPlates();
    }

    public void decreaseNumber()
    {
        plateNumber--;
        if (plateNumber == 0)
        {
            createNewPlates();
        }
    }

    public void createNewPlates()
    {
        for (int i = 0; i < 3; i++)
        {
            plates[i] = Instantiate(platePrefab, createReference.position, Quaternion.identity);
            plates[i].GetComponent<Collider>().enabled = false;
            StartCoroutine(plateMover(plates[i], i));
        }
        plateNumber = 3;
    }

    public IEnumerator plateMover(GameObject plate, int index)
    {
        int safetyCounter = 0; 
        int maxIterations = 1000; 

        // Двигать тарелку, пока она не окажется достаточно близко к целевой позиции
        while (Vector3.Distance(lerpPositions[index].position, plate.transform.position) > 0.1f)
        {
            plate.transform.position = Vector3.Lerp(plate.transform.position, lerpPositions[index].position, Time.deltaTime * moveSpeed);

            yield return null;

            safetyCounter++;
            if (safetyCounter > maxIterations)
            {
                Debug.LogError($"Тарелка {index} не может быть перемещена к целевой позиции. Достигнут лимит безопасности!");
                yield break;
            }
        }

        // Убедиться, что тарелка именно на целевой позиции
        plate.transform.position = lerpPositions[index].position;
        plate.GetComponent<Collider>().enabled = true;
    }

    public GameObject[] getCakePrefab(string[] types)
    {
        GameObject[] typesToReturn = new GameObject[types.Length];

        for (int i = 0; i < types.Length; i++)
        {
            string type = types[i];

            if (type.StartsWith("Cake") && int.TryParse(type.Substring(4), out int cakeNumber))
            {
                if (cakeNumber >= 1 && cakeNumber <= cakePrefabs.Length)
                {
                    typesToReturn[i] = cakePrefabs[cakeNumber - 1];
                }
                else
                {
                    Debug.LogWarning("Указанный номер торта недействителен: " + cakeNumber);
                    typesToReturn[i] = null;
                }
            }
            else
            {
                Debug.LogWarning("Недействительный тип торта: " + type);
                typesToReturn[i] = null;
            }
        }

        return typesToReturn;
    }
    public GameObject getCakePrefab(string type)
    {
        if (type.StartsWith("Cake") && int.TryParse(type.Substring(4), out int cakeNumber))
        {
            if (cakeNumber >= 1 && cakeNumber <= 10)
            {
                return cakePrefabs[cakeNumber - 1];
            }
        }
        return null;
    }
}

Класс Plate

using NUnit.Framework;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class Plate : MonoBehaviour
{
    public GameObject snappedHolder;
    public byte CakeIndex = 0;
    public byte CakeIndex2 = 0;
    public int TotalCakeIndex => CakeIndex + CakeIndex2; 
    public bool isEvenTyped;
    public bool isSwapped = false;
    public string[] cakeTypes;
    public string cakeType;
    private GameObject[] prefabs;
    private GameObject prefab;
    [SerializeField] public int maxCakeTypeIndex;
    [SerializeField] public float[] rotations;
    [SerializeField] public PlateManager manager;
    public bool isWorking = false;
    private CakeTransferRemastered cakeTransfer;
    public bool canInstantiateCakes = true;
    private void Start()
    {
        maxCakeTypeIndex = GameObject.FindGameObjectWithTag("ScoreManager").GetComponent<ScoreManager>().maxIndexOfCakes;
        cakeTransfer = GetComponent<CakeTransferRemastered>();
        manager = GameObject.FindGameObjectWithTag("PlateManager").GetComponent<PlateManager>();
        if (canInstantiateCakes)
        {
            InitializePlate();
            InstantiateCakeObjects();
        }
        else
        {
            GetComponent<DragAndDropMouse>().MouseUp(gameObject);
        }
    }

    private void InitializePlate()
    {
        cakeTypes = new string[2];
        gameObject.tag = "Cake" + Random.Range(1, 10);
    }

    private void InstantiateCakeObjects()
    {
        if (Random.Range(1, 3) == 2)
        {
            isEvenTyped = false;
            CakeIndex = (byte)Random.Range(1, 4);
            cakeType = "Cake" + Random.Range(1, maxCakeTypeIndex+1);
            prefab = manager?.getCakePrefab(cakeType);
            InstantiateCakeLayers(prefab, CakeIndex,0);
        }
        else
        {
            isEvenTyped = true;
            CakeIndex = 1;
            CakeIndex2 = 3;
            cakeTypes[0] = "Cake" + Random.Range(1, maxCakeTypeIndex+1);
            cakeTypes[1] = "Cake" + Random.Range(1, maxCakeTypeIndex+1);
            while (cakeTypes[0] == cakeTypes[1])
            {
                cakeTypes[1] = "Cake" + Random.Range(1, maxCakeTypeIndex+1);
            }
            prefabs = manager?.getCakePrefab(cakeTypes);

            if (prefabs != null)
            {
                InstantiateCakeLayers(prefabs[0], CakeIndex, 0);
                InstantiateCakeLayers(prefabs[1], CakeIndex2, CakeIndex);
                if (cakeTransfer.isContainsSingleType(gameObject))
                {
                    cakeTypes[1] = string.Empty;
                }
            }
            else
            {
                Debug.LogWarning("Префаб не найден или PlateManager не назначен.");
            }
        }
    }

    private void InstantiateCakeLayers(GameObject cakePrefab, int layers, int startRotationIndex)
    {
        for (int i = startRotationIndex; i < layers; i++)
        {
            GameObject cakeLayer = Instantiate(cakePrefab, transform);
            cakeLayer.transform.localScale = new Vector3(60f, 2500f, 60f);
            cakeLayer.transform.localPosition = Vector3.zero;
            cakeLayer.transform.eulerAngles = new Vector3(0, rotations[i], 0);
        }
    }

    public IEnumerator CheckSurroundingPlates(GameObject thisPlate)
    {
        isWorking = true;
        List<string> controlledIds = new List<string>();
        int processCount = 0;
        Vector3[] directions = { Vector3.forward, Vector3.back, Vector3.left, Vector3.right };
        Dictionary<GameObject, int> detectedPlates = new Dictionary<GameObject, int>();

 
        ControlDirections(directions, ref detectedPlates);
        bool isCommon = CheckForCommonCakes(thisPlate, detectedPlates);


        while (detectedPlates.Count > 0 && isCommon && processCount <= 4)
        {
            var firstPlate = detectedPlates.OrderBy(x => x.Value).First();

            foreach (GameObject detected in detectedPlates.Keys)
            {
                if (controlledIds.Contains(detected.GetInstanceID().ToString()))
                {
                    processCount++;
                }
                else
                {
                    controlledIds.Add(detected.GetInstanceID().ToString());
                    yield return StartCoroutine(cakeTransfer.StartTransfer(thisPlate, detected, isEvenTyped, detected.GetComponent<Plate>().isEvenTyped));
                }
            }

         
            detectedPlates.Clear();
            ControlDirections(directions, ref detectedPlates);
            isCommon = CheckForCommonCakes(thisPlate, detectedPlates);
        }

        yield return new WaitForEndOfFrame();
        isWorking = false;
    }


    private bool CheckForCommonCakes(GameObject thisPlate, Dictionary<GameObject, int> detectedPlates)
    {
        foreach (GameObject detectedPlate in detectedPlates.Keys)
        {
            if (cakeTransfer.ControlCommonCakes(thisPlate, detectedPlate) >= 1)
            {
                return true;
            }
        }
        return false;
    }

    private void ControlDirections(Vector3[] directions, ref Dictionary<GameObject, int> detectedPlates)
    {
        detectedPlates = new Dictionary<GameObject, int>();

        foreach (Vector3 direction in directions)
        {
            if (Physics.Raycast(transform.position, direction, out RaycastHit hit, 4.0f) && hit.collider.gameObject != gameObject)
            {
                Plate hitPlate = hit.collider.GetComponent<Plate>();
                if (hitPlate != null)
                {
                    detectedPlates.Add(hit.collider.gameObject, hitPlate.TotalCakeIndex);
                }
            }
        }
    }

    private void OnDestroy()
    {
        manager.decreaseNumber();
    }
    private void OnDrawGizmosSelected()
    {
        Gizmos.color = Color.red;
        Gizmos.DrawRay(transform.position, Vector3.forward * 4);
        Gizmos.DrawRay(transform.position, Vector3.back * 4);
        Gizmos.DrawRay(transform.position, Vector3.left * 4);
        Gizmos.DrawRay(transform.position, Vector3.right * 4);
    }
}

Классы DragAndDropMouse и Plate прикреплены к каждому объекту тарелки на сцене в качестве префабов.

Ответ или решение

Причины зацикливания в Unity при использовании Touch Input

Доброго времени суток. Ваша проблема с зацикливанием в Unity во время выполнения метода Application.EnterPlayMode() может иметь несколько причин, связанные с вашим кодом и структурой проекта. В данном ответе мы проведем детальный анализ вашего кода и предоставим рекомендации для разрешения проблемы.

Суть проблемы

Вы описали ситуацию, когда класс PlateManager при инициализации запускает внутреннюю логику, которая может вызывать бесконечный цикл, из-за чего Unity не может войти в режим воспроизведения. Это может происходить, если программы, запущенные в методах Start() и Update(), вызывают долговременные операции или бесконечные циклы.

Возможные причины и решения

  1. Бесконечный цикл в PlateManager: Ваш метод createNewPlates() вызывает plateMover(), который, в свою очередь, использует Lerp() для перемещения объектов. Если по каким-то причинам объекты не достигают целевой позиции, это может вызвать зависание. Проверьте расстояние до целевой точки. Например, в plateMover:

    while (Vector3.Distance(lerpPositions[index].position, plate.transform.position) > 0.1f)

    Убедитесь, что значение lerpPositions[index].position корректно и что объекты действительно не застревают. Можно добавить дополнительную логику или защитный лимит, чтобы сработать, если объект не продвигается:

    if (safetyCounter > maxIterations)
    {
       Debug.LogError($"Plate {index} не может быть перемещен. Элементы выходят за пределы.");
       yield break; // Прерываем выполнение
    }
  2. Инициализация объектов в Plate и зависимость от PlateManager: Убедитесь, что в классе Plate вы корректно инициализируете переменные и не выстраиваете циклы зависимостей, когда, например, Plate вызывает методы у PlateManager, который, в свою очередь, обращается к Plate. Проверьте порядок инициализации и зависимостей между этими классами.

  3. Состояния и флаги: Рассмотрите возможность неправильного состояния флагов, таких как isProcessing. Убедитесь, что везде, где вы используете состояния, проверяется их актуальность и корректность. Определите, возможна ли ситуация, когда ваш флаг остается установленным в одно из состояний, заставляя код повторять шаги.

  4. Проверка на null: В многих ваших местах в коде используются проверки на null, как в switchController. Однако, если Manager или gameObject не инициализированы должным образом, это может вызвать сбой. Пожалуйста, убедитесь, что все переменные жестко инициализированы и не допускают возврата null.

  5. Безопасная логика исполнения: Чтобы предотвратить зависания во время работы, вы можете реализовать проверку на флаг выполняемой задачи. Вместо прямого вызова функций, используя корутины и ставя их в очередь.

Общие рекомендации

  • Регулярно проверяйте и тестируйте каждую часть вашего кода по отдельности, чтобы выявить, на каком этапе происходит сбой.
  • Используйте отладочный вывод. Везде, где это возможно, добавляйте Debug.Log(), чтобы следить за состоянием ваших переменных и вызовов функций.
  • Применяйте паттерны проектирования, такие как состояние или управление потоками, если ваши операции становятся слишком сложными.

Заключение

Ваша проблема с Unity, вероятно, связана с недочетами в инициализации и обработке состояний в классе PlateManager и связанной логикой в других классах. Проверив вышеописанные места, вы сможете диагностировать и устранить проблему с зацикливанием. Если потребуется дополнительная помощь или разбор конкретных частей кода, всегда можно обратиться за помощью.

Удачи в вашем проекте Cake Sort 3D!

Оцените материал
Добавить комментарий

Капча загружается...