init
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public interface IUIFormController
|
||||
{
|
||||
int? OpenUI(object userData = null);
|
||||
void CloseUI();
|
||||
void BindUseCase(IUIUseCase useCase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public interface IUIUseCase
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public abstract class UGuiForm : UIFormLogic
|
||||
{
|
||||
public const int DepthFactor = 100;
|
||||
private const float FadeTime = 0.3f;
|
||||
|
||||
private static Font _mainFont = null;
|
||||
private static TMP_FontAsset _mainTMPFont = null;
|
||||
private Canvas _cachedCanvas = null;
|
||||
private CanvasGroup _canvasGroup = null;
|
||||
private List<Canvas> _cachedCanvasContainer = new List<Canvas>();
|
||||
|
||||
public int OriginalDepth { get; private set; }
|
||||
|
||||
public int Depth => _cachedCanvas.sortingOrder;
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Close(false);
|
||||
}
|
||||
|
||||
public void Close(bool ignoreFade)
|
||||
{
|
||||
StopAllCoroutines();
|
||||
|
||||
if (ignoreFade)
|
||||
{
|
||||
GameEntry.UI.CloseUIForm(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
StartCoroutine(CloseCo(FadeTime));
|
||||
}
|
||||
}
|
||||
|
||||
public void PlayUISound(int uiSoundId)
|
||||
{
|
||||
GameEntry.Sound.PlayUISound(uiSoundId);
|
||||
}
|
||||
|
||||
public static void SetMainFont(Font mainFont)
|
||||
{
|
||||
if (mainFont == null)
|
||||
{
|
||||
Log.Error("Main font is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
_mainFont = mainFont;
|
||||
}
|
||||
|
||||
public static void SetMainTMPFont(TMP_FontAsset mainTMPFont)
|
||||
{
|
||||
if (mainTMPFont == null)
|
||||
{
|
||||
Log.Error("Main font is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
_mainTMPFont = mainTMPFont;
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnInit(object userData)
|
||||
#else
|
||||
protected internal override void OnInit(object userData)
|
||||
#endif
|
||||
{
|
||||
base.OnInit(userData);
|
||||
|
||||
_cachedCanvas = gameObject.GetOrAddComponent<Canvas>();
|
||||
_cachedCanvas.overrideSorting = true;
|
||||
OriginalDepth = _cachedCanvas.sortingOrder;
|
||||
|
||||
_canvasGroup = gameObject.GetOrAddComponent<CanvasGroup>();
|
||||
|
||||
RectTransform rect = GetComponent<RectTransform>();
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.anchoredPosition = Vector2.zero;
|
||||
rect.sizeDelta = Vector2.zero;
|
||||
|
||||
gameObject.GetOrAddComponent<GraphicRaycaster>();
|
||||
|
||||
if (_mainTMPFont == null)
|
||||
{
|
||||
Log.Warning("Main TMP font isn't set.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var tmp_texts = GetComponentsInChildren<TMP_Text>(true);
|
||||
foreach (var text in tmp_texts)
|
||||
{
|
||||
text.font = _mainTMPFont;
|
||||
}
|
||||
}
|
||||
|
||||
if (_mainFont == null)
|
||||
{
|
||||
Log.Warning("Main font isn't set.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var texts = GetComponentsInChildren<Text>(true);
|
||||
foreach (var text in texts)
|
||||
{
|
||||
text.font = _mainFont;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnRecycle()
|
||||
#else
|
||||
protected internal override void OnRecycle()
|
||||
#endif
|
||||
{
|
||||
base.OnRecycle();
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnOpen(object userData)
|
||||
#else
|
||||
protected internal override void OnOpen(object userData)
|
||||
#endif
|
||||
{
|
||||
base.OnOpen(userData);
|
||||
|
||||
_canvasGroup.alpha = 0f;
|
||||
StopAllCoroutines();
|
||||
StartCoroutine(_canvasGroup.FadeToAlpha(1f, FadeTime));
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnClose(bool isShutdown, object userData)
|
||||
#else
|
||||
protected internal override void OnClose(bool isShutdown, object userData)
|
||||
#endif
|
||||
{
|
||||
base.OnClose(isShutdown, userData);
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnPause()
|
||||
#else
|
||||
protected internal override void OnPause()
|
||||
#endif
|
||||
{
|
||||
base.OnPause();
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnResume()
|
||||
#else
|
||||
protected internal override void OnResume()
|
||||
#endif
|
||||
{
|
||||
base.OnResume();
|
||||
|
||||
_canvasGroup.alpha = 0f;
|
||||
StopAllCoroutines();
|
||||
StartCoroutine(_canvasGroup.FadeToAlpha(1f, FadeTime));
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnCover()
|
||||
#else
|
||||
protected internal override void OnCover()
|
||||
#endif
|
||||
{
|
||||
base.OnCover();
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnReveal()
|
||||
#else
|
||||
protected internal override void OnReveal()
|
||||
#endif
|
||||
{
|
||||
base.OnReveal();
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnRefocus(object userData)
|
||||
#else
|
||||
protected internal override void OnRefocus(object userData)
|
||||
#endif
|
||||
{
|
||||
base.OnRefocus(userData);
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
#else
|
||||
protected internal override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
#endif
|
||||
{
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnDepthChanged(int uiGroupDepth, int depthInUIGroup)
|
||||
#else
|
||||
protected internal override void OnDepthChanged(int uiGroupDepth, int depthInUIGroup)
|
||||
#endif
|
||||
{
|
||||
int oldDepth = Depth;
|
||||
base.OnDepthChanged(uiGroupDepth, depthInUIGroup);
|
||||
int deltaDepth = UGuiGroupHelper.DepthFactor * uiGroupDepth + DepthFactor * depthInUIGroup - oldDepth +
|
||||
OriginalDepth;
|
||||
GetComponentsInChildren(true, _cachedCanvasContainer);
|
||||
for (int i = 0; i < _cachedCanvasContainer.Count; i++)
|
||||
{
|
||||
_cachedCanvasContainer[i].sortingOrder += deltaDepth;
|
||||
}
|
||||
|
||||
_cachedCanvasContainer.Clear();
|
||||
}
|
||||
|
||||
private IEnumerator CloseCo(float duration)
|
||||
{
|
||||
yield return _canvasGroup.FadeToAlpha(0f, duration);
|
||||
GameEntry.UI.CloseUIForm(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// uGUI 界面组辅助器。
|
||||
/// </summary>
|
||||
public class UGuiGroupHelper : UIGroupHelperBase
|
||||
{
|
||||
public const int DepthFactor = 10000;
|
||||
|
||||
private int m_Depth = 0;
|
||||
private Canvas m_CachedCanvas = null;
|
||||
|
||||
/// <summary>
|
||||
/// 设置界面组深度。
|
||||
/// </summary>
|
||||
/// <param name="depth">界面组深度。</param>
|
||||
public override void SetDepth(int depth)
|
||||
{
|
||||
m_Depth = depth;
|
||||
m_CachedCanvas.overrideSorting = true;
|
||||
m_CachedCanvas.sortingOrder = DepthFactor * depth;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_CachedCanvas = gameObject.GetOrAddComponent<Canvas>();
|
||||
gameObject.GetOrAddComponent<GraphicRaycaster>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
m_CachedCanvas.overrideSorting = true;
|
||||
m_CachedCanvas.sortingOrder = DepthFactor * m_Depth;
|
||||
|
||||
RectTransform transform = GetComponent<RectTransform>();
|
||||
transform.anchorMin = Vector2.zero;
|
||||
transform.anchorMax = Vector2.one;
|
||||
transform.anchoredPosition = Vector2.zero;
|
||||
transform.sizeDelta = Vector2.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class UIContext
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
using GameFramework.DataTable;
|
||||
using GameFramework.UI;
|
||||
using System.Collections;
|
||||
using GeometryTD.Procedure;
|
||||
using GeometryTD;
|
||||
using GeometryTD.CustomUtility;
|
||||
using GeometryTD.DataTable;
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public static class UIExtension
|
||||
{
|
||||
public static IEnumerator FadeToAlpha(this CanvasGroup canvasGroup, float alpha, float duration)
|
||||
{
|
||||
float time = 0f;
|
||||
float originalAlpha = canvasGroup.alpha;
|
||||
while (time < duration)
|
||||
{
|
||||
time += Time.deltaTime;
|
||||
canvasGroup.alpha = Mathf.Lerp(originalAlpha, alpha, time / duration);
|
||||
yield return new WaitForEndOfFrame();
|
||||
}
|
||||
|
||||
canvasGroup.alpha = alpha;
|
||||
}
|
||||
|
||||
public static IEnumerator SmoothValue(this Slider slider, float value, float duration)
|
||||
{
|
||||
float time = 0f;
|
||||
float originalValue = slider.value;
|
||||
while (time < duration)
|
||||
{
|
||||
time += Time.deltaTime;
|
||||
slider.value = Mathf.Lerp(originalValue, value, time / duration);
|
||||
yield return new WaitForEndOfFrame();
|
||||
}
|
||||
|
||||
slider.value = value;
|
||||
}
|
||||
|
||||
public static bool HasUIForm(this UIComponent uiComponent, UIFormType uiFormId, string uiGroupName = null)
|
||||
{
|
||||
return uiComponent.HasUIForm((int)uiFormId, uiGroupName);
|
||||
}
|
||||
|
||||
public static bool HasUIForm(this UIComponent uiComponent, int uiFormId, string uiGroupName = null)
|
||||
{
|
||||
IDataTable<DRUIForm> dtUIForm = GameEntry.DataTable.GetDataTable<DRUIForm>();
|
||||
DRUIForm drUIForm = dtUIForm.GetDataRow(uiFormId);
|
||||
if (drUIForm == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string assetName = AssetUtility.GetUIFormAsset(drUIForm.AssetName);
|
||||
if (string.IsNullOrEmpty(uiGroupName))
|
||||
{
|
||||
return uiComponent.HasUIForm(assetName);
|
||||
}
|
||||
|
||||
IUIGroup uiGroup = uiComponent.GetUIGroup(uiGroupName);
|
||||
if (uiGroup == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return uiGroup.HasUIForm(assetName);
|
||||
}
|
||||
|
||||
public static UGuiForm GetUIForm(this UIComponent uiComponent, UIFormType uiFormId, string uiGroupName = null)
|
||||
{
|
||||
return uiComponent.GetUIForm((int)uiFormId, uiGroupName);
|
||||
}
|
||||
|
||||
public static UGuiForm GetUIForm(this UIComponent uiComponent, int uiFormId, string uiGroupName = null)
|
||||
{
|
||||
IDataTable<DRUIForm> dtUIForm = GameEntry.DataTable.GetDataTable<DRUIForm>();
|
||||
DRUIForm drUIForm = dtUIForm.GetDataRow(uiFormId);
|
||||
if (drUIForm == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string assetName = AssetUtility.GetUIFormAsset(drUIForm.AssetName);
|
||||
UIForm uiForm = null;
|
||||
if (string.IsNullOrEmpty(uiGroupName))
|
||||
{
|
||||
uiForm = uiComponent.GetUIForm(assetName);
|
||||
if (uiForm == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (UGuiForm)uiForm.Logic;
|
||||
}
|
||||
|
||||
IUIGroup uiGroup = uiComponent.GetUIGroup(uiGroupName);
|
||||
if (uiGroup == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
uiForm = (UIForm)uiGroup.GetUIForm(assetName);
|
||||
if (uiForm == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (UGuiForm)uiForm.Logic;
|
||||
}
|
||||
|
||||
public static void CloseUIForm(this UIComponent uiComponent, UGuiForm uiForm)
|
||||
{
|
||||
uiComponent.CloseUIForm(uiForm.UIForm);
|
||||
}
|
||||
|
||||
public static int? OpenUIForm(this UIComponent uiComponent, UIFormType uiFormId, object userData = null)
|
||||
{
|
||||
return uiComponent.OpenUIForm((int)uiFormId, userData);
|
||||
}
|
||||
|
||||
public static int? OpenUIForm(this UIComponent uiComponent, int uiFormId, object userData = null)
|
||||
{
|
||||
IDataTable<DRUIForm> dtUIForm = GameEntry.DataTable.GetDataTable<DRUIForm>();
|
||||
DRUIForm drUIForm = dtUIForm.GetDataRow(uiFormId);
|
||||
if (drUIForm == null)
|
||||
{
|
||||
Log.Warning("Can not load UI form '{0}' from data table.", uiFormId.ToString());
|
||||
return null;
|
||||
}
|
||||
|
||||
string assetName = AssetUtility.GetUIFormAsset(drUIForm.AssetName);
|
||||
if (!drUIForm.AllowMultiInstance)
|
||||
{
|
||||
if (uiComponent.IsLoadingUIForm(assetName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (uiComponent.HasUIForm(assetName))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return uiComponent.OpenUIForm(assetName, drUIForm.UIGroupName, Constant.AssetPriority.UIFormAsset,
|
||||
drUIForm.PauseCoveredUIForm, userData);
|
||||
}
|
||||
|
||||
public static void OpenDialog(this UIComponent uiComponent, DialogParams dialogParams)
|
||||
{
|
||||
if (((ProcedureBase)GameEntry.Procedure.CurrentProcedure).UseNativeDialog)
|
||||
{
|
||||
OpenNativeDialog(dialogParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
uiComponent.OpenUIForm(UIFormType.DialogForm, dialogParams);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OpenNativeDialog(DialogParams dialogParams)
|
||||
{
|
||||
// TODO:这里应该弹出原生对话框,先简化实现为直接按确认按钮
|
||||
if (dialogParams.OnClickConfirm != null)
|
||||
{
|
||||
dialogParams.OnClickConfirm(dialogParams.UserData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public abstract class UIFormControllerBase<TContext> : IUIFormController where TContext : UIContext
|
||||
{
|
||||
protected abstract int? OpenUIInternal(TContext context);
|
||||
public abstract int? OpenUI(object userData = null);
|
||||
public abstract void CloseUI();
|
||||
public abstract void BindUseCase(IUIUseCase useCase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
using GeometryTD.Definition;
|
||||
using GameFramework.Event;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public abstract class UIFormControllerCommonBase<TContext, TForm> : UIFormControllerBase<TContext>
|
||||
where TContext : UIContext
|
||||
where TForm : UGuiForm
|
||||
{
|
||||
private TContext _context;
|
||||
private TForm _form;
|
||||
private int? _formSerialId;
|
||||
private bool _pendingRefresh;
|
||||
private bool _isBindEvent;
|
||||
|
||||
protected TContext Context => _context;
|
||||
|
||||
protected TForm Form => _form;
|
||||
|
||||
protected int? FormSerialId => _formSerialId;
|
||||
|
||||
protected abstract UIFormType UIFormTypeId { get; }
|
||||
|
||||
protected abstract void RefreshUI(TForm form, TContext context);
|
||||
|
||||
protected virtual void SubscribeCustomEvents()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void UnsubscribeCustomEvents()
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual void CloseLoadedFormDirect(TForm form)
|
||||
{
|
||||
form.Close();
|
||||
}
|
||||
|
||||
protected void SetContext(TContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
protected void RefreshCurrentUI()
|
||||
{
|
||||
if (_context == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_form == null)
|
||||
{
|
||||
_pendingRefresh = true;
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshUI(_form, _context);
|
||||
_pendingRefresh = false;
|
||||
}
|
||||
|
||||
protected override int? OpenUIInternal(TContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
Log.Warning("{0}.OpenUI() context is null.", GetType().Name);
|
||||
return null;
|
||||
}
|
||||
|
||||
_context = context;
|
||||
|
||||
if (_form != null && _formSerialId.HasValue && GameEntry.UI.HasUIForm(_formSerialId.Value))
|
||||
{
|
||||
RefreshUI(_form, _context);
|
||||
return _formSerialId;
|
||||
}
|
||||
|
||||
CloseUI();
|
||||
_pendingRefresh = true;
|
||||
SubscribeEvents();
|
||||
_formSerialId = GameEntry.UI.OpenUIForm(UIFormTypeId, _context);
|
||||
return _formSerialId;
|
||||
}
|
||||
|
||||
public override void CloseUI()
|
||||
{
|
||||
_pendingRefresh = false;
|
||||
UnsubscribeEvents();
|
||||
|
||||
if (_formSerialId.HasValue)
|
||||
{
|
||||
if (GameEntry.UI.HasUIForm(_formSerialId.Value))
|
||||
{
|
||||
GameEntry.UI.CloseUIForm(_formSerialId.Value);
|
||||
}
|
||||
|
||||
_form = null;
|
||||
_formSerialId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_form != null)
|
||||
{
|
||||
CloseLoadedFormDirect(_form);
|
||||
_form = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void SubscribeEvents()
|
||||
{
|
||||
if (_isBindEvent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess);
|
||||
GameEntry.Event.Subscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete);
|
||||
SubscribeCustomEvents();
|
||||
|
||||
_isBindEvent = true;
|
||||
}
|
||||
|
||||
private void UnsubscribeEvents()
|
||||
{
|
||||
if (!_isBindEvent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormSuccess);
|
||||
GameEntry.Event.Unsubscribe(CloseUIFormCompleteEventArgs.EventId, CloseUIFormComplete);
|
||||
UnsubscribeCustomEvents();
|
||||
|
||||
_isBindEvent = false;
|
||||
}
|
||||
|
||||
private void OpenUIFormSuccess(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(e is OpenUIFormSuccessEventArgs args))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_formSerialId.HasValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.UIForm == null || args.UIForm.SerialId != _formSerialId.Value || args.UserData != _context)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_form = args.UIForm.Logic as TForm;
|
||||
if (_form == null)
|
||||
{
|
||||
Log.Warning("{0} open success but form logic is invalid.", GetType().Name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_pendingRefresh)
|
||||
{
|
||||
RefreshCurrentUI();
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseUIFormComplete(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(e is CloseUIFormCompleteEventArgs args))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.SerialId != _formSerialId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_form = null;
|
||||
_formSerialId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class CombatFinishFormContext : UIContext
|
||||
{
|
||||
public string EnemyKilledText;
|
||||
public string GoldGainedText;
|
||||
public RepoItemContext[] RewardItems;
|
||||
public bool CanReturn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class CombatInfoFormContext : UIContext
|
||||
{
|
||||
public string LevelMetaText;
|
||||
public string LevelPhaseText;
|
||||
public string CoinText;
|
||||
public string BaseHpText;
|
||||
public string EnemyHpRateText;
|
||||
public bool CanPause;
|
||||
public bool CanEnd;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public enum CombatSelectActionType : byte
|
||||
{
|
||||
None = 0,
|
||||
BuildTower = 1,
|
||||
UpgradeTower = 2,
|
||||
DestroyTower = 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class CombatSelectFormContext : UIContext
|
||||
{
|
||||
public bool IsVisible;
|
||||
public Vector2 ScreenPosition;
|
||||
public bool ShowBuildArea;
|
||||
public bool ShowFuncArea;
|
||||
public TowerSelectItemContext[] BuildItems;
|
||||
public TowerSelectItemContext UpgradeItem;
|
||||
public TowerSelectItemContext DestroyItem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class TowerSelectItemContext : UIContext
|
||||
{
|
||||
public Sprite Icon;
|
||||
public string PriceText;
|
||||
public bool IsVisible;
|
||||
public bool IsInteractable;
|
||||
public CombatSelectActionType ActionType;
|
||||
public int ActionIndex;
|
||||
public TowerIconAreaContext TowerIconAreaContext;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.CustomUtility;
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public partial class CombatFinishFormController
|
||||
{
|
||||
private CombatFinishFormContext BuildContext(CombatFinishFormRawData rawData)
|
||||
{
|
||||
_itemDescSeedMap.Clear();
|
||||
if (rawData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CombatFinishFormContext
|
||||
{
|
||||
EnemyKilledText = rawData.DefeatedEnemyCount.ToString(),
|
||||
GoldGainedText = rawData.GainedGold.ToString(),
|
||||
RewardItems = BuildRewardItems(rawData.RewardInventory),
|
||||
CanReturn = rawData.CanReturn
|
||||
};
|
||||
}
|
||||
|
||||
private RepoItemContext[] BuildRewardItems(BackpackInventoryData inventory)
|
||||
{
|
||||
if (inventory == null)
|
||||
{
|
||||
return System.Array.Empty<RepoItemContext>();
|
||||
}
|
||||
|
||||
Dictionary<long, MuzzleCompItemData> muzzleMap = BuildComponentMap(inventory.MuzzleComponents);
|
||||
Dictionary<long, BearingCompItemData> bearingMap = BuildComponentMap(inventory.BearingComponents);
|
||||
Dictionary<long, BaseCompItemData> baseMap = BuildComponentMap(inventory.BaseComponents);
|
||||
List<RepoItemContext> itemContexts = new List<RepoItemContext>();
|
||||
|
||||
if (inventory.Towers != null)
|
||||
{
|
||||
foreach (TowerItemData tower in inventory.Towers)
|
||||
{
|
||||
if (tower == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
itemContexts.Add(new RepoItemContext
|
||||
{
|
||||
InstanceId = tower.InstanceId,
|
||||
CanDrag = false,
|
||||
EnduranceRate01 = ItemDescUtility.ResolveTowerEnduranceRate(tower, muzzleMap, bearingMap, baseMap),
|
||||
ClickActionType = RepoItemClickActionType.OpenDetail,
|
||||
ComponentSlotType = TowerCompSlotType.None,
|
||||
IconAreaContext = BuildTowerIconContext(tower, muzzleMap, bearingMap, baseMap)
|
||||
});
|
||||
|
||||
AddItemDescSeed(
|
||||
tower.InstanceId,
|
||||
tower.Name,
|
||||
"Tower",
|
||||
ItemDescUtility.BuildTowerDesc(tower, muzzleMap, bearingMap, baseMap) ?? string.Empty,
|
||||
tower.Stats?.Tags,
|
||||
tower.Stats?.TagRuntimes);
|
||||
}
|
||||
}
|
||||
|
||||
if (inventory.MuzzleComponents != null)
|
||||
{
|
||||
foreach (MuzzleCompItemData item in inventory.MuzzleComponents)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
itemContexts.Add(new RepoItemContext
|
||||
{
|
||||
InstanceId = item.InstanceId,
|
||||
CanDrag = false,
|
||||
EnduranceRate01 = ItemDescUtility.ResolveComponentEnduranceRate(item),
|
||||
ClickActionType = RepoItemClickActionType.OpenDetail,
|
||||
ComponentSlotType = TowerCompSlotType.Muzzle,
|
||||
IconAreaContext = BuildIconAreaContext(item)
|
||||
});
|
||||
|
||||
AddItemDescSeed(
|
||||
item.InstanceId,
|
||||
item.Name,
|
||||
BuildComponentTypeText(item.SlotType),
|
||||
ItemDescUtility.BuildMuzzleDesc(item) ?? string.Empty,
|
||||
item.Tags,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
if (inventory.BearingComponents != null)
|
||||
{
|
||||
foreach (BearingCompItemData item in inventory.BearingComponents)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
itemContexts.Add(new RepoItemContext
|
||||
{
|
||||
InstanceId = item.InstanceId,
|
||||
CanDrag = false,
|
||||
EnduranceRate01 = ItemDescUtility.ResolveComponentEnduranceRate(item),
|
||||
ClickActionType = RepoItemClickActionType.OpenDetail,
|
||||
ComponentSlotType = TowerCompSlotType.Bearing,
|
||||
IconAreaContext = BuildIconAreaContext(item)
|
||||
});
|
||||
|
||||
AddItemDescSeed(
|
||||
item.InstanceId,
|
||||
item.Name,
|
||||
BuildComponentTypeText(item.SlotType),
|
||||
ItemDescUtility.BuildBearingDesc(item) ?? string.Empty,
|
||||
item.Tags,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
if (inventory.BaseComponents != null)
|
||||
{
|
||||
foreach (BaseCompItemData item in inventory.BaseComponents)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
itemContexts.Add(new RepoItemContext
|
||||
{
|
||||
InstanceId = item.InstanceId,
|
||||
CanDrag = false,
|
||||
EnduranceRate01 = ItemDescUtility.ResolveComponentEnduranceRate(item),
|
||||
ClickActionType = RepoItemClickActionType.OpenDetail,
|
||||
ComponentSlotType = TowerCompSlotType.Base,
|
||||
IconAreaContext = BuildIconAreaContext(item)
|
||||
});
|
||||
|
||||
AddItemDescSeed(
|
||||
item.InstanceId,
|
||||
item.Name,
|
||||
BuildComponentTypeText(item.SlotType),
|
||||
ItemDescUtility.BuildBaseDesc(item) ?? string.Empty,
|
||||
item.Tags,
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
return itemContexts.ToArray();
|
||||
}
|
||||
|
||||
private static TowerIconAreaContext BuildTowerIconContext(
|
||||
TowerItemData tower,
|
||||
IReadOnlyDictionary<long, MuzzleCompItemData> muzzleMap,
|
||||
IReadOnlyDictionary<long, BearingCompItemData> bearingMap,
|
||||
IReadOnlyDictionary<long, BaseCompItemData> baseMap)
|
||||
{
|
||||
if (tower == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TowerIconAreaContext
|
||||
{
|
||||
Rarity = tower.Rarity,
|
||||
MuzzleColor = ResolveComponentColor(tower.MuzzleComponentInstanceId, muzzleMap),
|
||||
BearingColor = ResolveComponentColor(tower.BearingComponentInstanceId, bearingMap),
|
||||
BaseColor = ResolveComponentColor(tower.BaseComponentInstanceId, baseMap)
|
||||
};
|
||||
}
|
||||
|
||||
private static Color ResolveComponentColor<TComp>(long instanceId, IReadOnlyDictionary<long, TComp> componentMap)
|
||||
where TComp : TowerCompItemData
|
||||
{
|
||||
if (instanceId > 0 && componentMap != null && componentMap.TryGetValue(instanceId, out TComp comp) &&
|
||||
comp != null)
|
||||
{
|
||||
return IconColorGenerator.GenerateForComponent(comp);
|
||||
}
|
||||
|
||||
return Color.white;
|
||||
}
|
||||
|
||||
private void AddItemDescSeed(
|
||||
long itemId,
|
||||
string title,
|
||||
string typeText,
|
||||
string description,
|
||||
TagType[] tags,
|
||||
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 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public partial class CombatInfoFormController
|
||||
{
|
||||
private static CombatInfoFormContext BuildContext(CombatInfoFormRawData rawData)
|
||||
{
|
||||
if (rawData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CombatInfoFormContext
|
||||
{
|
||||
LevelMetaText = BuildLevelMetaText(rawData.LevelThemeType, rawData.LevelId),
|
||||
LevelPhaseText = BuildPhaseText(rawData.CurrentPhaseIndex, rawData.TotalPhaseCount),
|
||||
CoinText = BuildCoinText(rawData.Coin),
|
||||
BaseHpText = BuildBaseHpText(rawData.BaseHp, rawData.BaseHpMax),
|
||||
EnemyHpRateText = BuildEnemyHpRateText(rawData.EnemyHpRateMultiplier),
|
||||
CanPause = rawData.CanPause,
|
||||
CanEnd = rawData.CanEnd
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildLevelMetaText(LevelThemeType themeType, int levelId)
|
||||
{
|
||||
if (levelId <= 0)
|
||||
{
|
||||
return "Map -";
|
||||
}
|
||||
|
||||
return $"{themeType} Map.{levelId}";
|
||||
}
|
||||
|
||||
private static string BuildPhaseText(int currentPhaseIndex, int totalPhaseCount)
|
||||
{
|
||||
int phaseIndex = currentPhaseIndex < 0 ? 0 : currentPhaseIndex;
|
||||
if (totalPhaseCount <= 0)
|
||||
{
|
||||
return $"{phaseIndex}/?";
|
||||
}
|
||||
|
||||
return $"Wave: {phaseIndex}/{totalPhaseCount}";
|
||||
}
|
||||
|
||||
private static string BuildCoinText(int coin)
|
||||
{
|
||||
if (coin < 0)
|
||||
{
|
||||
return "Coin: -";
|
||||
}
|
||||
|
||||
return $"Coin: {coin}";
|
||||
}
|
||||
|
||||
private static string BuildBaseHpText(int baseHp, int baseHpMax)
|
||||
{
|
||||
if (baseHpMax <= 0)
|
||||
{
|
||||
return "\u57FA\u5730\uFF1A-";
|
||||
}
|
||||
|
||||
int clampedHp = Mathf.Clamp(baseHp, 0, baseHpMax);
|
||||
int percent = Mathf.RoundToInt((float)clampedHp / baseHpMax * 100f);
|
||||
return $"\u57FA\u5730\uFF1A{percent}%";
|
||||
}
|
||||
|
||||
private static string BuildEnemyHpRateText(int enemyHpRateMultiplier)
|
||||
{
|
||||
int resolvedMultiplier = enemyHpRateMultiplier > 0 ? enemyHpRateMultiplier : 1;
|
||||
return $"\u96BE\u5EA6\uFF1A{resolvedMultiplier}x";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public partial class CombatSelectFormController
|
||||
{
|
||||
private static CombatSelectFormContext BuildContext(CombatSelectFormRawData rawData)
|
||||
{
|
||||
if (rawData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CombatSelectFormContext
|
||||
{
|
||||
IsVisible = rawData.IsVisible,
|
||||
ScreenPosition = rawData.ScreenPosition,
|
||||
ShowBuildArea = rawData.IsVisible && rawData.DisplayMode == CombatSelectDisplayMode.Build,
|
||||
ShowFuncArea = rawData.IsVisible && rawData.DisplayMode == CombatSelectDisplayMode.Func,
|
||||
BuildItems = BuildItemContexts(rawData.BuildItems),
|
||||
UpgradeItem = BuildItemContext(rawData.UpgradeItem),
|
||||
DestroyItem = BuildItemContext(rawData.DestroyItem)
|
||||
};
|
||||
}
|
||||
|
||||
private static TowerSelectItemContext[] BuildItemContexts(TowerSelectItemRawData[] itemRawData)
|
||||
{
|
||||
if (itemRawData == null || itemRawData.Length <= 0)
|
||||
{
|
||||
return System.Array.Empty<TowerSelectItemContext>();
|
||||
}
|
||||
|
||||
TowerSelectItemContext[] contexts = new TowerSelectItemContext[itemRawData.Length];
|
||||
for (int i = 0; i < itemRawData.Length; i++)
|
||||
{
|
||||
contexts[i] = BuildItemContext(itemRawData[i]);
|
||||
}
|
||||
|
||||
return contexts;
|
||||
}
|
||||
|
||||
private static TowerSelectItemContext BuildItemContext(TowerSelectItemRawData rawData)
|
||||
{
|
||||
if (rawData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TowerSelectItemContext
|
||||
{
|
||||
Icon = rawData.Icon,
|
||||
PriceText = BuildPriceText(rawData.Price, rawData.IsGain),
|
||||
IsVisible = rawData.IsVisible,
|
||||
IsInteractable = rawData.IsInteractable,
|
||||
ActionType = rawData.ActionType,
|
||||
ActionIndex = rawData.ActionIndex,
|
||||
TowerIconAreaContext = new TowerIconAreaContext
|
||||
{
|
||||
BaseColor = rawData.BaseColor,
|
||||
BearingColor = rawData.BearingColor,
|
||||
MuzzleColor = rawData.MuzzleColor
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildPriceText(int price, bool isGain)
|
||||
{
|
||||
int positivePrice = Mathf.Max(0, price);
|
||||
if (isGain)
|
||||
{
|
||||
return $"+{positivePrice}";
|
||||
}
|
||||
|
||||
return positivePrice.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
using System.Collections.Generic;
|
||||
using GameFramework.Event;
|
||||
using GeometryTD.CustomEvent;
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public partial class CombatFinishFormController : UIFormControllerCommonBase<CombatFinishFormContext, CombatFinishForm>
|
||||
{
|
||||
private CombatFinishFormUseCase _useCase;
|
||||
private readonly Dictionary<long, ItemDescSeed> _itemDescSeedMap = new Dictionary<long, ItemDescSeed>();
|
||||
|
||||
private sealed class ItemDescSeed
|
||||
{
|
||||
public string Title;
|
||||
public string TypeText;
|
||||
public string Description;
|
||||
public TagType[] Tags;
|
||||
public TagRuntimeData[] TagRuntimes;
|
||||
}
|
||||
|
||||
protected override UIFormType UIFormTypeId => UIFormType.CombatFinishForm;
|
||||
|
||||
protected override void RefreshUI(CombatFinishForm form, CombatFinishFormContext context)
|
||||
{
|
||||
form.RefreshUI(context);
|
||||
}
|
||||
|
||||
protected override void SubscribeCustomEvents()
|
||||
{
|
||||
GameEntry.Event.Subscribe(CombatFinishReturnEventArgs.EventId, OnCombatFinishReturnButtonClicked);
|
||||
GameEntry.Event.Subscribe(RepoItemClickedEventArgs.EventId, OnRepoItemClicked);
|
||||
}
|
||||
|
||||
protected override void UnsubscribeCustomEvents()
|
||||
{
|
||||
GameEntry.Event.Unsubscribe(CombatFinishReturnEventArgs.EventId, OnCombatFinishReturnButtonClicked);
|
||||
GameEntry.Event.Unsubscribe(RepoItemClickedEventArgs.EventId, OnRepoItemClicked);
|
||||
}
|
||||
|
||||
public override int? OpenUI(object userData = null)
|
||||
{
|
||||
if (userData is CombatFinishFormContext context)
|
||||
{
|
||||
return OpenUIInternal(context);
|
||||
}
|
||||
|
||||
if (userData is CombatFinishFormRawData rawDataFromUserData)
|
||||
{
|
||||
return OpenUI(rawDataFromUserData);
|
||||
}
|
||||
|
||||
if (userData != null)
|
||||
{
|
||||
Log.Warning("CombatFinishFormController.OpenUI() userData type is invalid.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_useCase == null)
|
||||
{
|
||||
Log.Error("CombatFinishFormController.OpenUI() useCase is null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
CombatFinishFormRawData rawData = _useCase.CreateInitialModel();
|
||||
return OpenUI(rawData);
|
||||
}
|
||||
|
||||
public int? OpenUI(CombatFinishFormRawData rawData)
|
||||
{
|
||||
CombatFinishFormContext context = BuildContext(rawData);
|
||||
return OpenUIInternal(context);
|
||||
}
|
||||
|
||||
public override void BindUseCase(IUIUseCase useCase)
|
||||
{
|
||||
if (!(useCase is CombatFinishFormUseCase combatFinishFormUseCase))
|
||||
{
|
||||
Log.Error("CombatFinishFormController.BindUseCase() useCase is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
_useCase = combatFinishFormUseCase;
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
private void OnCombatFinishReturnButtonClicked(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!IsEventFromCurrentForm(sender) || !(e is CombatFinishReturnEventArgs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_useCase?.TryReturnToMenu();
|
||||
}
|
||||
|
||||
private void OnRepoItemClicked(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!IsEventFromCurrentForm(sender))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(e is RepoItemClickedEventArgs args))
|
||||
{
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private bool IsEventFromCurrentForm(object sender)
|
||||
{
|
||||
if (Form == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(sender, Form))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sender is Component component)
|
||||
{
|
||||
CombatFinishForm ownerForm = component.GetComponentInParent<CombatFinishForm>();
|
||||
return ownerForm == Form;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
using GeometryTD.CustomEvent;
|
||||
using GeometryTD.Definition;
|
||||
using GameFramework.Event;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public partial class CombatInfoFormController : UIFormControllerCommonBase<CombatInfoFormContext, CombatInfoForm>
|
||||
{
|
||||
private CombatInfoFormUseCase _useCase;
|
||||
|
||||
protected override UIFormType UIFormTypeId => UIFormType.CombatInfoForm;
|
||||
|
||||
protected override void RefreshUI(CombatInfoForm form, CombatInfoFormContext context)
|
||||
{
|
||||
form.RefreshUI(context);
|
||||
}
|
||||
|
||||
protected override void SubscribeCustomEvents()
|
||||
{
|
||||
GameEntry.Event.Subscribe(CombatPauseEventArgs.EventId, OnCombatPauseButtonClicked);
|
||||
GameEntry.Event.Subscribe(CombatEndEventArgs.EventId, OnCombatEndButtonClicked);
|
||||
GameEntry.Event.Subscribe(CombatDebugFailEventArgs.EventId, OnCombatDebugFailButtonClicked);
|
||||
GameEntry.Event.Subscribe(CombatProcessEventArgs.EventId, OnCombatProcessChanged);
|
||||
GameEntry.Event.Subscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged);
|
||||
GameEntry.Event.Subscribe(CombatBaseHpChangedEventArgs.EventId, OnCombatBaseHpChanged);
|
||||
GameEntry.Event.Subscribe(CombatEnemyHpRateChangedEventArgs.EventId, OnCombatEnemyHpRateChanged);
|
||||
}
|
||||
|
||||
protected override void UnsubscribeCustomEvents()
|
||||
{
|
||||
GameEntry.Event.Unsubscribe(CombatPauseEventArgs.EventId, OnCombatPauseButtonClicked);
|
||||
GameEntry.Event.Unsubscribe(CombatEndEventArgs.EventId, OnCombatEndButtonClicked);
|
||||
GameEntry.Event.Unsubscribe(CombatDebugFailEventArgs.EventId, OnCombatDebugFailButtonClicked);
|
||||
GameEntry.Event.Unsubscribe(CombatProcessEventArgs.EventId, OnCombatProcessChanged);
|
||||
GameEntry.Event.Unsubscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged);
|
||||
GameEntry.Event.Unsubscribe(CombatBaseHpChangedEventArgs.EventId, OnCombatBaseHpChanged);
|
||||
GameEntry.Event.Unsubscribe(CombatEnemyHpRateChangedEventArgs.EventId, OnCombatEnemyHpRateChanged);
|
||||
}
|
||||
|
||||
public int? OpenUI(CombatInfoFormRawData rawData)
|
||||
{
|
||||
CombatInfoFormContext context = BuildContext(rawData);
|
||||
return OpenUIInternal(context);
|
||||
}
|
||||
|
||||
public override int? OpenUI(object userData = null)
|
||||
{
|
||||
if (userData is CombatInfoFormContext context)
|
||||
{
|
||||
return OpenUIInternal(context);
|
||||
}
|
||||
|
||||
if (userData is CombatInfoFormRawData rawDataFromUserData)
|
||||
{
|
||||
return OpenUI(rawDataFromUserData);
|
||||
}
|
||||
|
||||
if (userData != null)
|
||||
{
|
||||
Log.Warning("CombatInfoFormController.OpenUI() userData type is invalid.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_useCase == null)
|
||||
{
|
||||
Log.Error("CombatInfoFormController.OpenUI() useCase is null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
CombatInfoFormRawData rawData = _useCase.CreateInitialModel();
|
||||
return OpenUI(rawData);
|
||||
}
|
||||
|
||||
public override void BindUseCase(IUIUseCase useCase)
|
||||
{
|
||||
if (!(useCase is CombatInfoFormUseCase combatInfoFormUseCase))
|
||||
{
|
||||
Log.Error("CombatInfoFormController.BindUseCase() useCase is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
_useCase = combatInfoFormUseCase;
|
||||
}
|
||||
|
||||
private void OnCombatPauseButtonClicked(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(sender is CombatInfoForm) || !(e is CombatPauseEventArgs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.UIRouter.OpenUI(UIFormType.DialogForm, new DialogFormRawData
|
||||
{
|
||||
Mode = 1,
|
||||
Title = "Pause",
|
||||
Message = "Game paused.",
|
||||
PauseGame = true,
|
||||
ConfirmText = "Continue"
|
||||
});
|
||||
}
|
||||
|
||||
private void OnCombatEndButtonClicked(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(sender is CombatInfoForm) || !(e is CombatEndEventArgs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_useCase?.TryEndCombat();
|
||||
}
|
||||
|
||||
private void OnCombatDebugFailButtonClicked(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(sender is CombatInfoForm) || !(e is CombatDebugFailEventArgs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_useCase?.TryDebugFail();
|
||||
}
|
||||
|
||||
private void OnCombatProcessChanged(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(e is CombatProcessEventArgs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshFromUseCase();
|
||||
}
|
||||
|
||||
private void OnCombatCoinChanged(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(e is CombatCoinChangedEventArgs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshFromUseCase();
|
||||
}
|
||||
|
||||
private void OnCombatBaseHpChanged(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(e is CombatBaseHpChangedEventArgs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshFromUseCase();
|
||||
}
|
||||
|
||||
private void OnCombatEnemyHpRateChanged(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(e is CombatEnemyHpRateChangedEventArgs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshFromUseCase();
|
||||
}
|
||||
|
||||
private void RefreshFromUseCase()
|
||||
{
|
||||
if (_useCase == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CombatInfoFormRawData rawData = _useCase.TryRefresh();
|
||||
CombatInfoFormContext context = BuildContext(rawData);
|
||||
if (context == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetContext(context);
|
||||
RefreshCurrentUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
using GameFramework.Event;
|
||||
using GeometryTD.CustomEvent;
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public partial class CombatSelectFormController : UIFormControllerCommonBase<CombatSelectFormContext, CombatSelectForm>
|
||||
{
|
||||
private CombatSelectFormUseCase _useCase;
|
||||
|
||||
protected override UIFormType UIFormTypeId => UIFormType.CombatSelectForm;
|
||||
|
||||
protected override void RefreshUI(CombatSelectForm form, CombatSelectFormContext context)
|
||||
{
|
||||
form.RefreshUI(context);
|
||||
}
|
||||
|
||||
protected override void SubscribeCustomEvents()
|
||||
{
|
||||
GameEntry.Event.Subscribe(CombatSelectItemClickEventArgs.EventId, OnSelectItemClicked);
|
||||
GameEntry.Event.Subscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged);
|
||||
}
|
||||
|
||||
protected override void UnsubscribeCustomEvents()
|
||||
{
|
||||
GameEntry.Event.Unsubscribe(CombatSelectItemClickEventArgs.EventId, OnSelectItemClicked);
|
||||
GameEntry.Event.Unsubscribe(CombatCoinChangedEventArgs.EventId, OnCombatCoinChanged);
|
||||
}
|
||||
|
||||
public int? OpenUI(CombatSelectFormRawData rawData)
|
||||
{
|
||||
CombatSelectFormContext context = BuildContext(rawData);
|
||||
if (context == null || !context.IsVisible)
|
||||
{
|
||||
CloseUI();
|
||||
return null;
|
||||
}
|
||||
|
||||
return OpenUIInternal(context);
|
||||
}
|
||||
|
||||
public override int? OpenUI(object userData = null)
|
||||
{
|
||||
if (userData is CombatSelectFormUserData clickUserData)
|
||||
{
|
||||
return OpenUI(clickUserData);
|
||||
}
|
||||
|
||||
if (userData is CombatSelectFormContext context)
|
||||
{
|
||||
if (context == null || !context.IsVisible)
|
||||
{
|
||||
CloseUI();
|
||||
return null;
|
||||
}
|
||||
|
||||
return OpenUIInternal(context);
|
||||
}
|
||||
|
||||
if (userData is CombatSelectFormRawData rawDataFromUserData)
|
||||
{
|
||||
return OpenUI(rawDataFromUserData);
|
||||
}
|
||||
|
||||
if (userData != null)
|
||||
{
|
||||
Log.Warning("CombatSelectFormController.OpenUI() userData type is invalid.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_useCase == null)
|
||||
{
|
||||
Log.Error("CombatSelectFormController.OpenUI() useCase is null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
CombatSelectFormRawData rawData = _useCase.CreateInitialModel();
|
||||
return OpenUI(rawData);
|
||||
}
|
||||
|
||||
public int? OpenUI(CombatSelectFormUserData userData)
|
||||
{
|
||||
if (_useCase == null)
|
||||
{
|
||||
Log.Error("CombatSelectFormController.OpenUI() useCase is null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (userData == null)
|
||||
{
|
||||
_useCase.Hide();
|
||||
CloseUI();
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (userData.ClickObjectType)
|
||||
{
|
||||
case CombatSelectClickObjectType.Foundation:
|
||||
_useCase.SetUpgradeVisible(true);
|
||||
_useCase.ShowForFoundation(userData.ScreenPosition);
|
||||
break;
|
||||
case CombatSelectClickObjectType.Tower:
|
||||
_useCase.SetUpgradePrice(userData.UpgradeCost);
|
||||
_useCase.SetUpgradeVisible(!userData.IsTowerAtMaxLevel);
|
||||
_useCase.SetDestroyGain(userData.DestroyGain);
|
||||
_useCase.ShowForTower(userData.ScreenPosition);
|
||||
break;
|
||||
default:
|
||||
_useCase.Hide();
|
||||
CloseUI();
|
||||
return null;
|
||||
}
|
||||
|
||||
return OpenUI(_useCase.TryRefresh());
|
||||
}
|
||||
|
||||
public override void BindUseCase(IUIUseCase useCase)
|
||||
{
|
||||
if (!(useCase is CombatSelectFormUseCase combatSelectFormUseCase))
|
||||
{
|
||||
Log.Error("CombatSelectFormController.BindUseCase() useCase is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
_useCase = combatSelectFormUseCase;
|
||||
}
|
||||
|
||||
public int? ShowForFoundation(Vector2 screenPosition)
|
||||
{
|
||||
if (_useCase == null)
|
||||
{
|
||||
Log.Error("CombatSelectFormController.ShowForFoundation() useCase is null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
_useCase.SetUpgradeVisible(true);
|
||||
_useCase.ShowForFoundation(screenPosition);
|
||||
return OpenUI(_useCase.TryRefresh());
|
||||
}
|
||||
|
||||
public int? ShowForTower(Vector2 screenPosition, int upgradeCost, int destroyGain)
|
||||
{
|
||||
if (_useCase == null)
|
||||
{
|
||||
Log.Error("CombatSelectFormController.ShowForTower() useCase is null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
_useCase.SetUpgradePrice(upgradeCost);
|
||||
_useCase.SetUpgradeVisible(true);
|
||||
_useCase.SetDestroyGain(destroyGain);
|
||||
_useCase.ShowForTower(screenPosition);
|
||||
return OpenUI(_useCase.TryRefresh());
|
||||
}
|
||||
|
||||
public void HideSelection()
|
||||
{
|
||||
_useCase?.Hide();
|
||||
CloseUI();
|
||||
}
|
||||
|
||||
public void RefreshFromUseCase()
|
||||
{
|
||||
if (_useCase == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CombatSelectFormRawData rawData = _useCase.TryRefresh();
|
||||
CombatSelectFormContext context = BuildContext(rawData);
|
||||
if (context == null || !context.IsVisible)
|
||||
{
|
||||
CloseUI();
|
||||
return;
|
||||
}
|
||||
|
||||
SetContext(context);
|
||||
RefreshCurrentUI();
|
||||
}
|
||||
|
||||
private void OnSelectItemClicked(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(e is CombatSelectItemClickEventArgs args))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_useCase == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Form != null && sender is Component component)
|
||||
{
|
||||
CombatSelectForm ownerForm = component.GetComponentInParent<CombatSelectForm>();
|
||||
if (ownerForm != Form)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_useCase.TryExecuteAction(args.ActionType, args.ActionIndex);
|
||||
RefreshFromUseCase();
|
||||
}
|
||||
|
||||
private void OnCombatCoinChanged(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!(e is CombatCoinChangedEventArgs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Form == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshFromUseCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using GeometryTD.Definition;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class CombatFinishFormRawData
|
||||
{
|
||||
public int DefeatedEnemyCount;
|
||||
public int GainedGold;
|
||||
public BackpackInventoryData RewardInventory;
|
||||
public bool CanReturn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using GeometryTD.Definition;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class CombatInfoFormRawData
|
||||
{
|
||||
public LevelThemeType LevelThemeType;
|
||||
public int LevelId;
|
||||
public int CurrentPhaseIndex;
|
||||
public int TotalPhaseCount;
|
||||
public int Coin;
|
||||
public int BaseHp;
|
||||
public int BaseHpMax;
|
||||
public int EnemyHpRateMultiplier;
|
||||
public bool CanPause;
|
||||
public bool CanEnd;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public enum CombatSelectClickObjectType : byte
|
||||
{
|
||||
None = 0,
|
||||
Foundation = 1,
|
||||
Tower = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public enum CombatSelectDisplayMode : byte
|
||||
{
|
||||
Hidden = 0,
|
||||
Build = 1,
|
||||
Func = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class CombatSelectFormRawData
|
||||
{
|
||||
public bool IsVisible;
|
||||
public Vector2 ScreenPosition;
|
||||
public CombatSelectDisplayMode DisplayMode;
|
||||
public TowerSelectItemRawData[] BuildItems;
|
||||
public TowerSelectItemRawData UpgradeItem;
|
||||
public TowerSelectItemRawData DestroyItem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class CombatSelectFormUserData
|
||||
{
|
||||
public CombatSelectClickObjectType ClickObjectType;
|
||||
public Vector2 ScreenPosition;
|
||||
public Vector3 WorldPosition;
|
||||
public Vector3Int CellPosition;
|
||||
public int TowerEntityId;
|
||||
public bool IsTowerAtMaxLevel;
|
||||
public int UpgradeCost;
|
||||
public int DestroyGain;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class TowerSelectItemRawData
|
||||
{
|
||||
public Sprite Icon;
|
||||
public int Price;
|
||||
public bool IsGain;
|
||||
public bool IsVisible;
|
||||
public bool IsInteractable;
|
||||
public CombatSelectActionType ActionType;
|
||||
public int ActionIndex;
|
||||
public bool UseTowerLayers;
|
||||
public Color BaseColor = Color.white;
|
||||
public Color BearingColor = Color.white;
|
||||
public Color MuzzleColor = Color.white;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using GeometryTD.CustomComponent;
|
||||
using GeometryTD.CustomUtility;
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class CombatFinishFormUseCase : IUIUseCase
|
||||
{
|
||||
private ICombatSchedulerPort _combatSchedulerPort;
|
||||
private CombatSettlementContext _settlementContext;
|
||||
private bool _isSummaryPrepared;
|
||||
|
||||
public CombatFinishFormRawData CreateInitialModel()
|
||||
{
|
||||
return BuildModel();
|
||||
}
|
||||
|
||||
public CombatFinishFormRawData TryRefresh()
|
||||
{
|
||||
return BuildModel();
|
||||
}
|
||||
|
||||
internal void SetSummary(CombatSettlementContext settlementContext)
|
||||
{
|
||||
_settlementContext = settlementContext;
|
||||
_isSummaryPrepared = true;
|
||||
}
|
||||
|
||||
internal CombatFinishFormUseCase(ICombatSchedulerPort combatSchedulerPort)
|
||||
{
|
||||
_combatSchedulerPort = combatSchedulerPort;
|
||||
}
|
||||
|
||||
public bool TryReturnToMenu()
|
||||
{
|
||||
return _combatSchedulerPort != null && _combatSchedulerPort.OnCombatFinishReturnRequested();
|
||||
}
|
||||
|
||||
private CombatFinishFormRawData BuildModel()
|
||||
{
|
||||
if (!_isSummaryPrepared)
|
||||
{
|
||||
_settlementContext = null;
|
||||
}
|
||||
|
||||
BackpackInventoryData rewardInventory = _settlementContext?.Summary.RewardInventory;
|
||||
if (rewardInventory == null)
|
||||
{
|
||||
rewardInventory = InventorySeedUtility.CreateSampleInventory();
|
||||
}
|
||||
|
||||
return new CombatFinishFormRawData
|
||||
{
|
||||
DefeatedEnemyCount = Mathf.Max(0, _settlementContext?.Summary.DefeatedEnemyCount ?? 0),
|
||||
GainedGold = Mathf.Max(0, _settlementContext?.Summary.GainedGold ?? 0),
|
||||
RewardInventory = rewardInventory,
|
||||
CanReturn = true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class CombatInfoFormUseCase : IUIUseCase
|
||||
{
|
||||
private Func<CombatInfoFormRawData> _modelProvider;
|
||||
private Func<bool> _tryEndCombat;
|
||||
private Func<bool> _tryDebugFail;
|
||||
|
||||
public CombatInfoFormRawData CreateInitialModel()
|
||||
{
|
||||
return BuildModel();
|
||||
}
|
||||
|
||||
public CombatInfoFormRawData TryRefresh()
|
||||
{
|
||||
return BuildModel();
|
||||
}
|
||||
|
||||
public void Configure(Func<CombatInfoFormRawData> modelProvider, Func<bool> tryEndCombat, Func<bool> tryDebugFail)
|
||||
{
|
||||
_modelProvider = modelProvider;
|
||||
_tryEndCombat = tryEndCombat;
|
||||
_tryDebugFail = tryDebugFail;
|
||||
}
|
||||
|
||||
public bool TryEndCombat()
|
||||
{
|
||||
if (_tryEndCombat == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _tryEndCombat.Invoke();
|
||||
}
|
||||
|
||||
public bool TryDebugFail()
|
||||
{
|
||||
if (_tryDebugFail == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _tryDebugFail.Invoke();
|
||||
}
|
||||
|
||||
private CombatInfoFormRawData BuildModel()
|
||||
{
|
||||
return _modelProvider != null ? _modelProvider.Invoke() : null;
|
||||
}
|
||||
|
||||
public static CombatInfoFormRawData BuildRawData(
|
||||
LevelThemeType themeType,
|
||||
int levelId,
|
||||
int currentPhaseIndex,
|
||||
int totalPhaseCount,
|
||||
int coin,
|
||||
int baseHp,
|
||||
int baseHpMax,
|
||||
bool canEnd)
|
||||
{
|
||||
return new CombatInfoFormRawData
|
||||
{
|
||||
LevelThemeType = themeType,
|
||||
LevelId = levelId,
|
||||
CurrentPhaseIndex = Mathf.Max(0, currentPhaseIndex),
|
||||
TotalPhaseCount = Mathf.Max(0, totalPhaseCount),
|
||||
Coin = Mathf.Max(0, coin),
|
||||
BaseHp = Mathf.Max(0, baseHp),
|
||||
BaseHpMax = Mathf.Max(0, baseHpMax),
|
||||
EnemyHpRateMultiplier = ResolveEnemyHpRateMultiplier(currentPhaseIndex, totalPhaseCount),
|
||||
CanPause = true,
|
||||
CanEnd = canEnd
|
||||
};
|
||||
}
|
||||
|
||||
private static int ResolveEnemyHpRateMultiplier(int currentPhaseIndex, int totalPhaseCount)
|
||||
{
|
||||
if (currentPhaseIndex <= 0 || totalPhaseCount <= 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
int completedLoopCount = Mathf.Max(0, (currentPhaseIndex - 1) / totalPhaseCount);
|
||||
if (completedLoopCount >= 30)
|
||||
{
|
||||
return int.MaxValue;
|
||||
}
|
||||
|
||||
return 1 << completedLoopCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class CombatSelectFormUseCase : IUIUseCase
|
||||
{
|
||||
private const int BuildOptionCount = 4;
|
||||
|
||||
private readonly Option[] _buildOptions = new Option[BuildOptionCount];
|
||||
private readonly Option _upgradeOption = new Option(CombatSelectActionType.UpgradeTower, 0, true);
|
||||
private readonly Option _destroyOption = new Option(CombatSelectActionType.DestroyTower, 0, false);
|
||||
|
||||
private Func<int> _coinProvider;
|
||||
private bool _isVisible;
|
||||
private CombatSelectDisplayMode _displayMode = CombatSelectDisplayMode.Hidden;
|
||||
private Vector2 _screenPosition;
|
||||
|
||||
public CombatSelectFormUseCase()
|
||||
{
|
||||
_coinProvider = DefaultCoinProvider;
|
||||
|
||||
for (int i = 0; i < BuildOptionCount; i++)
|
||||
{
|
||||
_buildOptions[i] = new Option(CombatSelectActionType.BuildTower, i, true);
|
||||
}
|
||||
}
|
||||
|
||||
public CombatSelectFormRawData CreateInitialModel()
|
||||
{
|
||||
return BuildModel();
|
||||
}
|
||||
|
||||
public CombatSelectFormRawData TryRefresh()
|
||||
{
|
||||
return BuildModel();
|
||||
}
|
||||
|
||||
public void SetCoinProvider(Func<int> coinProvider)
|
||||
{
|
||||
_coinProvider = coinProvider ?? DefaultCoinProvider;
|
||||
}
|
||||
|
||||
public void SetBuildAction(int buildIndex, Func<bool> buildAction, int cost, Sprite icon = null)
|
||||
{
|
||||
SetBuildAction(buildIndex, buildAction, cost, icon, false, Color.white, Color.white, Color.white);
|
||||
}
|
||||
|
||||
public void SetBuildAction(
|
||||
int buildIndex,
|
||||
Func<bool> buildAction,
|
||||
int cost,
|
||||
Sprite icon,
|
||||
bool useTowerLayers,
|
||||
Color baseColor,
|
||||
Color bearingColor,
|
||||
Color muzzleColor)
|
||||
{
|
||||
if (buildIndex < 0 || buildIndex >= _buildOptions.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Option option = _buildOptions[buildIndex];
|
||||
option.Action = buildAction;
|
||||
option.Price = Mathf.Max(0, cost);
|
||||
option.Icon = icon;
|
||||
option.IsGain = false;
|
||||
option.IsVisible = true;
|
||||
option.UseTowerLayers = useTowerLayers;
|
||||
option.BaseColor = baseColor;
|
||||
option.BearingColor = bearingColor;
|
||||
option.MuzzleColor = muzzleColor;
|
||||
}
|
||||
|
||||
public void SetBuildAction(int buildIndex, Action buildAction, int cost, Sprite icon = null)
|
||||
{
|
||||
if (buildAction == null)
|
||||
{
|
||||
SetBuildAction(buildIndex, (Func<bool>)null, cost, icon);
|
||||
return;
|
||||
}
|
||||
|
||||
SetBuildAction(
|
||||
buildIndex,
|
||||
() =>
|
||||
{
|
||||
buildAction.Invoke();
|
||||
return true;
|
||||
},
|
||||
cost,
|
||||
icon);
|
||||
}
|
||||
|
||||
public void SetBuildVisible(int buildIndex, bool visible)
|
||||
{
|
||||
if (buildIndex < 0 || buildIndex >= _buildOptions.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_buildOptions[buildIndex].IsVisible = visible;
|
||||
}
|
||||
|
||||
public void SetUpgradeAction(Func<bool> upgradeAction, int cost, Sprite icon = null)
|
||||
{
|
||||
_upgradeOption.Action = upgradeAction;
|
||||
_upgradeOption.Price = Mathf.Max(0, cost);
|
||||
_upgradeOption.Icon = icon;
|
||||
_upgradeOption.IsGain = false;
|
||||
_upgradeOption.IsVisible = true;
|
||||
_upgradeOption.UseTowerLayers = false;
|
||||
}
|
||||
|
||||
public void SetUpgradeAction(Action upgradeAction, int cost, Sprite icon = null)
|
||||
{
|
||||
if (upgradeAction == null)
|
||||
{
|
||||
SetUpgradeAction((Func<bool>)null, cost, icon);
|
||||
return;
|
||||
}
|
||||
|
||||
SetUpgradeAction(
|
||||
() =>
|
||||
{
|
||||
upgradeAction.Invoke();
|
||||
return true;
|
||||
},
|
||||
cost,
|
||||
icon);
|
||||
}
|
||||
|
||||
public void SetDestroyAction(Func<bool> destroyAction, int gain, Sprite icon = null)
|
||||
{
|
||||
_destroyOption.Action = destroyAction;
|
||||
_destroyOption.Price = Mathf.Max(0, gain);
|
||||
_destroyOption.Icon = icon;
|
||||
_destroyOption.IsGain = true;
|
||||
_destroyOption.IsVisible = true;
|
||||
_destroyOption.UseTowerLayers = false;
|
||||
}
|
||||
|
||||
public void SetDestroyAction(Action destroyAction, int gain, Sprite icon = null)
|
||||
{
|
||||
if (destroyAction == null)
|
||||
{
|
||||
SetDestroyAction((Func<bool>)null, gain, icon);
|
||||
return;
|
||||
}
|
||||
|
||||
SetDestroyAction(
|
||||
() =>
|
||||
{
|
||||
destroyAction.Invoke();
|
||||
return true;
|
||||
},
|
||||
gain,
|
||||
icon);
|
||||
}
|
||||
|
||||
public void SetUpgradePrice(int cost)
|
||||
{
|
||||
_upgradeOption.Price = Mathf.Max(0, cost);
|
||||
}
|
||||
|
||||
public void SetUpgradeVisible(bool visible)
|
||||
{
|
||||
_upgradeOption.IsVisible = visible;
|
||||
}
|
||||
|
||||
public void SetDestroyGain(int gain)
|
||||
{
|
||||
_destroyOption.Price = Mathf.Max(0, gain);
|
||||
_destroyOption.IsGain = true;
|
||||
}
|
||||
|
||||
public void ShowForFoundation(Vector2 screenPosition)
|
||||
{
|
||||
_screenPosition = screenPosition;
|
||||
_displayMode = CombatSelectDisplayMode.Build;
|
||||
_isVisible = true;
|
||||
}
|
||||
|
||||
public void ShowForTower(Vector2 screenPosition)
|
||||
{
|
||||
_screenPosition = screenPosition;
|
||||
_displayMode = CombatSelectDisplayMode.Func;
|
||||
_isVisible = true;
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
_isVisible = false;
|
||||
_displayMode = CombatSelectDisplayMode.Hidden;
|
||||
}
|
||||
|
||||
public bool TryExecuteAction(CombatSelectActionType actionType, int actionIndex)
|
||||
{
|
||||
if (!_isVisible)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Option option = GetOption(actionType, actionIndex);
|
||||
if (option == null || !option.IsVisible)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int currentCoin = Mathf.Max(0, _coinProvider != null ? _coinProvider.Invoke() : 0);
|
||||
if (!CanExecute(option, currentCoin))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (option.Action == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = option.Action.Invoke();
|
||||
if (result)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private CombatSelectFormRawData BuildModel()
|
||||
{
|
||||
int currentCoin = Mathf.Max(0, _coinProvider != null ? _coinProvider.Invoke() : 0);
|
||||
TowerSelectItemRawData[] buildItems = new TowerSelectItemRawData[_buildOptions.Length];
|
||||
for (int i = 0; i < _buildOptions.Length; i++)
|
||||
{
|
||||
buildItems[i] = BuildOptionRawData(_buildOptions[i], currentCoin);
|
||||
}
|
||||
|
||||
return new CombatSelectFormRawData
|
||||
{
|
||||
IsVisible = _isVisible,
|
||||
ScreenPosition = _screenPosition,
|
||||
DisplayMode = _displayMode,
|
||||
BuildItems = buildItems,
|
||||
UpgradeItem = BuildOptionRawData(_upgradeOption, currentCoin),
|
||||
DestroyItem = BuildOptionRawData(_destroyOption, currentCoin)
|
||||
};
|
||||
}
|
||||
|
||||
private Option GetOption(CombatSelectActionType actionType, int actionIndex)
|
||||
{
|
||||
switch (actionType)
|
||||
{
|
||||
case CombatSelectActionType.BuildTower:
|
||||
if (actionIndex < 0 || actionIndex >= _buildOptions.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _buildOptions[actionIndex];
|
||||
case CombatSelectActionType.UpgradeTower:
|
||||
return _upgradeOption;
|
||||
case CombatSelectActionType.DestroyTower:
|
||||
return _destroyOption;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static int DefaultCoinProvider()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static bool CanExecute(Option option, int currentCoin)
|
||||
{
|
||||
if (option == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (option.RequiresAffordable && currentCoin < option.Price)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static TowerSelectItemRawData BuildOptionRawData(Option option, int currentCoin)
|
||||
{
|
||||
if (option == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TowerSelectItemRawData
|
||||
{
|
||||
Icon = option.Icon,
|
||||
Price = option.Price,
|
||||
IsGain = option.IsGain,
|
||||
IsVisible = option.IsVisible,
|
||||
IsInteractable = option.Action != null && CanExecute(option, currentCoin),
|
||||
ActionType = option.ActionType,
|
||||
ActionIndex = option.ActionIndex,
|
||||
UseTowerLayers = option.UseTowerLayers,
|
||||
BaseColor = option.BaseColor,
|
||||
BearingColor = option.BearingColor,
|
||||
MuzzleColor = option.MuzzleColor
|
||||
};
|
||||
}
|
||||
|
||||
private sealed class Option
|
||||
{
|
||||
public readonly CombatSelectActionType ActionType;
|
||||
public readonly int ActionIndex;
|
||||
public readonly bool RequiresAffordable;
|
||||
|
||||
public int Price;
|
||||
public bool IsGain;
|
||||
public bool IsVisible = true;
|
||||
public Sprite Icon;
|
||||
public Func<bool> Action;
|
||||
public bool UseTowerLayers;
|
||||
public Color BaseColor = Color.white;
|
||||
public Color BearingColor = Color.white;
|
||||
public Color MuzzleColor = Color.white;
|
||||
|
||||
public Option(CombatSelectActionType actionType, int actionIndex, bool requiresAffordable)
|
||||
{
|
||||
ActionType = actionType;
|
||||
ActionIndex = actionIndex;
|
||||
RequiresAffordable = requiresAffordable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
using System.Collections.Generic;
|
||||
using GameFramework.ObjectPool;
|
||||
using GeometryTD.CustomEvent;
|
||||
using GeometryTD.PoolObjectBase;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class CombatFinishForm : UGuiForm
|
||||
{
|
||||
[SerializeField] private TMP_Text _enemyKilledText;
|
||||
[SerializeField] private TMP_Text _goldGainedText;
|
||||
[SerializeField] private Transform _itemGainedParent;
|
||||
[SerializeField] private RepoItem _itemTemplate;
|
||||
[SerializeField] private int _instancePoolCapacity = 16;
|
||||
|
||||
private readonly List<RepoItem> _activeItems = new List<RepoItem>();
|
||||
private IObjectPool<RepoItemObject> _itemPool;
|
||||
private string _poolName;
|
||||
private CombatFinishFormContext _context;
|
||||
|
||||
public void RefreshUI(CombatFinishFormContext context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
if (_enemyKilledText != null)
|
||||
{
|
||||
_enemyKilledText.text = context?.EnemyKilledText ?? "0";
|
||||
}
|
||||
|
||||
if (_goldGainedText != null)
|
||||
{
|
||||
_goldGainedText.text = context?.GoldGainedText ?? "0";
|
||||
}
|
||||
|
||||
RefreshRewardItems(context?.RewardItems);
|
||||
}
|
||||
|
||||
public void OnReturnButtonClick()
|
||||
{
|
||||
if (_context != null && !_context.CanReturn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.Event.Fire(this, CombatFinishReturnEventArgs.Create());
|
||||
}
|
||||
|
||||
protected override void OnOpen(object userData)
|
||||
{
|
||||
base.OnOpen(userData);
|
||||
|
||||
if (userData is CombatFinishFormContext context)
|
||||
{
|
||||
RefreshUI(context);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Warning("CombatFinishForm requires CombatFinishFormContext as userData.");
|
||||
}
|
||||
|
||||
protected override void OnClose(bool isShutdown, object userData)
|
||||
{
|
||||
_context = null;
|
||||
|
||||
if (_enemyKilledText != null)
|
||||
{
|
||||
_enemyKilledText.text = string.Empty;
|
||||
}
|
||||
|
||||
if (_goldGainedText != null)
|
||||
{
|
||||
_goldGainedText.text = string.Empty;
|
||||
}
|
||||
|
||||
ClearRewardItems();
|
||||
base.OnClose(isShutdown, userData);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ClearRewardItems();
|
||||
_itemPool?.ReleaseAllUnused();
|
||||
}
|
||||
|
||||
private void EnsurePool()
|
||||
{
|
||||
if (_itemPool != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_poolName = $"CombatFinishRepoItemPool_{GetInstanceID()}";
|
||||
_itemPool = GameEntry.ObjectPool.CreateSingleSpawnObjectPool<RepoItemObject>(_poolName, _instancePoolCapacity);
|
||||
}
|
||||
|
||||
private void RefreshRewardItems(RepoItemContext[] rewardItems)
|
||||
{
|
||||
ClearRewardItems();
|
||||
|
||||
if (rewardItems == null || rewardItems.Length <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsurePool();
|
||||
|
||||
for (int i = 0; i < rewardItems.Length; i++)
|
||||
{
|
||||
RepoItem item = CreateOrSpawnItem();
|
||||
if (item == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
item.gameObject.SetActive(true);
|
||||
item.OnInit(rewardItems[i]);
|
||||
_activeItems.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearRewardItems()
|
||||
{
|
||||
for (int i = _activeItems.Count - 1; i >= 0; i--)
|
||||
{
|
||||
RepoItem item = _activeItems[i];
|
||||
if (item == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
item.OnReset();
|
||||
item.gameObject.SetActive(false);
|
||||
_itemPool?.Unspawn(item);
|
||||
}
|
||||
|
||||
_activeItems.Clear();
|
||||
}
|
||||
|
||||
private RepoItem CreateOrSpawnItem()
|
||||
{
|
||||
if (_itemGainedParent == null)
|
||||
{
|
||||
Log.Warning("CombatFinishForm requires an item gained parent.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_itemPool == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
RepoItemObject itemObject = _itemPool.Spawn();
|
||||
RepoItem item = itemObject != null ? (RepoItem)itemObject.Target : null;
|
||||
if (item == null)
|
||||
{
|
||||
if (_itemTemplate == null)
|
||||
{
|
||||
Log.Warning("CombatFinishForm requires an item template.");
|
||||
return null;
|
||||
}
|
||||
|
||||
item = Instantiate(_itemTemplate);
|
||||
_itemPool.Register(RepoItemObject.Create(item), true);
|
||||
}
|
||||
|
||||
item.transform.SetParent(_itemGainedParent, false);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using GeometryTD.CustomEvent;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class CombatInfoForm : UGuiForm
|
||||
{
|
||||
[SerializeField] private TMP_Text _levelMetaText;
|
||||
|
||||
[SerializeField] private TMP_Text _levelPhaseText;
|
||||
|
||||
[SerializeField] private TMP_Text _coinText;
|
||||
|
||||
[SerializeField] private TMP_Text _baseHpText;
|
||||
|
||||
[SerializeField] private TMP_Text _enemyHpRateText;
|
||||
|
||||
[SerializeField] private CommonButton _pauseButton;
|
||||
|
||||
[SerializeField] private CommonButton _endButton;
|
||||
|
||||
private CombatInfoFormContext _context;
|
||||
|
||||
public void RefreshUI(CombatInfoFormContext context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
if (_levelMetaText != null)
|
||||
{
|
||||
_levelMetaText.text = context?.LevelMetaText ?? string.Empty;
|
||||
}
|
||||
|
||||
if (_levelPhaseText != null)
|
||||
{
|
||||
_levelPhaseText.text = context?.LevelPhaseText ?? string.Empty;
|
||||
}
|
||||
|
||||
if (_coinText != null)
|
||||
{
|
||||
_coinText.text = context?.CoinText ?? "0";
|
||||
}
|
||||
|
||||
if (_baseHpText != null)
|
||||
{
|
||||
_baseHpText.text = context?.BaseHpText ?? string.Empty;
|
||||
}
|
||||
|
||||
if (_enemyHpRateText != null)
|
||||
{
|
||||
_enemyHpRateText.text = context?.EnemyHpRateText ?? string.Empty;
|
||||
}
|
||||
|
||||
if (_pauseButton != null)
|
||||
{
|
||||
_pauseButton.Interactive = context?.CanPause ?? false;
|
||||
}
|
||||
|
||||
if (_endButton != null)
|
||||
{
|
||||
_endButton.gameObject.SetActive(context?.CanEnd ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPauseButtonClick()
|
||||
{
|
||||
if (_context != null && !_context.CanPause)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.Event.Fire(this, CombatPauseEventArgs.Create());
|
||||
}
|
||||
|
||||
public void OnEndButtonClick()
|
||||
{
|
||||
if (_context != null && !_context.CanEnd)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.Event.Fire(this, CombatEndEventArgs.Create());
|
||||
}
|
||||
|
||||
public void OnFailButtonClick()
|
||||
{
|
||||
GameEntry.Event.Fire(this, CombatDebugFailEventArgs.Create());
|
||||
}
|
||||
|
||||
protected override void OnOpen(object userData)
|
||||
{
|
||||
base.OnOpen(userData);
|
||||
|
||||
if (userData is CombatInfoFormContext context)
|
||||
{
|
||||
RefreshUI(context);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Warning("CombatInfoForm requires CombatInfoFormContext as userData.");
|
||||
}
|
||||
|
||||
protected override void OnClose(bool isShutdown, object userData)
|
||||
{
|
||||
_context = null;
|
||||
|
||||
if (_levelMetaText != null)
|
||||
{
|
||||
_levelMetaText.text = string.Empty;
|
||||
}
|
||||
|
||||
if (_levelPhaseText != null)
|
||||
{
|
||||
_levelPhaseText.text = string.Empty;
|
||||
}
|
||||
|
||||
if (_coinText != null)
|
||||
{
|
||||
_coinText.text = string.Empty;
|
||||
}
|
||||
|
||||
if (_baseHpText != null)
|
||||
{
|
||||
_baseHpText.text = string.Empty;
|
||||
}
|
||||
|
||||
if (_enemyHpRateText != null)
|
||||
{
|
||||
_enemyHpRateText.text = string.Empty;
|
||||
}
|
||||
|
||||
base.OnClose(isShutdown, userData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class CombatSelectBuildArea : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private TowerSelectItem[] _towerBuildItems;
|
||||
|
||||
public void OnInit(TowerSelectItemContext[] itemContexts)
|
||||
{
|
||||
if (_towerBuildItems == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _towerBuildItems.Length; i++)
|
||||
{
|
||||
TowerSelectItem item = _towerBuildItems[i];
|
||||
if (item == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TowerSelectItemContext itemContext =
|
||||
itemContexts != null && i < itemContexts.Length ? itemContexts[i] : null;
|
||||
bool isVisible = itemContext != null && itemContext.IsVisible;
|
||||
|
||||
item.gameObject.SetActive(isVisible);
|
||||
if (isVisible)
|
||||
{
|
||||
item.OnInit(itemContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.OnReset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
if (_towerBuildItems == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _towerBuildItems.Length; i++)
|
||||
{
|
||||
TowerSelectItem item = _towerBuildItems[i];
|
||||
if (item == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
item.OnReset();
|
||||
item.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class CombatSelectForm : UGuiForm
|
||||
{
|
||||
[SerializeField] private RectTransform _content;
|
||||
|
||||
[SerializeField] private CombatSelectBuildArea _buildArea;
|
||||
|
||||
[SerializeField] private CombatSelectFuncArea _funcArea;
|
||||
|
||||
private CombatSelectFormContext _context;
|
||||
|
||||
public void RefreshUI(CombatSelectFormContext context)
|
||||
{
|
||||
_context = context;
|
||||
EnsureContentReference();
|
||||
|
||||
if (_content == null)
|
||||
{
|
||||
Log.Warning("CombatSelectForm content is missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
bool isVisible = context != null && context.IsVisible;
|
||||
_content.gameObject.SetActive(isVisible);
|
||||
if (!isVisible)
|
||||
{
|
||||
_buildArea?.OnReset();
|
||||
_funcArea?.OnReset();
|
||||
return;
|
||||
}
|
||||
|
||||
_content.anchoredPosition = ResolveContentAnchoredPosition(context.ScreenPosition);
|
||||
|
||||
if (_buildArea != null)
|
||||
{
|
||||
bool showBuildArea = context.ShowBuildArea;
|
||||
_buildArea.gameObject.SetActive(showBuildArea);
|
||||
if (showBuildArea)
|
||||
{
|
||||
_buildArea.OnInit(context.BuildItems);
|
||||
}
|
||||
else
|
||||
{
|
||||
_buildArea.OnReset();
|
||||
}
|
||||
}
|
||||
|
||||
if (_funcArea != null)
|
||||
{
|
||||
bool showFuncArea = context.ShowFuncArea;
|
||||
_funcArea.gameObject.SetActive(showFuncArea);
|
||||
if (showFuncArea)
|
||||
{
|
||||
_funcArea.OnInit(context.UpgradeItem, context.DestroyItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
_funcArea.OnReset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnOpen(object userData)
|
||||
{
|
||||
base.OnOpen(userData);
|
||||
|
||||
if (userData is CombatSelectFormContext context)
|
||||
{
|
||||
RefreshUI(context);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Warning("CombatSelectForm requires CombatSelectFormContext as userData.");
|
||||
RefreshUI(null);
|
||||
}
|
||||
|
||||
protected override void OnClose(bool isShutdown, object userData)
|
||||
{
|
||||
_context = null;
|
||||
_buildArea?.OnReset();
|
||||
_funcArea?.OnReset();
|
||||
|
||||
if (_content != null)
|
||||
{
|
||||
_content.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
base.OnClose(isShutdown, userData);
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
EnsureContentReference();
|
||||
}
|
||||
|
||||
private void EnsureContentReference()
|
||||
{
|
||||
if (_content != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Transform contentTransform = transform.Find("Content");
|
||||
_content = contentTransform as RectTransform;
|
||||
}
|
||||
|
||||
private Vector2 ResolveContentAnchoredPosition(Vector2 screenPosition)
|
||||
{
|
||||
if (_content == null)
|
||||
{
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
RectTransform parentRect = _content.parent as RectTransform;
|
||||
if (parentRect == null)
|
||||
{
|
||||
return screenPosition;
|
||||
}
|
||||
|
||||
Canvas canvas = _content.GetComponentInParent<Canvas>();
|
||||
Canvas rootCanvas = canvas != null ? canvas.rootCanvas : null;
|
||||
Camera uiCamera = null;
|
||||
if (rootCanvas != null && rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay)
|
||||
{
|
||||
uiCamera = rootCanvas.worldCamera != null ? rootCanvas.worldCamera : Camera.main;
|
||||
}
|
||||
|
||||
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(parentRect, screenPosition, uiCamera,
|
||||
out Vector2 localPoint))
|
||||
{
|
||||
return Vector2.zero;
|
||||
}
|
||||
|
||||
return localPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class CombatSelectFuncArea : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private TowerSelectItem _upgradeItem;
|
||||
|
||||
[SerializeField] private TowerSelectItem _destroyItem;
|
||||
|
||||
public void OnInit(TowerSelectItemContext upgradeItemContext, TowerSelectItemContext destroyItemContext)
|
||||
{
|
||||
RefreshItem(_upgradeItem, upgradeItemContext);
|
||||
RefreshItem(_destroyItem, destroyItemContext);
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
ResetItem(_upgradeItem);
|
||||
ResetItem(_destroyItem);
|
||||
}
|
||||
|
||||
private static void RefreshItem(TowerSelectItem item, TowerSelectItemContext context)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool isVisible = context != null && context.IsVisible;
|
||||
item.gameObject.SetActive(isVisible);
|
||||
if (isVisible)
|
||||
{
|
||||
item.OnInit(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.OnReset();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResetItem(TowerSelectItem item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
item.OnReset();
|
||||
item.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using GeometryTD.CustomEvent;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class TowerSelectItem : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Image _icon;
|
||||
|
||||
[SerializeField] private TMP_Text _price;
|
||||
|
||||
[SerializeField] private CommonButton _button;
|
||||
|
||||
[SerializeField] private TowerIconArea _towerIconArea;
|
||||
|
||||
private TowerSelectItemContext _context;
|
||||
|
||||
private Sprite _defaultIcon;
|
||||
|
||||
private bool _hasCachedDefaultIcon;
|
||||
|
||||
public void OnInit(TowerSelectItemContext context)
|
||||
{
|
||||
CacheDefaultIcon();
|
||||
_context = context;
|
||||
|
||||
if (context.ActionType == CombatSelectActionType.BuildTower)
|
||||
{
|
||||
_icon?.gameObject.SetActive(false);
|
||||
_towerIconArea?.OnInit(_context.TowerIconAreaContext);
|
||||
|
||||
if (_price != null)
|
||||
{
|
||||
_price.text = context.PriceText ?? string.Empty;
|
||||
}
|
||||
|
||||
if (_button != null)
|
||||
{
|
||||
_button.Interactive = context.IsInteractable;
|
||||
}
|
||||
}
|
||||
else if (context.ActionType is CombatSelectActionType.UpgradeTower or CombatSelectActionType.DestroyTower)
|
||||
{
|
||||
if (_icon != null)
|
||||
{
|
||||
_icon.sprite = _context.Icon ?? _defaultIcon;
|
||||
_icon.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
_towerIconArea?.OnReset();
|
||||
|
||||
if (_price != null)
|
||||
{
|
||||
_price.text = context.PriceText ?? string.Empty;
|
||||
}
|
||||
|
||||
if (_button != null)
|
||||
{
|
||||
_button.Interactive = context.IsInteractable;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_icon?.gameObject.SetActive(false);
|
||||
_towerIconArea?.OnReset();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
_context = null;
|
||||
CacheDefaultIcon();
|
||||
|
||||
if (_icon != null)
|
||||
{
|
||||
_icon.sprite = _defaultIcon;
|
||||
_icon.enabled = _icon.sprite != null;
|
||||
}
|
||||
|
||||
_towerIconArea.OnReset();
|
||||
|
||||
if (_price != null)
|
||||
{
|
||||
_price.text = string.Empty;
|
||||
}
|
||||
|
||||
if (_button != null)
|
||||
{
|
||||
_button.Interactive = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnClick()
|
||||
{
|
||||
if (_context == null || !_context.IsInteractable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.Event.Fire(
|
||||
this,
|
||||
CombatSelectItemClickEventArgs.Create(_context.ActionType, _context.ActionIndex));
|
||||
}
|
||||
|
||||
private void CacheDefaultIcon()
|
||||
{
|
||||
if (_hasCachedDefaultIcon || _icon == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_defaultIcon = _icon.sprite;
|
||||
_hasCachedDefaultIcon = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class CommonButton : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerDownHandler,
|
||||
IPointerUpHandler
|
||||
{
|
||||
private const float FadeTime = 0.3f;
|
||||
private const float OnHoverAlpha = 0.7f;
|
||||
private const float OnClickAlpha = 0.6f;
|
||||
|
||||
[FormerlySerializedAs("m_OnHover")] [SerializeField]
|
||||
private UnityEvent _onHover = null;
|
||||
|
||||
[FormerlySerializedAs("m_OnClick")] [SerializeField]
|
||||
private UnityEvent _onClick = null;
|
||||
|
||||
[SerializeField] private UnityEvent _onHoverEnd = null;
|
||||
|
||||
[SerializeField] private bool _allowFade = true;
|
||||
|
||||
private bool _interactive = true;
|
||||
|
||||
public bool Interactive
|
||||
{
|
||||
get => _interactive;
|
||||
set
|
||||
{
|
||||
_interactive = value;
|
||||
if (!value)
|
||||
{
|
||||
StopAllCoroutines();
|
||||
_canvasGroup.alpha = 1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CanvasGroup _canvasGroup = null;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_canvasGroup = gameObject.GetOrAddComponent<CanvasGroup>();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
_canvasGroup.alpha = 1f;
|
||||
}
|
||||
|
||||
public void OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
if (!Interactive) return;
|
||||
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopAllCoroutines();
|
||||
if (_allowFade)
|
||||
StartCoroutine(_canvasGroup.FadeToAlpha(OnHoverAlpha, FadeTime));
|
||||
_onHover.Invoke();
|
||||
}
|
||||
|
||||
public void OnPointerExit(PointerEventData eventData)
|
||||
{
|
||||
if (!Interactive) return;
|
||||
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StopAllCoroutines();
|
||||
if (_allowFade)
|
||||
StartCoroutine(_canvasGroup.FadeToAlpha(1f, FadeTime));
|
||||
_onHoverEnd?.Invoke();
|
||||
}
|
||||
|
||||
public void OnPointerDown(PointerEventData eventData)
|
||||
{
|
||||
if (!Interactive) return;
|
||||
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_canvasGroup.alpha = OnClickAlpha;
|
||||
_onClick.Invoke();
|
||||
}
|
||||
|
||||
public void OnPointerUp(PointerEventData eventData)
|
||||
{
|
||||
if (!Interactive) return;
|
||||
|
||||
if (eventData.button != PointerEventData.InputButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_canvasGroup.alpha = OnHoverAlpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
using GameFramework;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 对话框显示数据。
|
||||
/// </summary>
|
||||
public class DialogParams
|
||||
{
|
||||
/// <summary>
|
||||
/// 模式,即按钮数量。取值 1、2、3。
|
||||
/// </summary>
|
||||
public int Mode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标题。
|
||||
/// </summary>
|
||||
public string Title
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息内容。
|
||||
/// </summary>
|
||||
public string Message
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 弹出窗口时是否暂停游戏。
|
||||
/// </summary>
|
||||
public bool PauseGame
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确认按钮文本。
|
||||
/// </summary>
|
||||
public string ConfirmText
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确定按钮回调。
|
||||
/// </summary>
|
||||
public GameFrameworkAction<object> OnClickConfirm
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消按钮文本。
|
||||
/// </summary>
|
||||
public string CancelText
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消按钮回调。
|
||||
/// </summary>
|
||||
public GameFrameworkAction<object> OnClickCancel
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 中立按钮文本。
|
||||
/// </summary>
|
||||
public string OtherText
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 其它按钮回调。
|
||||
/// </summary>
|
||||
public GameFrameworkAction<object> OnClickOther
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户自定义数据。
|
||||
/// </summary>
|
||||
public string UserData
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 "当前无法加入参战区,请稍后重试。";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,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>";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using GeometryTD.Definition;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public interface IRepoDragItemView
|
||||
{
|
||||
long InstanceId { get; }
|
||||
|
||||
bool CanDrag { get; }
|
||||
|
||||
TowerCompSlotType ComponentSlotType { get; }
|
||||
|
||||
RepoItemClickActionType ClickActionType { get; }
|
||||
|
||||
RepoItemContext ComponentContext { get; }
|
||||
|
||||
void SetDropResult(bool assigned);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 ?? "出售模式";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class TowerRepoItem : RepoItem
|
||||
{
|
||||
private static readonly Vector2 DefaultTowerDragGhostSize = new Vector2(64f, 64f);
|
||||
|
||||
public new TowerRepoItemContext Context => base.Context as TowerRepoItemContext;
|
||||
|
||||
public void OnInit(TowerRepoItemContext context)
|
||||
{
|
||||
base.OnInit(context);
|
||||
}
|
||||
|
||||
protected override bool CreateDragGhost()
|
||||
{
|
||||
DestroyDragGhost();
|
||||
|
||||
if (DragRoot == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TowerIconArea towerIconArea = IconArea as TowerIconArea;
|
||||
if (towerIconArea == null)
|
||||
{
|
||||
return base.CreateDragGhost();
|
||||
}
|
||||
|
||||
_dragGhostObject = new GameObject("TowerRepoItemDragGhost", typeof(RectTransform), typeof(CanvasGroup));
|
||||
_dragGhostObject.layer = gameObject.layer;
|
||||
|
||||
_dragGhostRect = _dragGhostObject.GetComponent<RectTransform>();
|
||||
_dragGhostRect.SetParent(DragRoot, false);
|
||||
_dragGhostRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
_dragGhostRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
_dragGhostRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
|
||||
Vector2 iconSize = IconArea != null ? IconArea.CurrentIconSize : Vector2.zero;
|
||||
if (iconSize.x <= 0f || iconSize.y <= 0f)
|
||||
{
|
||||
iconSize = BackgroundImage != null ? BackgroundImage.rectTransform.rect.size : DefaultTowerDragGhostSize;
|
||||
}
|
||||
|
||||
if (iconSize.x <= 0f || iconSize.y <= 0f)
|
||||
{
|
||||
iconSize = DefaultTowerDragGhostSize;
|
||||
}
|
||||
|
||||
_dragGhostRect.sizeDelta = iconSize;
|
||||
|
||||
bool hasAnyLayer = false;
|
||||
hasAnyLayer |= TryCreateLayer("Base", towerIconArea.BaseIconSprite, towerIconArea.BaseIconColor,
|
||||
towerIconArea.BaseIconMaterial, _dragGhostRect, 0);
|
||||
hasAnyLayer |= TryCreateLayer("Bearing", towerIconArea.BearingIconSprite, towerIconArea.BearingIconColor,
|
||||
towerIconArea.BearingIconMaterial, _dragGhostRect, 1);
|
||||
hasAnyLayer |= TryCreateLayer("Muzzle", towerIconArea.MuzzleIconSprite, towerIconArea.MuzzleIconColor,
|
||||
towerIconArea.MuzzleIconMaterial, _dragGhostRect, 2);
|
||||
|
||||
if (!hasAnyLayer)
|
||||
{
|
||||
DestroyDragGhost();
|
||||
return base.CreateDragGhost();
|
||||
}
|
||||
|
||||
CanvasGroup ghostCanvasGroup = _dragGhostObject.GetComponent<CanvasGroup>();
|
||||
ghostCanvasGroup.blocksRaycasts = false;
|
||||
ghostCanvasGroup.interactable = false;
|
||||
|
||||
_dragGhostObject.transform.SetAsLastSibling();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryCreateLayer(
|
||||
string layerName,
|
||||
Sprite sprite,
|
||||
Color color,
|
||||
Material material,
|
||||
RectTransform parent,
|
||||
int siblingIndex)
|
||||
{
|
||||
if (parent == null || sprite == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GameObject layerObject = new GameObject($"TowerGhost{layerName}", typeof(RectTransform), typeof(Image));
|
||||
layerObject.layer = parent.gameObject.layer;
|
||||
|
||||
RectTransform layerRect = layerObject.GetComponent<RectTransform>();
|
||||
layerRect.SetParent(parent, false);
|
||||
layerRect.anchorMin = Vector2.zero;
|
||||
layerRect.anchorMax = Vector2.one;
|
||||
layerRect.offsetMin = Vector2.zero;
|
||||
layerRect.offsetMax = Vector2.zero;
|
||||
layerRect.SetSiblingIndex(siblingIndex);
|
||||
|
||||
Image layerImage = layerObject.GetComponent<Image>();
|
||||
layerImage.raycastTarget = false;
|
||||
layerImage.sprite = sprite;
|
||||
layerImage.color = color;
|
||||
layerImage.material = material;
|
||||
layerImage.preserveAspect = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using GameFramework;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class DialogFormContext : UIContext
|
||||
{
|
||||
public int Mode;
|
||||
public string Title;
|
||||
public string Message;
|
||||
public bool PauseGame;
|
||||
public string ConfirmText;
|
||||
public GameFrameworkAction<object> OnClickConfirm;
|
||||
public string CancelText;
|
||||
public GameFrameworkAction<object> OnClickCancel;
|
||||
public string OtherText;
|
||||
public GameFrameworkAction<object> OnClickOther;
|
||||
public object UserData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
[Serializable]
|
||||
public class IconAreaContext : UIContext
|
||||
{
|
||||
public RarityType Rarity;
|
||||
public TowerCompSlotType ComponentSlotType;
|
||||
public Color Color = Color.white;
|
||||
public Sprite Icon;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class ItemDescFormContext : UIContext
|
||||
{
|
||||
public string Title;
|
||||
public string TypeText;
|
||||
public string Description;
|
||||
public int Price;
|
||||
public Vector2 ScreenPosition;
|
||||
public TagItemContext[] Tags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class RewardItemContext : UIContext
|
||||
{
|
||||
public int Index;
|
||||
public IconAreaContext IconArea;
|
||||
public string Title;
|
||||
public string TypeText;
|
||||
public string Description;
|
||||
public TagItemContext[] Tags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class RewardSelectFormContext : UIContext
|
||||
{
|
||||
public string TipText;
|
||||
public string RefreshButtonText;
|
||||
public bool CanRefresh;
|
||||
public bool CanGiveUp;
|
||||
public RewardItemContext[] RewardItems;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class TagItemContext : UIContext
|
||||
{
|
||||
public string TagName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
[Serializable]
|
||||
public class TowerIconAreaContext : IconAreaContext
|
||||
{
|
||||
public Color MuzzleColor = Color.white;
|
||||
public Color BearingColor = Color.white;
|
||||
public Color BaseColor = Color.white;
|
||||
|
||||
public TowerIconAreaContext()
|
||||
{
|
||||
ComponentSlotType = TowerCompSlotType.None;
|
||||
Color = Color.white;
|
||||
Icon = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using GeometryTD.Definition;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class DialogFormController : UIFormControllerCommonBase<DialogFormContext, DialogForm>
|
||||
{
|
||||
protected override UIFormType UIFormTypeId => UIFormType.DialogForm;
|
||||
|
||||
protected override void RefreshUI(DialogForm form, DialogFormContext context)
|
||||
{
|
||||
form.RefreshUI(context);
|
||||
}
|
||||
|
||||
protected override void CloseLoadedFormDirect(DialogForm form)
|
||||
{
|
||||
GameEntry.UI.CloseUIForm(form);
|
||||
}
|
||||
|
||||
private static DialogFormContext BuildContext(DialogFormRawData rawData)
|
||||
{
|
||||
if (rawData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DialogFormContext
|
||||
{
|
||||
Mode = rawData.Mode,
|
||||
Title = rawData.Title,
|
||||
Message = rawData.Message,
|
||||
PauseGame = rawData.PauseGame,
|
||||
ConfirmText = rawData.ConfirmText,
|
||||
OnClickConfirm = rawData.OnClickConfirm,
|
||||
CancelText = rawData.CancelText,
|
||||
OnClickCancel = rawData.OnClickCancel,
|
||||
OtherText = rawData.OtherText,
|
||||
OnClickOther = rawData.OnClickOther,
|
||||
UserData = rawData.UserData
|
||||
};
|
||||
}
|
||||
|
||||
public int? OpenUI(DialogFormRawData rawData)
|
||||
{
|
||||
DialogFormContext context = BuildContext(rawData);
|
||||
return OpenUIInternal(context);
|
||||
}
|
||||
|
||||
public override int? OpenUI(object userData = null)
|
||||
{
|
||||
if (userData is DialogFormContext context)
|
||||
{
|
||||
return OpenUIInternal(context);
|
||||
}
|
||||
|
||||
if (userData is DialogFormRawData rawData)
|
||||
{
|
||||
return OpenUI(rawData);
|
||||
}
|
||||
|
||||
if (userData != null)
|
||||
{
|
||||
Log.Warning("DialogFormController.OpenUI() userData type is invalid.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return OpenUIInternal(Context);
|
||||
}
|
||||
|
||||
public override void BindUseCase(IUIUseCase useCase)
|
||||
{
|
||||
if (useCase != null)
|
||||
{
|
||||
Log.Warning("DialogFormController does not use a use case.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using GameFramework.Event;
|
||||
using GeometryTD.CustomUtility;
|
||||
using GeometryTD.Definition;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class ItemDescFormController : UIFormControllerCommonBase<
|
||||
ItemDescFormContext, ItemDescForm>
|
||||
{
|
||||
protected override UIFormType UIFormTypeId => UIFormType.ItemDescForm;
|
||||
|
||||
|
||||
protected override void RefreshUI(ItemDescForm form, ItemDescFormContext context)
|
||||
{
|
||||
form.RefreshUI(context);
|
||||
}
|
||||
|
||||
protected override void CloseLoadedFormDirect(ItemDescForm form)
|
||||
{
|
||||
GameEntry.UI.CloseUIForm(form);
|
||||
}
|
||||
|
||||
private static ItemDescFormContext BuildContext(ItemDescFormRawData rawData)
|
||||
{
|
||||
if (rawData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ItemDescFormContext
|
||||
{
|
||||
Title = rawData.Title,
|
||||
TypeText = rawData.TypeText,
|
||||
Description = BuildDescription(rawData),
|
||||
Price = rawData.Price,
|
||||
ScreenPosition = rawData.ScreenPosition,
|
||||
Tags = BuildTags(rawData)
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildDescription(ItemDescFormRawData rawData)
|
||||
{
|
||||
string baseDescription = rawData?.Description ?? string.Empty;
|
||||
string tagDescription = rawData?.TagRuntimes != null && rawData.TagRuntimes.Length > 0
|
||||
? TagDisplayUtility.BuildTagDescriptionText(rawData.TagRuntimes)
|
||||
: TagDisplayUtility.BuildTagDescriptionText(rawData?.Tags);
|
||||
if (string.IsNullOrWhiteSpace(tagDescription))
|
||||
{
|
||||
return baseDescription;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(baseDescription))
|
||||
{
|
||||
return tagDescription;
|
||||
}
|
||||
|
||||
return $"{baseDescription}\n{tagDescription}";
|
||||
}
|
||||
|
||||
private static TagItemContext[] BuildTags(ItemDescFormRawData rawData)
|
||||
{
|
||||
string[] tagTexts = rawData?.TagRuntimes != null && rawData.TagRuntimes.Length > 0
|
||||
? TagDisplayUtility.BuildTagTexts(rawData.TagRuntimes)
|
||||
: rawData?.Tags != null
|
||||
? TagDisplayUtility.BuildTagTexts(rawData.Tags)
|
||||
: null;
|
||||
|
||||
if (tagTexts == null || tagTexts.Length <= 0)
|
||||
{
|
||||
return System.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;
|
||||
}
|
||||
|
||||
public int? OpenUI(ItemDescFormRawData rawData)
|
||||
{
|
||||
ItemDescFormContext context = BuildContext(rawData);
|
||||
return OpenUIInternal(context);
|
||||
}
|
||||
|
||||
public override int? OpenUI(object userData = null)
|
||||
{
|
||||
if (userData is ItemDescFormContext context)
|
||||
{
|
||||
return OpenUIInternal(context);
|
||||
}
|
||||
|
||||
if (userData is ItemDescFormRawData rawData)
|
||||
{
|
||||
return OpenUI(rawData);
|
||||
}
|
||||
|
||||
if (userData != null)
|
||||
{
|
||||
Log.Warning("ItemDescFormController.OpenUI() userData type is invalid.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return OpenUIInternal(Context);
|
||||
}
|
||||
|
||||
public override void BindUseCase(IUIUseCase useCase)
|
||||
{
|
||||
if (!(useCase is ItemDescFormUseCase))
|
||||
{
|
||||
Log.Error("ItemDescForm.BindUseCase() useCase is invalid.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
using GeometryTD.CustomEvent;
|
||||
using GeometryTD.Definition;
|
||||
using GameFramework.Event;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public partial class RewardSelectFormController : UIFormControllerCommonBase<RewardSelectFormContext, RewardSelectForm>
|
||||
{
|
||||
private RewardSelectFormUseCase _useCase;
|
||||
|
||||
protected override UIFormType UIFormTypeId => UIFormType.RewardSelectForm;
|
||||
|
||||
protected override void RefreshUI(RewardSelectForm form, RewardSelectFormContext context)
|
||||
{
|
||||
form.RefreshUI(context);
|
||||
}
|
||||
|
||||
protected override void SubscribeCustomEvents()
|
||||
{
|
||||
GameEntry.Event.Subscribe(RewardSelectItemSelectedEventArgs.EventId, OnRewardSelected);
|
||||
GameEntry.Event.Subscribe(RewardSelectRefreshEventArgs.EventId, OnRefreshClicked);
|
||||
GameEntry.Event.Subscribe(RewardSelectGiveUpEventArgs.EventId, OnGiveUpClicked);
|
||||
}
|
||||
|
||||
protected override void UnsubscribeCustomEvents()
|
||||
{
|
||||
GameEntry.Event.Unsubscribe(RewardSelectItemSelectedEventArgs.EventId, OnRewardSelected);
|
||||
GameEntry.Event.Unsubscribe(RewardSelectRefreshEventArgs.EventId, OnRefreshClicked);
|
||||
GameEntry.Event.Unsubscribe(RewardSelectGiveUpEventArgs.EventId, OnGiveUpClicked);
|
||||
}
|
||||
|
||||
public override int? OpenUI(object userData = null)
|
||||
{
|
||||
if (userData is RewardSelectFormContext context)
|
||||
{
|
||||
return OpenUIInternal(context);
|
||||
}
|
||||
|
||||
if (userData is RewardSelectFormRawData rawDataFromUserData)
|
||||
{
|
||||
return OpenUI(rawDataFromUserData);
|
||||
}
|
||||
|
||||
if (userData != null)
|
||||
{
|
||||
Log.Warning("RewardSelectFormController.OpenUI() userData type is invalid.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_useCase == null)
|
||||
{
|
||||
Log.Error("RewardSelectFormController.OpenUI() useCase is null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
RewardSelectFormRawData rawData = _useCase.CreateInitialModel();
|
||||
return OpenUI(rawData);
|
||||
}
|
||||
|
||||
public int? OpenUI(RewardSelectFormRawData rawData)
|
||||
{
|
||||
RewardSelectFormContext context = BuildContext(rawData);
|
||||
return OpenUIInternal(context);
|
||||
}
|
||||
|
||||
public override void BindUseCase(IUIUseCase useCase)
|
||||
{
|
||||
if (!(useCase is RewardSelectFormUseCase rewardSelectUseCase))
|
||||
{
|
||||
Log.Error("RewardSelectFormController.BindUseCase() useCase is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
_useCase = rewardSelectUseCase;
|
||||
}
|
||||
|
||||
private void OnRewardSelected(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!IsEventFromCurrentForm(sender) || !(e is RewardSelectItemSelectedEventArgs args))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_useCase == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RewardSelectFormRawData nextRawData = _useCase.SelectReward(args.SelectedIndex);
|
||||
if (nextRawData == null)
|
||||
{
|
||||
CloseUI();
|
||||
return;
|
||||
}
|
||||
|
||||
OpenUI(nextRawData);
|
||||
}
|
||||
|
||||
private void OnRefreshClicked(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!IsEventFromCurrentForm(sender) || !(e is RewardSelectRefreshEventArgs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_useCase == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RewardSelectFormRawData nextRawData = _useCase.TryRotateSelection();
|
||||
if (nextRawData == null)
|
||||
{
|
||||
CloseUI();
|
||||
return;
|
||||
}
|
||||
|
||||
OpenUI(nextRawData);
|
||||
}
|
||||
|
||||
private void OnGiveUpClicked(object sender, GameEventArgs e)
|
||||
{
|
||||
if (!IsEventFromCurrentForm(sender) || !(e is RewardSelectGiveUpEventArgs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_useCase == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_useCase.TryGiveUp())
|
||||
{
|
||||
CloseUI();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsEventFromCurrentForm(object sender)
|
||||
{
|
||||
if (Form == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(sender, Form))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sender is Component component)
|
||||
{
|
||||
RewardSelectForm ownerForm = component.GetComponentInParent<RewardSelectForm>();
|
||||
return ownerForm == Form;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using GameFramework;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 对话框显示数据。
|
||||
/// </summary>
|
||||
public class DialogFormRawData
|
||||
{
|
||||
/// <summary>
|
||||
/// 模式,即按钮数量。取值 1、2、3。
|
||||
/// </summary>
|
||||
public int Mode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标题。
|
||||
/// </summary>
|
||||
public string Title
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息内容。
|
||||
/// </summary>
|
||||
public string Message
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 弹出窗口时是否暂停游戏。
|
||||
/// </summary>
|
||||
public bool PauseGame
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确认按钮文本。
|
||||
/// </summary>
|
||||
public string ConfirmText
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确定按钮回调。
|
||||
/// </summary>
|
||||
public GameFrameworkAction<object> OnClickConfirm
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消按钮文本。
|
||||
/// </summary>
|
||||
public string CancelText
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消按钮回调。
|
||||
/// </summary>
|
||||
public GameFrameworkAction<object> OnClickCancel
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 中立按钮文本。
|
||||
/// </summary>
|
||||
public string OtherText
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 其它按钮回调。
|
||||
/// </summary>
|
||||
public GameFrameworkAction<object> OnClickOther
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户自定义数据。
|
||||
/// </summary>
|
||||
public object UserData
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class ItemDescFormRawData
|
||||
{
|
||||
public string Title;
|
||||
public string TypeText;
|
||||
public string Description;
|
||||
public int Price;
|
||||
public Vector2 ScreenPosition;
|
||||
public TagType[] Tags;
|
||||
public TagRuntimeData[] TagRuntimes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class RewardSelectFormRawData
|
||||
{
|
||||
public string TipText;
|
||||
public RewardSelectItemRawData[] RewardItems;
|
||||
public int RefreshCost;
|
||||
public bool CanRefresh;
|
||||
public bool CanGiveUp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class RewardSelectItemRawData
|
||||
{
|
||||
public TowerCompSlotType SlotType;
|
||||
public string Title;
|
||||
public string TypeText;
|
||||
public string Description;
|
||||
public RarityType Rarity;
|
||||
public TagType[] Tags;
|
||||
public Sprite Icon;
|
||||
public TowerCompItemData SourceItem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using GeometryTD.CustomUtility;
|
||||
using GeometryTD.Definition;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public static class RewardSelectItemRawDataBuilder
|
||||
{
|
||||
public static RewardSelectItemRawData Build(TowerCompItemData item)
|
||||
{
|
||||
return new RewardSelectItemRawData
|
||||
{
|
||||
SlotType = item.SlotType,
|
||||
Title = item.Name,
|
||||
TypeText = BuildTypeText(item.SlotType),
|
||||
Description = BuildDescription(item),
|
||||
Rarity = item.Rarity,
|
||||
Tags = item.Tags != null ? (TagType[])item.Tags.Clone() : Array.Empty<TagType>(),
|
||||
Icon = null,
|
||||
SourceItem = item
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildTypeText(TowerCompSlotType slotType)
|
||||
{
|
||||
return slotType switch
|
||||
{
|
||||
TowerCompSlotType.Muzzle => "Muzzle Component",
|
||||
TowerCompSlotType.Bearing => "Bearing Component",
|
||||
TowerCompSlotType.Base => "Base Component",
|
||||
TowerCompSlotType.Accessory => "Accessory",
|
||||
_ => "Component"
|
||||
};
|
||||
}
|
||||
|
||||
private static string BuildDescription(TowerCompItemData item)
|
||||
{
|
||||
if (item is MuzzleCompItemData muzzle)
|
||||
{
|
||||
return ItemDescUtility.BuildMuzzleDesc(muzzle);
|
||||
}
|
||||
|
||||
if (item is BearingCompItemData bearing)
|
||||
{
|
||||
return ItemDescUtility.BuildBearingDesc(bearing);
|
||||
}
|
||||
|
||||
if (item is BaseCompItemData baseComp)
|
||||
{
|
||||
return ItemDescUtility.BuildBaseDesc(baseComp);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class ItemDescFormUseCase : IUIUseCase
|
||||
{
|
||||
public ItemDescFormUseCase()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class RewardSelectFormUseCase : IUIUseCase
|
||||
{
|
||||
private readonly List<RewardSelectItemRawData> _rewardPool = new List<RewardSelectItemRawData>();
|
||||
|
||||
private RewardSelectFormRawData _currentModel;
|
||||
private Action<RewardSelectItemRawData> _onRewardSelected;
|
||||
private Action _onGiveUp;
|
||||
|
||||
private int _displayCount = 3;
|
||||
private int _refreshCost;
|
||||
private bool _allowRotateOnce = true;
|
||||
private bool _allowGiveUp = true;
|
||||
private bool _hasRotated;
|
||||
private int _selectionOffset;
|
||||
private string _tipText = "Select one reward";
|
||||
|
||||
// RewardSelectForm keeps a fixed reward pool for the current node.
|
||||
// The "refresh" action only rotates which slice of that pool is shown so save/load
|
||||
// can reopen the same node with the exact same reward contents.
|
||||
public void ConfigureRewardPool(
|
||||
IReadOnlyList<RewardSelectItemRawData> rewardPool,
|
||||
int displayCount = 3,
|
||||
int refreshCost = 0,
|
||||
bool allowRotateOnce = true,
|
||||
bool allowGiveUp = true,
|
||||
string tipText = null)
|
||||
{
|
||||
_rewardPool.Clear();
|
||||
if (rewardPool != null)
|
||||
{
|
||||
foreach (var item in rewardPool)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_rewardPool.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
_displayCount = Mathf.Max(1, displayCount);
|
||||
_refreshCost = Mathf.Max(0, refreshCost);
|
||||
_allowRotateOnce = allowRotateOnce;
|
||||
_allowGiveUp = allowGiveUp;
|
||||
_tipText = string.IsNullOrWhiteSpace(tipText) ? "Select one reward" : tipText;
|
||||
_hasRotated = false;
|
||||
_selectionOffset = 0;
|
||||
_currentModel = null;
|
||||
}
|
||||
|
||||
public void ConfigureRewardCandidates(
|
||||
IReadOnlyList<TowerCompItemData> rewardCandidates,
|
||||
int displayCount = 3,
|
||||
int refreshCost = 0,
|
||||
bool allowRotateOnce = true,
|
||||
bool allowGiveUp = true,
|
||||
string tipText = null)
|
||||
{
|
||||
_rewardPool.Clear();
|
||||
if (rewardCandidates != null)
|
||||
{
|
||||
foreach (var item in rewardCandidates)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_rewardPool.Add(RewardSelectItemRawDataBuilder.Build(item));
|
||||
}
|
||||
}
|
||||
|
||||
_displayCount = Mathf.Max(1, displayCount);
|
||||
_refreshCost = Mathf.Max(0, refreshCost);
|
||||
_allowRotateOnce = allowRotateOnce;
|
||||
_allowGiveUp = allowGiveUp;
|
||||
_tipText = string.IsNullOrWhiteSpace(tipText) ? "Select one reward" : tipText;
|
||||
_hasRotated = false;
|
||||
_selectionOffset = 0;
|
||||
_currentModel = null;
|
||||
}
|
||||
|
||||
public void SetCallbacks(Action<RewardSelectItemRawData> onRewardSelected, Action onGiveUp = null)
|
||||
{
|
||||
_onRewardSelected = onRewardSelected;
|
||||
_onGiveUp = onGiveUp;
|
||||
}
|
||||
|
||||
public RewardSelectFormRawData CreateInitialModel()
|
||||
{
|
||||
_hasRotated = false;
|
||||
_currentModel = BuildModel();
|
||||
return _currentModel;
|
||||
}
|
||||
|
||||
public RewardSelectFormRawData TryRotateSelection()
|
||||
{
|
||||
if (_currentModel == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!CanRotateSelectionInternal())
|
||||
{
|
||||
return _currentModel;
|
||||
}
|
||||
|
||||
if (!TryConsumeRotateCost())
|
||||
{
|
||||
_currentModel.CanRefresh = false;
|
||||
return _currentModel;
|
||||
}
|
||||
|
||||
_hasRotated = true;
|
||||
if (_rewardPool.Count > 0)
|
||||
{
|
||||
_selectionOffset = (_selectionOffset + _displayCount) % _rewardPool.Count;
|
||||
}
|
||||
|
||||
_currentModel = BuildModel();
|
||||
return _currentModel;
|
||||
}
|
||||
|
||||
public RewardSelectFormRawData SelectReward(int selectedIndex)
|
||||
{
|
||||
if (_currentModel?.RewardItems == null || _currentModel.RewardItems.Length <= 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (selectedIndex < 0 || selectedIndex >= _currentModel.RewardItems.Length)
|
||||
{
|
||||
return _currentModel;
|
||||
}
|
||||
|
||||
RewardSelectItemRawData selectedReward = _currentModel.RewardItems[selectedIndex];
|
||||
_onRewardSelected?.Invoke(selectedReward);
|
||||
|
||||
_currentModel = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool TryGiveUp()
|
||||
{
|
||||
if (!_allowGiveUp)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_onGiveUp?.Invoke();
|
||||
_currentModel = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private RewardSelectFormRawData BuildModel()
|
||||
{
|
||||
RewardSelectItemRawData[] selectedRewards = SelectRewards();
|
||||
|
||||
return new RewardSelectFormRawData
|
||||
{
|
||||
TipText = _tipText,
|
||||
RewardItems = selectedRewards,
|
||||
RefreshCost = _refreshCost,
|
||||
CanRefresh = selectedRewards.Length > 0 && CanRotateSelectionInternal(),
|
||||
CanGiveUp = _allowGiveUp
|
||||
};
|
||||
}
|
||||
|
||||
private RewardSelectItemRawData[] SelectRewards()
|
||||
{
|
||||
if (_rewardPool.Count <= 0)
|
||||
{
|
||||
return Array.Empty<RewardSelectItemRawData>();
|
||||
}
|
||||
|
||||
int finalCount = Mathf.Clamp(_displayCount, 1, _rewardPool.Count);
|
||||
RewardSelectItemRawData[] results = new RewardSelectItemRawData[finalCount];
|
||||
for (int i = 0; i < finalCount; i++)
|
||||
{
|
||||
int sourceIndex = (_selectionOffset + i) % _rewardPool.Count;
|
||||
RewardSelectItemRawData source = _rewardPool[sourceIndex];
|
||||
results[i] = source;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private bool CanRotateSelectionInternal()
|
||||
{
|
||||
if (!_allowRotateOnce || _hasRotated)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return CanPayRotateCost();
|
||||
}
|
||||
|
||||
private bool CanPayRotateCost()
|
||||
{
|
||||
if (_refreshCost <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (GameEntry.PlayerInventory == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return GameEntry.PlayerInventory.Gold >= _refreshCost;
|
||||
}
|
||||
|
||||
private bool TryConsumeRotateCost()
|
||||
{
|
||||
if (_refreshCost <= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (GameEntry.PlayerInventory == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return GameEntry.PlayerInventory.TryConsumeGold(_refreshCost);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
using GameFramework;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class DialogForm : UGuiForm
|
||||
{
|
||||
[SerializeField] private TMP_Text _titleText = null;
|
||||
[SerializeField] private TMP_Text _messageText = null;
|
||||
[SerializeField] private GameObject[] _modeObjects = null;
|
||||
[SerializeField] private TMP_Text[] _confirmTexts = null;
|
||||
[SerializeField] private TMP_Text[] _cancelTexts = null;
|
||||
[SerializeField] private TMP_Text[] _otherTexts = null;
|
||||
|
||||
private int _dialogMode = 1;
|
||||
private bool _pauseGame = false;
|
||||
private object _userData = null;
|
||||
private GameFrameworkAction<object> _onClickConfirmGFAction = null;
|
||||
private GameFrameworkAction<object> _onClickCancelGFAction = null;
|
||||
private GameFrameworkAction<object> _onClickOtherGFAction = null;
|
||||
private DialogFormContext _context = null;
|
||||
|
||||
public int DialogMode => _dialogMode;
|
||||
|
||||
public bool PauseGame => _pauseGame;
|
||||
|
||||
public object UserData => _userData;
|
||||
|
||||
public void OnConfirmButtonClick()
|
||||
{
|
||||
Close();
|
||||
|
||||
if (_onClickConfirmGFAction != null)
|
||||
{
|
||||
_onClickConfirmGFAction(_userData);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnCancelButtonClick()
|
||||
{
|
||||
Close();
|
||||
|
||||
if (_onClickCancelGFAction != null)
|
||||
{
|
||||
_onClickCancelGFAction(_userData);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnOtherButtonClick()
|
||||
{
|
||||
Close();
|
||||
|
||||
if (_onClickOtherGFAction != null)
|
||||
{
|
||||
_onClickOtherGFAction(_userData);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnOpen(object userData)
|
||||
#else
|
||||
protected internal override void OnOpen(object userData)
|
||||
#endif
|
||||
{
|
||||
base.OnOpen(userData);
|
||||
|
||||
if (userData is DialogFormContext context)
|
||||
{
|
||||
RefreshUI(context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (userData is DialogFormRawData dialogParams)
|
||||
{
|
||||
RefreshUI(BuildContext(dialogParams));
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Warning("DialogForm requires DialogFormContext or DialogParams as userData.");
|
||||
}
|
||||
|
||||
#if UNITY_2017_3_OR_NEWER
|
||||
protected override void OnClose(bool isShutdown, object userData)
|
||||
#else
|
||||
protected internal override void OnClose(bool isShutdown, object userData)
|
||||
#endif
|
||||
{
|
||||
if (_pauseGame)
|
||||
{
|
||||
GameEntry.Base.ResumeGame();
|
||||
}
|
||||
|
||||
_dialogMode = 1;
|
||||
_titleText.text = string.Empty;
|
||||
_messageText.text = string.Empty;
|
||||
_pauseGame = false;
|
||||
_userData = null;
|
||||
_context = null;
|
||||
|
||||
RefreshConfirmText(string.Empty);
|
||||
_onClickConfirmGFAction = null;
|
||||
|
||||
RefreshCancelText(string.Empty);
|
||||
_onClickCancelGFAction = null;
|
||||
|
||||
RefreshOtherText(string.Empty);
|
||||
_onClickOtherGFAction = null;
|
||||
|
||||
base.OnClose(isShutdown, userData);
|
||||
}
|
||||
|
||||
public void RefreshUI(DialogFormContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
Log.Warning("DialogForm context is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
_context = context;
|
||||
|
||||
_dialogMode = context.Mode;
|
||||
RefreshDialogMode();
|
||||
|
||||
_titleText.text = context.Title;
|
||||
_messageText.text = context.Message;
|
||||
|
||||
_pauseGame = context.PauseGame;
|
||||
RefreshPauseGame();
|
||||
|
||||
_userData = context.UserData;
|
||||
|
||||
RefreshConfirmText(context.ConfirmText);
|
||||
_onClickConfirmGFAction = context.OnClickConfirm;
|
||||
|
||||
RefreshCancelText(context.CancelText);
|
||||
_onClickCancelGFAction = context.OnClickCancel;
|
||||
|
||||
RefreshOtherText(context.OtherText);
|
||||
_onClickOtherGFAction = context.OnClickOther;
|
||||
}
|
||||
|
||||
private static DialogFormContext BuildContext(DialogFormRawData rawData)
|
||||
{
|
||||
if (rawData == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DialogFormContext
|
||||
{
|
||||
Mode = rawData.Mode,
|
||||
Title = rawData.Title,
|
||||
Message = rawData.Message,
|
||||
PauseGame = rawData.PauseGame,
|
||||
ConfirmText = rawData.ConfirmText,
|
||||
OnClickConfirm = rawData.OnClickConfirm,
|
||||
CancelText = rawData.CancelText,
|
||||
OnClickCancel = rawData.OnClickCancel,
|
||||
OtherText = rawData.OtherText,
|
||||
OnClickOther = rawData.OnClickOther,
|
||||
UserData = rawData.UserData
|
||||
};
|
||||
}
|
||||
|
||||
private void RefreshDialogMode()
|
||||
{
|
||||
for (int i = 1; i <= _modeObjects.Length; i++)
|
||||
{
|
||||
_modeObjects[i - 1].SetActive(i == _dialogMode);
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshPauseGame()
|
||||
{
|
||||
if (_pauseGame)
|
||||
{
|
||||
GameEntry.Base.PauseGame();
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshConfirmText(string confirmText)
|
||||
{
|
||||
foreach (var text in _confirmTexts)
|
||||
{
|
||||
text.text = confirmText;
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshCancelText(string cancelText)
|
||||
{
|
||||
foreach (var text in _cancelTexts)
|
||||
{
|
||||
text.text = cancelText;
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshOtherText(string otherText)
|
||||
{
|
||||
foreach (var text in _otherTexts)
|
||||
{
|
||||
text.text = otherText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using GeometryTD.Definition;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class IconArea : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Image _board;
|
||||
|
||||
[SerializeField] private Image _icon;
|
||||
|
||||
private IconAreaContext _context;
|
||||
|
||||
public virtual Sprite CurrentIconSprite => _icon != null ? _icon.sprite : null;
|
||||
|
||||
public virtual Color CurrentIconColor => _icon != null ? _icon.color : Color.white;
|
||||
|
||||
public virtual Vector2 CurrentIconSize => _icon != null ? _icon.rectTransform.rect.size : Vector2.zero;
|
||||
|
||||
public virtual Material CurrentIconMaterial => _icon != null ? _icon.material : null;
|
||||
|
||||
public virtual RectTransform IconRectTransform => _icon != null ? _icon.rectTransform : null;
|
||||
|
||||
public virtual void OnInit(IconAreaContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
Log.Error("Icon area need IconAreaContext.");
|
||||
return;
|
||||
}
|
||||
|
||||
_context = context;
|
||||
|
||||
switch (context.ComponentSlotType)
|
||||
{
|
||||
case TowerCompSlotType.Base:
|
||||
case TowerCompSlotType.Bearing:
|
||||
case TowerCompSlotType.Muzzle:
|
||||
GameEntry.SpriteCache.GetSprite(context.ComponentSlotType.ToString(), SetIcon);
|
||||
break;
|
||||
default:
|
||||
SetIcon(_context.Icon);
|
||||
break;
|
||||
}
|
||||
|
||||
SetRarity(_context.Rarity);
|
||||
SetIconColor(_context.Color);
|
||||
SetIconVisible(true);
|
||||
}
|
||||
|
||||
public virtual void SetIcon(Sprite sprite)
|
||||
{
|
||||
if (_icon != null)
|
||||
{
|
||||
_icon.sprite = sprite;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void SetRarity(RarityType rarity)
|
||||
{
|
||||
if (_board == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_board.color = rarity switch
|
||||
{
|
||||
RarityType.White => Color.white,
|
||||
RarityType.Green => Color.green,
|
||||
RarityType.Blue => Color.blue,
|
||||
RarityType.Purple => Color.magenta,
|
||||
RarityType.Red => Color.red,
|
||||
_ => Color.clear,
|
||||
};
|
||||
}
|
||||
|
||||
public virtual void SetIconColor(Color color)
|
||||
{
|
||||
if (_icon == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_icon.color = color;
|
||||
}
|
||||
|
||||
public virtual void SetIconVisible(bool visible)
|
||||
{
|
||||
if (_icon != null)
|
||||
{
|
||||
_icon.enabled = visible;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnReset()
|
||||
{
|
||||
SetIcon(null);
|
||||
SetRarity(RarityType.None);
|
||||
SetIconColor(Color.clear);
|
||||
SetIconVisible(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class ItemDescForm : UGuiForm
|
||||
{
|
||||
[SerializeField] private RectTransform _content;
|
||||
|
||||
[SerializeField] private TMP_Text _itemTitle;
|
||||
|
||||
[SerializeField] private TMP_Text _itemTypeText;
|
||||
|
||||
[SerializeField] private RectTransform _descScrollView;
|
||||
|
||||
[SerializeField] private TMP_Text _itemDescription;
|
||||
|
||||
[SerializeField] private TMP_Text _itemPrice;
|
||||
|
||||
[SerializeField] private Transform _tagAreaParent;
|
||||
|
||||
[SerializeField] private GameObject _tagItemPrefab;
|
||||
|
||||
[SerializeField] private bool _autoResizeHeight = true;
|
||||
|
||||
[SerializeField] private float _fixedTopHeight = 150f;
|
||||
|
||||
[SerializeField] private float _fixedBottomHeight = 150f;
|
||||
|
||||
[SerializeField] private float _fixedWidth = 300f;
|
||||
|
||||
[SerializeField] private float _maxDescriptionViewportHeight = 0f;
|
||||
|
||||
[SerializeField] private float _screenEdgePadding = 0f;
|
||||
|
||||
[SerializeField] private float _sideGap = 16f;
|
||||
|
||||
[SerializeField] private float _anchorItemWidth = 0f;
|
||||
|
||||
private readonly List<GameObject> _runtimeTagItems = new List<GameObject>();
|
||||
|
||||
private ItemDescFormContext _context;
|
||||
private Vector2 _targetPos;
|
||||
private bool _isOpened;
|
||||
|
||||
public void RefreshUI(ItemDescFormContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
Log.Warning("ItemDescForm context is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
_context = context;
|
||||
|
||||
_targetPos = ConvertScreenToAnchored(_context.ScreenPosition);
|
||||
|
||||
if (_content != null)
|
||||
{
|
||||
_content.anchoredPosition3D = _targetPos;
|
||||
}
|
||||
|
||||
if (_itemTitle != null) _itemTitle.text = _context.Title ?? string.Empty;
|
||||
if (_itemTypeText != null) _itemTypeText.text = _context.TypeText ?? string.Empty;
|
||||
if (_itemDescription != null) _itemDescription.text = _context.Description ?? string.Empty;
|
||||
if (_itemPrice != null) _itemPrice.text = $"Price: {_context.Price} Gold";
|
||||
|
||||
RefreshTags(_context.Tags);
|
||||
|
||||
ResizeToFitContent();
|
||||
ApplySideAnchoredTargetPos();
|
||||
}
|
||||
|
||||
protected override void OnOpen(object userData)
|
||||
{
|
||||
base.OnOpen(userData);
|
||||
_isOpened = true;
|
||||
|
||||
if (!(userData is ItemDescFormContext context))
|
||||
{
|
||||
Log.Error("ItemDescFormContext is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshUI(context);
|
||||
}
|
||||
|
||||
protected override void OnClose(bool isShutdown, object userData)
|
||||
{
|
||||
_isOpened = false;
|
||||
ClearTags();
|
||||
_context = null;
|
||||
base.OnClose(isShutdown, userData);
|
||||
}
|
||||
|
||||
public void OnBlankAreaClick()
|
||||
{
|
||||
CloseSelf();
|
||||
}
|
||||
|
||||
public void OnBackgroundClick()
|
||||
{
|
||||
CloseSelf();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_isOpened || _content == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryGetPointerDownScreenPosition(out Vector2 screenPoint))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsPointerInContent(screenPoint))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CloseSelf();
|
||||
}
|
||||
|
||||
private void ResizeToFitContent()
|
||||
{
|
||||
if (!_autoResizeHeight || _content == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_itemDescription == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_itemDescription.ForceMeshUpdate();
|
||||
float descriptionHeight = Mathf.Max(0f, _itemDescription.preferredHeight);
|
||||
SetRectHeight(_itemDescription.rectTransform, descriptionHeight);
|
||||
|
||||
RectTransform scrollContent = _itemDescription.rectTransform.parent as RectTransform;
|
||||
if (scrollContent != null)
|
||||
{
|
||||
SetRectHeight(scrollContent, descriptionHeight);
|
||||
}
|
||||
|
||||
float viewportHeight = descriptionHeight;
|
||||
if (_descScrollView != null)
|
||||
{
|
||||
viewportHeight = Mathf.Min(descriptionHeight, ResolveMaxDescriptionViewportHeight());
|
||||
SetRectHeight(_descScrollView, viewportHeight);
|
||||
|
||||
if (_descScrollView.TryGetComponent(out ScrollRect scrollRect))
|
||||
{
|
||||
scrollRect.verticalNormalizedPosition = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
float targetHeight = _fixedTopHeight + viewportHeight + _fixedBottomHeight;
|
||||
|
||||
Vector2 size = new Vector2(_fixedWidth, targetHeight);
|
||||
_content.sizeDelta = size;
|
||||
}
|
||||
|
||||
private float ResolveMaxDescriptionViewportHeight()
|
||||
{
|
||||
float maxViewportHeight = _maxDescriptionViewportHeight;
|
||||
RectTransform parent = _content.parent as RectTransform;
|
||||
if (parent != null)
|
||||
{
|
||||
float availableHeight = parent.rect.height - _fixedTopHeight - _fixedBottomHeight
|
||||
- _screenEdgePadding * 2f;
|
||||
availableHeight = Mathf.Max(0f, availableHeight);
|
||||
maxViewportHeight = maxViewportHeight > 0f
|
||||
? Mathf.Min(maxViewportHeight, availableHeight)
|
||||
: availableHeight;
|
||||
}
|
||||
|
||||
return maxViewportHeight > 0f ? maxViewportHeight : float.MaxValue;
|
||||
}
|
||||
|
||||
private static void SetRectHeight(RectTransform rectTransform, float height)
|
||||
{
|
||||
if (rectTransform == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 size = rectTransform.sizeDelta;
|
||||
size.y = height;
|
||||
rectTransform.sizeDelta = size;
|
||||
}
|
||||
|
||||
private void ApplySideAnchoredTargetPos()
|
||||
{
|
||||
if (_content == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RectTransform parent = _content.parent as RectTransform;
|
||||
if (parent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 size = _content.sizeDelta;
|
||||
if (size.x <= 0f || size.y <= 0f)
|
||||
{
|
||||
size = _content.rect.size;
|
||||
}
|
||||
|
||||
Vector2 pivot = _content.pivot;
|
||||
float halfWidth = parent.rect.width * 0.5f;
|
||||
float halfHeight = parent.rect.height * 0.5f;
|
||||
|
||||
float minX = -halfWidth + size.x * pivot.x + _screenEdgePadding;
|
||||
float maxX = halfWidth - size.x * (1f - pivot.x) - _screenEdgePadding;
|
||||
float minY = -halfHeight + size.y * pivot.y + _screenEdgePadding;
|
||||
float maxY = halfHeight - size.y * (1f - pivot.y) - _screenEdgePadding;
|
||||
|
||||
float clampedY = minY <= maxY ? Mathf.Clamp(_targetPos.y, minY, maxY) : 0f;
|
||||
|
||||
float anchorHalfWidth = Mathf.Max(0f, _anchorItemWidth * 0.5f);
|
||||
float sideGap = Mathf.Max(0f, _sideGap);
|
||||
float horizontalOffset = anchorHalfWidth + size.x * 0.5f + sideGap;
|
||||
|
||||
float leftCandidateX = _targetPos.x - horizontalOffset;
|
||||
float rightCandidateX = _targetPos.x + horizontalOffset;
|
||||
|
||||
float finalX;
|
||||
if (IsInsideBounds(leftCandidateX, minX, maxX))
|
||||
{
|
||||
finalX = leftCandidateX;
|
||||
}
|
||||
else if (IsInsideBounds(rightCandidateX, minX, maxX))
|
||||
{
|
||||
finalX = rightCandidateX;
|
||||
}
|
||||
else
|
||||
{
|
||||
finalX = minX <= maxX ? Mathf.Clamp(leftCandidateX, minX, maxX) : 0f;
|
||||
}
|
||||
|
||||
_content.anchoredPosition = new Vector2(finalX, clampedY);
|
||||
}
|
||||
|
||||
private Vector2 ConvertScreenToAnchored(Vector2 screenPosition)
|
||||
{
|
||||
if (_content == null)
|
||||
{
|
||||
return screenPosition;
|
||||
}
|
||||
|
||||
RectTransform parent = _content.parent as RectTransform;
|
||||
if (parent == null)
|
||||
{
|
||||
return screenPosition;
|
||||
}
|
||||
|
||||
Canvas canvas = _content.GetComponentInParent<Canvas>();
|
||||
Canvas rootCanvas = canvas != null ? canvas.rootCanvas : null;
|
||||
Camera uiCamera = null;
|
||||
if (rootCanvas != null && rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay)
|
||||
{
|
||||
uiCamera = rootCanvas.worldCamera != null ? rootCanvas.worldCamera : Camera.main;
|
||||
}
|
||||
|
||||
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(parent, screenPosition, uiCamera,
|
||||
out Vector2 localPoint))
|
||||
{
|
||||
return screenPosition;
|
||||
}
|
||||
|
||||
return localPoint;
|
||||
}
|
||||
|
||||
private static bool IsInsideBounds(float value, float min, float max)
|
||||
{
|
||||
return value >= min && value <= max;
|
||||
}
|
||||
|
||||
private void RefreshTags(TagItemContext[] tags)
|
||||
{
|
||||
ClearTags();
|
||||
|
||||
if (_tagAreaParent == null || _tagItemPrefab == null || tags == null || tags.Length <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_tagItemPrefab.scene.IsValid() && _tagItemPrefab.transform.IsChildOf(_tagAreaParent))
|
||||
{
|
||||
_tagItemPrefab.SetActive(false);
|
||||
}
|
||||
|
||||
for (int i = 0; i < tags.Length; i++)
|
||||
{
|
||||
TagItemContext tagContext = tags[i];
|
||||
if (tagContext == null || string.IsNullOrWhiteSpace(tagContext.TagName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GameObject tagGo = Instantiate(_tagItemPrefab, _tagAreaParent);
|
||||
tagGo.SetActive(true);
|
||||
|
||||
if (tagGo.TryGetComponent(out TagItem tagItem))
|
||||
{
|
||||
tagItem.OnInit(tagContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
TMP_Text tagText = tagGo.GetComponentInChildren<TMP_Text>(true);
|
||||
if (tagText != null)
|
||||
{
|
||||
tagText.text = tagContext.TagName;
|
||||
}
|
||||
}
|
||||
|
||||
_runtimeTagItems.Add(tagGo);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearTags()
|
||||
{
|
||||
for (int i = _runtimeTagItems.Count - 1; i >= 0; i--)
|
||||
{
|
||||
GameObject tagGo = _runtimeTagItems[i];
|
||||
if (tagGo != null)
|
||||
{
|
||||
Destroy(tagGo);
|
||||
}
|
||||
}
|
||||
|
||||
_runtimeTagItems.Clear();
|
||||
}
|
||||
|
||||
private void CloseSelf()
|
||||
{
|
||||
GameEntry.UI.CloseUIForm(this);
|
||||
}
|
||||
|
||||
private bool IsPointerInContent(Vector2 screenPoint)
|
||||
{
|
||||
Canvas canvas = _content.GetComponentInParent<Canvas>();
|
||||
Canvas rootCanvas = canvas != null ? canvas.rootCanvas : null;
|
||||
Camera uiCamera = null;
|
||||
if (rootCanvas != null && rootCanvas.renderMode != RenderMode.ScreenSpaceOverlay)
|
||||
{
|
||||
uiCamera = rootCanvas.worldCamera != null ? rootCanvas.worldCamera : Camera.main;
|
||||
}
|
||||
|
||||
return RectTransformUtility.RectangleContainsScreenPoint(_content, screenPoint, uiCamera);
|
||||
}
|
||||
|
||||
private static bool TryGetPointerDownScreenPosition(out Vector2 screenPoint)
|
||||
{
|
||||
Touchscreen touchscreen = Touchscreen.current;
|
||||
if (touchscreen != null)
|
||||
{
|
||||
var touches = touchscreen.touches;
|
||||
for (int i = 0; i < touches.Count; i++)
|
||||
{
|
||||
var touch = touches[i];
|
||||
if (!touch.press.wasPressedThisFrame)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
screenPoint = touch.position.ReadValue();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Mouse mouse = Mouse.current;
|
||||
if (mouse != null && mouse.leftButton.wasPressedThisFrame)
|
||||
{
|
||||
screenPoint = mouse.position.ReadValue();
|
||||
return true;
|
||||
}
|
||||
|
||||
screenPoint = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.CustomEvent;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace GeometryTD.UI
|
||||
{
|
||||
public class RewardItem : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private IconArea _iconArea;
|
||||
|
||||
[SerializeField] private TMP_Text _titleText;
|
||||
|
||||
[SerializeField] private TMP_Text _typeText;
|
||||
|
||||
[SerializeField] private TMP_Text _descriptionText;
|
||||
|
||||
[SerializeField] private RectTransform _descriptionScrollView;
|
||||
|
||||
[SerializeField] private RectTransform _descriptionContent;
|
||||
|
||||
[SerializeField] private float _maxDescriptionViewportHeight = 0f;
|
||||
|
||||
[SerializeField] private Transform _tagItemParent;
|
||||
|
||||
[SerializeField] private TagItem _tagItemTemplate;
|
||||
|
||||
private readonly List<TagItem> _runtimeTagItems = new List<TagItem>();
|
||||
|
||||
private RewardItemContext _context;
|
||||
|
||||
|
||||
public void OnInit(RewardItemContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
OnReset();
|
||||
gameObject.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_context = context;
|
||||
gameObject.SetActive(true);
|
||||
|
||||
if (_iconArea != null)
|
||||
{
|
||||
if (context.IconArea != null)
|
||||
{
|
||||
_iconArea.OnInit(context.IconArea);
|
||||
}
|
||||
else
|
||||
{
|
||||
_iconArea.OnReset();
|
||||
}
|
||||
}
|
||||
|
||||
if (_titleText != null)
|
||||
{
|
||||
_titleText.text = context.Title ?? string.Empty;
|
||||
}
|
||||
|
||||
if (_typeText != null)
|
||||
{
|
||||
_typeText.text = context.TypeText ?? string.Empty;
|
||||
}
|
||||
|
||||
if (_descriptionText != null)
|
||||
{
|
||||
_descriptionText.text = context.Description ?? string.Empty;
|
||||
}
|
||||
|
||||
ResizeDescriptionArea();
|
||||
|
||||
RefreshTags(context.Tags);
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
_context = null;
|
||||
|
||||
if (_iconArea != null)
|
||||
{
|
||||
_iconArea.OnReset();
|
||||
}
|
||||
|
||||
if (_titleText != null)
|
||||
{
|
||||
_titleText.text = string.Empty;
|
||||
}
|
||||
|
||||
if (_typeText != null)
|
||||
{
|
||||
_typeText.text = string.Empty;
|
||||
}
|
||||
|
||||
if (_descriptionText != null)
|
||||
{
|
||||
_descriptionText.text = string.Empty;
|
||||
}
|
||||
|
||||
ResetDescriptionScroll();
|
||||
|
||||
ClearTags();
|
||||
}
|
||||
|
||||
public void OnClick()
|
||||
{
|
||||
if (_context == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameEntry.Event.Fire(this, RewardSelectItemSelectedEventArgs.Create(_context.Index));
|
||||
}
|
||||
|
||||
private void RefreshTags(TagItemContext[] tagContexts)
|
||||
{
|
||||
ClearTags();
|
||||
|
||||
if (_tagItemParent == null || tagContexts == null || tagContexts.Length <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TagItem template = ResolveTagTemplate();
|
||||
if (template == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < tagContexts.Length; i++)
|
||||
{
|
||||
TagItemContext tagContext = tagContexts[i];
|
||||
if (tagContext == null || string.IsNullOrWhiteSpace(tagContext.TagName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TagItem tagItem = Instantiate(template, _tagItemParent);
|
||||
tagItem.gameObject.SetActive(true);
|
||||
tagItem.OnInit(tagContext);
|
||||
_runtimeTagItems.Add(tagItem);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResizeDescriptionArea()
|
||||
{
|
||||
if (_descriptionText == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_descriptionText.ForceMeshUpdate();
|
||||
float descriptionHeight = Mathf.Max(0f, _descriptionText.preferredHeight);
|
||||
SetRectHeight(_descriptionText.rectTransform, descriptionHeight);
|
||||
|
||||
if (_descriptionContent != null)
|
||||
{
|
||||
SetRectHeight(_descriptionContent, descriptionHeight);
|
||||
}
|
||||
|
||||
if (_descriptionScrollView != null)
|
||||
{
|
||||
float viewportHeight = _maxDescriptionViewportHeight > 0f
|
||||
? Mathf.Min(descriptionHeight, _maxDescriptionViewportHeight)
|
||||
: descriptionHeight;
|
||||
SetRectHeight(_descriptionScrollView, viewportHeight);
|
||||
}
|
||||
|
||||
ResetDescriptionScroll();
|
||||
}
|
||||
|
||||
private void ResetDescriptionScroll()
|
||||
{
|
||||
if (_descriptionScrollView != null &&
|
||||
_descriptionScrollView.TryGetComponent(out ScrollRect scrollRect))
|
||||
{
|
||||
scrollRect.verticalNormalizedPosition = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetRectHeight(RectTransform rectTransform, float height)
|
||||
{
|
||||
if (rectTransform == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 size = rectTransform.sizeDelta;
|
||||
size.y = height;
|
||||
rectTransform.sizeDelta = size;
|
||||
}
|
||||
|
||||
private TagItem ResolveTagTemplate()
|
||||
{
|
||||
if (_tagItemTemplate != null)
|
||||
{
|
||||
return _tagItemTemplate;
|
||||
}
|
||||
|
||||
if (_tagItemParent == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_tagItemTemplate = _tagItemParent.GetComponentInChildren<TagItem>(true);
|
||||
if (_tagItemTemplate != null)
|
||||
{
|
||||
_tagItemTemplate.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
return _tagItemTemplate;
|
||||
}
|
||||
|
||||
private void ClearTags()
|
||||
{
|
||||
for (int i = _runtimeTagItems.Count - 1; i >= 0; i--)
|
||||
{
|
||||
TagItem tagItem = _runtimeTagItems[i];
|
||||
if (tagItem != null)
|
||||
{
|
||||
Destroy(tagItem.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
_runtimeTagItems.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user