Project_WL/AgentScripts/WL814n_Build.cs

178 lines
9.2 KiB
C#
Raw Permalink Normal View History

// ─────────────────────────────────────────────────────────────────────────────
// WL814n_Build.cs — `Assets/Res_Addr/PC/LH_M05.prefab` 조립 (에디터 전용 · 발주서 WL-814n §1-2)
//
// M05(Little Heroes RedKnight) 모델을 본체로 하고 `Ai01` 의 컴포넌트 구성·직렬화 값을 그대로 이식한다.
// 🔴 원본 `Ai01.prefab` 과 `Assets/Suriyun/**` 은 **읽기만** 한다(수정 0).
//
// 하는 일
// 1. M05 프리팹을 인스턴스화 → 언팩(원본 연결 끊기) → 이름 LH_M05
// 2. 레이어 3 전체 적용(Ai01 실측: 223 중 222 가 L3)
// 3. Animator: 아바타 = M05 자신(Humanoid) · 컨트롤러·applyRootMotion·cullingMode 는 Ai01 과 동일 배선
// (런타임에 `PCActor.Set_Obj()` 가 pcanim_* 로 갈아끼운다)
// 4. Ai01 루트의 WizardActor · NavMeshAgent · Rigidbody · CapsuleCollider · SkinnedMeshAfterImage 를
// ComponentUtility 로 값 복사
// 5. 무기 소켓 7개를 Ai01 과 **같은 본 경로**에 같은 로컬 TRS 로 생성 → WizardActor.tfs_weapon 재배선
// 6. WLPcScaleCompensator 부착(월드 높이 1.191 m 유지)
// 7. 팩 기본 장착 검 `Sword_M05` 비활성(게임의 Change_Weapon 이 소켓에 무기를 꽂는다)
// ─────────────────────────────────────────────────────────────────────────────
using System;
using System.Collections.Generic;
using System.Text;
using UnityEditor;
using UnityEngine;
using UnityEditorInternal;
using WL.Character;
public static class WL814n_Build
{
const string AI01 = "Assets/Res_Addr/PC/Ai01.prefab";
const string M05 = "Assets/Suriyun/Characters/RedKnight/Prefab/M05/M05.prefab";
const string OUT = "Assets/Res_Addr/PC/LH_M05.prefab";
// Ai01 실측(PROBE1): 소켓 이름 · 부모 본 경로(루트 제외) · 로컬 TRS
struct Socket { public string name, parentPath; public Vector3 pos, scale; public Quaternion rot; public int layer; }
static Transform FindPath(Transform root, string path)
{
var t = root;
foreach (var seg in path.Split('/'))
{
t = t.Find(seg);
if (t == null) return null;
}
return t;
}
static void SetLayerAll(GameObject go, int layer)
{
foreach (var t in go.GetComponentsInChildren<Transform>(true)) t.gameObject.layer = layer;
}
public static void Run()
{
var sb = new StringBuilder();
var ai01Pf = AssetDatabase.LoadAssetAtPath<GameObject>(AI01);
var m05Pf = AssetDatabase.LoadAssetAtPath<GameObject>(M05);
if (ai01Pf == null || m05Pf == null) { Debug.LogError("[WL-814n] 원본 프리팹을 찾지 못함"); return; }
// ── 1) Ai01 을 읽기 전용으로 인스턴스화해 소켓 정보를 실측 채집
var ai = (GameObject)PrefabUtility.InstantiatePrefab(ai01Pf);
ai.transform.localScale = Vector3.one;
var aiActor = ai.GetComponent<PCActor>();
var socks = new List<Socket>();
foreach (var tf in aiActor.tfs_weapon)
{
// 루트 이름을 뺀 부모 경로
var parts = new List<string>();
var p = tf.parent;
while (p != null && p != ai.transform) { parts.Insert(0, p.name); p = p.parent; }
socks.Add(new Socket
{
name = tf.name,
parentPath = string.Join("/", parts.ToArray()),
pos = tf.localPosition,
rot = tf.localRotation,
scale = tf.localScale,
layer = tf.gameObject.layer
});
}
sb.AppendLine("Ai01 소켓 채집 " + socks.Count + "/7");
// ── 2) M05 인스턴스화 + 언팩
var go = (GameObject)PrefabUtility.InstantiatePrefab(m05Pf);
PrefabUtility.UnpackPrefabInstance(go, PrefabUnpackMode.Completely, InteractionMode.AutomatedAction);
go.name = "LH_M05";
go.transform.position = Vector3.zero;
go.transform.rotation = Quaternion.identity;
go.transform.localScale = Vector3.one;
SetLayerAll(go, ai.layer); // Ai01 = Layer 3
go.tag = ai.tag; // Untagged
// ── 3) 팩 기본 장착 검 비활성(게임의 Change_Weapon 이 소켓에 꽂는다)
var packSword = FindPath(go.transform, "Root_M/RootPart1_M/Spine1_M/Chest_M/Scapula_R/Shoulder_R/Elbow_R/Wrist_R/Sword_parentR/Sword_M05");
if (packSword != null) { packSword.gameObject.SetActive(false); sb.AppendLine("팩 기본 검 Sword_M05 비활성"); }
// ── 4) 무기 소켓 7개를 같은 본 경로에 생성
var newSockets = new Transform[socks.Count];
for (int i = 0; i < socks.Count; i++)
{
var s = socks[i];
var parent = FindPath(go.transform, s.parentPath);
if (parent == null) { Debug.LogError("[WL-814n] M05 에 본 경로 없음: " + s.parentPath); sb.AppendLine("MISSING " + s.parentPath); continue; }
var n = new GameObject(s.name);
n.transform.SetParent(parent, false);
n.transform.localPosition = s.pos;
n.transform.localRotation = s.rot;
n.transform.localScale = s.scale;
n.layer = s.layer;
newSockets[i] = n.transform;
sb.AppendLine(string.Format("소켓 {0} {1} → {2}", i, s.name, s.parentPath));
}
// ── 5) Animator 를 Ai01 과 같은 배선으로 (아바타만 M05 자신 것)
var aiAnim = ai.GetComponent<Animator>();
var anim = go.GetComponent<Animator>();
anim.runtimeAnimatorController = aiAnim.runtimeAnimatorController; // 런타임에 pcanim_* 로 교체된다
anim.applyRootMotion = aiAnim.applyRootMotion; // false
anim.updateMode = aiAnim.updateMode;
anim.cullingMode = aiAnim.cullingMode;
sb.AppendLine("Animator avatar=" + anim.avatar.name + " isHuman=" + anim.avatar.isHuman
+ " ctrl=" + (anim.runtimeAnimatorController ? anim.runtimeAnimatorController.name : "null")
+ " rootMotion=" + anim.applyRootMotion);
// ── 6) Ai01 루트 컴포넌트 값 복사(Transform/Animator 제외)
foreach (var c in ai.GetComponents<Component>())
{
if (c is Transform || c is Animator) continue;
ComponentUtility.CopyComponent(c);
var existing = go.GetComponent(c.GetType());
if (existing != null) ComponentUtility.PasteComponentValues(existing);
else ComponentUtility.PasteComponentAsNew(go);
sb.AppendLine("컴포넌트 이식 " + c.GetType().Name);
}
// ── 7) tfs_weapon 재배선 (복사된 참조는 Ai01 것이므로 반드시 덮어쓴다)
var actor = go.GetComponent<PCActor>();
actor.tfs_weapon = newSockets;
int ok = 0;
foreach (var t in actor.tfs_weapon) if (t != null && t.IsChildOf(go.transform)) ok++;
sb.AppendLine("tfs_weapon 배선 " + ok + "/7 (전부 LH_M05 하위인가)");
// ── 8) 크기 보정 컴포넌트
var comp = go.GetComponent<WLPcScaleCompensator>();
if (comp == null) comp = go.AddComponent<WLPcScaleCompensator>();
float kComp = comp.Compensation;
sb.AppendLine("WLPcScaleCompensator 부착 (보정 " + kComp.ToString("F4") + ")");
// ── 8-b) 🔴 CapsuleCollider 재보정 (프로브 실측으로 발견)
// 콜라이더는 루트에 붙어 있어 루트 스케일(0.7 × k)을 그대로 받는다.
// Ai01 값을 그대로 쓰면 월드 크기가 k 배(=1.5배) 커진다 → 로컬 값을 k 로 나눠
// 월드 크기를 Ai01(h 1.106 · r 0.385 · centerY 0.553)과 **동일**하게 맞춘다.
// (NavMeshAgent 의 radius/height 는 트랜스폼 스케일을 받지 않으므로 Ai01 값 그대로 둔다.)
var aiCc = ai.GetComponent<CapsuleCollider>();
var cc = go.GetComponent<CapsuleCollider>();
if (cc != null && aiCc != null && kComp > 0f)
{
cc.radius = aiCc.radius / kComp;
cc.height = aiCc.height / kComp;
cc.center = aiCc.center / kComp;
sb.AppendLine(string.Format("CapsuleCollider 재보정 r {0:F4}→{1:F4} h {2:F4}→{3:F4} centerY {4:F4}→{5:F4} (월드는 Ai01 과 동일)",
aiCc.radius, cc.radius, aiCc.height, cc.height, aiCc.center.y, cc.center.y));
}
// ── 9) 저장
System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(OUT));
var saved = PrefabUtility.SaveAsPrefabAsset(go, OUT, out bool success);
sb.AppendLine("저장 " + OUT + " success=" + success);
UnityEngine.Object.DestroyImmediate(go);
UnityEngine.Object.DestroyImmediate(ai);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
System.IO.File.WriteAllText("AgentScripts/staging/WL814n/BUILD.txt", sb.ToString(), Encoding.UTF8);
Debug.Log("[WL-814n] BUILD\n" + sb);
}
}