using UnityEngine; /// /// MinimapObjectMarker - Creates minimap markers for cash bags and vaults. /// Uses different shapes than character markers for differentiation. /// CashBag = Square, Vaults = Diamond /// /// IMPORTANT: You MUST create a layer called "Minimap" in Unity Project Settings /// and configure your Main Camera to NOT render this layer. /// public class MinimapObjectMarker : MonoBehaviour { public enum ObjectType { CashBag, PlayerVault, EnemyVault } [Header("Marker Settings")] [SerializeField] private float heightOffset = 3f; [SerializeField] private float markerSize = 5f; [SerializeField] private ObjectType objectType = ObjectType.CashBag; [Header("Colors")] [SerializeField] private Color cashBagColor = new Color(1f, 0.85f, 0f, 1f); // Gold/Yellow [SerializeField] private Color playerVaultColor = new Color(0.2f, 0.8f, 0.2f, 1f); // Green [SerializeField] private Color enemyVaultColor = new Color(0.9f, 0.2f, 0.2f, 1f); // Red [Header("Layer Settings")] [SerializeField] private string minimapLayerName = "Minimap"; private GameObject markerObject; private MeshRenderer markerRenderer; private bool hasConfiguredLayer = false; private bool _markerCreated = false; private void Start() { AutoDetectType(); if (!_markerCreated) CreateMarker(); UpdateMarkerColor(); } private void OnEnable() { // Re-create marker if it was destroyed (e.g. pooled object returned and re-enabled) if (markerObject == null) { _markerCreated = false; AutoDetectType(); CreateMarker(); UpdateMarkerColor(); } else { markerObject.SetActive(true); } } private void OnDisable() { // Hide marker when the source object is disabled (pooled/despawned) if (markerObject != null) markerObject.SetActive(false); } private void AutoDetectType() { // Check if this is a CashBag if (GetComponent() != null) { objectType = ObjectType.CashBag; return; } // Check if this is a TeamVault TeamVault vault = GetComponent(); if (vault != null) { objectType = vault.TeamTag == "Player" ? ObjectType.PlayerVault : ObjectType.EnemyVault; } } private void LateUpdate() { if (markerObject == null) return; // Ensure layer is correctly set if (!hasConfiguredLayer) { ConfigureLayer(); } // Marker is parented, so local position keeps it above owner // No need to update position every frame — it follows automatically // Only update rotation for the correct shape appearance if (objectType == ObjectType.CashBag) { markerObject.transform.localRotation = Quaternion.Euler(90f, 0f, 0f); } else { markerObject.transform.localRotation = Quaternion.Euler(90f, 45f, 0f); } } private void CreateMarker() { // GUARD: prevent duplicate markers if (_markerCreated && markerObject != null) return; // Also destroy any stale marker children from previous lifecycle for (int i = transform.childCount - 1; i >= 0; i--) { var child = transform.GetChild(i); if (child.name.Contains("MinimapObjectMarker")) Destroy(child.gameObject); } // Use different heights per type to prevent Z-fighting float h = heightOffset; switch (objectType) { case ObjectType.CashBag: h = heightOffset; break; case ObjectType.PlayerVault: h = heightOffset + 1f; break; case ObjectType.EnemyVault: h = heightOffset + 2f; break; } markerObject = GameObject.CreatePrimitive(PrimitiveType.Quad); markerObject.name = $"{gameObject.name}_MinimapObjectMarker"; // Parent to owner so it follows position automatically markerObject.transform.SetParent(transform, false); markerObject.transform.localPosition = Vector3.up * h; // Initial rotation based on type if (objectType == ObjectType.CashBag) markerObject.transform.localRotation = Quaternion.Euler(90f, 0f, 0f); else markerObject.transform.localRotation = Quaternion.Euler(90f, 45f, 0f); markerObject.transform.localScale = Vector3.one * markerSize; // Remove collider Collider col = markerObject.GetComponent(); if (col != null) Destroy(col); // Configure layer ConfigureLayer(); // Get renderer and create material markerRenderer = markerObject.GetComponent(); Material markerMaterial = new Material(Shader.Find("Sprites/Default")); if (markerMaterial != null) { markerRenderer.material = markerMaterial; } _markerCreated = true; Debug.Log($"[MinimapObjectMarker] Created {objectType} marker for {gameObject.name}"); } private void ConfigureLayer() { int minimapLayer = LayerMask.NameToLayer(minimapLayerName); if (minimapLayer == -1) { if (!hasConfiguredLayer) { Debug.LogError($"[MinimapObjectMarker] CRITICAL: Layer '{minimapLayerName}' not found! " + "Please create this layer in Project Settings > Tags and Layers. " + "Main Camera must EXCLUDE this layer from its Culling Mask!"); } // Use layer 31 as fallback markerObject.layer = 31; } else { markerObject.layer = minimapLayer; hasConfiguredLayer = true; } } private void UpdateMarkerColor() { if (markerRenderer == null) return; Color targetColor; switch (objectType) { case ObjectType.CashBag: targetColor = cashBagColor; break; case ObjectType.PlayerVault: targetColor = playerVaultColor; break; case ObjectType.EnemyVault: targetColor = enemyVaultColor; break; default: targetColor = Color.white; break; } markerRenderer.material.color = targetColor; // Set texture — required for Sprites/Default in URP to render visibly Texture2D tex = (objectType == ObjectType.CashBag) ? GetSquareTexture() : GetDiamondTexture(); markerRenderer.material.mainTexture = tex; } // ─── Texture Generation (cached statics, same pattern as MinimapMarker) ─── private static Texture2D _cachedSquareTex; private static Texture2D _cachedDiamondTex; private static Texture2D GetSquareTexture() { if (_cachedSquareTex == null) _cachedSquareTex = GenerateSquareTexture(); return _cachedSquareTex; } private static Texture2D GetDiamondTexture() { if (_cachedDiamondTex == null) _cachedDiamondTex = GenerateDiamondTexture(); return _cachedDiamondTex; } private static Texture2D GenerateSquareTexture() { int size = 64; Texture2D tex = new Texture2D(size, size); Color[] colors = new Color[size * size]; float border = 4f; for (int y = 0; y < size; y++) { for (int x = 0; x < size; x++) { bool inside = x >= border && x < size - border && y >= border && y < size - border; colors[y * size + x] = inside ? Color.white : Color.clear; } } tex.SetPixels(colors); tex.Apply(); return tex; } private static Texture2D GenerateDiamondTexture() { int size = 64; Texture2D tex = new Texture2D(size, size); Color[] colors = new Color[size * size]; float center = size / 2f; float halfSize = center - 4f; for (int y = 0; y < size; y++) { for (int x = 0; x < size; x++) { float dx = Mathf.Abs(x - center); float dy = Mathf.Abs(y - center); float dist = dx + dy; // Manhattan distance → diamond shape if (dist <= halfSize - 1.5f) colors[y * size + x] = Color.white; else if (dist <= halfSize + 1.5f) { float alpha = 1f - Mathf.Clamp01((dist - halfSize + 1.5f) / 3f); colors[y * size + x] = new Color(1, 1, 1, alpha); } else colors[y * size + x] = Color.clear; } } tex.SetPixels(colors); tex.Apply(); return tex; } private void OnDestroy() { if (markerObject != null) { Destroy(markerObject); } } public void SetType(ObjectType type) { objectType = type; UpdateMarkerColor(); } public void SetVisible(bool visible) { if (markerObject != null) { markerObject.SetActive(visible); } } }