补充参战防御塔进入战斗逻辑

- 将 Inventory 中参战的防御塔数据传递到 CombatSelectForm 中
- 为防御塔的图标展示添加 TowerRepoItem 与 TowerIconArea 适配防御塔的三层 Image
This commit is contained in:
SepComet
2026-03-05 13:44:49 +08:00
parent 9db9aeabcd
commit 8a478982f8
56 changed files with 2507 additions and 467 deletions
@@ -0,0 +1,21 @@
using System;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.UI
{
[Serializable]
public class TowerIconAreaContext : IconAreaContext
{
public Color MuzzleColor = Color.white;
public Color BearingColor = Color.white;
public Color BaseColor = Color.white;
public TowerIconAreaContext()
{
ComponentSlotType = TowerCompSlotType.None;
Color = Color.white;
Icon = null;
}
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 6fb39901e8409cd4db9dbde01bee1ced
guid: e3576597006db5b458566429f98a6f43
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -1,443 +0,0 @@
using GameFramework.DataTable;
using GeometryTD.DataTable;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.CustomUtility
{
public static class IconColorGenerator
{
private const int StatCount = 5;
private const float MinSaturation = 0.4f;
private const float MinValue = 0.6f;
private const float TinyValue = 0.0001f;
private static readonly float[] HueMap = { 0f, 30f, 200f, 120f, 270f };
private static StatRange _attackDamageRange = new StatRange(1f, 300f);
private static StatRange _damageRandomRateRange = new StatRange(0f, 0.3f);
private static StatRange _rotateSpeedRange = new StatRange(10f, 240f);
private static StatRange _attackRangeRange = new StatRange(2f, 8f);
private static StatRange _attackSpeedRange = new StatRange(0.5f, 4f);
private static bool s_HasMuzzleRange;
private static bool s_HasBearingRange;
private static bool s_HasBaseRange;
public static Color GenerateForComponent(TowerCompItemData item)
{
if (item == null)
{
return Color.white;
}
float attackDamage = 0f;
float damageRandomRate = 0f;
float rotateSpeed = 0f;
float attackRange = 0f;
float attackSpeed = 0f;
if (item is MuzzleCompItemData muzzle)
{
attackDamage = ResolveArrayValue(muzzle.AttackDamage);
damageRandomRate = Mathf.Max(0f, muzzle.DamageRandomRate);
}
else if (item is BearingCompItemData bearing)
{
rotateSpeed = ResolveArrayValue(bearing.RotateSpeed);
attackRange = ResolveArrayValue(bearing.AttackRange);
}
else if (item is BaseCompItemData baseComp)
{
attackSpeed = ResolveArrayValue(baseComp.AttackSpeed);
}
return GenerateColor(
attackDamage,
damageRandomRate,
rotateSpeed,
attackRange,
attackSpeed,
item.SlotType);
}
public static Color GenerateForTower(TowerItemData tower)
{
if (tower?.Stats == null)
{
return Color.white;
}
TowerStatsData stats = tower.Stats;
return GenerateColor(
ResolveArrayValue(stats.AttackDamage),
stats.DamageRandomRate,
ResolveArrayValue(stats.RotateSpeed),
ResolveArrayValue(stats.AttackRange),
ResolveArrayValue(stats.AttackSpeed),
TowerCompSlotType.None);
}
private static Color GenerateColor(
float attackDamage,
float damageRandomRate,
float rotateSpeed,
float attackRange,
float attackSpeed,
TowerCompSlotType slotType)
{
TryRefreshRangesFromDataTables();
float[] normalizedStats = new float[StatCount];
normalizedStats[0] = Normalize(attackDamage, _attackDamageRange);
normalizedStats[1] = Normalize(damageRandomRate, _damageRandomRateRange);
normalizedStats[2] = Normalize(rotateSpeed, _rotateSpeedRange);
normalizedStats[3] = Normalize(attackRange, _attackRangeRange);
normalizedStats[4] = Normalize(attackSpeed, _attackSpeedRange);
FindTopTwoIndexes(normalizedStats, out int primaryIndex, out int secondaryIndex);
float denominator = normalizedStats[primaryIndex] + normalizedStats[secondaryIndex] + TinyValue;
float blendWeight = normalizedStats[secondaryIndex] / denominator;
float hue = LerpHue(HueMap[primaryIndex], HueMap[secondaryIndex], blendWeight);
hue = NormalizeHue(hue + ResolveSlotHueOffset(slotType));
float purity = normalizedStats[primaryIndex] - normalizedStats[secondaryIndex];
float saturation = Mathf.Lerp(0.45f, 0.9f, Mathf.Clamp01(purity * 1.5f));
saturation = Mathf.Max(saturation, MinSaturation);
float intensity = 0f;
for (int i = 0; i < normalizedStats.Length; i++)
{
intensity += normalizedStats[i];
}
float value = Mathf.Lerp(0.55f, 0.95f, intensity / StatCount);
value = Mathf.Max(value, MinValue);
Color color = Color.HSVToRGB(hue / 360f, saturation, value);
color.a = 1f;
return color;
}
private static void TryRefreshRangesFromDataTables()
{
if (GameEntry.DataTable == null)
{
return;
}
if (!s_HasMuzzleRange &&
TryResolveMuzzleRange(out StatRange attackDamageRange, out StatRange damageRandomRateRange))
{
_attackDamageRange = attackDamageRange;
_damageRandomRateRange = damageRandomRateRange;
s_HasMuzzleRange = true;
}
if (!s_HasBearingRange &&
TryResolveBearingRange(out StatRange rotateSpeedRange, out StatRange attackRangeRange))
{
_rotateSpeedRange = rotateSpeedRange;
_attackRangeRange = attackRangeRange;
s_HasBearingRange = true;
}
if (!s_HasBaseRange && TryResolveBaseRange(out StatRange attackSpeedRange))
{
_attackSpeedRange = attackSpeedRange;
s_HasBaseRange = true;
}
}
private static bool TryResolveMuzzleRange(out StatRange attackDamageRange, out StatRange damageRandomRateRange)
{
attackDamageRange = _attackDamageRange;
damageRandomRateRange = _damageRandomRateRange;
IDataTable<DRMuzzleComp> dataTable = GameEntry.DataTable.GetDataTable<DRMuzzleComp>();
if (dataTable == null || dataTable.Count <= 0)
{
return false;
}
DRMuzzleComp[] rows = dataTable.GetAllDataRows();
if (rows == null || rows.Length <= 0)
{
return false;
}
bool hasAttackDamage = false;
bool hasDamageRandomRate = false;
float minAttackDamage = float.MaxValue;
float maxAttackDamage = float.MinValue;
float minDamageRandomRate = float.MaxValue;
float maxDamageRandomRate = float.MinValue;
for (int i = 0; i < rows.Length; i++)
{
DRMuzzleComp row = rows[i];
if (row == null)
{
continue;
}
IncludeValues(row.AttackDamage, ref minAttackDamage, ref maxAttackDamage, ref hasAttackDamage);
IncludeValue(Mathf.Max(0f, row.DamageRandomRate), ref minDamageRandomRate, ref maxDamageRandomRate,
ref hasDamageRandomRate);
}
if (!hasAttackDamage || !hasDamageRandomRate)
{
return false;
}
attackDamageRange = BuildRange(minAttackDamage, maxAttackDamage, _attackDamageRange);
damageRandomRateRange = BuildRange(minDamageRandomRate, maxDamageRandomRate, _damageRandomRateRange);
return true;
}
private static bool TryResolveBearingRange(out StatRange rotateSpeedRange, out StatRange attackRangeRange)
{
rotateSpeedRange = _rotateSpeedRange;
attackRangeRange = _attackRangeRange;
IDataTable<DRBearingComp> dataTable = GameEntry.DataTable.GetDataTable<DRBearingComp>();
if (dataTable == null || dataTable.Count <= 0)
{
return false;
}
DRBearingComp[] rows = dataTable.GetAllDataRows();
if (rows == null || rows.Length <= 0)
{
return false;
}
bool hasRotateSpeed = false;
bool hasAttackRange = false;
float minRotateSpeed = float.MaxValue;
float maxRotateSpeed = float.MinValue;
float minAttackRange = float.MaxValue;
float maxAttackRange = float.MinValue;
for (int i = 0; i < rows.Length; i++)
{
DRBearingComp row = rows[i];
if (row == null)
{
continue;
}
IncludeValues(row.RotateSpeed, ref minRotateSpeed, ref maxRotateSpeed, ref hasRotateSpeed);
IncludeValues(row.AttackRange, ref minAttackRange, ref maxAttackRange, ref hasAttackRange);
}
if (!hasRotateSpeed || !hasAttackRange)
{
return false;
}
rotateSpeedRange = BuildRange(minRotateSpeed, maxRotateSpeed, _rotateSpeedRange);
attackRangeRange = BuildRange(minAttackRange, maxAttackRange, _attackRangeRange);
return true;
}
private static bool TryResolveBaseRange(out StatRange attackSpeedRange)
{
attackSpeedRange = _attackSpeedRange;
IDataTable<DRBaseComp> dataTable = GameEntry.DataTable.GetDataTable<DRBaseComp>();
if (dataTable == null || dataTable.Count <= 0)
{
return false;
}
DRBaseComp[] rows = dataTable.GetAllDataRows();
if (rows == null || rows.Length <= 0)
{
return false;
}
bool hasAttackSpeed = false;
float minAttackSpeed = float.MaxValue;
float maxAttackSpeed = float.MinValue;
for (int i = 0; i < rows.Length; i++)
{
DRBaseComp row = rows[i];
if (row == null)
{
continue;
}
IncludeValues(row.AttackSpeed, ref minAttackSpeed, ref maxAttackSpeed, ref hasAttackSpeed);
}
if (!hasAttackSpeed)
{
return false;
}
attackSpeedRange = BuildRange(minAttackSpeed, maxAttackSpeed, _attackSpeedRange);
return true;
}
private static void FindTopTwoIndexes(float[] values, out int primaryIndex, out int secondaryIndex)
{
primaryIndex = 0;
secondaryIndex = 1;
if (values[secondaryIndex] > values[primaryIndex])
{
(primaryIndex, secondaryIndex) = (secondaryIndex, primaryIndex);
}
for (int i = 2; i < values.Length; i++)
{
float value = values[i];
if (value > values[primaryIndex])
{
secondaryIndex = primaryIndex;
primaryIndex = i;
}
else if (value > values[secondaryIndex])
{
secondaryIndex = i;
}
}
}
private static float ResolveSlotHueOffset(TowerCompSlotType slotType)
{
return slotType switch
{
TowerCompSlotType.Bearing => 15f,
TowerCompSlotType.Base => -15f,
_ => 0f
};
}
private static float ResolveArrayValue(int[] values)
{
if (values == null || values.Length <= 0)
{
return 0f;
}
return Mathf.Max(0f, values[0]);
}
private static float ResolveArrayValue(float[] values)
{
if (values == null || values.Length <= 0)
{
return 0f;
}
return Mathf.Max(0f, values[0]);
}
private static void IncludeValues(int[] values, ref float min, ref float max, ref bool hasValue)
{
if (values == null || values.Length <= 0)
{
return;
}
for (int i = 0; i < values.Length; i++)
{
IncludeValue(Mathf.Max(0f, values[i]), ref min, ref max, ref hasValue);
}
}
private static void IncludeValues(float[] values, ref float min, ref float max, ref bool hasValue)
{
if (values == null || values.Length <= 0)
{
return;
}
for (int i = 0; i < values.Length; i++)
{
IncludeValue(Mathf.Max(0f, values[i]), ref min, ref max, ref hasValue);
}
}
private static void IncludeValue(float value, ref float min, ref float max, ref bool hasValue)
{
if (float.IsNaN(value) || float.IsInfinity(value))
{
return;
}
if (!hasValue)
{
min = value;
max = value;
hasValue = true;
return;
}
if (value < min)
{
min = value;
}
if (value > max)
{
max = value;
}
}
private static float Normalize(float value, StatRange range)
{
float denominator = Mathf.Max(TinyValue, range.Max - range.Min);
return Mathf.Clamp01((value - range.Min) / denominator);
}
private static StatRange BuildRange(float min, float max, StatRange fallback)
{
if (float.IsNaN(min) || float.IsInfinity(min) || float.IsNaN(max) || float.IsInfinity(max))
{
return fallback;
}
if (max - min < TinyValue)
{
return fallback;
}
return new StatRange(min, max);
}
private static float LerpHue(float fromDegree, float toDegree, float t)
{
float delta = Mathf.DeltaAngle(fromDegree, toDegree);
return fromDegree + delta * Mathf.Clamp01(t);
}
private static float NormalizeHue(float hueDegree)
{
float normalized = hueDegree % 360f;
if (normalized < 0f)
{
normalized += 360f;
}
return normalized;
}
private readonly struct StatRange
{
public StatRange(float min, float max)
{
Min = min;
Max = max;
}
public float Min { get; }
public float Max { get; }
}
}
}
@@ -1,4 +1,3 @@
using System;
using GeometryTD.Definition;
using UnityEngine;
using UnityEngine.UI;
@@ -14,15 +13,17 @@ namespace GeometryTD.UI
private IconAreaContext _context;
public Sprite CurrentIconSprite => _icon != null ? _icon.sprite : null;
public virtual Sprite CurrentIconSprite => _icon != null ? _icon.sprite : null;
public Color CurrentIconColor => _icon != null ? _icon.color : Color.white;
public virtual Color CurrentIconColor => _icon != null ? _icon.color : Color.white;
public Vector2 CurrentIconSize => _icon != null ? _icon.rectTransform.rect.size : Vector2.zero;
public virtual Vector2 CurrentIconSize => _icon != null ? _icon.rectTransform.rect.size : Vector2.zero;
public Material CurrentIconMaterial => _icon != null ? _icon.material : null;
public virtual Material CurrentIconMaterial => _icon != null ? _icon.material : null;
public void OnInit(IconAreaContext context)
public virtual RectTransform IconRectTransform => _icon != null ? _icon.rectTransform : null;
public virtual void OnInit(IconAreaContext context)
{
if (context == null)
{
@@ -46,9 +47,10 @@ namespace GeometryTD.UI
SetRarity(_context.Rarity);
SetIconColor(_context.Color);
SetIconVisible(true);
}
public void SetIcon(Sprite sprite)
public virtual void SetIcon(Sprite sprite)
{
if (_icon != null)
{
@@ -56,7 +58,7 @@ namespace GeometryTD.UI
}
}
public void SetRarity(RarityType rarity)
public virtual void SetRarity(RarityType rarity)
{
if (_board == null)
{
@@ -74,7 +76,7 @@ namespace GeometryTD.UI
};
}
public void SetIconColor(Color color)
public virtual void SetIconColor(Color color)
{
if (_icon == null)
{
@@ -84,11 +86,20 @@ namespace GeometryTD.UI
_icon.color = color;
}
public void OnReset()
public virtual void SetIconVisible(bool visible)
{
if (_icon != null)
{
_icon.enabled = visible;
}
}
public virtual void OnReset()
{
SetIcon(null);
SetRarity(RarityType.None);
SetIconColor(Color.clear);
SetIconVisible(true);
}
}
}
@@ -0,0 +1,72 @@
using UnityEngine;
using UnityEngine.UI;
namespace GeometryTD.UI
{
public class TowerIconArea : IconArea
{
[SerializeField] private Image _baseIcon;
[SerializeField] private Image _bearingIcon;
[SerializeField] private Image _muzzleIcon;
public Sprite BaseIconSprite => _baseIcon != null ? _baseIcon.sprite : null;
public Sprite BearingIconSprite => _bearingIcon != null ? _bearingIcon.sprite : null;
public Sprite MuzzleIconSprite => _muzzleIcon != null ? _muzzleIcon.sprite : null;
public Color BaseIconColor => _baseIcon != null ? _baseIcon.color : Color.white;
public Color BearingIconColor => _bearingIcon != null ? _bearingIcon.color : Color.white;
public Color MuzzleIconColor => _muzzleIcon != null ? _muzzleIcon.color : Color.white;
public Material BaseIconMaterial => _baseIcon != null ? _baseIcon.material : null;
public Material BearingIconMaterial => _bearingIcon != null ? _bearingIcon.material : null;
public Material MuzzleIconMaterial => _muzzleIcon != null ? _muzzleIcon.material : null;
public override void OnInit(IconAreaContext context)
{
if (!(context is TowerIconAreaContext towerContext))
{
base.OnInit(context);
return;
}
SetRarity(towerContext.Rarity);
SetIconVisible(false);
SetLayerColor(_baseIcon, towerContext.BaseColor);
SetLayerColor(_bearingIcon, towerContext.BearingColor);
SetLayerColor(_muzzleIcon, towerContext.MuzzleColor);
}
public void OnInit(TowerIconAreaContext context)
{
OnInit((IconAreaContext)context);
}
public override void OnReset()
{
base.OnReset();
SetLayerColor(_baseIcon, Color.clear);
SetLayerColor(_bearingIcon, Color.clear);
SetLayerColor(_muzzleIcon, Color.clear);
SetIconVisible(false);
}
private static void SetLayerColor(Image icon, Color color)
{
if (icon == null)
{
return;
}
icon.color = color;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 33a1436bff53c844e858f3bced1565ca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: