Project_WL/AgentScripts/WL814n_Build.cs

178 lines
9.2 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ─────────────────────────────────────────────────────────────────────────────
// 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);
}
}