using System; using System.Collections.Generic; using Cielonos.MainGame.Inventory; using UnityEngine; namespace Cielonos.MainGame.Items { /// /// Resources/Items 下的物品 prefab 目录。它只负责发现、筛选和抽取 ItemBase prefab, /// 不负责奖励的实例化、归属或 UI 行为。 /// public static class ItemPool { private const string ItemsRootPath = "Items"; private static readonly Dictionary PrefabCache = new(); public static List Roll(int count, System.Random rng, ItemFilter filter = null, params string[] resourceFolders) { return Roll(count, rng, filter, null, resourceFolders); } /// /// 按权重从候选池中无重复抽取。excluded 用于同一次生成内避免重复物品。 /// public static List Roll(int count, System.Random rng, ItemFilter filter, ISet excluded, params string[] resourceFolders) { if (count <= 0 || rng == null) { return new List(); } List candidates = BuildCandidates(filter, excluded, resourceFolders); List results = new(Mathf.Min(count, candidates.Count)); while (results.Count < count && candidates.Count > 0) { float totalWeight = 0f; foreach (CandidateEntry candidate in candidates) { totalWeight += candidate.weight; } if (totalWeight <= 0f) { break; } float roll = (float)(rng.NextDouble() * totalWeight); float cumulative = 0f; int selectedIndex = candidates.Count - 1; for (int index = 0; index < candidates.Count; index++) { cumulative += candidates[index].weight; if (roll <= cumulative) { selectedIndex = index; break; } } CandidateEntry selected = candidates[selectedIndex]; results.Add(selected.itemPrefab); candidates.RemoveAt(selectedIndex); } return results; } public static bool HasCandidate(ItemFilter filter, ISet excluded = null, params string[] resourceFolders) { return BuildCandidates(filter, excluded, resourceFolders).Count > 0; } public static void ClearCache() { PrefabCache.Clear(); } private static List BuildCandidates(ItemFilter filter, ISet excluded, string[] resourceFolders) { List candidates = new(); string[] paths = resourceFolders != null && resourceFolders.Length > 0 ? resourceFolders : new[] { string.Empty }; foreach (string resourceFolder in paths) { string resourcePath = string.IsNullOrEmpty(resourceFolder) ? ItemsRootPath : $"{ItemsRootPath}/{resourceFolder}"; foreach (ItemBase itemPrefab in LoadPrefabs(resourcePath)) { if (itemPrefab == null || itemPrefab.contentData == null) { continue; } if (excluded != null && excluded.Contains(itemPrefab)) { continue; } ContentData data = itemPrefab.contentData; if (data.dropWeight <= 0f || (filter != null && !filter.Match(itemPrefab))) { continue; } candidates.Add(new CandidateEntry(itemPrefab, data.dropWeight)); } } return candidates; } private static ItemBase[] LoadPrefabs(string resourcePath) { if (PrefabCache.TryGetValue(resourcePath, out ItemBase[] cached)) { return cached; } GameObject[] prefabs = Resources.LoadAll(resourcePath); List items = new(prefabs.Length); foreach (GameObject prefab in prefabs) { ItemBase item = prefab.GetComponent(); if (item != null) { items.Add(item); } } cached = items.ToArray(); PrefabCache[resourcePath] = cached; return cached; } private readonly struct CandidateEntry { public readonly ItemBase itemPrefab; public readonly float weight; public CandidateEntry(ItemBase itemPrefab, float weight) { this.itemPrefab = itemPrefab; this.weight = weight; } } } }