diff --git a/Assets/01_Scenes/MyProject/GameScene.unity b/Assets/01_Scenes/MyProject/GameScene.unity index 5118b932..9aca56e1 100644 --- a/Assets/01_Scenes/MyProject/GameScene.unity +++ b/Assets/01_Scenes/MyProject/GameScene.unity @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a6307f49e4424357e639c3cbfa363ae74dd9b2bad20973ad5582e7016390b3f -size 12072711 +oid sha256:43e2c6f97649e023a3ede79a2e282f615b72c03bda268ab63f97b4aac98ecac1 +size 12073484 diff --git a/Assets/02_Scripts/Data/Player/PlayerWallet.cs b/Assets/02_Scripts/Data/Player/PlayerWallet.cs index fd109490..72b5bad9 100644 --- a/Assets/02_Scripts/Data/Player/PlayerWallet.cs +++ b/Assets/02_Scripts/Data/Player/PlayerWallet.cs @@ -32,5 +32,12 @@ public bool PayMoney(int cost) //예산 부족 return false; } + + // 미션 생성 시점에 예산 덮어쓰기 + public void SetBudget(int budget) + { + _budget = Mathf.Max(0, budget); + OnBudgetChanged?.Invoke(_budget); + } } } diff --git a/Assets/02_Scripts/Data/Shopping/ShoppingOrderEntry.cs b/Assets/02_Scripts/Data/Shopping/ShoppingOrderEntry.cs index aff9aa8e..1a1b52f5 100644 --- a/Assets/02_Scripts/Data/Shopping/ShoppingOrderEntry.cs +++ b/Assets/02_Scripts/Data/Shopping/ShoppingOrderEntry.cs @@ -11,5 +11,13 @@ public class ShoppingOrderEntry public ProductGroup ProductGroup => _productGroup; public int RequiredQuantity => _requiredQuantity; + + public ShoppingOrderEntry() { } + + public ShoppingOrderEntry(ProductGroup productGroup, int requiredQuantity) + { + _productGroup = productGroup; + _requiredQuantity = requiredQuantity; + } } } diff --git a/Assets/02_Scripts/Data/Shopping/ShoppingOrderList.cs b/Assets/02_Scripts/Data/Shopping/ShoppingOrderList.cs index ee558e2c..1563a916 100644 --- a/Assets/02_Scripts/Data/Shopping/ShoppingOrderList.cs +++ b/Assets/02_Scripts/Data/Shopping/ShoppingOrderList.cs @@ -10,5 +10,12 @@ public class ShoppingOrderList : ScriptableObject public IReadOnlyList Entries => _entries; public int Count => _entries.Count; + + // 런타임 생성용 (MissionGenerator 등에서 사용) + public void SetEntries(IEnumerable entries) + { + _entries.Clear(); + _entries.AddRange(entries); + } } } diff --git a/Assets/02_Scripts/Shopping/MissionGenerator.cs b/Assets/02_Scripts/Shopping/MissionGenerator.cs new file mode 100644 index 00000000..33dc3bda --- /dev/null +++ b/Assets/02_Scripts/Shopping/MissionGenerator.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using UnityEngine; +using VRShopping.Items; +using VRShopping.Player; +using VRShopping.UI; + +namespace VRShopping.Shopping +{ + // 씬 로드 시 현재 씬의 상품 재고를 스캔해서 랜덤 쇼핑 미션과 예산을 생성한다. + // - 미션 항목 수: [_minMissionCount, _maxMissionCount] 범위에서 랜덤 (씬 보유 그룹 수로 자동 클램프) + // - 항목당 수량: [1, _maxQuantityPerEntry] 범위에서 랜덤 (그룹별 재고 수로 자동 클램프) + // - 예산: 그룹별 최저가 기준 최적 비용 × (1 + Random[_budgetMarginMin, _budgetMarginMax]) + public class MissionGenerator : MonoBehaviour, ISceneInitializable + { + [Header("Mission Size")] + [SerializeField, Min(1)] private int _minMissionCount = 10; + [SerializeField, Min(1)] private int _maxMissionCount = 15; + + [Header("Quantity Per Entry")] + [SerializeField, Min(1)] private int _maxQuantityPerEntry = 5; + + [Header("Budget Margin (over optimal cost)")] + [SerializeField, Range(0f, 1f)] private float _budgetMarginMin = 0.20f; + [SerializeField, Range(0f, 1f)] private float _budgetMarginMax = 0.30f; + + [Header("References")] + [SerializeField] private ShoppingOrderView _orderView; + [SerializeField] private PlayerWallet _wallet; + + public void OnSceneLoaded() + { + Generate(); + } + + public void Generate() + { + if (_orderView == null || _wallet == null) + { + Debug.LogWarning("[MissionGenerator] 참조 누락 (orderView/wallet)"); + return; + } + + // 1. 씬의 모든 상품을 ProductGroup별로 집계 (None과 ItemData 누락은 제외) + var stockByGroup = new Dictionary>(); + var allItems = Object.FindObjectsByType(FindObjectsSortMode.None); + foreach (var inst in allItems) + { + var data = inst.ItemDataInfo; + if (data == null) continue; + if (data.ProductGroup == ProductGroup.None) continue; + + if (!stockByGroup.TryGetValue(data.ProductGroup, out var list)) + { + list = new List(); + stockByGroup[data.ProductGroup] = list; + } + list.Add(data); + } + + if (stockByGroup.Count == 0) + { + Debug.LogWarning("[MissionGenerator] 씬에 유효한 상품이 없습니다"); + return; + } + + // 2. 사용 가능한 그룹 풀에서 미션 개수만큼 무작위 선택 + var groupPool = new List(stockByGroup.Keys); + Shuffle(groupPool); + + int desiredCount = Random.Range(_minMissionCount, _maxMissionCount + 1); + int missionCount = Mathf.Min(desiredCount, groupPool.Count); + + // 3. 각 그룹별로 (재고로 클램프된) 수량 부여 + 최저가 기준 최적 비용 누적 + var entries = new List(missionCount); + int optimalCost = 0; + + for (int i = 0; i < missionCount; i++) + { + var group = groupPool[i]; + var stock = stockByGroup[group]; + + int maxQty = Mathf.Min(_maxQuantityPerEntry, stock.Count); + int qty = Random.Range(1, maxQty + 1); + + int cheapestPrice = int.MaxValue; + foreach (var data in stock) + { + if (data.FinalPrice < cheapestPrice) cheapestPrice = data.FinalPrice; + } + + entries.Add(new ShoppingOrderEntry(group, qty)); + optimalCost += cheapestPrice * qty; + } + + // 4. 런타임 ShoppingOrderList 생성 후 뷰에 주입 + var runtimeList = ScriptableObject.CreateInstance(); + runtimeList.name = "ShoppingOrderList (Runtime)"; + runtimeList.SetEntries(entries); + _orderView.SetOrderList(runtimeList); + + // 5. 예산 책정 (최적 비용 + 20~30% 마진) + float margin = Random.Range(_budgetMarginMin, _budgetMarginMax); + int budget = Mathf.CeilToInt(optimalCost * (1f + margin)); + _wallet.SetBudget(budget); + + Debug.Log($"[MissionGenerator] 미션 {missionCount}개 / 최적가 {optimalCost:N0} / 예산 {budget:N0} (+{margin * 100f:F0}%)"); + } + + private static void Shuffle(IList list) + { + for (int i = list.Count - 1; i > 0; i--) + { + int j = Random.Range(0, i + 1); + (list[i], list[j]) = (list[j], list[i]); + } + } + } +} diff --git a/Assets/02_Scripts/Shopping/MissionGenerator.cs.meta b/Assets/02_Scripts/Shopping/MissionGenerator.cs.meta new file mode 100644 index 00000000..ee066eaa --- /dev/null +++ b/Assets/02_Scripts/Shopping/MissionGenerator.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: beba3b48f8e8647409d75cef1950a419 \ No newline at end of file diff --git a/Assets/02_Scripts/UI/ShoppingOrderView.cs b/Assets/02_Scripts/UI/ShoppingOrderView.cs index 76edff46..d7361c02 100644 --- a/Assets/02_Scripts/UI/ShoppingOrderView.cs +++ b/Assets/02_Scripts/UI/ShoppingOrderView.cs @@ -20,6 +20,13 @@ private void Start() Rebuild(); } + // 런타임에 미션 목록 교체 (MissionGenerator에서 호출) + public void SetOrderList(ShoppingOrderList orderList) + { + _orderList = orderList; + Rebuild(); + } + public void Rebuild() { if (_orderList == null || _rowPrefab == null || _rowContainer == null) return;