S5 finish

This commit is contained in:
SepComet 2026-03-11 17:24:15 +08:00
parent 9c5871b518
commit 8c8f786013
11 changed files with 558 additions and 48 deletions

View File

@ -12,20 +12,20 @@ namespace GeometryTD.CustomEvent
public long ItemId { get; private set; }
public Vector3 TargetPos { get; private set; }
public Vector2 ScreenPosition { get; private set; }
public static RepoItemClickedEventArgs Create(long itemId, Vector3 targetPos)
public static RepoItemClickedEventArgs Create(long itemId, Vector2 screenPosition)
{
RepoItemClickedEventArgs args = ReferencePool.Acquire<RepoItemClickedEventArgs>();
args.ItemId = itemId;
args.TargetPos = targetPos;
args.ScreenPosition = screenPosition;
return args;
}
public override void Clear()
{
ItemId = 0;
TargetPos = Vector3.zero;
ScreenPosition = Vector2.zero;
}
}
}

View File

@ -390,7 +390,7 @@ namespace GeometryTD.UI
TypeText = seed.TypeText,
Description = seed.Description ?? string.Empty,
Price = 0,
TargetPos = args.TargetPos,
ScreenPosition = args.ScreenPosition,
Tags = CloneTags(seed.Tags),
TagRuntimes = CloneTagRuntimes(seed.TagRuntimes)
});

View File

@ -152,7 +152,7 @@ namespace GeometryTD.UI
TypeText = seed.TypeText,
Description = seed.Description ?? string.Empty,
Price = 0,
TargetPos = args.TargetPos,
ScreenPosition = args.ScreenPosition,
Tags = CloneTags(seed.Tags),
TagRuntimes = CloneTagRuntimes(seed.TagRuntimes)
});

View File

@ -120,7 +120,7 @@ namespace GeometryTD.UI
return;
}
GameEntry.Event.Fire(this, RepoItemClickedEventArgs.Create(_context.InstanceId, transform.position));
GameEntry.Event.Fire(this, RepoItemClickedEventArgs.Create(_context.InstanceId, ResolveScreenPosition()));
}
public void OnBeginDrag(PointerEventData eventData)
@ -239,6 +239,25 @@ namespace GeometryTD.UI
}
}
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>();
@ -348,4 +367,3 @@ namespace GeometryTD.UI
}
}
}

View File

@ -8,7 +8,7 @@ namespace GeometryTD.UI
public string TypeText;
public string Description;
public int Price;
public Vector3 TargetPos;
public Vector2 ScreenPosition;
public TagItemContext[] Tags;
}
}

View File

@ -34,7 +34,7 @@ namespace GeometryTD.UI
TypeText = rawData.TypeText,
Description = BuildDescription(rawData),
Price = rawData.Price,
TargetPos = rawData.TargetPos,
ScreenPosition = rawData.ScreenPosition,
Tags = BuildTags(rawData)
};
}

View File

@ -9,7 +9,7 @@ namespace GeometryTD.UI
public string TypeText;
public string Description;
public int Price;
public Vector3 TargetPos;
public Vector2 ScreenPosition;
public TagType[] Tags;
public TagRuntimeData[] TagRuntimes;
}

View File

@ -14,6 +14,8 @@ namespace GeometryTD.UI
[SerializeField] private TMP_Text _itemTypeText;
[SerializeField] private RectTransform _descScrollView;
[SerializeField] private TMP_Text _itemDescription;
[SerializeField] private TMP_Text _itemPrice;
@ -30,6 +32,8 @@ namespace GeometryTD.UI
[SerializeField] private float _fixedWidth = 300f;
[SerializeField] private float _maxDescriptionViewportHeight = 0f;
[SerializeField] private float _screenEdgePadding = 0f;
[SerializeField] private float _sideGap = 16f;
@ -45,7 +49,7 @@ namespace GeometryTD.UI
private readonly List<GameObject> _runtimeTagItems = new List<GameObject>();
private ItemDescFormContext _context;
private Vector3 _targetPos;
private Vector2 _targetPos;
private bool _isOpened;
public void RefreshUI(ItemDescFormContext context)
@ -58,7 +62,7 @@ namespace GeometryTD.UI
_context = context;
_targetPos = ConvertWorldToAnchored(_context.TargetPos);
_targetPos = ConvertScreenToAnchored(_context.ScreenPosition);
if (_content != null)
{
@ -146,16 +150,62 @@ namespace GeometryTD.UI
}
_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;
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)
@ -207,20 +257,20 @@ namespace GeometryTD.UI
finalX = minX <= maxX ? Mathf.Clamp(leftCandidateX, minX, maxX) : 0f;
}
_content.anchoredPosition3D = new Vector3(finalX, clampedY, _targetPos.z);
_content.anchoredPosition = new Vector2(finalX, clampedY);
}
private Vector3 ConvertWorldToAnchored(Vector3 worldPos)
private Vector2 ConvertScreenToAnchored(Vector2 screenPosition)
{
if (_content == null)
{
return worldPos;
return screenPosition;
}
RectTransform parent = _content.parent as RectTransform;
if (parent == null)
{
return worldPos;
return screenPosition;
}
Canvas canvas = _content.GetComponentInParent<Canvas>();
@ -231,15 +281,13 @@ namespace GeometryTD.UI
uiCamera = rootCanvas.worldCamera != null ? rootCanvas.worldCamera : Camera.main;
}
Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(uiCamera, worldPos);
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(parent, screenPoint, uiCamera,
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(parent, screenPosition, uiCamera,
out Vector2 localPoint))
{
return worldPos;
return screenPosition;
}
float z = _content.anchoredPosition3D.z;
return new Vector3(localPoint.x, localPoint.y, z);
return localPoint;
}
private static bool IsInsideBounds(float value, float min, float max)

