480 lines
17 KiB
C#
480 lines
17 KiB
C#
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<Prize> prizes;
|
||
List<GameObject> generatedObjects;
|
||
List<RectTransform> generatedIcons;
|
||
List<RectTransform> generatedPoints;
|
||
List<RectTransform> generatedLinks;
|
||
List<RectTransform> 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<RectTransform>();
|
||
icon.sizeDelta = new Vector2(IconSize, IconSize);
|
||
icon.anchoredPosition = new Vector2(IconSize / 2, -IconSize / 2 - IconSize * i);
|
||
|
||
icon.GetComponent<Image>().sprite = characters[i].IconSmall;
|
||
|
||
generatedObjects.Add(icon.gameObject);
|
||
generatedIcons.Add(icon);
|
||
|
||
}
|
||
}
|
||
|
||
private void GeneratePoints()
|
||
{
|
||
List<Vector2> 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<RectTransform>();
|
||
point.sizeDelta = new Vector2(PointSize, PointSize);
|
||
point.localPosition = position;
|
||
|
||
Image image = point.GetComponent<Image>();
|
||
image.sprite = circle;
|
||
image.color = ScaleColor;
|
||
|
||
generatedObjects.Add(point.gameObject);
|
||
generatedPoints.Add(point);
|
||
}
|
||
}
|
||
|
||
|
||
private void GenerateLinks()
|
||
{
|
||
generatedLinks = new();
|
||
List<Vector2> 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<RectTransform>();
|
||
link.sizeDelta = new Vector2(LinkWidth, IconSize);
|
||
link.localPosition = position;
|
||
Image image = link.GetComponent<Image>();
|
||
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<TextMeshProUGUI>();
|
||
label.text = i.ToString();
|
||
label.transform.localPosition = point.localPosition;
|
||
|
||
generatedObjects.Add(label.gameObject);
|
||
i++;
|
||
}
|
||
}
|
||
|
||
private void GenerateSubpoints()
|
||
{
|
||
generatedSubpoints = new List<RectTransform>();
|
||
List<Vector2> positions = new List<Vector2>();
|
||
|
||
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<RectTransform>();
|
||
subpoint.localPosition = position;
|
||
subpoint.sizeDelta = Vector2.one * SubpointSize;
|
||
|
||
Image image = subpoint.GetComponent<Image>();
|
||
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<RectTransform>();
|
||
// 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>();
|
||
// 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<TextMeshProUGUI>();
|
||
// 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<RectTransform>();
|
||
// 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<RectTransform>();
|
||
// gift.localPosition = giftPosition;
|
||
// gift.sizeDelta = Vector2.one * IconSize;
|
||
// gift.GetComponent<Image>().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<RectTransform>();
|
||
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>();
|
||
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<RectTransform>();
|
||
gift.localPosition = iconPosition;
|
||
gift.sizeDelta = Vector2.one * IconSize * giftImageSizeMultiplier;
|
||
|
||
gift.GetComponent<Image>().sprite = SpriteByType(prize.PrizeType);
|
||
|
||
generatedObjects.Add(gift.gameObject);
|
||
|
||
// 2. ТЕПЕРЬ ГЕНЕРИРУЕМ ТЕКСТ (СПРАВА ОТ ИКОНКИ)
|
||
TextMeshProUGUI label = Instantiate(PointNumberTemplate, ScaleContainer).GetComponent<TextMeshProUGUI>();
|
||
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<RectTransform>();
|
||
rect.localPosition = labelPosition;
|
||
rect.sizeDelta = new Vector2(prizetextWidth, IconSize);
|
||
|
||
generatedObjects.Add(label.gameObject);
|
||
}
|
||
|
||
|
||
|
||
|
||
private void GenerateInstructions()
|
||
{
|
||
//заполняем призы которые надо проинструктировать
|
||
HashSet<CountablePrizeType> 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($"<b>{prefix}</b><br>Забирать в окне выдачи");
|
||
}
|
||
if (hasQr)
|
||
{
|
||
InsertBlock($"<b>Qr-приз</b>" +
|
||
"<br>1. После победы откроется окно с Qr-кодом" +
|
||
"<br>2. Сфотографировать окно" +
|
||
"<br>3. Сканировать Qr-код" +
|
||
"<br>4. Отправить сгенерированное сообщение администратору в мессенджере");
|
||
}
|
||
if (hasVitrina)
|
||
{
|
||
InsertBlock($"<b>Витрина</b>" +
|
||
"<br>1. После победы откроется окно выбором ячейки" +
|
||
"<br>2. Выбрать ячейку и ожидать выдачу");
|
||
}
|
||
|
||
void InsertBlock(string text)
|
||
{
|
||
RectTransform block = Instantiate(PointNumberTemplate, InstructionContainer).GetComponent<RectTransform>();
|
||
TextMeshProUGUI label = block.GetComponent<TextMeshProUGUI>();
|
||
|
||
label.text = text;
|
||
label.alignment = TextAlignmentOptions.TopLeft;
|
||
label.fontSize = instructionFontsize;
|
||
|
||
generatedObjects.Add(block.gameObject);
|
||
}
|
||
|
||
}
|
||
|
||
string[] countableToString(IEnumerable<CountablePrizeType> values)
|
||
{
|
||
List<string> result = new List<string>();
|
||
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}</b>";
|
||
}
|
||
return "";
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Возвращает правильную форму существительного для русского языка в зависимости от числа
|
||
/// </summary>
|
||
/// <param name="count">Количество предметов</param>
|
||
/// <param name="form1">Форма для 1 (именительный падеж, ед.ч.): "яблоко"</param>
|
||
/// <param name="form2">Форма для 2-4 (родительный падеж, ед.ч.): "яблока"</param>
|
||
/// <param name="form3">Форма для 5+ (родительный падеж, мн.ч.): "яблок"</param>
|
||
/// <returns>Правильная форма существительного</returns>
|
||
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;
|
||
}
|
||
}
|
||
}
|