refactor & fix

- 将 `CombatScheduler` 的职责分散到 3 个类中
    - `PhaseLoopRuntime`:负责关卡各波次的调度
    - `CombatLoadSession`:负责地图与 UI 等资源的加载
    - `CombatEventBridge`:负责相关事件的处理
- 统一了战斗的命名为 `Combat`
- 调整了各个地图的坐标,使其位于摄像头中心
- 新增了 CombatInfoForm 展示战斗内的信息
- 补充了循环执行 LevelPhase,手动结束战斗的逻辑
This commit is contained in:
SepComet
2026-03-01 13:15:53 +08:00
parent 26dc1a5600
commit 92cca14503
99 changed files with 2418 additions and 243 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 34c97e1af6767d049b17b5c4ff75cb5a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,6 @@
namespace GeometryTD.UI
{
public class CombineAreaContext
{
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8b1bd513fdab4dc8b8b0a26fccbe44a2
timeCreated: 1772106303
@@ -0,0 +1,7 @@
namespace GeometryTD.UI
{
public class CompAreaContext
{
public RepoItemContext[] Items;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5c3c289363eb4ef0a591d4fcaf1ad293
timeCreated: 1772106302
@@ -0,0 +1,12 @@
using GeometryTD.UI;
namespace GeometryTD.UI
{
public class EventFormContext : UIContext
{
public int EventId;
public string Title;
public string Description;
public EventOptionItemContext[] OptionItems;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 95f3e80a51954c66b14e41365622d63f
timeCreated: 1772104202
@@ -0,0 +1,8 @@
namespace GeometryTD.UI
{
public class EventOptionItemContext
{
public int OptionIndex;
public string OptionText;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f56befc364564ac8b499f81ca48a165b
timeCreated: 1772104201
@@ -0,0 +1,6 @@
namespace GeometryTD.UI
{
public class MainFormContext : UIContext
{
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 50722ebad52b4f619bd33883598842ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,10 @@
using GeometryTD.UI;
namespace GeometryTD.UI
{
public class RepoFormContext : UIContext
{
public CombineAreaContext CombineAreaContext;
public CompAreaContext CompAreaContext;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bf3353a1d70c48f79fcbc64611e0085a
timeCreated: 1772106304
@@ -0,0 +1,11 @@
using GeometryTD.Definition;
namespace GeometryTD.UI
{
public class RepoItemContext
{
public string Title;
public long InstanceId;
public TowerCompSlotType ComponentSlotType;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3f20599444b448b1933d3b0cfb94ade2
timeCreated: 1772106301
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 043cb0d817552b549aeff457ac685a0b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,123 @@
using GeometryTD.Definition;
using GeometryTD.CustomEvent;
using GameFramework.Event;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class EventFormController : UIFormControllerCommonBase<EventFormContext, EventForm>
{
private EventFormUseCase m_UseCase;
protected override UIFormType UIFormTypeId => UIFormType.EventForm;
protected override void RefreshUI(EventForm form, EventFormContext context)
{
form.RefreshUI(context);
}
protected override void SubscribeCustomEvents()
{
GameEntry.Event.Subscribe(EventOptionItemSelectedEventArgs.EventId, OnEventOptionItemSelected);
}
protected override void UnsubscribeCustomEvents()
{
GameEntry.Event.Unsubscribe(EventOptionItemSelectedEventArgs.EventId, OnEventOptionItemSelected);
}
public override int? OpenUI(object userData = null)
{
if (userData is EventFormContext eventFormContext)
{
return OpenUIInternal(eventFormContext);
}
if (userData is EventFormRawData rawDataFromUserData)
{
return OpenUI(rawDataFromUserData);
}
if (userData != null)
{
Log.Warning("EventFormController.OpenUI() userData type is invalid.");
return null;
}
if (m_UseCase == null)
{
Log.Error("EventFormController.OpenUI() useCase is null.");
return null;
}
EventFormRawData rawData = m_UseCase.CreateInitialModel();
return OpenUI(rawData);
}
public int? OpenUI(EventFormRawData rawData)
{
EventFormContext context = BuildContext(rawData);
return OpenUIInternal(context);
}
public override void BindUseCase(IUIUseCase useCase)
{
if (!(useCase is EventFormUseCase eventFormUseCase))
{
Log.Error("EventFormController.BindUseCase() useCase is invalid.");
return;
}
m_UseCase = eventFormUseCase;
}
private static EventFormContext BuildContext(EventFormRawData rawData)
{
if (rawData?.EventItem == null)
{
return null;
}
EventOptionItemContext[] options = new EventOptionItemContext[4];
for (int i = 0; i < options.Length; i++)
{
string optionText = string.Empty;
if (rawData.EventItem.Options != null && i < rawData.EventItem.Options.Length &&
rawData.EventItem.Options[i] != null)
{
optionText = rawData.EventItem.Options[i].OptionText;
}
options[i] = new EventOptionItemContext
{
OptionIndex = i,
OptionText = optionText
};
}
return new EventFormContext
{
EventId = rawData.EventItem.Id,
Title = rawData.EventItem.Title,
Description = rawData.EventItem.Description,
OptionItems = options
};
}
private void OnEventOptionItemSelected(object sender, GameEventArgs e)
{
if (!(e is EventOptionItemSelectedEventArgs args))
{
return;
}
if (m_UseCase == null)
{
Log.Warning("EventFormController.OnOptionSelected() useCase is null.");
return;
}
m_UseCase.SelectOption(args.SelectedItemId);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 916da3490ed14b01ae064cbb37880bba
timeCreated: 1772104204
@@ -0,0 +1,79 @@
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using GameFramework.Event;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class MainFormController : UIFormControllerCommonBase<MainFormContext, MainForm>
{
protected override UIFormType UIFormTypeId => UIFormType.MainForm;
protected override void RefreshUI(MainForm form, MainFormContext context)
{
form.RefreshUI(context);
}
protected override void SubscribeCustomEvents()
{
GameEntry.Event.Subscribe(ReturnButtonClickedEventArgs.EventId, OnReturnButtonClicked);
GameEntry.Event.Subscribe(RepoButtonClickedEventArgs.EventId, OnRepoButtonClicked);
}
protected override void UnsubscribeCustomEvents()
{
GameEntry.Event.Unsubscribe(ReturnButtonClickedEventArgs.EventId, OnReturnButtonClicked);
GameEntry.Event.Unsubscribe(RepoButtonClickedEventArgs.EventId, OnRepoButtonClicked);
}
public override int? OpenUI(object userData = null)
{
if (userData is MainFormContext context)
{
return OpenUIInternal(context);
}
if (userData != null)
{
Log.Warning("MainFormController.OpenUI() userData type is invalid.");
return null;
}
return OpenUIInternal(BuildDefaultContext());
}
public override void BindUseCase(IUIUseCase useCase)
{
if (useCase != null)
{
Log.Warning("MainFormController does not use a use case.");
}
}
private static MainFormContext BuildDefaultContext()
{
return new MainFormContext();
}
private void OnReturnButtonClicked(object sender, GameEventArgs e)
{
if (!(sender is MainForm) || !(e is ReturnButtonClickedEventArgs))
{
return;
}
//TODO:游戏 UI 返回菜单 UI
//GameEntry.UIRouter.CloseUI(UIFormType.MainForm);
}
private void OnRepoButtonClicked(object sender, GameEventArgs e)
{
if (!(sender is MainForm) || !(e is RepoButtonClickedEventArgs))
{
return;
}
GameEntry.UIRouter.OpenUI(UIFormType.RepoForm);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 88a17d9010c944a8a0123f31ed09f7d9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,198 @@
using System.Collections.Generic;
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using GameFramework.Event;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class RepoFormController : UIFormControllerCommonBase<RepoFormContext, RepoForm>
{
private RepoFormUseCase _useCase;
private readonly Dictionary<long, RepoItemContext> _itemContextMap = new Dictionary<long, RepoItemContext>();
protected override UIFormType UIFormTypeId => UIFormType.RepoForm;
protected override void RefreshUI(RepoForm form, RepoFormContext context)
{
form.RefreshUI(context);
}
protected override void SubscribeCustomEvents()
{
GameEntry.Event.Subscribe(RepoItemSelectedEventArgs.EventId, OnRepoItemSelected);
GameEntry.Event.Subscribe(CombineSlotClickedEventArgs.EventId, OnCombineSlotClicked);
}
protected override void UnsubscribeCustomEvents()
{
GameEntry.Event.Unsubscribe(RepoItemSelectedEventArgs.EventId, OnRepoItemSelected);
GameEntry.Event.Unsubscribe(CombineSlotClickedEventArgs.EventId, OnCombineSlotClicked);
}
public override int? OpenUI(object userData = null)
{
if (userData is RepoFormContext repoFormContext)
{
return OpenUIInternal(repoFormContext);
}
if (userData is RepoFormRawData rawDataFromUserData)
{
return OpenUI(rawDataFromUserData);
}
if (userData != null)
{
Log.Warning("RepoFormController.OpenUI() userData type is invalid.");
return null;
}
if (_useCase == null)
{
Log.Error("RepoFormController.OpenUI() useCase is null.");
return null;
}
RepoFormRawData rawData = _useCase.CreateInitialModel();
return OpenUI(rawData);
}
public int? OpenUI(RepoFormRawData rawData)
{
RepoFormContext context = BuildContext(rawData);
return OpenUIInternal(context);
}
public override void BindUseCase(IUIUseCase useCase)
{
if (!(useCase is RepoFormUseCase repoFormUseCase))
{
Log.Error("RepoFormController.BindUseCase() useCase is invalid.");
return;
}
_useCase = repoFormUseCase;
}
private RepoFormContext BuildContext(RepoFormRawData rawData)
{
_itemContextMap.Clear();
if (rawData?.Inventory == null)
{
return null;
}
List<RepoItemContext> items = new List<RepoItemContext>();
if (rawData.Inventory.Towers != null)
{
foreach (var tower in rawData.Inventory.Towers)
{
AddItemContext(items, new RepoItemContext
{
Title = $"[Tower] {tower.Name} ({tower.Rarity})",
InstanceId = tower.InstanceId,
ComponentSlotType = TowerCompSlotType.None
});
}
}
if (rawData.Inventory.MuzzleComponents != null)
{
foreach (var item in rawData.Inventory.MuzzleComponents)
{
AddItemContext(items, new RepoItemContext
{
Title = $"[Muzzle] {item.Name} ({item.Rarity})",
InstanceId = item.InstanceId,
ComponentSlotType = TowerCompSlotType.Muzzle
});
}
}
if (rawData.Inventory.BearingComponents != null)
{
foreach (var item in rawData.Inventory.BearingComponents)
{
AddItemContext(items, new RepoItemContext
{
Title = $"[Bearing] {item.Name} ({item.Rarity})",
InstanceId = item.InstanceId,
ComponentSlotType = TowerCompSlotType.Bearing
});
}
}
if (rawData.Inventory.BaseComponents != null)
{
foreach (var item in rawData.Inventory.BaseComponents)
{
AddItemContext(items, new RepoItemContext
{
Title = $"[Base] {item.Name} ({item.Rarity})",
InstanceId = item.InstanceId,
ComponentSlotType = TowerCompSlotType.Base
});
}
}
return new RepoFormContext
{
CombineAreaContext = new CombineAreaContext(),
CompAreaContext = new CompAreaContext
{
Items = items.ToArray()
}
};
}
private void AddItemContext(List<RepoItemContext> items, RepoItemContext itemContext)
{
if (itemContext == null)
{
return;
}
items.Add(itemContext);
_itemContextMap[itemContext.InstanceId] = itemContext;
}
private void OnRepoItemSelected(object sender, GameEventArgs e)
{
if (!(e is RepoItemSelectedEventArgs args))
{
return;
}
if (Form == null || !_itemContextMap.TryGetValue(args.ItemId, out RepoItemContext itemContext))
{
return;
}
bool assigned = Form.TryAssignItemToCombineArea(itemContext);
if (assigned)
{
Form.SetRepoItemSelected(args.ItemId, true);
}
}
private void OnCombineSlotClicked(object sender, GameEventArgs e)
{
if (!(e is CombineSlotClickedEventArgs args))
{
return;
}
if (Form == null)
{
return;
}
if (Form.TryClearCombineSlot(args.SlotIndex, out long removedItemId) && removedItemId > 0)
{
Form.SetRepoItemSelected(removedItemId, false);
}
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6c2172dd10a54c2dbbf6d01632ba6a8b
timeCreated: 1772106306
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a0e002d6d3ee84d46859c7933b285513
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
using GeometryTD.Definition;
namespace GeometryTD.UI
{
public class EventFormRawData
{
public EventItem EventItem;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 463e60ff83314069b83158ccf64cddef
timeCreated: 1772104200
@@ -0,0 +1,9 @@
using GeometryTD.Definition;
namespace GeometryTD.UI
{
public class RepoFormRawData
{
public BackpackInventoryData Inventory;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: efed1063c6334c90b09475548c2b560e
timeCreated: 1772106300
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 90fb2ec88edcd2942abcf17a4e206f3e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,48 @@
using GeometryTD.CustomComponent;
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class EventFormUseCase : IUIUseCase
{
private EventItem _currentEvent;
public void SetCurrentEvent(EventItem eventItem)
{
_currentEvent = eventItem;
}
public EventFormRawData CreateInitialModel()
{
if (_currentEvent == null)
{
return null;
}
return new EventFormRawData
{
EventItem = _currentEvent
};
}
public EventOption SelectOption(int optionIndex)
{
if (_currentEvent == null || _currentEvent.Options == null)
{
return null;
}
if (optionIndex < 0 || optionIndex >= _currentEvent.Options.Length)
{
Log.Warning("EventFormUseCase.SelectOption() option index is invalid: {0}", optionIndex);
return null;
}
// TODO: 执行 Requirements 校验、CostEffects/RewardEffects 结算与后续节点推进。
GameEntry.EventNode.EndEvent();
return _currentEvent.Options[optionIndex];
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 80d6d6100109462ea498781e9e70201d
timeCreated: 1772104203
@@ -0,0 +1,115 @@
using System.Collections.Generic;
using GeometryTD.Definition;
namespace GeometryTD.UI
{
public class RepoFormUseCase : IUIUseCase
{
public RepoFormRawData CreateInitialModel()
{
BackpackInventoryData sample = BuildSampleInventory();
return new RepoFormRawData
{
Inventory = sample
};
}
private static BackpackInventoryData BuildSampleInventory()
{
BackpackInventoryData inventory = new BackpackInventoryData
{
Gold = 500
};
MuzzleCompItemData muzzle = new MuzzleCompItemData
{
InstanceId = 10001,
ConfigId = 1,
Name = "元素枪口",
Rarity = RarityType.Green,
Endurance = 90f,
AttackDamage = new[] { 20, 30, 40, 50, 80 },
DamageRandomRate = 0.05f,
AttackMethodType = AttackMethodType.NormalBullet,
Constraint = string.Empty,
Tags = new[] { TagType.Fire }
};
inventory.MuzzleComponents.Add(muzzle);
BearingCompItemData bearing = new BearingCompItemData
{
InstanceId = 20001,
ConfigId = 1,
Name = "元素轴承",
Rarity = RarityType.Green,
Endurance = 95f,
RotateSpeed = new[] { 10f, 12f, 13f, 14f, 15f },
AttackRange = new[] { 2f, 2f, 2f, 2f, 2f },
Constraint = string.Empty,
Tags = new[] { TagType.Fire }
};
inventory.BearingComponents.Add(bearing);
BaseCompItemData baseComp = new BaseCompItemData
{
InstanceId = 30001,
ConfigId = 1,
Name = "元素底座",
Rarity = RarityType.Green,
Endurance = 88f,
AttackSpeed = new[] { 2f, 1.5f, 1f, 0.8f, 0.7f },
AttackPropertyType = AttackPropertyType.Fire,
Constraint = string.Empty,
Tags = new[] { TagType.Fire }
};
inventory.BaseComponents.Add(baseComp);
DefenseTowerItemData tower = new DefenseTowerItemData
{
InstanceId = 90001,
Name = "测试防御塔-A",
Rarity = RarityType.Green,
Endurance = 92f,
MuzzleComponentInstanceId = muzzle.InstanceId,
BearingComponentInstanceId = bearing.InstanceId,
BaseComponentInstanceId = baseComp.InstanceId,
Stats = new DefenseTowerStatsData
{
AttackDamage = 30,
DamageRandomRate = 0.05f,
RotateSpeed = 12f,
AttackRange = 2f,
AttackSpeed = 1.5f,
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Fire,
Tags = new[] { TagType.Fire, TagType.BurnSpread }
}
};
inventory.Towers.Add(tower);
inventory.Towers.Add(new DefenseTowerItemData
{
InstanceId = 90002,
Name = "测试防御塔-B",
Rarity = RarityType.Blue,
Endurance = 100f,
MuzzleComponentInstanceId = 0,
BearingComponentInstanceId = 0,
BaseComponentInstanceId = 0,
Stats = new DefenseTowerStatsData
{
AttackDamage = 50,
DamageRandomRate = 0.03f,
RotateSpeed = 20f,
AttackRange = 4f,
AttackSpeed = 1f,
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Physics,
Tags = new[] { TagType.Pierce }
}
});
return inventory;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ad54433a032e489f82217d85992a8159
timeCreated: 1772106305
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1f5bf29ff4f1c404cb5b42fb54a8e0b4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,102 @@
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.UI
{
public class CombineArea : MonoBehaviour
{
[SerializeField] private CombineSlotItem[] _slots;
public void OnInit(CombineAreaContext context)
{
if (_slots == null)
{
return;
}
for (int i = 0; i < _slots.Length; i++)
{
if (_slots[i] != null)
{
_slots[i].OnInit(i);
}
}
}
public void OnReset()
{
if (_slots == null)
{
return;
}
foreach (var slot in _slots)
{
if (slot != null)
{
slot.OnReset();
}
}
}
public void OnCombineClick()
{
// TODO: Combine logic.
}
public bool TryAssignItem(RepoItemContext itemContext)
{
if (itemContext == null || itemContext.ComponentSlotType == TowerCompSlotType.None)
{
return false;
}
CombineSlotItem targetSlot = FindSlot(itemContext.ComponentSlotType);
if (targetSlot == null || targetSlot.HasItem)
{
return false;
}
targetSlot.BindItem(itemContext);
return true;
}
public bool TryClearSlot(int slotIndex, out long removedItemId)
{
removedItemId = 0;
if (_slots == null || slotIndex < 0 || slotIndex >= _slots.Length)
{
return false;
}
CombineSlotItem slot = _slots[slotIndex];
if (slot == null || !slot.HasItem)
{
return false;
}
removedItemId = slot.BoundItemId;
slot.ClearSlot();
return true;
}
private CombineSlotItem FindSlot(TowerCompSlotType slotType)
{
if (_slots == null)
{
return null;
}
for (int i = 0; i < _slots.Length; i++)
{
CombineSlotItem slot = _slots[i];
if (slot != null && slot.AcceptSlotType == slotType)
{
return slot;
}
}
return null;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ce74fc07cb1849b89ad101683cc63546
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,82 @@
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using TMPro;
using UnityEngine;
namespace GeometryTD.UI
{
public class CombineSlotItem : MonoBehaviour
{
[SerializeField] private TowerCompSlotType _acceptSlotType = TowerCompSlotType.None;
[SerializeField] private TMP_Text _titleText;
[SerializeField] private CommonButton _button;
private string _defaultTitle = string.Empty;
private long _boundItemId;
private int _slotIndex = -1;
public TowerCompSlotType AcceptSlotType => _acceptSlotType;
public bool HasItem => _boundItemId > 0;
public int SlotIndex => _slotIndex;
public long BoundItemId => _boundItemId;
private void Awake()
{
if (_titleText != null)
{
_defaultTitle = _titleText.text;
}
}
public void OnInit(int slotIndex)
{
_slotIndex = slotIndex;
ClearSlot();
}
public void BindItem(RepoItemContext itemContext)
{
_boundItemId = itemContext?.InstanceId ?? 0;
_button.Interactive = true;
if (_titleText != null)
{
_titleText.text = itemContext?.Title ?? string.Empty;
}
}
public void ClearSlot()
{
_boundItemId = 0;
_button.Interactive = false;
if (_titleText != null)
{
_titleText.text = _defaultTitle;
}
}
public void OnReset()
{
_slotIndex = -1;
ClearSlot();
}
public void OnClick()
{
if (_slotIndex < 0)
{
return;
}
GameEntry.Event.Fire(this, CombineSlotClickedEventArgs.Create(_slotIndex));
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b172c08f0d9b46d3b2cb108a1cc67377
timeCreated: 1772107100
@@ -0,0 +1,126 @@
using System.Collections.Generic;
using GameFramework.ObjectPool;
using GeometryTD.PoolObjectBase;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class CompArea : MonoBehaviour
{
[SerializeField] private Transform m_Content;
[SerializeField] private RepoItem m_ItemTemplate;
[SerializeField] private int m_InstancePoolCapacity = 64;
private readonly List<RepoItem> m_ActiveItems = new List<RepoItem>();
private readonly Dictionary<long, RepoItem> m_ItemMap = new Dictionary<long, RepoItem>();
private IObjectPool<RepoItemObject> m_ItemPool;
private string m_PoolName;
public void OnInit(CompAreaContext context)
{
if (context == null)
{
OnReset();
return;
}
EnsurePool();
OnReset();
if (context.Items == null || context.Items.Length == 0)
{
return;
}
for (int i = 0; i < context.Items.Length; i++)
{
RepoItem item = CreateOrSpawnItem();
if (item == null)
{
continue;
}
item.gameObject.SetActive(true);
item.OnInit(context.Items[i]);
m_ActiveItems.Add(item);
m_ItemMap[context.Items[i].InstanceId] = item;
}
}
public void OnReset()
{
for (int i = m_ActiveItems.Count - 1; i >= 0; i--)
{
RepoItem item = m_ActiveItems[i];
if (item == null)
{
continue;
}
item.OnReset();
item.gameObject.SetActive(false);
m_ItemPool?.Unspawn(item);
}
m_ActiveItems.Clear();
m_ItemMap.Clear();
}
private void OnDestroy()
{
OnReset();
m_ItemPool?.ReleaseAllUnused();
}
private void EnsurePool()
{
if (m_ItemPool != null)
{
return;
}
m_PoolName = $"RepoItemPool_{GetInstanceID()}";
m_ItemPool = GameEntry.ObjectPool.CreateSingleSpawnObjectPool<RepoItemObject>(m_PoolName, m_InstancePoolCapacity);
}
private RepoItem CreateOrSpawnItem()
{
if (m_ItemPool == null)
{
return null;
}
RepoItemObject itemObject = m_ItemPool.Spawn();
RepoItem item = itemObject != null ? (RepoItem)itemObject.Target : null;
if (item == null)
{
if (m_ItemTemplate == null)
{
Log.Warning("CompArea requires an item template.");
return null;
}
item = Instantiate(m_ItemTemplate);
m_ItemPool.Register(RepoItemObject.Create(item), true);
}
if (m_Content != null)
{
item.transform.SetParent(m_Content, false);
}
return item;
}
public void SetItemSelected(long itemId, bool isSelected)
{
if (!m_ItemMap.TryGetValue(itemId, out RepoItem item))
{
return;
}
item.SetSelected(isSelected);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f3b1aede0e52479995cf71ba52a69d63
timeCreated: 1772106310
@@ -0,0 +1,82 @@
using TMPro;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class EventForm : UGuiForm
{
[SerializeField] private TMP_Text _titleText;
[SerializeField] private TMP_Text _descriptionText;
[SerializeField] private EventOptionItem[] _optionItems;
private EventFormContext _context;
public void RefreshUI(EventFormContext context)
{
_context = context;
if (_titleText != null)
{
_titleText.text = context?.Title ?? string.Empty;
}
if (_descriptionText != null)
{
_descriptionText.text = context?.Description ?? string.Empty;
}
for (int i = 0; i < _optionItems.Length; i++)
{
EventOptionItemContext optionContext = null;
if (context?.OptionItems != null && i < context.OptionItems.Length)
{
optionContext = context.OptionItems[i];
}
if (_optionItems[i] != null)
{
_optionItems[i].OnInit(optionContext);
}
}
}
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
if (userData is EventFormContext context)
{
RefreshUI(context);
return;
}
Log.Warning("EventForm requires EventFormContext as userData.");
}
protected override void OnClose(bool isShutdown, object userData)
{
_context = null;
if (_titleText != null)
{
_titleText.text = string.Empty;
}
if (_descriptionText != null)
{
_descriptionText.text = string.Empty;
}
foreach (var item in _optionItems)
{
if (item != null)
{
item.OnReset();
}
}
base.OnClose(isShutdown, userData);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 81064d72b60548b6b54d07d94d20b519
timeCreated: 1772104206
@@ -0,0 +1,50 @@
using GeometryTD.CustomEvent;
using TMPro;
using UnityEngine;
namespace GeometryTD.UI
{
public class EventOptionItem : MonoBehaviour
{
[SerializeField] private TMP_Text _optionText;
private int _optionIndex;
public void OnInit(EventOptionItemContext context)
{
if (context == null)
{
OnReset();
gameObject.SetActive(false);
return;
}
_optionIndex = context.OptionIndex;
if (_optionText != null)
{
_optionText.text = context.OptionText ?? string.Empty;
}
gameObject.SetActive(!string.IsNullOrEmpty(context.OptionText));
}
public void OnReset()
{
_optionIndex = -1;
if (_optionText != null)
{
_optionText.text = string.Empty;
}
}
public void OnClick()
{
if (_optionIndex < 0)
{
return;
}
GameEntry.Event.Fire(this, EventOptionItemSelectedEventArgs.Create(_optionIndex));
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 86cf465d14c649a58473c19ae946d220
timeCreated: 1772104205
@@ -0,0 +1,49 @@
using GeometryTD.CustomEvent;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class MainForm : UGuiForm
{
private MainFormContext _context;
public void RefreshUI(MainFormContext context)
{
_context = context;
if (_context == null)
{
return;
}
}
public void OnReturnButtonClick()
{
GameEntry.Event.Fire(this, ReturnButtonClickedEventArgs.Create());
}
public void OnRepoButtonClick()
{
GameEntry.Event.Fire(this, RepoButtonClickedEventArgs.Create());
}
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
if (userData is MainFormContext context)
{
RefreshUI(context);
return;
}
Log.Warning("MainForm requires MainFormContext as userData.");
}
protected override void OnClose(bool isShutdown, object userData)
{
_context = null;
base.OnClose(isShutdown, userData);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 838d81ed05914e248972f7ad7df7787d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,67 @@
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class RepoForm : UGuiForm
{
[SerializeField] private CombineArea _combineArea;
[SerializeField] private CompArea _compArea;
public void RefreshUI(RepoFormContext context)
{
if (context == null)
{
return;
}
_combineArea?.OnInit(context.CombineAreaContext);
_compArea?.OnInit(context.CompAreaContext);
}
public bool TryAssignItemToCombineArea(RepoItemContext itemContext)
{
if (_combineArea == null)
{
return false;
}
return _combineArea.TryAssignItem(itemContext);
}
public bool TryClearCombineSlot(int slotIndex, out long removedItemId)
{
removedItemId = 0;
if (_combineArea == null)
{
return false;
}
return _combineArea.TryClearSlot(slotIndex, out removedItemId);
}
public void SetRepoItemSelected(long itemId, bool isSelected)
{
_compArea?.SetItemSelected(itemId, isSelected);
}
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
if (userData is RepoFormContext context)
{
RefreshUI(context);
return;
}
Log.Warning("RepoForm requires RepoFormContext as userData.");
}
protected override void OnClose(bool isShutdown, object userData)
{
_combineArea?.OnReset();
_compArea?.OnReset();
base.OnClose(isShutdown, userData);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e10fdbb413c14f5eb749888590f56ff7
timeCreated: 1772106307
@@ -0,0 +1,69 @@
using GeometryTD.CustomEvent;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class RepoItem : MonoBehaviour
{
[SerializeField] private TMP_Text _titleText;
[SerializeField] private Image _highlightImage;
private static readonly Color NormalColor = new Color32(40, 40, 40, 180);
private static readonly Color SelectedColor = new Color32(255, 216, 102, 255);
private RepoItemContext _context;
public void OnInit(RepoItemContext context)
{
if (context == null)
{
OnReset();
return;
}
_context = context;
if (_titleText != null)
{
_titleText.text = context.Title ?? string.Empty;
}
SetSelected(false);
}
public void OnReset()
{
_context = null;
if (_titleText != null)
{
_titleText.text = string.Empty;
}
SetSelected(false);
}
public void SetSelected(bool isSelected)
{
if (_highlightImage == null)
{
return;
}
_highlightImage.color = isSelected ? SelectedColor : NormalColor;
}
public void OnClick()
{
if (_context == null)
{
return;
}
GameEntry.Event.Fire(this, RepoItemSelectedEventArgs.Create(_context.InstanceId));
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ba332072a148499b8f6ec886c661c00f
timeCreated: 1772106309