View File

@ -29,8 +29,8 @@ RectTransform:
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 7543853000780573685}
- {fileID: 1164222149922529831}
- {fileID: 7543853000780573685}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
@ -53,6 +53,7 @@ MonoBehaviour:
_content: {fileID: 7543853000780573685}
_itemTitle: {fileID: 103163210424128144}
_itemTypeText: {fileID: 7330401870143477410}
_descScrollView: {fileID: 5256025271502919839}
_itemDescription: {fileID: 6102478747521363808}
_itemPrice: {fileID: 3372850760549036539}
_tagAreaParent: {fileID: 5974414274741800357}
@ -62,6 +63,7 @@ MonoBehaviour:
_fixedTopHeight: 300
_fixedBottomHeight: 200
_fixedWidth: 800
_maxDescriptionViewportHeight: 1000
_screenEdgePadding: 0
_sideGap: 16
_anchorItemWidth: 0
@ -163,9 +165,9 @@ RectTransform:
m_Children:
- {fileID: 2312726378312491185}
- {fileID: 2131471118485666816}
- {fileID: 8691033862686863159}
- {fileID: 4626694267165989230}
- {fileID: 5974414274741800357}
- {fileID: 5256025271502919839}
m_Father: {fileID: 6368625289863409235}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
@ -221,8 +223,83 @@ CanvasGroup:
m_Enabled: 1
m_Alpha: 1
m_Interactable: 0
m_BlocksRaycasts: 0
m_BlocksRaycasts: 1
m_IgnoreParentGroups: 0
--- !u!1 &2412012916441856513
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7881731673096662725}
- component: {fileID: 7298444941623630875}
- component: {fileID: 1112425355264495322}
m_Layer: 5
m_Name: Handle
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &7881731673096662725
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2412012916441856513}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 4517295342785105071}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 20, y: 20}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7298444941623630875
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2412012916441856513}
m_CullTransparentMesh: 1
--- !u!114 &1112425355264495322
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2412012916441856513}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &2619564942114189097
GameObject:
m_ObjectHideFlags: 0
@ -523,11 +600,11 @@ RectTransform:
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 7543853000780573685}
m_Father: {fileID: 551487876586793062}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 1}
m_AnchorMax: {x: 0.5, y: 1}
m_AnchoredPosition: {x: 0.00000036508, y: -250}
m_AnchoredPosition: {x: 8.5, y: 0}
m_SizeDelta: {x: 740, y: 500}
m_Pivot: {x: 0.5, y: 1}
--- !u!222 &4798810177026917072
@ -552,7 +629,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
@ -628,6 +705,371 @@ MonoBehaviour:
m_hasFontAssetChanged: 0
m_baseMaterial: {fileID: 0}
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
--- !u!1 &5204679832914275170
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4517295342785105071}
m_Layer: 5
m_Name: Sliding Area
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4517295342785105071
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5204679832914275170}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 7881731673096662725}
m_Father: {fileID: 3420065201910878183}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: -20, y: -20}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &6212260387770401143
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 551487876586793062}
m_Layer: 5
m_Name: Content
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &551487876586793062
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6212260387770401143}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 8691033862686863159}
m_Father: {fileID: 5512843442966546476}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0.00007219115}
m_SizeDelta: {x: 0, y: 300}
m_Pivot: {x: 0, y: 1}
--- !u!1 &6492817632056103165
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3420065201910878183}
- component: {fileID: 5652751692287784381}
- component: {fileID: 8483010013983203406}
- component: {fileID: 8645065629472015101}
m_Layer: 5
m_Name: Scrollbar Vertical
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3420065201910878183
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6492817632056103165}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4517295342785105071}
m_Father: {fileID: 5256025271502919839}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 1, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 20, y: -17}
m_Pivot: {x: 1, y: 1}
--- !u!222 &5652751692287784381
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6492817632056103165}
m_CullTransparentMesh: 1
--- !u!114 &8483010013983203406
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6492817632056103165}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &8645065629472015101
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6492817632056103165}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 2a4db7a114972834c8e4117be1d82ba3, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 1112425355264495322}
m_HandleRect: {fileID: 7881731673096662725}
m_Direction: 2
m_Value: 0
m_Size: 1
m_NumberOfSteps: 0
m_OnValueChanged:
m_PersistentCalls:
m_Calls: []
--- !u!1 &7502327435573383977
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5512843442966546476}
- component: {fileID: 3800436505147399416}
- component: {fileID: 7535246470184249899}
- component: {fileID: 7266648198088563062}
m_Layer: 5
m_Name: Viewport
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &5512843442966546476
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7502327435573383977}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 551487876586793062}
m_Father: {fileID: 5256025271502919839}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 1}
--- !u!222 &3800436505147399416
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7502327435573383977}
m_CullTransparentMesh: 1
--- !u!114 &7535246470184249899
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7502327435573383977}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10917, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &7266648198088563062
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7502327435573383977}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 31a19414c41e5ae4aae2af33fee712f6, type: 3}
m_Name:
m_EditorClassIdentifier:
m_ShowMaskGraphic: 0
--- !u!1 &7827340776580214163
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5256025271502919839}
- component: {fileID: 7512403649071092031}
- component: {fileID: 7359473554909842726}
m_Layer: 5
m_Name: Scroll View
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &5256025271502919839
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7827340776580214163}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 5512843442966546476}
- {fileID: 3420065201910878183}
m_Father: {fileID: 7543853000780573685}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 740, y: 500}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &7512403649071092031
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7827340776580214163}
m_CullTransparentMesh: 1
--- !u!114 &7359473554909842726
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7827340776580214163}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1aa08ab6e0800fa44ae55d278d1423e3, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Content: {fileID: 551487876586793062}
m_Horizontal: 0
m_Vertical: 1
m_MovementType: 2
m_Elasticity: 0.1
m_Inertia: 0
m_DecelerationRate: 0.135
m_ScrollSensitivity: 1
m_Viewport: {fileID: 5512843442966546476}
m_HorizontalScrollbar: {fileID: 0}
m_VerticalScrollbar: {fileID: 8645065629472015101}
m_HorizontalScrollbarVisibility: 2
m_VerticalScrollbarVisibility: 2
m_HorizontalScrollbarSpacing: -3
m_VerticalScrollbarSpacing: -3
m_OnValueChanged:
m_PersistentCalls:
m_Calls: []
--- !u!1 &8191619470729415355
GameObject:
m_ObjectHideFlags: 0

