[WL-814p] 치비 손에 맞게 무기 크기 자동 보정 (#814)
- Assets/WL/Character/WLWeaponFitSettings.cs/.asset (NEW · Resources/WL/) enabled_ 1 · targetRatio 0.60 · min/max 0.3/1.0 · perWeapon 빈 목록 · verboseLog 0 - Assets/WL/Character/WLWeaponFitter.cs (NEW · LH_M05 프리팹에만 부착) Make_Weapon 이 Addressables 비동기라 LateUpdate 로 소켓 7개를 스캔 · 새 인스턴스에만 1회 적용(멱등) 배수 = targetRatio × 캐릭터키 / 무기 월드 최대변 → min/max 클램프 - 원본 Assets/Script/** 0줄 · Core 0 · Ai01 0 · Suriyun 0 · 테이블 0 · Addressables 0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f9a04fe6c4
commit
46b0cd8a8c
|
|
@ -6189,6 +6189,7 @@ GameObject:
|
|||
- component: {fileID: 3657923760099891804}
|
||||
- component: {fileID: 1835281278008071509}
|
||||
- component: {fileID: 1155056168462878929}
|
||||
- component: {fileID: 8835979289890761207}
|
||||
m_Layer: 3
|
||||
m_Name: LH_M05
|
||||
m_TagString: Untagged
|
||||
|
|
@ -6400,6 +6401,20 @@ MonoBehaviour:
|
|||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::WL.Character.WLPcScaleCompensator
|
||||
overrideCompensation: 0
|
||||
--- !u!114 &8835979289890761207
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7522112115323654996}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1ce0821367b0b2d4d805337732041172, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::WL.Character.WLWeaponFitter
|
||||
overrideTargetRatio: 0
|
||||
sockets: []
|
||||
--- !u!1 &7572986597832592123
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// WLWeaponFitSettings.cs — 치비 PC 의 무기 크기 자동 보정 단일 출처(SOT · C45)
|
||||
//
|
||||
// 발주서 WL-814p (#814 잔여 결함) · 근거 = 완료보고 WL-814n §1 ⑰
|
||||
// 치비 교체 후 `Elven_Sword_01` 월드 최대변 1.341 m = 캐릭터 키(1.1912 m)의 **113 %**
|
||||
// → 어른 몸(Ai01 1.7017 m) 기준으로 만든 무기가 치비 손에 그대로 붙어 과대.
|
||||
//
|
||||
// ■ 어디를 고치나 — 원본 수정 0
|
||||
// 원본이 무기를 다는 지점은 `Assets/Script/Character/PCActor.cs:358`
|
||||
// Make_Weapon(fullpath, tfs_weapon[index], Vector3.zero, Vector3.one)
|
||||
// = Addressables 비동기 콜백에서 소켓에 **localScale 1** 로 붙인다(실측).
|
||||
// 814n 에서 원본 1줄 예외를 이미 소진했으므로 훅을 더 넣지 않는다 →
|
||||
// **새 프리팹 `LH_M05` 에만 붙는 `WLWeaponFitter`** 가 소켓을 감시해서 보정한다.
|
||||
// Ai01 경로에는 이 컴포넌트가 없으므로 구조적으로 영향 0.
|
||||
//
|
||||
// ■ 되돌리기(C8)
|
||||
// 이 에셋의 `enabled_` 를 끄거나 에셋을 지우면 배수 1.0000 = 컴포넌트 no-op = 지금 상태 100 %.
|
||||
//
|
||||
// 에셋 경로(고정): Assets/WL/Settings/Resources/WL/WLWeaponFitSettings.asset
|
||||
// 🔴 Assets/WL/Character/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일).
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WL.Character
|
||||
{
|
||||
[CreateAssetMenu(fileName = "WLWeaponFitSettings", menuName = "WL/Weapon Fit Settings", order = 332)]
|
||||
public sealed class WLWeaponFitSettings : ScriptableObject
|
||||
{
|
||||
public const string ResourcesPath = "WL/WLWeaponFitSettings";
|
||||
|
||||
static WLWeaponFitSettings s_cached;
|
||||
static bool s_lookupDone;
|
||||
|
||||
/// <summary>설정 에셋(1회 로드 후 캐시). 없으면 null = 무동작.</summary>
|
||||
public static WLWeaponFitSettings Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!s_lookupDone) { s_cached = Resources.Load<WLWeaponFitSettings>(ResourcesPath); s_lookupDone = true; }
|
||||
return s_cached;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>QA·A/B 전용 런타임 스위치. 0 = 에셋의 enabled_ · 1 = 강제 on · -1 = 강제 off.</summary>
|
||||
public static int RuntimeOverride;
|
||||
|
||||
/// <summary>보정이 살아 있는가. false 면 무기 스케일에 손대지 않는다(C8).</summary>
|
||||
public static bool Enabled
|
||||
{
|
||||
get
|
||||
{
|
||||
var i = Instance;
|
||||
if (i == null || RuntimeOverride < 0) return false;
|
||||
return RuntimeOverride > 0 || i.enabled_;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>테스트용: 캐시를 비운다.</summary>
|
||||
public static void ClearCache() { s_cached = null; s_lookupDone = false; }
|
||||
|
||||
[Serializable]
|
||||
public sealed class WeaponRatio
|
||||
{
|
||||
[Tooltip("무기 프리팹 이름(Assets/Res_Addr/Weapon/<이름>.prefab). \"(Clone)\" 은 무시하고 비교한다.")]
|
||||
public string weaponName;
|
||||
|
||||
[Tooltip("이 무기만 쓸 비율(무기 최대변 ÷ 캐릭터 키). 0 이하면 targetRatio 를 쓴다.")]
|
||||
public float ratio;
|
||||
}
|
||||
|
||||
[Header("전체 스위치 (PD 되돌리기 1순위)")]
|
||||
[Tooltip("끄면 무기 스케일에 아무것도 하지 않는다 = 지금 상태 100 %. 에셋을 지워도 같다.")]
|
||||
public bool enabled_ = true;
|
||||
|
||||
[Tooltip("보정이 일어날 때 Debug.Log 를 남긴다(A/B 확인용). 기본 끔.")]
|
||||
public bool verboseLog;
|
||||
|
||||
[Header("목표 비율")]
|
||||
[Tooltip("보정 후 무기 최대변 ÷ 캐릭터 키. 0.60 = 캐릭터 키의 60 %.\n" +
|
||||
"치비 교체 전 어른 Ai01(1.7017 m) 기준 Elven_Sword_01 은 0.79 였다(1.341/1.7017).")]
|
||||
public float targetRatio = 0.60f;
|
||||
|
||||
[Tooltip("보정 배수 하한(원본 크기 대비). 0.3 = 아무리 커도 30 % 미만으로는 줄이지 않는다.")]
|
||||
public float minScale = 0.3f;
|
||||
|
||||
[Tooltip("보정 배수 상한(원본 크기 대비). 1.0 = 무기를 키우지는 않는다.")]
|
||||
public float maxScale = 1.0f;
|
||||
|
||||
[Header("무기별 예외 (비어 있으면 전부 targetRatio)")]
|
||||
[Tooltip("이름 → 비율 override. 방패·오브처럼 길이 기준이 다른 무기를 따로 잡을 때만 쓴다.")]
|
||||
public List<WeaponRatio> perWeapon = new List<WeaponRatio>();
|
||||
|
||||
/// <summary>무기 이름에 맞는 목표 비율. 스위치가 꺼져 있으면 0(= 무보정).</summary>
|
||||
public static float ResolveRatio(string weaponName)
|
||||
{
|
||||
var i = Instance;
|
||||
if (!Enabled || i == null) return 0f;
|
||||
if (i.perWeapon != null && !string.IsNullOrEmpty(weaponName))
|
||||
{
|
||||
var key = Strip(weaponName);
|
||||
for (int n = 0; n < i.perWeapon.Count; n++)
|
||||
{
|
||||
var row = i.perWeapon[n];
|
||||
if (row == null || string.IsNullOrEmpty(row.weaponName) || row.ratio <= 0f) continue;
|
||||
if (string.Equals(Strip(row.weaponName), key, StringComparison.OrdinalIgnoreCase)) return row.ratio;
|
||||
}
|
||||
}
|
||||
return i.targetRatio > 0f ? i.targetRatio : 0f;
|
||||
}
|
||||
|
||||
/// <summary>"Elven_Sword_01(Clone)" → "Elven_Sword_01" (원본 `DSUtil.Get_Clone` 이 Instantiate 기본 이름을 쓴다).</summary>
|
||||
public static string Strip(string n)
|
||||
{
|
||||
if (string.IsNullOrEmpty(n)) return n;
|
||||
int i = n.IndexOf("(Clone)", StringComparison.Ordinal);
|
||||
return i >= 0 ? n.Substring(0, i).Trim() : n.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5f27385554e136541a8204a9809b8435
|
||||
|
|
@ -0,0 +1,240 @@
|
|||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// WLWeaponFitter.cs — 치비 PC 손에 맞게 무기 프리팹 크기를 자동 보정한다 (발주서 WL-814p)
|
||||
//
|
||||
// ■ 문제 (814n §1 ⑰ 실측)
|
||||
// 무기는 어른 몸(Ai01 키 1.7017 m · 인게임 1.1912 m)에 맞춰 만들어졌는데, 치비(M05)는
|
||||
// 키가 같아도 **팔이 짧고 손이 작다**. `Elven_Sword_01` 월드 최대변 1.341 m = 키의 113 %.
|
||||
//
|
||||
// ■ 왜 여기(프리팹 컴포넌트)인가 — 원본 수정 0
|
||||
// 무기 부착 지점은 `PCActor.cs:358` `Make_Weapon(fullpath, tfs_weapon[index], Vector3.zero, Vector3.one)`
|
||||
// = **Addressables 비동기 콜백**(`AddrResourceMgr.LoadObject<GameObject>(… handle => …)`)이라
|
||||
// 호출 프레임에 자식이 생기지 않는다. 814n 에서 원본 1줄 훅 예외를 이미 소진했으므로
|
||||
// 훅을 더 넣지 않고 **소켓을 감시**한다.
|
||||
//
|
||||
// ■ 생성 시점 감지 = LateUpdate 소켓 스캔 (근거)
|
||||
// ⓐ 비동기라 `Change_Weapon()` 직후 스캔은 항상 빈손이다 → 1회 스캔 불가.
|
||||
// ⓑ `Transform` 에는 자식 추가 콜백이 없다(`OnTransformChildrenChanged` 는 **부모 오브젝트**에
|
||||
// 스크립트가 붙어 있어야 하는데 소켓은 원본 본 계층이라 컴포넌트를 붙일 수 없다 = 원본 프리팹 수정).
|
||||
// ⓒ 그래서 루트(이 컴포넌트)가 `LateUpdate` 마다 소켓 7개의 자식을 훑는다.
|
||||
// 비용: 소켓 7 × 자식 0~1 = 프레임당 7~9 회 `childCount`/`GetChild` — 측정·보정은 **새 자식에만 1회**.
|
||||
// ⓓ `LateUpdate` 인 이유: 애니메이터가 본을 갱신한 뒤라 소켓의 월드 스케일(`lossyScale`)이 확정돼 있다.
|
||||
//
|
||||
// ■ 멱등 (같은 인스턴스 재보정 0)
|
||||
// 보정한 자식의 `GetInstanceID()` 를 기억한다. `Change_Weapon()` 이 `Destroy` 후 새로 만들면
|
||||
// 인스턴스 ID 가 달라지므로 **재장착 시에는 다시 계산**된다. 사라진 ID 는 매 스캔 정리(누수 0).
|
||||
//
|
||||
// ■ 크기 측정 = 포즈 무관 (측정 방식이 무기와 캐릭터가 서로 다르다 · 실측 근거)
|
||||
// · 무기 = `Renderer.localBounds`(= `mesh.bounds`) 를 **무기 루트 로컬 공간**으로 모아 재고 `lossyScale` 을 곱한다.
|
||||
// 월드 AABB 는 손이 기울면 값이 변하지만 이 방식은 **방향 맞춘 실치수**라 프레임마다 같다.
|
||||
// · 캐릭터 = `Renderer.bounds`(월드 AABB) ÷ 루트 `lossyScale.y`. 🔴 스킨드 메시의 `localBounds` 는
|
||||
// **루트 본 공간** 기준이라 `r.transform` 행렬로 환산하면 틀린다(LH_M05 실측 0.4635 m ≠ 실제 1.1912 m).
|
||||
// `bounds` 는 정적 바운즈를 루트 본으로 옮긴 것이라 **크기가 포즈에 안 변한다**.
|
||||
// · 트레일·파티클·라인 렌더러는 **크기 측정에서 제외**한다(이펙트가 길이를 부풀리면 무기가 과도하게 줄어든다).
|
||||
// · 배수는 (키 ÷ 무기길이) 비율이라 **루트 스케일에 불변** → 814n `WLPcScaleCompensator` 와 실행 순서가 엇갈려도 값이 같다.
|
||||
//
|
||||
// 🔴 이 컴포넌트는 새 프리팹 `LH_M05` 에만 붙는다 → `Ai01` 경로(스위치 off)는 구조적으로 영향 0.
|
||||
// 🔴 `WLWeaponFitSettings.enabled_ = 0` 이면 배수 1.0000 · 아무것도 건드리지 않는다(C8).
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WL.Character
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class WLWeaponFitter : MonoBehaviour
|
||||
{
|
||||
[Tooltip("0 보다 크면 설정 에셋의 targetRatio 대신 이 값을 쓴다(프리팹 단위 예외용).")]
|
||||
public float overrideTargetRatio;
|
||||
|
||||
[Tooltip("비워 두면 루트의 PCActor.tfs_weapon 7개를 자동으로 쓴다. 채우면 이 목록만 감시한다.")]
|
||||
public Transform[] sockets;
|
||||
|
||||
// 보정 완료한 무기 인스턴스 ID → 적용한 배수(멱등 · 재장착 시 ID 가 바뀌므로 자동 재계산)
|
||||
readonly Dictionary<int, float> _applied = new Dictionary<int, float>();
|
||||
readonly List<int> _live = new List<int>();
|
||||
readonly List<int> _dead = new List<int>();
|
||||
|
||||
Transform[] _cachedSockets;
|
||||
float _baseHeight = -1f; // 루트 로컬 공간에서 잰 캐릭터 키(스케일 1 기준) · 1회 측정
|
||||
|
||||
/// <summary>지금 캐릭터 키(월드 m). 루트 스케일이 바뀌면 같이 바뀐다.</summary>
|
||||
public float CharacterHeight
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_baseHeight <= 0f) _baseHeight = MeasureBodyLocalHeight();
|
||||
return _baseHeight * Mathf.Abs(transform.lossyScale.y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 몸 높이를 루트 스케일 1 기준으로 환산해 1회만 잰다. **소켓 아래(= 무기)는 제외**한다 —
|
||||
/// 무기가 먼저 붙은 프레임에 키를 재면 무기 길이만큼 키가 부풀어 배수가 틀어진다.
|
||||
///
|
||||
/// 🔴 캐릭터는 `Renderer.localBounds` 를 쓰면 안 된다 — 스킨드 메시의 로컬 바운즈는
|
||||
/// **루트 본 공간** 기준이라 `r.transform` 행렬로 환산하면 틀린다(LH_M05 실측 0.4635 m ≠ 1.1912 m).
|
||||
/// `Renderer.bounds`(월드)는 스킨드라도 정적 바운즈를 루트 본으로 옮긴 것이라 **크기가 포즈에 안 변한다**
|
||||
/// → 월드 AABB 높이를 루트 lossyScale 로 나눠 저장하면 로비(1.0)·인게임(0.7) 어디서도 같은 값이 나온다.
|
||||
/// </summary>
|
||||
float MeasureBodyLocalHeight()
|
||||
{
|
||||
var socks = ResolveSockets();
|
||||
var rends = GetComponentsInChildren<Renderer>(false);
|
||||
bool any = false; var b = new Bounds();
|
||||
for (int i = 0; i < rends.Length; i++)
|
||||
{
|
||||
var r = rends[i];
|
||||
if (r == null) continue;
|
||||
if (!(r is SkinnedMeshRenderer) && !(r is MeshRenderer)) continue;
|
||||
if (socks != null && IsUnder(r.transform, socks)) continue;
|
||||
if (!any) { b = r.bounds; any = true; } else b.Encapsulate(r.bounds);
|
||||
}
|
||||
if (!any) return 0f;
|
||||
float sy = Mathf.Abs(transform.lossyScale.y);
|
||||
return sy > 1e-5f ? b.size.y / sy : b.size.y;
|
||||
}
|
||||
|
||||
/// <summary>마지막 스캔에서 보정한 무기 수(프로브용).</summary>
|
||||
public int LastApplied { get; private set; }
|
||||
|
||||
void OnEnable() { _applied.Clear(); _baseHeight = -1f; }
|
||||
|
||||
void LateUpdate() { Scan(false); }
|
||||
|
||||
/// <summary>소켓을 훑어 새로 생긴 무기에만 보정을 1회 적용한다. 보정한 개수를 돌려준다.</summary>
|
||||
public int Scan(bool force)
|
||||
{
|
||||
LastApplied = 0;
|
||||
var socks = ResolveSockets();
|
||||
if (socks == null || socks.Length == 0) return 0;
|
||||
|
||||
// 🔴 스위치 off = 완전 무동작(기록조차 남기지 않는다 · C8 되돌리기 = 지금 상태 100 %)
|
||||
if (!WLWeaponFitSettings.Enabled) { if (_applied.Count > 0) _applied.Clear(); return 0; }
|
||||
_live.Clear();
|
||||
|
||||
for (int s = 0; s < socks.Length; s++)
|
||||
{
|
||||
var sock = socks[s];
|
||||
if (sock == null) continue;
|
||||
int cc = sock.childCount;
|
||||
for (int c = 0; c < cc; c++)
|
||||
{
|
||||
var w = sock.GetChild(c);
|
||||
int id = w.gameObject.GetInstanceID();
|
||||
_live.Add(id);
|
||||
if (!force && _applied.ContainsKey(id)) continue; // 이미 보정한 인스턴스 = 건너뜀(멱등)
|
||||
float k = Fit(w, sock);
|
||||
_applied[id] = k;
|
||||
if (k != 1f) LastApplied++;
|
||||
}
|
||||
}
|
||||
|
||||
// 사라진 무기 인스턴스 정리(누수 0)
|
||||
if (_applied.Count > _live.Count)
|
||||
{
|
||||
_dead.Clear();
|
||||
foreach (var kv in _applied) if (!_live.Contains(kv.Key)) _dead.Add(kv.Key);
|
||||
for (int i = 0; i < _dead.Count; i++) _applied.Remove(_dead[i]);
|
||||
}
|
||||
return LastApplied;
|
||||
}
|
||||
|
||||
/// <summary>무기 1개를 보정한다. 적용한 배수(1 = 무보정)를 돌려준다.</summary>
|
||||
public float Fit(Transform weapon, Transform socket)
|
||||
{
|
||||
var st = WLWeaponFitSettings.Instance;
|
||||
float ratio = overrideTargetRatio > 0f
|
||||
? (WLWeaponFitSettings.Enabled ? overrideTargetRatio : 0f)
|
||||
: WLWeaponFitSettings.ResolveRatio(weapon.name);
|
||||
if (ratio <= 0f) return 1f; // 스위치 off · 설정 없음 = 무동작
|
||||
|
||||
float charH = CharacterHeight;
|
||||
if (charH <= 0f) return 1f;
|
||||
|
||||
float cur = CurrentMaxDim(weapon);
|
||||
if (cur <= 1e-5f) return 1f; // 측정할 메시가 없다(이펙트 전용 등)
|
||||
|
||||
float k = (ratio * charH) / cur;
|
||||
float lo = st != null && st.minScale > 0f ? st.minScale : 0.3f;
|
||||
float hi = st != null && st.maxScale > 0f ? st.maxScale : 1f;
|
||||
k = Mathf.Clamp(k, lo, hi);
|
||||
if (Mathf.Abs(k - 1f) < 1e-4f) return 1f;
|
||||
|
||||
weapon.localScale = weapon.localScale * k;
|
||||
|
||||
if (st != null && st.verboseLog)
|
||||
Debug.Log(string.Format("[WL-814p] 무기 보정: {0} @{1} · {2:F4} m / 키 {3:F4} m = {4:F1} % → ×{5:F4} → {6:F4} m ({7:F1} %)",
|
||||
WLWeaponFitSettings.Strip(weapon.name), socket != null ? socket.name : "?",
|
||||
cur, charH, cur / charH * 100f, k, cur * k, cur * k / charH * 100f));
|
||||
return k;
|
||||
}
|
||||
|
||||
/// <summary>무기의 지금 월드 최대변(m · 포즈 무관 = 로컬 AABB × lossyScale).</summary>
|
||||
public float CurrentMaxDim(Transform weapon)
|
||||
{
|
||||
var sz = MeasureLocalSize(weapon);
|
||||
var ls = weapon.lossyScale;
|
||||
return Mathf.Max(sz.x * Mathf.Abs(ls.x), Mathf.Max(sz.y * Mathf.Abs(ls.y), sz.z * Mathf.Abs(ls.z)));
|
||||
}
|
||||
|
||||
Transform[] ResolveSockets()
|
||||
{
|
||||
if (sockets != null && sockets.Length > 0) return sockets;
|
||||
if (_cachedSockets != null && _cachedSockets.Length > 0) return _cachedSockets;
|
||||
var actor = GetComponent<PCActor>();
|
||||
if (actor != null && actor.tfs_weapon != null && actor.tfs_weapon.Length > 0) _cachedSockets = actor.tfs_weapon;
|
||||
return _cachedSockets;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 무기(<paramref name="root"/>) 로컬 공간에서 잰 메시 AABB 크기 = **방향 맞춘 실치수**(스케일 1 기준 · 포즈 무관).
|
||||
/// 월드 AABB 는 손이 기울면 값이 변해 프레임마다 달라지므로 쓰지 않는다.
|
||||
/// 트레일·파티클·라인 렌더러는 제외한다(이펙트가 길이를 부풀리면 무기가 과도하게 줄어든다).
|
||||
/// 무기는 전부 `MeshRenderer` 라 `localBounds`(= `mesh.bounds`) × `r.transform` 행렬이 정확하다(실측).
|
||||
/// </summary>
|
||||
public static Vector3 MeasureLocalSize(Transform root)
|
||||
{
|
||||
var rends = root.GetComponentsInChildren<Renderer>(false);
|
||||
bool any = false;
|
||||
Vector3 min = Vector3.zero, max = Vector3.zero;
|
||||
var w2l = root.worldToLocalMatrix;
|
||||
|
||||
for (int i = 0; i < rends.Length; i++)
|
||||
{
|
||||
var r = rends[i];
|
||||
if (r == null) continue;
|
||||
if (!(r is SkinnedMeshRenderer) && !(r is MeshRenderer)) continue; // ParticleSystem/Trail/Line 제외
|
||||
|
||||
var b = r.localBounds; // 렌더러 로컬 공간 · 포즈 무관
|
||||
var m = w2l * r.localToWorldMatrix;
|
||||
var c = b.center; var e = b.extents;
|
||||
for (int k = 0; k < 8; k++)
|
||||
{
|
||||
var p = m.MultiplyPoint3x4(new Vector3(
|
||||
c.x + ((k & 1) == 0 ? -e.x : e.x),
|
||||
c.y + ((k & 2) == 0 ? -e.y : e.y),
|
||||
c.z + ((k & 4) == 0 ? -e.z : e.z)));
|
||||
if (!any) { min = max = p; any = true; }
|
||||
else
|
||||
{
|
||||
min = Vector3.Min(min, p);
|
||||
max = Vector3.Max(max, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
return any ? (max - min) : Vector3.zero;
|
||||
}
|
||||
|
||||
static bool IsUnder(Transform t, Transform[] parents)
|
||||
{
|
||||
for (int i = 0; i < parents.Length; i++)
|
||||
{
|
||||
var p = parents[i];
|
||||
if (p == null) continue;
|
||||
if (t == p || t.IsChildOf(p)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1ce0821367b0b2d4d805337732041172
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 5f27385554e136541a8204a9809b8435, type: 3}
|
||||
m_Name: WLWeaponFitSettings
|
||||
m_EditorClassIdentifier: Assembly-CSharp::WL.Character.WLWeaponFitSettings
|
||||
enabled_: 1
|
||||
verboseLog: 0
|
||||
targetRatio: 0.6
|
||||
minScale: 0.3
|
||||
maxScale: 1
|
||||
perWeapon: []
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a67110aa61f25e14196931ef77889cba
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Reference in New Issue