Вопрос или проблема
У меня есть сложный мобильный проект в 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()
, вызывают долговременные операции или бесконечные циклы.
Возможные причины и решения
-
Бесконечный цикл в
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; // Прерываем выполнение }
-
Инициализация объектов в
Plate
и зависимость отPlateManager
: Убедитесь, что в классеPlate
вы корректно инициализируете переменные и не выстраиваете циклы зависимостей, когда, например,Plate
вызывает методы уPlateManager
, который, в свою очередь, обращается кPlate
. Проверьте порядок инициализации и зависимостей между этими классами. -
Состояния и флаги: Рассмотрите возможность неправильного состояния флагов, таких как
isProcessing
. Убедитесь, что везде, где вы используете состояния, проверяется их актуальность и корректность. Определите, возможна ли ситуация, когда ваш флаг остается установленным в одно из состояний, заставляя код повторять шаги. -
Проверка на
null
: В многих ваших местах в коде используются проверки наnull
, как вswitchController
. Однако, еслиManager
илиgameObject
не инициализированы должным образом, это может вызвать сбой. Пожалуйста, убедитесь, что все переменные жестко инициализированы и не допускают возвратаnull
. -
Безопасная логика исполнения: Чтобы предотвратить зависания во время работы, вы можете реализовать проверку на флаг выполняемой задачи. Вместо прямого вызова функций, используя корутины и ставя их в очередь.
Общие рекомендации
- Регулярно проверяйте и тестируйте каждую часть вашего кода по отдельности, чтобы выявить, на каком этапе происходит сбой.
- Используйте отладочный вывод. Везде, где это возможно, добавляйте
Debug.Log()
, чтобы следить за состоянием ваших переменных и вызовов функций. - Применяйте паттерны проектирования, такие как состояние или управление потоками, если ваши операции становятся слишком сложными.
Заключение
Ваша проблема с Unity, вероятно, связана с недочетами в инициализации и обработке состояний в классе PlateManager
и связанной логикой в других классах. Проверив вышеописанные места, вы сможете диагностировать и устранить проблему с зацикливанием. Если потребуется дополнительная помощь или разбор конкретных частей кода, всегда можно обратиться за помощью.
Удачи в вашем проекте Cake Sort 3D!