설계 1. 아이템 사용 시, 캐릭터 정보창에 버프 관련 아이콘 띄웁니다.
설계 2. 버프 적용 시간에 따라 아이콘을 검게 물들여 남은 시간을 간접적으로 보여줍니다.
디자인

캐릭터 상태창에 활성화되어있는 버프들을 Instantiate할 때 사용하는 prefab을 먼저 만들어 봅시다.
바깥의 하얀 Image 가 버프 관련 아이콘(sprite)로 변경 될 공간이고, 안쪽의 검은 Image가 남은 버프 시간을 간접적으로 보여주는 부분입니다.
public List<GameObject> activeBuff; // 활성화되어있는 버프들의 리스트
public GameObject buffIcon; // 캐릭터 상태창에 나타나는 버프아이콘
public void OnBuffItem(Consumable itemData, float duration)
{
GameObject existingBuff = CheckExistingBuff(itemData.icon);
if (existingBuff != null)
{
BuffIconTimer timer = existingBuff.GetComponent<BuffIconTimer>();
timer.StartTimer(duration);
return;
}
...
}
public GameObject CheckExistingBuff(Sprite buffIcon)
{
foreach (var buff in activeBuff)
{
BuffIconTimer timer = buff.GetComponent<BuffIconTimer>();
if(timer.GetComponent<Image>().sprite == buffIcon)
{
return buff;
}
}
return null;
}
public void OffBuffCallback(GameObject buffEffect)
{
activeBuff.Remove(buffEffect);
SortIcons();
}
위 두 코드의 관계를 보자면, 위 코드는 새로운 버프 요청이 들어왔을 때 실행됩니다.
요청된 버프가 현재 지속중인지 확인합니다.
public class BuffIconTimer : MonoBehaviour
{
private float curTime;
private float duration;
private Image buffStateBar;
private void Update()
{
curTime += Time.deltaTime;
if (curTime >= duration)
{
timerRunning = false;
Destroy(gameObject);
}
UpdateBuffState();
}
private void UpdateBuffState()
{
buffStateBar.fillAmount = curTime / duration;
}
private void OnDestroy()
{
OnBuffEnd?.Invoke(gameObject);
}
}
위 코드는 버프 아이콘에 할당되어있는 컴포넌트입니다.
buffStateBar는 처음에 설명했던 검은 바탕의 이미지를 의미합니다. 이 이미지가 시간이 지남에 따라 아래에서 위로 채워지면서 남은 시간을 간접적으로 알려줍니다.
그리고 시간이 만료되었으면 해당 아이콘(this.gamObject)를 제거하고 콜백으로 해당 버프가 종료 되었다고 BuffManager에게 알려줍니다.
public void SortIcons()
{
Vector2 startPosition = new Vector2(0f, 0f);
Vector2 componentSize = new Vector2(40f, 40f);
int padding = 5;
int i = 0;
foreach(var buffIcon in activeBuff)
{
RectTransform rectTransform = buffIcon.GetComponent<RectTransform>();
rectTransform.sizeDelta = componentSize;
rectTransform.localScale = Vector2.one;
rectTransform.anchoredPosition = new Vector2(startPosition.x + i * (componentSize.x + padding), startPosition.y);
i++;
}
}
위 코드는 캐릭터 상태창의 버프창에 아이콘을 정렬하는 역할을 수행합니다.
앞서 수행한 버프가 종료되면 뒤 버프들을 차례로 앞으로 당겨 정렬을 수행합니다.

public class State{
public void UesConsumable(Consumable itemData)
{
switch (itemData.subType)
{
...
case ConsumableType.SpeedUp:
{
speed += itemData.value;
float duration = 10f;
PlayerController.myBuffManager.OnBuffItem(itemData, duration);
break;
}
}
...
}
}

위처럼 노란색 포션을 사용하여 버프가 활성화 되는 것을 볼 수 있습니다.
public class BuffManager : MonoBehaviour
{
public List<GameObject> activeBuff;
public GameObject testBuffPrefab;
public Action OnOffBuffChanged;
private void Start()
{
SortIcons();
}
public void SortIcons()
{
Vector2 startPosition = new Vector2(0f, 0f);
Vector2 componentSize = new Vector2(40f, 40f);
int padding = 5;
int i = 0;
foreach(var buffIcon in activeBuff)
{
RectTransform rectTransform = buffIcon.GetComponent<RectTransform>();
rectTransform.sizeDelta = componentSize;
rectTransform.localScale = Vector2.one;
rectTransform.anchoredPosition = new Vector2(startPosition.x + i * (componentSize.x + padding), startPosition.y);
i++;
}
}
public void OnBuffItem(Consumable itemData, float duration)
{
GameObject existingBuff = CheckExistingBuff(itemData.icon);
if (existingBuff != null)
{
BuffIconTimer timer = existingBuff.GetComponent<BuffIconTimer>();
timer.StartTimer(duration);
return;
}
GameObject newBuff = Instantiate(testBuffPrefab);
newBuff.GetComponent<Image>().sprite = itemData.icon;
newBuff.transform.SetParent(transform);
activeBuff.Add(newBuff);
BuffIconTimer newTimer = newBuff.GetComponent<BuffIconTimer>();
if (newTimer != null)
{
newTimer.OnBuffEnd = OffBuffCallback;
}
newTimer.StartTimer(duration);
SortIcons();
}
public void OffBuffCallback(GameObject buffEffect)
{
activeBuff.Remove(buffEffect);
SortIcons();
}
public GameObject CheckExistingBuff(Sprite buffIcon)
{
foreach (var buff in activeBuff)
{
BuffIconTimer timer = buff.GetComponent<BuffIconTimer>();
if(timer.GetComponent<Image>().sprite == buffIcon)
{
return buff;
}
}
return null;
}
}
public class BuffIconTimer : MonoBehaviour
{
private float curTime;
private float duration;
private bool timerRunning;
public GameObject buffTimerImg;
private Image buffStateBar;
public Action<GameObject> OnBuffEnd; // 버프 종료 시 실행할 콜백
private void Start()
{
buffStateBar = buffTimerImg.GetComponent<Image>();
}
public void StartTimer(float duration)
{
timerRunning = true;
this.duration = duration;
curTime = 0;
}
private void Update()
{
curTime += Time.deltaTime;
if (curTime >= duration)
{
timerRunning = false;
Destroy(gameObject);
}
UpdateBuffState();
}
private void UpdateBuffState()
{
buffStateBar.fillAmount = curTime / duration;
}
private void OnDestroy()
{
OnBuffEnd?.Invoke(gameObject);
}
}