补全 UI 设计
- `ItemDescForm` 道具详细信息面板,目前还未添加 DescText 内容的赋值 - `RepoForm` 道具入槽逻辑调整,为了适应 `ItemDescForm` 的点击打开逻辑,点击道具自动匹配槽位改成拖动道具到相应槽位 - 添加了一系列图片素材
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
using System;
|
||||
using GeometryTD.Definition;
|
||||
using GeometryTD.UI;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
[Serializable]
|
||||
public class IconAreaContext : UIContext
|
||||
{
|
||||
public RarityType Rarity;
|
||||
public TowerCompSlotType ComponentSlotType;
|
||||
public Color Color = Color.white;
|
||||
public Sprite Icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class ItemDescFormContext : UIContext
|
||||
{
|
||||
public string Title;
|
||||
public string TypeText;
|
||||
public string Description;
|
||||
public int Price;
|
||||
public Vector3 TargetPos;
|
||||
public List<TagType> Tags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d01ce2ecd9339fb40acface0ef371ed4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Collections.Generic;
|
||||
using GameFramework.Event;
|
||||
using GeometryTD.Definition;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class ItemDescFormController : UIFormControllerCommonBase<
|
||||
ItemDescFormContext, ItemDescForm>
|
||||
{
|
||||
protected override UIFormType UIFormTypeId => UIFormType.ItemDescForm;
|
||||
|
||||
|
||||
protected override void RefreshUI(ItemDescForm form, ItemDescFormContext context)
|
||||
{
|
||||
form.RefreshUI(context);
|
||||
}
|
||||
|
||||
protected override void CloseLoadedFormDirect(ItemDescForm form)
|
||||
{
|
||||
GameEntry.UI.CloseUIForm(form);
|
||||
}
|
||||
|
||||
private static ItemDescFormContext BuildContext(ItemDescFormRawData rawData)
|
||||
{
|
||||
if (rawData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ItemDescFormContext
|
||||
{
|
||||
Title = rawData.Title,
|
||||
TypeText = rawData.TypeText,
|
||||
Description = rawData.Description,
|
||||
Price = rawData.Price,
|
||||
TargetPos = rawData.TargetPos,
|
||||
Tags = BuildTags(rawData.Tags)
|
||||
};
|
||||
}
|
||||
|
||||
private static List<TagType> BuildTags(TagType[] tags)
|
||||
{
|
||||
if (tags == null || tags.Length <= 0)
|
||||
{
|
||||
return new List<TagType>();
|
||||
}
|
||||
|
||||
return new List<TagType>(tags);
|
||||
}
|
||||
|
||||
public int? OpenUI(ItemDescFormRawData rawData)
|
||||
{
|
||||
ItemDescFormContext context = BuildContext(rawData);
|
||||
return OpenUIInternal(context);
|
||||
}
|
||||
|
||||
public override int? OpenUI(object userData = null)
|
||||
{
|
||||
if (userData is ItemDescFormContext context)
|
||||
{
|
||||
return OpenUIInternal(context);
|
||||
}
|
||||
|
||||
if (userData is ItemDescFormRawData rawData)
|
||||
{
|
||||
return OpenUI(rawData);
|
||||
}
|
||||
|
||||
if (userData != null)
|
||||
{
|
||||
Log.Warning("ItemDescFormController.OpenUI() userData type is invalid.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return OpenUIInternal(Context);
|
||||
}
|
||||
|
||||
public override void BindUseCase(IUIUseCase useCase)
|
||||
{
|
||||
if (!(useCase is ItemDescFormUseCase))
|
||||
{
|
||||
Log.Error("ItemDescForm.BindUseCase() useCase is invalid.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2970dfbfc4901234ca38bda4d94d01d5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -3,6 +3,7 @@ using GeometryTD.CustomEvent;
|
||||
using GeometryTD.DataTable;
|
||||
using GeometryTD.Definition;
|
||||
using GameFramework.Event;
|
||||
using GeometryTD.CustomUtility;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
@@ -132,8 +133,10 @@ namespace GeometryTD.UI
|
||||
RewardId = rawItem.RewardId,
|
||||
IconArea = new IconAreaContext
|
||||
{
|
||||
ComponentSlotType = rawItem.SlotType,
|
||||
Icon = rawItem.Icon,
|
||||
Rarity = rawItem.Rarity
|
||||
Rarity = rawItem.Rarity,
|
||||
Color = ResolveIconColor(rawItem)
|
||||
},
|
||||
Title = rawItem.Title ?? string.Empty,
|
||||
TypeText = ResolveTypeText(rawItem),
|
||||
@@ -147,6 +150,16 @@ namespace GeometryTD.UI
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private static Color ResolveIconColor(RewardSelectItemRawData rawItem)
|
||||
{
|
||||
if (rawItem?.SourceItem == null)
|
||||
{
|
||||
return Color.white;
|
||||
}
|
||||
|
||||
return IconColorGenerator.GenerateForComponent(rawItem.SourceItem);
|
||||
}
|
||||
|
||||
private static string ResolveTypeText(RewardSelectItemRawData rawItem)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(rawItem.TypeText))
|
||||
|
||||
@@ -0,0 +1,440 @@
|
||||
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(DefenseTowerItemData tower)
|
||||
{
|
||||
if (tower?.Stats == null)
|
||||
{
|
||||
return Color.white;
|
||||
}
|
||||
|
||||
DefenseTowerStatsData stats = tower.Stats;
|
||||
return GenerateColor(
|
||||
ResolveArrayValue(stats.AttackDamage),
|
||||
ResolveArrayValue(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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6fb39901e8409cd4db9dbde01bee1ced
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,15 @@
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class ItemDescFormRawData
|
||||
{
|
||||
public string Title;
|
||||
public string TypeText;
|
||||
public string Description;
|
||||
public int Price;
|
||||
public Vector3 TargetPos;
|
||||
public TagType[] Tags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f84926be492fb4242846ee76ca4890f4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class ItemDescFormUseCase : IUIUseCase
|
||||
{
|
||||
public ItemDescFormUseCase()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fbb608569de315b42b7812af024c10a3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
@@ -13,6 +14,14 @@ namespace GeometryTD.UI
|
||||
|
||||
private IconAreaContext _context;
|
||||
|
||||
public Sprite CurrentIconSprite => _icon != null ? _icon.sprite : null;
|
||||
|
||||
public Color CurrentIconColor => _icon != null ? _icon.color : Color.white;
|
||||
|
||||
public Vector2 CurrentIconSize => _icon != null ? _icon.rectTransform.rect.size : Vector2.zero;
|
||||
|
||||
public Material CurrentIconMaterial => _icon != null ? _icon.material : null;
|
||||
|
||||
public void OnInit(IconAreaContext context)
|
||||
{
|
||||
if (context == null)
|
||||
@@ -22,17 +31,38 @@ namespace GeometryTD.UI
|
||||
}
|
||||
|
||||
_context = context;
|
||||
SetIcon(_context.Icon);
|
||||
|
||||
switch (context.ComponentSlotType)
|
||||
{
|
||||
case TowerCompSlotType.Base:
|
||||
case TowerCompSlotType.Bearing:
|
||||
case TowerCompSlotType.Muzzle:
|
||||
GameEntry.SpriteCache.GetSprite(context.ComponentSlotType.ToString(), SetIcon);
|
||||
break;
|
||||
default:
|
||||
SetIcon(_context.Icon);
|
||||
break;
|
||||
}
|
||||
|
||||
SetRarity(_context.Rarity);
|
||||
SetIconColor(_context.Color);
|
||||
}
|
||||
|
||||
public void SetIcon(Sprite sprite)
|
||||
{
|
||||
_icon.sprite = sprite;
|
||||
if (_icon != null)
|
||||
{
|
||||
_icon.sprite = sprite;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRarity(RarityType rarity)
|
||||
{
|
||||
if (_board == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_board.color = rarity switch
|
||||
{
|
||||
RarityType.White => Color.white,
|
||||
@@ -44,10 +74,21 @@ namespace GeometryTD.UI
|
||||
};
|
||||
}
|
||||
|
||||
public void SetIconColor(Color color)
|
||||
{
|
||||
if (_icon == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_icon.color = color;
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
SetIcon(null);
|
||||
SetRarity(RarityType.None);
|
||||
SetIconColor(Color.clear);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,376 @@
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.DataTable;
|
||||
using GeometryTD.Definition;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class ItemDescForm : UGuiForm
|
||||
{
|
||||
[SerializeField] private RectTransform _content;
|
||||
|
||||
[SerializeField] private TMP_Text _itemTitle;
|
||||
|
||||
[SerializeField] private TMP_Text _itemTypeText;
|
||||
|
||||
[SerializeField] private TMP_Text _itemDescription;
|
||||
|
||||
[SerializeField] private TMP_Text _itemPrice;
|
||||
|
||||
[SerializeField] private Transform _tagAreaParent;
|
||||
|
||||
[SerializeField] private GameObject _tagItemPrefab;
|
||||
|
||||
[SerializeField] private bool _autoResizeHeight = true;
|
||||
|
||||
[SerializeField] private float _fixedTopHeight = 150f;
|
||||
|
||||
[SerializeField] private float _fixedBottomHeight = 150f;
|
||||
|
||||
[SerializeField] private float _fixedWidth = 300f;
|
||||
|
||||
[SerializeField] private float _screenEdgePadding = 0f;
|
||||
|
||||
[SerializeField] private float _sideGap = 16f;
|
||||
|
||||
[SerializeField] private float _anchorItemWidth = 0f;
|
||||
|
||||
[SerializeField] private Graphic _blankAreaGraphic;
|
||||
|
||||
[SerializeField] private bool _disableBlankAreaRaycast = true;
|
||||
|
||||
[SerializeField] private bool _closeOnPointerDownOutsideContent = true;
|
||||
|
||||
private readonly List<GameObject> _runtimeTagItems = new List<GameObject>();
|
||||
|
||||
private ItemDescFormContext _context;
|
||||
private Vector3 _targetPos;
|
||||
private bool _isOpened;
|
||||
|
||||
public void RefreshUI(ItemDescFormContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
Log.Warning("ItemDescForm context is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
_context = context;
|
||||
|
||||
_targetPos = ConvertWorldToAnchored(_context.TargetPos);
|
||||
|
||||
if (_content != null)
|
||||
{
|
||||
_content.anchoredPosition3D = _targetPos;
|
||||
}
|
||||
|
||||
if (_itemTitle != null) _itemTitle.text = _context.Title ?? string.Empty;
|
||||
if (_itemTypeText != null) _itemTypeText.text = _context.TypeText ?? string.Empty;
|
||||
if (_itemDescription != null) _itemDescription.text = _context.Description ?? string.Empty;
|
||||
if (_itemPrice != null) _itemPrice.text = $"Price: {_context.Price} Gold";
|
||||
|
||||
RefreshTags(_context.Tags);
|
||||
|
||||
ResizeToFitContent();
|
||||
ApplySideAnchoredTargetPos();
|
||||
}
|
||||
|
||||
protected override void OnOpen(object userData)
|
||||
{
|
||||
base.OnOpen(userData);
|
||||
_isOpened = true;
|
||||
|
||||
if (_disableBlankAreaRaycast && _blankAreaGraphic != null)
|
||||
{
|
||||
_blankAreaGraphic.raycastTarget = false;
|
||||
}
|
||||
|
||||
if (!(userData is ItemDescFormContext context))
|
||||
{
|
||||
Log.Error("ItemDescFormContext is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshUI(context);
|
||||
}
|
||||
|
||||
protected override void OnClose(bool isShutdown, object userData)
|
||||
{
|
||||
_isOpened = false;
|
||||
ClearTags();
|
||||
_context = null;
|
||||
base.OnClose(isShutdown, userData);
|
||||
}
|
||||
|
||||
public void OnBlankAreaClick()
|
||||
{
|
||||
CloseSelf();
|
||||
}
|
||||
|
||||
public void OnBackgroundClick()
|
||||
{
|
||||
CloseSelf();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_isOpened || !_closeOnPointerDownOutsideContent || _content == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetPointerDownScreenPosition(out Vector2 screenPoint))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsPointerInContent(screenPoint))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CloseSelf();
|
||||
}
|
||||
|
||||
private void ResizeToFitContent()
|
||||
{
|
||||
if (!_autoResizeHeight || _content == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_itemDescription == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_itemDescription.ForceMeshUpdate();
|
||||
var descriptionSize = _itemDescription.rectTransform.sizeDelta;
|
||||
descriptionSize.y = _itemDescription.preferredHeight;
|
||||
_itemDescription.rectTransform.sizeDelta = descriptionSize;
|
||||
float descriptionHeight = Mathf.Max(0f, _itemDescription.preferredHeight);
|
||||
float targetHeight = _fixedTopHeight + descriptionHeight + _fixedBottomHeight;
|
||||
|
||||
Vector2 size = new Vector2(_fixedWidth, targetHeight);
|
||||
_content.sizeDelta = size;
|
||||
}
|
||||
|
||||
private void ApplySideAnchoredTargetPos()
|
||||
{
|
||||
if (_content == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RectTransform parent = _content.parent as RectTransform;
|
||||
if (parent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 size = _content.sizeDelta;
|
||||
if (size.x <= 0f || size.y <= 0f)
|
||||
{
|
||||
size = _content.rect.size;
|
||||
}
|
||||
|
||||
Vector2 pivot = _content.pivot;
|
||||
float halfWidth = parent.rect.width * 0.5f;
|
||||
float halfHeight = parent.rect.height * 0.5f;
|
||||
|
||||
float minX = -halfWidth + size.x * pivot.x + _screenEdgePadding;
|
||||
float maxX = halfWidth - size.x * (1f - pivot.x) - _screenEdgePadding;
|
||||
float minY = -halfHeight + size.y * pivot.y + _screenEdgePadding;
|
||||
float maxY = halfHeight - size.y * (1f - pivot.y) - _screenEdgePadding;
|
||||
|
||||
float clampedY = minY <= maxY ? Mathf.Clamp(_targetPos.y, minY, maxY) : 0f;
|
||||
|
||||
float anchorHalfWidth = Mathf.Max(0f, _anchorItemWidth * 0.5f);
|
||||
float sideGap = Mathf.Max(0f, _sideGap);
|
||||
float horizontalOffset = anchorHalfWidth + size.x * 0.5f + sideGap;
|
||||
|
||||
float leftCandidateX = _targetPos.x - horizontalOffset;
|
||||
float rightCandidateX = _targetPos.x + horizontalOffset;
|
||||
|
||||
float finalX;
|
||||
if (IsInsideBounds(leftCandidateX, minX, maxX))
|
||||
{
|
||||
finalX = leftCandidateX;
|
||||
}
|
||||
else if (IsInsideBounds(rightCandidateX, minX, maxX))
|
||||
{
|
||||
finalX = rightCandidateX;
|
||||
}
|
||||
else
|
||||
{
|
||||
finalX = minX <= maxX ? Mathf.Clamp(leftCandidateX, minX, maxX) : 0f;
|
||||
}
|
||||
|
||||
_content.anchoredPosition3D = new Vector3(finalX, clampedY, _targetPos.z);
|
||||
}
|
||||
|
||||
private Vector3 ConvertWorldToAnchored(Vector3 worldPos)
|
||||
{
|
||||
if (_content == null)
|
||||
{
|
||||
return worldPos;
|
||||
}
|
||||
|
||||
RectTransform parent = _content.parent as RectTransform;
|
||||
if (parent == null)
|
||||
{
|
||||
return worldPos;
|
||||
}
|
||||
|
||||
Canvas canvas = _content.GetComponentInParent<Canvas>();
|
||||
Canvas rootCanvas = canvas != null ? canvas.rootCanvas : null;
|
||||
Camera uiCamera = null;
|
||||
if (rootCanvas != null && rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay)
|
||||
{
|
||||
uiCamera = rootCanvas.worldCamera != null ? rootCanvas.worldCamera : Camera.main;
|
||||
}
|
||||
|
||||
Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(uiCamera, worldPos);
|
||||
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(parent, screenPoint, uiCamera,
|
||||
out Vector2 localPoint))
|
||||
{
|
||||
return worldPos;
|
||||
}
|
||||
|
||||
float z = _content.anchoredPosition3D.z;
|
||||
return new Vector3(localPoint.x, localPoint.y, z);
|
||||
}
|
||||
|
||||
private static bool IsInsideBounds(float value, float min, float max)
|
||||
{
|
||||
return value >= min && value <= max;
|
||||
}
|
||||
|
||||
private void RefreshTags(List<TagType> tags)
|
||||
{
|
||||
ClearTags();
|
||||
|
||||
if (_tagAreaParent == null || _tagItemPrefab == null || tags == null || tags.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_tagItemPrefab.scene.IsValid() && _tagItemPrefab.transform.IsChildOf(_tagAreaParent))
|
||||
{
|
||||
_tagItemPrefab.SetActive(false);
|
||||
}
|
||||
|
||||
for (int i = 0; i < tags.Count; i++)
|
||||
{
|
||||
string tagName = ResolveTagName(tags[i]);
|
||||
if (string.IsNullOrWhiteSpace(tagName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GameObject tagGo = Instantiate(_tagItemPrefab, _tagAreaParent);
|
||||
tagGo.SetActive(true);
|
||||
|
||||
if (tagGo.TryGetComponent(out TagItem tagItem))
|
||||
{
|
||||
tagItem.OnInit(new TagItemContext
|
||||
{
|
||||
TagName = tagName
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
TMP_Text tagText = tagGo.GetComponentInChildren<TMP_Text>(true);
|
||||
if (tagText != null)
|
||||
{
|
||||
tagText.text = tagName;
|
||||
}
|
||||
}
|
||||
|
||||
_runtimeTagItems.Add(tagGo);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearTags()
|
||||
{
|
||||
for (int i = _runtimeTagItems.Count - 1; i >= 0; i--)
|
||||
{
|
||||
GameObject tagGo = _runtimeTagItems[i];
|
||||
if (tagGo != null)
|
||||
{
|
||||
Destroy(tagGo);
|
||||
}
|
||||
}
|
||||
|
||||
_runtimeTagItems.Clear();
|
||||
}
|
||||
|
||||
private static string ResolveTagName(TagType tagType)
|
||||
{
|
||||
if (tagType == TagType.None)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var tagTable = GameEntry.DataTable.GetDataTable<DRTag>();
|
||||
if (tagTable != null)
|
||||
{
|
||||
DRTag tagRow = tagTable.GetDataRow((int)tagType);
|
||||
if (tagRow != null && !string.IsNullOrWhiteSpace(tagRow.Name))
|
||||
{
|
||||
return tagRow.Name;
|
||||
}
|
||||
}
|
||||
|
||||
return tagType.ToString();
|
||||
}
|
||||
|
||||
private void CloseSelf()
|
||||
{
|
||||
GameEntry.UI.CloseUIForm(this);
|
||||
}
|
||||
|
||||
private bool IsPointerInContent(Vector2 screenPoint)
|
||||
{
|
||||
Canvas canvas = _content.GetComponentInParent<Canvas>();
|
||||
Canvas rootCanvas = canvas != null ? canvas.rootCanvas : null;
|
||||
Camera uiCamera = null;
|
||||
if (rootCanvas != null && rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay)
|
||||
{
|
||||
uiCamera = rootCanvas.worldCamera != null ? rootCanvas.worldCamera : Camera.main;
|
||||
}
|
||||
|
||||
return RectTransformUtility.RectangleContainsScreenPoint(_content, screenPoint, uiCamera);
|
||||
}
|
||||
|
||||
private static bool TryGetPointerDownScreenPosition(out Vector2 screenPoint)
|
||||
{
|
||||
if (Input.touchCount > 0)
|
||||
{
|
||||
for (int i = 0; i < Input.touchCount; i++)
|
||||
{
|
||||
Touch touch = Input.GetTouch(i);
|
||||
if (touch.phase != TouchPhase.Began)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
screenPoint = touch.position;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (Input.GetMouseButtonDown(0))
|
||||
{
|
||||
screenPoint = Input.mousePosition;
|
||||
return true;
|
||||
}
|
||||
|
||||
screenPoint = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c97cf85e5027b944ab0580d944ef660
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user