92 lines
4.3 KiB
C#
92 lines
4.3 KiB
C#
using System.Text;
|
|
using UnityEditor;
|
|
using UnityEditor.SceneManagement;
|
|
using UnityEngine;
|
|
using WL.Player;
|
|
|
|
namespace WL.EditorTools
|
|
{
|
|
/// <summary>
|
|
/// 카메라 충돌 회피에서 제외할 오브젝트(나무 등)를 전용 레이어 "CameraIgnore" 로 옮기고,
|
|
/// FollowCamera.obstacleMask 에서 그 레이어를 뺀다 (PD #736 — 특정 지역에서 카메라가 멋대로 줌인).
|
|
/// 실측: 플레이어 주변 48m 격자 255점 중 88점에서 카메라 캐스트가 Tree02 크라운 BoxCollider 에 걸려 카메라가 0.2~6m 로 당겨졌다.
|
|
/// 나무는 캐릭터 물리 충돌엔 그대로 참여한다(레이어 충돌 행렬은 기본값 = 전부 충돌).
|
|
/// </summary>
|
|
public static class CameraObstacleLayerSetup
|
|
{
|
|
public const string LayerName = "CameraIgnore";
|
|
/// <summary>이 접두어로 시작하는 오브젝트(자식 콜라이더 포함)를 카메라 통과 대상으로 본다.</summary>
|
|
public static readonly string[] IgnorePrefixes = { "Tree" };
|
|
|
|
[MenuItem("WL/카메라 - 나무를 카메라 회피에서 제외 (CameraIgnore 레이어) (PD #736)")]
|
|
public static void MenuApply() { Debug.Log(Apply()); }
|
|
|
|
public static string Apply()
|
|
{
|
|
var sb = new StringBuilder();
|
|
int layer = EnsureLayer(LayerName, sb);
|
|
if (layer < 0) return sb.ToString();
|
|
|
|
int moved = 0, already = 0;
|
|
foreach (var col in Object.FindObjectsByType<Collider>(FindObjectsInactive.Include, FindObjectsSortMode.None))
|
|
{
|
|
if (!MatchesPrefix(col.transform)) continue;
|
|
if (col.gameObject.layer == layer) { already++; continue; }
|
|
Undo.RecordObject(col.gameObject, "camera ignore layer");
|
|
col.gameObject.layer = layer;
|
|
EditorUtility.SetDirty(col.gameObject);
|
|
moved++;
|
|
}
|
|
sb.AppendLine("레이어 이동: " + moved + "개 (이미 적용 " + already + "개) → " + LayerName + "(" + layer + ")");
|
|
|
|
var fc = Object.FindFirstObjectByType<FollowCamera>();
|
|
if (fc != null)
|
|
{
|
|
var so = new SerializedObject(fc);
|
|
var prop = so.FindProperty("obstacleMask");
|
|
int mask = prop.intValue & ~(1 << layer);
|
|
prop.intValue = mask;
|
|
so.ApplyModifiedPropertiesWithoutUndo();
|
|
sb.AppendLine("FollowCamera.obstacleMask = " + mask + " (CameraIgnore 제외)");
|
|
EditorSceneManager.MarkSceneDirty(fc.gameObject.scene);
|
|
EditorSceneManager.SaveScene(fc.gameObject.scene);
|
|
sb.AppendLine("씬 저장: " + fc.gameObject.scene.name);
|
|
}
|
|
else sb.AppendLine("FollowCamera 없음");
|
|
AssetDatabase.SaveAssets();
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static bool MatchesPrefix(Transform t)
|
|
{
|
|
// 자기 이름 또는 부모 이름이 접두어로 시작하면 대상 (콜라이더가 자식에 붙은 프리팹 대응)
|
|
for (var cur = t; cur != null; cur = cur.parent)
|
|
foreach (var p in IgnorePrefixes)
|
|
if (cur.name.StartsWith(p)) return true;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>TagManager 에 레이어가 없으면 빈 사용자 슬롯(8~31)에 만든다.</summary>
|
|
private static int EnsureLayer(string name, StringBuilder sb)
|
|
{
|
|
int existing = LayerMask.NameToLayer(name);
|
|
if (existing >= 0) return existing;
|
|
var tagManager = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset")[0]);
|
|
var layers = tagManager.FindProperty("layers");
|
|
for (int i = 8; i < layers.arraySize; i++)
|
|
{
|
|
var el = layers.GetArrayElementAtIndex(i);
|
|
if (string.IsNullOrEmpty(el.stringValue))
|
|
{
|
|
el.stringValue = name;
|
|
tagManager.ApplyModifiedProperties();
|
|
sb.AppendLine("레이어 생성: " + name + " = " + i);
|
|
return i;
|
|
}
|
|
}
|
|
sb.AppendLine("빈 레이어 슬롯이 없다");
|
|
return -1;
|
|
}
|
|
}
|
|
}
|