Rework shop to support multiple item types and add unified PlayerModel

Generalize the shop from selling only ModifierDefinition to any IShopItem
(modifiers + dice). Add purchase condition checks, hover tooltips, and
fixed prices. Introduce PlayerModel as a facade over CurrencyBank,
InventoryModel, and DiceCollection for centralized state and save/load.

New files:
- IShopItem interface for purchasable items
- ShopCatalog SO (unified catalog for all item types)
- ShopTooltipView (description on hover)
- PlayerModel (aggregates player state)
- DiceCollection (owned dice tracking)
- DiceCatalog SO (dice definition registry)
- DiceCollectionTests

Modified:
- ModifierDefinition/DieDefinitionSO implement IShopItem
- ShopModel/ShopView/ShopItemView/ShopController use IShopItem
- SaveData v3 with OwnedDiceIds
- GameController uses PlayerModel for save/load
- GameLifetimeScope registers new services

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 21:04:34 +07:00
parent 85d639aa70
commit fcceb0ce45
18 changed files with 576 additions and 54 deletions
+28 -7
View File
@@ -1,13 +1,14 @@
using System;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using YachtDice.Modifiers.Core;
using YachtDice.Modifiers.Definition;
namespace YachtDice.Shop
{
public class ShopItemView : MonoBehaviour
public class ShopItemView : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
[SerializeField] private Image iconImage;
[SerializeField] private TMP_Text nameText;
@@ -24,9 +25,11 @@ namespace YachtDice.Shop
[SerializeField] private Color rareColor = new(0.4f, 0.6f, 1f);
[SerializeField] private Color epicColor = new(0.8f, 0.4f, 1f);
private ModifierDefinition data;
private IShopItem data;
public event Action<ModifierDefinition> OnBuyClicked;
public event Action<IShopItem> OnBuyClicked;
public event Action<IShopItem, RectTransform> OnHoverEnter;
public event Action OnHoverExit;
private void Awake()
{
@@ -34,9 +37,9 @@ namespace YachtDice.Shop
buyButton.onClick.AddListener(() => OnBuyClicked?.Invoke(data));
}
public void Setup(ModifierDefinition modifierDef, ShopItemState state)
public void Setup(IShopItem item, ShopItemState state)
{
data = modifierDef;
data = item;
if (nameText != null) nameText.text = data.DisplayName;
if (descriptionText != null) descriptionText.text = data.Description;
@@ -45,8 +48,16 @@ namespace YachtDice.Shop
if (rarityText != null)
{
rarityText.text = data.Rarity.ToString();
rarityText.color = GetRarityColor(data.Rarity);
if (data is ModifierDefinition mod)
{
rarityText.gameObject.SetActive(true);
rarityText.text = mod.Rarity.ToString();
rarityText.color = GetRarityColor(mod.Rarity);
}
else
{
rarityText.gameObject.SetActive(false);
}
}
SetState(state);
@@ -77,6 +88,16 @@ namespace YachtDice.Shop
}
}
public void OnPointerEnter(PointerEventData eventData)
{
OnHoverEnter?.Invoke(data, transform as RectTransform);
}
public void OnPointerExit(PointerEventData eventData)
{
OnHoverExit?.Invoke();
}
private Color GetRarityColor(ModifierRarity rarity)
{
return rarity switch