Панель правил доработана под новую систему призов

This commit is contained in:
se_ga 2026-03-18 13:10:55 +03:00
parent 9a7ffd1ed0
commit 8a182f6a8d
7 changed files with 1367 additions and 114 deletions

View File

@ -11349,7 +11349,7 @@ Material:
- _ClipRect: {r: -32767, g: -32767, b: 32767, a: 32767}
- _EnvMatrixRotation: {r: 0, g: 0, b: 0, a: 0}
- _FaceColor: {r: 1, g: 1, b: 1, a: 1}
- _GlowColor: {r: 0, g: 1, b: 0.87572384, a: 1}
- _GlowColor: {r: 1, g: 0, b: 0.8832669, a: 1}
- _MaskCoord: {r: 0, g: 0, b: 32767, a: 32767}
- _OutlineColor: {r: 0.554717, g: 0.554717, b: 0.554717, a: 1}
- _ReflectFaceColor: {r: 0, g: 0, b: 0, a: 1}

View File

@ -2428,12 +2428,13 @@ MonoBehaviour:
IconSize: 100
PointSize: 30
SubpointSize: 15
PointOffsetX: 100
LinkWidth: 5
StrokeWidth: 15
StrokeGap: 15
prizetextFontsize: 30
instructionFontsize: 16
PointOffsetX: 75
LinkWidth: 2
StrokeWidth: 10.53
StrokeGap: 2
prizetextFontsize: 22
instructionFontsize: 22
giftImageSizeMultiplier: 1
circle: {fileID: -2413806693520163455, guid: ebe73ca9363db456bacf42c025bb4847, type: 3}
square: {fileID: 7482667652216324306, guid: 48e93eef0688c4a259cb0eddcd8661f7, type: 3}
giftSprite: {fileID: 21300000, guid: 6e622d073fff38b468edb48bec60df75, type: 3}
@ -4498,6 +4499,7 @@ GameObject:
- component: {fileID: 219261899}
- component: {fileID: 219261901}
- component: {fileID: 219261900}
- component: {fileID: 219261902}
m_Layer: 5
m_Name: prize instructions
m_TagString: Untagged
@ -4562,6 +4564,32 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 219261898}
m_CullTransparentMesh: 1
--- !u!114 &219261902
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 219261898}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Padding:
m_Left: 0
m_Right: 0
m_Top: 0
m_Bottom: 0
m_ChildAlignment: 0
m_Spacing: 0
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 1
m_ChildControlWidth: 1
m_ChildControlHeight: 1
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0
--- !u!1 &221607736
GameObject:
m_ObjectHideFlags: 0
@ -9912,9 +9940,9 @@ MonoBehaviour:
\u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u043D\u0438\u0435
\u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C, \u043D\u0435\u0439\u0440\u043E\u0441\u0435\u0442\u0435\u0439
\u0438 \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u0440\u0435\u0434\u0441\u0442\u0432
\u0438\u043B\u0438 \u0441\u0442\u043E\u0440\u043E\u043D\u043D\u0438\u0435 \u043F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0438
\u0438\u043B\u0438 \u0441\u0442\u043E\u0440\u043E\u043D\u043D\u0438\u0445 \u043F\u043E\u0434\u0441\u043A\u0430\u0437\u043E\u043A,
\u0434\u0430\u044E\u0449\u0438\u0445 \u043F\u0440\u0435\u0438\u043C\u0443\u0449\u0435\u0441\u0442\u0432\u0430
\u0438\u0433\u0440\u043E\u043A\u0443"
\u0438\u0433\u0440\u043E\u043A\u0443."
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 3f10bbcf486189d4f95b8de6dc2e2177, type: 2}
m_sharedMaterial: {fileID: 4262305343556741190, guid: 3f10bbcf486189d4f95b8de6dc2e2177, type: 2}
@ -13796,7 +13824,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
adminPasswordFallback: 1234
passwordFileName: admin_password.txt
settingActions: []
--- !u!4 &592995719
Transform:

View File

@ -80,7 +80,7 @@ public class COMinput : InputSource
pinMappings[ButtonPinMapping.hasPrize] = 14;
pinMappings[ButtonPinMapping.prizeGiven] = 15;
Debug.Log($"Используется версия 1 распиновки (значение по умолчанию). Полученная версия: {elektroVersion}");
Debug.Log($"Используется версия 1 распиновки. Полученная версия: {elektroVersion}");

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
@ -97,10 +97,8 @@ public class QrTextInput : MonoBehaviour
{
var sb = new StringBuilder();
foreach (var slot in slots)
{
sb.Append(slot.GetText().Replace("", " "));
}
sb.Append(slot.GetText());
return sb.ToString().Trim();
}

View File

@ -1,4 +1,4 @@
using TMPro;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
@ -14,7 +14,14 @@ public class QrTextSlot : MonoBehaviour
private void OnEnable() => SetActive(false);
public void SetText(string text) => label.text = text;
public string GetText() => label.text;
public string GetText()
{
if(label.text == "")
{
label.text = " ";
}
return label.text;
}
public void SetActive(bool on) => image.color = on ? HighlightedColor : NormalColor;
}