View File

@ -215,16 +215,17 @@
| 状态 | ID | 任务 | 交付物路径 | 验收标准 |
|-----|-------|-----------------------|-------------------------------------------------------------------------------------------------|----------------|
| [ ] | S5-01 | 先确认 M1 是否保留完整耐久闭环 | `docs/CodeX-TODO.md`<br>`docs/TODO.md` | 范围先明确,避免边做边改目标 |
| [ ] | S5-02 | 若保留,定义耐久对属性/出战资格的影响方式 | `Assets/GameMain/Scripts/Definition/`<br>`Assets/GameMain/Scripts/Entity/` | 规则口径清晰且可测试 |
| [ ] | S5-03 | 实现耐久扣减后的实际生效 | `Assets/GameMain/Scripts/CustomComponent/PlayerInventory/`<br>`Assets/GameMain/Scripts/Entity/` | 耐久不再只是展示字段 |
| [ ] | S5-04 | 实现 `0` 耐久销毁或失效闭环 | `Assets/GameMain/Scripts/CustomComponent/PlayerInventory/`<br>`Assets/GameMain/Scripts/UI/` | 归零后行为符合最终口径 |
| [x] | S5-01 | 先确认 M1 是否保留完整耐久闭环 | `docs/CodeX-TODO.md`<br>`docs/TODO.md` | 已明确按“最小耐久闭环”推进,不扩展维修/折价/自动销毁 |
| [x] | S5-02 | 若保留,定义耐久对属性/出战资格的影响方式 | `Assets/GameMain/Scripts/Definition/`<br>`Assets/GameMain/Scripts/Entity/` | 已统一为“归零失效、不可参战、非归零不衰减” |
| [x] | S5-03 | 实现耐久扣减后的实际生效 | `Assets/GameMain/Scripts/CustomComponent/PlayerInventory/`<br>`Assets/GameMain/Scripts/Entity/` | 已按“每场固定扣 1、仅扣本场参战塔”接进主链 |
| [x] | S5-04 | 实现 `0` 耐久失效闭环 | `Assets/GameMain/Scripts/CustomComponent/PlayerInventory/`<br>`Assets/GameMain/Scripts/UI/` | 已实现失效不可参战、仓库提示、节点结束后自动踢出参战区 |
### S5 规划结论
- `S5` 当前按“最小耐久闭环”推进,不展开维修、折价、复杂恢复、耐久与 Tag 联动等后续深度系统。
- M1 保留耐久规则,但当前目标只收口到“战斗后真实扣减 + 归零后真实失效 + UI 可解释”,不要求一并实现完整维修玩法。
- 本阶段优先保证耐久从展示字段变成实际规则入口,而不是继续增加额外经济系统或复杂数值衰减。
- `S5-01 ~ S5-04` 当前代码口径已完成:战斗后会真实扣减本场参战塔耐久,`0` 耐久会拦截参战与战斗入口,仓库详情会显示损坏状态,节点结束后会自动把损坏塔移出参战区并弹提示。
### S5-01 范围结论
@ -246,6 +247,7 @@
- 扣减结果必须真实回写到库存快照 / 当前 Run 库存,而不是只改战斗内临时对象。
- 参战合法性统一复用现有校验入口扩展,不额外再造一套“耐久专用判断链”。
- 战斗入口仍需要保留最终二次校验,避免旧快照或外部改动绕过组装区 / 参战区限制。
- 当前仓库实现已经按此口径落地:不再沿用旧的“低血量才扣耐久、且扣全库存塔”的逻辑。
### S5-04 推荐闭环口径
@ -253,13 +255,13 @@
- 装配了 `0` 耐久组件的塔在 UI 上需要给出明确提示,例如“组件已损坏”或“耐久为 0无法参战”。
- 若塔已在参战区且组件在上一场战斗后归零,需要在后续刷新中把该塔视为非法参战塔,并通过统一校验入口阻止再次进入战斗。
- “自动销毁组件”与“自动拆塔”保留到后续阶段再决定,当前不作为 `S5` 通过标准。
- 当前仓库实现已经进一步收口:节点结束时会自动检查损坏参战塔,将其移出 `ParticipantTowerInstanceIds`,并通过 `DialogForm` 明确提示玩家。
- Boss 完成导致 Run 正式结束时仍会执行损坏塔清理但当前不会额外插入第二个耐久提示弹窗去覆盖“Run 完成”弹窗。
### S5 推荐执行顺序
### S5 当前结论
1. 先完成 `S5-01`,把 M1 的耐久范围冻结为“最小闭环”,文档先对齐。
2. 再完成 `S5-02`,把“归零失效、非归零不衰减、不可参战”的规则写成统一口径。
3. 然后做 `S5-03`,把战斗结算后的耐久扣减与库存回写接进主链路。
4. 最后做 `S5-04`补齐参战拦截、UI 提示与 `0` 耐久失效闭环。
1. `S5-01 ~ S5-04` 已按当前 M1 口径完成,不再作为 `P0-12` 的主阻塞项。
2. 当前未实现的是长期设计里的维修、自动销毁、耐久折价、连续属性衰减,这些后续若要恢复,应作为新的增强项重新拆分,而不是回滚当前 MVP 口径。
## 阶段 S6 - 回归与文档收尾
@ -276,15 +278,15 @@
2. `S2` 已完成口径对齐,`NodeMapForm` 不再作为 M1 阻塞项。
3. 接下来优先做 `S3`,补齐出战合法性,解决 `P0-10`
4. 再做 `S4`,统一品质 / Tag 的规则口径,解决 `P0-11`。其中当前优先级已经收口为:`S4-06` 已完成,下一步转入 `S4-07` 的数据表映射。
5. 最后做 `S5`,按“最小耐久闭环”收口真实扣减、归零失效与参战拦截,解决 `P0-12`
6. 全部完成后做 `S6`,补测试并同步文档状态
5. `S5` 已按当前 M1 口径完成,后续重点转入 `S6` 的测试补强与文档收尾
6. `S6` 收尾后,再决定是否把维修、自动销毁、耐久折价等长期设计拆成新的增强阶段
## 本周建议开工顺序
1. `S1``S2` 已完成口径收口,可直接进入规则侧收尾
2. `S3-01 ~ S3-04` 已完成,当前可转入 `S4`
3. 当前先完成 `S4-07` 的三表口径收口,并把 `Tag.txt` / `TagConfig.txt` / `RarityTagBudget.txt` 的实际消费链稳定下来
4. 然后转入 `S5-01 ~ S5-04`,按“最小耐久闭环”推进耐久真实生效
4. `S5-01 ~ S5-04` 已按“最小耐久闭环”落地,当前无需再按旧 TODO 继续拆这部分
5. 最后补 `S6-01 ~ S6-04`
## 备注

