This commit is contained in:
2026-04-30 19:33:16 +08:00
commit fb2252f688
556 changed files with 69721 additions and 0 deletions
@@ -0,0 +1,6 @@
namespace GeometryTD.UI
{
public class CombineAreaContext
{
}
}
@@ -0,0 +1,8 @@
namespace GeometryTD.UI
{
public class CompAreaContext
{
public RepoItemContext[] ComponentItems;
public TowerRepoItemContext[] TowerItems;
}
}
@@ -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,10 @@
namespace GeometryTD.UI
{
public class EventOptionItemContext
{
public int OptionIndex;
public string OptionText;
public bool IsSelectable;
public string BlockedReason;
}
}
@@ -0,0 +1,6 @@
namespace GeometryTD.UI
{
public class MainFormContext : UIContext
{
}
}
@@ -0,0 +1,16 @@
using GeometryTD.Procedure;
namespace GeometryTD.UI
{
public sealed class NodeItemContext : UIContext
{
public int NodeId;
public int SequenceIndex;
public RunNodeType NodeType;
public bool IsLocked;
public bool IsCurrent;
public bool IsCompleted;
public bool IsException;
public bool CanClick;
}
}
@@ -0,0 +1,11 @@
namespace GeometryTD.UI
{
public sealed class NodeMapFormContext : UIContext
{
public string Title;
public string ProgressText;
public string CurrentNodeText;
public bool IsRunCompleted;
public NodeItemContext[] NodeItems;
}
}
@@ -0,0 +1,8 @@
namespace GeometryTD.UI
{
public class ParticipantAreaContext : UIContext
{
public TowerRepoItemContext[] TowerItems;
public int MaxCount;
}
}
@@ -0,0 +1,16 @@
namespace GeometryTD.UI
{
public class RepoFormContext : UIContext
{
public string GoldText;
public RepoFormState State;
public bool ShowSellModeButton;
public string SellModeButtonText;
public bool ShowCombineArea;
public bool ShowSellArea;
public CombineAreaContext CombineAreaContext;
public SellAreaContext SellAreaContext;
public CompAreaContext CompAreaContext;
public ParticipantAreaContext ParticipantAreaContext;
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace GeometryTD.UI
{
public enum RepoFormState
{
Assemble = 0,
Sell = 1
}
}
@@ -0,0 +1,8 @@
namespace GeometryTD.UI
{
public enum RepoItemClickActionType : byte
{
OpenDetail = 0,
RemoveParticipant = 1
}
}
@@ -0,0 +1,19 @@
using System;
using GeometryTD.Definition;
namespace GeometryTD.UI
{
[Serializable]
public class RepoItemContext
{
public long InstanceId;
public bool CanDrag;
public float EnduranceRate01;
public RepoItemClickActionType ClickActionType;
public TowerCompSlotType ComponentSlotType;
public bool IsSellMode;
public bool IsSellable = true;
public bool IsSellSelected;
public IconAreaContext IconAreaContext;
}
}
@@ -0,0 +1,10 @@
namespace GeometryTD.UI
{
public class SellAreaContext
{
public string TotalPriceText;
public bool CanConfirmSell;
public RepoItemContext[] ComponentItems;
public TowerRepoItemContext[] TowerItems;
}
}
@@ -0,0 +1,9 @@
using System;
namespace GeometryTD.UI
{
[Serializable]
public class TowerRepoItemContext : RepoItemContext
{
}
}
@@ -0,0 +1,41 @@
using GeometryTD.Definition;
using GeometryTD.Procedure;
namespace GeometryTD.UI
{
public sealed partial class NodeMapFormController
{
private static NodeMapFormContext BuildContext(NodeMapFormRawData rawData)
{
NodeItemContext[] nodeItemContexts = System.Array.Empty<NodeItemContext>();
if (rawData?.Nodes != null && rawData.Nodes.Count > 0)
{
nodeItemContexts = new NodeItemContext[rawData.Nodes.Count];
for (int i = 0; i < rawData.Nodes.Count; i++)
{
NodeMapNodeRawData node = rawData.Nodes[i];
nodeItemContexts[i] = new NodeItemContext
{
NodeId = node?.NodeId ?? 0,
SequenceIndex = node?.SequenceIndex ?? i,
NodeType = node?.NodeType ?? RunNodeType.None,
IsLocked = node != null && node.Status == RunNodeStatus.Locked,
IsCurrent = node != null && node.IsCurrentNode,
IsCompleted = node != null && node.Status == RunNodeStatus.Completed,
IsException = node != null && node.Status == RunNodeStatus.Exception,
CanClick = node != null && node.CanEnter
};
}
}
return new NodeMapFormContext
{
Title = rawData?.Title ?? "节点地图",
ProgressText = rawData?.ProgressText ?? "0 / 0",
CurrentNodeText = rawData?.CurrentNodeText ?? "当前节点: 无",
IsRunCompleted = rawData != null && rawData.IsRunCompleted,
NodeItems = nodeItemContexts
};
}
}
}
@@ -0,0 +1,497 @@
using System;
using System.Collections.Generic;
using GeometryTD.CustomUtility;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.UI
{
public partial class RepoFormController
{
private RepoFormContext BuildContext(RepoFormRawData rawData)
{
_itemClickActionMap.Clear();
_itemDescSeedMap.Clear();
_compAreaTowerIds.Clear();
if (rawData?.Inventory == null)
{
_participantTowerIds.Clear();
return null;
}
Dictionary<long, MuzzleCompItemData> muzzleMap = BuildComponentMap(rawData.Inventory.MuzzleComponents);
Dictionary<long, BearingCompItemData> bearingMap = BuildComponentMap(rawData.Inventory.BearingComponents);
Dictionary<long, BaseCompItemData> baseMap = BuildComponentMap(rawData.Inventory.BaseComponents);
Dictionary<long, TowerItemData> towerMap = BuildTowerMap(rawData.Inventory.Towers);
HashSet<long> selectedSellItemIds = BuildSelectedSellItemSet(rawData.SelectedSellItemIds);
bool isSellState = rawData.State == RepoFormState.Sell;
List<RepoItemContext> componentItems = new List<RepoItemContext>();
List<TowerRepoItemContext> towerItems = new List<TowerRepoItemContext>();
List<RepoItemContext> selectedSellComponentItems = new List<RepoItemContext>();
List<TowerRepoItemContext> selectedSellTowerItems = new List<TowerRepoItemContext>();
if (rawData.Inventory.Towers != null)
{
foreach (TowerItemData tower in rawData.Inventory.Towers)
{
if (tower == null)
{
continue;
}
bool isParticipantTower = rawData.Inventory.ParticipantTowerInstanceIds != null &&
rawData.Inventory.ParticipantTowerInstanceIds.Contains(tower.InstanceId);
bool isSellSelected = isSellState && selectedSellItemIds.Contains(tower.InstanceId);
TowerRepoItemContext towerContext = BuildTowerRepoItemContext(
tower,
muzzleMap,
bearingMap,
baseMap,
RepoItemClickActionType.OpenDetail,
!isSellState,
isSellState,
!isParticipantTower,
isSellSelected,
false);
AddTowerItemContext(towerItems, towerContext);
if (isSellSelected)
{
selectedSellTowerItems.Add(towerContext);
}
AddItemDescSeed(
tower.InstanceId,
tower.Name,
"Tower",
ItemDescUtility.BuildTowerDesc(tower, muzzleMap, bearingMap, baseMap) ?? string.Empty,
tower.Stats?.Tags,
tower.Stats?.TagRuntimes);
}
}
if (rawData.Inventory.MuzzleComponents != null)
{
foreach (MuzzleCompItemData item in rawData.Inventory.MuzzleComponents)
{
if (item == null || item.IsAssembledIntoTower)
{
continue;
}
RepoItemContext componentContext = BuildSellableComponentContext(
item,
TowerCompSlotType.Muzzle,
isSellState,
selectedSellItemIds.Contains(item.InstanceId));
AddComponentItemContext(componentItems, componentContext);
if (componentContext.IsSellSelected)
{
selectedSellComponentItems.Add(componentContext);
}
AddItemDescSeed(
item.InstanceId,
item.Name,
BuildComponentTypeText(item.SlotType),
ItemDescUtility.BuildMuzzleDesc(item) ?? string.Empty,
item.Tags,
null);
}
}
if (rawData.Inventory.BearingComponents != null)
{
foreach (BearingCompItemData item in rawData.Inventory.BearingComponents)
{
if (item == null || item.IsAssembledIntoTower)
{
continue;
}
RepoItemContext componentContext = BuildSellableComponentContext(
item,
TowerCompSlotType.Bearing,
isSellState,
selectedSellItemIds.Contains(item.InstanceId));
AddComponentItemContext(componentItems, componentContext);
if (componentContext.IsSellSelected)
{
selectedSellComponentItems.Add(componentContext);
}
AddItemDescSeed(
item.InstanceId,
item.Name,
BuildComponentTypeText(item.SlotType),
ItemDescUtility.BuildBearingDesc(item) ?? string.Empty,
item.Tags,
null);
}
}
if (rawData.Inventory.BaseComponents != null)
{
foreach (BaseCompItemData item in rawData.Inventory.BaseComponents)
{
if (item == null || item.IsAssembledIntoTower)
{
continue;
}
RepoItemContext componentContext = BuildSellableComponentContext(
item,
TowerCompSlotType.Base,
isSellState,
selectedSellItemIds.Contains(item.InstanceId));
AddComponentItemContext(componentItems, componentContext);
if (componentContext.IsSellSelected)
{
selectedSellComponentItems.Add(componentContext);
}
AddItemDescSeed(
item.InstanceId,
item.Name,
BuildComponentTypeText(item.SlotType),
ItemDescUtility.BuildBaseDesc(item) ?? string.Empty,
item.Tags,
null);
}
}
ParticipantAreaContext participantAreaContext =
BuildParticipantAreaContext(rawData.Inventory, towerMap, muzzleMap, bearingMap, baseMap);
return new RepoFormContext
{
GoldText = $"金币: {rawData.Inventory.Gold}",
State = rawData.State,
ShowSellModeButton = rawData.State == RepoFormState.Assemble,
SellModeButtonText = "出售模式",
ShowCombineArea = rawData.State == RepoFormState.Assemble,
ShowSellArea = rawData.State == RepoFormState.Sell,
CombineAreaContext = new CombineAreaContext(),
SellAreaContext = new SellAreaContext
{
TotalPriceText = $"总价值: {rawData.SelectedSellTotalPrice}",
CanConfirmSell = rawData.State == RepoFormState.Sell && rawData.SelectedSellItemCount > 0,
ComponentItems = selectedSellComponentItems.ToArray(),
TowerItems = selectedSellTowerItems.ToArray()
},
CompAreaContext = new CompAreaContext
{
ComponentItems = componentItems.ToArray(),
TowerItems = towerItems.ToArray()
},
ParticipantAreaContext = participantAreaContext
};
}
private static RepoItemContext BuildSellableComponentContext(
TowerCompItemData item,
TowerCompSlotType slotType,
bool isSellState,
bool isSellSelected)
{
return new RepoItemContext
{
InstanceId = item.InstanceId,
CanDrag = !isSellState,
EnduranceRate01 = ItemDescUtility.ResolveComponentEnduranceRate(item),
ClickActionType = RepoItemClickActionType.OpenDetail,
ComponentSlotType = slotType,
IsSellMode = isSellState,
IsSellable = true,
IsSellSelected = isSellState && isSellSelected,
IconAreaContext = BuildIconAreaContext(item)
};
}
private void AddComponentItemContext(List<RepoItemContext> items, RepoItemContext itemContext)
{
if (itemContext == null)
{
return;
}
items.Add(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,
TagRuntimeData[] tagRuntimes)
{
if (itemId <= 0)
{
return;
}
_itemDescSeedMap[itemId] = new ItemDescSeed
{
Title = string.IsNullOrWhiteSpace(title) ? $"Item {itemId}" : title,
TypeText = typeText ?? string.Empty,
Description = description ?? string.Empty,
Tags = tags,
TagRuntimes = tagRuntimes
};
}
private static string BuildComponentTypeText(TowerCompSlotType slotType)
{
return slotType switch
{
TowerCompSlotType.Muzzle => "Muzzle Component",
TowerCompSlotType.Bearing => "Bearing Component",
TowerCompSlotType.Base => "Base Component",
TowerCompSlotType.Accessory => "Accessory",
_ => "Component"
};
}
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;
}
foreach (TComp item in items)
{
if (item == null || item.InstanceId <= 0)
{
continue;
}
map[item.InstanceId] = item;
}
return map;
}
private static HashSet<long> BuildSelectedSellItemSet(IReadOnlyList<long> selectedSellItemIds)
{
HashSet<long> selectedIds = new HashSet<long>();
if (selectedSellItemIds == null)
{
return selectedIds;
}
for (int i = 0; i < selectedSellItemIds.Count; i++)
{
long itemId = selectedSellItemIds[i];
if (itemId > 0)
{
selectedIds.Add(itemId);
}
}
return selectedIds;
}
private static Dictionary<long, TowerItemData> BuildTowerMap(IReadOnlyList<TowerItemData> towers)
{
Dictionary<long, TowerItemData> map = new Dictionary<long, TowerItemData>();
if (towers == null)
{
return map;
}
foreach (TowerItemData tower in towers)
{
if (tower == null || tower.InstanceId <= 0)
{
continue;
}
map[tower.InstanceId] = tower;
}
return map;
}
private ParticipantAreaContext BuildParticipantAreaContext(
BackpackInventoryData inventory,
IReadOnlyDictionary<long, TowerItemData> towerMap,
IReadOnlyDictionary<long, MuzzleCompItemData> muzzleMap,
IReadOnlyDictionary<long, BearingCompItemData> bearingMap,
IReadOnlyDictionary<long, BaseCompItemData> baseMap)
{
_participantTowerIds.Clear();
List<TowerRepoItemContext> participantItems = new List<TowerRepoItemContext>();
if (inventory?.ParticipantTowerInstanceIds != null && towerMap != null)
{
foreach (long towerId in inventory.ParticipantTowerInstanceIds)
{
if (towerId <= 0)
{
continue;
}
if (!towerMap.TryGetValue(towerId, out TowerItemData tower) || tower == null)
{
continue;
}
if (!_participantTowerIds.Add(towerId))
{
continue;
}
TowerRepoItemContext towerContext = BuildTowerRepoItemContext(
tower,
muzzleMap,
bearingMap,
baseMap,
RepoItemClickActionType.RemoveParticipant,
false,
false,
false,
false,
false);
if (towerContext != null)
{
participantItems.Add(towerContext);
_itemClickActionMap[towerContext.InstanceId] = towerContext.ClickActionType;
}
}
}
return new ParticipantAreaContext
{
TowerItems = participantItems.ToArray(),
MaxCount = MaxParticipantCount
};
}
private void ApplyParticipantSelection()
{
if (Form == null || _compAreaTowerIds.Count <= 0)
{
return;
}
if (Context != null && Context.State == RepoFormState.Sell)
{
return;
}
foreach (long towerId in _compAreaTowerIds)
{
Form.SetRepoItemSelected(towerId, _participantTowerIds.Contains(towerId));
}
}
private void RefreshParticipantAreaOnly()
{
if (_useCase == null || Form == null)
{
return;
}
RepoFormContext latestContext = BuildContext(_useCase.CreateInitialModel());
if (latestContext?.ParticipantAreaContext == null)
{
return;
}
Form.RefreshGoldText(latestContext.GoldText);
Form.RefreshParticipantArea(latestContext.ParticipantAreaContext);
ApplyParticipantSelection();
}
private static TowerRepoItemContext BuildTowerRepoItemContext(
TowerItemData tower,
IReadOnlyDictionary<long, MuzzleCompItemData> muzzleMap,
IReadOnlyDictionary<long, BearingCompItemData> bearingMap,
IReadOnlyDictionary<long, BaseCompItemData> baseMap,
RepoItemClickActionType clickActionType,
bool canDrag,
bool isSellMode,
bool isSellable,
bool isSellSelected,
bool highlightSelected)
{
if (tower == null)
{
return null;
}
return new TowerRepoItemContext
{
InstanceId = tower.InstanceId,
CanDrag = canDrag,
EnduranceRate01 = ItemDescUtility.ResolveTowerEnduranceRate(tower, muzzleMap, bearingMap, baseMap),
ClickActionType = clickActionType,
ComponentSlotType = TowerCompSlotType.None,
IsSellMode = isSellMode,
IsSellable = isSellable,
IsSellSelected = isSellSelected || highlightSelected,
IconAreaContext = new TowerIconAreaContext
{
Rarity = tower.Rarity,
MuzzleColor = ResolveComponentColor(tower.MuzzleComponentInstanceId, muzzleMap),
BearingColor = ResolveComponentColor(tower.BearingComponentInstanceId, bearingMap),
BaseColor = ResolveComponentColor(tower.BaseComponentInstanceId, baseMap)
}
};
}
private static IconAreaContext BuildIconAreaContext(TowerCompItemData item)
{
if (item == null)
{
return new IconAreaContext
{
ComponentSlotType = TowerCompSlotType.None,
Rarity = RarityType.None,
Color = Color.white,
Icon = null
};
}
return new IconAreaContext
{
ComponentSlotType = item.SlotType,
Rarity = item.Rarity,
Color = IconColorGenerator.GenerateForComponent(item),
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;
}
}
}
@@ -0,0 +1,142 @@
using System;
using GeometryTD.CustomUtility;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.UI
{
public partial class RewardSelectFormController
{
private static RewardSelectFormContext BuildContext(RewardSelectFormRawData rawData)
{
if (rawData == null)
{
return null;
}
return new RewardSelectFormContext
{
TipText = string.IsNullOrWhiteSpace(rawData.TipText) ? "Select one reward" : rawData.TipText,
RefreshButtonText = BuildRefreshButtonText(rawData.RefreshCost, rawData.CanRefresh),
CanRefresh = rawData.CanRefresh,
CanGiveUp = rawData.CanGiveUp,
RewardItems = BuildRewardItemContexts(rawData.RewardItems)
};
}
private static string BuildRefreshButtonText(int refreshCost, bool canRefresh)
{
if (!canRefresh)
{
return "Refreshed";
}
if (refreshCost <= 0)
{
return "Refresh";
}
return $"Refresh -{refreshCost}";
}
private static RewardItemContext[] BuildRewardItemContexts(RewardSelectItemRawData[] rawItems)
{
if (rawItems == null || rawItems.Length <= 0)
{
return Array.Empty<RewardItemContext>();
}
RewardItemContext[] contexts = new RewardItemContext[rawItems.Length];
for (int i = 0; i < rawItems.Length; i++)
{
RewardSelectItemRawData rawItem = rawItems[i];
if (rawItem == null)
{
continue;
}
contexts[i] = new RewardItemContext
{
Index = i,
IconArea = new IconAreaContext
{
ComponentSlotType = rawItem.SlotType,
Icon = rawItem.Icon,
Rarity = rawItem.Rarity,
Color = ResolveIconColor(rawItem.SourceItem)
},
Title = rawItem.Title ?? string.Empty,
TypeText = ResolveTypeText(rawItem.TypeText, rawItem.SlotType),
Description = BuildDescription(rawItem),
Tags = BuildTagContexts(rawItem),
};
}
return contexts;
}
private static string BuildDescription(RewardSelectItemRawData rawItem)
{
string baseDescription = rawItem.Description;
string tagDescription = TagDisplayUtility.BuildTagDescriptionText(rawItem.Tags);
if (string.IsNullOrWhiteSpace(tagDescription))
{
return baseDescription;
}
if (string.IsNullOrWhiteSpace(baseDescription))
{
return tagDescription;
}
return $"{baseDescription}\n{tagDescription}";
}
private static Color ResolveIconColor(TowerCompItemData sourceItem)
{
if (sourceItem == null)
{
return Color.white;
}
return IconColorGenerator.GenerateForComponent(sourceItem);
}
private static string ResolveTypeText(string typeText, TowerCompSlotType slotType)
{
if (!string.IsNullOrWhiteSpace(typeText))
{
return typeText;
}
return slotType switch
{
TowerCompSlotType.Muzzle => "Muzzle Component",
TowerCompSlotType.Bearing => "Bearing Component",
TowerCompSlotType.Base => "Base Component",
TowerCompSlotType.Accessory => "Accessory",
_ => "Component"
};
}
private static TagItemContext[] BuildTagContexts(RewardSelectItemRawData rawItem)
{
string[] tagTexts = TagDisplayUtility.BuildTagTexts(rawItem?.Tags);
if (tagTexts == null || tagTexts.Length <= 0)
{
return Array.Empty<TagItemContext>();
}
TagItemContext[] contexts = new TagItemContext[tagTexts.Length];
for (int i = 0; i < tagTexts.Length; i++)
{
contexts[i] = new TagItemContext
{
TagName = tagTexts[i] ?? string.Empty
};
}
return contexts;
}
}
}
@@ -0,0 +1,129 @@
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 == null)
{
return null;
}
EventOptionItemContext[] options = new EventOptionItemContext[4];
for (int i = 0; i < options.Length; i++)
{
string optionText = string.Empty;
bool isSelectable = false;
string blockedReason = string.Empty;
if (rawData.OptionItems != null && i < rawData.OptionItems.Length &&
rawData.OptionItems[i] != null)
{
optionText = rawData.OptionItems[i].OptionText;
isSelectable = rawData.OptionItems[i].IsSelectable;
blockedReason = rawData.OptionItems[i].BlockedReason;
}
options[i] = new EventOptionItemContext
{
OptionIndex = i,
OptionText = optionText,
IsSelectable = isSelectable,
BlockedReason = blockedReason
};
}
return new EventFormContext
{
EventId = rawData.EventId,
Title = rawData.Title,
Description = rawData.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.TrySelectOption(args.SelectedItemId);
}
}
}
@@ -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,130 @@
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using GeometryTD.Procedure;
using GameFramework.Event;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public sealed partial class NodeMapFormController : UIFormControllerCommonBase<NodeMapFormContext, NodeMapForm>
{
private NodeMapFormUseCase _useCase;
protected override UIFormType UIFormTypeId => UIFormType.NodeMapForm;
protected override void RefreshUI(NodeMapForm form, NodeMapFormContext context)
{
form.RefreshUI(context);
}
protected override void SubscribeCustomEvents()
{
GameEntry.Event.Subscribe(NodeMapNodeClickEventArgs.EventId, OnNodeClicked);
}
protected override void UnsubscribeCustomEvents()
{
GameEntry.Event.Unsubscribe(NodeMapNodeClickEventArgs.EventId, OnNodeClicked);
}
public override int? OpenUI(object userData = null)
{
if (userData is NodeMapFormContext context)
{
return OpenUIInternal(context);
}
if (userData is NodeMapFormRawData rawDataFromUserData)
{
return OpenUI(rawDataFromUserData);
}
if (userData != null)
{
Log.Warning("NodeMapFormController.OpenUI() userData type is invalid.");
return null;
}
if (_useCase == null)
{
Log.Error("NodeMapFormController.OpenUI() useCase is null.");
return null;
}
return OpenUI(_useCase.CreateInitialModel());
}
public int? OpenUI(NodeMapFormRawData rawData)
{
return OpenUIInternal(BuildContext(rawData));
}
public override void BindUseCase(IUIUseCase useCase)
{
if (!(useCase is NodeMapFormUseCase nodeMapFormUseCase))
{
Log.Error("NodeMapFormController.BindUseCase() useCase is invalid.");
return;
}
_useCase = nodeMapFormUseCase;
}
private void OnNodeClicked(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender) || !(e is NodeMapNodeClickEventArgs args) || _useCase == null)
{
return;
}
if (!_useCase.TryRequestEnterNode(args.SequenceIndex, out NodeMapNodeRawData selectedNode, out NodeMapFormRawData latestRawData))
{
if (latestRawData != null)
{
SetContext(BuildContext(latestRawData));
RefreshCurrentUI();
}
return;
}
if (latestRawData != null)
{
SetContext(BuildContext(latestRawData));
RefreshCurrentUI();
}
if (Form != null && selectedNode != null)
{
GameEntry.Event.Fire(Form, NodeMapNodeEnterRequestedEventArgs.Create(
selectedNode.RunId,
selectedNode.NodeId,
selectedNode.NodeType,
selectedNode.SequenceIndex));
}
}
private bool IsEventFromCurrentForm(object sender)
{
if (Form == null)
{
return false;
}
if (ReferenceEquals(sender, Form))
{
return true;
}
if (sender is Component component)
{
NodeMapForm ownerForm = component.GetComponentInParent<NodeMapForm>();
return ownerForm == Form;
}
return false;
}
}
}
@@ -0,0 +1,425 @@
using System.Collections.Generic;
using GeometryTD.CustomEvent;
using GeometryTD.CustomUtility;
using GeometryTD.Definition;
using GameFramework.Event;
using GeometryTD.CustomComponent;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public partial class RepoFormController : UIFormControllerCommonBase<RepoFormContext, RepoForm>
{
private const int MaxParticipantCount = 4;
private RepoFormUseCase _useCase;
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
{
public string Title;
public string TypeText;
public string Description;
public TagType[] Tags;
public TagRuntimeData[] TagRuntimes;
}
protected override UIFormType UIFormTypeId => UIFormType.RepoForm;
protected override void RefreshUI(RepoForm form, RepoFormContext context)
{
form.RefreshUI(context);
ApplyParticipantSelection();
}
protected override void SubscribeCustomEvents()
{
GameEntry.Event.Subscribe(RepoItemClickedEventArgs.EventId, OnRepoItemClicked);
GameEntry.Event.Subscribe(RepoItemDragEndedEventArgs.EventId, OnRepoItemDragEnded);
GameEntry.Event.Subscribe(CombineSlotClickedEventArgs.EventId, OnCombineSlotClicked);
GameEntry.Event.Subscribe(RepoCombineRequestedEventArgs.EventId, OnRepoCombineRequested);
GameEntry.Event.Subscribe(RepoParticipantAssignRequestedEventArgs.EventId, OnRepoParticipantAssignRequested);
GameEntry.Event.Subscribe(RepoFormReturnEventArgs.EventId, OnRepoFormReturn);
GameEntry.Event.Subscribe(RepoSellModeToggleRequestedEventArgs.EventId, OnRepoSellModeToggleRequested);
GameEntry.Event.Subscribe(RepoSellCancelRequestedEventArgs.EventId, OnRepoSellCancelRequested);
GameEntry.Event.Subscribe(RepoSellConfirmRequestedEventArgs.EventId, OnRepoSellConfirmRequested);
}
protected override void UnsubscribeCustomEvents()
{
GameEntry.Event.Unsubscribe(RepoItemClickedEventArgs.EventId, OnRepoItemClicked);
GameEntry.Event.Unsubscribe(RepoItemDragEndedEventArgs.EventId, OnRepoItemDragEnded);
GameEntry.Event.Unsubscribe(CombineSlotClickedEventArgs.EventId, OnCombineSlotClicked);
GameEntry.Event.Unsubscribe(RepoCombineRequestedEventArgs.EventId, OnRepoCombineRequested);
GameEntry.Event.Unsubscribe(RepoParticipantAssignRequestedEventArgs.EventId, OnRepoParticipantAssignRequested);
GameEntry.Event.Unsubscribe(RepoFormReturnEventArgs.EventId, OnRepoFormReturn);
GameEntry.Event.Unsubscribe(RepoSellModeToggleRequestedEventArgs.EventId, OnRepoSellModeToggleRequested);
GameEntry.Event.Unsubscribe(RepoSellCancelRequestedEventArgs.EventId, OnRepoSellCancelRequested);
GameEntry.Event.Unsubscribe(RepoSellConfirmRequestedEventArgs.EventId, OnRepoSellConfirmRequested);
}
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;
}
#region Event Handlers
private void OnRepoItemClicked(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender))
{
return;
}
if (!(e is RepoItemClickedEventArgs args))
{
return;
}
RepoItemClickActionType clickActionType = RepoItemClickActionType.OpenDetail;
if (_itemClickActionMap.TryGetValue(args.ItemId, out RepoItemClickActionType mappedAction))
{
clickActionType = mappedAction;
}
if (args.ItemId <= 0)
{
return;
}
if (_useCase != null && _useCase.State == RepoFormState.Sell)
{
if (_useCase.TryToggleSellSelection(
args.ItemId,
out RepoFormRawData sellModeRawData,
out PlayerInventorySaleFailureReason failureReason))
{
SetContext(BuildContext(sellModeRawData));
RefreshCurrentUI();
}
else if (failureReason == PlayerInventorySaleFailureReason.ParticipantTower)
{
OpenSellBlockedDialog("参战防御塔不能直接出售,请先移出参战区。");
}
return;
}
if (clickActionType == RepoItemClickActionType.RemoveParticipant)
{
if (_useCase == null || Form == null)
{
return;
}
if (!_useCase.TryRemoveParticipantTower(args.ItemId))
{
return;
}
RefreshParticipantAreaOnly();
return;
}
if (!_itemDescSeedMap.TryGetValue(args.ItemId, out ItemDescSeed seed))
{
return;
}
GameEntry.UIRouter.OpenUI(UIFormType.ItemDescForm, new ItemDescFormRawData
{
Title = seed.Title,
TypeText = seed.TypeText,
Description = seed.Description ?? string.Empty,
Price = 0,
ScreenPosition = args.ScreenPosition,
Tags = seed.Tags,
TagRuntimes = seed.TagRuntimes
});
}
private void OnRepoItemDragEnded(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender))
{
return;
}
if (Context != null && Context.State == RepoFormState.Sell)
{
return;
}
if (!(e is RepoItemDragEndedEventArgs args))
{
return;
}
if (Form == null || !_itemClickActionMap.ContainsKey(args.ItemId))
{
return;
}
bool isSelected = _compAreaTowerIds.Contains(args.ItemId)
? _participantTowerIds.Contains(args.ItemId)
: args.Assigned;
Form.SetRepoItemSelected(args.ItemId, isSelected);
}
private void OnCombineSlotClicked(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender))
{
return;
}
if (Context != null && Context.State == RepoFormState.Sell)
{
return;
}
if (!(e is CombineSlotClickedEventArgs args))
{
return;
}
if (Form == null)
{
return;
}
if (Form.TryClearCombineSlot(args.SlotIndex, out long removedItemId) && removedItemId > 0)
{
Form.SetRepoItemSelected(removedItemId, false);
}
}
private void OnRepoFormReturn(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender) || !(e is RepoFormReturnEventArgs))
{
return;
}
if (Form == null)
{
return;
}
CloseUI();
}
private void OnRepoCombineRequested(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender))
{
return;
}
if (!(e is RepoCombineRequestedEventArgs args))
{
return;
}
if (_useCase == null || Form == null)
{
return;
}
if (!_useCase.TryAssembleTower(args.MuzzleItemId, args.BearingItemId, args.BaseItemId))
{
return;
}
RepoFormRawData latestRawData = _useCase.CreateInitialModel();
RepoFormContext latestContext = BuildContext(latestRawData);
if (latestContext != null)
{
SetContext(latestContext);
RefreshCurrentUI();
}
}
private void OnRepoParticipantAssignRequested(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender))
{
return;
}
if (Context != null && Context.State == RepoFormState.Sell)
{
return;
}
if (!(e is RepoParticipantAssignRequestedEventArgs args))
{
return;
}
if (_useCase == null || Form == null || args.TowerItemId <= 0)
{
return;
}
ParticipantTowerAssignResult result = _useCase.TryAddParticipantTower(args.TowerItemId);
if (result == null || !result.IsSuccess)
{
if (result != null)
{
Log.Warning(
"RepoFormController denied participant assignment. TowerId={0}, FailureReason={1}, ValidationFailureReason={2}.",
result.TowerInstanceId,
result.FailureReason,
result.ValidationFailureReason);
GameEntry.UIRouter.OpenUI(
UIFormType.DialogForm,
RepoParticipantAssignDialogUtility.BuildDialog(result, MaxParticipantCount));
}
return;
}
RefreshParticipantAreaOnly();
}
private void OnRepoSellModeToggleRequested(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender) || !(e is RepoSellModeToggleRequestedEventArgs) || _useCase == null)
{
return;
}
if (_useCase.State != RepoFormState.Assemble)
{
return;
}
RepoFormRawData rawData = _useCase.EnterSellState();
SetContext(BuildContext(rawData));
RefreshCurrentUI();
}
private void OnRepoSellCancelRequested(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender) || !(e is RepoSellCancelRequestedEventArgs) || _useCase == null)
{
return;
}
if (_useCase.State != RepoFormState.Sell)
{
return;
}
SetContext(BuildContext(_useCase.ExitSellState()));
RefreshCurrentUI();
}
private void OnRepoSellConfirmRequested(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender) || !(e is RepoSellConfirmRequestedEventArgs) || _useCase == null)
{
return;
}
if (!_useCase.TryConfirmSellSelection(out RepoFormRawData rawData, out PlayerInventorySaleResult result))
{
if (result != null && result.FailureReason == PlayerInventorySaleFailureReason.ParticipantTower)
{
OpenSellBlockedDialog("参战防御塔不能直接出售,请先移出参战区。");
}
return;
}
SetContext(BuildContext(rawData));
RefreshCurrentUI();
if (GameEntry.UI.GetUIForm(UIFormType.ShopForm) != null)
{
GameEntry.UIRouter.OpenUI(UIFormType.ShopForm);
}
}
private bool IsEventFromCurrentForm(object sender)
{
if (Form == null)
{
return false;
}
if (ReferenceEquals(sender, Form))
{
return true;
}
if (sender is Component component)
{
RepoForm ownerForm = component.GetComponentInParent<RepoForm>();
return ownerForm == Form;
}
return false;
}
private static void OpenSellBlockedDialog(string message)
{
GameEntry.UIRouter.OpenUI(UIFormType.DialogForm, new DialogFormRawData
{
Mode = 1,
Title = "无法出售",
Message = message,
PauseGame = false,
ConfirmText = "知道了"
});
}
#endregion
}
}
@@ -0,0 +1,18 @@
namespace GeometryTD.UI
{
public class EventFormRawData
{
public int EventId;
public string Title;
public string Description;
public EventOptionRawData[] OptionItems;
}
public sealed class EventOptionRawData
{
public int OptionIndex;
public string OptionText;
public bool IsSelectable;
public string BlockedReason;
}
}
@@ -0,0 +1,28 @@
using System.Collections.Generic;
using GeometryTD.Procedure;
namespace GeometryTD.UI
{
public sealed class NodeMapFormRawData
{
public string RunId;
public string Title;
public string ProgressText;
public string CurrentNodeText;
public bool IsRunCompleted;
public List<NodeMapNodeRawData> Nodes;
}
public sealed class NodeMapNodeRawData
{
public string RunId;
public int NodeId;
public int SequenceIndex;
public RunNodeType NodeType;
public RunNodeStatus Status;
public bool IsCurrentNode;
public bool CanEnter;
public string Title;
public string StatusText;
}
}
@@ -0,0 +1,13 @@
using GeometryTD.Definition;
namespace GeometryTD.UI
{
public class RepoFormRawData
{
public BackpackInventoryData Inventory;
public RepoFormState State;
public long[] SelectedSellItemIds;
public int SelectedSellItemCount;
public int SelectedSellTotalPrice;
}
}
@@ -0,0 +1,45 @@
using GeometryTD.Definition;
namespace GeometryTD.UI
{
public static class RepoParticipantAssignDialogUtility
{
public static DialogFormRawData BuildDialog(
ParticipantTowerAssignResult result,
int maxParticipantCount)
{
return new DialogFormRawData
{
Mode = 1,
Title = "无法加入参战区",
Message = BuildMessage(result, maxParticipantCount),
PauseGame = false,
ConfirmText = "知道了"
};
}
private static string BuildMessage(
ParticipantTowerAssignResult result,
int maxParticipantCount)
{
if (result == null)
{
return "当前无法加入参战区,请稍后重试。";
}
switch (result.FailureReason)
{
case ParticipantTowerAssignFailureReason.TowerMissing:
return $"塔 #{result.TowerInstanceId} 已不存在,无法加入参战区。";
case ParticipantTowerAssignFailureReason.InvalidTower:
return $"塔 #{result.TowerInstanceId} {CombatParticipantTowerValidationText.GetFailureReasonMessage(result.ValidationFailureReason)}";
case ParticipantTowerAssignFailureReason.AlreadyAssigned:
return $"塔 #{result.TowerInstanceId} 已在参战区中。";
case ParticipantTowerAssignFailureReason.ParticipantAreaFull:
return $"参战区已满,最多只能放入 {maxParticipantCount} 座塔。";
default:
return "当前无法加入参战区,请稍后重试。";
}
}
}
}
+106
View File
@@ -0,0 +1,106 @@
using GeometryTD.Definition;
using GeometryTD.Procedure;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class EventFormUseCase : IUIUseCase
{
private EventItem _currentEvent;
private RunNodeExecutionContext _currentContext;
private readonly EventOptionExecutor _executor = new EventOptionExecutor();
public void BindEvent(
EventItem eventItem,
RunNodeExecutionContext context)
{
_currentEvent = eventItem;
_currentContext = context != null ? context.Clone() : null;
}
public void Clear()
{
_currentEvent = null;
_currentContext = null;
}
public EventFormRawData CreateInitialModel()
{
if (_currentEvent == null)
{
return null;
}
return new EventFormRawData
{
EventId = _currentEvent.Id,
Title = _currentEvent.Title,
Description = _currentEvent.Description,
OptionItems = BuildOptionItems()
};
}
public bool TrySelectOption(int optionIndex)
{
if (_currentEvent == null || _currentEvent.Options == null)
{
return false;
}
if (optionIndex < 0 || optionIndex >= _currentEvent.Options.Length)
{
Log.Warning("EventFormUseCase.TrySelectOption() option index is invalid: {0}", optionIndex);
return false;
}
BackpackInventoryData workingInventory = GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetInventorySnapshot()
: new BackpackInventoryData();
EventOptionExecutionResult executionResult = _executor.Execute(
_currentEvent,
optionIndex,
_currentContext,
workingInventory);
if (!executionResult.IsAccepted)
{
Log.Warning(
"EventFormUseCase.TrySelectOption() rejected. EventId={0}, OptionIndex={1}, Reason={2}",
_currentEvent.Id,
optionIndex,
executionResult.FailureReason);
return false;
}
if (GameEntry.PlayerInventory != null)
{
GameEntry.PlayerInventory.ReplaceInventorySnapshot(workingInventory);
}
GameEntry.EventNode.EndEvent();
return true;
}
private EventOptionRawData[] BuildOptionItems()
{
EventOption[] options = _currentEvent.Options ?? System.Array.Empty<EventOption>();
EventOptionRawData[] result = new EventOptionRawData[options.Length];
BackpackInventoryData inventory = GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetInventorySnapshot()
: new BackpackInventoryData();
for (int i = 0; i < options.Length; i++)
{
EventOption option = options[i];
EventOptionAvailability availability = _executor.EvaluateOption(option, inventory);
result[i] = new EventOptionRawData
{
OptionIndex = i,
OptionText = option?.OptionText ?? string.Empty,
IsSelectable = availability.IsSelectable,
BlockedReason = availability.BlockedReason
};
}
return result;
}
}
}
@@ -0,0 +1,156 @@
using System.Collections.Generic;
using GeometryTD.Procedure;
namespace GeometryTD.UI
{
public sealed class NodeMapFormUseCase : IUIUseCase
{
private RunState _runState;
public void SetRunState(RunState runState)
{
_runState = runState;
}
public NodeMapFormRawData CreateInitialModel()
{
return BuildRawData(_runState);
}
public bool TryRequestEnterNode(int sequenceIndex, out NodeMapNodeRawData selectedNode, out NodeMapFormRawData latestRawData)
{
latestRawData = BuildRawData(_runState);
selectedNode = null;
if (_runState == null || _runState.IsCompleted)
{
return false;
}
RunNodeState currentNode = _runState.CurrentNode;
if (currentNode == null || currentNode.SequenceIndex != sequenceIndex || !_runState.CanEnterCurrentNode)
{
return false;
}
RunNodeState targetNode = _runState.GetNodeBySequenceIndex(sequenceIndex);
if (targetNode == null || targetNode.Status != RunNodeStatus.Available)
{
return false;
}
selectedNode = BuildNodeRawData(_runState.RunId, targetNode, currentNode.SequenceIndex);
return selectedNode != null && selectedNode.CanEnter;
}
private static NodeMapFormRawData BuildRawData(RunState runState)
{
if (runState == null)
{
return new NodeMapFormRawData
{
Title = "节点地图",
ProgressText = "0 / 0",
CurrentNodeText = "当前节点: 无",
IsRunCompleted = true,
Nodes = new List<NodeMapNodeRawData>()
};
}
List<NodeMapNodeRawData> nodes = new List<NodeMapNodeRawData>(runState.NodeCount);
int currentSequenceIndex = runState.CurrentNode != null ? runState.CurrentNode.SequenceIndex : -1;
foreach (RunNodeState node in runState.Nodes)
{
NodeMapNodeRawData nodeRawData = BuildNodeRawData(runState.RunId, node, currentSequenceIndex);
if (nodeRawData != null)
{
nodes.Add(nodeRawData);
}
}
string currentNodeText = runState.IsCompleted
? "当前节点: 已完成"
: $"当前节点: {BuildNodeTitle(runState.CurrentNode)}";
return new NodeMapFormRawData
{
RunId = runState.RunId,
Title = "节点地图",
ProgressText = $"{runState.CompletedNodeCount} / {runState.NodeCount}",
CurrentNodeText = currentNodeText,
IsRunCompleted = runState.IsCompleted,
Nodes = nodes
};
}
private static NodeMapNodeRawData BuildNodeRawData(string runId, RunNodeState node, int currentSequenceIndex)
{
if (node == null)
{
return null;
}
bool isCurrentNode = node.SequenceIndex == currentSequenceIndex;
bool canEnter = isCurrentNode && node.Status == RunNodeStatus.Available;
return new NodeMapNodeRawData
{
RunId = runId,
NodeId = node.NodeId,
SequenceIndex = node.SequenceIndex,
NodeType = node.NodeType,
Status = node.Status,
IsCurrentNode = isCurrentNode,
CanEnter = canEnter,
Title = BuildNodeTitle(node),
StatusText = BuildStatusText(node.Status, canEnter)
};
}
private static string BuildNodeTitle(RunNodeState node)
{
return node == null ? "未知节点" : BuildNodeTitle(node.NodeType);
}
private static string BuildNodeTitle(RunNodeType nodeType)
{
switch (nodeType)
{
case RunNodeType.Combat:
return "战斗";
case RunNodeType.Event:
return "事件";
case RunNodeType.Shop:
return "商店";
case RunNodeType.BossCombat:
return "Boss";
default:
return "未知节点";
}
}
private static string BuildStatusText(RunNodeStatus status, bool canEnter)
{
if (canEnter)
{
return "当前";
}
switch (status)
{
case RunNodeStatus.Locked:
return "未解锁";
case RunNodeStatus.Available:
return "可进入";
case RunNodeStatus.Completed:
return "已完成";
case RunNodeStatus.Exception:
return "异常";
case RunNodeStatus.Skipped:
return "跳过";
default:
return string.Empty;
}
}
}
}
+209
View File
@@ -0,0 +1,209 @@
using System.Collections.Generic;
using GeometryTD.CustomComponent;
using GeometryTD.CustomUtility;
using GeometryTD.Definition;
namespace GeometryTD.UI
{
public class RepoFormUseCase : IUIUseCase
{
private const int MaxParticipantCount = 4;
private BackpackInventoryData _fallbackInventory;
private readonly HashSet<long> _selectedSellItemIds = new HashSet<long>();
private RepoFormState _state = RepoFormState.Assemble;
public RepoFormRawData CreateInitialModel()
{
BackpackInventoryData sample = GameEntry.PlayerInventory != null
? GameEntry.PlayerInventory.GetInventorySnapshot()
: GetOrCreateFallbackInventory();
return new RepoFormRawData
{
Inventory = sample,
State = _state,
SelectedSellItemIds = BuildSelectedSellItemArray(),
SelectedSellItemCount = _selectedSellItemIds.Count,
SelectedSellTotalPrice = ResolveSelectedSellTotalPrice()
};
}
public RepoFormState State => _state;
public bool TryAssembleTower(long muzzleItemId, long bearingItemId, long baseItemId)
{
if (_state == RepoFormState.Sell)
{
return false;
}
if (GameEntry.PlayerInventory == null)
{
return false;
}
return GameEntry.PlayerInventory.TryAssembleTower(
muzzleItemId,
bearingItemId,
baseItemId,
out _);
}
public ParticipantTowerAssignResult TryAddParticipantTower(long towerItemId)
{
if (_state == RepoFormState.Sell)
{
return new ParticipantTowerAssignResult
{
TowerInstanceId = towerItemId,
FailureReason = ParticipantTowerAssignFailureReason.ParticipantAreaFull
};
}
if (GameEntry.PlayerInventory == null)
{
BackpackInventoryData fallbackInventory = GetOrCreateFallbackInventory();
return InventoryParticipantUtility.TryAddParticipantTower(
fallbackInventory,
towerItemId,
MaxParticipantCount);
}
return GameEntry.PlayerInventory.TryAddParticipantTower(towerItemId, MaxParticipantCount);
}
public bool TryRemoveParticipantTower(long towerItemId)
{
if (_state == RepoFormState.Sell)
{
return false;
}
if (GameEntry.PlayerInventory == null)
{
BackpackInventoryData fallbackInventory = GetOrCreateFallbackInventory();
return InventoryParticipantUtility.TryRemoveParticipantTower(
fallbackInventory,
towerItemId,
MaxParticipantCount);
}
return GameEntry.PlayerInventory.TryRemoveParticipantTower(towerItemId);
}
public RepoFormRawData EnterSellState()
{
_state = RepoFormState.Sell;
_selectedSellItemIds.Clear();
return CreateInitialModel();
}
public RepoFormRawData ExitSellState()
{
_state = RepoFormState.Assemble;
_selectedSellItemIds.Clear();
return CreateInitialModel();
}
public bool TryToggleSellSelection(
long itemId,
out RepoFormRawData rawData,
out PlayerInventorySaleFailureReason failureReason)
{
rawData = null;
failureReason = PlayerInventorySaleFailureReason.None;
if (_state != RepoFormState.Sell || itemId <= 0 || GameEntry.PlayerInventory == null)
{
failureReason = PlayerInventorySaleFailureReason.InvalidSelection;
return false;
}
if (_selectedSellItemIds.Contains(itemId))
{
_selectedSellItemIds.Remove(itemId);
rawData = CreateInitialModel();
return true;
}
if (!GameEntry.PlayerInventory.TryGetSaleCandidate(itemId, out PlayerInventorySaleCandidate candidate) ||
candidate == null)
{
failureReason = PlayerInventorySaleFailureReason.ItemNotFound;
return false;
}
if (!candidate.IsSellable)
{
failureReason = candidate.FailureReason;
return false;
}
_selectedSellItemIds.Add(itemId);
rawData = CreateInitialModel();
return true;
}
public bool TryConfirmSellSelection(out RepoFormRawData rawData, out PlayerInventorySaleResult result)
{
rawData = null;
result = null;
if (_state != RepoFormState.Sell || _selectedSellItemIds.Count <= 0 || GameEntry.PlayerInventory == null)
{
return false;
}
if (!GameEntry.PlayerInventory.TrySellItems(_selectedSellItemIds, out PlayerInventorySaleResult saleResult) ||
saleResult == null ||
!saleResult.IsSuccess)
{
result = saleResult;
rawData = CreateInitialModel();
return false;
}
_selectedSellItemIds.Clear();
result = saleResult;
rawData = CreateInitialModel();
return true;
}
private BackpackInventoryData GetOrCreateFallbackInventory()
{
_fallbackInventory ??= InventorySeedUtility.CreateSampleInventory();
InventoryParticipantUtility.NormalizeParticipantState(_fallbackInventory, MaxParticipantCount);
return _fallbackInventory;
}
private long[] BuildSelectedSellItemArray()
{
if (_selectedSellItemIds.Count <= 0)
{
return System.Array.Empty<long>();
}
long[] itemIds = new long[_selectedSellItemIds.Count];
_selectedSellItemIds.CopyTo(itemIds);
return itemIds;
}
private int ResolveSelectedSellTotalPrice()
{
if (_state != RepoFormState.Sell || _selectedSellItemIds.Count <= 0 || GameEntry.PlayerInventory == null)
{
return 0;
}
int total = 0;
foreach (long itemId in _selectedSellItemIds)
{
if (GameEntry.PlayerInventory.TryGetSaleCandidate(itemId, out PlayerInventorySaleCandidate candidate) &&
candidate != null &&
candidate.IsSellable)
{
total += candidate.Price;
}
}
return total;
}
}
}
+169
View File
@@ -0,0 +1,169 @@
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using UnityEngine;
using UnityEngine.EventSystems;
namespace GeometryTD.UI
{
public class CombineArea : MonoBehaviour, IDropHandler
{
[SerializeField] private CombineSlotItem[] _slots;
private bool _isInteractionEnabled = true;
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()
{
if (!_isInteractionEnabled)
{
return;
}
if (!TryGetBoundItemId(TowerCompSlotType.Muzzle, out long muzzleItemId) ||
!TryGetBoundItemId(TowerCompSlotType.Bearing, out long bearingItemId) ||
!TryGetBoundItemId(TowerCompSlotType.Base, out long baseItemId))
{
return;
}
GameEntry.Event.Fire(this, RepoCombineRequestedEventArgs.Create(muzzleItemId, bearingItemId, baseItemId));
}
public bool TryAssignItem(RepoItemContext itemContext)
{
if (!_isInteractionEnabled)
{
return false;
}
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;
}
public void OnDrop(PointerEventData eventData)
{
if (!_isInteractionEnabled)
{
return;
}
if (eventData == null)
{
return;
}
GameObject pointerDrag = eventData.pointerDrag;
if (pointerDrag == null)
{
return;
}
IRepoDragItemView dragItem = pointerDrag.GetComponent<IRepoDragItemView>() ??
pointerDrag.GetComponentInParent<IRepoDragItemView>();
if (dragItem == null)
{
return;
}
bool assigned = TryAssignItem(dragItem.ComponentContext);
dragItem.SetDropResult(assigned);
}
public void SetInteractionEnabled(bool enabled)
{
_isInteractionEnabled = enabled;
}
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;
}
private bool TryGetBoundItemId(TowerCompSlotType slotType, out long boundItemId)
{
boundItemId = 0;
CombineSlotItem slot = FindSlot(slotType);
if (slot == null || !slot.HasItem || slot.BoundItemId <= 0)
{
return false;
}
boundItemId = slot.BoundItemId;
return true;
}
}
}
+69
View File
@@ -0,0 +1,69 @@
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.UI
{
public class CombineSlotItem : MonoBehaviour
{
[SerializeField] private TowerCompSlotType _acceptSlotType = TowerCompSlotType.None;
[SerializeField] private IconArea _iconArea;
[SerializeField] private CommonButton _button;
private long _boundItemId;
private int _slotIndex = -1;
public TowerCompSlotType AcceptSlotType => _acceptSlotType;
public bool HasItem => _boundItemId > 0;
public int SlotIndex => _slotIndex;
public long BoundItemId => _boundItemId;
public void OnInit(int slotIndex)
{
_slotIndex = slotIndex;
ClearSlot();
_iconArea.OnReset();
}
public void BindItem(RepoItemContext itemContext)
{
_boundItemId = itemContext?.InstanceId ?? 0;
_button.Interactive = true;
if (itemContext != null)
{
_iconArea.OnInit(itemContext.IconAreaContext);
}
}
public void ClearSlot()
{
_boundItemId = 0;
_button.Interactive = false;
_iconArea.OnReset();
}
public void OnReset()
{
_slotIndex = -1;
ClearSlot();
_iconArea.OnReset();
}
public void OnClick()
{
if (_slotIndex < 0)
{
return;
}
GameEntry.Event.Fire(this, CombineSlotClickedEventArgs.Create(_slotIndex));
}
}
}
+203
View File
@@ -0,0 +1,203 @@
using System.Collections.Generic;
using GameFramework.ObjectPool;
using GeometryTD.PoolObjectBase;
using UnityEngine;
using UnityEngine.Serialization;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class CompArea : MonoBehaviour
{
[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> _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)
{
if (context == null)
{
OnReset();
return;
}
EnsurePool();
OnReset();
if (context.ComponentItems != null)
{
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;
}
}
if (context.TowerItems != null)
{
foreach (TowerRepoItemContext itemContext in context.TowerItems)
{
TowerRepoItem item = CreateOrSpawnTowerItem();
if (item == null)
{
continue;
}
item.gameObject.SetActive(true);
item.OnInit(itemContext);
_activeTowerItems.Add(item);
_towerItemMap[itemContext.InstanceId] = item;
}
}
}
public void OnReset()
{
for (int i = _activeComponentItems.Count - 1; i >= 0; i--)
{
RepoItem item = _activeComponentItems[i];
if (item == null)
{
continue;
}
item.OnReset();
item.gameObject.SetActive(false);
_componentItemPool?.Unspawn(item);
}
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();
_componentItemPool?.ReleaseAllUnused();
_towerItemPool?.ReleaseAllUnused();
}
private void EnsurePool()
{
if (_componentItemPool == null)
{
_componentPoolName = $"RepoItemPool_{GetInstanceID()}";
_componentItemPool =
GameEntry.ObjectPool.CreateSingleSpawnObjectPool<RepoItemObject>(_componentPoolName,
_instancePoolCapacity);
}
if (_towerItemPool == null)
{
_towerPoolName = $"TowerRepoItemPool_{GetInstanceID()}";
_towerItemPool =
GameEntry.ObjectPool.CreateSingleSpawnObjectPool<TowerRepoItemObject>(_towerPoolName,
_instancePoolCapacity);
}
}
private RepoItem CreateOrSpawnComponentItem()
{
if (_componentItemPool == null)
{
return null;
}
RepoItemObject itemObject = _componentItemPool.Spawn();
RepoItem item = itemObject != null ? (RepoItem)itemObject.Target : null;
if (item == null)
{
if (_itemTemplate == null)
{
Log.Warning("CompArea requires a component item template.");
return null;
}
item = Instantiate(_itemTemplate);
_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)
{
item.transform.SetParent(_content, false);
}
return item;
}
public void SetItemSelected(long itemId, bool isSelected)
{
if (_componentItemMap.TryGetValue(itemId, out RepoItem componentItem))
{
componentItem.SetSelected(isSelected);
}
if (_towerItemMap.TryGetValue(itemId, out TowerRepoItem towerItem))
{
towerItem.SetSelected(isSelected);
}
}
}
}
+82
View File
@@ -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);
}
}
}
+85
View File
@@ -0,0 +1,85 @@
using GeometryTD.CustomEvent;
using TMPro;
using UnityEngine;
namespace GeometryTD.UI
{
public class EventOptionItem : MonoBehaviour
{
[SerializeField] private TMP_Text _optionText;
private static readonly Color SelectableTextColor = Color.white;
private static readonly Color BlockedTextColor = new Color(0.75f, 0.75f, 0.75f, 1f);
private int _optionIndex;
private bool _isSelectable;
private CommonButton _commonButton;
private void Awake()
{
_commonButton = GetComponent<CommonButton>();
}
public void OnInit(EventOptionItemContext context)
{
if (context == null)
{
OnReset();
gameObject.SetActive(false);
return;
}
_optionIndex = context.OptionIndex;
_isSelectable = context.IsSelectable;
if (_optionText != null)
{
_optionText.text = BuildDisplayText(context);
_optionText.color = _isSelectable ? SelectableTextColor : BlockedTextColor;
}
if (_commonButton != null)
{
_commonButton.Interactive = _isSelectable;
}
gameObject.SetActive(!string.IsNullOrEmpty(context.OptionText));
}
public void OnReset()
{
_optionIndex = -1;
_isSelectable = false;
if (_optionText != null)
{
_optionText.text = string.Empty;
_optionText.color = SelectableTextColor;
}
if (_commonButton != null)
{
_commonButton.Interactive = true;
}
}
public void OnClick()
{
if (_optionIndex < 0 || !_isSelectable)
{
return;
}
GameEntry.Event.Fire(this, EventOptionItemSelectedEventArgs.Create(_optionIndex));
}
private static string BuildDisplayText(EventOptionItemContext context)
{
string optionText = context.OptionText ?? string.Empty;
if (context.IsSelectable || string.IsNullOrEmpty(context.BlockedReason))
{
return optionText;
}
return $"{optionText}\n<size=75%>{context.BlockedReason}</size>";
}
}
}
+19
View File
@@ -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);
}
}
+49
View File
@@ -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);
}
}
}
+121
View File
@@ -0,0 +1,121 @@
using UnityEngine;
using UnityEngine.UI;
using GeometryTD.CustomEvent;
using GeometryTD.Procedure;
namespace GeometryTD.UI
{
public class NodeItem : MonoBehaviour
{
private const string CombatIconAssetName = "Combat";
private const string EventIconAssetName = "Event";
private const string ShopIconAssetName = "Shop";
private const string BossIconAssetName = "BossCombat";
[SerializeField] private Image _icon;
[SerializeField] private Image _bg;
[SerializeField] private CommonButton _button;
[SerializeField] private Color _lockedColor = Color.gray;
[SerializeField] private Color _activeColor = Color.cyan;
[SerializeField] private Color _passedColor = Color.green;
[SerializeField] private Color _exceptionColor = Color.red;
[SerializeField] private Color _defaultIconColor = Color.white;
[SerializeField] private Color _lockedIconColor = Color.gray;
private NodeItemContext _context;
private string _requestedIconAssetName;
public void RefreshUI(NodeItemContext context)
{
_context = context;
if (_button != null)
{
_button.Interactive = context != null && context.CanClick;
}
Color targetColor = _lockedColor;
if (context == null)
{
targetColor = _lockedColor;
}
else if (context.IsCompleted)
{
targetColor = _passedColor;
}
else if (context.IsException)
{
targetColor = _exceptionColor;
}
else if (context.IsCurrent)
{
targetColor = _activeColor;
}
if (_bg != null)
{
_bg.color = targetColor;
}
if (_icon != null)
{
RefreshIcon(context != null ? context.NodeType : RunNodeType.None);
_icon.color = context != null && !context.IsLocked ? _defaultIconColor : _lockedIconColor;
}
}
public void OnClick()
{
if (_context == null || !_context.CanClick)
{
return;
}
GameEntry.Event.Fire(this, NodeMapNodeClickEventArgs.Create(_context.SequenceIndex));
}
private void RefreshIcon(RunNodeType nodeType)
{
if (_icon == null)
{
return;
}
string assetName = ResolveIconAssetName(nodeType);
_requestedIconAssetName = assetName;
if (string.IsNullOrWhiteSpace(assetName) || GameEntry.SpriteCache == null)
{
_icon.sprite = null;
return;
}
GameEntry.SpriteCache.GetSprite(assetName, sprite =>
{
if (_icon == null || _requestedIconAssetName != assetName)
{
return;
}
_icon.sprite = sprite;
});
}
private static string ResolveIconAssetName(RunNodeType nodeType)
{
switch (nodeType)
{
case RunNodeType.Combat:
return CombatIconAssetName;
case RunNodeType.Event:
return EventIconAssetName;
case RunNodeType.Shop:
return ShopIconAssetName;
case RunNodeType.BossCombat:
return BossIconAssetName;
default:
return null;
}
}
}
}
+89
View File
@@ -0,0 +1,89 @@
using TMPro;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class NodeMapForm : UGuiForm
{
[SerializeField] private TMP_Text _titleText;
[SerializeField] private TMP_Text _progressText;
[SerializeField] private TMP_Text _currentNodeText;
[SerializeField] private NodeItem[] _nodeItems;
private NodeMapFormContext _context;
public void RefreshUI(NodeMapFormContext context)
{
_context = context;
if (_titleText != null)
{
_titleText.text = context?.Title ?? string.Empty;
}
if (_progressText != null)
{
_progressText.text = context?.ProgressText ?? string.Empty;
}
if (_currentNodeText != null)
{
_currentNodeText.text = context?.CurrentNodeText ?? string.Empty;
}
if (_nodeItems == null)
{
return;
}
for (int i = 0; i < _nodeItems.Length; i++)
{
NodeItemContext itemContext =
context?.NodeItems != null && i < context.NodeItems.Length ? context.NodeItems[i] : null;
_nodeItems[i]?.RefreshUI(itemContext);
}
}
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
if (userData is NodeMapFormContext context)
{
RefreshUI(context);
return;
}
Log.Warning("NodeMapForm requires NodeMapFormContext as userData.");
}
protected override void OnClose(bool isShutdown, object userData)
{
_context = null;
if (_titleText != null)
{
_titleText.text = string.Empty;
}
if (_progressText != null)
{
_progressText.text = string.Empty;
}
if (_currentNodeText != null)
{
_currentNodeText.text = string.Empty;
}
if (_nodeItems != null)
{
for (int i = 0; i < _nodeItems.Length; i++)
{
_nodeItems[i]?.RefreshUI(null);
}
}
base.OnClose(isShutdown, userData);
}
}
}
+194
View File
@@ -0,0 +1,194 @@
using System.Collections.Generic;
using GameFramework.ObjectPool;
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using GeometryTD.PoolObjectBase;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class ParticipantArea : MonoBehaviour, IDropHandler
{
[SerializeField] private Transform _content;
[SerializeField] private TowerRepoItem _towerItemTemplate;
[SerializeField] private int _instancePoolCapacity = 8;
private bool _isInteractionEnabled = true;
private readonly List<TowerRepoItem> _activeItems = new List<TowerRepoItem>();
private readonly HashSet<long> _boundItemIds = new HashSet<long>();
private IObjectPool<TowerRepoItemObject> _itemPool;
private string _poolName;
private int _maxCount = 4;
public void OnInit(ParticipantAreaContext context)
{
OnReset();
EnsurePool();
_maxCount = Mathf.Max(1, context != null ? context.MaxCount : 4);
if (context?.TowerItems == null || context.TowerItems.Length <= 0)
{
return;
}
for (int i = 0; i < context.TowerItems.Length; i++)
{
TowerRepoItemContext itemContext = context.TowerItems[i];
if (itemContext == null || itemContext.InstanceId <= 0)
{
continue;
}
TowerRepoItem item = CreateOrSpawnItem();
if (item == null)
{
continue;
}
item.gameObject.SetActive(true);
item.OnInit(itemContext);
_activeItems.Add(item);
_boundItemIds.Add(itemContext.InstanceId);
}
}
public void OnReset()
{
for (int i = _activeItems.Count - 1; i >= 0; i--)
{
TowerRepoItem item = _activeItems[i];
if (item == null)
{
continue;
}
item.OnReset();
item.gameObject.SetActive(false);
_itemPool?.Unspawn(item);
}
_activeItems.Clear();
_boundItemIds.Clear();
_maxCount = 4;
}
private void OnDestroy()
{
OnReset();
_itemPool?.ReleaseAllUnused();
}
public bool CanAssign(IRepoDragItemView dragItem)
{
if (!_isInteractionEnabled)
{
return false;
}
if (dragItem == null || dragItem.InstanceId <= 0)
{
return false;
}
if (dragItem.ComponentSlotType != TowerCompSlotType.None)
{
return false;
}
if (_boundItemIds.Contains(dragItem.InstanceId))
{
return false;
}
if (_boundItemIds.Count >= _maxCount)
{
return false;
}
return true;
}
public void OnDrop(PointerEventData eventData)
{
if (!_isInteractionEnabled)
{
return;
}
if (eventData == null)
{
return;
}
GameObject pointerDrag = eventData.pointerDrag;
if (pointerDrag == null)
{
return;
}
IRepoDragItemView dragItem = pointerDrag.GetComponent<IRepoDragItemView>() ??
pointerDrag.GetComponentInParent<IRepoDragItemView>();
if (dragItem == null)
{
return;
}
bool assigned = CanAssign(dragItem);
dragItem.SetDropResult(assigned);
if (!assigned)
{
return;
}
GameEntry.Event.Fire(this, RepoParticipantAssignRequestedEventArgs.Create(dragItem.InstanceId));
}
public void SetInteractionEnabled(bool enabled)
{
_isInteractionEnabled = enabled;
}
private void EnsurePool()
{
if (_itemPool != null)
{
return;
}
_poolName = $"ParticipantTowerRepoItemPool_{GetInstanceID()}";
_itemPool =
GameEntry.ObjectPool.CreateSingleSpawnObjectPool<TowerRepoItemObject>(_poolName, _instancePoolCapacity);
}
private TowerRepoItem CreateOrSpawnItem()
{
if (_itemPool == null)
{
return null;
}
TowerRepoItemObject itemObject = _itemPool.Spawn();
TowerRepoItem item = itemObject != null ? (TowerRepoItem)itemObject.Target : null;
if (item == null)
{
if (_towerItemTemplate == null)
{
Log.Warning("ParticipantArea requires a tower item template.");
return null;
}
item = Instantiate(_towerItemTemplate);
_itemPool.Register(TowerRepoItemObject.Create(item), true);
}
if (_content != null)
{
item.transform.SetParent(_content, false);
}
return item;
}
}
}
+136
View File
@@ -0,0 +1,136 @@
using GeometryTD.CustomEvent;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class RepoForm : UGuiForm
{
[SerializeField] private CombineArea _combineArea;
[SerializeField] private CompArea _compArea;
[SerializeField] private SellArea _sellArea;
[SerializeField] private ParticipantArea _participantArea;
[SerializeField] private TMP_Text _goldText;
[SerializeField] private CommonButton _sellModeButton;
[SerializeField] private TMP_Text _sellModeButtonText;
public void RefreshUI(RepoFormContext context)
{
if (context == null)
{
return;
}
RefreshGoldText(context.GoldText);
RefreshStateUI(context);
_combineArea?.OnInit(context.CombineAreaContext);
_compArea?.OnInit(context.CompAreaContext);
_sellArea?.OnInit(context.SellAreaContext);
_participantArea?.OnInit(context.ParticipantAreaContext);
if (_combineArea != null)
{
_combineArea.gameObject.SetActive(context.ShowCombineArea);
_combineArea.SetInteractionEnabled(context.State == RepoFormState.Assemble);
}
if (_sellArea != null)
{
_sellArea.gameObject.SetActive(context.ShowSellArea);
}
_participantArea?.SetInteractionEnabled(context.State == RepoFormState.Assemble);
}
public void RefreshGoldText(string goldText)
{
if (_goldText != null)
{
_goldText.text = goldText ?? string.Empty;
}
}
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);
}
public void RefreshParticipantArea(ParticipantAreaContext context)
{
_participantArea?.OnInit(context);
}
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)
{
if (_goldText != null)
{
_goldText.text = string.Empty;
}
_combineArea?.OnReset();
_compArea?.OnReset();
_sellArea?.OnReset();
_participantArea?.OnReset();
base.OnClose(isShutdown, userData);
}
public void OnReturnButtonClick()
{
GameEntry.Event.Fire(this, RepoFormReturnEventArgs.Create());
}
public void OnSellModeButtonClick()
{
GameEntry.Event.Fire(this, RepoSellModeToggleRequestedEventArgs.Create());
}
private void RefreshStateUI(RepoFormContext context)
{
if (_sellModeButton != null)
{
_sellModeButton.gameObject.SetActive(context != null && context.ShowSellModeButton);
}
if (_sellModeButtonText != null)
{
_sellModeButtonText.text = context?.SellModeButtonText ?? "出售模式";
}
}
}
}
+381
View File
@@ -0,0 +1,381 @@
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace GeometryTD.UI
{
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);
private static readonly Color DisabledSellColor = new Color32(96, 96, 96, 255);
private static readonly Color EmptyEnduranceColor = new Color(0.8f, 0f, 0f, 1f);
private static readonly Color FullEnduranceColor = new Color(0f, 0.8f, 0f, 1f);
private static readonly Vector2 DefaultDragGhostSize = new Vector2(64f, 64f);
[SerializeField] private RepoItemContext _context;
private CanvasGroup _canvasGroup;
private RectTransform _dragRoot;
protected GameObject _dragGhostObject;
protected RectTransform _dragGhostRect;
private bool _isSelected;
private bool _isDragging;
private bool _dropHandled;
private bool _dropAssigned;
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>();
if (_canvasGroup == null)
{
_canvasGroup = gameObject.AddComponent<CanvasGroup>();
}
}
private void OnDisable()
{
ResetDragState();
}
public virtual void OnInit(RepoItemContext context)
{
if (context == null)
{
OnReset();
return;
}
_context = context;
_iconArea.OnInit(context.IconAreaContext);
SetSelected(context.IsSellSelected);
ResetDragState();
}
public virtual void OnReset()
{
_context = null;
_iconArea.OnReset();
SetSelected(false);
ResetDragState();
}
public void SetSelected(bool isSelected)
{
_isSelected = isSelected;
RefreshBackgroundColor();
}
private void RefreshBackgroundColor()
{
if (_bgImage == null)
{
return;
}
if (_isSelected)
{
_bgImage.color = SelectedColor;
return;
}
if (_context != null && _context.IsSellMode && !_context.IsSellable)
{
_bgImage.color = DisabledSellColor;
return;
}
float enduranceRate = _context != null ? Mathf.Clamp01(_context.EnduranceRate01) : 1f;
_bgImage.color = Color.Lerp(EmptyEnduranceColor, FullEnduranceColor, enduranceRate);
}
public void OnClick()
{
if (_context == null)
{
return;
}
if (_isDragging)
{
return;
}
GameEntry.Event.Fire(this, RepoItemClickedEventArgs.Create(_context.InstanceId, ResolveScreenPosition()));
}
public void OnBeginDrag(PointerEventData eventData)
{
if (!CanStartDrag(eventData))
{
return;
}
_dragRoot = ResolveDragRoot();
if (_dragRoot == null)
{
return;
}
_isDragging = true;
_dropHandled = false;
_dropAssigned = false;
CloseItemDescFormIfOpen();
if (!CreateDragGhost())
{
_isDragging = false;
_dragRoot = null;
return;
}
if (_canvasGroup != null)
{
_canvasGroup.blocksRaycasts = false;
}
SetSelected(true);
UpdateDragGhostPosition(eventData);
}
public void OnDrag(PointerEventData eventData)
{
if (!_isDragging)
{
return;
}
UpdateDragGhostPosition(eventData);
}
public void OnEndDrag(PointerEventData eventData)
{
if (!_isDragging)
{
return;
}
bool assigned = _dropHandled && _dropAssigned;
_isDragging = false;
if (_canvasGroup != null)
{
_canvasGroup.blocksRaycasts = true;
}
DestroyDragGhost();
_dragRoot = null;
if (_context == null)
{
return;
}
GameEntry.Event.Fire(this, RepoItemDragEndedEventArgs.Create(_context.InstanceId, assigned));
}
public void SetDropResult(bool assigned)
{
if (!_isDragging)
{
return;
}
_dropHandled = true;
_dropAssigned = assigned;
}
private bool CanStartDrag(PointerEventData eventData)
{
if (_context == null || _isDragging)
{
return false;
}
if (_context.IsSellMode)
{
return false;
}
if (_isSelected && _context.ComponentSlotType != TowerCompSlotType.None)
{
return false;
}
if (!_context.CanDrag)
{
return false;
}
if (eventData == null || eventData.button != PointerEventData.InputButton.Left)
{
return false;
}
return true;
}
private static void CloseItemDescFormIfOpen()
{
var itemDescForm = GameEntry.UI.GetUIForm(UIFormType.ItemDescForm);
if (itemDescForm != null)
{
GameEntry.UI.CloseUIForm(itemDescForm);
}
}
private Vector2 ResolveScreenPosition()
{
RectTransform rectTransform = transform as RectTransform;
if (rectTransform == null)
{
return Vector2.zero;
}
Canvas canvas = GetComponentInParent<Canvas>();
Canvas rootCanvas = canvas != null ? canvas.rootCanvas : null;
Camera uiCamera = null;
if (rootCanvas != null && rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay)
{
uiCamera = rootCanvas.worldCamera != null ? rootCanvas.worldCamera : Camera.main;
}
return RectTransformUtility.WorldToScreenPoint(uiCamera, rectTransform.position);
}
private RectTransform ResolveDragRoot()
{
Canvas canvas = GetComponentInParent<Canvas>();
if (canvas == null || canvas.rootCanvas == null)
{
return null;
}
return canvas.rootCanvas.transform as RectTransform;
}
protected virtual bool CreateDragGhost()
{
DestroyDragGhost();
if (_dragRoot == null)
{
return false;
}
Sprite iconSprite = _iconArea != null ? _iconArea.CurrentIconSprite : null;
Color iconColor = _iconArea != null ? _iconArea.CurrentIconColor : Color.white;
Material iconMaterial = _iconArea != null ? _iconArea.CurrentIconMaterial : null;
Vector2 iconSize = _iconArea != null ? _iconArea.CurrentIconSize : Vector2.zero;
if (iconSprite == null && _bgImage != null)
{
iconSprite = _bgImage.sprite;
iconColor = _bgImage.color;
iconMaterial = _bgImage.material;
iconSize = _bgImage.rectTransform.rect.size;
}
if (iconSprite == null)
{
return false;
}
_dragGhostObject = new GameObject("RepoItemDragGhost", typeof(RectTransform), typeof(CanvasGroup),
typeof(Image));
_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);
if (iconSize.x <= 0f || iconSize.y <= 0f)
{
iconSize = DefaultDragGhostSize;
}
_dragGhostRect.sizeDelta = iconSize;
Image ghostImage = _dragGhostObject.GetComponent<Image>();
ghostImage.raycastTarget = false;
ghostImage.sprite = iconSprite;
ghostImage.color = iconColor;
ghostImage.material = iconMaterial;
ghostImage.preserveAspect = true;
CanvasGroup ghostCanvasGroup = _dragGhostObject.GetComponent<CanvasGroup>();
ghostCanvasGroup.blocksRaycasts = false;
ghostCanvasGroup.interactable = false;
_dragGhostObject.transform.SetAsLastSibling();
return true;
}
protected virtual void UpdateDragGhostPosition(PointerEventData eventData)
{
if (_dragGhostRect == null || _dragRoot == null || eventData == null)
{
return;
}
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(_dragRoot, eventData.position,
eventData.pressEventCamera, out Vector2 localPoint))
{
_dragGhostRect.anchoredPosition = localPoint;
}
}
protected virtual void DestroyDragGhost()
{
if (_dragGhostObject != null)
{
Destroy(_dragGhostObject);
_dragGhostObject = null;
_dragGhostRect = null;
}
}
private void ResetDragState()
{
_isDragging = false;
_dropHandled = false;
_dropAssigned = false;
_dragRoot = null;
DestroyDragGhost();
if (_canvasGroup != null)
{
_canvasGroup.blocksRaycasts = true;
}
}
}
}
+112
View File
@@ -0,0 +1,112 @@
using System.Collections.Generic;
using GeometryTD.CustomEvent;
using TMPro;
using UnityEngine;
namespace GeometryTD.UI
{
public class SellArea : MonoBehaviour
{
[SerializeField] private Transform _content;
[SerializeField] private RepoItem _itemTemplate;
[SerializeField] private TowerRepoItem _towerItemTemplate;
[SerializeField] private TMP_Text _totalPrice;
private readonly List<RepoItem> _activeComponentItems = new List<RepoItem>();
private readonly List<TowerRepoItem> _activeTowerItems = new List<TowerRepoItem>();
private SellAreaContext _context;
public void OnInit(SellAreaContext context)
{
OnReset();
_context = context;
if (_totalPrice != null)
{
_totalPrice.text = context?.TotalPriceText ?? string.Empty;
}
if (context == null || _content == null)
{
return;
}
if (context.ComponentItems != null)
{
foreach (RepoItemContext itemContext in context.ComponentItems)
{
if (itemContext == null || _itemTemplate == null)
{
continue;
}
RepoItem item = Instantiate(_itemTemplate, _content);
item.gameObject.SetActive(true);
item.OnInit(itemContext);
_activeComponentItems.Add(item);
}
}
if (context.TowerItems != null)
{
foreach (TowerRepoItemContext itemContext in context.TowerItems)
{
if (itemContext == null || _towerItemTemplate == null)
{
continue;
}
TowerRepoItem item = Instantiate(_towerItemTemplate, _content);
item.gameObject.SetActive(true);
item.OnInit(itemContext);
_activeTowerItems.Add(item);
}
}
}
public void OnReset()
{
for (int i = _activeComponentItems.Count - 1; i >= 0; i--)
{
RepoItem item = _activeComponentItems[i];
if (item != null)
{
Destroy(item.gameObject);
}
}
for (int i = _activeTowerItems.Count - 1; i >= 0; i--)
{
TowerRepoItem item = _activeTowerItems[i];
if (item != null)
{
Destroy(item.gameObject);
}
}
_activeComponentItems.Clear();
_activeTowerItems.Clear();
_context = null;
if (_totalPrice != null)
{
_totalPrice.text = string.Empty;
}
}
public void OnCancelButtonClick()
{
GameEntry.Event.Fire(this, RepoSellCancelRequestedEventArgs.Create());
}
public void OnConfirmButtonClick()
{
if (_context == null || !_context.CanConfirmSell)
{
return;
}
GameEntry.Event.Fire(this, RepoSellConfirmRequestedEventArgs.Create());
}
}
}
+109
View File
@@ -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;
}
}
}