Доработка панели ввода призов персонажей

This commit is contained in:
Сергей Бондарь 2026-08-25 13:20:51 +03:00
parent 963115e311
commit 0cfd38da13
9 changed files with 551 additions and 274 deletions

View File

@ -423,6 +423,7 @@ GameObject:
- component: {fileID: 6087319970201647579}
- component: {fileID: 5163440344643624749}
- component: {fileID: 7572701421691474762}
- component: {fileID: 1117177583243168359}
m_Layer: 5
m_Name: Added Prizes Table
m_TagString: Untagged
@ -487,6 +488,32 @@ MonoBehaviour:
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &1117177583243168359
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1230475039650522997}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.VerticalLayoutGroup
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: 0
m_ChildControlHeight: 0
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0
--- !u!1 &1590808388917995624
GameObject:
m_ObjectHideFlags: 0
@ -719,12 +746,11 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 852fdedcfe3484442b0d7e16ba459a3b, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::CharacterSelectionPanel
CharacterNameLabel: {fileID: 8964039495605377843}
CharacterIcon: {fileID: 5624866514520992697}
ClickSound: {fileID: 8300000, guid: 1c48900c9ab0aa8418920a8193050870, type: 3}
_characterNameLabel: {fileID: 8964039495605377843}
_characterIcon: {fileID: 5624866514520992697}
_prizeTable: {fileID: 3273224345942325796}
_prizeViewPrefab: {fileID: 1203032466002125781, guid: f8172e74629723c4e993e8ac2f322bdf, type: 3}
buttons:
_buttons:
- {fileID: 1081245877292184043}
- {fileID: 6492870122095265573}
_selectButton: {fileID: 1081245877292184043}
@ -1369,10 +1395,9 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::PrizesSelector
_addedPrizesTable: {fileID: 6087319970201647579}
_prizeViewPrefab: {fileID: 1203032466002125781, guid: f8172e74629723c4e993e8ac2f322bdf, type: 3}
_availablePrizesTable: {fileID: 978156554344200501}
skipFrame: 0
pointer: {fileID: 2717940143288701108}
_selectButton: {fileID: 6745117456236530743}
_pointer: {fileID: 2717940143288701108}
--- !u!1 &8288873238761209220
GameObject:
m_ObjectHideFlags: 0
@ -1464,12 +1489,10 @@ MonoBehaviour:
m_EditorClassIdentifier: Assembly-CSharp::AvailablePrizesTable
_prizeButtonPrefab: {fileID: 942699711437869465, guid: f4d8e373cd4ebc948abb9692f14709e6, type: 3}
_buttonsRoot: {fileID: 4388752671798834034}
prizeButtons: []
countablePrizeConfigurator: {fileID: 5680088572919083885, guid: 8b123b0e9e795b842948d07b6e135d84, type: 3}
qrPrizeConfigurator: {fileID: 8201104754896611310, guid: 419ede247f441324681aeb7eeacf4ea0, type: 3}
vitrinaPrizeConfigurator: {fileID: 2013129107579668837, guid: fa4e8b6466206ac4cb113d8b98d3a8e5, type: 3}
root: {fileID: 4098477273951441249}
selector: {fileID: 2866604199148495954}
_countablePrizeConfigurator: {fileID: 5680088572919083885, guid: 8b123b0e9e795b842948d07b6e135d84, type: 3}
_qrPrizeConfigurator: {fileID: 8201104754896611310, guid: 419ede247f441324681aeb7eeacf4ea0, type: 3}
_vitrinaPrizeConfigurator: {fileID: 2013129107579668837, guid: fa4e8b6466206ac4cb113d8b98d3a8e5, type: 3}
_configuratorsRoot: {fileID: 4098477273951441249}
--- !u!114 &2520044121805980792
MonoBehaviour:
m_ObjectHideFlags: 0
@ -1676,6 +1699,17 @@ PrefabInstance:
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: c244207dd10914141af206f200c91b0a, type: 3}
--- !u!114 &6745117456236530743 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 6123304204807351912, guid: c244207dd10914141af206f200c91b0a, type: 3}
m_PrefabInstance: {fileID: 675856799151252063}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6422f146b37ef714db72d2550697c362, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::MSignalButton
--- !u!224 &8162745928107063912 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 8657825045506627639, guid: c244207dd10914141af206f200c91b0a, type: 3}

