using System.Collections.Generic; using System.Data; using System.Linq; using TMPro; using UnityEngine; public class SettingsScreen : MonoBehaviour { [SerializeField] RectTransform pointer; [SerializeField] List settingActions; private List settings; [SerializeField] private List excludedIds; Dictionary groups; List groupsOrder; List 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(); groups = new Dictionary(); actionItems = new(); viewProvider.Initialize(); List groupNames = GetGroupNames(settings, settingActions); //создаю группы. foreach (string groupName in groupNames) { List groupSettings = GetSettingsByGroup(settings, groupName); List 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); /// /// Определяет есть ли в группе хоть один неисключенный элемент /// /// Настройки группы /// Действия группы /// private bool AllGroupItemsExcluded(List settings, List 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 GetGroupNames(List settings, List actions) { // HashSet обеспечивает O(1) для Contains var uniqueNames = new HashSet(); 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 GetSettingsByGroup(List settings, string group) { return settings .Where(s => s.Group == group) .ToList(); } private List GetActionsByGroup(List 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(); TextMeshProUGUI headerLabel = groupHeaderRect.GetComponent(); groupHeaderRect.anchoredPosition = anchoredPosition; NextYPosition -= groupHeaderRect.sizeDelta.y; headerLabel.text = text; //Иконка RectTransform iconRect = Instantiate(IconsProvider.GetIcon(text), parent).GetComponent(); 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(); TextMeshProUGUI itemLabel = groupItemRect.GetComponent(); groupItemRect.anchoredPosition = anchoredPosition; NextYPosition -= groupItemRect.sizeDelta.y; itemLabel.text = Item.GetDisplayName(); Item.ItemRect = groupItemRect; Item.SetupView(viewProvider); } #region NESTED class Group { public List 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(); rect.anchoredPosition = ItemRect.rect.max;//тут мб другое view = rect.GetComponent(); 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(); rect.anchoredPosition = ItemRect.rect.max;//тут мб другое view = rect.GetComponent(); 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 }