using System.Collections; using System.Collections.Generic; using System.Linq; using BlueWater.Items; using BlueWater.Tycoons; using DG.Tweening; using Sirenix.OdinInspector; using TMPro; using UnityEngine; using UnityEngine.Pool; using UnityEngine.UI; namespace BlueWater { public class LiquidController : MonoBehaviour { #region Variables [Title("컴포넌트")] [SerializeField] private GameObject _liquidPanel; [SerializeField] private GameObject _shaker; [SerializeField] private Renderer _renderTexture; [SerializeField] private Renderer _liquidRenderer; [SerializeField] private Collider2D _reachedCollider; [SerializeField] private TMP_Text _amountText; [SerializeField] private Image _completeCocktailImage; [SerializeField] private TMP_Text _completeText; [Title("스폰 데이터")] [SerializeField, Required] private Transform _spawnTransform; [SerializeField] private Transform _spawnLocation; [SerializeField] private Vector3 _pushDirection; [SerializeField] private float _pushPower; [Title("액체")] [SerializeField, Required, Tooltip("액체 프리팹")] private Liquid _liquidObject; [SerializeField, Tooltip("초당 생성되는 액체 수(ml)")] private int _liquidsPerSecond = 80; [SerializeField] private int _maxLiquidCount = 400; [SerializeField, Range(0f, 1f), Tooltip("목표 색상으로 변경되는데 걸리는 시간")] private float _colorLerpSpeed = 0.5f; [SerializeField, Range(1f, 5f), Tooltip("목표 색상 * 밝기")] private float _colorIntensity = 2f; [Title("오브젝트 풀링")] [SerializeField, Tooltip("오브젝트 풀링 최대 개수")] private int _objectPoolCount = 1000; [Title("패널")] [SerializeField] private float _moveDuration = 0.5f; [Title("실시간 정보")] [SerializeField] private Barrel _currentBarrel; private IObjectPool _objectPool; private List _activeLiquids = new(); private Dictionary _liquidDataCounts = new(7); private Material _instanceMaterial; private Tween _showTween; private Tween _hideTween; private bool _isShowingPanel; private bool _isPouring; private float _startTime = float.PositiveInfinity; private int _instanceLiquidCount; private float _currentLiquidAmount; private float _liquidReachedTime; private float _timeInterval; private Color _currentMixedColor = Color.black; private Color _targetColor; // Hashes private static readonly int _liquidAmountHash = Shader.PropertyToID("_LiquidAmount"); private static readonly int _liquidColorHash = Shader.PropertyToID("_LiquidColor"); private static readonly int _renderTextureColorHash = Shader.PropertyToID("_Color"); #endregion // Unity events #region Unity events private void Awake() { _objectPool = new ObjectPool(CreateObject, OnGetObject, OnReleaseObject, OnDestroyObject, maxSize: _objectPoolCount); _hideTween = _liquidPanel.transform.DOMoveX(-150f, _moveDuration).Pause() .SetAutoKill(false); _showTween = _liquidPanel.transform.DOMoveX(-249f, _moveDuration).Pause() .SetAutoKill(false); } private void Start() { TycoonEvents.OnLiquidRegionEntered += ShowPanel; TycoonEvents.OnLiquidRegionExited += HidePanel; _instanceMaterial = Instantiate(_liquidRenderer.material); _liquidRenderer.material = _instanceMaterial; _instanceMaterial.SetFloat(_liquidAmountHash, 0f); _timeInterval = 1f / _liquidsPerSecond; _shaker.SetActive(true); _amountText.enabled = true; _completeCocktailImage.enabled = false; _completeText.enabled = false; _instanceLiquidCount = 0; SetCurrentAmount(0f); } private void Update() { if (_isPouring) { // 현재 술의 재고가 없을 때 if (!_currentBarrel.CanConsume(1)) { InActiveIsPouring(); return; } // 술이 완성되었을 때 if (_instanceLiquidCount >= _maxLiquidCount) { StartCoroutine(nameof(CompleteCocktail)); return; } if (Time.time - _startTime >= _timeInterval) { _objectPool.Get(); if (!_liquidDataCounts.TryAdd(_currentBarrel.GetLiquidData(), 1)) { _liquidDataCounts[_currentBarrel.GetLiquidData()] += 1; } _currentBarrel.Consume(1); _startTime = Time.time; } } if (_liquidReachedTime + _colorLerpSpeed >= Time.time) { _currentMixedColor = Color.Lerp(_currentMixedColor, _targetColor, _colorLerpSpeed * Time.deltaTime); _instanceMaterial.SetColor(_liquidColorHash, _currentMixedColor * _colorIntensity); } } private void OnDestroy() { TycoonEvents.OnLiquidRegionEntered -= ShowPanel; TycoonEvents.OnLiquidRegionExited -= HidePanel; } #endregion // Object pooling system #region Object pooling system private Liquid CreateObject() { var instance = Instantiate(_liquidObject, _spawnTransform.position, Quaternion.identity, _spawnLocation); instance.SetManagedPool(_objectPool); return instance; } private void OnGetObject(Liquid liquid) { liquid.transform.position = _spawnTransform.position; liquid.transform.rotation = Quaternion.identity; liquid.gameObject.SetActive(true); _instanceLiquidCount++; var liquidColor = _currentBarrel.GetLiquidData().Color; liquid.Initialize(this, _reachedCollider, liquidColor, _pushDirection.normalized * _pushPower); if (_renderTexture && _renderTexture.material.GetColor(_renderTextureColorHash) != liquidColor) { _renderTexture.material.SetColor(_renderTextureColorHash, liquidColor); } _activeLiquids.Add(liquid); } private void OnReleaseObject(Liquid liquid) { liquid.gameObject.SetActive(false); _activeLiquids.Remove(liquid); } private void OnDestroyObject(Liquid liquid) { Destroy(liquid.gameObject); _activeLiquids.Remove(liquid); } #endregion // Custom methods #region Custom methods /// /// 술 제조 과정 초기화 함수 /// public void ReleaseAllObject() { // 리스트 삭제는 뒤에서부터 해야 오류가 없음 for (var i = _activeLiquids.Count - 1; i >= 0; i--) { _activeLiquids[i].Destroy(); } _liquidDataCounts.Clear(); _instanceLiquidCount = 0; SetCurrentAmount(0f); _instanceMaterial.SetFloat(_liquidAmountHash, 0f); } /// /// 현재 상호작용 중인 Barrel 연동 /// /// public void SetBarrel(Barrel barrel) { _currentBarrel = barrel; } public void ActiveIsPouring() { if (_instanceLiquidCount == 0) { _shaker.SetActive(true); _amountText.enabled = true; _completeCocktailImage.enabled = false; _completeText.enabled = false; _currentMixedColor = _currentBarrel.GetLiquidData().Color; _instanceMaterial.SetColor(_liquidColorHash, _currentMixedColor * _colorIntensity); } _startTime = Time.time; _isPouring = true; } public void InActiveIsPouring() { _isPouring = false; } private void SetCurrentAmount(float value) { _currentLiquidAmount = value; if (_amountText) { var percent = (int)(_currentLiquidAmount / _maxLiquidCount * 100); _amountText.text = $"{percent}%"; } else { if (_amountText.enabled) { _amountText.enabled = false; } } } /// /// 술을 완성한 경우 /// private IEnumerator CompleteCocktail() { InActiveIsPouring(); yield return new WaitUntil(() => _currentLiquidAmount >= _maxLiquidCount); var currentCocktailIngredients = new List(7); foreach (var element in _liquidDataCounts) { var idx = element.Key.Idx; var amount = element.Value; currentCocktailIngredients.Add(new CocktailIngredient(idx, amount)); } // ItemManager를 통해 모든 CocktailData 가져오기 var cocktailDatas = ItemManager.Instance.GetCocktailDatas(); CocktailData matchingCocktail = null; // 모든 칵테일 데이터를 순회하면서 조건에 맞는 칵테일 찾기 foreach (var cocktailData in cocktailDatas.Values) { var validIngredients = cocktailData.GetValidIngredients(); // 조건 1: 재료 개수 동일 체크 if (validIngredients.Count != currentCocktailIngredients.Count) continue; var allIngredientsMatch = true; // 현재 음료 재료를 하나씩 validIngredients와 비교 foreach (var currentIngredient in currentCocktailIngredients) { // 동일한 Idx를 가진 재료가 있는지 찾음 var matchingValidIngredient = validIngredients.FirstOrDefault(ingredient => ingredient.Idx == currentIngredient.Idx); // 만약 Idx가 일치하는 재료가 없으면 조건 불충족 if (matchingValidIngredient == null) { allIngredientsMatch = false; break; } // 조건 2: Amount 값이 RatioRange에 따른 오차 범위 내에 있는지 체크 var validAmount = matchingValidIngredient.Amount; //var maxLiquidCount = cocktailData.GetCocktailAmount(validIngredients); var range = _maxLiquidCount / 100 * cocktailData.RatioRange; var minAmount = validAmount - range; var maxAmount = validAmount + range; if (currentIngredient.Amount < minAmount || currentIngredient.Amount > maxAmount) { allIngredientsMatch = false; break; } } // 조건이 모두 만족하면 매칭되는 칵테일을 찾음 if (allIngredientsMatch) { matchingCocktail = cocktailData; break; } } // 조건에 만족하는 칵테일이 없음 if (matchingCocktail == null) { matchingCocktail = ItemManager.Instance.GetCocktailDataByIdx("Cocktail000"); _completeText.text = "실패"; } else { _completeText.text = $"성공!\n{matchingCocktail.Name}"; } // TODO : 음료 제조 성공, 실패 연출 추가 _shaker.SetActive(false); _amountText.enabled = false; _completeCocktailImage.sprite = matchingCocktail.Sprite; _completeCocktailImage.enabled = true; _completeText.enabled = true; // 1. 플레이어 음료 들기 TycoonEvents.OnCocktailCompleted?.Invoke(matchingCocktail); yield return new WaitForSeconds(1f); HidePanel(); } /// /// 사용된 색상의 비율에 맞게 색을 혼합시키는 함수 /// private Color MixColorsByTime() { var totalCounts = _liquidDataCounts.Values.Sum(); var mixedColor = Color.black; foreach (var element in _liquidDataCounts) { var color = element.Key.Color; var count = element.Value; var ratio = count / (float)totalCounts; mixedColor += color * ratio; } mixedColor.a = 1f; return mixedColor; } /// /// 액체가 특정 오브젝트에 충돌했을 때, 실행해야하는 과정 /// public void OnLiquidReached() { _liquidReachedTime = Time.time; SetCurrentAmount(++_currentLiquidAmount); var liquidAmount = Mathf.Clamp(_currentLiquidAmount / _maxLiquidCount, 0f, 1f); _instanceMaterial.SetFloat(_liquidAmountHash, liquidAmount); _targetColor = MixColorsByTime(); if (liquidAmount >= 1f) { InActiveIsPouring(); } } public void ShowPanel() { if (_isShowingPanel) return; _isShowingPanel = true; _hideTween.Pause(); _showTween.Restart(); } public void HidePanel() { if (!_isShowingPanel) return; _isShowingPanel = false; _showTween.Pause(); _hideTween.Restart(); } #endregion } }