140 lines
4.9 KiB
C#
140 lines
4.9 KiB
C#
using System.Collections.Generic;
|
|
using Cielonos.MainGame.Inventory;
|
|
using UnityEngine;
|
|
|
|
namespace Cielonos.MainGame.Items
|
|
{
|
|
/// <summary>
|
|
/// 从 Resources/Items 目录动态加载物品预制体,并提供加权随机抽取功能。
|
|
/// 替代手动维护的 ScriptableObject 物品池,新增装备只需放入对应目录即可自动进入候选池。
|
|
/// </summary>
|
|
public static class ItemPoolHelper
|
|
{
|
|
private const string ITEMS_ROOT_PATH = "Items";
|
|
|
|
private static readonly Dictionary<string, GameObject[]> prefabCache = new Dictionary<string, GameObject[]>();
|
|
|
|
/// <summary>
|
|
/// 从 Resources/Items 的指定子目录中加载所有带 ItemBase 的预制体,
|
|
/// 按 ItemFilter 过滤后进行加权随机抽取(不重复)。
|
|
/// </summary>
|
|
/// <param name="count">需要抽取的数量。</param>
|
|
/// <param name="rng">随机数生成器,确保同 seed 可复现。</param>
|
|
/// <param name="filter">过滤器,传 null 则不过滤。</param>
|
|
/// <param name="subFolders">Resources/Items 下的子目录名。不传则搜索整个 Items 目录。</param>
|
|
/// <returns>抽中的物品预制体列表(不重复)。</returns>
|
|
public static List<GameObject> Roll(int count, System.Random rng, ItemFilter filter = null, params string[] subFolders)
|
|
{
|
|
List<CandidateEntry> candidates = BuildCandidateList(filter, subFolders);
|
|
return WeightedPick(candidates, count, rng);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 清除预制体缓存。可在场景切换或 Run 结束时调用。
|
|
/// </summary>
|
|
public static void ClearCache()
|
|
{
|
|
prefabCache.Clear();
|
|
}
|
|
|
|
private static List<CandidateEntry> BuildCandidateList(ItemFilter filter, string[] subFolders)
|
|
{
|
|
List<CandidateEntry> candidates = new List<CandidateEntry>();
|
|
|
|
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<ItemBase>();
|
|
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<GameObject>(resourcePath);
|
|
prefabCache[resourcePath] = loaded;
|
|
return loaded;
|
|
}
|
|
|
|
private static List<GameObject> WeightedPick(List<CandidateEntry> candidates, int count, System.Random rng)
|
|
{
|
|
List<GameObject> results = new List<GameObject>();
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|