Files
Shopping_UnityVR/Assets/02_Scripts/Shopping/MissionGenerator.cs

124 lines
5.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
// 테스트용 — 여기에 추가된 ProductGroup은 미션 후보에서 자동 제외
[Header("Excluded Groups (Test)")]
[SerializeField] private List<ProductGroup> _excludedGroups = new List<ProductGroup>();
[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 (_excludedGroups.Contains(data.ProductGroup)) 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]);
}
}
}
}