Poop obstacle pipeline working
This commit is contained in:
@@ -56,12 +56,12 @@ namespace Minigames.BirdPooper
|
||||
return;
|
||||
}
|
||||
|
||||
// Register as override consumer to capture ALL input (except UI button)
|
||||
// Register as override consumer to capture ALL input (except UI button)
|
||||
// Register as default consumer (gets input if nothing else consumes it)
|
||||
// This allows UI buttons to work while still flapping when tapping empty space
|
||||
if (Input.InputManager.Instance != null)
|
||||
{
|
||||
Input.InputManager.Instance.RegisterOverrideConsumer(this);
|
||||
Debug.Log("[BirdPlayerController] Registered as override input consumer");
|
||||
Input.InputManager.Instance.SetDefaultConsumer(this);
|
||||
Debug.Log("[BirdPlayerController] Registered as default input consumer");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -161,12 +161,12 @@ namespace Minigames.BirdPooper
|
||||
|
||||
/// <summary>
|
||||
/// Called when a trigger collider enters this object's trigger.
|
||||
/// Used for detecting obstacles without physics interactions.
|
||||
/// Used for detecting obstacles and targets without physics interactions.
|
||||
/// </summary>
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
// Check if the colliding object is tagged as an obstacle
|
||||
if (other.CompareTag("Obstacle"))
|
||||
// Check if the colliding object is tagged as an obstacle or target
|
||||
if (other.CompareTag("Obstacle") || other.CompareTag("Target"))
|
||||
{
|
||||
HandleDeath();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Minigames.BirdPooper
|
||||
[Header("References")]
|
||||
[SerializeField] private BirdPlayerController player;
|
||||
[SerializeField] private ObstacleSpawner obstacleSpawner;
|
||||
[SerializeField] private TargetSpawner targetSpawner;
|
||||
[SerializeField] private GameOverScreen gameOverScreen;
|
||||
[SerializeField] private GameObject poopPrefab;
|
||||
|
||||
@@ -47,6 +48,11 @@ namespace Minigames.BirdPooper
|
||||
Debug.LogError("[BirdPooperGameManager] ObstacleSpawner reference not assigned!");
|
||||
}
|
||||
|
||||
if (targetSpawner == null)
|
||||
{
|
||||
Debug.LogWarning("[BirdPooperGameManager] TargetSpawner reference not assigned! Targets will not spawn.");
|
||||
}
|
||||
|
||||
if (gameOverScreen == null)
|
||||
{
|
||||
Debug.LogError("[BirdPooperGameManager] GameOverScreen reference not assigned!");
|
||||
@@ -84,6 +90,13 @@ namespace Minigames.BirdPooper
|
||||
obstacleSpawner.StartSpawning();
|
||||
Debug.Log("[BirdPooperGameManager] Started obstacle spawning");
|
||||
}
|
||||
|
||||
// Start target spawning
|
||||
if (targetSpawner != null)
|
||||
{
|
||||
targetSpawner.StartSpawning();
|
||||
Debug.Log("[BirdPooperGameManager] Started target spawning");
|
||||
}
|
||||
}
|
||||
|
||||
internal override void OnManagedDestroy()
|
||||
@@ -120,6 +133,12 @@ namespace Minigames.BirdPooper
|
||||
obstacleSpawner.StopSpawning();
|
||||
}
|
||||
|
||||
// Stop spawning targets
|
||||
if (targetSpawner != null)
|
||||
{
|
||||
targetSpawner.StopSpawning();
|
||||
}
|
||||
|
||||
// Show game over screen
|
||||
if (gameOverScreen != null)
|
||||
{
|
||||
|
||||
@@ -1,300 +1,28 @@
|
||||
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).
|
||||
/// Obstacle entity for Bird Pooper minigame.
|
||||
/// Inherits scrolling, anchoring, and despawn behavior from ScrollingEntity.
|
||||
/// Player dies on collision with obstacles.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
[RequireComponent(typeof(EdgeAnchor))]
|
||||
public class Obstacle : MonoBehaviour
|
||||
public class Obstacle : ScrollingEntity
|
||||
{
|
||||
[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.
|
||||
/// Returns obstacle move speed from settings.
|
||||
/// </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)
|
||||
protected override float GetMoveSpeed()
|
||||
{
|
||||
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}");
|
||||
return settings != null ? settings.ObstacleMoveSpeed : 5f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively tag all GameObjects with Collider2D as "Obstacle" for player collision detection.
|
||||
/// Returns "Obstacle" tag for collision detection.
|
||||
/// </summary>
|
||||
private void TagChildCollidersRecursive(Transform current)
|
||||
protected override string GetColliderTag()
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
return "Obstacle";
|
||||
}
|
||||
|
||||
#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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
300
Assets/Scripts/Minigames/BirdPooper/Obstacle.cs.backup
Normal file
300
Assets/Scripts/Minigames/BirdPooper/Obstacle.cs.backup
Normal file
@@ -0,0 +1,300 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc9c7bac4482311439b4c2e7879f3d73
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -21,6 +21,12 @@ namespace Minigames.BirdPooper
|
||||
{
|
||||
base.OnManagedAwake();
|
||||
|
||||
// Tag as Projectile for target detection
|
||||
if (!gameObject.CompareTag("Projectile"))
|
||||
{
|
||||
gameObject.tag = "Projectile";
|
||||
}
|
||||
|
||||
// Load settings
|
||||
settings = GameManager.GetSettingsObject<IBirdPooperSettings>();
|
||||
if (settings == null)
|
||||
@@ -37,16 +43,19 @@ namespace Minigames.BirdPooper
|
||||
Rigidbody2D rb = GetComponent<Rigidbody2D>();
|
||||
if (rb != null)
|
||||
{
|
||||
rb.bodyType = RigidbodyType2D.Dynamic;
|
||||
rb.gravityScale = 0f; // Manual gravity
|
||||
rb.bodyType = RigidbodyType2D.Kinematic; // Kinematic = manual control, no physics
|
||||
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
|
||||
}
|
||||
|
||||
// Verify collider is trigger (for target detection in Phase 5)
|
||||
Collider2D col = GetComponent<Collider2D>();
|
||||
if (col != null && !col.isTrigger)
|
||||
// Find and set all colliders to trigger (we use OnTriggerEnter2D)
|
||||
Collider2D[] colliders = GetComponentsInChildren<Collider2D>(true);
|
||||
foreach (Collider2D col in colliders)
|
||||
{
|
||||
Debug.LogWarning("[PoopProjectile] Collider should be set as Trigger for target detection!");
|
||||
if (!col.isTrigger)
|
||||
{
|
||||
col.isTrigger = true;
|
||||
Debug.Log($"[PoopProjectile] Set collider '{col.name}' to trigger");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,12 +100,10 @@ namespace Minigames.BirdPooper
|
||||
|
||||
/// <summary>
|
||||
/// Trigger collision detection for targets (Phase 5).
|
||||
/// TODO: Uncomment when Target.cs is implemented in Phase 5
|
||||
/// Uses OnTriggerEnter2D with trigger collider.
|
||||
/// </summary>
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
// Phase 5 integration - currently commented out
|
||||
/*
|
||||
if (other.CompareTag("Target"))
|
||||
{
|
||||
// Notify target it was hit
|
||||
@@ -108,7 +115,6 @@ namespace Minigames.BirdPooper
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
230
Assets/Scripts/Minigames/BirdPooper/ScrollingEntity.cs
Normal file
230
Assets/Scripts/Minigames/BirdPooper/ScrollingEntity.cs
Normal file
@@ -0,0 +1,230 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69a3d32fc5cf4789a5e6e8dbc5f64996
|
||||
timeCreated: 1763713709
|
||||
117
Assets/Scripts/Minigames/BirdPooper/Target.cs
Normal file
117
Assets/Scripts/Minigames/BirdPooper/Target.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using AppleHillsCamera;
|
||||
|
||||
namespace Minigames.BirdPooper
|
||||
{
|
||||
/// <summary>
|
||||
/// Target entity for Bird Pooper minigame.
|
||||
/// Inherits scrolling, anchoring, and despawn behavior from ScrollingEntity.
|
||||
/// Can be hit by poop projectiles for scoring. Player still dies on collision.
|
||||
/// </summary>
|
||||
public class Target : ScrollingEntity
|
||||
{
|
||||
[Header("Visual Feedback")]
|
||||
[SerializeField] private SpriteRenderer spriteRenderer;
|
||||
[SerializeField] private Color hitColor = Color.green;
|
||||
|
||||
[Header("Events")]
|
||||
public UnityEvent onTargetHit;
|
||||
|
||||
private bool isHit;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize target and set up event.
|
||||
/// </summary>
|
||||
public override void Initialize(float despawnX, ScreenReferenceMarker referenceMarker, CameraScreenAdapter cameraAdapter)
|
||||
{
|
||||
base.Initialize(despawnX, referenceMarker, cameraAdapter);
|
||||
|
||||
isHit = false;
|
||||
|
||||
// Initialize event
|
||||
if (onTargetHit == null)
|
||||
{
|
||||
onTargetHit = new UnityEvent();
|
||||
}
|
||||
|
||||
// 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($"[Target] Set collider '{col.name}' to trigger");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override Update to stop movement when hit.
|
||||
/// </summary>
|
||||
protected override void Update()
|
||||
{
|
||||
if (isHit) return; // Don't move or check bounds if hit
|
||||
|
||||
base.Update();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns target move speed from settings.
|
||||
/// </summary>
|
||||
protected override float GetMoveSpeed()
|
||||
{
|
||||
return settings != null ? settings.TargetMoveSpeed : 4f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns "Target" tag for collision detection.
|
||||
/// </summary>
|
||||
protected override string GetColliderTag()
|
||||
{
|
||||
return "Target";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trigger collision detection for both player and projectiles.
|
||||
/// Single trigger collider handles both cases.
|
||||
/// </summary>
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
// Check for projectile collision
|
||||
if (other.CompareTag("Projectile") && !isHit)
|
||||
{
|
||||
OnHitByProjectile();
|
||||
}
|
||||
// Player collision is handled by BirdPlayerController's OnTriggerEnter2D
|
||||
// (both have trigger colliders, so trigger occurs naturally)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when target is hit by a poop projectile.
|
||||
/// Shows visual feedback and notifies manager.
|
||||
/// </summary>
|
||||
public void OnHitByProjectile()
|
||||
{
|
||||
if (isHit) return;
|
||||
|
||||
isHit = true;
|
||||
|
||||
// Visual feedback
|
||||
if (spriteRenderer != null)
|
||||
{
|
||||
spriteRenderer.color = hitColor;
|
||||
}
|
||||
|
||||
// Notify manager
|
||||
onTargetHit?.Invoke();
|
||||
|
||||
Debug.Log($"[Target] Hit by projectile at position {transform.position}");
|
||||
|
||||
// Destroy after brief delay for visual feedback
|
||||
Destroy(gameObject, 0.2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/Minigames/BirdPooper/Target.cs.meta
Normal file
3
Assets/Scripts/Minigames/BirdPooper/Target.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5331a770bc634a738b82f9450441de12
|
||||
timeCreated: 1763713776
|
||||
198
Assets/Scripts/Minigames/BirdPooper/TargetSpawner.cs
Normal file
198
Assets/Scripts/Minigames/BirdPooper/TargetSpawner.cs
Normal file
@@ -0,0 +1,198 @@
|
||||
using UnityEngine;
|
||||
using Core;
|
||||
using Core.Settings;
|
||||
using Core.Lifecycle;
|
||||
using AppleHillsCamera;
|
||||
|
||||
namespace Minigames.BirdPooper
|
||||
{
|
||||
/// <summary>
|
||||
/// Spawns targets at regular intervals for Bird Pooper minigame.
|
||||
/// Uses Transform references for spawn and despawn positions.
|
||||
/// All targets are spawned at Y = 0 (EdgeAnchor positions them vertically).
|
||||
/// </summary>
|
||||
public class TargetSpawner : ManagedBehaviour
|
||||
{
|
||||
[Header("Spawn Configuration")]
|
||||
[Tooltip("Transform marking where targets spawn (off-screen right)")]
|
||||
[SerializeField] private Transform spawnPoint;
|
||||
|
||||
[Tooltip("Transform marking where targets despawn (off-screen left)")]
|
||||
[SerializeField] private Transform despawnPoint;
|
||||
|
||||
[Header("EdgeAnchor References")]
|
||||
[Tooltip("ScreenReferenceMarker to pass to spawned targets")]
|
||||
[SerializeField] private ScreenReferenceMarker referenceMarker;
|
||||
|
||||
[Tooltip("CameraScreenAdapter to pass to spawned targets")]
|
||||
[SerializeField] private CameraScreenAdapter cameraAdapter;
|
||||
|
||||
[Header("Target Prefabs")]
|
||||
[Tooltip("Array of target prefabs to spawn randomly")]
|
||||
[SerializeField] private GameObject[] targetPrefabs;
|
||||
|
||||
private IBirdPooperSettings settings;
|
||||
private float spawnTimer;
|
||||
private bool isSpawning;
|
||||
|
||||
internal override void OnManagedAwake()
|
||||
{
|
||||
base.OnManagedAwake();
|
||||
|
||||
// Load settings
|
||||
settings = GameManager.GetSettingsObject<IBirdPooperSettings>();
|
||||
if (settings == null)
|
||||
{
|
||||
Debug.LogError("[TargetSpawner] BirdPooperSettings not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate references
|
||||
if (spawnPoint == null)
|
||||
{
|
||||
Debug.LogError("[TargetSpawner] Spawn Point not assigned! Please assign a Transform in the Inspector.");
|
||||
}
|
||||
|
||||
if (despawnPoint == null)
|
||||
{
|
||||
Debug.LogError("[TargetSpawner] Despawn Point not assigned! Please assign a Transform in the Inspector.");
|
||||
}
|
||||
|
||||
if (targetPrefabs == null || targetPrefabs.Length == 0)
|
||||
{
|
||||
Debug.LogError("[TargetSpawner] No target prefabs assigned! Please assign at least one prefab in the Inspector.");
|
||||
}
|
||||
|
||||
if (referenceMarker == null)
|
||||
{
|
||||
Debug.LogError("[TargetSpawner] ScreenReferenceMarker not assigned! Targets need this for EdgeAnchor positioning.");
|
||||
}
|
||||
|
||||
if (cameraAdapter == null)
|
||||
{
|
||||
Debug.LogWarning("[TargetSpawner] CameraScreenAdapter not assigned. EdgeAnchor will attempt to auto-find camera.");
|
||||
}
|
||||
|
||||
Debug.Log("[TargetSpawner] Initialized successfully");
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!isSpawning || settings == null)
|
||||
return;
|
||||
|
||||
spawnTimer += Time.deltaTime;
|
||||
|
||||
if (spawnTimer >= settings.TargetSpawnInterval)
|
||||
{
|
||||
SpawnTarget();
|
||||
spawnTimer = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns a random target prefab at the spawn point.
|
||||
/// Target is spawned at Y = 0, EdgeAnchor will position it vertically.
|
||||
/// </summary>
|
||||
private void SpawnTarget()
|
||||
{
|
||||
if (targetPrefabs == null || targetPrefabs.Length == 0 || spawnPoint == null)
|
||||
{
|
||||
Debug.LogError("[TargetSpawner] Cannot spawn target - missing prefabs or spawn point!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Randomly select target prefab
|
||||
GameObject prefab = targetPrefabs[Random.Range(0, targetPrefabs.Length)];
|
||||
|
||||
// Spawn at spawn point X, but Y = 0 (EdgeAnchor will position vertically)
|
||||
Vector3 spawnPosition = new Vector3(spawnPoint.position.x, 0f, 0f);
|
||||
GameObject targetObj = Instantiate(prefab, spawnPosition, Quaternion.identity);
|
||||
|
||||
// Initialize target
|
||||
Target target = targetObj.GetComponent<Target>();
|
||||
if (target != null)
|
||||
{
|
||||
float despawnX = despawnPoint != null ? despawnPoint.position.x : -12f;
|
||||
target.Initialize(despawnX, referenceMarker, cameraAdapter);
|
||||
|
||||
// Subscribe to target hit event to notify manager
|
||||
target.onTargetHit.AddListener(OnTargetHit);
|
||||
|
||||
Debug.Log($"[TargetSpawner] Spawned target at {spawnPosition}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"[TargetSpawner] Spawned prefab '{prefab.name}' does not have Target component!");
|
||||
Destroy(targetObj);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a target is hit by a projectile.
|
||||
/// Notifies the game manager for score tracking.
|
||||
/// </summary>
|
||||
private void OnTargetHit()
|
||||
{
|
||||
// Find and notify manager
|
||||
BirdPooperGameManager manager = BirdPooperGameManager.Instance;
|
||||
if (manager != null)
|
||||
{
|
||||
manager.OnTargetHit();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[TargetSpawner] BirdPooperGameManager not found!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start spawning targets at regular intervals.
|
||||
/// </summary>
|
||||
public void StartSpawning()
|
||||
{
|
||||
isSpawning = true;
|
||||
spawnTimer = 0f;
|
||||
Debug.Log("[TargetSpawner] Started spawning targets");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop spawning new targets (existing targets continue).
|
||||
/// </summary>
|
||||
public void StopSpawning()
|
||||
{
|
||||
isSpawning = false;
|
||||
Debug.Log("[TargetSpawner] Stopped spawning targets");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if spawner is currently spawning.
|
||||
/// </summary>
|
||||
public bool IsSpawning => isSpawning;
|
||||
|
||||
/// <summary>
|
||||
/// Draw gizmos to visualize spawn and despawn points in the editor.
|
||||
/// </summary>
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (spawnPoint != null)
|
||||
{
|
||||
Gizmos.color = Color.cyan;
|
||||
Gizmos.DrawLine(
|
||||
new Vector3(spawnPoint.position.x, -10f, 0f),
|
||||
new Vector3(spawnPoint.position.x, 10f, 0f)
|
||||
);
|
||||
}
|
||||
|
||||
if (despawnPoint != null)
|
||||
{
|
||||
Gizmos.color = Color.magenta;
|
||||
Gizmos.DrawLine(
|
||||
new Vector3(despawnPoint.position.x, -10f, 0f),
|
||||
new Vector3(despawnPoint.position.x, 10f, 0f)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16beae843b5f431f9256a56aab02b53d
|
||||
timeCreated: 1763713803
|
||||
Reference in New Issue
Block a user