Merge remote-tracking branch 'origin/savya' into work_branch

This commit is contained in:
Mausham
2026-05-29 17:00:27 +05:45
10 changed files with 239 additions and 46 deletions

View File

@@ -328,7 +328,7 @@ namespace Darkmatter.Features.DrawingTemplates.Editor
foreach (var m in markers)
{
if (m.Shape == null) { missing++; continue; }
if (!pieces.Contains(m.Shape)) pieces.Add(m.Shape);
pieces.Add(m.Shape);
}
Undo.RecordObject(t, "Scan Drawing Prefab");
t.EditorSet(t.Id, t.DisplayName, t.DefaultThumbnail, t.DrawingPrefab, t.ColoringPrefab,

View File

@@ -12,6 +12,7 @@ namespace Darkmatter.Features.ShapeBuilder.Commands
private readonly Quaternion _prevRot;
private readonly Transform _prevParent;
private readonly int _prevSiblingIndex;
private SlotMarker _snappedSlot;
public SnapPieceCommand(ShapePiece piece)
{
@@ -23,7 +24,12 @@ namespace Darkmatter.Features.ShapeBuilder.Commands
_prevRot = Quaternion.identity;
}
public void Execute() => _piece.SnapInternal();
public void Execute()
{
if (_snappedSlot != null) _piece.ReassignActiveSlot(_snappedSlot);
_piece.SnapInternal();
if (_snappedSlot == null) _snappedSlot = _piece.ActiveSlot;
}
public void Undo() => _piece.UnsnapInternal(_prevParent, _prevSiblingIndex, _prevPos, _prevSize, _prevRot);
}

View File

@@ -6,6 +6,6 @@ namespace Darkmatter.Features.ShapeBuilder.Systems
{
public interface IShapePieceFactory
{
ShapePiece Create(GameObject prefab, ShapeSO shape, SlotMarker slot, Vector2 trayPos);
ShapePiece Create(GameObject prefab, ShapeSO shape, SlotMarker[] candidateSlots, Vector2 trayPos);
}
}

View File

@@ -117,28 +117,40 @@ namespace Darkmatter.Features.ShapeBuilder.Systems
int count,
SlotMarker[] slots, float pitch, float trayW)
{
var preSnapCounts = new Dictionary<string, int>();
if (preSnappedIds != null)
foreach (var id in preSnappedIds)
{
if (string.IsNullOrEmpty(id)) continue;
preSnapCounts[id] = preSnapCounts.GetValueOrDefault(id) + 1;
}
for (int i = 0; i < count; i++)
{
var shape = template.Pieces[i];
var slot = FindSlotForShape(slots, shape);
if (slot == null)
var candidates = FindSlotsForShape(slots, shape);
if (candidates.Length == 0)
{
Debug.LogError($"[ShapeBuilder] No SlotMarker for '{shape.Id}' in '{template.Id}'");
continue;
}
var trayPos = new Vector2(pitch * (i + 1) - trayW * 0.5f, 0f);
bool preSnapped = preSnappedIds != null && preSnappedIds.Contains(shape.Id);
var piece = _factory.Create(_piecePrefab, shape, slot, trayPos);
var piece = _factory.Create(_piecePrefab, shape, candidates, trayPos);
_pieces.Add(piece);
if (preSnapped)
if (preSnapCounts.TryGetValue(shape.Id, out var remaining) && remaining > 0)
{
var unoccupied = FindFirstUnoccupied(candidates);
if (unoccupied != null)
{
_undo.Append(new SnapPieceCommand(piece));
piece.SnapInstantly();
piece.SnapInstantlyTo(unoccupied);
unoccupied.SetOccupied(true);
_snapped++;
_snappedPieceIds.Add(shape.Id);
preSnapCounts[shape.Id] = remaining - 1;
}
}
}
}
@@ -160,10 +172,19 @@ namespace Darkmatter.Features.ShapeBuilder.Systems
}
}
private static SlotMarker FindSlotForShape(SlotMarker[] slots, ShapeSO shape)
private static SlotMarker[] FindSlotsForShape(SlotMarker[] slots, ShapeSO shape)
{
var list = new List<SlotMarker>();
foreach (var s in slots)
if (s != null && s.Shape == shape)
list.Add(s);
return list.ToArray();
}
private static SlotMarker FindFirstUnoccupied(SlotMarker[] slots)
{
foreach (var s in slots)
if (s.Shape == shape)
if (s != null && !s.IsOccupied)
return s;
return null;
}

