using System.Collections.Generic; using Cielonos.MainGame.Inventory; using UnityEngine; namespace Cielonos.MainGame.Items { /// /// 从 Resources/Items 目录动态加载物品预制体,并提供加权随机抽取功能。 /// 替代手动维护的 ScriptableObject 物品池,新增装备只需放入对应目录即可自动进入候选池。 /// public static class ItemPoolHelper { private const string ITEMS_ROOT_PATH = "Items"; private static readonly Dictionary prefabCache = new Dictionary(); /// /// 从 Resources/Items 的指定子目录中加载所有带 ItemBase 的预制体, /// 按 ItemFilter 过滤后进行加权随机抽取(不重复)。 /// /// 需要抽取的数量。 /// 随机数生成器,确保同 seed 可复现。 /// 过滤器,传 null 则不过滤。 /// Resources/Items 下的子目录名。不传则搜索整个 Items 目录。 /// 抽中的物品预制体列表(不重复)。 public static List Roll(int count, System.Random rng, ItemFilter filter = null, params string[] subFolders) { List candidates = BuildCandidateList(filter, subFolders); return WeightedPick(candidates, count, rng); } /// /// 清除预制体缓存。可在场景切换或 Run 结束时调用。 /// public static void ClearCache() { prefabCache.Clear(); } private static List BuildCandidateList(ItemFilter filter, string[] subFolders) { List candidates = new List(); string[] paths = subFolders != null && subFolders.Length > 0 ? subFolders : new[] { string.Empty }; foreach (string sub in paths) { string resourcePath = string.IsNullOrEmpty(sub) ? ITEMS_ROOT_PATH : $"{ITEMS_ROOT_PATH}/{sub}"; GameObject[] prefabs = LoadPrefabs(resourcePath); foreach (GameObject prefab in prefabs) { ItemBase item = prefab.GetComponent(); if (item == null || item.contentData == null) continue; ContentData data = item.contentData; // 权重为 0 的物品不进入随机池 if (data.dropWeight <= 0f) continue; // 应用过滤器 if (filter != null && !filter.Match(item)) continue; candidates.Add(new CandidateEntry(prefab, data)); } } return candidates; } private static GameObject[] LoadPrefabs(string resourcePath) { if (prefabCache.TryGetValue(resourcePath, out GameObject[] cached)) { return cached; } GameObject[] loaded = Resources.LoadAll(resourcePath); prefabCache[resourcePath] = loaded; return loaded; } private static List WeightedPick(List candidates, int count, System.Random rng) { List results = new List(); int pickCount = Mathf.Min(count, candidates.Count); for (int i = 0; i < pickCount; i++) { float totalWeight = 0f; foreach (CandidateEntry entry in candidates) { totalWeight += entry.data.dropWeight; } if (totalWeight <= 0f) break; float roll = (float)(rng.NextDouble() * totalWeight); float cumulative = 0f; CandidateEntry picked = null; foreach (CandidateEntry entry in candidates) { cumulative += entry.data.dropWeight; if (roll <= cumulative) { picked = entry; break; } } // 安全回退 picked ??= candidates[^1]; results.Add(picked.prefab); candidates.Remove(picked); } return results; } private sealed class CandidateEntry { public readonly GameObject prefab; public readonly ContentData data; public CandidateEntry(GameObject prefab, ContentData data) { this.prefab = prefab; this.data = data; } } } }