using System; using System.Collections.Generic; using System.Linq; using Sirenix.OdinInspector; using UnityEngine; using UnityEngine.Pool; namespace BlueWater { public class LiquidController : MonoBehaviour { #region Variables [Title("컴포넌트")] [SerializeField] private Renderer _renderTexture; [SerializeField] private Renderer _liquidRenderer; [SerializeField] private Collider2D _reachedCollider; [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("떨어지는 액체의 색상")] private Color _liquidColor = new(1f, 0.8431373f, 0f, 1f); [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; private IObjectPool _objectPool; private List _activeLiquids = new(); private Dictionary _colorCounts = new(); private Material _instanceMaterial; 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); } private void Start() { TycoonEvents.OnDrinkUiOpened += Initialize; TycoonEvents.OnDrinkUiClosed += ReleaseAllObject; _instanceMaterial = Instantiate(_liquidRenderer.material); _liquidRenderer.material = _instanceMaterial; _timeInterval = 1f / _liquidsPerSecond; _instanceMaterial.SetFloat(_liquidAmountHash, 0f); } private void Update() { if (_isPouring) { if (_instanceLiquidCount >= _maxLiquidCount) { InActiveIsPouring(); return; } if (Time.time - _startTime >= _timeInterval) { _objectPool.Get(); if (!_colorCounts.TryAdd(_liquidColor, 1)) { _colorCounts[_liquidColor] += 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.OnDrinkUiOpened -= Initialize; TycoonEvents.OnDrinkUiClosed -= ReleaseAllObject; } #endregion // Initialize methods #region Initialize methods public void Initialize() { _instanceLiquidCount = 0; _currentLiquidAmount = 0f; _currentMixedColor = _liquidColor; _instanceMaterial.SetColor(_liquidColorHash, _currentMixedColor * _colorIntensity); } #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++; 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 [Button("기본 색상")] private void DefaultColor() => _liquidColor = new Color(1f, 0.8431373f, 0f, 1f); /// /// 술 제조 과정 초기화 함수 /// public void ReleaseAllObject() { // 리스트 삭제는 뒤에서부터 해야 오류가 없음 for (var i = _activeLiquids.Count - 1; i >= 0; i--) { _activeLiquids[i].Destroy(); } _colorCounts.Clear(); _instanceLiquidCount = 0; _currentLiquidAmount = 0f; _instanceMaterial.SetFloat(_liquidAmountHash, 0f); } public void ActiveIsPouring() { _startTime = Time.time; _isPouring = true; } public void InActiveIsPouring() { _isPouring = false; } /// /// 사용된 색상의 비율에 맞게 색을 혼합시키는 함수 /// private Color MixColorsByTime() { var totalCounts = _colorCounts.Values.Sum(); var mixedColor = Color.black; foreach (var element in _colorCounts) { var color = element.Key; var count = element.Value; var ratio = count / (float)totalCounts; mixedColor += color * ratio; } mixedColor.a = 1f; return mixedColor; } /// /// 액체가 특정 오브젝트에 충돌했을 때, 실행해야하는 과정 /// public void OnLiquidReached() { _liquidReachedTime = Time.time; _currentLiquidAmount++; var liquidAmount = Mathf.Clamp(_currentLiquidAmount / _maxLiquidCount, 0f, 1f); _instanceMaterial.SetFloat(_liquidAmountHash, liquidAmount); _targetColor = MixColorsByTime(); if (liquidAmount >= 1f) { InActiveIsPouring(); } } #endregion } }