282 lines
8.6 KiB
C#
282 lines
8.6 KiB
C#
|
|
using System.Collections;
|
|||
|
|
using UnityEngine;
|
|||
|
|
using UnityEngine.UI;
|
|||
|
|
using TMPro;
|
|||
|
|
|
|||
|
|
public class UI_ShopItem : MonoBehaviour
|
|||
|
|
{
|
|||
|
|
[Header("Databases")]
|
|||
|
|
[SerializeField] private ShopAudioDatabase audioDatabase; // Ссылка на БД звуков
|
|||
|
|
|
|||
|
|
[Header("UI Elements")]
|
|||
|
|
[SerializeField] public Color giftBackgroundColor = new Color32(255, 0, 69, 255); // Твой FF0045
|
|||
|
|
[SerializeField] private TMP_Text titleText;
|
|||
|
|
[SerializeField] private TMP_Text priceText;
|
|||
|
|
[SerializeField] private TMP_Text oldPriceText;
|
|||
|
|
[SerializeField] private TMP_Text descpText;
|
|||
|
|
[SerializeField] private Image iconImage;
|
|||
|
|
|
|||
|
|
[Header("Audio Overrides (Optional)")]
|
|||
|
|
[SerializeField] private AudioClip customPurchaseSfx;
|
|||
|
|
[SerializeField] private AudioClip customErrorSfx;
|
|||
|
|
|
|||
|
|
[Header("Settings & Config")]
|
|||
|
|
[SerializeField] private ShopVisualConfig config;
|
|||
|
|
[SerializeField] private ShopIconDatabase iconDb;
|
|||
|
|
|
|||
|
|
// Внимание: если у тебя несколько товаров, статический инстанс делать нельзя!
|
|||
|
|
// public static UI_ShopItem Instance;
|
|||
|
|
|
|||
|
|
private ShopItem _data;
|
|||
|
|
private Button _button;
|
|||
|
|
private Image _backgroundImage;
|
|||
|
|
private Color _initialColor;
|
|||
|
|
private Vector3 _originalScale;
|
|||
|
|
private Coroutine _pulseCoroutine;
|
|||
|
|
private Coroutine _shakeCoroutine;
|
|||
|
|
|
|||
|
|
private readonly Color _giftBgColor = new Color32(255, 0, 69, 255);
|
|||
|
|
|
|||
|
|
private void Awake()
|
|||
|
|
{
|
|||
|
|
_button = GetComponent<Button>();
|
|||
|
|
_backgroundImage = GetComponent<Image>();
|
|||
|
|
|
|||
|
|
if (_backgroundImage != null)
|
|||
|
|
_initialColor = _backgroundImage.color;
|
|||
|
|
|
|||
|
|
_originalScale = transform.localScale;
|
|||
|
|
if (_button != null) _button.onClick.AddListener(OnPurchaseAttempt);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Setup(ShopItem item)
|
|||
|
|
{
|
|||
|
|
_data = item;
|
|||
|
|
|
|||
|
|
if (_data.isLocked)
|
|||
|
|
{
|
|||
|
|
gameObject.SetActive(false);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
gameObject.SetActive(true);
|
|||
|
|
StopAllCoroutines();
|
|||
|
|
transform.localScale = _originalScale;
|
|||
|
|
|
|||
|
|
titleText.text = item.title;
|
|||
|
|
descpText.text = item.description;
|
|||
|
|
|
|||
|
|
if (iconImage != null && iconDb != null)
|
|||
|
|
{
|
|||
|
|
Sprite foundIcon = iconDb.GetIcon(item.icon_id);
|
|||
|
|
iconImage.sprite = foundIcon;
|
|||
|
|
iconImage.gameObject.SetActive(foundIcon != null);
|
|||
|
|
iconImage.preserveAspect = item.preserve_aspect;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
UpdateVisualState();
|
|||
|
|
HandleAnimations();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void SetPrice(float price, float discountPercent)
|
|||
|
|
{
|
|||
|
|
if (config == null) return;
|
|||
|
|
|
|||
|
|
if (price <= 0)
|
|||
|
|
{
|
|||
|
|
priceText.text = "0000";
|
|||
|
|
priceText.color = config.giftColor;
|
|||
|
|
}
|
|||
|
|
else if (discountPercent > 0 && discountPercent < 100)
|
|||
|
|
{
|
|||
|
|
float finalPrice = price * (1 - (discountPercent / 100f));
|
|||
|
|
priceText.text = Mathf.CeilToInt(finalPrice).ToString();
|
|||
|
|
priceText.color = config.discountedPriceColor;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
priceText.text = price.ToString();
|
|||
|
|
priceText.color = config.ironColor;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void UpdateVisualState()
|
|||
|
|
{
|
|||
|
|
if (config == null || _data == null) return;
|
|||
|
|
|
|||
|
|
bool isFree = _data.isGift || _data.FinalPrice <= 0;
|
|||
|
|
bool hasDiscount = _data.discount > 0 && _data.discount < 100;
|
|||
|
|
|
|||
|
|
// --- ЛОГИКА ЦВЕТА ФОНА ---
|
|||
|
|
if (_backgroundImage != null)
|
|||
|
|
{
|
|||
|
|
if (isFree)
|
|||
|
|
{
|
|||
|
|
_backgroundImage.color = config.giftBackgroundColor;
|
|||
|
|
}
|
|||
|
|
else if (hasDiscount)
|
|||
|
|
{
|
|||
|
|
_backgroundImage.color = config.discountBackgroundColor;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
_backgroundImage.color = _initialColor;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- ЛОГИКА ТЕКСТА ЦЕНЫ ---
|
|||
|
|
if (isFree)
|
|||
|
|
{
|
|||
|
|
priceText.text = "FREE";
|
|||
|
|
priceText.color = config.giftColor;
|
|||
|
|
priceText.outlineWidth = 0;
|
|||
|
|
oldPriceText.gameObject.SetActive(false);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
string symbol = _data.currency_type == "Gold" ? "Go" : "Ir";
|
|||
|
|
priceText.text = $"{_data.FinalPrice}{symbol}";
|
|||
|
|
|
|||
|
|
ApplyCurrencyStyle(_data.currency_type);
|
|||
|
|
|
|||
|
|
if (hasDiscount)
|
|||
|
|
{
|
|||
|
|
priceText.color = config.discountedPriceColor;
|
|||
|
|
oldPriceText.text = $"<s>{_data.price}{symbol}</s>";
|
|||
|
|
oldPriceText.gameObject.SetActive(true);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
oldPriceText.gameObject.SetActive(false);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void ApplyCurrencyStyle(string type)
|
|||
|
|
{
|
|||
|
|
if (type == "Gold")
|
|||
|
|
{
|
|||
|
|
priceText.color = config.goldColor;
|
|||
|
|
priceText.outlineWidth = 0;
|
|||
|
|
}
|
|||
|
|
else // Iron
|
|||
|
|
{
|
|||
|
|
priceText.color = config.ironColor;
|
|||
|
|
priceText.outlineColor = config.ironOutlineColor;
|
|||
|
|
priceText.outlineWidth = config.ironOutlineWidth;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void HandleAnimations()
|
|||
|
|
{
|
|||
|
|
if (_data.isGift || _data.discount > 0)
|
|||
|
|
{
|
|||
|
|
_pulseCoroutine = StartCoroutine(PulseAnimation());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnPurchaseAttempt()
|
|||
|
|
{
|
|||
|
|
bool canAfford = MANAGER_Shop.Instance.CanPlayerAfford(_data);
|
|||
|
|
|
|||
|
|
if (canAfford)
|
|||
|
|
{
|
|||
|
|
StartCoroutine(SuccessfulPurchaseSequence());
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
FeedbackInsufficientFunds();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void FeedbackInsufficientFunds()
|
|||
|
|
{
|
|||
|
|
if (_shakeCoroutine != null) StopCoroutine(_shakeCoroutine);
|
|||
|
|
_shakeCoroutine = StartCoroutine(ShakeAndRedden());
|
|||
|
|
|
|||
|
|
// Воспроизводим звук ошибки
|
|||
|
|
PlayShopSound(_data?.errorSfxId, SFX_AudioControl.Instance.defaultErrorSFX, customErrorSfx);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private IEnumerator ShakeAndRedden()
|
|||
|
|
{
|
|||
|
|
Color originalColor = priceText.color;
|
|||
|
|
priceText.color = config.insufficientFundsColor;
|
|||
|
|
|
|||
|
|
Vector3 originalPos = transform.localPosition;
|
|||
|
|
float elapsed = 0f;
|
|||
|
|
|
|||
|
|
while (elapsed < config.shakeDuration)
|
|||
|
|
{
|
|||
|
|
elapsed += Time.deltaTime;
|
|||
|
|
float x = Random.Range(-1f, 1f) * config.shakeIntensity;
|
|||
|
|
transform.localPosition = originalPos + new Vector3(x, 0, 0);
|
|||
|
|
yield return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
transform.localPosition = originalPos;
|
|||
|
|
priceText.color = originalColor;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private IEnumerator PulseAnimation()
|
|||
|
|
{
|
|||
|
|
while (true)
|
|||
|
|
{
|
|||
|
|
float scale = 1f + Mathf.Sin(Time.time * config.pulseSpeed) * config.pulseAmount;
|
|||
|
|
transform.localScale = _originalScale * scale;
|
|||
|
|
yield return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private IEnumerator SuccessfulPurchaseSequence()
|
|||
|
|
{
|
|||
|
|
// Воспроизводим звук успешной покупки
|
|||
|
|
PlayShopSound(_data?.purchaseSfxId, SFX_AudioControl.Instance.defaultPurchaseSFX, customPurchaseSfx);
|
|||
|
|
_button.interactable = false;
|
|||
|
|
|
|||
|
|
// Анимация исчезновения
|
|||
|
|
float timer = 0;
|
|||
|
|
float duration = 0.3f;
|
|||
|
|
while (timer < duration)
|
|||
|
|
{
|
|||
|
|
timer += Time.deltaTime;
|
|||
|
|
transform.localScale = Vector3.Lerp(_originalScale, Vector3.zero, timer / duration);
|
|||
|
|
yield return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
MANAGER_Shop.Instance.BuyItem(_data);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- ИДЕТ ПОЛНОСТЬЮ ПЕРЕРАБОТАННЫЙ МЕТОД ЗВУКА ---
|
|||
|
|
private void PlayShopSound(string jsonId, AudioClip globalDefault, AudioClip localOverride)
|
|||
|
|
{
|
|||
|
|
if (SFX_AudioControl.Instance == null) return;
|
|||
|
|
|
|||
|
|
AudioClip clipToPlay = null;
|
|||
|
|
|
|||
|
|
// 1. Поиск клипа (логика та же)
|
|||
|
|
if (audioDatabase != null && !string.IsNullOrEmpty(jsonId))
|
|||
|
|
clipToPlay = audioDatabase.GetAudioClip(jsonId);
|
|||
|
|
|
|||
|
|
if (clipToPlay == null && localOverride != null)
|
|||
|
|
clipToPlay = localOverride;
|
|||
|
|
|
|||
|
|
if (clipToPlay == null)
|
|||
|
|
clipToPlay = globalDefault;
|
|||
|
|
|
|||
|
|
// 2. А вот тут магия затухания!
|
|||
|
|
if (clipToPlay != null)
|
|||
|
|
{
|
|||
|
|
// Если ID из JSON равен "dohodyaga", вызываем метод с затуханием музыки
|
|||
|
|
if (jsonId == "dohodyaga")
|
|||
|
|
{
|
|||
|
|
SFX_AudioControl.Instance.PlaySfxWithDucking(clipToPlay);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
// Для всех остальных — обычный звук
|
|||
|
|
SFX_AudioControl.Instance.PlayOneShot(clipToPlay);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|