231 lines
9.1 KiB
C#
231 lines
9.1 KiB
C#
|
|
using UnityEngine;
|
|||
|
|
using Core;
|
|||
|
|
using Core.Settings;
|
|||
|
|
using AppleHillsCamera;
|
|||
|
|
|
|||
|
|
namespace Minigames.BirdPooper
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// Abstract base class for all scrolling entities in Bird Pooper minigame.
|
|||
|
|
/// Provides common functionality: manual left-scrolling, EdgeAnchor integration, despawn detection.
|
|||
|
|
/// Subclasses: Obstacle, Target
|
|||
|
|
/// </summary>
|
|||
|
|
[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;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Initialize the entity with despawn position and EdgeAnchor references.
|
|||
|
|
/// Called by spawner immediately after instantiation.
|
|||
|
|
/// </summary>
|
|||
|
|
public virtual void Initialize(float despawnX, ScreenReferenceMarker referenceMarker, CameraScreenAdapter cameraAdapter)
|
|||
|
|
{
|
|||
|
|
despawnXPosition = despawnX;
|
|||
|
|
isInitialized = true;
|
|||
|
|
|
|||
|
|
// Load settings
|
|||
|
|
settings = GameManager.GetSettingsObject<IBirdPooperSettings>();
|
|||
|
|
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<Collider2D>(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<EdgeAnchor>();
|
|||
|
|
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}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Recursively tag all GameObjects with Collider2D for collision detection.
|
|||
|
|
/// Subclasses override GetColliderTag() to specify their tag.
|
|||
|
|
/// </summary>
|
|||
|
|
protected virtual void TagChildCollidersRecursive(Transform current)
|
|||
|
|
{
|
|||
|
|
string tagToApply = GetColliderTag();
|
|||
|
|
|
|||
|
|
// Tag this GameObject if it has a collider
|
|||
|
|
Collider2D col = current.GetComponent<Collider2D>();
|
|||
|
|
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
|
|||
|
|
/// <summary>
|
|||
|
|
/// Called when values are changed in the Inspector (Editor only).
|
|||
|
|
/// Updates EdgeAnchor configuration to match entity settings.
|
|||
|
|
/// </summary>
|
|||
|
|
protected virtual void OnValidate()
|
|||
|
|
{
|
|||
|
|
// Only run in editor, not during play mode
|
|||
|
|
if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
|
|||
|
|
return;
|
|||
|
|
|
|||
|
|
EdgeAnchor anchor = GetComponent<EdgeAnchor>();
|
|||
|
|
if (anchor != null)
|
|||
|
|
{
|
|||
|
|
// Auto-find and assign references if not set (for editor-time visual updates)
|
|||
|
|
if (anchor.referenceMarker == null)
|
|||
|
|
{
|
|||
|
|
anchor.referenceMarker = FindAnyObjectByType<ScreenReferenceMarker>();
|
|||
|
|
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>();
|
|||
|
|
// 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);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Editor version of recursive tagging for child colliders.
|
|||
|
|
/// </summary>
|
|||
|
|
protected virtual void TagChildCollidersRecursiveEditor(Transform current)
|
|||
|
|
{
|
|||
|
|
string tagToApply = GetColliderTag();
|
|||
|
|
|
|||
|
|
// Tag this GameObject if it has a collider
|
|||
|
|
Collider2D col = current.GetComponent<Collider2D>();
|
|||
|
|
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();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Move entity left at constant speed (manual movement, no physics).
|
|||
|
|
/// Override GetMoveSpeed() to customize speed per entity type.
|
|||
|
|
/// </summary>
|
|||
|
|
protected virtual void MoveLeft()
|
|||
|
|
{
|
|||
|
|
transform.position += Vector3.left * (GetMoveSpeed() * Time.deltaTime);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Check if entity has passed despawn position and destroy if so.
|
|||
|
|
/// </summary>
|
|||
|
|
protected virtual void CheckBounds()
|
|||
|
|
{
|
|||
|
|
if (transform.position.x < despawnXPosition)
|
|||
|
|
{
|
|||
|
|
Debug.Log($"[{GetType().Name}] Reached despawn position, destroying at X: {transform.position.x}");
|
|||
|
|
Destroy(gameObject);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Get the move speed for this entity type.
|
|||
|
|
/// Subclasses override to return appropriate speed from settings.
|
|||
|
|
/// </summary>
|
|||
|
|
protected abstract float GetMoveSpeed();
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Get the tag to apply to colliders for this entity type.
|
|||
|
|
/// Subclasses override to return "Obstacle", "Target", etc.
|
|||
|
|
/// </summary>
|
|||
|
|
protected abstract string GetColliderTag();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|