View File

@@ -33,13 +33,13 @@ namespace Darkmatter.Features.ShapeBuilder.Systems
_refs = refs;
}
public ShapePiece Create(GameObject prefab, ShapeSO shape, SlotMarker slot, Vector2 trayPos)
public ShapePiece Create(GameObject prefab, ShapeSO shape, SlotMarker[] candidateSlots, Vector2 trayPos)
{
var go = Object.Instantiate(prefab, _holder.SpawnRoot);
go.name = $"Piece_{shape.Id}";
var piece = go.GetComponent<ShapePiece>();
piece.Setup(shape, slot, _cfg, _sfx, _bus, _undo, trayPos, _refs.PaperRoot);
piece.Setup(shape, candidateSlots, _cfg, _sfx, _bus, _undo, trayPos, _refs.PaperRoot);
return piece;
}
}

View File

@@ -20,7 +20,8 @@ namespace Darkmatter.Features.ShapeBuilder.UI
// Bound by Setup
private ShapeSO _shape;
private SlotMarker _slot;
private SlotMarker[] _candidateSlots;
private SlotMarker _activeSlot;
private ShapeBuilderConfig _cfg;
private ISfxPlayer _sfx;
private IEventBus _bus;
@@ -30,6 +31,9 @@ namespace Darkmatter.Features.ShapeBuilder.UI
private RectTransform _dragRoot;
private Transform _homeParent;
private int _homeSiblingIndex;
private Vector2 _origAnchorMin;
private Vector2 _origAnchorMax;
private Vector2 _origPivot;
// Per-drag state
private RectTransform _rt;
@@ -51,10 +55,13 @@ namespace Darkmatter.Features.ShapeBuilder.UI
public int HomeSiblingIndex => _homeSiblingIndex;
public Vector2 TrayPosition => _trayPos;
public Vector2 TraySize => _traySize;
public SlotMarker ActiveSlot => _activeSlot;
public void ReassignActiveSlot(SlotMarker slot) => _activeSlot = slot;
public void Setup(
ShapeSO shape,
SlotMarker slot,
SlotMarker[] candidateSlots,
ShapeBuilderConfig cfg,
ISfxPlayer sfx,
IEventBus bus,
@@ -63,7 +70,8 @@ namespace Darkmatter.Features.ShapeBuilder.UI
RectTransform dragRoot)
{
_shape = shape;
_slot = slot;
_candidateSlots = candidateSlots;
_activeSlot = null;
_cfg = cfg;
_sfx = sfx;
_bus = bus;
@@ -74,6 +82,9 @@ namespace Darkmatter.Features.ShapeBuilder.UI
_homeParent = RectTransform.parent;
_homeSiblingIndex = RectTransform.GetSiblingIndex();
_origAnchorMin = RectTransform.anchorMin;
_origAnchorMax = RectTransform.anchorMax;
_origPivot = RectTransform.pivot;
image.sprite = shape.Sprite;
ApplyTrayPose();
@@ -103,17 +114,25 @@ namespace Darkmatter.Features.ShapeBuilder.UI
if (_locked) return;
var pointerLocal = ScreenToLocal(e.position) + _grabOffset;
bool insidePreview = IsOverSlot(e.position);
var hovered = FindSlotUnder(e.position);
bool insidePreview = hovered != null;
if (insidePreview && !_inPreview)
{
_activeSlot = hovered;
_inPreview = true;
_sfx.Play(SfxId.ShapeHover);
AnimatePreviewPose(toSlot: true);
}
else if (insidePreview && _inPreview && hovered != _activeSlot)
{
_activeSlot = hovered;
AnimatePreviewPose(toSlot: true);
}
else if (!insidePreview && _inPreview)
{
_inPreview = false;
_activeSlot = null;
AnimatePreviewPose(toSlot: false);
}
@@ -125,25 +144,39 @@ namespace Darkmatter.Features.ShapeBuilder.UI
{
if (_locked) return;
if (IsOverSlot(e.position))
var target = FindSlotUnder(e.position);
if (target != null)
{
_activeSlot = target;
_undo.Push(new SnapPieceCommand(this));
}
else
{
_activeSlot = null;
ReturnToTray();
}
}
private bool IsOverSlot(Vector2 screenPos)
private SlotMarker FindSlotUnder(Vector2 screenPos)
{
return RectTransformUtility.RectangleContainsScreenPoint(
_slot.RectTransform, screenPos, _eventCam);
if (_candidateSlots == null) return null;
foreach (var s in _candidateSlots)
{
if (s == null) continue;
if (s.IsOccupied && s != _activeSlot) continue;
if (RectTransformUtility.RectangleContainsScreenPoint(s.RectTransform, screenPos, _eventCam))
return s;
}
return null;
}
private void AnimatePreviewPose(bool toSlot)
{
if (_previewSeq.isAlive) _previewSeq.Stop();
if (toSlot)
if (toSlot && _activeSlot != null)
{
var slot = _slot.RectTransform;
var slot = _activeSlot.RectTransform;
_previewSeq = Sequence.Create()
.Group(Tween.UIAnchoredPosition(RectTransform, SlotPosInDragSpace(), _cfg.SnapDuration, Ease.OutQuad))
.Group(Tween.LocalScale(RectTransform, SlotScaleInDragSpace(), _cfg.SnapDuration, Ease.OutQuad))
@@ -161,7 +194,7 @@ namespace Darkmatter.Features.ShapeBuilder.UI
private Vector2 SlotPosInDragSpace()
{
Vector3 worldPos = _slot.RectTransform.position;
Vector3 worldPos = _activeSlot.RectTransform.position;
Vector3 local = _parentRect.InverseTransformPoint(worldPos);
Vector2 parentSize = _parentRect.rect.size;
Vector2 anchorRef = (RectTransform.anchorMin - _parentRect.pivot) * parentSize;
@@ -177,13 +210,13 @@ namespace Darkmatter.Features.ShapeBuilder.UI
private Quaternion SlotRotInDragSpace()
{
return Quaternion.Inverse(_parentRect.rotation) * _slot.RectTransform.rotation;
return Quaternion.Inverse(_parentRect.rotation) * _activeSlot.RectTransform.rotation;
}
private Vector3 SlotScaleInDragSpace()
{
Vector3 parentLossy = _parentRect.lossyScale;
Vector3 slotLossy = _slot.RectTransform.lossyScale;
Vector3 slotLossy = _activeSlot.RectTransform.lossyScale;
return new Vector3(
slotLossy.x / Mathf.Max(0.0001f, parentLossy.x),
slotLossy.y / Mathf.Max(0.0001f, parentLossy.y),
@@ -194,27 +227,49 @@ namespace Darkmatter.Features.ShapeBuilder.UI
{
StopPreviewTweens();
Lock();
var slot = _slot.RectTransform;
var slot = _activeSlot.RectTransform;
Tween.Position(RectTransform, slot.position, _cfg.SnapDuration, Ease.OutBack);
Tween.Rotation(RectTransform, slot.rotation, _cfg.SnapDuration, Ease.OutBack);
Tween.LocalScale(RectTransform, slot.localScale, _cfg.SnapDuration, Ease.OutBack);
Tween.UISizeDelta(RectTransform, slot.sizeDelta, _cfg.SnapDuration, Ease.OutBack);
Sequence.Create()
.Group(Tween.Position(RectTransform, slot.position, _cfg.SnapDuration, Ease.OutBack))
.Group(Tween.Rotation(RectTransform, slot.rotation, _cfg.SnapDuration, Ease.OutBack))
.Group(Tween.LocalScale(RectTransform, slot.localScale, _cfg.SnapDuration, Ease.OutBack))
.Group(Tween.UISizeDelta(RectTransform, slot.sizeDelta, _cfg.SnapDuration, Ease.OutBack))
.ChainCallback(AlignRectToSlot);
_sfx.Play(SfxId.ShapeSnap);
_bus.Publish(new PieceSnappedSignal(_shape.Id));
}
private void AlignRectToSlot()
{
if (this == null || _activeSlot == null) return;
var rt = RectTransform;
var slot = _activeSlot.RectTransform;
rt.anchorMin = Vector2.zero;
rt.anchorMax = Vector2.one;
rt.pivot = slot.pivot;
rt.anchoredPosition = Vector2.zero;
rt.sizeDelta = Vector2.zero;
rt.localRotation = Quaternion.identity;
rt.localScale = Vector3.one;
}
internal void UnsnapInternal(Transform parent, int siblingIndex, Vector2 pos, Vector2 size, Quaternion rot)
{
_locked = false;
image.raycastTarget = true;
if (_activeSlot != null) _activeSlot.SetOccupied(false);
_activeSlot = null;
Tween.StopAll(onTarget: RectTransform);
RectTransform.SetParent(parent, worldPositionStays: false);
if (siblingIndex >= 0) RectTransform.SetSiblingIndex(siblingIndex);
RectTransform.anchorMin = _origAnchorMin;
RectTransform.anchorMax = _origAnchorMax;
RectTransform.pivot = _origPivot;
RectTransform.anchoredPosition = pos;
RectTransform.sizeDelta = size;
RectTransform.localRotation = rot;
@@ -224,14 +279,11 @@ namespace Darkmatter.Features.ShapeBuilder.UI
_bus.Publish(new PieceUnsnappedSignal(_shape.Id));
}
public void SnapInstantly()
public void SnapInstantlyTo(SlotMarker slot)
{
_activeSlot = slot;
Lock();
var slot = _slot.RectTransform;
RectTransform.position = slot.position;
RectTransform.rotation = slot.rotation;
RectTransform.localScale = slot.localScale;
RectTransform.sizeDelta = slot.sizeDelta;
AlignRectToSlot();
}
private void ReturnToTray()
@@ -258,7 +310,11 @@ namespace Darkmatter.Features.ShapeBuilder.UI
{
_locked = true;
image.raycastTarget = false;
RectTransform.SetParent(_slot.RectTransform.parent, worldPositionStays: true);
if (_activeSlot != null)
{
_activeSlot.SetOccupied(true);
RectTransform.SetParent(_activeSlot.RectTransform, worldPositionStays: true);
}
}
private void ApplyTrayPose()

View File

@@ -12,6 +12,9 @@ namespace Darkmatter.Features.ShapeBuilder.UI
public ShapeSO Shape => shape;
public string SlotId => shape != null ? shape.Id : null;
public RectTransform RectTransform => (RectTransform)transform;
public bool IsOccupied { get; private set; }
public void SetOccupied(bool value) => IsOccupied = value;
public void SetOutlineVisible(bool visible)
{

View File

@@ -0,0 +1,92 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3388453239853753565
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7141417102443485026}
- component: {fileID: 1022797824599580644}
- component: {fileID: 7793443009330116033}
- component: {fileID: 2829602331308694695}
m_Layer: 5
m_Name: Square
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &7141417102443485026
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3388453239853753565}
m_LocalRotation: {x: 0, y: 0, z: 0.7071068, w: 0.7071068}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0.4978243, y: 0.4978243, z: 0.4978243}
m_ConstrainProportionsScale: 1
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 90}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 170, y: 449}
m_SizeDelta: {x: 375, y: 386}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &1022797824599580644
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3388453239853753565}
m_CullTransparentMesh: 1
--- !u!114 &7793443009330116033
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3388453239853753565}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
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: 21300000, guid: c3293fddd457324499e794c843736653, type: 3}
m_Type: 0
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 &2829602331308694695
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3388453239853753565}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5d79b18d536324085b58d842648372a8, type: 3}
m_Name:
m_EditorClassIdentifier: Features.ShapeBuilder::Darkmatter.Features.ShapeBuilder.UI.SlotMarker
shape: {fileID: 11400000, guid: 4a67406eb6fe043628d2b6a4e0c970ba, type: 2}
outline: {fileID: 0}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4d7801ac4730f46b6ba82c8025ebd177
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1935,6 +1935,14 @@ PrefabInstance:
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 198479350452278120, guid: 0d57e66d7df915b4aabb02005dd9f8b1, type: 3}
propertyPath: playOnAwake
value: 0
objectReference: {fileID: 0}
- target: {fileID: 198799323246219528, guid: 0d57e66d7df915b4aabb02005dd9f8b1, type: 3}
propertyPath: playOnAwake
value: 0
objectReference: {fileID: 0}
- target: {fileID: 199137177167099770, guid: 0d57e66d7df915b4aabb02005dd9f8b1, type: 3}
propertyPath: m_Enabled
value: 0
@@ -3690,8 +3698,8 @@ MonoBehaviour:
m_Calls: []
m_text: Artbook
m_isRightToLeft: 0
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
m_fontAsset: {fileID: 11400000, guid: f282e17aebd2cf547bdab7be22e5c474, type: 2}
m_sharedMaterial: {fileID: 6332886355625335628, guid: f282e17aebd2cf547bdab7be22e5c474, type: 2}
m_fontSharedMaterials: []
m_fontMaterial: {fileID: 0}
m_fontMaterials: []