119 lines
4.7 KiB
C#
119 lines
4.7 KiB
C#
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]);
|
||
}
|
||
}
|
||
}
|
||
}
|