View File

@ -18836,7 +18836,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 0
m_IsActive: 1
--- !u!224 &1967587903
RectTransform:
m_ObjectHideFlags: 0
@ -22504,7 +22504,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 5423109734219761443, guid: dcef42dd0beb95f4e9b230192966f091, type: 3}
propertyPath: m_IsActive
value: 0
value: 1
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []

View File

@ -1,22 +1,65 @@
using CharacterSystem;
using CharacterSystem;
using PrizeSystem;
using System;
using UnityEngine;
public class CharacterPrizeConfigurator : MonoBehaviour
{
public event Action OnClosed;
[SerializeField] private CharacterSelectionPanel _characterSelection;
[SerializeField] private PrizesSelector _prizesSelector;
private void OnEnable()
private void Awake()
{
_characterSelection.gameObject.SetActive(true);
_prizesSelector.gameObject.SetActive(false);
_characterSelection.OnCharacterSelected += OnCharacterSelected;
_characterSelection.OnCharacterSelected += OpenPrizeSelector;
_characterSelection.OnExitRequested += SaveAndClose;
_prizesSelector.OnConfirmed += OpenCharacterSelection;
}
private void OnCharacterSelected(Character character)
private void OnEnable()
{
OpenCharacterSelection();
}
private void Start()
{
OpenCharacterSelection();
}
private void OnDestroy()
{
_characterSelection.OnCharacterSelected -= OpenPrizeSelector;
_characterSelection.OnExitRequested -= SaveAndClose;
_prizesSelector.OnConfirmed -= OpenCharacterSelection;
}
private void OpenPrizeSelector(Character character)
{
_characterSelection.gameObject.SetActive(false);
_prizesSelector.Open(character);
ControlsPerformer.Instance.SetControlable(_prizesSelector);
}
private void OpenCharacterSelection()
{
_prizesSelector.gameObject.SetActive(false);
_characterSelection.gameObject.SetActive(true);
_characterSelection.Open();
if (ControlsPerformer.Instance != null)
ControlsPerformer.Instance.SetControlable(_characterSelection);
}
private void SaveAndClose()
{
PrizeSerializer.SaveToFile();
PrizeSystemManager.SavePrizes();
if (ControlsPerformer.Instance != null)
ControlsPerformer.Instance.SetControlable(null);
gameObject.SetActive(false);
OnClosed?.Invoke();
}
}

View File

