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

- 将 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
@@ -7,8 +7,11 @@ namespace Components
public class BasicBaseComp : MonoBehaviour
{
[SerializeField] [Min(0.01f)] private float _attackSpeed = 1f;
[SerializeField] private AttackPropertyType _attackPropertyType = AttackPropertyType.Physics;
[SerializeField] private SpriteRenderer _renderer;
private float _cooldownRemaining;
public float AttackSpeed => _attackSpeed;
@@ -29,6 +32,14 @@ namespace Components
_cooldownRemaining = 0f;
}
public void SetColor(Color color)
{
if (_renderer != null)
{
_renderer.color = color;
}
}
public void Tick(float deltaTime)
{
if (_cooldownRemaining <= 0f)
@@ -6,10 +6,15 @@ namespace Components
public class BasicBearingComp : MonoBehaviour
{
[SerializeField] [Min(1f)] private float _rotateSpeed = 180f;
[SerializeField] [Min(0.1f)] private float _attackRange = 5f;
[SerializeField] [Min(0.1f)] private float _aimToleranceAngle = 2f;
[SerializeField] private Transform _rotateRoot;
[SerializeField] private SpriteRenderer _renderer;
public float RotateSpeed => _rotateSpeed;
public float AttackRange => _attackRange;
public float AimToleranceAngle => _aimToleranceAngle;
@@ -26,6 +31,14 @@ namespace Components
_attackRange = 5f;
}
public void SetColor(Color color)
{
if (_renderer != null)
{
_renderer.color = color;
}
}
public bool IsTargetInRange(Transform target, Transform origin = null)
{
if (target == null)
@@ -9,11 +9,17 @@ namespace Components
public class ShooterMuzzleComp : MonoBehaviour
{
[SerializeField] [Min(1f)] private int _attackDamage = 100;
[SerializeField] private AttackMethodType _attackMethodType = AttackMethodType.NormalBullet;
[SerializeField] [Min(1)] private int _bulletTypeId = 501;
[SerializeField] private Transform _muzzlePoint;
[SerializeField] [Min(0.1f)] private float _bulletSpeed = 12f;
[SerializeField] private SpriteRenderer _renderer;
public int AttackDamage => _attackDamage;
public AttackMethodType AttackMethodType => _attackMethodType;
@@ -31,6 +37,14 @@ namespace Components
_bulletTypeId = 501;
}
public void SetColor(Color color)
{
if (_renderer != null)
{
_renderer.color = color;
}
}
public bool Attack(Transform target)
{
return Attack(target, AttackPropertyType.None);
@@ -10,7 +10,8 @@ namespace Components
private const string AttackRangeIndicatorObjectName = "AttackRangeIndicator";
private const int MinTowerLevel = 0;
private const int MaxTowerLevel = 4;
private static Material s_AttackRangeSharedMaterial;
private static Material _attackRangeSharedMaterial;
[SerializeField] private ShooterMuzzleComp _muzzleComp;
[SerializeField] private BasicBearingComp _bearingComp;
@@ -27,6 +28,9 @@ namespace Components
private float _retargetTimer;
private float _attackRange;
private int _towerLevel;
private Color _muzzleColor = Color.white;
private Color _bearingColor = Color.white;
private Color _baseColor = Color.white;
public Transform CurrentTarget => _currentTarget;
@@ -40,6 +44,12 @@ namespace Components
OnInit(stats, MinTowerLevel);
}
public void OnInit(TowerStatsData stats, int towerLevel, Color muzzleColor, Color bearingColor, Color baseColor)
{
SetComponentColors(muzzleColor, bearingColor, baseColor);
OnInit(stats, towerLevel);
}
public void OnInit(TowerStatsData stats, int towerLevel)
{
ResolveComponents();
@@ -57,6 +67,7 @@ namespace Components
_muzzleComp?.OnInit(attackDamage, stats.AttackMethodType);
_bearingComp?.OnInit(rotateSpeed, attackRange);
_baseComp?.OnInit(attackSpeed, stats.AttackPropertyType);
ApplyComponentColors();
SetAttackRange(attackRange);
SetAttackRangeVisible(false);
_currentTarget = null;
@@ -90,6 +101,14 @@ namespace Components
RebuildAttackRangeGeometry();
}
public void SetComponentColors(Color muzzleColor, Color bearingColor, Color baseColor)
{
_muzzleColor = muzzleColor;
_bearingColor = bearingColor;
_baseColor = baseColor;
ApplyComponentColors();
}
public void SetTarget(Transform target)
{
_currentTarget = target;
@@ -141,9 +160,9 @@ namespace Components
EnemyEntity bestEnemy = null;
float bestDistanceSquared = float.MaxValue;
for (int i = 0; i < EnemyEntity.ActiveEnemies.Count; i++)
foreach (var enemy in EnemyEntity.ActiveEnemies)
{
EnemyEntity enemy = EnemyEntity.ActiveEnemies[i];
if (enemy == null || !enemy.isActiveAndEnabled)
{
continue;
@@ -164,6 +183,13 @@ namespace Components
return bestEnemy != null ? bestEnemy.transform : null;
}
private void ApplyComponentColors()
{
_muzzleComp?.SetColor(_muzzleColor);
_bearingComp?.SetColor(_bearingColor);
_baseComp?.SetColor(_baseColor);
}
private void ResolveComponents()
{
if (_muzzleComp == null)
@@ -181,6 +207,7 @@ namespace Components
_baseComp = GetComponent<BasicBaseComp>();
}
ApplyComponentColors();
EnsureAttackRangeRenderer();
}
@@ -250,18 +277,18 @@ namespace Components
_attackRangeRenderer.allowOcclusionWhenDynamic = false;
_attackRangeRenderer.enabled = false;
if (s_AttackRangeSharedMaterial == null)
if (_attackRangeSharedMaterial == null)
{
Shader lineShader = Shader.Find("Sprites/Default");
if (lineShader != null)
{
s_AttackRangeSharedMaterial = new Material(lineShader);
_attackRangeSharedMaterial = new Material(lineShader);
}
}
if (s_AttackRangeSharedMaterial != null)
if (_attackRangeSharedMaterial != null)
{
_attackRangeRenderer.sharedMaterial = s_AttackRangeSharedMaterial;
_attackRangeRenderer.sharedMaterial = _attackRangeSharedMaterial;
}
}
@@ -23,6 +23,7 @@ namespace GeometryTD.CustomComponent
private readonly Dictionary<int, List<DRLevelSpawnEntry>> _spawnEntriesByPhaseId = new();
private readonly Dictionary<int, IReadOnlyList<DRLevelSpawnEntry>> _selectedSpawnEntriesByPhaseId = new();
private readonly List<int> _levelIdBuffer = new();
private readonly List<TowerStatsData> _currentBuildTowerStats = new();
private readonly CombatScheduler _combatScheduler = new CombatScheduler();
private bool _runtimeInitialized;
@@ -39,6 +40,7 @@ namespace GeometryTD.CustomComponent
public int CurrentBaseHp => _currentBaseHp;
public bool LastCombatSucceeded => _lastCombatSucceeded;
public bool CanEndCombat => _combatScheduler.CanEndCombat;
public int CurrentBuildTowerCount => _currentBuildTowerStats.Count;
public int LastDefeatedEnemyCount { get; private set; }
public int LastGainedCoin { get; private set; }
public int LastGainedGold { get; private set; }
@@ -88,6 +90,7 @@ namespace GeometryTD.CustomComponent
_spawnEntriesByPhaseId.Clear();
_selectedSpawnEntriesByPhaseId.Clear();
_levelIdBuffer.Clear();
_currentBuildTowerStats.Clear();
IDataTable<DRLevel> dtLevel = GameEntry.DataTable.GetDataTable<DRLevel>();
IDataTable<DRLevelPhase> dtLevelPhase = GameEntry.DataTable.GetDataTable<DRLevelPhase>();
@@ -181,6 +184,7 @@ namespace GeometryTD.CustomComponent
public void StartCombat()
{
_currentBuildTowerStats.Clear();
if (!EnsureCombatRuntimeInitialized())
{
Log.Warning("CombatNodeComponent start failed. Missing scheduler runtime.");
@@ -216,6 +220,7 @@ namespace GeometryTD.CustomComponent
}
CurrentLevel = selectedLevel;
CacheBuildTowerStatsSnapshot();
_isCombatActive = false;
_currentCoin = Mathf.Max(0, selectedLevel.StartCoin);
_currentGold = 0;
@@ -230,6 +235,7 @@ namespace GeometryTD.CustomComponent
_currentCoin = 0;
_currentGold = 0;
_currentBaseHp = 0;
_currentBuildTowerStats.Clear();
return;
}
@@ -252,6 +258,7 @@ namespace GeometryTD.CustomComponent
_currentGold = 0;
_currentBaseHp = 0;
CurrentLevel = null;
_currentBuildTowerStats.Clear();
GameEntry.Event.Fire(this, NodeCompleteEventArgs.Create());
}
@@ -281,6 +288,7 @@ namespace GeometryTD.CustomComponent
LastDefeatedEnemyCount = 0;
LastGainedCoin = 0;
LastGainedGold = 0;
_currentBuildTowerStats.Clear();
ShutdownCombatRuntime();
}
@@ -341,6 +349,18 @@ namespace GeometryTD.CustomComponent
return true;
}
public bool TryGetBuildTowerStats(int buildIndex, out TowerStatsData stats)
{
stats = null;
if (buildIndex < 0 || buildIndex >= _currentBuildTowerStats.Count)
{
return false;
}
stats = CloneTowerStats(_currentBuildTowerStats[buildIndex]);
return stats != null;
}
public void AddCoin(int coin)
{
int gainCoin = Mathf.Max(0, coin);
@@ -353,6 +373,54 @@ namespace GeometryTD.CustomComponent
FireCoinChangedEventIfNeeded();
}
private void CacheBuildTowerStatsSnapshot()
{
_currentBuildTowerStats.Clear();
if (GameEntry.PlayerInventory == null)
{
return;
}
IReadOnlyList<TowerItemData> towers = GameEntry.PlayerInventory.GetParticipantTowerSnapshot();
if (towers == null || towers.Count <= 0)
{
return;
}
for (int i = 0; i < towers.Count; i++)
{
TowerItemData tower = towers[i];
TowerStatsData clonedStats = CloneTowerStats(tower?.Stats);
if (clonedStats == null)
{
continue;
}
_currentBuildTowerStats.Add(clonedStats);
}
}
private static TowerStatsData CloneTowerStats(TowerStatsData source)
{
if (source == null)
{
return null;
}
return new TowerStatsData
{
AttackDamage = source.AttackDamage != null ? (int[])source.AttackDamage.Clone() : null,
DamageRandomRate = source.DamageRandomRate,
RotateSpeed = source.RotateSpeed != null ? (float[])source.RotateSpeed.Clone() : null,
AttackRange = source.AttackRange != null ? (float[])source.AttackRange.Clone() : null,
AttackSpeed = source.AttackSpeed != null ? (float[])source.AttackSpeed.Clone() : null,
AttackMethodType = source.AttackMethodType,
AttackPropertyType = source.AttackPropertyType,
Tags = source.Tags != null ? (TagType[])source.Tags.Clone() : null
};
}
private void OnDestroy()
{
OnShutdown();
@@ -437,3 +505,5 @@ namespace GeometryTD.CustomComponent
}
}
}
@@ -1,4 +1,5 @@
using GeometryTD.CustomUtility;
using System.Collections.Generic;
using GeometryTD.CustomUtility;
using GeometryTD.Definition;
using UnityGameFramework.Runtime;
@@ -44,6 +45,31 @@ namespace GeometryTD.CustomComponent
return _queryModel.GetSnapshot();
}
public IReadOnlyList<TowerItemData> GetParticipantTowerSnapshot()
{
EnsureInitialized();
BackpackInventoryData inventory = _queryModel.Inventory;
if (inventory?.ParticipantTowerInstanceIds == null || inventory.ParticipantTowerInstanceIds.Count <= 0)
{
return new List<TowerItemData>();
}
List<TowerItemData> result = new List<TowerItemData>(inventory.ParticipantTowerInstanceIds.Count);
for (int i = 0; i < inventory.ParticipantTowerInstanceIds.Count && result.Count < MaxParticipantTowerCount; i++)
{
long towerId = inventory.ParticipantTowerInstanceIds[i];
if (!_queryModel.TryGetTowerById(towerId, out TowerItemData tower) || tower == null)
{
continue;
}
result.Add(InventoryCloneUtility.CloneTower(tower));
}
return result;
}
public void MergeInventory(BackpackInventoryData gainedInventory)
{
EnsureInitialized();
@@ -126,3 +152,4 @@ namespace GeometryTD.CustomComponent
}
}
}
@@ -34,6 +34,9 @@ namespace GeometryTD.Definition
/// </summary>
public List<TowerItemData> Towers { get; set; } = new List<TowerItemData>();
/// <summary>
/// 参与战斗的防御塔 Id。
/// </summary>
public List<long> ParticipantTowerInstanceIds { get; set; } = new List<long>();
}
}
@@ -1,4 +1,6 @@
using System;
using Newtonsoft.Json;
using UnityEngine;
namespace GeometryTD.Definition
{
@@ -41,7 +43,7 @@ namespace GeometryTD.Definition
public RarityType Rarity { get; set; }
public bool IsParticipatingInCombat { get; set; }
/// <summary>
/// 构成该防御塔的枪口组件实例 Id。
/// </summary>
@@ -61,5 +63,9 @@ namespace GeometryTD.Definition
/// 防御塔独立属性,不依赖组件对象引用。
/// </summary>
public TowerStatsData Stats { get; set; } = new TowerStatsData();
[JsonIgnore] public Sprite ComposedIconSprite { get; set; }
[JsonIgnore] public string ComposedIconKey { get; set; }
}
}
}
@@ -9,15 +9,22 @@ namespace GeometryTD.Entity.EntityData
{
[SerializeField] private TowerStatsData _stats = new TowerStatsData();
[SerializeField] private int _towerLevel = 0;
[SerializeField] private Color _muzzleColor = Color.white;
[SerializeField] private Color _bearingColor = Color.white;
[SerializeField] private Color _baseColor = Color.white;
public TowerData(int entityId, int typeId, Vector3 position, Quaternion rotation,
TowerStatsData stats, int towerLevel = 0)
TowerStatsData stats, int towerLevel = 0, Color? muzzleColor = null, Color? bearingColor = null,
Color? baseColor = null)
: base(entityId, typeId)
{
Position = position;
Rotation = rotation;
_stats = stats ?? new TowerStatsData();
_towerLevel = Mathf.Max(0, towerLevel);
_muzzleColor = muzzleColor ?? Color.white;
_bearingColor = bearingColor ?? Color.white;
_baseColor = baseColor ?? Color.white;
}
public TowerStatsData Stats
@@ -31,5 +38,23 @@ namespace GeometryTD.Entity.EntityData
get => _towerLevel;
set => _towerLevel = Mathf.Max(0, value);
}
public Color MuzzleColor
{
get => _muzzleColor;
set => _muzzleColor = value;
}
public Color BearingColor
{
get => _bearingColor;
set => _bearingColor = value;
}
public Color BaseColor
{
get => _baseColor;
set => _baseColor = value;
}
}
}
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using GeometryTD.CustomComponent;
using GeometryTD.CustomUtility;
using GeometryTD.Map;
using GeometryTD.Definition;
using GeometryTD.Entity.EntityData;
@@ -14,12 +16,17 @@ namespace GeometryTD.Entity
public class MapEntity : EntityBase
{
private const int DefaultTowerTypeId = 401;
private static readonly Spawner[] EmptySpawners = Array.Empty<Spawner>();
[SerializeField] private bool _enableCombatSelectInput = true;
[SerializeField] private int _towerTypeId = DefaultTowerTypeId;
[SerializeField] private int[] _buildTowerCosts = { 40, 60, 60, 80 };
[SerializeField] private int _upgradeCost = 50;
[SerializeField] private int _destroyGain = 40;
private MapDataRefs _mapDataRefs;
@@ -29,6 +36,8 @@ namespace GeometryTD.Entity
private CombatSelectInputService _combatSelectInputService;
private TowerPlacementService _towerPlacementService;
private TowerSelectionPresenter _towerSelectionPresenter;
private readonly BuildTowerVisualInfo[] _buildTowerVisualInfos = new BuildTowerVisualInfo[4];
public IReadOnlyList<Vector3Int> PathCells => _mapTopologyService != null
? _mapTopologyService.PathCells
@@ -209,22 +218,38 @@ namespace GeometryTD.Entity
}
_combatSelectFormUseCase.SetCoinProvider(GetCurrentCoin);
BackpackInventoryData inventorySnapshot = GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetInventorySnapshot()
: null;
IReadOnlyList<TowerItemData> participantTowers = GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetParticipantTowerSnapshot()
: null;
Dictionary<long, MuzzleCompItemData> muzzleMap = BuildComponentMap(inventorySnapshot?.MuzzleComponents);
Dictionary<long, BearingCompItemData> bearingMap = BuildComponentMap(inventorySnapshot?.BearingComponents);
Dictionary<long, BaseCompItemData> baseMap = BuildComponentMap(inventorySnapshot?.BaseComponents);
int currentBuildTowerCount = GetCurrentBuildTowerCount();
for (int i = 0; i < 4; i++)
{
int buildIndex = i;
int buildCost = GetBuildTowerCost(buildIndex);
bool isBuildAvailable = buildIndex < currentBuildTowerCount;
BuildTowerVisualInfo buildVisual = ResolveBuildTowerVisual(participantTowers, buildIndex, muzzleMap, bearingMap, baseMap);
_buildTowerVisualInfos[buildIndex] = buildVisual;
_combatSelectFormUseCase.SetBuildAction(
buildIndex,
() => TryBuildTower(buildIndex),
GetBuildTowerCost(buildIndex));
isBuildAvailable ? () => TryBuildTower(buildIndex) : (Func<bool>)null,
buildCost,
null,
isBuildAvailable,
buildVisual.BaseColor,
buildVisual.BearingColor,
buildVisual.MuzzleColor);
_combatSelectFormUseCase.SetBuildVisible(buildIndex, isBuildAvailable);
}
_combatSelectFormUseCase.SetUpgradeAction(
TryUpgradeTower,
Mathf.Max(0, _upgradeCost));
_combatSelectFormUseCase.SetDestroyAction(
TryDestroyTower,
Mathf.Max(0, _destroyGain));
_combatSelectFormUseCase.SetUpgradeAction(TryUpgradeTower, Mathf.Max(0, _upgradeCost));
_combatSelectFormUseCase.SetDestroyAction(TryDestroyTower, Mathf.Max(0, _destroyGain));
}
private void HandleCombatSelectInput()
@@ -275,8 +300,18 @@ namespace GeometryTD.Entity
return false;
}
CombatNodeComponent combatNode = GameEntry.CombatNode;
if (combatNode == null || !combatNode.TryGetBuildTowerStats(buildIndex, out TowerStatsData buildTowerStats))
{
return false;
}
BuildTowerVisualInfo buildVisual = buildIndex >= 0 && buildIndex < _buildTowerVisualInfos.Length
? _buildTowerVisualInfos[buildIndex]
: BuildTowerVisualInfo.Default;
if (!_towerPlacementService.TryBuildTower(selectedFoundationCell, IsFoundationCell, buildIndex, _buildTowerCosts,
_towerTypeId, Tilemap, TryConsumeCoin, AddCoin, out int towerEntityId))
buildTowerStats, buildVisual.MuzzleColor, buildVisual.BearingColor, buildVisual.BaseColor, _towerTypeId, Tilemap, TryConsumeCoin, AddCoin, out int towerEntityId))
{
return false;
}
@@ -350,6 +385,77 @@ namespace GeometryTD.Entity
return Mathf.Max(0, _buildTowerCosts[buildIndex]);
}
private static int GetCurrentBuildTowerCount()
{
if (GameEntry.CombatNode == null)
{
return 0;
}
return Mathf.Clamp(GameEntry.CombatNode.CurrentBuildTowerCount, 0, 4);
}
private static BuildTowerVisualInfo ResolveBuildTowerVisual(
IReadOnlyList<TowerItemData> participantTowers,
int buildIndex,
IReadOnlyDictionary<long, MuzzleCompItemData> muzzleMap,
IReadOnlyDictionary<long, BearingCompItemData> bearingMap,
IReadOnlyDictionary<long, BaseCompItemData> baseMap)
{
if (participantTowers == null || buildIndex < 0 || buildIndex >= participantTowers.Count)
{
return BuildTowerVisualInfo.Default;
}
TowerItemData tower = participantTowers[buildIndex];
if (tower == null)
{
return BuildTowerVisualInfo.Default;
}
Color muzzleColor = ResolveComponentColor(muzzleMap, tower.MuzzleComponentInstanceId);
Color bearingColor = ResolveComponentColor(bearingMap, tower.BearingComponentInstanceId);
Color baseColor = ResolveComponentColor(baseMap, tower.BaseComponentInstanceId);
return new BuildTowerVisualInfo(muzzleColor, bearingColor, baseColor);
}
private static Color ResolveComponentColor<TComp>(IReadOnlyDictionary<long, TComp> componentMap, long componentId)
where TComp : TowerCompItemData
{
if (componentMap == null || componentId <= 0)
{
return Color.white;
}
return componentMap.TryGetValue(componentId, out TComp component) && component != null
? IconColorGenerator.GenerateForComponent(component)
: Color.white;
}
private static Dictionary<long, TComp> BuildComponentMap<TComp>(IReadOnlyList<TComp> items)
where TComp : TowerCompItemData
{
Dictionary<long, TComp> map = new Dictionary<long, TComp>();
if (items == null)
{
return map;
}
for (int i = 0; i < items.Count; i++)
{
TComp item = items[i];
if (item == null || item.InstanceId <= 0)
{
continue;
}
map[item.InstanceId] = item;
}
return map;
}
private static bool TryConsumeCoin(int cost)
{
int requiredCoin = Mathf.Max(0, cost);
@@ -371,6 +477,22 @@ namespace GeometryTD.Entity
return GameEntry.CombatNode != null ? Mathf.Max(0, GameEntry.CombatNode.CurrentCoin) : 0;
}
private readonly struct BuildTowerVisualInfo
{
public static BuildTowerVisualInfo Default => new BuildTowerVisualInfo(Color.white, Color.white, Color.white);
public BuildTowerVisualInfo(Color muzzleColor, Color bearingColor, Color baseColor)
{
MuzzleColor = muzzleColor;
BearingColor = bearingColor;
BaseColor = baseColor;
}
public Color MuzzleColor { get; }
public Color BearingColor { get; }
public Color BaseColor { get; }
}
private static void AddCoin(int coin)
{
int amount = Mathf.Max(0, coin);
@@ -382,3 +504,4 @@ namespace GeometryTD.Entity
}
}
}
@@ -53,7 +53,12 @@ namespace GeometryTD.Entity
return;
}
_towerController.OnInit(towerData.Stats, towerData.TowerLevel);
_towerController.OnInit(
towerData.Stats,
towerData.TowerLevel,
towerData.MuzzleColor,
towerData.BearingColor,
towerData.BaseColor);
}
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
@@ -0,0 +1,28 @@
using GameFramework;
using GameFramework.ObjectPool;
using GeometryTD.UI;
using UnityEngine;
namespace GeometryTD.PoolObjectBase
{
public class TowerRepoItemObject : ObjectBase
{
public static TowerRepoItemObject Create(object target)
{
TowerRepoItemObject itemObject = ReferencePool.Acquire<TowerRepoItemObject>();
itemObject.Initialize(target);
return itemObject;
}
protected override void Release(bool isShutdown)
{
TowerRepoItem item = (TowerRepoItem)Target;
if (item == null)
{
return;
}
Object.Destroy(item.gameObject);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6f6f2603e451c5f4f9b0d0cb8ee247d6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -40,7 +40,7 @@ namespace GeometryTD.Map
}
public bool TryBuildTower(Vector3Int foundationCell, Func<Vector3Int, bool> isFoundationCell, int buildIndex,
int[] buildTowerCosts, int towerTypeId, Tilemap tilemap, Func<int, bool> tryConsumeCoin,
int[] buildTowerCosts, TowerStatsData buildTowerStats, Color muzzleColor, Color bearingColor, Color baseColor, int towerTypeId, Tilemap tilemap, Func<int, bool> tryConsumeCoin,
Action<int> addCoin,
out int towerEntityId)
{
@@ -55,14 +55,19 @@ namespace GeometryTD.Map
return false;
}
TowerStatsData towerStats = CloneTowerStats(buildTowerStats);
if (towerStats == null)
{
return false;
}
int buildCost = GetBuildTowerCost(buildTowerCosts, buildIndex);
if (tryConsumeCoin != null && !tryConsumeCoin(buildCost))
{
return false;
}
TowerStatsData towerStats = BuildTowerStats(buildIndex);
if (!TryShowTowerEntity(foundationCell, towerStats, towerTypeId, tilemap, out int newTowerEntityId))
if (!TryShowTowerEntity(foundationCell, towerStats, muzzleColor, bearingColor, baseColor, towerTypeId, tilemap, out int newTowerEntityId))
{
addCoin?.Invoke(buildCost);
return false;
@@ -177,7 +182,7 @@ namespace GeometryTD.Map
return Mathf.Max(0, buildTowerCosts[buildIndex]);
}
private static bool TryShowTowerEntity(Vector3Int foundationCell, TowerStatsData towerStats,
private static bool TryShowTowerEntity(Vector3Int foundationCell, TowerStatsData towerStats, Color muzzleColor, Color bearingColor, Color baseColor,
int towerTypeId,
Tilemap tilemap, out int towerEntityId)
{
@@ -192,7 +197,7 @@ namespace GeometryTD.Map
Vector3 towerPosition = tilemap != null ? tilemap.GetCellCenterWorld(foundationCell) : foundationCell;
towerPosition.z = 0f;
var towerData = new TowerData(entityId, typeId, towerPosition, Quaternion.identity, towerStats,
MinTowerLevel);
MinTowerLevel, muzzleColor, bearingColor, baseColor);
GameEntry.Entity.ShowDefenseTower(towerData);
towerEntityId = entityId;
@@ -346,4 +351,4 @@ namespace GeometryTD.Map
return values != null ? values.Length : 0;
}
}
}
}
@@ -10,5 +10,6 @@ namespace GeometryTD.UI
public bool IsInteractable;
public CombatSelectActionType ActionType;
public int ActionIndex;
public TowerIconAreaContext TowerIconAreaContext;
}
}
@@ -116,9 +116,8 @@ namespace GeometryTD.UI
if (inventory.Towers != null)
{
for (int i = 0; i < inventory.Towers.Count; i++)
foreach (TowerItemData tower in inventory.Towers)
{
var tower = inventory.Towers[i];
if (tower == null)
{
continue;
@@ -131,7 +130,7 @@ namespace GeometryTD.UI
EnduranceRate01 = ItemDescUtility.ResolveTowerEnduranceRate(tower, muzzleMap, bearingMap, baseMap),
ClickActionType = RepoItemClickActionType.OpenDetail,
ComponentSlotType = TowerCompSlotType.None,
IconAreaContext = BuildIconAreaContext(tower)
IconAreaContext = BuildTowerIconContext(tower, muzzleMap, bearingMap, baseMap)
});
AddItemDescSeed(
@@ -145,9 +144,8 @@ namespace GeometryTD.UI
if (inventory.MuzzleComponents != null)
{
for (int i = 0; i < inventory.MuzzleComponents.Count; i++)
foreach (MuzzleCompItemData item in inventory.MuzzleComponents)
{
var item = inventory.MuzzleComponents[i];
if (item == null)
{
continue;
@@ -174,7 +172,7 @@ namespace GeometryTD.UI
if (inventory.BearingComponents != null)
{
foreach (var item in inventory.BearingComponents)
foreach (BearingCompItemData item in inventory.BearingComponents)
{
if (item == null)
{
@@ -202,9 +200,8 @@ namespace GeometryTD.UI
if (inventory.BaseComponents != null)
{
for (int i = 0; i < inventory.BaseComponents.Count; i++)
foreach (BaseCompItemData item in inventory.BaseComponents)
{
var item = inventory.BaseComponents[i];
if (item == null)
{
continue;
@@ -232,6 +229,38 @@ namespace GeometryTD.UI
return itemContexts.ToArray();
}
private static TowerIconAreaContext BuildTowerIconContext(
TowerItemData tower,
IReadOnlyDictionary<long, MuzzleCompItemData> muzzleMap,
IReadOnlyDictionary<long, BearingCompItemData> bearingMap,
IReadOnlyDictionary<long, BaseCompItemData> baseMap)
{
if (tower == null)
{
return null;
}
return new TowerIconAreaContext
{
Rarity = tower.Rarity,
MuzzleColor = ResolveComponentColor(tower.MuzzleComponentInstanceId, muzzleMap),
BearingColor = ResolveComponentColor(tower.BearingComponentInstanceId, bearingMap),
BaseColor = ResolveComponentColor(tower.BaseComponentInstanceId, baseMap)
};
}
private static Color ResolveComponentColor<TComp>(long instanceId, IReadOnlyDictionary<long, TComp> componentMap)
where TComp : TowerCompItemData
{
if (instanceId > 0 && componentMap != null && componentMap.TryGetValue(instanceId, out TComp comp) &&
comp != null)
{
return IconColorGenerator.GenerateForComponent(comp);
}
return Color.white;
}
private void AddItemDescSeed(long itemId, string title, string typeText, string description, TagType[] tags)
{
if (itemId <= 0)
@@ -274,9 +303,8 @@ namespace GeometryTD.UI
return map;
}
for (int i = 0; i < items.Count; i++)
foreach (TComp item in items)
{
TComp item = items[i];
if (item == null || item.InstanceId <= 0)
{
continue;
@@ -288,35 +316,13 @@ namespace GeometryTD.UI
return map;
}
private static IconAreaContext BuildIconAreaContext(TowerItemData tower)
{
if (tower == null)
{
return new IconAreaContext
{
ComponentSlotType = TowerCompSlotType.None,
Rarity = RarityType.None,
Color = Color.white,
Icon = null
};
}
return new IconAreaContext
{
ComponentSlotType = TowerCompSlotType.None,
Rarity = tower.Rarity,
Color = IconColorGenerator.GenerateForTower(tower),
Icon = null
};
}
private static IconAreaContext BuildIconAreaContext(TowerCompItemData item)
{
if (item == null)
{
return new IconAreaContext
{
ComponentSlotType = TowerCompSlotType.None,
ComponentSlotType = TowerCompSlotType.None,
Rarity = RarityType.None,
Color = Color.white,
Icon = null
@@ -332,6 +338,8 @@ namespace GeometryTD.UI
};
}
#region Event Handlers
private void OnCombatFinishReturnButtonClicked(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender) || !(e is CombatFinishReturnEventArgs))
@@ -370,6 +378,8 @@ namespace GeometryTD.UI
});
}
#endregion
private bool IsEventFromCurrentForm(object sender)
{
if (Form == null)
@@ -392,3 +402,7 @@ namespace GeometryTD.UI
}
}
}
@@ -229,7 +229,13 @@ namespace GeometryTD.UI
IsVisible = rawData.IsVisible,
IsInteractable = rawData.IsInteractable,
ActionType = rawData.ActionType,
ActionIndex = rawData.ActionIndex
ActionIndex = rawData.ActionIndex,
TowerIconAreaContext = new TowerIconAreaContext()
{
BaseColor = rawData.BaseColor,
BearingColor = rawData.BearingColor,
MuzzleColor = rawData.MuzzleColor
}
};
}
@@ -284,4 +290,4 @@ namespace GeometryTD.UI
RefreshFromUseCase();
}
}
}
}
@@ -11,5 +11,9 @@ namespace GeometryTD.UI
public bool IsInteractable;
public CombatSelectActionType ActionType;
public int ActionIndex;
public bool UseTowerLayers;
public Color BaseColor = Color.white;
public Color BearingColor = Color.white;
public Color MuzzleColor = Color.white;
}
}
@@ -42,6 +42,19 @@ namespace GeometryTD.UI
}
public void SetBuildAction(int buildIndex, Func<bool> buildAction, int cost, Sprite icon = null)
{
SetBuildAction(buildIndex, buildAction, cost, icon, false, Color.white, Color.white, Color.white);
}
public void SetBuildAction(
int buildIndex,
Func<bool> buildAction,
int cost,
Sprite icon,
bool useTowerLayers,
Color baseColor,
Color bearingColor,
Color muzzleColor)
{
if (buildIndex < 0 || buildIndex >= _buildOptions.Length)
{
@@ -54,6 +67,10 @@ namespace GeometryTD.UI
option.Icon = icon;
option.IsGain = false;
option.IsVisible = true;
option.UseTowerLayers = useTowerLayers;
option.BaseColor = baseColor;
option.BearingColor = bearingColor;
option.MuzzleColor = muzzleColor;
}
public void SetBuildAction(int buildIndex, Action buildAction, int cost, Sprite icon = null)
@@ -75,6 +92,16 @@ namespace GeometryTD.UI
icon);
}
public void SetBuildVisible(int buildIndex, bool visible)
{
if (buildIndex < 0 || buildIndex >= _buildOptions.Length)
{
return;
}
_buildOptions[buildIndex].IsVisible = visible;
}
public void SetUpgradeAction(Func<bool> upgradeAction, int cost, Sprite icon = null)
{
_upgradeOption.Action = upgradeAction;
@@ -82,6 +109,7 @@ namespace GeometryTD.UI
_upgradeOption.Icon = icon;
_upgradeOption.IsGain = false;
_upgradeOption.IsVisible = true;
_upgradeOption.UseTowerLayers = false;
}
public void SetUpgradeAction(Action upgradeAction, int cost, Sprite icon = null)
@@ -109,6 +137,7 @@ namespace GeometryTD.UI
_destroyOption.Icon = icon;
_destroyOption.IsGain = true;
_destroyOption.IsVisible = true;
_destroyOption.UseTowerLayers = false;
}
public void SetDestroyAction(Action destroyAction, int gain, Sprite icon = null)
@@ -278,7 +307,11 @@ namespace GeometryTD.UI
IsVisible = option.IsVisible,
IsInteractable = option.Action != null && CanExecute(option, currentCoin),
ActionType = option.ActionType,
ActionIndex = option.ActionIndex
ActionIndex = option.ActionIndex,
UseTowerLayers = option.UseTowerLayers,
BaseColor = option.BaseColor,
BearingColor = option.BearingColor,
MuzzleColor = option.MuzzleColor
};
}
@@ -293,6 +326,10 @@ namespace GeometryTD.UI
public bool IsVisible = true;
public Sprite Icon;
public Func<bool> Action;
public bool UseTowerLayers;
public Color BaseColor = Color.white;
public Color BearingColor = Color.white;
public Color MuzzleColor = Color.white;
public Option(CombatSelectActionType actionType, int actionIndex, bool requiresAffordable)
{
@@ -2,20 +2,23 @@ using TMPro;
using UnityEngine;
using UnityEngine.UI;
using GeometryTD.CustomEvent;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class TowerSelectItem : MonoBehaviour
{
[SerializeField] private Image _icon;
[SerializeField] private TMP_Text _price;
[SerializeField] private CommonButton _button;
[SerializeField] private TowerIconArea _towerIconArea;
private TowerSelectItemContext _context;
private Sprite _defaultIcon;
private bool _hasCachedDefaultIcon;
public void OnInit(TowerSelectItemContext context)
@@ -23,28 +26,45 @@ namespace GeometryTD.UI
CacheDefaultIcon();
_context = context;
if (_icon != null)
if (context.ActionType == CombatSelectActionType.BuildTower)
{
if (context != null && context.Icon != null)
_icon?.gameObject.SetActive(false);
_towerIconArea?.OnInit(_context.TowerIconAreaContext);
if (_price != null)
{
_icon.sprite = context.Icon;
}
else if (_defaultIcon != null)
{
_icon.sprite = _defaultIcon;
_price.text = context.PriceText ?? string.Empty;
}
_icon.enabled = _icon.sprite != null;
if (_button != null)
{
_button.Interactive = context.IsInteractable;
}
}
if (_price != null)
else if (context.ActionType is CombatSelectActionType.UpgradeTower or CombatSelectActionType.DestroyTower)
{
_price.text = context?.PriceText ?? string.Empty;
if (_icon != null)
{
_icon.sprite = _context.Icon ?? _defaultIcon;
_icon.gameObject.SetActive(true);
}
_towerIconArea?.OnReset();
if (_price != null)
{
_price.text = context.PriceText ?? string.Empty;
}
if (_button != null)
{
_button.Interactive = context.IsInteractable;
}
}
if (_button != null)
else
{
_button.Interactive = context != null && context.IsInteractable;
_icon?.gameObject.SetActive(false);
_towerIconArea?.OnReset();
}
}
@@ -59,6 +79,8 @@ namespace GeometryTD.UI
_icon.enabled = _icon.sprite != null;
}
_towerIconArea.OnReset();
if (_price != null)
{
_price.text = string.Empty;
@@ -93,4 +115,4 @@ namespace GeometryTD.UI
_hasCachedDefaultIcon = true;
}
}
}
}
@@ -2,6 +2,7 @@ namespace GeometryTD.UI
{
public class CompAreaContext
{
public RepoItemContext[] Items;
public RepoItemContext[] ComponentItems;
public TowerRepoItemContext[] TowerItems;
}
}
@@ -2,7 +2,7 @@ namespace GeometryTD.UI
{
public class ParticipantAreaContext : UIContext
{
public RepoItemContext[] Items;
public TowerRepoItemContext[] TowerItems;
public int MaxCount;
}
}
@@ -0,0 +1,9 @@
using System;
namespace GeometryTD.UI
{
[Serializable]
public class TowerRepoItemContext : RepoItemContext
{
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 813496ec671788a4b975fa980ce16555
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -10,8 +10,9 @@ namespace GeometryTD.UI
{
private RepoFormContext BuildContext(RepoFormRawData rawData)
{
_itemContextMap.Clear();
_itemClickActionMap.Clear();
_itemDescSeedMap.Clear();
_compAreaTowerIds.Clear();
if (rawData?.Inventory == null)
{
_participantTowerIds.Clear();
@@ -22,7 +23,8 @@ namespace GeometryTD.UI
Dictionary<long, BearingCompItemData> bearingMap = BuildComponentMap(rawData.Inventory.BearingComponents);
Dictionary<long, BaseCompItemData> baseMap = BuildComponentMap(rawData.Inventory.BaseComponents);
Dictionary<long, TowerItemData> towerMap = BuildTowerMap(rawData.Inventory.Towers);
List<RepoItemContext> items = new List<RepoItemContext>();
List<RepoItemContext> componentItems = new List<RepoItemContext>();
List<TowerRepoItemContext> towerItems = new List<TowerRepoItemContext>();
if (rawData.Inventory.Towers != null)
{
@@ -33,15 +35,9 @@ namespace GeometryTD.UI
continue;
}
AddItemContext(items, new RepoItemContext
{
InstanceId = tower.InstanceId,
CanDrag = true,
EnduranceRate01 = ItemDescUtility.ResolveTowerEnduranceRate(tower, muzzleMap, bearingMap, baseMap),
ClickActionType = RepoItemClickActionType.OpenDetail,
ComponentSlotType = TowerCompSlotType.None,
IconAreaContext = BuildIconAreaContext(tower)
});
TowerRepoItemContext towerContext = BuildTowerRepoItemContext(tower, muzzleMap, bearingMap, baseMap,
RepoItemClickActionType.OpenDetail, true);
AddTowerItemContext(towerItems, towerContext);
AddItemDescSeed(
tower.InstanceId,
tower.Name,
@@ -60,7 +56,7 @@ namespace GeometryTD.UI
continue;
}
AddItemContext(items, new RepoItemContext
RepoItemContext componentContext = new RepoItemContext
{
InstanceId = item.InstanceId,
CanDrag = true,
@@ -68,7 +64,8 @@ namespace GeometryTD.UI
ClickActionType = RepoItemClickActionType.OpenDetail,
ComponentSlotType = TowerCompSlotType.Muzzle,
IconAreaContext = BuildIconAreaContext(item)
});
};
AddComponentItemContext(componentItems, componentContext);
AddItemDescSeed(
item.InstanceId,
item.Name,
@@ -87,7 +84,7 @@ namespace GeometryTD.UI
continue;
}
AddItemContext(items, new RepoItemContext
RepoItemContext componentContext = new RepoItemContext
{
InstanceId = item.InstanceId,
CanDrag = true,
@@ -95,7 +92,8 @@ namespace GeometryTD.UI
ClickActionType = RepoItemClickActionType.OpenDetail,
ComponentSlotType = TowerCompSlotType.Bearing,
IconAreaContext = BuildIconAreaContext(item)
});
};
AddComponentItemContext(componentItems, componentContext);
AddItemDescSeed(
item.InstanceId,
item.Name,
@@ -114,7 +112,7 @@ namespace GeometryTD.UI
continue;
}
AddItemContext(items, new RepoItemContext
RepoItemContext componentContext = new RepoItemContext
{
InstanceId = item.InstanceId,
CanDrag = true,
@@ -122,7 +120,8 @@ namespace GeometryTD.UI
ClickActionType = RepoItemClickActionType.OpenDetail,
ComponentSlotType = TowerCompSlotType.Base,
IconAreaContext = BuildIconAreaContext(item)
});
};
AddComponentItemContext(componentItems, componentContext);
AddItemDescSeed(
item.InstanceId,
item.Name,
@@ -140,13 +139,14 @@ namespace GeometryTD.UI
CombineAreaContext = new CombineAreaContext(),
CompAreaContext = new CompAreaContext
{
Items = items.ToArray()
ComponentItems = componentItems.ToArray(),
TowerItems = towerItems.ToArray()
},
ParticipantAreaContext = participantAreaContext
};
}
private void AddItemContext(List<RepoItemContext> items, RepoItemContext itemContext)
private void AddComponentItemContext(List<RepoItemContext> items, RepoItemContext itemContext)
{
if (itemContext == null)
{
@@ -154,7 +154,19 @@ namespace GeometryTD.UI
}
items.Add(itemContext);
_itemContextMap[itemContext.InstanceId] = itemContext;
_itemClickActionMap[itemContext.InstanceId] = itemContext.ClickActionType;
}
private void AddTowerItemContext(List<TowerRepoItemContext> items, TowerRepoItemContext itemContext)
{
if (itemContext == null)
{
return;
}
items.Add(itemContext);
_itemClickActionMap[itemContext.InstanceId] = itemContext.ClickActionType;
_compAreaTowerIds.Add(itemContext.InstanceId);
}
private void AddItemDescSeed(long itemId, string title, string typeText, string description, TagType[] tags)
@@ -243,7 +255,7 @@ namespace GeometryTD.UI
IReadOnlyDictionary<long, BaseCompItemData> baseMap)
{
_participantTowerIds.Clear();
List<RepoItemContext> participantItems = new List<RepoItemContext>();
List<TowerRepoItemContext> participantItems = new List<TowerRepoItemContext>();
if (inventory?.ParticipantTowerInstanceIds != null && towerMap != null)
{
for (int i = 0; i < inventory.ParticipantTowerInstanceIds.Count; i++)
@@ -264,41 +276,33 @@ namespace GeometryTD.UI
continue;
}
participantItems.Add(new RepoItemContext
TowerRepoItemContext towerContext = BuildTowerRepoItemContext(tower, muzzleMap, bearingMap, baseMap,
RepoItemClickActionType.RemoveParticipant, false);
if (towerContext != null)
{
InstanceId = tower.InstanceId,
CanDrag = false,
EnduranceRate01 = ItemDescUtility.ResolveTowerEnduranceRate(tower, muzzleMap, bearingMap, baseMap),
ClickActionType = RepoItemClickActionType.RemoveParticipant,
ComponentSlotType = TowerCompSlotType.None,
IconAreaContext = BuildIconAreaContext(tower)
});
participantItems.Add(towerContext);
_itemClickActionMap[towerContext.InstanceId] = towerContext.ClickActionType;
}
}
}
return new ParticipantAreaContext
{
Items = participantItems.ToArray(),
TowerItems = participantItems.ToArray(),
MaxCount = MaxParticipantCount
};
}
private void ApplyParticipantSelection()
{
if (Form == null || _itemContextMap.Count <= 0)
if (Form == null || _compAreaTowerIds.Count <= 0)
{
return;
}
foreach (KeyValuePair<long, RepoItemContext> pair in _itemContextMap)
foreach (long towerId in _compAreaTowerIds)
{
RepoItemContext itemContext = pair.Value;
if (itemContext == null || itemContext.ComponentSlotType != TowerCompSlotType.None)
{
continue;
}
Form.SetRepoItemSelected(pair.Key, _participantTowerIds.Contains(pair.Key));
Form.SetRepoItemSelected(towerId, _participantTowerIds.Contains(towerId));
}
}
@@ -319,25 +323,33 @@ namespace GeometryTD.UI
ApplyParticipantSelection();
}
private static IconAreaContext BuildIconAreaContext(TowerItemData tower)
private static TowerRepoItemContext BuildTowerRepoItemContext(
TowerItemData tower,
IReadOnlyDictionary<long, MuzzleCompItemData> muzzleMap,
IReadOnlyDictionary<long, BearingCompItemData> bearingMap,
IReadOnlyDictionary<long, BaseCompItemData> baseMap,
RepoItemClickActionType clickActionType,
bool canDrag)
{
if (tower == null)
{
return new IconAreaContext
{
ComponentSlotType = TowerCompSlotType.None,
Rarity = RarityType.None,
Color = Color.white,
Icon = null
};
return null;
}
return new IconAreaContext
return new TowerRepoItemContext
{
InstanceId = tower.InstanceId,
CanDrag = canDrag,
EnduranceRate01 = ItemDescUtility.ResolveTowerEnduranceRate(tower, muzzleMap, bearingMap, baseMap),
ClickActionType = clickActionType,
ComponentSlotType = TowerCompSlotType.None,
Rarity = tower.Rarity,
Color = IconColorGenerator.GenerateForTower(tower),
Icon = null
IconAreaContext = new TowerIconAreaContext
{
Rarity = tower.Rarity,
MuzzleColor = ResolveComponentColor(tower.MuzzleComponentInstanceId, muzzleMap),
BearingColor = ResolveComponentColor(tower.BearingComponentInstanceId, bearingMap),
BaseColor = ResolveComponentColor(tower.BaseComponentInstanceId, baseMap)
}
};
}
@@ -362,5 +374,21 @@ namespace GeometryTD.UI
Icon = null
};
}
private static Color ResolveComponentColor<TComp>(long instanceId,
IReadOnlyDictionary<long, TComp> componentMap)
where TComp : TowerCompItemData
{
if (instanceId > 0 &&
componentMap != null &&
componentMap.TryGetValue(instanceId, out TComp component) &&
component != null)
{
return IconColorGenerator.GenerateForComponent(component);
}
return Color.white;
}
}
}
@@ -11,9 +11,11 @@ namespace GeometryTD.UI
{
private const int MaxParticipantCount = 4;
private RepoFormUseCase _useCase;
private readonly Dictionary<long, RepoItemContext> _itemContextMap = new Dictionary<long, RepoItemContext>();
private readonly Dictionary<long, RepoItemClickActionType> _itemClickActionMap =
new Dictionary<long, RepoItemClickActionType>();
private readonly Dictionary<long, ItemDescSeed> _itemDescSeedMap = new Dictionary<long, ItemDescSeed>();
private readonly HashSet<long> _participantTowerIds = new HashSet<long>();
private readonly HashSet<long> _compAreaTowerIds = new HashSet<long>();
private sealed class ItemDescSeed
{
@@ -111,14 +113,9 @@ namespace GeometryTD.UI
}
RepoItemClickActionType clickActionType = RepoItemClickActionType.OpenDetail;
if (sender is RepoItem clickedItem && clickedItem.Context != null)
if (_itemClickActionMap.TryGetValue(args.ItemId, out RepoItemClickActionType mappedAction))
{
clickActionType = clickedItem.Context.ClickActionType;
}
else if (_itemContextMap.TryGetValue(args.ItemId, out RepoItemContext itemContextFromMap) &&
itemContextFromMap != null)
{
clickActionType = itemContextFromMap.ClickActionType;
clickActionType = mappedAction;
}
if (args.ItemId <= 0)
@@ -170,7 +167,7 @@ namespace GeometryTD.UI
return;
}
if (Form == null || !_itemContextMap.ContainsKey(args.ItemId))
if (Form == null || !_itemClickActionMap.ContainsKey(args.ItemId))
{
return;
}
@@ -212,8 +209,8 @@ namespace GeometryTD.UI
{
return;
}
this.CloseUI();
CloseUI();
}
private void OnRepoCombineRequested(object sender, GameEventArgs e)
@@ -258,12 +255,12 @@ namespace GeometryTD.UI
{
return;
}
if (_useCase == null || Form == null || args.TowerItemId <= 0)
{
return;
}
if (!_useCase.TryAddParticipantTower(args.TowerItemId))
{
return;
@@ -67,11 +67,6 @@ namespace GeometryTD.UI
InventoryParticipantUtility.NormalizeParticipantState(_fallbackInventory, MaxParticipantCount);
return _fallbackInventory;
}
public static BackpackInventoryData SampleInventory()
{
return InventorySeedUtility.CreateSampleInventory();
}
}
}
@@ -1,5 +1,5 @@
using GeometryTD.Definition;
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using UnityEngine;
using UnityEngine.EventSystems;
@@ -102,19 +102,15 @@ namespace GeometryTD.UI
return;
}
RepoItem repoItem = pointerDrag.GetComponent<RepoItem>();
if (repoItem == null)
{
repoItem = pointerDrag.GetComponentInParent<RepoItem>();
}
if (repoItem == null)
IRepoDragItemView dragItem = pointerDrag.GetComponent<IRepoDragItemView>() ??
pointerDrag.GetComponentInParent<IRepoDragItemView>();
if (dragItem == null)
{
return;
}
bool assigned = TryAssignItem(repoItem.Context);
repoItem.SetDropResult(assigned);
bool assigned = TryAssignItem(dragItem.ComponentContext);
dragItem.SetDropResult(assigned);
}
private CombineSlotItem FindSlot(TowerCompSlotType slotType)
+109 -33
View File
@@ -11,12 +11,17 @@ namespace GeometryTD.UI
{
[FormerlySerializedAs("m_Content")] [SerializeField] private Transform _content;
[FormerlySerializedAs("m_ItemTemplate")] [SerializeField] private RepoItem _itemTemplate;
[SerializeField] private TowerRepoItem _towerItemTemplate;
[FormerlySerializedAs("m_InstancePoolCapacity")] [SerializeField] private int _instancePoolCapacity = 64;
private readonly List<RepoItem> _activeItems = new List<RepoItem>();
private readonly Dictionary<long, RepoItem> _itemMap = new Dictionary<long, RepoItem>();
private IObjectPool<RepoItemObject> _itemPool;
private string _poolName;
private readonly List<RepoItem> _activeComponentItems = new List<RepoItem>();
private readonly List<TowerRepoItem> _activeTowerItems = new List<TowerRepoItem>();
private readonly Dictionary<long, RepoItem> _componentItemMap = new Dictionary<long, RepoItem>();
private readonly Dictionary<long, TowerRepoItem> _towerItemMap = new Dictionary<long, TowerRepoItem>();
private IObjectPool<RepoItemObject> _componentItemPool;
private IObjectPool<TowerRepoItemObject> _towerItemPool;
private string _componentPoolName;
private string _towerPoolName;
public void OnInit(CompAreaContext context)
{
@@ -29,31 +34,46 @@ namespace GeometryTD.UI
EnsurePool();
OnReset();
if (context.Items == null || context.Items.Length == 0)
if (context.ComponentItems != null)
{
return;
foreach (RepoItemContext itemContext in context.ComponentItems)
{
RepoItem item = CreateOrSpawnComponentItem();
if (item == null)
{
continue;
}
item.gameObject.SetActive(true);
item.OnInit(itemContext);
_activeComponentItems.Add(item);
_componentItemMap[itemContext.InstanceId] = item;
}
}
foreach (var itemContext in context.Items)
if (context.TowerItems != null)
{
RepoItem item = CreateOrSpawnItem();
if (item == null)
foreach (TowerRepoItemContext itemContext in context.TowerItems)
{
continue;
}
TowerRepoItem item = CreateOrSpawnTowerItem();
if (item == null)
{
continue;
}
item.gameObject.SetActive(true);
item.OnInit(itemContext);
_activeItems.Add(item);
_itemMap[itemContext.InstanceId] = item;
item.gameObject.SetActive(true);
item.OnInit(itemContext);
_activeTowerItems.Add(item);
_towerItemMap[itemContext.InstanceId] = item;
}
}
}
public void OnReset()
{
for (int i = _activeItems.Count - 1; i >= 0; i--)
for (int i = _activeComponentItems.Count - 1; i >= 0; i--)
{
RepoItem item = _activeItems[i];
RepoItem item = _activeComponentItems[i];
if (item == null)
{
continue;
@@ -61,49 +81,102 @@ namespace GeometryTD.UI
item.OnReset();
item.gameObject.SetActive(false);
_itemPool?.Unspawn(item);
_componentItemPool?.Unspawn(item);
}
_activeItems.Clear();
_itemMap.Clear();
for (int i = _activeTowerItems.Count - 1; i >= 0; i--)
{
TowerRepoItem item = _activeTowerItems[i];
if (item == null)
{
continue;
}
item.OnReset();
item.gameObject.SetActive(false);
_towerItemPool?.Unspawn(item);
}
_activeComponentItems.Clear();
_activeTowerItems.Clear();
_componentItemMap.Clear();
_towerItemMap.Clear();
}
private void OnDestroy()
{
OnReset();
_itemPool?.ReleaseAllUnused();
_componentItemPool?.ReleaseAllUnused();
_towerItemPool?.ReleaseAllUnused();
}
private void EnsurePool()
{
if (_itemPool != null)
if (_componentItemPool == null)
{
return;
_componentPoolName = $"RepoItemPool_{GetInstanceID()}";
_componentItemPool =
GameEntry.ObjectPool.CreateSingleSpawnObjectPool<RepoItemObject>(_componentPoolName,
_instancePoolCapacity);
}
_poolName = $"RepoItemPool_{GetInstanceID()}";
_itemPool = GameEntry.ObjectPool.CreateSingleSpawnObjectPool<RepoItemObject>(_poolName, _instancePoolCapacity);
if (_towerItemPool == null)
{
_towerPoolName = $"TowerRepoItemPool_{GetInstanceID()}";
_towerItemPool =
GameEntry.ObjectPool.CreateSingleSpawnObjectPool<TowerRepoItemObject>(_towerPoolName,
_instancePoolCapacity);
}
}
private RepoItem CreateOrSpawnItem()
private RepoItem CreateOrSpawnComponentItem()
{
if (_itemPool == null)
if (_componentItemPool == null)
{
return null;
}
RepoItemObject itemObject = _itemPool.Spawn();
RepoItemObject itemObject = _componentItemPool.Spawn();
RepoItem item = itemObject != null ? (RepoItem)itemObject.Target : null;
if (item == null)
{
if (_itemTemplate == null)
{
Log.Warning("CompArea requires an item template.");
Log.Warning("CompArea requires a component item template.");
return null;
}
item = Instantiate(_itemTemplate);
_itemPool.Register(RepoItemObject.Create(item), true);
_componentItemPool.Register(RepoItemObject.Create(item), true);
}
if (_content != null)
{
item.transform.SetParent(_content, false);
}
return item;
}
private TowerRepoItem CreateOrSpawnTowerItem()
{
if (_towerItemPool == null)
{
return null;
}
TowerRepoItemObject itemObject = _towerItemPool.Spawn();
TowerRepoItem item = itemObject != null ? (TowerRepoItem)itemObject.Target : null;
if (item == null)
{
if (_towerItemTemplate == null)
{
Log.Warning("CompArea requires a tower item template.");
return null;
}
item = Instantiate(_towerItemTemplate);
_towerItemPool.Register(TowerRepoItemObject.Create(item), true);
}
if (_content != null)
@@ -116,12 +189,15 @@ namespace GeometryTD.UI
public void SetItemSelected(long itemId, bool isSelected)
{
if (!_itemMap.TryGetValue(itemId, out RepoItem item))
if (_componentItemMap.TryGetValue(itemId, out RepoItem componentItem))
{
return;
componentItem.SetSelected(isSelected);
}
item.SetSelected(isSelected);
if (_towerItemMap.TryGetValue(itemId, out TowerRepoItem towerItem))
{
towerItem.SetSelected(isSelected);
}
}
}
}
@@ -0,0 +1,19 @@
using GeometryTD.Definition;
namespace GeometryTD.UI
{
public interface IRepoDragItemView
{
long InstanceId { get; }
bool CanDrag { get; }
TowerCompSlotType ComponentSlotType { get; }
RepoItemClickActionType ClickActionType { get; }
RepoItemContext ComponentContext { get; }
void SetDropResult(bool assigned);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5cb3c8e275a9ba646a58a2656af31faa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -12,12 +12,12 @@ namespace GeometryTD.UI
public class ParticipantArea : MonoBehaviour, IDropHandler
{
[SerializeField] private Transform _content;
[SerializeField] private RepoItem _itemTemplate;
[SerializeField] private TowerRepoItem _towerItemTemplate;
[SerializeField] private int _instancePoolCapacity = 8;
private readonly List<RepoItem> _activeItems = new List<RepoItem>();
private readonly List<TowerRepoItem> _activeItems = new List<TowerRepoItem>();
private readonly HashSet<long> _boundItemIds = new HashSet<long>();
private IObjectPool<RepoItemObject> _itemPool;
private IObjectPool<TowerRepoItemObject> _itemPool;
private string _poolName;
private int _maxCount = 4;
@@ -27,20 +27,20 @@ namespace GeometryTD.UI
EnsurePool();
_maxCount = Mathf.Max(1, context != null ? context.MaxCount : 4);
if (context?.Items == null || context.Items.Length <= 0)
if (context?.TowerItems == null || context.TowerItems.Length <= 0)
{
return;
}
for (int i = 0; i < context.Items.Length; i++)
for (int i = 0; i < context.TowerItems.Length; i++)
{
RepoItemContext itemContext = context.Items[i];
TowerRepoItemContext itemContext = context.TowerItems[i];
if (itemContext == null || itemContext.InstanceId <= 0)
{
continue;
}
RepoItem item = CreateOrSpawnItem();
TowerRepoItem item = CreateOrSpawnItem();
if (item == null)
{
continue;
@@ -57,7 +57,7 @@ namespace GeometryTD.UI
{
for (int i = _activeItems.Count - 1; i >= 0; i--)
{
RepoItem item = _activeItems[i];
TowerRepoItem item = _activeItems[i];
if (item == null)
{
continue;
@@ -79,19 +79,19 @@ namespace GeometryTD.UI
_itemPool?.ReleaseAllUnused();
}
public bool CanAssign(RepoItemContext itemContext)
public bool CanAssign(IRepoDragItemView dragItem)
{
if (itemContext == null || itemContext.InstanceId <= 0)
if (dragItem == null || dragItem.InstanceId <= 0)
{
return false;
}
if (itemContext.ComponentSlotType != TowerCompSlotType.None)
if (dragItem.ComponentSlotType != TowerCompSlotType.None)
{
return false;
}
if (_boundItemIds.Contains(itemContext.InstanceId))
if (_boundItemIds.Contains(dragItem.InstanceId))
{
return false;
}
@@ -117,21 +117,21 @@ namespace GeometryTD.UI
return;
}
RepoItem repoItem = pointerDrag.GetComponent<RepoItem>() ?? pointerDrag.GetComponentInParent<RepoItem>();
if (repoItem == null)
IRepoDragItemView dragItem = pointerDrag.GetComponent<IRepoDragItemView>() ??
pointerDrag.GetComponentInParent<IRepoDragItemView>();
if (dragItem == null)
{
return;
}
RepoItemContext itemContext = repoItem.Context;
bool assigned = CanAssign(itemContext);
repoItem.SetDropResult(assigned);
bool assigned = CanAssign(dragItem);
dragItem.SetDropResult(assigned);
if (!assigned)
{
return;
}
GameEntry.Event.Fire(this, RepoParticipantAssignRequestedEventArgs.Create(itemContext.InstanceId));
GameEntry.Event.Fire(this, RepoParticipantAssignRequestedEventArgs.Create(dragItem.InstanceId));
}
private void EnsurePool()
@@ -141,29 +141,30 @@ namespace GeometryTD.UI
return;
}
_poolName = $"ParticipantRepoItemPool_{GetInstanceID()}";
_itemPool = GameEntry.ObjectPool.CreateSingleSpawnObjectPool<RepoItemObject>(_poolName, _instancePoolCapacity);
_poolName = $"ParticipantTowerRepoItemPool_{GetInstanceID()}";
_itemPool =
GameEntry.ObjectPool.CreateSingleSpawnObjectPool<TowerRepoItemObject>(_poolName, _instancePoolCapacity);
}
private RepoItem CreateOrSpawnItem()
private TowerRepoItem CreateOrSpawnItem()
{
if (_itemPool == null)
{
return null;
}
RepoItemObject itemObject = _itemPool.Spawn();
RepoItem item = itemObject != null ? (RepoItem)itemObject.Target : null;
TowerRepoItemObject itemObject = _itemPool.Spawn();
TowerRepoItem item = itemObject != null ? (TowerRepoItem)itemObject.Target : null;
if (item == null)
{
if (_itemTemplate == null)
if (_towerItemTemplate == null)
{
Log.Warning("ParticipantArea requires an item template.");
Log.Warning("ParticipantArea requires a tower item template.");
return null;
}
item = Instantiate(_itemTemplate);
_itemPool.Register(RepoItemObject.Create(item), true);
item = Instantiate(_towerItemTemplate);
_itemPool.Register(TowerRepoItemObject.Create(item), true);
}
if (_content != null)
@@ -6,10 +6,10 @@ using UnityEngine.UI;
namespace GeometryTD.UI
{
public class RepoItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
public class RepoItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IRepoDragItemView
{
[SerializeField] private Image _bgImage;
[SerializeField] private IconArea _iconArea;
private static readonly Color SelectedColor = new Color32(255, 216, 102, 255);
@@ -20,8 +20,8 @@ namespace GeometryTD.UI
[SerializeField] private RepoItemContext _context;
private CanvasGroup _canvasGroup;
private RectTransform _dragRoot;
private GameObject _dragGhostObject;
private RectTransform _dragGhostRect;
protected GameObject _dragGhostObject;
protected RectTransform _dragGhostRect;
private bool _isSelected;
private bool _isDragging;
private bool _dropHandled;
@@ -29,6 +29,24 @@ namespace GeometryTD.UI
public RepoItemContext Context => _context;
public long InstanceId => _context != null ? _context.InstanceId : 0;
bool IRepoDragItemView.CanDrag => _context != null && _context.CanDrag;
public TowerCompSlotType ComponentSlotType =>
_context != null ? _context.ComponentSlotType : TowerCompSlotType.None;
public RepoItemClickActionType ClickActionType =>
_context != null ? _context.ClickActionType : RepoItemClickActionType.OpenDetail;
public RepoItemContext ComponentContext => _context;
protected IconArea IconArea => _iconArea;
protected Image BackgroundImage => _bgImage;
protected RectTransform DragRoot => _dragRoot;
private void Awake()
{
_canvasGroup = GetComponent<CanvasGroup>();
@@ -43,7 +61,7 @@ namespace GeometryTD.UI
ResetDragState();
}
public void OnInit(RepoItemContext context)
public virtual void OnInit(RepoItemContext context)
{
if (context == null)
{
@@ -52,14 +70,13 @@ namespace GeometryTD.UI
}
_context = context;
_iconArea.OnInit(context.IconAreaContext);
SetSelected(false);
ResetDragState();
}
public void OnReset()
public virtual void OnReset()
{
_context = null;
_iconArea.OnReset();
@@ -165,6 +182,7 @@ namespace GeometryTD.UI
{
_canvasGroup.blocksRaycasts = true;
}
DestroyDragGhost();
_dragRoot = null;
@@ -232,7 +250,7 @@ namespace GeometryTD.UI
return canvas.rootCanvas.transform as RectTransform;
}
private bool CreateDragGhost()
protected virtual bool CreateDragGhost()
{
DestroyDragGhost();
@@ -246,7 +264,6 @@ namespace GeometryTD.UI
Material iconMaterial = _iconArea != null ? _iconArea.CurrentIconMaterial : null;
Vector2 iconSize = _iconArea != null ? _iconArea.CurrentIconSize : Vector2.zero;
// Towers may not have an icon sprite; fall back to background sprite so drag still works.
if (iconSprite == null && _bgImage != null)
{
iconSprite = _bgImage.sprite;
@@ -260,7 +277,8 @@ namespace GeometryTD.UI
return false;
}
_dragGhostObject = new GameObject("RepoItemDragGhost", typeof(RectTransform), typeof(CanvasGroup), typeof(Image));
_dragGhostObject = new GameObject("RepoItemDragGhost", typeof(RectTransform), typeof(CanvasGroup),
typeof(Image));
_dragGhostObject.layer = gameObject.layer;
_dragGhostRect = _dragGhostObject.GetComponent<RectTransform>();
@@ -273,6 +291,7 @@ namespace GeometryTD.UI
{
iconSize = DefaultDragGhostSize;
}
_dragGhostRect.sizeDelta = iconSize;
Image ghostImage = _dragGhostObject.GetComponent<Image>();
@@ -290,7 +309,7 @@ namespace GeometryTD.UI
return true;
}
private void UpdateDragGhostPosition(PointerEventData eventData)
protected virtual void UpdateDragGhostPosition(PointerEventData eventData)
{
if (_dragGhostRect == null || _dragRoot == null || eventData == null)
{
@@ -304,7 +323,7 @@ namespace GeometryTD.UI
}
}
private void DestroyDragGhost()
protected virtual void DestroyDragGhost()
{
if (_dragGhostObject != null)
{
@@ -329,3 +348,4 @@ namespace GeometryTD.UI
}
}
}
@@ -0,0 +1,109 @@
using UnityEngine;
using UnityEngine.UI;
namespace GeometryTD.UI
{
public class TowerRepoItem : RepoItem
{
private static readonly Vector2 DefaultTowerDragGhostSize = new Vector2(64f, 64f);
public new TowerRepoItemContext Context => base.Context as TowerRepoItemContext;
public void OnInit(TowerRepoItemContext context)
{
base.OnInit(context);
}
protected override bool CreateDragGhost()
{
DestroyDragGhost();
if (DragRoot == null)
{
return false;
}
TowerIconArea towerIconArea = IconArea as TowerIconArea;
if (towerIconArea == null)
{
return base.CreateDragGhost();
}
_dragGhostObject = new GameObject("TowerRepoItemDragGhost", typeof(RectTransform), typeof(CanvasGroup));
_dragGhostObject.layer = gameObject.layer;
_dragGhostRect = _dragGhostObject.GetComponent<RectTransform>();
_dragGhostRect.SetParent(DragRoot, false);
_dragGhostRect.anchorMin = new Vector2(0.5f, 0.5f);
_dragGhostRect.anchorMax = new Vector2(0.5f, 0.5f);
_dragGhostRect.pivot = new Vector2(0.5f, 0.5f);
Vector2 iconSize = IconArea != null ? IconArea.CurrentIconSize : Vector2.zero;
if (iconSize.x <= 0f || iconSize.y <= 0f)
{
iconSize = BackgroundImage != null ? BackgroundImage.rectTransform.rect.size : DefaultTowerDragGhostSize;
}
if (iconSize.x <= 0f || iconSize.y <= 0f)
{
iconSize = DefaultTowerDragGhostSize;
}
_dragGhostRect.sizeDelta = iconSize;
bool hasAnyLayer = false;
hasAnyLayer |= TryCreateLayer("Base", towerIconArea.BaseIconSprite, towerIconArea.BaseIconColor,
towerIconArea.BaseIconMaterial, _dragGhostRect, 0);
hasAnyLayer |= TryCreateLayer("Bearing", towerIconArea.BearingIconSprite, towerIconArea.BearingIconColor,
towerIconArea.BearingIconMaterial, _dragGhostRect, 1);
hasAnyLayer |= TryCreateLayer("Muzzle", towerIconArea.MuzzleIconSprite, towerIconArea.MuzzleIconColor,
towerIconArea.MuzzleIconMaterial, _dragGhostRect, 2);
if (!hasAnyLayer)
{
DestroyDragGhost();
return base.CreateDragGhost();
}
CanvasGroup ghostCanvasGroup = _dragGhostObject.GetComponent<CanvasGroup>();
ghostCanvasGroup.blocksRaycasts = false;
ghostCanvasGroup.interactable = false;
_dragGhostObject.transform.SetAsLastSibling();
return true;
}
private static bool TryCreateLayer(
string layerName,
Sprite sprite,
Color color,
Material material,
RectTransform parent,
int siblingIndex)
{
if (parent == null || sprite == null)
{
return false;
}
GameObject layerObject = new GameObject($"TowerGhost{layerName}", typeof(RectTransform), typeof(Image));
layerObject.layer = parent.gameObject.layer;
RectTransform layerRect = layerObject.GetComponent<RectTransform>();
layerRect.SetParent(parent, false);
layerRect.anchorMin = Vector2.zero;
layerRect.anchorMax = Vector2.one;
layerRect.offsetMin = Vector2.zero;
layerRect.offsetMax = Vector2.zero;
layerRect.SetSiblingIndex(siblingIndex);
Image layerImage = layerObject.GetComponent<Image>();
layerImage.raycastTarget = false;
layerImage.sprite = sprite;
layerImage.color = color;
layerImage.material = material;
layerImage.preserveAspect = true;
return true;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: caf7b65cc179a5d46828128f574ed63e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e3576597006db5b458566429f98a6f43
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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:
@@ -1,4 +1,4 @@
using System;
using System;
using GeometryTD.Definition;
using UnityEngine;
@@ -162,7 +162,9 @@ namespace GeometryTD.CustomUtility
MuzzleComponentInstanceId = source.MuzzleComponentInstanceId,
BearingComponentInstanceId = source.BearingComponentInstanceId,
BaseComponentInstanceId = source.BaseComponentInstanceId,
Stats = CloneTowerStats(source.Stats)
Stats = CloneTowerStats(source.Stats),
ComposedIconSprite = source.ComposedIconSprite,
ComposedIconKey = source.ComposedIconKey
};
}
@@ -205,3 +207,4 @@ namespace GeometryTD.CustomUtility
}
@@ -19,41 +19,12 @@ namespace GeometryTD.CustomUtility
Rarity = RarityType.Green,
Endurance = 90f,
IsAssembledIntoTower = true,
AttackDamage = new[] { 20, 30, 40, 50, 80 },
AttackDamage = new[] { 200, 300, 400, 500, 800 },
DamageRandomRate = 0.05f,
AttackMethodType = AttackMethodType.NormalBullet,
Constraint = string.Empty,
Tags = new[] { TagType.Fire }
};
inventory.MuzzleComponents.Add(muzzle);
inventory.MuzzleComponents.Add(new MuzzleCompItemData
{
InstanceId = 10002,
ConfigId = 2,
Name = "控制枪口",
Rarity = RarityType.Blue,
Endurance = 80,
IsAssembledIntoTower = false,
AttackDamage = new[] { 30, 50, 70, 90, 100 },
DamageRandomRate = 0.01f,
AttackMethodType = AttackMethodType.NormalBullet,
Constraint = string.Empty,
Tags = new[] { TagType.Ice, TagType.FreezeMask }
});
inventory.MuzzleComponents.Add(new MuzzleCompItemData
{
InstanceId = 10003,
ConfigId = 3,
Name = "穿透枪口",
Rarity = RarityType.Purple,
Endurance = 97f,
IsAssembledIntoTower = false,
AttackDamage = new[] { 50, 55, 60, 80, 90 },
DamageRandomRate = 0.02f,
AttackMethodType = AttackMethodType.NormalBullet,
Constraint = string.Empty,
Tags = new[] { TagType.Pierce, TagType.Crit }
});
BearingCompItemData bearing = new BearingCompItemData
{
@@ -63,38 +34,11 @@ namespace GeometryTD.CustomUtility
Rarity = RarityType.Green,
Endurance = 1f,
IsAssembledIntoTower = true,
RotateSpeed = new[] { 10f, 12f, 13f, 14f, 15f },
AttackRange = new[] { 2f, 2f, 2f, 2f, 2f },
RotateSpeed = new[] { 100f, 120f, 130f, 140f, 150f },
AttackRange = new[] { 3f, 4f, 5f, 6f, 8f },
Constraint = string.Empty,
Tags = new[] { TagType.Fire }
};
inventory.BearingComponents.Add(bearing);
inventory.BearingComponents.Add(new BearingCompItemData
{
InstanceId = 20002,
ConfigId = 2,
Name = "控制轴承",
Rarity = RarityType.Blue,
Endurance = 20,
IsAssembledIntoTower = false,
RotateSpeed = new[] { 20f, 25f, 30f, 32f, 35f },
AttackRange = new[] { 6f, 6.5f, 7f, 8f, 8f },
Constraint = string.Empty,
Tags = new[] { TagType.Ice, TagType.Shatter }
});
inventory.BearingComponents.Add(new BearingCompItemData
{
InstanceId = 20003,
ConfigId = 3,
Name = "穿透轴承",
Rarity = RarityType.Purple,
Endurance = 96f,
IsAssembledIntoTower = false,
RotateSpeed = new[] { 60f, 70f, 80f, 90f, 100f },
AttackRange = new[] { 4f, 4.5f, 5f, 5.5f, 6f },
Constraint = string.Empty,
Tags = new[] { TagType.Pierce, TagType.Overpenetrate }
});
BaseCompItemData baseComp = new BaseCompItemData
{
@@ -104,38 +48,11 @@ namespace GeometryTD.CustomUtility
Rarity = RarityType.Green,
Endurance = 88f,
IsAssembledIntoTower = true,
AttackSpeed = new[] { 2f, 1.5f, 1f, 0.8f, 0.7f },
AttackSpeed = new[] { 1f, 2f, 3f, 3.5f, 0.7f },
AttackPropertyType = AttackPropertyType.Fire,
Constraint = string.Empty,
Tags = new[] { TagType.Fire }
};
inventory.BaseComponents.Add(baseComp);
inventory.BaseComponents.Add(new BaseCompItemData
{
InstanceId = 30002,
ConfigId = 2,
Name = "控制底座",
Rarity = RarityType.Blue,
Endurance = 50f,
IsAssembledIntoTower = false,
AttackSpeed = new[] { 4f, 4.2f, 4.4f, 4.6f, 4.8f },
AttackPropertyType = AttackPropertyType.Ice,
Constraint = string.Empty,
Tags = new[] { TagType.Ice, TagType.AbsoluteZero }
});
inventory.BaseComponents.Add(new BaseCompItemData
{
InstanceId = 30003,
ConfigId = 3,
Name = "穿透底座",
Rarity = RarityType.Purple,
Endurance = 30f,
IsAssembledIntoTower = false,
AttackSpeed = new[] { 1f, 1f, 1f, 1f, 1f },
AttackPropertyType = AttackPropertyType.Physics,
Constraint = string.Empty,
Tags = new[] { TagType.Pierce, TagType.Execution }
});
TowerItemData tower = new TowerItemData
{
@@ -151,35 +68,103 @@ namespace GeometryTD.CustomUtility
AttackDamage = new[] { 200, 220, 240, 260, 300 },
DamageRandomRate = 0f,
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
AttackRange = new[] { 4.5f, 4.5f, 4.5f, 4.5f, 4.5f },
AttackSpeed = new[] { 1.0f, 1.2f, 1.3f, 1.4f, 0.5f },
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Fire,
Tags = new[] { TagType.Fire, TagType.BurnSpread }
}
};
inventory.MuzzleComponents.Add(muzzle);
inventory.BaseComponents.Add(baseComp);
inventory.BearingComponents.Add(bearing);
inventory.Towers.Add(tower);
inventory.Towers.Add(new TowerItemData
inventory.MuzzleComponents.Add(new MuzzleCompItemData
{
InstanceId = 90002,
Name = "测试防御塔-B",
InstanceId = 10002,
ConfigId = 2,
Name = "控制枪口",
Rarity = RarityType.Blue,
IsParticipatingInCombat = false,
MuzzleComponentInstanceId = 0,
BearingComponentInstanceId = 0,
BaseComponentInstanceId = 0,
Stats = new TowerStatsData
{
AttackDamage = new[] { 200, 220, 240, 260, 300 },
DamageRandomRate = 0.1f,
RotateSpeed = new[] { 200f, 210f, 220f, 230f, 240f },
AttackRange = new[] { 4.5f, 4.7f, 4.9f, 5.1f, 5.3f },
AttackSpeed = new[] { 1.5f, 1.2f, 1.1f, 1.0f, 0.8f },
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Physics,
Tags = new[] { TagType.Pierce }
}
Endurance = 80,
IsAssembledIntoTower = false,
AttackDamage = new[] { 200, 300, 400, 500, 600 },
DamageRandomRate = 0.01f,
AttackMethodType = AttackMethodType.NormalBullet,
Constraint = string.Empty,
Tags = new[] { TagType.Ice, TagType.FreezeMask }
});
inventory.MuzzleComponents.Add(new MuzzleCompItemData
{
InstanceId = 10003,
ConfigId = 3,
Name = "穿透枪口",
Rarity = RarityType.Purple,
Endurance = 97f,
IsAssembledIntoTower = false,
AttackDamage = new[] { 50, 55, 60, 80, 90 },
DamageRandomRate = 0.02f,
AttackMethodType = AttackMethodType.NormalBullet,
Constraint = string.Empty,
Tags = new[] { TagType.Pierce, TagType.Crit }
});
inventory.BearingComponents.Add(new BearingCompItemData
{
InstanceId = 20002,
ConfigId = 2,
Name = "控制轴承",
Rarity = RarityType.Blue,
Endurance = 20,
IsAssembledIntoTower = false,
RotateSpeed = new[] { 200f, 250f, 300f, 320f, 350f },
AttackRange = new[] { 6f, 6.5f, 7f, 8f, 8f },
Constraint = string.Empty,
Tags = new[] { TagType.Ice, TagType.Shatter }
});
inventory.BearingComponents.Add(new BearingCompItemData
{
InstanceId = 20003,
ConfigId = 3,
Name = "穿透轴承",
Rarity = RarityType.Purple,
Endurance = 96f,
IsAssembledIntoTower = false,
RotateSpeed = new[] { 60f, 70f, 80f, 90f, 100f },
AttackRange = new[] { 4f, 4.5f, 5f, 5.5f, 6f },
Constraint = string.Empty,
Tags = new[] { TagType.Pierce, TagType.Overpenetrate }
});
inventory.BaseComponents.Add(new BaseCompItemData
{
InstanceId = 30002,
ConfigId = 2,
Name = "控制底座",
Rarity = RarityType.Blue,
Endurance = 50f,
IsAssembledIntoTower = false,
AttackSpeed = new[] { 4f, 4.2f, 4.4f, 4.6f, 4.8f },
AttackPropertyType = AttackPropertyType.Ice,
Constraint = string.Empty,
Tags = new[] { TagType.Ice, TagType.AbsoluteZero }
});
inventory.BaseComponents.Add(new BaseCompItemData
{
InstanceId = 30003,
ConfigId = 3,
Name = "穿透底座",
Rarity = RarityType.Purple,
Endurance = 30f,
IsAssembledIntoTower = false,
AttackSpeed = new[] { 1f, 1f, 1f, 1f, 1f },
AttackPropertyType = AttackPropertyType.Physics,
Constraint = string.Empty,
Tags = new[] { TagType.Pierce, TagType.Execution }
});
inventory.ParticipantTowerInstanceIds.Add(90001);
@@ -0,0 +1,130 @@
using System.Collections.Generic;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.CustomUtility
{
public static class TowerComposedIconCacheUtility
{
private const string MuzzleAssetName = "Muzzle";
private const string BearingAssetName = "Bearing";
private const string BaseAssetName = "Base";
private static Sprite _muzzleSprite;
private static Sprite _bearingSprite;
private static Sprite _baseSprite;
private static bool s_RequestedMuzzle;
private static bool s_RequestedBearing;
private static bool s_RequestedBase;
public static Sprite ResolveTowerIconSprite(
TowerItemData tower,
IReadOnlyDictionary<long, MuzzleCompItemData> muzzleMap,
IReadOnlyDictionary<long, BearingCompItemData> bearingMap,
IReadOnlyDictionary<long, BaseCompItemData> baseMap)
{
if (tower == null)
{
return null;
}
if (!TryGetComponents(tower, muzzleMap, bearingMap, baseMap,
out MuzzleCompItemData muzzleComp,
out BearingCompItemData bearingComp,
out BaseCompItemData baseComp))
{
return null;
}
Color muzzleColor = IconColorGenerator.GenerateForComponent(muzzleComp);
Color bearingColor = IconColorGenerator.GenerateForComponent(bearingComp);
Color baseColor = IconColorGenerator.GenerateForComponent(baseComp);
string cacheKey = TowerIconComposeUtility.BuildCacheKey(
tower.MuzzleComponentInstanceId,
tower.BearingComponentInstanceId,
tower.BaseComponentInstanceId,
muzzleColor,
bearingColor,
baseColor);
if (tower.ComposedIconSprite != null && string.Equals(tower.ComposedIconKey, cacheKey))
{
return tower.ComposedIconSprite;
}
EnsureBaseSpritesRequested();
if (_muzzleSprite == null || _bearingSprite == null || _baseSprite == null)
{
return null;
}
Sprite composedSprite = TowerIconComposeUtility.Compose(
_muzzleSprite,
muzzleColor,
_bearingSprite,
bearingColor,
_baseSprite,
baseColor);
if (composedSprite == null)
{
return null;
}
tower.ComposedIconSprite = composedSprite;
tower.ComposedIconKey = cacheKey;
return composedSprite;
}
private static bool TryGetComponents(
TowerItemData tower,
IReadOnlyDictionary<long, MuzzleCompItemData> muzzleMap,
IReadOnlyDictionary<long, BearingCompItemData> bearingMap,
IReadOnlyDictionary<long, BaseCompItemData> baseMap,
out MuzzleCompItemData muzzleComp,
out BearingCompItemData bearingComp,
out BaseCompItemData baseComp)
{
muzzleComp = null;
bearingComp = null;
baseComp = null;
if (tower == null || muzzleMap == null || bearingMap == null || baseMap == null)
{
return false;
}
return muzzleMap.TryGetValue(tower.MuzzleComponentInstanceId, out muzzleComp) &&
bearingMap.TryGetValue(tower.BearingComponentInstanceId, out bearingComp) &&
baseMap.TryGetValue(tower.BaseComponentInstanceId, out baseComp) &&
muzzleComp != null && bearingComp != null && baseComp != null;
}
private static void EnsureBaseSpritesRequested()
{
if (GameEntry.SpriteCache == null)
{
return;
}
if (!s_RequestedMuzzle)
{
s_RequestedMuzzle = true;
GameEntry.SpriteCache.GetSprite(MuzzleAssetName, sprite => { _muzzleSprite = sprite; });
}
if (!s_RequestedBearing)
{
s_RequestedBearing = true;
GameEntry.SpriteCache.GetSprite(BearingAssetName, sprite => { _bearingSprite = sprite; });
}
if (!s_RequestedBase)
{
s_RequestedBase = true;
GameEntry.SpriteCache.GetSprite(BaseAssetName, sprite => { _baseSprite = sprite; });
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a705ccdd02d2d3440934d4ab06228547
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,148 @@
using System;
using System.Globalization;
using UnityEngine;
namespace GeometryTD.CustomUtility
{
public static class TowerIconComposeUtility
{
public static Sprite Compose(
Sprite muzzleSprite,
Color muzzleColor,
Sprite bearingSprite,
Color bearingColor,
Sprite baseSprite,
Color baseColor)
{
if (muzzleSprite == null || bearingSprite == null || baseSprite == null)
{
return null;
}
Sprite referenceSprite = muzzleSprite ?? bearingSprite ?? baseSprite;
if (referenceSprite == null)
{
return null;
}
int width = Mathf.RoundToInt(referenceSprite.rect.width);
int height = Mathf.RoundToInt(referenceSprite.rect.height);
if (width <= 0 || height <= 0)
{
return null;
}
return ComposeWithRenderTexture(
muzzleSprite,
muzzleColor,
bearingSprite,
bearingColor,
baseSprite,
baseColor,
width,
height,
referenceSprite);
}
public static string BuildCacheKey(
long muzzleId,
long bearingId,
long baseId,
Color muzzleColor,
Color bearingColor,
Color baseColor)
{
return string.Format(
CultureInfo.InvariantCulture,
"v2|{0}|{1}|{2}|{3}|{4}|{5}",
muzzleId,
bearingId,
baseId,
ToColorKey(muzzleColor),
ToColorKey(bearingColor),
ToColorKey(baseColor));
}
private static Sprite ComposeWithRenderTexture(
Sprite muzzleSprite,
Color muzzleColor,
Sprite bearingSprite,
Color bearingColor,
Sprite baseSprite,
Color baseColor,
int width,
int height,
Sprite referenceSprite)
{
RenderTexture rt = RenderTexture.GetTemporary(width, height, 0, RenderTextureFormat.ARGB32);
RenderTexture previous = RenderTexture.active;
try
{
RenderTexture.active = rt;
GL.PushMatrix();
GL.LoadPixelMatrix(0f, width, 0f, height);
GL.Clear(true, true, Color.clear);
DrawSpriteToRenderTarget(baseSprite, baseColor, width, height);
DrawSpriteToRenderTarget(bearingSprite, bearingColor, width, height);
DrawSpriteToRenderTarget(muzzleSprite, muzzleColor, width, height);
GL.PopMatrix();
Texture2D composedTexture = new Texture2D(width, height, TextureFormat.RGBA32, false);
composedTexture.ReadPixels(new Rect(0f, 0f, width, height), 0, 0);
composedTexture.Apply(false, false);
return CreateSprite(composedTexture, referenceSprite);
}
catch (Exception)
{
return null;
}
finally
{
RenderTexture.active = previous;
RenderTexture.ReleaseTemporary(rt);
}
}
private static void DrawSpriteToRenderTarget(Sprite sprite, Color color, int width, int height)
{
if (sprite == null || sprite.texture == null)
{
return;
}
Rect textureRect = sprite.textureRect;
Texture texture = sprite.texture;
Rect uvRect = new Rect(
textureRect.x / texture.width,
textureRect.y / texture.height,
textureRect.width / texture.width,
textureRect.height / texture.height);
Graphics.DrawTexture(new Rect(0f, 0f, width, height), texture, uvRect, 0, 0, 0, 0, color);
}
private static Sprite CreateSprite(Texture2D texture, Sprite referenceSprite)
{
if (texture == null || referenceSprite == null)
{
return null;
}
float width = Mathf.Max(1f, referenceSprite.rect.width);
float height = Mathf.Max(1f, referenceSprite.rect.height);
Vector2 pivot = new Vector2(referenceSprite.pivot.x / width, referenceSprite.pivot.y / height);
return Sprite.Create(texture, new Rect(0f, 0f, texture.width, texture.height), pivot, referenceSprite.pixelsPerUnit);
}
private static string ToColorKey(Color color)
{
Color32 c = color;
return c.r.ToString("X2", CultureInfo.InvariantCulture) +
c.g.ToString("X2", CultureInfo.InvariantCulture) +
c.b.ToString("X2", CultureInfo.InvariantCulture) +
c.a.ToString("X2", CultureInfo.InvariantCulture);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d9256e2c4f74b7448afbdbac8f7cb26f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: