2026.06.24
This commit is contained in:
@@ -1,76 +1,205 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 미니게임 성공, 퀘스트 완료, 버튼 이벤트 등에서 아이템을 지급할 때 사용합니다.
|
||||
/// 예: 낚시 성공 이벤트 -> ItemReward.GiveReward()
|
||||
/// 미니게임 성공, 퀘스트 완료, 버튼 이벤트, 아이템 사용 상호작용에 사용합니다.
|
||||
/// 기능: 보상 지급, 한 번만 지급, 저장, 필요 아이템 검사, 필요 아이템 소모, 부족 메시지.
|
||||
/// 예: 고양이 영입 = requireItemBeforeAction true + Fish 3개 소모 + giveRewardItem false + onActionCompleted에 고양이 합류 이벤트 연결.
|
||||
/// 예: 쓰레기 정화 = requireItemBeforeAction true + Trash 5개 소모 + onActionCompleted에 정화 이벤트 연결.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class ItemReward : MonoBehaviour
|
||||
{
|
||||
[Header("Reference")]
|
||||
[SerializeField] private InventoryManager inventoryManager;
|
||||
|
||||
[Header("Reward")]
|
||||
[SerializeField] private InventoryItemType rewardItemType = InventoryItemType.Fish;
|
||||
[SerializeField] private int rewardAmount = 1;
|
||||
[SerializeField] private bool giveOnlyOnce = false;
|
||||
|
||||
[Header("Auto Find")]
|
||||
[SerializeField] private bool autoFindManager = true;
|
||||
|
||||
[Header("Required Item / Interaction Cost")]
|
||||
[Tooltip("true면 액션 전에 특정 아이템이 필요한지 검사합니다.")]
|
||||
[SerializeField] private bool requireItemBeforeAction = false;
|
||||
[SerializeField] private InventoryItemType requiredItemType = InventoryItemType.Fish;
|
||||
[Min(1)] [SerializeField] private int requiredAmount = 1;
|
||||
[SerializeField] private bool consumeRequiredItem = true;
|
||||
[SerializeField] private string requirementFailMessage;
|
||||
[SerializeField] private string requirementSuccessMessage;
|
||||
|
||||
[Header("Reward")]
|
||||
[Tooltip("false면 보상 아이템을 지급하지 않고, 필요 아이템 소모와 이벤트만 실행합니다.")]
|
||||
[SerializeField] private bool giveRewardItem = true;
|
||||
[SerializeField] private InventoryItemType rewardItemType = InventoryItemType.Fish;
|
||||
[Min(1)] [SerializeField] private int rewardAmount = 1;
|
||||
[Tooltip("true이면 보상을 전부 받을 수 있을 때만 필요 아이템을 소모합니다. 보상 공간 부족으로 비용만 사라지는 상황을 방지합니다.")]
|
||||
[SerializeField] private bool requireFullRewardCapacity = true;
|
||||
[SerializeField] private bool giveOnlyOnce = false;
|
||||
|
||||
[Header("Persistent State")]
|
||||
[Tooltip("true면 액션 완료 여부가 저장됩니다. 퀘스트/미니게임 보상 중복 방지에 사용하세요.")]
|
||||
[SerializeField] private bool persistActionState = false;
|
||||
[SerializeField] private string persistentActionKey;
|
||||
|
||||
[Header("Messages")]
|
||||
[SerializeField] private bool showMessages = true;
|
||||
[SerializeField] private string rewardGivenMessage;
|
||||
[SerializeField] private string alreadyCompletedMessage = "이미 완료된 보상입니다.";
|
||||
|
||||
[Header("Events")]
|
||||
public UnityEvent onActionCompleted;
|
||||
public UnityEvent onRewardGiven;
|
||||
public UnityEvent onRewardAlreadyGiven;
|
||||
public UnityEvent onAlreadyCompleted;
|
||||
public UnityEvent onRequirementMissing;
|
||||
public UnityEvent onRequiredItemConsumed;
|
||||
public UnityEvent onActionFailed;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showDebugLog = true;
|
||||
|
||||
private bool alreadyGiven;
|
||||
private bool alreadyCompleted;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (inventoryManager == null && autoFindManager)
|
||||
inventoryManager = FindFirstObjectByType<InventoryManager>();
|
||||
ResolveManager();
|
||||
ResolvePersistentKey();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (persistActionState && ResolveManager() != null && inventoryManager.IsPersistentKeyConsumed(persistentActionKey))
|
||||
alreadyCompleted = true;
|
||||
}
|
||||
|
||||
public void GiveReward()
|
||||
{
|
||||
if (giveOnlyOnce && alreadyGiven)
|
||||
ExecuteAction();
|
||||
}
|
||||
|
||||
public void ExecuteAction()
|
||||
{
|
||||
InventoryManager manager = ResolveManager();
|
||||
if (manager == null)
|
||||
{
|
||||
onRewardAlreadyGiven?.Invoke();
|
||||
Debug.LogWarning("[ItemReward] InventoryManager가 연결되지 않았습니다.", this);
|
||||
onActionFailed?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
if (giveOnlyOnce && alreadyCompleted)
|
||||
{
|
||||
if (showMessages)
|
||||
manager.RequestMessage(alreadyCompletedMessage);
|
||||
|
||||
onAlreadyCompleted?.Invoke();
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("[ItemReward] 이미 지급된 아이템 보상입니다.");
|
||||
Debug.Log("[ItemReward] 이미 완료된 액션입니다.", this);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (inventoryManager == null)
|
||||
int requestedRewardAmount = Mathf.Max(1, rewardAmount);
|
||||
if (giveRewardItem)
|
||||
{
|
||||
Debug.LogWarning("[ItemReward] InventoryManager가 연결되지 않았습니다.");
|
||||
return;
|
||||
int addableAmount = manager.GetAddableAmount(rewardItemType, requestedRewardAmount);
|
||||
bool rewardCapacityOk = requireFullRewardCapacity ? addableAmount >= requestedRewardAmount : addableAmount > 0;
|
||||
if (!rewardCapacityOk)
|
||||
{
|
||||
if (showMessages)
|
||||
manager.RequestMessage($"{manager.GetDisplayName(rewardItemType)}은(는) 더 이상 받을 수 없습니다.");
|
||||
|
||||
onActionFailed?.Invoke();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int amount = Mathf.Max(1, rewardAmount);
|
||||
inventoryManager.AddItem(rewardItemType, amount);
|
||||
alreadyGiven = true;
|
||||
if (requireItemBeforeAction)
|
||||
{
|
||||
if (!manager.HasItem(requiredItemType, requiredAmount))
|
||||
{
|
||||
string message = string.IsNullOrWhiteSpace(requirementFailMessage)
|
||||
? manager.GetInsufficientMessage(requiredItemType, requiredAmount)
|
||||
: requirementFailMessage;
|
||||
|
||||
onRewardGiven?.Invoke();
|
||||
if (showMessages)
|
||||
manager.RequestMessage(message);
|
||||
|
||||
onRequirementMissing?.Invoke();
|
||||
onActionFailed?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
if (consumeRequiredItem)
|
||||
{
|
||||
bool consumed = manager.TryUseItem(requiredItemType, requiredAmount, true, false, true);
|
||||
if (!consumed)
|
||||
{
|
||||
onActionFailed?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
onRequiredItemConsumed?.Invoke();
|
||||
}
|
||||
|
||||
if (showMessages && !string.IsNullOrWhiteSpace(requirementSuccessMessage))
|
||||
manager.RequestMessage(requirementSuccessMessage);
|
||||
}
|
||||
|
||||
int actuallyGiven = 0;
|
||||
if (giveRewardItem)
|
||||
{
|
||||
actuallyGiven = manager.AddItemAndGetAddedAmount(rewardItemType, requestedRewardAmount);
|
||||
if (actuallyGiven <= 0)
|
||||
{
|
||||
onActionFailed?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
onRewardGiven?.Invoke();
|
||||
}
|
||||
|
||||
alreadyCompleted = true;
|
||||
|
||||
if (persistActionState)
|
||||
manager.MarkPersistentKeyConsumed(persistentActionKey);
|
||||
|
||||
if (showMessages)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(rewardGivenMessage))
|
||||
manager.RequestMessage(rewardGivenMessage);
|
||||
else if (!giveRewardItem)
|
||||
manager.RequestMessage("완료되었습니다.");
|
||||
}
|
||||
|
||||
onActionCompleted?.Invoke();
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log($"[ItemReward] {rewardItemType} +{amount}");
|
||||
{
|
||||
if (giveRewardItem)
|
||||
Debug.Log($"[ItemReward] {rewardItemType} +{actuallyGiven}", this);
|
||||
else
|
||||
Debug.Log("[ItemReward] 아이템 지급 없이 액션 완료", this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이번 호출에서만 amount를 바꿔 지급합니다. Inspector의 rewardAmount 값은 바꾸지 않습니다.
|
||||
/// </summary>
|
||||
public void GiveRewardAmount(int amount)
|
||||
{
|
||||
int previousAmount = rewardAmount;
|
||||
rewardAmount = Mathf.Max(1, amount);
|
||||
GiveReward();
|
||||
ExecuteAction();
|
||||
rewardAmount = previousAmount;
|
||||
}
|
||||
|
||||
public void ResetRewardState()
|
||||
public void ResetActionState()
|
||||
{
|
||||
alreadyGiven = false;
|
||||
alreadyCompleted = false;
|
||||
|
||||
if (persistActionState && ResolveManager() != null)
|
||||
inventoryManager.ResetPersistentKey(persistentActionKey);
|
||||
}
|
||||
|
||||
public void SetRewardItemType(InventoryItemType itemType)
|
||||
@@ -78,8 +207,67 @@ public void SetRewardItemType(InventoryItemType itemType)
|
||||
rewardItemType = itemType;
|
||||
}
|
||||
|
||||
public void SetRequiredItemType(InventoryItemType itemType)
|
||||
{
|
||||
requiredItemType = itemType;
|
||||
}
|
||||
|
||||
public void SetInventoryManager(InventoryManager manager)
|
||||
{
|
||||
inventoryManager = manager;
|
||||
}
|
||||
|
||||
private InventoryManager ResolveManager()
|
||||
{
|
||||
if (inventoryManager != null)
|
||||
return inventoryManager;
|
||||
|
||||
if (InventoryManager.Instance != null)
|
||||
inventoryManager = InventoryManager.Instance;
|
||||
else if (autoFindManager)
|
||||
inventoryManager = FindFirstObjectByType<InventoryManager>();
|
||||
|
||||
return inventoryManager;
|
||||
}
|
||||
|
||||
private void ResolvePersistentKey()
|
||||
{
|
||||
if (!persistActionState)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(persistentActionKey))
|
||||
persistentActionKey = $"Reward_{gameObject.scene.name}_{GetHierarchyPath(transform)}";
|
||||
}
|
||||
|
||||
private string GetHierarchyPath(Transform target)
|
||||
{
|
||||
if (target == null)
|
||||
return "Unknown";
|
||||
|
||||
string path = target.name;
|
||||
Transform parent = target.parent;
|
||||
while (parent != null)
|
||||
{
|
||||
path = parent.name + "/" + path;
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
requiredAmount = Mathf.Max(1, requiredAmount);
|
||||
rewardAmount = Mathf.Max(1, rewardAmount);
|
||||
|
||||
if (persistActionState && string.IsNullOrWhiteSpace(persistentActionKey))
|
||||
{
|
||||
string editorId = GlobalObjectId.GetGlobalObjectIdSlow(this).ToString();
|
||||
persistentActionKey = string.IsNullOrWhiteSpace(editorId)
|
||||
? $"Reward_{gameObject.scene.name}_{GetHierarchyPath(transform)}"
|
||||
: $"Reward_{editorId}";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user