@ -1,81 +1,41 @@
using PrizeSystem;
using PrizeSystem;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Serialization;
public class AvailablePrizesTable : MonoBehaviour
{
public PrizeButton _prizeButtonPrefab;
public event Action<Prize> OnPrizeConfigured;
[SerializeField] RectTransform _buttonsRoot;
[SerializeField] private PrizeButton _prizeButtonPrefab;
[SerializeField] private RectTransform _buttonsRoot;
[SerializeField, FormerlySerializedAs("countablePrizeConfigurator")] private CountablePrizeConfigurator _countablePrizeConfigurator;
[SerializeField, FormerlySerializedAs("qrPrizeConfigurator")] private QrPrizeConfigurator _qrPrizeConfigurator;
[SerializeField, FormerlySerializedAs("vitrinaPrizeConfigurator")] private VitrinaPrizeConfigurator _vitrinaPrizeConfigurator;
[SerializeField, FormerlySerializedAs("root")] private RectTransform _configuratorsRoot;
public List<PrizeButton> prizeButtons;
public CountablePrizeConfigurator countablePrizeConfigurator;
public QrPrizeConfigurator qrPrizeConfigurator;
public VitrinaPrizeConfigurator vitrinaPrizeConfigurator;
private readonly List<PrizeButton> _prizeButtons = new();
public PrizeSet cachedSet;
public RectTransform root;
public PrizesSelector selector;
public IReadOnlyList<PrizeButton> PrizeButtons => _prizeButtons;
public PrizeButton PrizeButtonPrefab => _prizeButtonPrefab;
public void Refresh(PrizeSet set)
public void Refresh(PrizeSet prizeSet)
{
cachedSet = set;
ClearButtons();
prizeButtons = new();
foreach (var type in GetMissingPrizeTypes(set.Prizes))
foreach (PrizeType type in GetMissingPrizeTypes(prizeSet.Prizes))
{
var button = Instantiate(_prizeButtonPrefab, _buttonsRoot);
PrizeButton button = Instantiate(_prizeButtonPrefab, _buttonsRoot);
button.Configure(type);
button.OnPrize += OnPrizeTypeSelected;
prizeButtons.Add(button);
button.OnPrize += OpenConfigurator;
_prizeButtons.Add(button);
}
}
private void OnPrizeTypeSelected(PrizeType type)
public static List<PrizeType> GetMissingPrizeTypes(IReadOnlyCollection<Prize> prizes)
{
print($"Выбран приз {type}");
BasePrizeConfigurator window = type switch
{
PrizeType.Hopper => countablePrizeConfigurator,
PrizeType.Marbles => countablePrizeConfigurator,
PrizeType.Ticket => countablePrizeConfigurator,
PrizeType.VitrinaBox => vitrinaPrizeConfigurator,
PrizeType.Qr => qrPrizeConfigurator
};
var configurator = Instantiate(window, root);
configurator.Configure(type);
configurator.OnPrizeConfigured += Configurator_OnPrizeConfigured;
ControlsPerformer.Instance.SetControlable(configurator);
}
private void Configurator_OnPrizeConfigured(Prize prize)
{
cachedSet.Prizes.Add(prize);
Refresh(cachedSet);
selector.skipFrame = true;
}
private void ClearButtons()
{
for (int i = 0; i < _buttonsRoot.childCount; i++)
{
var go = _buttonsRoot.GetChild(i).gameObject;
Destroy(go);
}
}
public static List<PrizeType> GetMissingPrizeTypes(List<Prize> prizes)
{
// Все типы призов (кроме None и Empty)
var allTypes = new List<PrizeType>
PrizeType[] allTypes =
{
PrizeType.Hopper,
PrizeType.Marbles,
@ -84,62 +44,63 @@ public class AvailablePrizesTable : MonoBehaviour
PrizeType.VitrinaBox
};
// Определяем, есть ли в списке хоть один CountablePrize
bool hasCountablePrize = prizes.Any(p => p is CountablePrize);
bool hasCountablePrize = prizes.Any(prize => prize is CountablePrize);
HashSet<PrizeType> existingTypes = prizes
.Where(prize => prize is not EmptyPrize)
.Select(prize => prize.PrizeType)
.ToHashSet();
// Если есть CountablePrize, все счетные типы считаются добавленными
var countableTypes = new HashSet<PrizeType>
{
PrizeType.Hopper,
PrizeType.Marbles,
PrizeType.Ticket
};
// Собираем существующие типы
var existingTypes = new HashSet<PrizeType>();
foreach (var prize in prizes)
{
// Если это EmptyPrize - пропускаем (не влияет на существующие типы)
if (prize is EmptyPrize) continue;
// Если это CountablePrize и у нас уже есть флаг, что счетные есть
if (prize is CountablePrize)
{
// Добавляем все счетные типы, если их еще нет
if (hasCountablePrize)
{
foreach (var type in countableTypes)
{
existingTypes.Add(type);
}
}
else
{
// На случай, если hasCountablePrize false (но такого быть не может)
existingTypes.Add(prize.PrizeType);
}
}
else
{
// Для не-счетных призов добавляем их конкретный тип
existingTypes.Add(prize.PrizeType);
}
}
// Если есть CountablePrize, но не все типы добавлены, добавляем все счетные
if (hasCountablePrize)
{
foreach (var type in countableTypes)
{
existingTypes.Add(type);
}
}
// Возвращаем типы, которых нет в существующих
return allTypes
.Where(type => !existingTypes.Contains(type))
.Where(type => IsCountable(type) ? !hasCountablePrize : !existingTypes.Contains(type))
.ToList();
}
}
private void OpenConfigurator(PrizeType type)
{
BasePrizeConfigurator prefab = GetConfiguratorPrefab(type);
if (prefab == null)
{
Debug.LogError($"Не найден конфигуратор для приза {type}");
return;
}
BasePrizeConfigurator configurator = Instantiate(prefab, _configuratorsRoot);
configurator.OnPrizeConfigured += CompleteConfiguration;
configurator.Configure(type);
ControlsPerformer.Instance.SetControlable(configurator);
}
private BasePrizeConfigurator GetConfiguratorPrefab(PrizeType type)
{
return type switch
{
PrizeType.Hopper => _countablePrizeConfigurator,
PrizeType.Marbles => _countablePrizeConfigurator,
PrizeType.Ticket => _countablePrizeConfigurator,
PrizeType.VitrinaBox => _vitrinaPrizeConfigurator,
PrizeType.Qr => _qrPrizeConfigurator,
_ => null
};
}
private void CompleteConfiguration(Prize prize)
{
OnPrizeConfigured?.Invoke(prize);
}
private void ClearButtons()
{
_prizeButtons.Clear();
for (int i = _buttonsRoot.childCount - 1; i >= 0; i--)
{
Transform child = _buttonsRoot.GetChild(i);
child.SetParent(null, false);
Destroy(child.gameObject);
}
}
private static bool IsCountable(PrizeType type)
{
return type is PrizeType.Hopper or PrizeType.Marbles or PrizeType.Ticket;
}
}

