282 lines
No EOL
8.6 KiB
C#
282 lines
No EOL
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);
|
||
}
|
||
}
|
||
}
|
||
} |