using UnityEngine; using Core; using Core.Settings; using AppleHillsCamera; namespace Minigames.BirdPooper { /// /// Abstract base class for all scrolling entities in Bird Pooper minigame. /// Provides common functionality: manual left-scrolling, EdgeAnchor integration, despawn detection. /// Subclasses: Obstacle, Target /// [RequireComponent(typeof(Collider2D))] [RequireComponent(typeof(EdgeAnchor))] public abstract class ScrollingEntity : MonoBehaviour { [Header("Positioning")] [Tooltip("Which vertical edge to anchor to (Top/Middle/Bottom)")] [SerializeField] protected EdgeAnchor.AnchorEdge verticalAnchor = EdgeAnchor.AnchorEdge.Middle; protected IBirdPooperSettings settings; protected float despawnXPosition; protected bool isInitialized; protected EdgeAnchor edgeAnchor; /// /// Initialize the entity with despawn position and EdgeAnchor references. /// Called by spawner immediately after instantiation. /// public virtual void Initialize(float despawnX, ScreenReferenceMarker referenceMarker, CameraScreenAdapter cameraAdapter) { despawnXPosition = despawnX; isInitialized = true; // Load settings settings = GameManager.GetSettingsObject(); if (settings == null) { Debug.LogError($"[{GetType().Name}] BirdPooperSettings not found!"); } // Tag all child GameObjects with colliders TagChildCollidersRecursive(transform); // Find and set all colliders to trigger (we use OnTriggerEnter2D) Collider2D[] colliders = GetComponentsInChildren(true); foreach (Collider2D col in colliders) { if (!col.isTrigger) { col.isTrigger = true; Debug.Log($"[{GetType().Name}] Set collider '{col.name}' to trigger"); } } // Configure and update EdgeAnchor edgeAnchor = GetComponent(); if (edgeAnchor != null) { // Assign references from spawner edgeAnchor.referenceMarker = referenceMarker; edgeAnchor.cameraAdapter = cameraAdapter; // Only allow Top, Middle, or Bottom anchoring if (verticalAnchor == EdgeAnchor.AnchorEdge.Left || verticalAnchor == EdgeAnchor.AnchorEdge.Right) { Debug.LogWarning($"[{GetType().Name}] Invalid anchor edge (Left/Right not supported). Defaulting to Middle."); verticalAnchor = EdgeAnchor.AnchorEdge.Middle; } edgeAnchor.anchorEdge = verticalAnchor; edgeAnchor.useReferenceMargin = false; // No custom offset edgeAnchor.customMargin = 0f; edgeAnchor.preserveOtherAxes = true; // Keep X position (for scrolling) edgeAnchor.accountForObjectSize = true; // Trigger position update edgeAnchor.UpdatePosition(); Debug.Log($"[{GetType().Name}] EdgeAnchor configured to {verticalAnchor} at position {transform.position}"); } else { Debug.LogError($"[{GetType().Name}] EdgeAnchor component not found! Make sure the prefab has an EdgeAnchor component."); } Debug.Log($"[{GetType().Name}] Initialized at position {transform.position} with despawn X: {despawnX}"); } /// /// Recursively tag all GameObjects with Collider2D for collision detection. /// Subclasses override GetColliderTag() to specify their tag. /// protected virtual void TagChildCollidersRecursive(Transform current) { string tagToApply = GetColliderTag(); // Tag this GameObject if it has a collider Collider2D col = current.GetComponent(); if (col != null && !current.CompareTag(tagToApply)) { current.tag = tagToApply; Debug.Log($"[{GetType().Name}] Tagged '{current.name}' as {tagToApply}"); } // Recurse to children foreach (Transform child in current) { TagChildCollidersRecursive(child); } } #if UNITY_EDITOR /// /// Called when values are changed in the Inspector (Editor only). /// Updates EdgeAnchor configuration to match entity settings. /// protected virtual void OnValidate() { // Only run in editor, not during play mode if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) return; EdgeAnchor anchor = GetComponent(); if (anchor != null) { // Auto-find and assign references if not set (for editor-time visual updates) if (anchor.referenceMarker == null) { anchor.referenceMarker = FindAnyObjectByType(); if (anchor.referenceMarker == null) { Debug.LogWarning($"[{GetType().Name}] No ScreenReferenceMarker found in scene. EdgeAnchor positioning won't work in editor."); } } if (anchor.cameraAdapter == null) { anchor.cameraAdapter = FindAnyObjectByType(); // CameraScreenAdapter is optional - EdgeAnchor can auto-find camera } // Validate and set anchor edge if (verticalAnchor == EdgeAnchor.AnchorEdge.Left || verticalAnchor == EdgeAnchor.AnchorEdge.Right) { Debug.LogWarning($"[{GetType().Name}] Invalid anchor edge (Left/Right not supported). Defaulting to Middle."); verticalAnchor = EdgeAnchor.AnchorEdge.Middle; } // Configure EdgeAnchor to match entity settings anchor.anchorEdge = verticalAnchor; anchor.useReferenceMargin = false; anchor.customMargin = 0f; anchor.preserveOtherAxes = true; anchor.accountForObjectSize = true; // Mark as dirty so Unity saves the changes UnityEditor.EditorUtility.SetDirty(anchor); } // Tag all child GameObjects with colliders TagChildCollidersRecursiveEditor(transform); } /// /// Editor version of recursive tagging for child colliders. /// protected virtual void TagChildCollidersRecursiveEditor(Transform current) { string tagToApply = GetColliderTag(); // Tag this GameObject if it has a collider Collider2D col = current.GetComponent(); if (col != null && !current.CompareTag(tagToApply)) { current.tag = tagToApply; UnityEditor.EditorUtility.SetDirty(current.gameObject); } // Recurse to children foreach (Transform child in current) { TagChildCollidersRecursiveEditor(child); } } #endif protected virtual void Update() { if (!isInitialized || settings == null) return; MoveLeft(); CheckBounds(); } /// /// Move entity left at constant speed (manual movement, no physics). /// Override GetMoveSpeed() to customize speed per entity type. /// protected virtual void MoveLeft() { transform.position += Vector3.left * (GetMoveSpeed() * Time.deltaTime); } /// /// Check if entity has passed despawn position and destroy if so. /// protected virtual void CheckBounds() { if (transform.position.x < despawnXPosition) { Debug.Log($"[{GetType().Name}] Reached despawn position, destroying at X: {transform.position.x}"); Destroy(gameObject); } } /// /// Get the move speed for this entity type. /// Subclasses override to return appropriate speed from settings. /// protected abstract float GetMoveSpeed(); /// /// Get the tag to apply to colliders for this entity type. /// Subclasses override to return "Obstacle", "Target", etc. /// protected abstract string GetColliderTag(); } }