View File

@ -1,9 +1,10 @@
using CharacterSystem;
using CharacterSystem;
using PrizeSystem;
using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
public class CharacterSelectionPanel : MonoBehaviour, IControlable
@ -11,114 +12,127 @@ public class CharacterSelectionPanel : MonoBehaviour, IControlable
public event Action<Character> OnCharacterSelected;
public event Action OnExitRequested;
[SerializeField] TextMeshProUGUI CharacterNameLabel;
[SerializeField] Image CharacterIcon;
[SerializeField] AudioClip ClickSound;
[SerializeField] RectTransform _prizeTable;
[SerializeField] PrizeView _prizeViewPrefab;
private Character[] characters;
private int characterIndex = 0;
[SerializeField] private List<MSignalButton> buttons;
[SerializeField, FormerlySerializedAs("CharacterNameLabel")] private TextMeshProUGUI _characterNameLabel;
[SerializeField, FormerlySerializedAs("CharacterIcon")] private Image _characterIcon;
[SerializeField] private RectTransform _prizeTable;
[SerializeField] private PrizeView _prizeViewPrefab;
[SerializeField, FormerlySerializedAs("buttons")] private List<MSignalButton> _buttons;
[SerializeField] private MSignalButton _selectButton;
[SerializeField] private MSignalButton _exitButton;
private int buttonIndex = 0;
private Character[] _characters;
private int _characterIndex;
private int _buttonIndex;
public int CharactersCount => characters.Length;
public Character SelectedCharacter => characters[characterIndex];
public int CharactersCount => _characters?.Length ?? 0;
public Character SelectedCharacter => _characters[_characterIndex];
public bool ControlsEnabled { get; set; } = true;
private MSignalButton SelectedButton => _buttons[_buttonIndex];
public bool ControlsEnabled { get; set; }
private MSignalButton SelectedButton => buttons[buttonIndex];
private void Update()
private void Awake()
{
UpdateControls();
_selectButton.OnClick += SelectCharacter;
_exitButton.OnClick += RequestExit;
}
private void Start()
{
_selectButton.OnClick += _selectButton_OnClick;
_exitButton.OnClick += _exitButton_OnClick;
_characters = CharactersController.GetCharactersArray();
Open();
}
characters = CharactersController.GetCharactersArray();
characterIndex = 0;
DisplaySelectedCharacter();
private void OnDestroy()
{
_selectButton.OnClick -= SelectCharacter;
_exitButton.OnClick -= RequestExit;
}
buttonIndex = 0;
public void Open()
{
if (_buttons == null || _buttons.Count == 0)
return;
SelectedButton.DeselectAnimation();
_buttonIndex = 0;
SelectedButton.SelectAnimation();
if (_characters == null || _characters.Length == 0)
return;
_characterIndex = Mathf.Clamp(_characterIndex, 0, _characters.Length - 1);
DisplaySelectedCharacter();
}
private void _selectButton_OnClick()
{
OnCharacterSelected?.Invoke(SelectedCharacter);
}
private void _exitButton_OnClick()
{
OnExitRequested?.Invoke();
}
public void SwitchCharacter(int direction)
{
if (direction == 0) return;
if (direction == 0 || CharactersCount == 0)
return;
characterIndex = (characterIndex + direction + characters.Length) % characters.Length;
_characterIndex = (_characterIndex + direction + _characters.Length) % _characters.Length;
DisplaySelectedCharacter();
}
public void SwitchButton(int direction)
{
if (direction == 0) return;
if (direction == 0)
return;
int newIndex = Mathf.Clamp(buttonIndex + direction, 0, buttons.Count - 1);
if (newIndex == buttonIndex) return;
int newIndex = Mathf.Clamp(_buttonIndex + direction, 0, _buttons.Count - 1);
if (newIndex == _buttonIndex)
return;
SelectedButton.DeselectAnimation();
buttonIndex = newIndex;
_buttonIndex = newIndex;
SelectedButton.SelectAnimation();
}
private void DisplaySelectedCharacter()
{
CharacterNameLabel.text = SelectedCharacter.characterName;
CharacterIcon.sprite = SelectedCharacter.visualModule.icon;
ClearTable();
var set = PrizeSystemManager.PrizeContainer.Sets[SelectedCharacter.CharacterIndex];
DisplayPrizeSet(set);
}
public void UpdateControls()
{
if (CharactersCount == 0)
return;
Vector2Int axes = In.GetAxis(JoystickSide.Both, InteractMode.Discrete);
SwitchCharacter(axes.x);
SwitchButton(-axes.y);
SelectedButton.TakeInput(In.GetAction(JoystickSide.Both, InteractMode.Continious));
}
private void SelectCharacter()
{
OnCharacterSelected?.Invoke(SelectedCharacter);
}
private void RequestExit()
{
OnExitRequested?.Invoke();
}
private void DisplaySelectedCharacter()
{
_characterNameLabel.text = SelectedCharacter.characterName;
_characterIcon.sprite = SelectedCharacter.visualModule.icon;
ClearTable();
PrizeSet prizeSet = PrizeSystemManager.PrizeContainer.Sets[SelectedCharacter.CharacterIndex];
DisplayPrizeSet(prizeSet);
}
private void ClearTable()
{
for (int i = _prizeTable.childCount - 1; i >= 0; i--)
{
var go = _prizeTable.GetChild(i).gameObject;
Destroy(go);
Transform child = _prizeTable.GetChild(i);
child.SetParent(null, false);
Destroy(child.gameObject);
}
}
private void DisplayPrizeSet(PrizeSet prizeSet)
{
foreach (var item in prizeSet.Prizes)
foreach (Prize prize in prizeSet.Prizes)
{
var view = Instantiate(_prizeViewPrefab, _prizeTable);
view.SetPrize(item);
PrizeView view = Instantiate(_prizeViewPrefab, _prizeTable);
view.SetPrize(prize);
}
}
}

