Files
Cielonos/Assets/Scripts/MainGame/Items/ItemPool.cs
SoulliesOfficial 39b43680a9 爆更
2026-07-18 03:16:20 -04:00

158 lines
5.2 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;
using System.Collections.Generic;
using Cielonos.MainGame.Inventory;
using UnityEngine;
namespace Cielonos.MainGame.Items
{
/// <summary>
/// Resources/Items 下的物品 prefab 目录。它只负责发现、筛选和抽取 ItemBase prefab
/// 不负责奖励的实例化、归属或 UI 行为。
/// </summary>
public static class ItemPool
{
private const string ItemsRootPath = "Items";
private static readonly Dictionary<string, ItemBase[]> PrefabCache = new();
public static List<ItemBase> Roll(int count, System.Random rng, ItemFilter filter = null,
params string[] resourceFolders)
{
return Roll(count, rng, filter, null, resourceFolders);
}
/// <summary>
/// 按权重从候选池中无重复抽取。excluded 用于同一次生成内避免重复物品。
/// </summary>
public static List<ItemBase> Roll(int count, System.Random rng, ItemFilter filter,
ISet<ItemBase> excluded, params string[] resourceFolders)
{
if (count <= 0 || rng == null)
{
return new List<ItemBase>();
}
List<CandidateEntry> candidates = BuildCandidates(filter, excluded, resourceFolders);
List<ItemBase> 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<ItemBase> excluded = null,
params string[] resourceFolders)
{
return BuildCandidates(filter, excluded, resourceFolders).Count > 0;
}
public static void ClearCache()
{
PrefabCache.Clear();
}
private static List<CandidateEntry> BuildCandidates(ItemFilter filter, ISet<ItemBase> excluded,
string[] resourceFolders)
{
List<CandidateEntry> 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<GameObject>(resourcePath);
List<ItemBase> items = new(prefabs.Length);
foreach (GameObject prefab in prefabs)
{
ItemBase item = prefab.GetComponent<ItemBase>();
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;
}
}
}
}