Files
Cielonos/Assets/Scripts/MainGame/Items/ItemPoolHelper.cs
SoulliesOfficial 649b7a5ddc 更新
2026-05-23 08:27:50 -04:00

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;
}
}
}
}