View File

@ -1,33 +1,45 @@
using PrizeSystem;
using PrizeSystem;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
public class CountablePrizeConfigurator : BasePrizeConfigurator, IControlable
{
public Image icon;
public MNumberStepper numberInput;
[SerializeField, FormerlySerializedAs("icon")] private Image _icon;
[SerializeField, FormerlySerializedAs("numberInput")] private MNumberStepper _numberInput;
private PrizeType _prizeType;
public override void Configure(PrizeType prizeType)
{
icon.sprite = PrizeSystemManager.GetPrizeIcon(prizeType);
_prizeType = prizeType;
_icon.sprite = PrizeSystemManager.GetPrizeIcon(prizeType);
int maxValue = prizeType switch
{
int maxValue = prizeType switch
{
PrizeType.Hopper => 10,
PrizeType.Ticket => 100,
PrizeType.Marbles => 10,
_ => 1
_ => 1
};
numberInput.Initialize(value: 1, min: 1, maxValue);
_numberInput.Initialize(value: 1, min: 1, maxValue);
}
public override void UpdateControls()
{
numberInput.SetDirection(In.GetAxis(JoystickSide.Both, InteractMode.Continious).x);
_numberInput.SetDirection(In.GetAxis(JoystickSide.Both, InteractMode.Continious).x);
if (In.GetAction(JoystickSide.Both, InteractMode.Continious))
if (In.GetAction(JoystickSide.Both, InteractMode.Discrete))
{
Prize prize = PrizeUtils.CreatePrize(CountablePrizeType.Hopper, numberInput.Value);
CountablePrizeType countableType = _prizeType switch
{
PrizeType.Hopper => CountablePrizeType.Hopper,
PrizeType.Ticket => CountablePrizeType.Ticket,
PrizeType.Marbles => CountablePrizeType.Marbles,
_ => CountablePrizeType.Hopper
};
Prize prize = PrizeUtils.CreatePrize(countableType, _numberInput.Value);
Complete(prize);
}
}

View File

@ -1,4 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
@ -6,40 +6,74 @@ namespace PrizeSystem
{
public class PrizeConverter : JsonConverter<Prize>
{
public override Prize ReadJson(JsonReader reader, Type objectType, Prize existingValue, bool hasExistingValue, JsonSerializer serializer)
public override Prize ReadJson(
JsonReader reader,
Type objectType,
Prize existingValue,
bool hasExistingValue,
JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
var prizeTypeToken = jsonObject["PrizeType"];
JObject jsonObject = JObject.Load(reader);
JToken prizeTypeToken = jsonObject["PrizeType"];
if (prizeTypeToken == null)
throw new JsonSerializationException("Missing PrizeType field");
var prizeType = prizeTypeToken.ToObject<PrizeType>();
// Создаем объект нужного типа
Prize prize = prizeType switch
PrizeType prizeType = prizeTypeToken.ToObject<PrizeType>(serializer);
return prizeType switch
{
PrizeType.None => new EmptyPrize(),
PrizeType.Qr => new QrPrize(""), // временный, заполним ниже
PrizeType.VitrinaBox => new VitrinaPrize(Array.Empty<int>()),
PrizeType.Ticket or PrizeType.Marbles or PrizeType.Hopper =>
new CountablePrize(CountablePrizeType.Ticket, 0), // временный
PrizeType.Qr => new QrPrize(jsonObject.Value<string>("PrizeText") ?? string.Empty),
PrizeType.VitrinaBox => new VitrinaPrize(
jsonObject["Boxes"]?.ToObject<int[]>(serializer) ?? Array.Empty<int>()),
PrizeType.Hopper or PrizeType.Ticket or PrizeType.Marbles => new CountablePrize(
jsonObject["Type"]?.ToObject<CountablePrizeType>(serializer)
?? GetCountableType(prizeType),
jsonObject.Value<int?>("Amount") ?? 0),
_ => throw new NotSupportedException($"Unknown prize type: {prizeType}")
};
// Заполняем поля через стандартную десериализацию
using (var subReader = jsonObject.CreateReader())
{
serializer.Populate(subReader, prize);
}
return prize;
}
public override void WriteJson(JsonWriter writer, Prize value, JsonSerializer serializer)
{
var jsonObject = JObject.FromObject(value, serializer);
jsonObject.WriteTo(writer);
writer.WriteStartObject();
WriteProperty(writer, serializer, "PrizeType", value.PrizeType);
switch (value)
{
case CountablePrize countablePrize:
WriteProperty(writer, serializer, "Type", countablePrize.Type);
WriteProperty(writer, serializer, "Amount", countablePrize.Amount);
break;
case QrPrize qrPrize:
WriteProperty(writer, serializer, "PrizeText", qrPrize.PrizeText);
break;
case VitrinaPrize vitrinaPrize:
WriteProperty(writer, serializer, "Boxes", vitrinaPrize.Boxes);
break;
}
writer.WriteEndObject();
}
private static void WriteProperty(
JsonWriter writer,
JsonSerializer serializer,
string propertyName,
object value)
{
writer.WritePropertyName(propertyName);
serializer.Serialize(writer, value);
}
private static CountablePrizeType GetCountableType(PrizeType prizeType)
{
return prizeType switch
{
PrizeType.Hopper => CountablePrizeType.Hopper,
PrizeType.Ticket => CountablePrizeType.Ticket,
PrizeType.Marbles => CountablePrizeType.Marbles,
_ => CountablePrizeType.Hopper
};
}
}
}
}

View File

@ -32,6 +32,13 @@ public class PrizeButton : MSignalButton
_textMeshPro.text = GetText(prizeType);
}
public void Configure(Prize prize)
{
_prizeType = prize.PrizeType;
_image.sprite = PrizeSystemManager.GetPrizeIcon(prize.PrizeType);
_textMeshPro.text = PrizeView.GetPrizeText(prize);
}
private string GetText(PrizeType prizeType)
=> prizeType switch
{
@ -43,4 +50,4 @@ public class PrizeButton : MSignalButton
_ => "ошибка"
};
}
}

