158 lines
5.2 KiB
C#
158 lines
5.2 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|
||
}
|