View File

@ -3,6 +3,7 @@ using UnityEngine;
using UnityEngine.UI;
using TMPro;
using PrizeSystem;
using System.Linq;
public class RulesPrizePanel : MonoBehaviour
{
@ -18,7 +19,7 @@ public class RulesPrizePanel : MonoBehaviour
[SerializeField] float StrokeGap = 15;
[SerializeField] float prizetextFontsize = 30;
[SerializeField] float instructionFontsize = 24;
[SerializeField] float giftImageSizeMultiplier = 1;
[SerializeField] Sprite circle;
[SerializeField] Sprite square;
@ -43,12 +44,6 @@ public class RulesPrizePanel : MonoBehaviour
private void OnEnable() => GenerateScale();
private void OnDisable() => Clear();
/* private void OnValidate()
{
Clear();
GenerateScale();
}*/
private void Clear()
{
for (int i = generatedObjects.Count - 1; i >= 0; i--)
@ -194,6 +189,65 @@ public class RulesPrizePanel : MonoBehaviour
}
}
//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;
@ -217,129 +271,187 @@ public class RulesPrizePanel : MonoBehaviour
generatedObjects.Add(stroke.gameObject);
}
// ПОЗИЦИЯ ПОСЛЕДНЕГО ШТРИХА
Vector2 lastStrokePosition = new Vector2(
x: startPoint.x + offsetX + StrokeWidth / 2 + (StrokeGap + StrokeWidth) * (strokeAmount - 1),
y: startPoint.y
);
//генерация текста
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 = giftSprite;
generatedObjects.Add(gift.gameObject);
// 2. ТЕПЕРЬ ГЕНЕРИРУЕМ ТЕКСТ (СПРАВА ОТ ИКОНКИ)
TextMeshProUGUI label = Instantiate(PointNumberTemplate, ScaleContainer).GetComponent<TextMeshProUGUI>();
label.fontSize = prizetextFontsize;
label.alignment = TextAlignmentOptions.MidlineLeft;
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>();
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 GenerateInstructions()
{
//заполняем призы которые надо проинструктировать
List<Prize> prizesWeHave = new();
HashSet<CountablePrizeType> countableTypes = new();
bool hasQr = false;
bool hasVitrina =false;
foreach (var prize in prizes)
{
bool unique = !prizesWeHave.Contains(prize);
bool notEmpty = prize.PrizeType != PrizeType.None;
if (unique && notEmpty)
prizesWeHave.Add(prize);
if (prize is CountablePrize cp) countableTypes.Add(cp.Type);
if (prize is QrPrize) hasQr = true;
if (prize is VitrinaPrize) hasVitrina = true;
}
if (prizesWeHave.Count == 0) return;
float blockWidth = InstructionContainer.sizeDelta.x;
float blockHeight = InstructionContainer.sizeDelta.y / prizesWeHave.Count;
for (int i = 0; i < prizesWeHave.Count; i++)
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>();
Vector2 anchor = new Vector2(0, 1);
block.anchorMin = anchor;
block.anchorMax = anchor;
block.pivot = anchor;
block.sizeDelta = new Vector2(blockWidth, blockHeight);
block.anchoredPosition = new Vector2(0, -blockHeight * i);
label.text = GetPrizeInstruction(prizesWeHave[i]);
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();
}
private string GetPrizeText(Prize prize)
{
string text = "";
if(prize is CountablePrize cp)
if (prize == null || prize is EmptyPrize)
{
if (cp.PrizeType == PrizeType.Marbles || cp.PrizeType == PrizeType.Ticket)
return "";
}
else if (prize is CountablePrize cp)
{
return prize.PrizeType switch
{
text = cp.Amount + " ";
}
text += cp.PrizeType switch
{
PrizeType.Hopper => "приз",
PrizeType.Hopper2 => "приз",
PrizeType.Ticket => "билет",
PrizeType.Marbles => "марблс",
_ => "Без приза"
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;
}
return text;
}
private string GetPrizeInstruction(Prize prize)
{
string HopperInstruction = "<b>Приз</b><br>Заберите маленький приз в окошке выдачи";
string TicketInstruction = "<b>Билет</b><br>Заберите билеты в окошке выдачи";
string SpecialInstruction = "<b>Суперприз</b><br>Чтобы выиграть главный суперприз нужно победить всех соперников. Если вы прошли все уровни, отсканируйте qr-код (он появится после победы) и свяжитесь с администратором для получения приза. Обязательно сфотографируйте победный экран!";
string MarblesInstruction = "<b>Марблс</b><br>Заберите марблс в окошке выдачи";
return prize.PrizeType switch
// Обычные правила склонения
switch (lastDigit)
{
PrizeType.Hopper => HopperInstruction,
PrizeType.Hopper2 => HopperInstruction,
PrizeType.Ticket => TicketInstruction,
PrizeType.Qr => SpecialInstruction,
PrizeType.Marbles => MarblesInstruction,
_ => "Что-то не так"
};
case 1:
return form1;
case 2:
case 3:
case 4:
return form2;
default:
return form3;
}
}
}

File diff suppressed because one or more lines are too long