미션 생성기

This commit is contained in:
2026-05-11 13:12:48 +09:00
parent 23470d36a9
commit a35b7f8b17
7 changed files with 151 additions and 2 deletions

Binary file not shown.

View File

@@ -32,5 +32,12 @@ public bool PayMoney(int cost)
//예산 부족
return false;
}
// 미션 생성 시점에 예산 덮어쓰기
public void SetBudget(int budget)
{
_budget = Mathf.Max(0, budget);
OnBudgetChanged?.Invoke(_budget);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -10,5 +10,12 @@ public class ShoppingOrderList : ScriptableObject
public IReadOnlyList<ShoppingOrderEntry> Entries => _entries;
public int Count => _entries.Count;
// 런타임 생성용 (MissionGenerator 등에서 사용)
public void SetEntries(IEnumerable<ShoppingOrderEntry> entries)
{
_entries.Clear();
_entries.AddRange(entries);
}
}
}

View File

@@ -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<ProductGroup, List<ItemData>>();
var allItems = Object.FindObjectsByType<ItemInstance>(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<ItemData>();
stockByGroup[data.ProductGroup] = list;
}
list.Add(data);
}
if (stockByGroup.Count == 0)
{
Debug.LogWarning("[MissionGenerator] 씬에 유효한 상품이 없습니다");
return;
}
// 2. 사용 가능한 그룹 풀에서 미션 개수만큼 무작위 선택
var groupPool = new List<ProductGroup>(stockByGroup.Keys);
Shuffle(groupPool);
int desiredCount = Random.Range(_minMissionCount, _maxMissionCount + 1);
int missionCount = Mathf.Min(desiredCount, groupPool.Count);
// 3. 각 그룹별로 (재고로 클램프된) 수량 부여 + 최저가 기준 최적 비용 누적
var entries = new List<ShoppingOrderEntry>(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<ShoppingOrderList>();
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<T>(IList<T> 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]);
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: beba3b48f8e8647409d75cef1950a419

View File

@@ -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;