237 lines
10 KiB
C#
237 lines
10 KiB
C#
|
|
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);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|