301 lines
12 KiB
C#
301 lines
12 KiB
C#
using UnityEngine;
|
|
using Core;
|
|
using Core.Settings;
|
|
using AppleHillsCamera;
|
|
|
|
namespace Minigames.BirdPooper
|
|
{
|
|
/// <summary>
|
|
/// Individual obstacle behavior for Bird Pooper minigame.
|
|
/// Scrolls left at constant speed and self-destructs when reaching despawn position.
|
|
/// Uses trigger colliders for collision detection (no Rigidbody2D needed).
|
|
/// Uses EdgeAnchor for vertical positioning (Top/Middle/Bottom).
|
|
/// </summary>
|
|
[RequireComponent(typeof(Collider2D))]
|
|
[RequireComponent(typeof(EdgeAnchor))]
|
|
public class Obstacle : MonoBehaviour
|
|
{
|
|
[Header("Positioning")]
|
|
[Tooltip("Which vertical edge to anchor to (Top/Middle/Bottom)")]
|
|
[SerializeField] private EdgeAnchor.AnchorEdge verticalAnchor = EdgeAnchor.AnchorEdge.Middle;
|
|
|
|
private IBirdPooperSettings settings;
|
|
private float despawnXPosition;
|
|
private bool isInitialized;
|
|
private EdgeAnchor edgeAnchor;
|
|
|
|
/// <summary>
|
|
/// Initialize the obstacle with despawn position and EdgeAnchor references.
|
|
/// Called by ObstacleSpawner immediately after instantiation.
|
|
/// </summary>
|
|
/// <param name="despawnX">X position where obstacle should be destroyed</param>
|
|
/// <param name="referenceMarker">ScreenReferenceMarker for EdgeAnchor</param>
|
|
/// <param name="cameraAdapter">CameraScreenAdapter for EdgeAnchor</param>
|
|
public void Initialize(float despawnX, ScreenReferenceMarker referenceMarker, CameraScreenAdapter cameraAdapter)
|
|
{
|
|
despawnXPosition = despawnX;
|
|
isInitialized = true;
|
|
|
|
// Load settings
|
|
settings = GameManager.GetSettingsObject<IBirdPooperSettings>();
|
|
if (settings == null)
|
|
{
|
|
Debug.LogError("[Obstacle] BirdPooperSettings not found!");
|
|
}
|
|
|
|
// Tag all child GameObjects with colliders as "Obstacle" for trigger detection
|
|
TagChildCollidersRecursive(transform);
|
|
|
|
// 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("[Obstacle] 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($"[Obstacle] EdgeAnchor configured to {verticalAnchor} at position {transform.position}");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("[Obstacle] EdgeAnchor component not found! Make sure the prefab has an EdgeAnchor component.");
|
|
}
|
|
|
|
Debug.Log($"[Obstacle] Initialized at position {transform.position} with despawn X: {despawnX}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recursively tag all GameObjects with Collider2D as "Obstacle" for player collision detection.
|
|
/// </summary>
|
|
private void TagChildCollidersRecursive(Transform current)
|
|
{
|
|
// Tag this GameObject if it has a collider
|
|
Collider2D col = current.GetComponent<Collider2D>();
|
|
if (col != null && !current.CompareTag("Obstacle"))
|
|
{
|
|
current.tag = "Obstacle";
|
|
Debug.Log($"[Obstacle] Tagged '{current.name}' as Obstacle");
|
|
}
|
|
|
|
// 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 Obstacle settings.
|
|
/// Also finds and assigns ScreenReferenceMarker and CameraScreenAdapter for visual updates.
|
|
/// </summary>
|
|
private 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("[Obstacle] 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("[Obstacle] Invalid anchor edge (Left/Right not supported). Defaulting to Middle.");
|
|
verticalAnchor = EdgeAnchor.AnchorEdge.Middle;
|
|
}
|
|
|
|
// Configure EdgeAnchor to match Obstacle 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 as "Obstacle" for collision detection
|
|
TagChildCollidersRecursiveEditor(transform);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Editor version of recursive tagging for child colliders.
|
|
/// </summary>
|
|
private void TagChildCollidersRecursiveEditor(Transform current)
|
|
{
|
|
// Tag this GameObject if it has a collider
|
|
Collider2D col = current.GetComponent<Collider2D>();
|
|
if (col != null && !current.CompareTag("Obstacle"))
|
|
{
|
|
current.tag = "Obstacle";
|
|
UnityEditor.EditorUtility.SetDirty(current.gameObject);
|
|
}
|
|
|
|
// Recurse to children
|
|
foreach (Transform child in current)
|
|
{
|
|
TagChildCollidersRecursiveEditor(child);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
private void Update()
|
|
{
|
|
if (!isInitialized || settings == null) return;
|
|
|
|
MoveLeft();
|
|
CheckBounds();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move obstacle left at constant speed (manual movement, no physics).
|
|
/// </summary>
|
|
private void MoveLeft()
|
|
{
|
|
transform.position += Vector3.left * (settings.ObstacleMoveSpeed * Time.deltaTime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if obstacle has passed despawn position and destroy if so.
|
|
/// </summary>
|
|
private void CheckBounds()
|
|
{
|
|
if (transform.position.x < despawnXPosition)
|
|
{
|
|
Debug.Log($"[Obstacle] Reached despawn position, destroying at X: {transform.position.x}");
|
|
Destroy(gameObject);
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
/// <summary>
|
|
/// Draw debug visualization of the obstacle's anchor point.
|
|
/// Red horizontal line through custom anchor point OR bounds edge (top/bottom).
|
|
/// </summary>
|
|
private void OnDrawGizmos()
|
|
{
|
|
EdgeAnchor anchor = GetComponent<EdgeAnchor>();
|
|
if (anchor == null) return;
|
|
|
|
// Determine what Y position to visualize
|
|
float visualY;
|
|
|
|
// If using custom anchor point, draw line through it
|
|
if (anchor.customAnchorPoint != null)
|
|
{
|
|
visualY = anchor.customAnchorPoint.position.y;
|
|
}
|
|
else
|
|
{
|
|
// Get bounds and determine which edge to visualize
|
|
Bounds bounds = GetVisualBounds();
|
|
|
|
// Check which vertical anchor is configured
|
|
EdgeAnchor.AnchorEdge edge = anchor.anchorEdge;
|
|
|
|
if (edge == EdgeAnchor.AnchorEdge.Top)
|
|
{
|
|
// Show top edge of bounds
|
|
visualY = bounds.max.y;
|
|
}
|
|
else if (edge == EdgeAnchor.AnchorEdge.Bottom)
|
|
{
|
|
// Show bottom edge of bounds
|
|
visualY = bounds.min.y;
|
|
}
|
|
else // Middle
|
|
{
|
|
// Show center of bounds
|
|
visualY = bounds.center.y;
|
|
}
|
|
}
|
|
|
|
// Draw thick red horizontal line through the anchor point
|
|
Color oldColor = Gizmos.color;
|
|
Gizmos.color = Color.red;
|
|
|
|
// Draw multiple lines to make it thicker
|
|
float lineLength = 2f; // Extend 2 units on each side
|
|
Vector3 leftPoint = new Vector3(transform.position.x - lineLength, visualY, transform.position.z);
|
|
Vector3 rightPoint = new Vector3(transform.position.x + lineLength, visualY, transform.position.z);
|
|
|
|
// Draw 5 lines stacked vertically to create thickness
|
|
for (int i = -2; i <= 2; i++)
|
|
{
|
|
float offset = i * 0.02f; // Small vertical offset for thickness
|
|
Vector3 offsetLeft = leftPoint + Vector3.up * offset;
|
|
Vector3 offsetRight = rightPoint + Vector3.up * offset;
|
|
Gizmos.DrawLine(offsetLeft, offsetRight);
|
|
}
|
|
|
|
Gizmos.color = oldColor;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get bounds for visualization purposes (works in editor without initialized settings).
|
|
/// </summary>
|
|
private Bounds GetVisualBounds()
|
|
{
|
|
// Get all renderers in this object and its children
|
|
Renderer[] renderers = GetComponentsInChildren<Renderer>();
|
|
|
|
if (renderers.Length > 0)
|
|
{
|
|
Bounds bounds = renderers[0].bounds;
|
|
for (int i = 1; i < renderers.Length; i++)
|
|
{
|
|
bounds.Encapsulate(renderers[i].bounds);
|
|
}
|
|
return bounds;
|
|
}
|
|
|
|
// Fallback to collider bounds
|
|
Collider2D col = GetComponent<Collider2D>();
|
|
if (col != null)
|
|
{
|
|
return col.bounds;
|
|
}
|
|
|
|
// Default small bounds
|
|
return new Bounds(transform.position, new Vector3(0.5f, 0.5f, 0.1f));
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|