using UnityEngine; using UnityEngine.UI; /// /// TeamIndicator - Creates an overhead team marker above characters to differentiate teammates from enemies. /// Attach to character prefabs or add dynamically via TeamIndicatorManager. /// public class TeamIndicator : MonoBehaviour { [Header("Indicator Settings")] [SerializeField] private float heightOffset = 2.5f; [SerializeField] private float indicatorSize = 0.3f; [Header("Team Colors")] [SerializeField] private Color allyColor = new Color(0.2f, 0.8f, 0.2f, 1f); // Green [SerializeField] private Color enemyColor = new Color(0.9f, 0.2f, 0.2f, 1f); // Red [SerializeField] private Color controlledColor = new Color(0.2f, 0.6f, 1f, 1f); // Blue for currently controlled [Header("Visual Options")] [SerializeField] private bool showOnAllies = true; [SerializeField] private bool showOnEnemies = true; [SerializeField] private bool hideWhenControlled = true; [SerializeField] private float fadeDistance = 50f; // Fade out at this distance private GameObject indicatorObject; private SpriteRenderer spriteRenderer; private Camera mainCamera; private TeamMember teamMember; private string myTeamTag; private bool isCurrentlyControlled = false; // Static reference to find the current player for team comparison private static Transform currentPlayerTransform; private static string currentPlayerTeamTag = "Player"; private void Start() { // Only activate in Cash System mode if (SelectionOptions.Instance == null || !SelectionOptions.Instance.isCashSystemSelected) { enabled = false; return; } mainCamera = Camera.main; teamMember = GetComponent(); myTeamTag = gameObject.tag; CreateIndicator(); UpdateIndicatorColor(); } private void LateUpdate() { if (indicatorObject == null || mainCamera == null) return; // Update position to stay above character indicatorObject.transform.position = transform.position + Vector3.up * heightOffset; // Billboard effect - always face camera indicatorObject.transform.rotation = Quaternion.LookRotation( indicatorObject.transform.position - mainCamera.transform.position ); // Check if this character is currently controlled bool wasControlled = isCurrentlyControlled; isCurrentlyControlled = teamMember != null && teamMember.IsPlayerControlled; // Update static reference if we're the controlled character if (isCurrentlyControlled) { currentPlayerTransform = transform; currentPlayerTeamTag = myTeamTag; } // Update color if control status changed if (wasControlled != isCurrentlyControlled) { UpdateIndicatorColor(); } // Handle visibility based on settings UpdateVisibility(); // Fade based on distance from camera UpdateFade(); } private void CreateIndicator() { // Create indicator game object indicatorObject = new GameObject($"{gameObject.name}_TeamIndicator"); indicatorObject.transform.position = transform.position + Vector3.up * heightOffset; // Add sprite renderer for the indicator spriteRenderer = indicatorObject.AddComponent(); spriteRenderer.sprite = CreateCircleSprite(); spriteRenderer.sortingOrder = 100; // Ensure it renders on top // Set initial size indicatorObject.transform.localScale = Vector3.one * indicatorSize; } private Sprite CreateCircleSprite() { // Create a simple diamond/arrow shaped indicator texture int size = 64; Texture2D texture = new Texture2D(size, size); Color transparent = new Color(0, 0, 0, 0); // Fill with transparent for (int x = 0; x < size; x++) { for (int y = 0; y < size; y++) { texture.SetPixel(x, y, transparent); } } // Draw a downward pointing triangle/arrow int centerX = size / 2; int topY = size - 8; int bottomY = 8; for (int y = bottomY; y <= topY; y++) { float progress = (float)(y - bottomY) / (topY - bottomY); int halfWidth = (int)(progress * (size / 2 - 4)); for (int x = centerX - halfWidth; x <= centerX + halfWidth; x++) { // Create a border effect bool isBorder = Mathf.Abs(x - (centerX - halfWidth)) < 3 || Mathf.Abs(x - (centerX + halfWidth)) < 3 || y == bottomY || y >= topY - 2; Color col = isBorder ? Color.white : Color.white; col.a = isBorder ? 1f : 0.8f; texture.SetPixel(x, y, col); } } texture.Apply(); texture.filterMode = FilterMode.Bilinear; return Sprite.Create(texture, new Rect(0, 0, size, size), new Vector2(0.5f, 0f), size); } private void UpdateIndicatorColor() { if (spriteRenderer == null) return; // Currently controlled character if (isCurrentlyControlled) { spriteRenderer.color = controlledColor; return; } // Determine if ally or enemy relative to player bool isAlly = (myTeamTag == currentPlayerTeamTag); if (isAlly) { spriteRenderer.color = allyColor; } else { spriteRenderer.color = enemyColor; } } private void UpdateVisibility() { if (spriteRenderer == null) return; bool shouldShow = true; // Hide if this is the controlled character and setting is enabled if (hideWhenControlled && isCurrentlyControlled) { shouldShow = false; } // Check ally/enemy visibility settings bool isAlly = (myTeamTag == currentPlayerTeamTag); if (isAlly && !showOnAllies) shouldShow = false; if (!isAlly && !showOnEnemies) shouldShow = false; indicatorObject.SetActive(shouldShow); } private void UpdateFade() { if (spriteRenderer == null || mainCamera == null) return; float distance = Vector3.Distance(mainCamera.transform.position, transform.position); // Calculate alpha based on distance float alpha = Mathf.Clamp01(1f - (distance / fadeDistance)); alpha = Mathf.Max(alpha, 0.3f); // Minimum alpha Color currentColor = spriteRenderer.color; currentColor.a = alpha; spriteRenderer.color = currentColor; } private void OnDestroy() { if (indicatorObject != null) { Destroy(indicatorObject); } } /// /// Force update the indicator color (call after team changes) /// public void RefreshIndicator() { myTeamTag = gameObject.tag; UpdateIndicatorColor(); UpdateVisibility(); } /// /// Set indicator visibility /// public void SetVisible(bool visible) { if (indicatorObject != null) { indicatorObject.SetActive(visible); } } /// /// Static method to update all indicators when player changes /// public static void RefreshAllIndicators() { foreach (var indicator in FindObjectsOfType()) { indicator.RefreshIndicator(); } } }