View File

@ -1,78 +1,250 @@
using CharacterSystem;
using CharacterSystem;
using PrizeSystem;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;
public class PrizesSelector : MonoBehaviour
public class PrizesSelector : MonoBehaviour, IControlable
{
[SerializeField] RectTransform _addedPrizesTable;
[SerializeField] PrizeView _prizeViewPrefab;
public event Action OnConfirmed;
[SerializeField] AvailablePrizesTable _availablePrizesTable;
int index = 0;
[SerializeField] private RectTransform _addedPrizesTable;
[SerializeField] private AvailablePrizesTable _availablePrizesTable;
[SerializeField] private MSignalButton _selectButton;
[SerializeField, FormerlySerializedAs("pointer")] private RectTransform _pointer;
private readonly List<PrizeButton> _addedPrizeButtons = new();
private PrizeSet _prizeSet;
private SelectionArea _selectionArea;
private int _rowIndex;
private bool _waitForActionRelease;
private PrizeSet set;
public bool ControlsEnabled { get; set; } = true;
public bool skipFrame = false;
private enum SelectionArea
{
Available,
Added,
Confirm
}
[SerializeField] Transform pointer;
private void Awake()
{
_availablePrizesTable.OnPrizeConfigured += OnPrizeConfigured;
_selectButton.OnClick += Confirm;
}
private void OnDestroy()
{
_availablePrizesTable.OnPrizeConfigured -= OnPrizeConfigured;
_selectButton.OnClick -= Confirm;
}
public void Open(Character character)
{
print($"открыто с персонажем {character.CharacterIndex}");
gameObject.SetActive(true);
_prizeSet = PrizeSystemManager.PrizeContainer.Sets[character.CharacterIndex];
RefreshView();
set = PrizeSystemManager.PrizeContainer.Sets[character.CharacterIndex];
UpdateView();
_selectionArea = _availablePrizesTable.PrizeButtons.Count > 0
? SelectionArea.Available
: GetInitialFallbackArea();
_rowIndex = 0;
_waitForActionRelease = In.GetAction(JoystickSide.Both, InteractMode.Continious);
RebuildLayouts();
UpdatePointer();
}
private void UpdateView()
public void UpdateControls()
{
ClearTable();
DisplayPrizeSet(set);
_availablePrizesTable.Refresh(set);
}
private void Update()
{
if (skipFrame)
bool actionPressed = In.GetAction(JoystickSide.Both, InteractMode.Continious);
if (_waitForActionRelease)
{
if(In.AnyButton() == false)
skipFrame = false;
_waitForActionRelease = actionPressed;
return;
}
if (_availablePrizesTable.prizeButtons == null) return;
if (_availablePrizesTable.prizeButtons.Count == 0) return;
Vector2Int axes = In.GetAxis(JoystickSide.Both, InteractMode.Discrete);
NavigateHorizontal(axes.x);
NavigateVertical(axes.y);
GetSelectedButton()?.TakeInput(actionPressed);
}
index -= In.GetAxis(JoystickSide.Both, InteractMode.Discrete).y;
index = Mathf.Clamp(index, 0, _availablePrizesTable.prizeButtons.Count);
pointer.position = _availablePrizesTable.prizeButtons[index].transform.position;
private void RefreshView()
{
ClearAddedPrizes();
DisplayAddedPrizes();
_availablePrizesTable.Refresh(_prizeSet);
}
if (In.GetAction(JoystickSide.Both, InteractMode.Continious))
private void DisplayAddedPrizes()
{
foreach (Prize prize in _prizeSet.Prizes.Where(prize => prize is not EmptyPrize))
{
_availablePrizesTable.prizeButtons[index].ClickAnimation();
PrizeButton button = Instantiate(_availablePrizesTable.PrizeButtonPrefab, _addedPrizesTable);
button.Configure(prize);
button.OnPrize += _ => RemovePrize(prize);
_addedPrizeButtons.Add(button);
}
}
private void ClearTable()
private void ClearAddedPrizes()
{
_addedPrizeButtons.Clear();
for (int i = _addedPrizesTable.childCount - 1; i >= 0; i--)
{
var go = _addedPrizesTable.GetChild(i).gameObject;
Destroy(go);
Transform child = _addedPrizesTable.GetChild(i);
child.SetParent(null, false);
Destroy(child.gameObject);
}
}
private void DisplayPrizeSet(PrizeSet prizeSet)
private void OnPrizeConfigured(Prize prize)
{
foreach (var item in prizeSet.Prizes)
_prizeSet.Prizes.RemoveAll(item => item is EmptyPrize);
_prizeSet.Prizes.Add(prize);
RefreshView();
_selectionArea = SelectionArea.Added;
_rowIndex = _addedPrizeButtons.Count - 1;
_waitForActionRelease = true;
RebuildLayouts();
UpdatePointer();
ControlsPerformer.Instance.SetControlable(this);
}
private void RemovePrize(Prize prize)
{
_prizeSet.Prizes.Remove(prize);
EnsureEmptyPrizeWhenNeeded();
RefreshView();
_selectionArea = _availablePrizesTable.PrizeButtons.Count > 0
? SelectionArea.Available
: GetInitialFallbackArea();
_rowIndex = 0;
_waitForActionRelease = true;
RebuildLayouts();
UpdatePointer();
}
private void EnsureEmptyPrizeWhenNeeded()
{
if (_prizeSet.Prizes.Count == 0)
_prizeSet.Prizes.Add(new EmptyPrize());
}
private void NavigateHorizontal(int direction)
{
if (direction < 0 && _availablePrizesTable.PrizeButtons.Count > 0)
{
var view = Instantiate(_prizeViewPrefab, _addedPrizesTable);
view.SetPrize(item);
_selectionArea = SelectionArea.Available;
ClampRowIndex();
UpdatePointer();
}
else if (direction > 0 && _addedPrizeButtons.Count > 0)
{
_selectionArea = SelectionArea.Added;
ClampRowIndex();
UpdatePointer();
}
}
private void NavigateVertical(int direction)
{
if (direction == 0)
return;
if (_selectionArea == SelectionArea.Confirm)
{
if (direction > 0)
SelectBottomAvailableRow();
return;
}
int buttonsCount = GetCurrentButtons().Count;
if (buttonsCount == 0)
{
SelectConfirm();
return;
}
if (direction < 0 && _rowIndex == buttonsCount - 1)
SelectConfirm();
else
_rowIndex = Mathf.Clamp(_rowIndex - direction, 0, buttonsCount - 1);
UpdatePointer();
}
private void SelectBottomAvailableRow()
{
if (_availablePrizesTable.PrizeButtons.Count > 0)
{
_selectionArea = SelectionArea.Available;
_rowIndex = _availablePrizesTable.PrizeButtons.Count - 1;
}
else if (_addedPrizeButtons.Count > 0)
{
_selectionArea = SelectionArea.Added;
_rowIndex = _addedPrizeButtons.Count - 1;
}
UpdatePointer();
}
private void SelectConfirm()
{
_selectionArea = SelectionArea.Confirm;
_rowIndex = 0;
}
private void ClampRowIndex()
{
_rowIndex = Mathf.Clamp(_rowIndex, 0, GetCurrentButtons().Count - 1);
}
private void UpdatePointer()
{
MSignalButton selectedButton = GetSelectedButton();
if (selectedButton != null)
_pointer.position = selectedButton.transform.position;
}
private void RebuildLayouts()
{
Canvas.ForceUpdateCanvases();
LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform)_availablePrizesTable.transform);
LayoutRebuilder.ForceRebuildLayoutImmediate(_addedPrizesTable);
Canvas.ForceUpdateCanvases();
}
private MSignalButton GetSelectedButton()
{
if (_selectionArea == SelectionArea.Confirm)
return _selectButton;
IReadOnlyList<PrizeButton> buttons = GetCurrentButtons();
return buttons.Count > 0 ? buttons[_rowIndex] : null;
}
private IReadOnlyList<PrizeButton> GetCurrentButtons()
{
return _selectionArea == SelectionArea.Added
? _addedPrizeButtons
: _availablePrizesTable.PrizeButtons;
}
private SelectionArea GetInitialFallbackArea()
{
return _addedPrizeButtons.Count > 0 ? SelectionArea.Added : SelectionArea.Confirm;
}
private void Confirm()
{
OnConfirmed?.Invoke();
}
}