First commit for private source control. Older commits available on Github.
This commit is contained in:
566
Assets/Scripts/UI/HUD/InventoryBar.cs
Normal file
566
Assets/Scripts/UI/HUD/InventoryBar.cs
Normal file
@@ -0,0 +1,566 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user