using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using TMPro; using PrizeSystem; public class RulesPrizePanel : MonoBehaviour { [SerializeField] RectTransform ScaleContainer; [SerializeField] GameObject IconTemplate; [SerializeField] GameObject PointNumberTemplate; [SerializeField] float IconSize = 100; [SerializeField] float PointSize = 30; [SerializeField] float SubpointSize = 15; [SerializeField] float PointOffsetX = 100; [SerializeField] float LinkWidth = 5; [SerializeField] float StrokeWidth = 15; [SerializeField] float StrokeGap = 15; [SerializeField] float prizetextFontsize = 30; [SerializeField] float instructionFontsize = 24; [SerializeField] float giftImageSizeMultiplier = 1; [SerializeField] Sprite circle; [SerializeField] Sprite square; [SerializeField] Sprite giftSprite; [SerializeField] Sprite hopperSprite; [SerializeField] Sprite marblesSprite; [SerializeField] Sprite ticketSprite; [SerializeField] Sprite vitrinaSprite; [SerializeField] Sprite qrSprite; [SerializeField] Color ScaleColor = Color.gray; [SerializeField] Color ScaleColorHighlighted = new Color(1, .7f, 0); private Character[] characters; private List prizes; List generatedObjects; List generatedIcons; List generatedPoints; List generatedLinks; List generatedSubpoints; [SerializeField] RectTransform InstructionContainer; private void OnEnable() => GenerateScale(); private void OnDisable() => Clear(); private Sprite SpriteByType(PrizeType prizeType) { return prizeType switch { PrizeType.Hopper => hopperSprite, PrizeType.Hopper2 => hopperSprite, PrizeType.Ticket => ticketSprite, PrizeType.Marbles => marblesSprite, PrizeType.Qr => qrSprite, PrizeType.VitrinaBox => vitrinaSprite, _ => giftSprite }; } private void Clear() { for (int i = generatedObjects.Count - 1; i >= 0; i--) { Destroy(generatedObjects[i]); } generatedObjects.Clear(); } private void GenerateScale() { characters = CharacterController.I.characters; prizes = PrizeSerializer.LoadPrizes(); generatedObjects = new(); GenerateIcons(); GeneratePoints(); GenerateLinks(); GenerateNumbers(); GenerateSubpoints(); GenerateInstructions(); } private void GenerateIcons() { generatedIcons = new(); for (int i = 0; i < characters.Length; i++) { RectTransform icon = Instantiate(IconTemplate, ScaleContainer).GetComponent(); icon.sizeDelta = new Vector2(IconSize, IconSize); icon.anchoredPosition = new Vector2(IconSize / 2, -IconSize / 2 - IconSize * i); icon.GetComponent().sprite = characters[i].IconSmall; generatedObjects.Add(icon.gameObject); generatedIcons.Add(icon); } } private void GeneratePoints() { List positions = new(); //собираем список точек куда спавнить кружки foreach (RectTransform icon in generatedIcons) { positions.Add((Vector2)icon.localPosition + Vector2.right * PointOffsetX); } // generatedPoints = new(); foreach (Vector2 position in positions) { RectTransform point = Instantiate(IconTemplate, ScaleContainer).GetComponent(); point.sizeDelta = new Vector2(PointSize, PointSize); point.localPosition = position; Image image = point.GetComponent(); image.sprite = circle; image.color = ScaleColor; generatedObjects.Add(point.gameObject); generatedPoints.Add(point); } } private void GenerateLinks() { generatedLinks = new(); List LinkPositions = new(); //собираем точки for (int i = 0; i < generatedPoints.Count - 1; i++) { float y = (generatedPoints[i].transform.localPosition.y + generatedPoints[i + 1].transform.localPosition.y) / 2; LinkPositions.Add(new Vector2(generatedPoints[0].localPosition.x, y)); } //последний уникальный LinkPositions.Add(new Vector2(generatedPoints[^1].localPosition.x, generatedPoints[^1].transform.localPosition.y)); foreach (Vector2 position in LinkPositions) { RectTransform link = Instantiate(IconTemplate, ScaleContainer).GetComponent(); link.sizeDelta = new Vector2(LinkWidth, IconSize); link.localPosition = position; Image image = link.GetComponent(); image.sprite = square; image.color = ScaleColor; generatedObjects.Add(link.gameObject); generatedLinks.Add(link); } } private void GenerateNumbers() { int i = 1; foreach (RectTransform point in generatedPoints) { TextMeshProUGUI label = Instantiate(PointNumberTemplate, ScaleContainer).GetComponent(); label.text = i.ToString(); label.transform.localPosition = point.localPosition; generatedObjects.Add(label.gameObject); i++; } } private void GenerateSubpoints() { generatedSubpoints = new List(); List positions = new List(); foreach (RectTransform point in generatedPoints) { positions.Add((Vector2)point.localPosition + Vector2.down * IconSize / 2); } int index = 0; foreach (Vector2 position in positions) { RectTransform subpoint = Instantiate(IconTemplate, ScaleContainer).GetComponent(); subpoint.localPosition = position; subpoint.sizeDelta = Vector2.one * SubpointSize; Image image = subpoint.GetComponent(); image.sprite = circle; bool prizedIndex = prizes[index].PrizeType != PrizeType.None; Color color = prizedIndex ? ScaleColorHighlighted : ScaleColor; image.color = color; if (prizedIndex) GeneratePrizeView(position, prizes[index]); generatedObjects.Add(subpoint.gameObject); generatedSubpoints.Add(subpoint); index++; } } //private void GeneratePrizeView(Vector2 startPoint, Prize prize) //{ // int strokeAmount = 5; // int offsetX = 15; // //генерация штрихов // for (int i = 0; i < strokeAmount; i++) // { // RectTransform stroke = Instantiate(IconTemplate, ScaleContainer).GetComponent(); // float x = startPoint.x + offsetX + StrokeWidth / 2 + (StrokeGap + StrokeWidth) * i; // float y = startPoint.y; // Vector2 position = new Vector2(x, y); // stroke.localPosition = position; // stroke.sizeDelta = new Vector2(StrokeWidth, LinkWidth); // Image image = stroke.GetComponent(); // image.sprite = square; // image.color = ScaleColor; // generatedObjects.Add(stroke.gameObject); // } // //генерация текста // Vector2 lastStrokePosition = new Vector2(x: startPoint.x + offsetX + StrokeWidth / 2 + (StrokeGap + StrokeWidth) * (strokeAmount - 1), // y: startPoint.y); // TextMeshProUGUI label = Instantiate(PointNumberTemplate, ScaleContainer).GetComponent(); // label.fontSize = prizetextFontsize; // label.alignment = TextAlignmentOptions.MidlineLeft; // //string prizeText = GetPrizeText(prize); // string prizeText = GetPrizeText(prize); // label.text = prizeText; // float prizetextWidth = label.GetPreferredValues(prizeText).x; // RectTransform rect = label.GetComponent(); // Vector2 labelPosition = new Vector2(x: lastStrokePosition.x + StrokeWidth / 2 + StrokeGap + prizetextWidth / 2, // y: lastStrokePosition.y); // rect.localPosition = labelPosition; // rect.sizeDelta = new Vector2(prizetextWidth, IconSize); // generatedObjects.Add(label.gameObject); // //генерация картинки подарка // Vector2 giftPosition = labelPosition + Vector2.right * (IconSize + prizetextWidth) / 2; // RectTransform gift = Instantiate(IconTemplate, ScaleContainer).GetComponent(); // gift.localPosition = giftPosition; // gift.sizeDelta = Vector2.one * IconSize; // gift.GetComponent().sprite = giftSprite; // generatedObjects.Add(gift.gameObject); //} private void GeneratePrizeView(Vector2 startPoint, Prize prize) { int strokeAmount = 5; int offsetX = 15; //генерация штрихов for (int i = 0; i < strokeAmount; i++) { RectTransform stroke = Instantiate(IconTemplate, ScaleContainer).GetComponent(); float x = startPoint.x + offsetX + StrokeWidth / 2 + (StrokeGap + StrokeWidth) * i; float y = startPoint.y; Vector2 position = new Vector2(x, y); stroke.localPosition = position; stroke.sizeDelta = new Vector2(StrokeWidth, LinkWidth); Image image = stroke.GetComponent(); image.sprite = square; image.color = ScaleColor; generatedObjects.Add(stroke.gameObject); } // ПОЗИЦИЯ ПОСЛЕДНЕГО ШТРИХА Vector2 lastStrokePosition = new Vector2( x: startPoint.x + offsetX + StrokeWidth / 2 + (StrokeGap + StrokeWidth) * (strokeAmount - 1), y: startPoint.y ); // 1. СНАЧАЛА ГЕНЕРИРУЕМ ИКОНКУ ПОДАРКА (ТЕПЕРЬ СЛЕВА) float iconX = lastStrokePosition.x + StrokeWidth / 2 + StrokeGap + IconSize / 2; Vector2 iconPosition = new Vector2(iconX, lastStrokePosition.y); RectTransform gift = Instantiate(IconTemplate, ScaleContainer).GetComponent(); gift.localPosition = iconPosition; gift.sizeDelta = Vector2.one * IconSize * giftImageSizeMultiplier; gift.GetComponent().sprite = SpriteByType(prize.PrizeType); generatedObjects.Add(gift.gameObject); // 2. ТЕПЕРЬ ГЕНЕРИРУЕМ ТЕКСТ (СПРАВА ОТ ИКОНКИ) TextMeshProUGUI label = Instantiate(PointNumberTemplate, ScaleContainer).GetComponent(); label.fontSize = prizetextFontsize; label.alignment = TextAlignmentOptions.MidlineLeft; // Оставляем выравнивание влево string prizeText = GetPrizeText(prize); label.text = prizeText; // Получаем ширину текста float prizetextWidth = label.GetPreferredValues(prizeText).x; // Рассчитываем позицию текста СПРАВА от иконки // Текст начинается сразу после иконки + отступ float labelX = iconPosition.x + IconSize / 2 + StrokeGap + prizetextWidth / 2; Vector2 labelPosition = new Vector2(labelX, lastStrokePosition.y); RectTransform rect = label.GetComponent(); rect.localPosition = labelPosition; rect.sizeDelta = new Vector2(prizetextWidth, IconSize); generatedObjects.Add(label.gameObject); } private void GenerateInstructions() { //заполняем призы которые надо проинструктировать HashSet countableTypes = new(); bool hasQr = false; bool hasVitrina = false; foreach (var prize in prizes) { if (prize is CountablePrize cp) countableTypes.Add(cp.Type); if (prize is QrPrize) hasQr = true; if (prize is VitrinaPrize) hasVitrina = true; } if (countableTypes.Count > 0) { string prefix = string.Join(", ", countableToString(countableTypes)); InsertBlock($"{prefix}
Забирать в окне выдачи"); } if (hasQr) { InsertBlock($"Qr-приз" + "
1. После победы откроется окно с Qr-кодом" + "
2. Сфотографировать окно" + "
3. Сканировать Qr-код" + "
4. Отправить сгенерированное сообщение администратору в мессенджере"); } if (hasVitrina) { InsertBlock($"Витрина" + "
1. После победы откроется окно выбором ячейки" + "
2. Выбрать ячейку и ожидать выдачу"); } void InsertBlock(string text) { RectTransform block = Instantiate(PointNumberTemplate, InstructionContainer).GetComponent(); TextMeshProUGUI label = block.GetComponent(); label.text = text; label.alignment = TextAlignmentOptions.TopLeft; label.fontSize = instructionFontsize; generatedObjects.Add(block.gameObject); } } string[] countableToString(IEnumerable values) { List result = new List(); foreach (CountablePrizeType type in values) { if (type == CountablePrizeType.Hopper || type == CountablePrizeType.Hopper2) { result.Add("Маленький приз"); } else if (type == CountablePrizeType.Marbles) { result.Add("Марблс"); } else if (type == CountablePrizeType.Ticket) { result.Add("Билет"); } } return result.ToArray(); } public static string GetPrizeText(Prize prize) { if (prize == null || prize is EmptyPrize) { return ""; } else if (prize is CountablePrize cp) { return prize.PrizeType switch { PrizeType.Hopper => $"{cp.Amount} {Pluralize(cp.Amount, "маленький приз", "маленьких приза", "маленьких призов")}", PrizeType.Hopper2 => $"{cp.Amount} {Pluralize(cp.Amount, "маленький приз", "маленьких приза", "маленьких призов")}", PrizeType.Ticket => $"{cp.Amount} {Pluralize(cp.Amount, "билет", "билета", "билетов")}", PrizeType.Marbles => $"{cp.Amount} марблс", _ => "Приз" }; } else if (prize is VitrinaPrize vp) { return $"Витрина ячейки: {string.Join(" | ", vp.Boxes)}"; } else if (prize is QrPrize qp) { return $"Qr: {qp.PrizeText}"; } return ""; } /// /// Возвращает правильную форму существительного для русского языка в зависимости от числа /// /// Количество предметов /// Форма для 1 (именительный падеж, ед.ч.): "яблоко" /// Форма для 2-4 (родительный падеж, ед.ч.): "яблока" /// Форма для 5+ (родительный падеж, мн.ч.): "яблок" /// Правильная форма существительного public static string Pluralize(int count, string form1, string form2, string form3) { // Приводим число к положительному виду для корректной работы с остатками int absCount = Mathf.Abs(count); // Определяем остатки для проверки правил long lastTwoDigits = absCount % 100; long lastDigit = absCount % 10; // Особый случай: числа от 11 до 19 всегда используют форму 3 if (lastTwoDigits >= 11 && lastTwoDigits <= 19) { return form3; } // Обычные правила склонения switch (lastDigit) { case 1: return form1; case 2: case 3: case 4: return form2; default: return form3; } } }