458 lines
14 KiB
C#
458 lines
14 KiB
C#
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Linq;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
|
|
public class SettingsScreen : MonoBehaviour
|
|
{
|
|
[SerializeField] RectTransform pointer;
|
|
[SerializeField] List<SettingAction> settingActions;
|
|
private List<Setting> settings;
|
|
|
|
[SerializeField] private List<string> excludedIds;
|
|
|
|
Dictionary<string, Group> groups;
|
|
List<string> groupsOrder;
|
|
List<SettingItem> actionItems;
|
|
|
|
|
|
int currentGroupIndex = 0;
|
|
int currentItemIndex = 0;
|
|
|
|
SettingItem activeSettingItem;
|
|
|
|
public ViewPrefabsProvider viewProvider;
|
|
|
|
|
|
[Header("Настройки генерации")]
|
|
[SerializeField] private RectTransform SettingsContent;
|
|
[SerializeField] private Vector2 GeneralOffset;
|
|
[SerializeField] private GameObject GroupHeaderPrefab;
|
|
[SerializeField] private float GroupHeaderLeftMargin = 0;
|
|
|
|
[SerializeField] private GameObject GroupItemPrefab;
|
|
[SerializeField] private float GroupItemLeftMargin = 20;
|
|
[SerializeField] private Vector2 pointerOffset = new Vector2(-40, -25);
|
|
|
|
[Header("Описания настроек")]
|
|
public Transform DescriptionContainer;
|
|
public SettingDescriptionProvider DescriptionProvider;
|
|
public HeaderIconsProvider IconsProvider;
|
|
private GameObject CurrentDescription;
|
|
|
|
[Header("Настройки scroll view")]
|
|
float minScrollY = 0;
|
|
Vector2 scrollVelocity = Vector2.zero;
|
|
|
|
public AudioClip clip;
|
|
|
|
bool Generated = false;
|
|
private void OnEnable()
|
|
{
|
|
Generate();
|
|
SetupActiveItem();
|
|
}
|
|
private void Update()
|
|
{
|
|
if (!Generated) return;
|
|
|
|
Controls();
|
|
|
|
Vector2 targetContentPosition = new Vector2(0, ContentYPos(activeSettingItem.ItemRect.anchoredPosition.y));
|
|
|
|
|
|
SettingsContent.anchoredPosition = Vector2.SmoothDamp(current: SettingsContent.anchoredPosition,
|
|
target: targetContentPosition,
|
|
currentVelocity: ref scrollVelocity,
|
|
smoothTime: 0.1f);
|
|
|
|
|
|
foreach (SettingItem action in actionItems)
|
|
{
|
|
action.ActionUpdate();
|
|
}
|
|
|
|
|
|
|
|
}
|
|
private void Controls()
|
|
{
|
|
int direction = 0;
|
|
//if (In.GetButtonDownAnyPlayer(PlayerAction.left)) direction -= 1;
|
|
//if (In.GetButtonDownAnyPlayer(PlayerAction.right)) direction += 1;
|
|
if (In.GetButtonAnyPlayer(PlayerAction.left)) direction -= 1;
|
|
if (In.GetButtonAnyPlayer(PlayerAction.right)) direction += 1;
|
|
|
|
if (direction != 0)
|
|
{
|
|
activeSettingItem.Interact(direction);
|
|
|
|
|
|
}
|
|
|
|
bool changeItem = In.GetButtonDown(ButtonPinMapping.down1);
|
|
bool changeGroup = In.GetButtonDown(ButtonPinMapping.down2);
|
|
|
|
if (changeItem)
|
|
{
|
|
string currentGroup = groupsOrder[currentGroupIndex];
|
|
int ItemsCount = groups[currentGroup].Items.Count;
|
|
|
|
currentItemIndex = (currentItemIndex + 1) % ItemsCount;
|
|
}
|
|
if (changeGroup)
|
|
{
|
|
currentGroupIndex = (currentGroupIndex + 1 + groupsOrder.Count) % groupsOrder.Count;
|
|
currentItemIndex = 0;
|
|
}
|
|
|
|
if (changeItem || changeGroup)
|
|
{
|
|
SetupActiveItem();
|
|
SoundManager.instance.playClip(clip);
|
|
}
|
|
}
|
|
|
|
private float ContentYPos(float itemY)
|
|
{
|
|
float baseOffset = -350f;//вынести в поле
|
|
float contentPos = baseOffset + Mathf.Abs(itemY);
|
|
|
|
contentPos = Mathf.Max(contentPos, minScrollY);
|
|
|
|
return contentPos;
|
|
}
|
|
|
|
|
|
|
|
void SetupActiveItem()
|
|
{
|
|
string groupName = groupsOrder[currentGroupIndex];
|
|
Group group = groups[groupName];
|
|
SettingItem item = group.Items[currentItemIndex];
|
|
|
|
activeSettingItem = item;
|
|
pointer.anchoredPosition = item.ItemRect.anchoredPosition + pointerOffset;
|
|
|
|
if (CurrentDescription != null) Destroy(CurrentDescription);
|
|
CurrentDescription = Instantiate(DescriptionProvider.GetDescription(item.GetID()), DescriptionContainer);
|
|
}
|
|
|
|
public void RefreshViews()
|
|
{
|
|
foreach (var group in groupsOrder)
|
|
{
|
|
foreach (SettingItem item in groups[group].Items)
|
|
{
|
|
item.RefreshView();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Generate()
|
|
{
|
|
if (Generated) return;
|
|
|
|
settings = SettingsManager.Container.Settings;
|
|
groupsOrder = new List<string>();
|
|
groups = new Dictionary<string, Group>();
|
|
actionItems = new();
|
|
viewProvider.Initialize();
|
|
|
|
|
|
List<string> groupNames = GetGroupNames(settings, settingActions);
|
|
|
|
//создаю группы.
|
|
foreach (string groupName in groupNames)
|
|
{
|
|
List<Setting> groupSettings = GetSettingsByGroup(settings, groupName);
|
|
List<SettingAction> groupActions = GetActionsByGroup(settingActions, groupName);
|
|
if (AllGroupItemsExcluded(groupSettings, groupActions)) continue;
|
|
|
|
var group = new Group();
|
|
|
|
//заполнить Items
|
|
foreach (Setting setting in groupSettings)
|
|
{
|
|
if (IsExcludedId(setting.Id)) continue;
|
|
|
|
SettingItem item = new SettingItem(setting);
|
|
group.Items.Add(item);
|
|
|
|
}
|
|
foreach (SettingAction action in groupActions)
|
|
{
|
|
if (IsExcludedId(action.Name)) continue;
|
|
|
|
SettingItem item = new SettingItem(action);
|
|
actionItems.Add(item);
|
|
group.Items.Add(item);
|
|
}
|
|
|
|
groupsOrder.Add(groupName);
|
|
groups.Add(groupName, group);
|
|
}
|
|
|
|
//генерит вьюхи
|
|
float NextYPosition = GeneralOffset.y;
|
|
foreach (string groupName in groupsOrder)
|
|
{
|
|
//Заголовок группы
|
|
Vector2 anchoredPosition = new Vector2(GeneralOffset.x + GroupHeaderLeftMargin, NextYPosition);
|
|
GenerateHeader(prefab: GroupHeaderPrefab,
|
|
parent: SettingsContent,
|
|
anchoredPosition: anchoredPosition,
|
|
text: groupName,
|
|
NextYPosition: ref NextYPosition);
|
|
|
|
//Элементы группы
|
|
foreach (var item in groups[groupName].Items)
|
|
{
|
|
anchoredPosition = new Vector2(GeneralOffset.x + GroupItemLeftMargin, NextYPosition);
|
|
//item.SetView(viewProvider.GetViewPrefab(setting.Id));
|
|
GenerateItem(prefab: GroupItemPrefab,
|
|
parent: SettingsContent,
|
|
Item: item,
|
|
anchoredPosition: anchoredPosition,
|
|
NextYPosition: ref NextYPosition);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
Generated = true;
|
|
}
|
|
|
|
private bool IsExcludedId(string id) => excludedIds.Contains(id);
|
|
|
|
/// <summary>
|
|
/// Определяет есть ли в группе хоть один неисключенный элемент
|
|
/// </summary>
|
|
/// <param name="settings">Настройки группы</param>
|
|
/// <param name="actions">Действия группы</param>
|
|
/// <returns></returns>
|
|
private bool AllGroupItemsExcluded(List<Setting> settings, List<SettingAction> actions)
|
|
{
|
|
foreach (var item in settings)
|
|
{
|
|
if (!excludedIds.Contains(item.Id)) return false;
|
|
}
|
|
foreach (var item in actions)
|
|
{
|
|
if (!excludedIds.Contains(item.Name)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
private List<string> GetGroupNames(List<Setting> settings, List<SettingAction> actions)
|
|
{
|
|
// HashSet обеспечивает O(1) для Contains
|
|
var uniqueNames = new HashSet<string>();
|
|
|
|
foreach (var setting in settings)
|
|
{
|
|
if (!string.IsNullOrEmpty(setting.Group))
|
|
uniqueNames.Add(setting.Group);
|
|
}
|
|
|
|
foreach (var action in actions)
|
|
{
|
|
if (!string.IsNullOrEmpty(action.Group))
|
|
uniqueNames.Add(action.Group);
|
|
}
|
|
|
|
return uniqueNames.ToList();
|
|
}
|
|
private List<Setting> GetSettingsByGroup(List<Setting> settings, string group)
|
|
{
|
|
return settings
|
|
.Where(s => s.Group == group)
|
|
.ToList();
|
|
}
|
|
private List<SettingAction> GetActionsByGroup(List<SettingAction> actions, string group)
|
|
{
|
|
return actions
|
|
.Where(a => a.Group == group)
|
|
.ToList();
|
|
}
|
|
|
|
|
|
private void GenerateHeader(GameObject prefab, RectTransform parent, Vector2 anchoredPosition, string text, ref float NextYPosition)
|
|
{
|
|
RectTransform groupHeaderRect = Instantiate(prefab, parent).GetComponent<RectTransform>();
|
|
TextMeshProUGUI headerLabel = groupHeaderRect.GetComponent<TextMeshProUGUI>();
|
|
|
|
groupHeaderRect.anchoredPosition = anchoredPosition;
|
|
NextYPosition -= groupHeaderRect.sizeDelta.y;
|
|
|
|
headerLabel.text = text;
|
|
|
|
//Иконка
|
|
RectTransform iconRect = Instantiate(IconsProvider.GetIcon(text), parent).GetComponent<RectTransform>();
|
|
float headerWidth = headerLabel.GetPreferredValues().x;
|
|
iconRect.anchoredPosition = anchoredPosition + new Vector2(headerWidth + 10, 0);
|
|
|
|
|
|
}
|
|
private void GenerateItem(GameObject prefab, RectTransform parent, SettingItem Item, Vector2 anchoredPosition, ref float NextYPosition)
|
|
{
|
|
RectTransform groupItemRect = Instantiate(prefab, parent).GetComponent<RectTransform>();
|
|
TextMeshProUGUI itemLabel = groupItemRect.GetComponent<TextMeshProUGUI>();
|
|
|
|
groupItemRect.anchoredPosition = anchoredPosition;
|
|
NextYPosition -= groupItemRect.sizeDelta.y;
|
|
|
|
itemLabel.text = Item.GetDisplayName();
|
|
Item.ItemRect = groupItemRect;
|
|
|
|
Item.SetupView(viewProvider);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region NESTED
|
|
class Group
|
|
{
|
|
public List<SettingItem> Items;
|
|
public Group() => Items = new();
|
|
}
|
|
class SettingItem
|
|
{
|
|
public SettingItemType Type;
|
|
|
|
public RectTransform ItemRect; //Отрисованный rect. Для навигации поинтера.
|
|
|
|
public IView view;
|
|
|
|
private Setting setting;
|
|
private SettingAction action;
|
|
|
|
|
|
float timeToInvoke = 1f;
|
|
float elapsedTime = 0f;
|
|
|
|
public float interactDelay = 0;
|
|
public float lastInteract;
|
|
|
|
public void ActionUpdate()
|
|
{
|
|
elapsedTime = Mathf.Clamp(elapsedTime - Time.deltaTime, 0, timeToInvoke);
|
|
view?.ChangeValue((elapsedTime / timeToInvoke).ToString());
|
|
}
|
|
public SettingItem(Setting setting)
|
|
{
|
|
this.setting = setting;
|
|
Type = SettingItemType.Setting;
|
|
|
|
if (setting.Type == SettingType.String) interactDelay = 0.5f;
|
|
|
|
}
|
|
|
|
public SettingItem(SettingAction action)
|
|
{
|
|
this.action = action;
|
|
Type = SettingItemType.Action;
|
|
//вью сделать
|
|
}
|
|
|
|
|
|
public string GetDisplayName()
|
|
{
|
|
return Type switch
|
|
{
|
|
SettingItemType.Setting => setting.Alias,
|
|
SettingItemType.Action => action.Name,
|
|
_ => "<ошибка получения имени SettingItem>"
|
|
};
|
|
}
|
|
public string GetID()
|
|
{
|
|
return Type switch
|
|
{
|
|
SettingItemType.Setting => setting.Id,
|
|
SettingItemType.Action => action.Name,
|
|
_ => "defaultId"
|
|
};
|
|
}
|
|
|
|
public void Interact(int direction)
|
|
{
|
|
if (Type == SettingItemType.Setting)
|
|
{
|
|
if (Time.time - lastInteract > interactDelay)
|
|
{
|
|
SettingsManager.ChangeSetting(setting, direction);
|
|
view.ChangeValue(setting.Id);
|
|
lastInteract = Time.time;
|
|
}
|
|
|
|
|
|
|
|
}
|
|
if (Type == SettingItemType.Action)
|
|
{
|
|
elapsedTime = Mathf.Clamp(elapsedTime + 2 * Time.deltaTime, 0, timeToInvoke);
|
|
if (elapsedTime >= timeToInvoke)
|
|
{
|
|
elapsedTime = 0f;
|
|
action.OnPressed?.Invoke();
|
|
view.OnChangeReady?.Invoke();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SetupView(ViewPrefabsProvider viewProvider)
|
|
{
|
|
|
|
if (Type == SettingItemType.Setting)
|
|
{
|
|
GameObject viewPrefab = viewProvider.GetViewPrefab(setting.Id);
|
|
RectTransform rect = Instantiate(viewPrefab, ItemRect).GetComponent<RectTransform>();
|
|
rect.anchoredPosition = ItemRect.rect.max;//тут мб другое
|
|
|
|
view = rect.GetComponent<IView>();
|
|
if (view == null)
|
|
Debug.LogError($"Нет вью на {viewPrefab}");
|
|
else
|
|
view.Initialize(setting.Id);
|
|
|
|
}
|
|
if (Type == SettingItemType.Action)
|
|
{
|
|
GameObject viewPrefab = viewProvider.GetViewPrefab("Action");
|
|
RectTransform rect = Instantiate(viewPrefab, ItemRect).GetComponent<RectTransform>();
|
|
rect.anchoredPosition = ItemRect.rect.max;//тут мб другое
|
|
|
|
view = rect.GetComponent<IView>();
|
|
if (view == null)
|
|
Debug.LogError($"Нет вью на {viewPrefab}");
|
|
else
|
|
view.Initialize("0");
|
|
}
|
|
}
|
|
|
|
public void RefreshView()
|
|
{
|
|
if (view == null)
|
|
{
|
|
print($"Вью {GetDisplayName()} null");
|
|
return;
|
|
}
|
|
|
|
if (Type == SettingItemType.Setting) view.Initialize(setting.Id);
|
|
else view.Initialize("0");
|
|
}
|
|
|
|
|
|
}
|
|
enum SettingItemType { Setting, Action }
|
|
#endregion
|
|
}
|
|
|