Files
AppleHillsProduction/Assets/Editor/CustomEditorsAndDrawers/ObstacleEditor.cs

237 lines
10 KiB
C#
Raw Normal View History

using UnityEngine;
using UnityEditor;
using Minigames.BirdPooper;
namespace Editor.CustomEditorsAndDrawers
{
/// <summary>
/// Custom editor for Obstacle component.
/// Provides utility button to auto-setup PolygonCollider2D from object bounds.
/// </summary>
[CustomEditor(typeof(Obstacle))]
public class ObstacleEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
// Draw default inspector
DrawDefaultInspector();
EditorGUILayout.Space(10);
EditorGUILayout.LabelField("Collider Setup", EditorStyles.boldLabel);
Obstacle obstacle = (Obstacle)target;
// Count sprite renderers and colliders in hierarchy
SpriteRenderer[] allSpriteRenderers = obstacle.GetComponentsInChildren<SpriteRenderer>(true);
Collider2D[] allColliders = obstacle.GetComponentsInChildren<Collider2D>(true);
int enabledColliders = 0;
int disabledColliders = 0;
foreach (Collider2D col in allColliders)
{
if (col.enabled) enabledColliders++;
else disabledColliders++;
}
int renderersWithoutColliders = CountRenderersWithoutColliders(obstacle.transform);
EditorGUILayout.LabelField($"Sprite Renderers: {allSpriteRenderers.Length}", EditorStyles.miniLabel);
EditorGUILayout.LabelField($"Colliders (enabled): {enabledColliders}", EditorStyles.miniLabel);
if (disabledColliders > 0)
{
EditorGUILayout.LabelField($"Colliders (disabled): {disabledColliders} - will be left alone", EditorStyles.miniLabel);
}
if (renderersWithoutColliders > 0)
{
EditorGUILayout.LabelField($"Renderers missing colliders: {renderersWithoutColliders}", EditorStyles.miniLabel);
}
EditorGUILayout.Space(5);
// Button to auto-fit existing colliders
if (enabledColliders > 0)
{
if (GUILayout.Button("Auto Fit All Colliders to Bounds"))
{
AutoFitAllColliders(obstacle);
}
EditorGUILayout.HelpBox(
"Adjusts all ENABLED colliders to match their renderer bounds. Disabled colliders are left alone.",
MessageType.Info
);
EditorGUILayout.Space(5);
}
// Button to create missing colliders and auto-fit all
if (GUILayout.Button("Create & Adjust All Colliders"))
{
CreateAndAdjustAllColliders(obstacle);
}
EditorGUILayout.HelpBox(
"Creates PolygonCollider2D on SpriteRenderers missing colliders, then auto-fits ENABLED colliders to bounds. Disabled colliders are left alone.",
MessageType.Info
);
}
/// <summary>
/// Count how many SpriteRenderers don't have ANY collider (enabled or disabled) on the same GameObject.
/// </summary>
private int CountRenderersWithoutColliders(Transform root)
{
int count = 0;
SpriteRenderer spriteRenderer = root.GetComponent<SpriteRenderer>();
if (spriteRenderer != null && root.GetComponent<Collider2D>() == null)
{
count++;
}
foreach (Transform child in root)
{
count += CountRenderersWithoutColliders(child);
}
return count;
}
/// <summary>
/// Auto-fit all existing colliders recursively.
/// </summary>
private void AutoFitAllColliders(Obstacle obstacle)
{
int fittedCount = 0;
AutoFitCollidersRecursive(obstacle.transform, ref fittedCount);
Debug.Log($"[ObstacleEditor] Auto-fitted {fittedCount} collider(s) for '{obstacle.name}'");
EditorUtility.DisplayDialog(
"Colliders Adjusted",
$"Successfully adjusted {fittedCount} collider(s) to match their renderer bounds.",
"OK"
);
}
/// <summary>
/// Create missing colliders and auto-fit all colliders recursively.
/// </summary>
private void CreateAndAdjustAllColliders(Obstacle obstacle)
{
int createdCount = 0;
int fittedCount = 0;
CreateCollidersRecursive(obstacle.transform, ref createdCount);
AutoFitCollidersRecursive(obstacle.transform, ref fittedCount);
Debug.Log($"[ObstacleEditor] Created {createdCount} collider(s) and adjusted {fittedCount} collider(s) for '{obstacle.name}'");
EditorUtility.DisplayDialog(
"Colliders Created & Adjusted",
$"Created {createdCount} new collider(s) and adjusted {fittedCount} total collider(s).",
"OK"
);
}
/// <summary>
/// Recursively create PolygonCollider2D on GameObjects with Renderer but no Collider2D.
/// Skips GameObjects that have disabled colliders (leaves them alone).
/// </summary>
private void CreateCollidersRecursive(Transform current, ref int createdCount)
{
// Check if this GameObject has a SpriteRenderer
SpriteRenderer spriteRenderer = current.GetComponent<SpriteRenderer>();
if (spriteRenderer != null)
{
// Check if there's ANY collider (enabled or disabled)
Collider2D existingCollider = current.GetComponent<Collider2D>();
// Only create if there's NO collider at all
if (existingCollider == null)
{
Undo.AddComponent<PolygonCollider2D>(current.gameObject);
EditorUtility.SetDirty(current.gameObject);
createdCount++;
}
// If collider exists but is disabled, leave it alone (skip)
}
// Recurse to children
foreach (Transform child in current)
{
CreateCollidersRecursive(child, ref createdCount);
}
}
/// <summary>
/// Recursively auto-fit all ENABLED PolygonCollider2D components to their renderer bounds.
/// Skips disabled colliders (leaves them alone).
/// </summary>
private void AutoFitCollidersRecursive(Transform current, ref int fittedCount)
{
// Try to fit collider on this GameObject
PolygonCollider2D polyCollider = current.GetComponent<PolygonCollider2D>();
Renderer renderer = current.GetComponent<Renderer>();
// Only fit if collider exists, is ENABLED, and has a renderer
if (polyCollider != null && polyCollider.enabled && renderer != null)
{
FitColliderToBounds(current, polyCollider, renderer);
fittedCount++;
}
// If collider is disabled, skip it (leave it alone)
// Recurse to children
foreach (Transform child in current)
{
AutoFitCollidersRecursive(child, ref fittedCount);
}
}
/// <summary>
/// Fit a single PolygonCollider2D to its sprite bounds.
/// Uses the sprite's local bounds directly to properly handle scale.
/// </summary>
private void FitColliderToBounds(Transform targetTransform, PolygonCollider2D polyCollider, Renderer renderer)
{
// Try to get SpriteRenderer for direct sprite bounds access
SpriteRenderer spriteRenderer = renderer as SpriteRenderer;
if (spriteRenderer != null && spriteRenderer.sprite != null)
{
// Use sprite's local bounds directly - this is already in local space and respects scale
Bounds spriteBounds = spriteRenderer.sprite.bounds;
Vector3 center = spriteBounds.center;
Vector3 extents = spriteBounds.extents;
// Create rectangle points (already in local space)
Vector2[] points = new Vector2[4];
points[0] = new Vector2(center.x - extents.x, center.y - extents.y); // Bottom-left
points[1] = new Vector2(center.x + extents.x, center.y - extents.y); // Bottom-right
points[2] = new Vector2(center.x + extents.x, center.y + extents.y); // Top-right
points[3] = new Vector2(center.x - extents.x, center.y + extents.y); // Top-left
// Apply to collider
Undo.RecordObject(polyCollider, "Auto Fit Collider");
polyCollider.SetPath(0, points);
EditorUtility.SetDirty(polyCollider);
}
else
{
// Fallback to world space bounds if not a sprite renderer
Bounds bounds = renderer.bounds;
Vector3 center = targetTransform.InverseTransformPoint(bounds.center);
Vector3 extents = bounds.extents;
Vector2[] points = new Vector2[4];
points[0] = new Vector2(center.x - extents.x, center.y - extents.y);
points[1] = new Vector2(center.x + extents.x, center.y - extents.y);
points[2] = new Vector2(center.x + extents.x, center.y + extents.y);
points[3] = new Vector2(center.x - extents.x, center.y + extents.y);
Undo.RecordObject(polyCollider, "Auto Fit Collider");
polyCollider.SetPath(0, points);
EditorUtility.SetDirty(polyCollider);
}
}
}
}