View File

@ -15,7 +15,7 @@
- `NodeMapForm` 已满足 MVP 所需的节点流程界面M1 现在的真实缺口是合法出战 / 品质 / Tag / 耐久规则是否真正统一收口。
- `P0-10` 的“三组件完整合法参战”主链已落地:当前参战分配、战斗入口最终校验、失败原因与拦截提示都已接入统一合法性判断入口,但文档仍保留 `[~]`,直到与 `docs/CodeX-TODO.md` 的验收口径完全同步。
- `P0-11` 已不再只是“局部展示字段”当前品质计算、Tag 生成、塔级 Tag 汇总、首发 7 个 Tag 的战斗效果、以及 `Tag.txt + RarityTagBudget.txt + TagConfig.txt` 三表驱动链路都已存在;剩余缺口主要是 `S4-07` 的最终文档口径与少量配置收口,而不是主功能缺失。
- `P0-12` 仍是当前 M1 最明确的规则缺口:已有耐久字段、展示与部分扣减入口,但还没有形成“战斗后真实扣减 -> 归零失效 -> 参战拦截”的最小闭环
- `P0-12` 的“最小耐久闭环”主链已落地:当前已实现战斗后按参战塔真实扣减耐久、`0` 耐久失效并拦截参战/战斗入口、仓库详情损坏提示,以及节点结束后自动将损坏塔移出参战区并弹窗说明
## 里程碑 M1P0- 最小可玩闭环
@ -32,7 +32,7 @@
| [x] | P0-09 | 敌人掉落与关卡奖励(组件/配件/金币) | `Assets/GameMain/Scripts/Entity/` | 战斗结束能发放掉落并写入库存 |
| [~] | P0-10 | 节点后组装:枪口/轴承/底座三组件约束 | `Assets/GameMain/Scripts/Entity/`<br>`Assets/GameMain/Scripts/UI/Templates/GameScene/` | 当前已形成“三组件完整合法参战”的统一校验链与战斗入口拦截;剩余工作主要是同步文档口径与收尾验收 |
| [~] | P0-11 | 品质 / Tag 规则统一入口(白绿蓝紫红) | `Assets/GameMain/Scripts/Definition/`<br>`Assets/GameMain/Scripts/Entity/` | 当前已完成品质统一、Tag 生成/汇总/展示与首发 7 个 Tag 的战斗生效;剩余工作主要是三表方案的最终收口与文档同步 |
| [~] | P0-12 | 组件/配件耐久生效与 0 耐久销毁 | `Assets/GameMain/Scripts/Entity/` | 当前按“最小耐久闭环”推进:目标是形成战斗后真实扣减、`0` 耐久失效与参战拦截;维修系统、自动销毁等后续深度规则暂不作为本阶段阻塞项 |
| [x] | P0-12 | 组件/配件耐久最小闭环 | `Assets/GameMain/Scripts/Procedure/`<br>`Assets/GameMain/Scripts/CustomComponent/PlayerInventory/`<br>`Assets/GameMain/Scripts/UI/` | 已实现战斗后真实扣减、`0` 耐久失效拦截、仓库损坏提示与节点结束后的自动移出参战区;自动销毁/维修系统保留到后续阶段 |
## 里程碑 M2P1- 核心深度
@ -64,8 +64,8 @@
## 本周建议开工顺序
1. 先完成 `P0-11` 的三表口径收口,把 `Tag.txt + RarityTagBudget.txt + TagConfig.txt` 的实际消费链与文档彻底对齐
2. 再推进 `P0-12`,按“最小耐久闭环”实现战斗后真实扣减、`0` 耐久失效与参战拦截
3. 最后补关键流程 / 规则回归测试,并同步 `docs/TODO.md``docs/CodeX-TODO.md` 的真实状态
2. 再`S6` 侧的主链路 / 规则回归测试,把当前 `S5` 代码口径固化下来
3. 最后同步其余执行文档与过期 TODO避免后续继续按旧的耐久/Tag 描述推进
## 设计优化 Backlog新增