using UnityEngine; using System.Collections.Generic; [RequireComponent(typeof(BoxCollider))] public class Shelf : MonoBehaviour { [Header("Grid Settings")] [Tooltip("How many items fit horizontally")] public int columns = 5; [Tooltip("How many items fit vertically/deep")] public int rows = 2; [Header("Snap Settings")] // Lift item slightly so it sits ON the shelf, not IN it public Vector3 offset = new Vector3(0, 0.1f, 0); // Track which object is in which slot. Index 0 = Slot 0. private GameObject[] slotOccupants; private Vector3[] localGridPositions; private BoxCollider shelfCollider; void Start() { shelfCollider = GetComponent(); // Ensure the collider is a trigger so items can pass through to be detected shelfCollider.isTrigger = true; CalculateGridPositions(); // Initialize occupancy array slotOccupants = new GameObject[columns * rows]; } [ContextMenu("Recalculate Grid")] void CalculateGridPositions() { if(shelfCollider == null) shelfCollider = GetComponent(); int totalSlots = columns * rows; localGridPositions = new Vector3[totalSlots]; // Get dimensions of the collider float width = shelfCollider.size.x; float depth = shelfCollider.size.z; // Assuming Y is Up, Z is depth // Calculate cell size float cellWidth = width / columns; float cellDepth = depth / rows; // Calculate starting point (Bottom-Left of the shelf surface) Vector3 startPos = shelfCollider.center - new Vector3(width / 2, -shelfCollider.size.y/2, depth / 2); startPos += new Vector3(cellWidth / 2, 0, cellDepth / 2); int index = 0; for (int r = 0; r < rows; r++) { for (int c = 0; c < columns; c++) { // Calculate local position Vector3 pos = startPos + new Vector3(c * cellWidth, 0, r * cellDepth); localGridPositions[index] = pos; index++; } } } void OnTriggerEnter(Collider other) { // 1. Check if it is an Item // 2. Check if we aren't ALREADY tracking it (prevents glitching) if (other.GetComponent() != null && !IsItemOnShelf(other.gameObject)) { TrySnapItem(other.gameObject); } } void OnTriggerExit(Collider other) { if (other.GetComponent() != null) { RemoveItem(other.gameObject); } } void TrySnapItem(GameObject itemObj) { int bestSlotIndex = -1; float closestDistance = float.MaxValue; // --- NEW LOGIC: FIND CLOSEST EMPTY SLOT --- // Instead of grabbing the first empty one (which causes teleporting), // we check distance to find the slot closest to where you are holding the item. for (int i = 0; i < localGridPositions.Length; i++) { // Only check EMPTY slots if (slotOccupants[i] == null) { // Convert local grid point to world space to compare distance Vector3 worldSlotPos = transform.TransformPoint(localGridPositions[i]); float dist = Vector3.Distance(itemObj.transform.position, worldSlotPos); // If this slot is closer than the last one we found, pick it if (dist < closestDistance) { closestDistance = dist; bestSlotIndex = i; } } } // If shelf is full, do nothing if (bestSlotIndex == -1) return; // --- SNAP LOGIC --- slotOccupants[bestSlotIndex] = itemObj; // --- PHYSICS FIX --- Rigidbody rb = itemObj.GetComponent(); if(rb != null) { // 1. Stop the object FIRST rb.linearVelocity = Vector3.zero; rb.angularVelocity = Vector3.zero; // 2. THEN disable physics interactions rb.isKinematic = true; } // Move item to the best slot found Vector3 targetPos = transform.TransformPoint(localGridPositions[bestSlotIndex]) + offset; itemObj.transform.position = targetPos; itemObj.transform.rotation = transform.rotation; } void RemoveItem(GameObject itemObj) { // Find where this item was and clear the slot for (int i = 0; i < slotOccupants.Length; i++) { if (slotOccupants[i] == itemObj) { slotOccupants[i] = null; // Re-enable physics when leaving Rigidbody rb = itemObj.GetComponent(); if (rb != null) rb.isKinematic = false; break; } } } // Helper to check if item is already physically tracked bool IsItemOnShelf(GameObject item) { for (int i = 0; i < slotOccupants.Length; i++) { if (slotOccupants[i] == item) return true; } return false; } // --- INVENTORY LOGIC MERGED --- // This replaces your old Dictionary. It counts the actual physical items. // This is safer because it can never get "out of sync" with what you see. public int GetTotalItemCount() { int count = 0; if (slotOccupants != null) { foreach (var item in slotOccupants) { if (item != null) count++; } } return count; } // DRAW THE GRID IN EDITOR SO WE CAN SEE IT void OnDrawGizmos() { if (shelfCollider == null) shelfCollider = GetComponent(); // Visualize the cached positions if game is running if (Application.isPlaying && localGridPositions != null) { Gizmos.color = Color.green; foreach (Vector3 localPos in localGridPositions) { Vector3 worldPos = transform.TransformPoint(localPos); Gizmos.DrawWireSphere(worldPos, 0.05f); Gizmos.DrawWireCube(worldPos + offset, new Vector3(0.1f, 0.1f, 0.1f)); } } else { // Fallback for Editor Mode (Logic repeated for visual aid before playing) Gizmos.color = Color.yellow; // ... Simple calc for editor preview only ... float w = shelfCollider.size.x; float d = shelfCollider.size.z; float cw = w / columns; float cd = d / rows; Vector3 start = shelfCollider.center - new Vector3(w/2, -shelfCollider.size.y/2, d/2) + new Vector3(cw/2, 0, cd/2); for(int r=0; r