补全 UI 设计

- `ItemDescForm` 道具详细信息面板,目前还未添加 DescText 内容的赋值
- `RepoForm` 道具入槽逻辑调整,为了适应 `ItemDescForm` 的点击打开逻辑,点击道具自动匹配槽位改成拖动道具到相应槽位
- 添加了一系列图片素材
This commit is contained in:
SepComet
2026-03-04 12:47:20 +08:00
parent 3e52a4df55
commit e12d2e73b2
147 changed files with 9866 additions and 277 deletions
@@ -10,9 +10,15 @@ namespace GeometryTD.CustomComponent
{
internal sealed class CombatResourceManager
{
// 每次击杀敌人后,进入“掉组件判定”的概率:
// chance = clamp(DropChanceBase + (phaseIndex - 1) * DropChancePerPhase, 0, DropChanceCap)
private const float DropChanceBase = 0.05f;
private const float DropChancePerPhase = 0.0125f;
private const float DropChanceCap = 0.45f;
private const float DropChancePerPhase = 0.2f;
private const float DropChanceCap = 0.2f;
// 用于把阶段映射到 [0,1] 的稀有度曲线输入:
// phaseT = clamp01((phaseIndex - 1) / RarityCurveScalePhase)
// 该值越大,稀有度随阶段提升的节奏越慢。
private const float RarityCurveScalePhase = 30f;
private readonly List<DROutGameDropPool> _eligibleDropPoolBuffer = new();
@@ -78,6 +84,13 @@ namespace GeometryTD.CustomComponent
_rewardInventory.Gold += gold;
}
/// <summary>
/// 击杀敌人时的掉落入口。
/// displayPhaseIndex 会影响:
/// 1) 总掉落概率(由 DropChanceBase / DropChancePerPhase / DropChanceCap 决定)
/// 2) 稀有度倾向(通过 phaseT 进入 RollRarity)
/// themeType 会按关卡主题过滤掉落池(LevelThemeType 不匹配直接不参与)。
/// </summary>
public bool TryRollOutGameItemDrop(int displayPhaseIndex, LevelThemeType themeType)
{
int phaseIndex = Mathf.Max(1, displayPhaseIndex);
@@ -113,6 +126,10 @@ namespace GeometryTD.CustomComponent
return false;
}
/// <summary>
/// 直接抽取一个具体掉落物(不走前面的总掉率门槛)。
/// 但 displayPhaseIndex 和 themeType 仍会通过掉落池过滤、稀有度加权影响结果。
/// </summary>
public bool TryRollOutGameItem(int displayPhaseIndex, LevelThemeType themeType, out TowerCompItemData droppedItem)
{
droppedItem = null;
@@ -178,6 +195,12 @@ namespace GeometryTD.CustomComponent
return candidates;
}
/// <summary>
/// 两阶段加权抽样:
/// 1) 先按主题 + 阶段区间过滤候选行
/// 2) 先抽稀有度(稀有度权重 = 同稀有度所有行的 row.Weight * 稀有度曲线权重 之和)
/// 3) 在该稀有度内再按 row.Weight 抽具体行
/// </summary>
private bool TryPickDropPoolRow(int displayPhaseIndex, LevelThemeType themeType, out DROutGameDropPool selectedRow)
{
selectedRow = null;
@@ -197,11 +220,13 @@ namespace GeometryTD.CustomComponent
continue;
}
// 主题过滤:主题不匹配的行,概率为 0。
if (row.LevelThemeType != themeType)
{
continue;
}
// 阶段过滤:不在 [MinPhase, MaxPhase] 的行,概率为 0。
if (displayPhaseIndex < row.MinPhase || displayPhaseIndex > row.MaxPhase)
{
continue;
@@ -230,6 +255,7 @@ namespace GeometryTD.CustomComponent
continue;
}
// 在已选稀有度内,row.Weight 是线性权重倍率。
totalWeight += Mathf.Max(1, row.Weight);
}
@@ -260,6 +286,12 @@ namespace GeometryTD.CustomComponent
return selectedRow != null;
}
/// <summary>
/// 在过滤后的候选行里抽稀有度。
/// 实际稀有度权重公式:
/// rarityWeight = sum(max(1, row.Weight) * GetRarityCurveWeight(row.Rarity, phaseT))
/// 其中 phaseT = clamp01((displayPhaseIndex - 1) / RarityCurveScalePhase)。
/// </summary>
private RarityType RollRarity(int displayPhaseIndex, List<DROutGameDropPool> candidates)
{
_rarityRollWeightBuffer.Clear();
@@ -318,6 +350,14 @@ namespace GeometryTD.CustomComponent
return RarityType.None;
}
/// <summary>
/// 返回各稀有度在当前阶段的曲线权重。
/// 返回值越大,该稀有度在 RollRarity 中越容易被抽到。
/// phaseT 取值范围为 [0,1]:
/// - White/Green:钟形趋势(前中期权重更高)
/// - Blue/Purple:随阶段近似线性上升
/// - Red:随阶段二次上升(前期很低,后期明显抬升)
/// </summary>
private static float GetRarityCurveWeight(RarityType rarityType, float phaseT)
{
float hump = Mathf.Exp(-Mathf.Pow((phaseT - 0.35f) / 0.28f, 2f));
@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using GameFramework.Resource;
using GeometryTD.CustomUtility;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.CustomComponent
{
public class SpriteCacheComponent : GameFrameworkComponent
{
[SerializeField] private float _pixelsPerUnit = 100f;
[SerializeField] private Vector2 _defaultPivot = new(0.5f, 0.5f);
private Dictionary<string, Sprite> _spriteCache;
private Dictionary<string, List<Action<Sprite>>> _pendingCallbacks;
private ResourceComponent _resource;
void Start()
{
_spriteCache = new Dictionary<string, Sprite>();
_pendingCallbacks = new Dictionary<string, List<Action<Sprite>>>();
_resource = GameEntry.Resource;
}
public void GetSprite(string assetName, Action<Sprite> callback)
{
if (_spriteCache.TryGetValue(assetName, out var sprite))
{
callback?.Invoke(sprite);
return;
}
if (_pendingCallbacks.TryGetValue(assetName, out var pendingList))
{
pendingList.Add(callback);
return;
}
_pendingCallbacks[assetName] = new List<Action<Sprite>> { callback };
_resource.LoadAsset
(
AssetUtility.GetTextureAsset(assetName),
Constant.AssetPriority.UIFormAsset,
new LoadAssetCallbacks(
(resourcePath, asset, duration, userData) =>
{
Log.Debug(resourcePath);
Texture2D texture = asset as Texture2D;
Sprite loadedSprite = null;
if (texture != null)
{
loadedSprite = Sprite.Create(
texture,
new Rect(0, 0, texture.width, texture.height),
_defaultPivot,
_pixelsPerUnit);
_spriteCache[assetName] = loadedSprite;
}
if (_pendingCallbacks.TryGetValue(assetName, out var callbacks))
{
_pendingCallbacks.Remove(assetName);
for (int i = 0; i < callbacks.Count; i++)
{
callbacks[i]?.Invoke(loadedSprite);
}
}
},
(resourcePath, status, errorMessage, userData) =>
{
Log.Error("Can not load icon '{0}' from '{1}' with error message '{2}'.",
assetName,
resourcePath,
errorMessage);
if (_pendingCallbacks.TryGetValue(assetName, out var callbacks))
{
_pendingCallbacks.Remove(assetName);
for (int i = 0; i < callbacks.Count; i++)
{
callbacks[i]?.Invoke(null);
}
}
}
)
);
}
private void OnDestroy()
{
_spriteCache.Clear();
_pendingCallbacks.Clear();
_resource = null;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8b0237c7a9ed81745b92fe3f1bd45af4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: