566 lines
16 KiB
C#
566 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using BriarQueen.Data.Identifiers;
|
|
using BriarQueen.Framework.Assets;
|
|
using BriarQueen.Framework.Coordinators.Events;
|
|
using BriarQueen.Framework.Events.Gameplay;
|
|
using BriarQueen.Framework.Managers.Player;
|
|
using BriarQueen.Framework.Managers.Player.Data;
|
|
using BriarQueen.Framework.Registries;
|
|
using BriarQueen.Framework.Services.Tutorials;
|
|
using Cysharp.Threading.Tasks;
|
|
using PrimeTween;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using VContainer;
|
|
|
|
namespace BriarQueen.UI.HUD
|
|
{
|
|
public class InventoryBar : MonoBehaviour
|
|
{
|
|
private const int ITEMS_PER_PAGE = 4;
|
|
|
|
[Header("Buttons")]
|
|
[SerializeField]
|
|
private Button _nextButton;
|
|
|
|
[SerializeField]
|
|
private Button _prevButton;
|
|
|
|
[Header("Paging")]
|
|
[SerializeField]
|
|
private RectTransform _content;
|
|
|
|
[SerializeField]
|
|
private float _pageTweenSeconds = 5f;
|
|
|
|
private readonly List<UIInventorySlot> _inventorySlots = new();
|
|
|
|
private AddressableManager _addressableManager;
|
|
private AssetRegistry _assetRegistry;
|
|
private EventCoordinator _eventCoordinator;
|
|
private PlayerManager _playerManager;
|
|
private TutorialService _tutorialService;
|
|
|
|
private int _currentPage;
|
|
private int _selectedIndex = -1;
|
|
|
|
private CancellationTokenSource _pageCts;
|
|
private CancellationTokenSource _rebuildCts;
|
|
private Sequence _pageSequence;
|
|
|
|
private float _pageZeroFirstSlotX;
|
|
|
|
private int PageCount
|
|
{
|
|
get
|
|
{
|
|
if (_inventorySlots.Count <= 0)
|
|
return 0;
|
|
|
|
return (_inventorySlots.Count + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE;
|
|
}
|
|
}
|
|
|
|
[Inject]
|
|
public void Construct(
|
|
EventCoordinator eventCoordinator,
|
|
PlayerManager playerManager,
|
|
AddressableManager addressableManager,
|
|
AssetRegistry assetRegistry,
|
|
TutorialService tutorialService)
|
|
{
|
|
_eventCoordinator = eventCoordinator;
|
|
_playerManager = playerManager;
|
|
_addressableManager = addressableManager;
|
|
_assetRegistry = assetRegistry;
|
|
_tutorialService = tutorialService;
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
if (_nextButton)
|
|
_nextButton.onClick.AddListener(OnNextClicked);
|
|
|
|
if (_prevButton)
|
|
_prevButton.onClick.AddListener(OnPrevClicked);
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
if (_eventCoordinator != null)
|
|
{
|
|
_eventCoordinator.Subscribe<InventoryChangedEvent>(OnInventoryChanged);
|
|
_eventCoordinator.Subscribe<SelectedItemChangedEvent>(OnSelectedItemCleared);
|
|
_eventCoordinator.Subscribe<OnNextItemClickedEvent>(OnNextItem);
|
|
_eventCoordinator.Subscribe<OnPreviousItemClickedEvent>(OnPreviousItem);
|
|
}
|
|
|
|
UpdateButtonState();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (_eventCoordinator != null)
|
|
{
|
|
_eventCoordinator.Unsubscribe<InventoryChangedEvent>(OnInventoryChanged);
|
|
_eventCoordinator.Unsubscribe<SelectedItemChangedEvent>(OnSelectedItemCleared);
|
|
_eventCoordinator.Unsubscribe<OnNextItemClickedEvent>(OnNextItem);
|
|
_eventCoordinator.Unsubscribe<OnPreviousItemClickedEvent>(OnPreviousItem);
|
|
}
|
|
|
|
StopPageTween();
|
|
StopRebuild();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (_nextButton)
|
|
_nextButton.onClick.RemoveListener(OnNextClicked);
|
|
|
|
if (_prevButton)
|
|
_prevButton.onClick.RemoveListener(OnPrevClicked);
|
|
|
|
StopPageTween();
|
|
StopRebuild();
|
|
}
|
|
|
|
private void OnInventoryChanged(InventoryChangedEvent evt)
|
|
{
|
|
RebuildInventoryAsync().Forget();
|
|
}
|
|
|
|
private void OnSelectedItemCleared(SelectedItemChangedEvent evt)
|
|
{
|
|
if (evt.Item != null)
|
|
return;
|
|
|
|
ClearSelectionVisualOnly();
|
|
}
|
|
|
|
public void ClearSelection()
|
|
{
|
|
ClearSelectionVisualOnly();
|
|
_eventCoordinator?.Publish(new SelectedItemChangedEvent(null));
|
|
}
|
|
|
|
public void SelectIndex(int index)
|
|
{
|
|
SetSelectedIndex(index, true);
|
|
}
|
|
|
|
public void OnSlotInteracted(UIInventorySlot slot)
|
|
{
|
|
if (slot == null || slot.Item == null)
|
|
return;
|
|
|
|
var index = _inventorySlots.IndexOf(slot);
|
|
if (index < 0)
|
|
return;
|
|
|
|
if (_selectedIndex == index)
|
|
{
|
|
ClearSelection();
|
|
return;
|
|
}
|
|
|
|
SelectIndex(index);
|
|
}
|
|
|
|
private void ClearContentChildrenImmediateVisual()
|
|
{
|
|
if (!_content)
|
|
return;
|
|
|
|
for (var i = _content.childCount - 1; i >= 0; i--)
|
|
{
|
|
var child = _content.GetChild(i);
|
|
if (child == null)
|
|
continue;
|
|
|
|
child.gameObject.SetActive(false);
|
|
Destroy(child.gameObject);
|
|
}
|
|
}
|
|
|
|
private async UniTask RebuildInventoryAsync()
|
|
{
|
|
StopRebuild();
|
|
_rebuildCts = new CancellationTokenSource();
|
|
var token = _rebuildCts.Token;
|
|
|
|
try
|
|
{
|
|
if (!_content)
|
|
{
|
|
ClearSelectionVisualOnly();
|
|
_eventCoordinator?.Publish(new SelectedItemChangedEvent(null));
|
|
UpdateButtonState();
|
|
return;
|
|
}
|
|
|
|
if (!_assetRegistry ||
|
|
!_assetRegistry.TryGetReference(AssetKeyIdentifiers.Get(UIKey.InventorySlot), out var slotRef))
|
|
{
|
|
ClearSelectionVisualOnly();
|
|
_eventCoordinator?.Publish(new SelectedItemChangedEvent(null));
|
|
UpdateButtonState();
|
|
return;
|
|
}
|
|
|
|
StopPageTween();
|
|
|
|
var inventoryItems = _playerManager.GetInventoryItems();
|
|
var items = new List<ItemDataSo>();
|
|
|
|
if (inventoryItems != null)
|
|
{
|
|
for (var i = 0; i < inventoryItems.Count; i++)
|
|
{
|
|
var item = inventoryItems[i];
|
|
if (item != null)
|
|
items.Add(item);
|
|
}
|
|
}
|
|
|
|
Debug.Log($"Rebuilding inventory with {items.Count} valid items");
|
|
|
|
_inventorySlots.Clear();
|
|
|
|
ClearContentChildrenImmediateVisual();
|
|
await UniTask.NextFrame(token);
|
|
|
|
Canvas.ForceUpdateCanvases();
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(_content);
|
|
|
|
for (var i = 0; i < items.Count; i++)
|
|
{
|
|
token.ThrowIfCancellationRequested();
|
|
|
|
var item = items[i];
|
|
if (item == null)
|
|
continue;
|
|
|
|
var slotObj = await _addressableManager.InstantiateAsync(slotRef, parent: _content);
|
|
token.ThrowIfCancellationRequested();
|
|
|
|
if (slotObj == null)
|
|
{
|
|
Debug.LogWarning("[InventoryBar] AddressableManager returned null slot object.");
|
|
continue;
|
|
}
|
|
|
|
var slot = slotObj.GetComponent<UIInventorySlot>();
|
|
if (slot == null)
|
|
{
|
|
Debug.LogWarning("[InventoryBar] Instantiated slot prefab is missing UIInventorySlot.");
|
|
Destroy(slotObj);
|
|
continue;
|
|
}
|
|
|
|
slot.Initialize(this, item);
|
|
_inventorySlots.Add(slot);
|
|
}
|
|
|
|
await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate, token);
|
|
|
|
Canvas.ForceUpdateCanvases();
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(_content);
|
|
|
|
CachePageAnchorData();
|
|
|
|
_currentPage = Mathf.Clamp(_currentPage, 0, Mathf.Max(0, PageCount - 1));
|
|
SnapToPage(_currentPage);
|
|
|
|
ClearSelectionVisualOnly();
|
|
_eventCoordinator.Publish(new SelectedItemChangedEvent(null));
|
|
|
|
UpdateButtonState();
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
}
|
|
}
|
|
|
|
private void StopRebuild()
|
|
{
|
|
_rebuildCts?.Cancel();
|
|
_rebuildCts?.Dispose();
|
|
_rebuildCts = null;
|
|
}
|
|
|
|
private void SetSelectedIndex(int index, bool scrollToSelection)
|
|
{
|
|
if (_inventorySlots.Count == 0)
|
|
{
|
|
ClearSelectionVisualOnly();
|
|
_eventCoordinator.Publish(new SelectedItemChangedEvent(null));
|
|
UpdateButtonState();
|
|
return;
|
|
}
|
|
|
|
if (index < 0)
|
|
{
|
|
ClearSelection();
|
|
return;
|
|
}
|
|
|
|
index = Mathf.Clamp(index, 0, _inventorySlots.Count - 1);
|
|
|
|
if (_selectedIndex >= 0 && _selectedIndex < _inventorySlots.Count)
|
|
_inventorySlots[_selectedIndex].SetSelected(false);
|
|
|
|
_selectedIndex = index;
|
|
_inventorySlots[_selectedIndex].SetSelected(true);
|
|
|
|
_eventCoordinator.Publish(new SelectedItemChangedEvent(_inventorySlots[_selectedIndex].Item));
|
|
CheckSelectedItemTutorial();
|
|
|
|
if (scrollToSelection)
|
|
{
|
|
var targetPage = IndexToPage(_selectedIndex);
|
|
GoToPage(targetPage).Forget();
|
|
}
|
|
|
|
UpdateButtonState();
|
|
}
|
|
|
|
private void CheckSelectedItemTutorial()
|
|
{
|
|
_tutorialService.DisplayTutorial(TutorialPopupID.ItemsAway);
|
|
}
|
|
|
|
private void ClearSelectionVisualOnly()
|
|
{
|
|
if (_selectedIndex >= 0 && _selectedIndex < _inventorySlots.Count)
|
|
_inventorySlots[_selectedIndex].SetSelected(false);
|
|
|
|
_selectedIndex = -1;
|
|
UpdateButtonState();
|
|
}
|
|
|
|
private int IndexToPage(int index)
|
|
{
|
|
if (index < 0)
|
|
return 0;
|
|
|
|
return index / ITEMS_PER_PAGE;
|
|
}
|
|
|
|
private void CachePageAnchorData()
|
|
{
|
|
_pageZeroFirstSlotX = 0f;
|
|
|
|
if (_inventorySlots.Count == 0)
|
|
return;
|
|
|
|
Canvas.ForceUpdateCanvases();
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(_content);
|
|
|
|
var first = _inventorySlots[0].transform as RectTransform;
|
|
if (!first)
|
|
return;
|
|
|
|
_pageZeroFirstSlotX = first.anchoredPosition.x;
|
|
}
|
|
|
|
private float PageToAnchoredX(int pageIndex)
|
|
{
|
|
if (_inventorySlots.Count == 0)
|
|
return 0f;
|
|
|
|
pageIndex = Mathf.Clamp(pageIndex, 0, Mathf.Max(0, PageCount - 1));
|
|
|
|
var firstIndexOnPage = pageIndex * ITEMS_PER_PAGE;
|
|
if (firstIndexOnPage < 0 || firstIndexOnPage >= _inventorySlots.Count)
|
|
return 0f;
|
|
|
|
var pageFirstSlot = _inventorySlots[firstIndexOnPage].transform as RectTransform;
|
|
if (!pageFirstSlot)
|
|
return 0f;
|
|
|
|
return _pageZeroFirstSlotX - pageFirstSlot.anchoredPosition.x;
|
|
}
|
|
|
|
private async UniTask GoToPage(int pageIndex)
|
|
{
|
|
if (!_content)
|
|
return;
|
|
|
|
var pages = PageCount;
|
|
if (pages <= 1)
|
|
{
|
|
_currentPage = 0;
|
|
SnapToPage(0);
|
|
UpdateButtonState();
|
|
return;
|
|
}
|
|
|
|
Canvas.ForceUpdateCanvases();
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(_content);
|
|
|
|
var clamped = Mathf.Clamp(pageIndex, 0, pages - 1);
|
|
if (clamped == _currentPage && !_pageSequence.isAlive)
|
|
{
|
|
UpdateButtonState();
|
|
return;
|
|
}
|
|
|
|
_currentPage = clamped;
|
|
|
|
StopPageTween();
|
|
_pageCts = new CancellationTokenSource();
|
|
|
|
var targetX = PageToAnchoredX(_currentPage);
|
|
|
|
var from = _content.anchoredPosition;
|
|
var to = from;
|
|
to.x = targetX;
|
|
|
|
var tweenSettings = new TweenSettings
|
|
{
|
|
duration = Mathf.Max(0f, _pageTweenSeconds),
|
|
ease = Ease.OutCubic,
|
|
useUnscaledTime = true
|
|
};
|
|
|
|
TweenSettings<Vector2> vecSettings = new TweenSettings<Vector2>
|
|
{
|
|
startValue = from,
|
|
endValue = to,
|
|
settings = tweenSettings
|
|
};
|
|
|
|
|
|
_pageSequence = Sequence.Create(useUnscaledTime: true).
|
|
Group(Tween.Custom(vecSettings, newValue => _content.anchoredPosition = newValue));
|
|
try
|
|
{
|
|
await _pageSequence.ToUniTask(cancellationToken: _pageCts.Token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
}
|
|
finally
|
|
{
|
|
_pageSequence = default;
|
|
UpdateButtonState();
|
|
}
|
|
}
|
|
|
|
private void SnapToPage(int pageIndex)
|
|
{
|
|
if (!_content)
|
|
return;
|
|
|
|
var pages = PageCount;
|
|
if (pages <= 1)
|
|
{
|
|
var p0 = _content.anchoredPosition;
|
|
p0.x = 0f;
|
|
_content.anchoredPosition = p0;
|
|
return;
|
|
}
|
|
|
|
Canvas.ForceUpdateCanvases();
|
|
LayoutRebuilder.ForceRebuildLayoutImmediate(_content);
|
|
|
|
pageIndex = Mathf.Clamp(pageIndex, 0, pages - 1);
|
|
|
|
var pos = _content.anchoredPosition;
|
|
pos.x = PageToAnchoredX(pageIndex);
|
|
_content.anchoredPosition = pos;
|
|
}
|
|
|
|
private void StopPageTween()
|
|
{
|
|
if (_pageSequence.isAlive)
|
|
_pageSequence.Stop();
|
|
|
|
_pageSequence = default;
|
|
|
|
_pageCts?.Cancel();
|
|
_pageCts?.Dispose();
|
|
_pageCts = null;
|
|
}
|
|
|
|
private void OnNextClicked()
|
|
{
|
|
if (PageCount <= 1)
|
|
return;
|
|
|
|
GoToPage(_currentPage + 1).Forget();
|
|
}
|
|
|
|
private void OnPrevClicked()
|
|
{
|
|
if (PageCount <= 1)
|
|
return;
|
|
|
|
GoToPage(_currentPage - 1).Forget();
|
|
}
|
|
|
|
private void UpdateButtonState()
|
|
{
|
|
var pages = PageCount;
|
|
var showNav = pages > 1;
|
|
|
|
if (_prevButton)
|
|
{
|
|
var showPrev = showNav && _currentPage > 0;
|
|
_prevButton.gameObject.SetActive(showPrev);
|
|
}
|
|
|
|
if (_nextButton)
|
|
{
|
|
var showNext = showNav && _currentPage < pages - 1;
|
|
_nextButton.gameObject.SetActive(showNext);
|
|
}
|
|
}
|
|
|
|
private void OnNextItem(OnNextItemClickedEvent e)
|
|
{
|
|
if (_inventorySlots.Count == 0)
|
|
return;
|
|
|
|
int nextIndex;
|
|
|
|
if (_selectedIndex < 0)
|
|
{
|
|
nextIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
nextIndex = _selectedIndex + 1;
|
|
|
|
if (nextIndex >= _inventorySlots.Count)
|
|
nextIndex = 0;
|
|
}
|
|
|
|
SetSelectedIndex(nextIndex, true);
|
|
}
|
|
|
|
private void OnPreviousItem(OnPreviousItemClickedEvent e)
|
|
{
|
|
if (_inventorySlots.Count == 0)
|
|
return;
|
|
|
|
int prevIndex;
|
|
|
|
if (_selectedIndex < 0)
|
|
{
|
|
prevIndex = _inventorySlots.Count - 1;
|
|
}
|
|
else
|
|
{
|
|
prevIndex = _selectedIndex - 1;
|
|
|
|
if (prevIndex < 0)
|
|
prevIndex = _inventorySlots.Count - 1;
|
|
}
|
|
|
|
SetSelectedIndex(prevIndex, true);
|
|
}
|
|
}
|
|
} |