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