Refactor : FootGroundingController를 Serializable 모듈로 전환
- FootGroundingController: MonoBehaviour → [Serializable] 클래스 - Start() → Initialize(TwoBoneIKSolver, Animator) - Update()/LateUpdate() → OnUpdate()/OnLateUpdate() - CRS에서 footGrounding 필드로 소유, Update/LateUpdate에서 호출 - CustomRetargetingScriptEditor: groundingSO 제거, serializedObject 경로로 접근 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
64a2069b69
commit
62a5a9bbb5
@ -98,6 +98,9 @@ namespace KindRetargeting
|
||||
[Header("어깨 보정")]
|
||||
[SerializeField] public ShoulderCorrectionFunction shoulderCorrection = new ShoulderCorrectionFunction();
|
||||
|
||||
[Header("발 접지")]
|
||||
[SerializeField] public FootGroundingController footGrounding = new FootGroundingController();
|
||||
|
||||
[Header("프랍 부착")]
|
||||
[SerializeField] public PropLocationController propLocation = new PropLocationController();
|
||||
|
||||
@ -328,6 +331,9 @@ namespace KindRetargeting
|
||||
if (targetAnimator != null)
|
||||
shoulderCorrection.Initialize(targetAnimator);
|
||||
|
||||
// 발 접지 모듈 초기화
|
||||
footGrounding.Initialize(ikSolver, targetAnimator);
|
||||
|
||||
// 프랍 부착 모듈 초기화
|
||||
if (targetAnimator != null)
|
||||
propLocation.Initialize(targetAnimator);
|
||||
@ -831,6 +837,9 @@ namespace KindRetargeting
|
||||
// 어깨 보정 (기존 ExecutionOrder 3)
|
||||
shoulderCorrection.OnUpdate();
|
||||
|
||||
// 발 접지 Pre-IK (기존 ExecutionOrder 5)
|
||||
footGrounding.OnUpdate();
|
||||
|
||||
// IK 솔버 (기존 ExecutionOrder 6)
|
||||
ikSolver.OnUpdate();
|
||||
|
||||
@ -847,6 +856,9 @@ namespace KindRetargeting
|
||||
/// </summary>
|
||||
void LateUpdate()
|
||||
{
|
||||
// 발 접지 Post-IK (기존 FootGroundingController LateUpdate)
|
||||
footGrounding.OnLateUpdate();
|
||||
|
||||
ApplyHeadRotationOffset();
|
||||
ApplyHeadScale();
|
||||
}
|
||||
|
||||
@ -10,8 +10,6 @@ namespace KindRetargeting
|
||||
{
|
||||
private const string CommonUssPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/StreamingleCommon.uss";
|
||||
|
||||
private SerializedObject groundingSO;
|
||||
|
||||
// SerializedProperty
|
||||
private SerializedProperty sourceAnimatorProp;
|
||||
private SerializedProperty targetAnimatorProp;
|
||||
@ -33,7 +31,7 @@ namespace KindRetargeting
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
groundingSO = null;
|
||||
// groundingSO 삭제됨 — footGrounding은 CRS 내부 모듈
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
@ -197,36 +195,27 @@ namespace KindRetargeting
|
||||
"• Toe Pivot: 발끝 고정 + 발목 회전 자동 감지",
|
||||
HelpBoxMessageType.Info));
|
||||
|
||||
// FootGroundingController의 SerializedObject를 직접 바인딩
|
||||
var script = (CustomRetargetingScript)target;
|
||||
var grounding = script.GetComponent<FootGroundingController>();
|
||||
if (grounding != null)
|
||||
{
|
||||
groundingSO = new SerializedObject(grounding);
|
||||
// FootGroundingController는 CRS 내부 모듈 — serializedObject의 프로퍼티 경로로 접근
|
||||
var groundHeightField = new PropertyField(serializedObject.FindProperty("footGrounding.groundHeight"), "바닥 높이");
|
||||
foldout.Add(groundHeightField);
|
||||
|
||||
var groundHeightField = new PropertyField(groundingSO.FindProperty("groundHeight"), "바닥 높이");
|
||||
foldout.Add(groundHeightField);
|
||||
var weightSlider = new Slider("접지 강도", 0f, 1f) { showInputField = true };
|
||||
weightSlider.BindProperty(serializedObject.FindProperty("footGrounding.groundingWeight"));
|
||||
foldout.Add(weightSlider);
|
||||
|
||||
var weightSlider = new Slider("접지 강도", 0f, 1f) { showInputField = true };
|
||||
weightSlider.BindProperty(groundingSO.FindProperty("groundingWeight"));
|
||||
foldout.Add(weightSlider);
|
||||
var activationField = new PropertyField(serializedObject.FindProperty("footGrounding.activationHeight"), "활성화 높이");
|
||||
activationField.tooltip = "발목이 이 높이 이상이면 접지 보정 비활성화 (점프 등)";
|
||||
foldout.Add(activationField);
|
||||
|
||||
var activationField = new PropertyField(groundingSO.FindProperty("activationHeight"), "활성화 높이");
|
||||
activationField.tooltip = "발목이 이 높이 이상이면 접지 보정 비활성화 (점프 등)";
|
||||
foldout.Add(activationField);
|
||||
var thresholdField = new PropertyField(serializedObject.FindProperty("footGrounding.plantThreshold"), "접지 판정 범위");
|
||||
thresholdField.tooltip = "Toes가 이 범위 안이면 접지 중으로 판정";
|
||||
foldout.Add(thresholdField);
|
||||
|
||||
var thresholdField = new PropertyField(groundingSO.FindProperty("plantThreshold"), "접지 판정 범위");
|
||||
thresholdField.tooltip = "Toes가 이 범위 안이면 접지 중으로 판정";
|
||||
foldout.Add(thresholdField);
|
||||
var smoothField = new PropertyField(serializedObject.FindProperty("footGrounding.smoothSpeed"), "보정 스무딩 속도");
|
||||
smoothField.tooltip = "보정량 변화 속도 (높을수록 빠른 반응, 낮으면 부드러운 전환)";
|
||||
foldout.Add(smoothField);
|
||||
|
||||
var smoothField = new PropertyField(groundingSO.FindProperty("smoothSpeed"), "보정 스무딩 속도");
|
||||
smoothField.tooltip = "보정량 변화 속도 (높을수록 빠른 반응, 낮으면 부드러운 전환)";
|
||||
foldout.Add(smoothField);
|
||||
|
||||
foldout.Add(new HelpBox("힙 높이 보정은 '바닥 높이 조정' 섹션의 floorHeight로 제어합니다.", HelpBoxMessageType.Info));
|
||||
|
||||
foldout.TrackSerializedObjectValue(groundingSO, so => so.ApplyModifiedProperties());
|
||||
}
|
||||
foldout.Add(new HelpBox("힙 높이 보정은 '바닥 높이 조정' 섹션의 floorHeight로 제어합니다.", HelpBoxMessageType.Info));
|
||||
else
|
||||
{
|
||||
foldout.Add(new HelpBox("FootGroundingController 컴포넌트를 찾을 수 없습니다.", HelpBoxMessageType.Warning));
|
||||
|
||||
@ -5,19 +5,14 @@ namespace KindRetargeting
|
||||
/// <summary>
|
||||
/// HIK 스타일 2-Pass 접지 시스템.
|
||||
///
|
||||
/// Pass 1 (Update, Order 5 → IK 전):
|
||||
/// Pass 1 (OnUpdate, Order 5 → IK 전):
|
||||
/// IK 타겟 위치를 수정하여 Toes가 바닥을 뚫지 않도록 보정.
|
||||
/// Toe Pivot 감지: 발끝이 바닥에 있고 발목이 올라가면
|
||||
/// 발목 타겟을 역산하여 Toes가 groundHeight에 고정.
|
||||
///
|
||||
/// Pass 2 (LateUpdate → IK 후):
|
||||
/// Pass 2 (OnLateUpdate → IK 후):
|
||||
/// IK 결과의 잔차를 Foot 회전으로 미세 보정.
|
||||
/// 위치 변경 없음 — 본 길이 보존.
|
||||
///
|
||||
/// 힙 높이 보정은 CRS의 floorHeight가 담당합니다 (이중 보정 방지).
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(5)]
|
||||
public class FootGroundingController : MonoBehaviour
|
||||
[System.Serializable]
|
||||
public class FootGroundingController
|
||||
{
|
||||
[Header("접지 설정")]
|
||||
[Tooltip("바닥 Y 좌표 (월드 공간)")]
|
||||
@ -41,38 +36,31 @@ namespace KindRetargeting
|
||||
private TwoBoneIKSolver ikSolver;
|
||||
private Animator animator;
|
||||
|
||||
// 타겟 아바타 캐싱
|
||||
private Transform leftFoot;
|
||||
private Transform rightFoot;
|
||||
private Transform leftToes;
|
||||
private Transform rightToes;
|
||||
|
||||
// Toes의 Foot 로컬 오프셋 (T-pose에서 캐싱)
|
||||
private Vector3 leftLocalToesOffset;
|
||||
private Vector3 rightLocalToesOffset;
|
||||
|
||||
// flat 상태에서 발목 최소 높이 (Foot.y - Toes.y)
|
||||
private float leftFootHeight;
|
||||
private float rightFootHeight;
|
||||
|
||||
// Toes 본 존재 여부
|
||||
private bool leftHasToes;
|
||||
private bool rightHasToes;
|
||||
|
||||
// 스무딩용: 이전 프레임 보정량
|
||||
private float leftPrevAdj;
|
||||
private float rightPrevAdj;
|
||||
|
||||
private bool isInitialized;
|
||||
|
||||
private void Start()
|
||||
public void Initialize(TwoBoneIKSolver ikSolver, Animator animator)
|
||||
{
|
||||
var crs = GetComponent<CustomRetargetingScript>();
|
||||
if (crs != null) ikSolver = crs.ikSolver;
|
||||
animator = GetComponent<Animator>();
|
||||
this.ikSolver = ikSolver;
|
||||
this.animator = animator;
|
||||
|
||||
if (animator == null || !animator.isHuman || ikSolver == null) return;
|
||||
if (leftFoot == null && rightFoot == null) return;
|
||||
|
||||
leftFoot = animator.GetBoneTransform(HumanBodyBones.LeftFoot);
|
||||
rightFoot = animator.GetBoneTransform(HumanBodyBones.RightFoot);
|
||||
@ -81,7 +69,6 @@ namespace KindRetargeting
|
||||
|
||||
if (leftFoot == null || rightFoot == null) return;
|
||||
|
||||
// Toes 존재 여부 + 캐싱
|
||||
leftHasToes = leftToes != null;
|
||||
rightHasToes = rightToes != null;
|
||||
|
||||
@ -92,7 +79,7 @@ namespace KindRetargeting
|
||||
}
|
||||
else
|
||||
{
|
||||
leftFootHeight = 0.05f; // Toes 없을 때 기본 발목 높이
|
||||
leftFootHeight = 0.05f;
|
||||
}
|
||||
|
||||
if (rightHasToes)
|
||||
@ -111,7 +98,7 @@ namespace KindRetargeting
|
||||
/// <summary>
|
||||
/// Pass 1: Pre-IK — IK 타겟 위치를 수정합니다.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
public void OnUpdate()
|
||||
{
|
||||
if (!isInitialized || groundingWeight < 0.001f) return;
|
||||
|
||||
@ -122,16 +109,11 @@ namespace KindRetargeting
|
||||
ikSolver.rightLeg, rightLocalToesOffset, rightFootHeight,
|
||||
rightHasToes, ikSolver.rightLeg.positionWeight);
|
||||
|
||||
// 스무딩: 보정량 급변 방지
|
||||
float dt = Time.deltaTime * smoothSpeed;
|
||||
leftPrevAdj = Mathf.Lerp(leftPrevAdj, leftAdj, Mathf.Clamp01(dt));
|
||||
rightPrevAdj = Mathf.Lerp(rightPrevAdj, rightAdj, Mathf.Clamp01(dt));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 발 IK 타겟을 접지 모드에 따라 보정합니다.
|
||||
/// Toes가 없는 아바타는 발목 Y 클램프만 수행합니다.
|
||||
/// </summary>
|
||||
private float AdjustFootTarget(TwoBoneIKSolver.LimbIK limb, Vector3 localToesOffset,
|
||||
float footHeight, bool hasToes, float ikWeight)
|
||||
{
|
||||
@ -141,12 +123,10 @@ namespace KindRetargeting
|
||||
Vector3 anklePos = ankleTarget.position;
|
||||
float ankleY = anklePos.y;
|
||||
|
||||
// AIRBORNE 체크
|
||||
if (ankleY - groundHeight > activationHeight) return 0f;
|
||||
|
||||
float weight = groundingWeight * ikWeight;
|
||||
|
||||
// === Toes 없는 아바타: 단순 Y 클램프 ===
|
||||
if (!hasToes)
|
||||
{
|
||||
float minAnkleY = groundHeight + footHeight;
|
||||
@ -160,7 +140,6 @@ namespace KindRetargeting
|
||||
return 0f;
|
||||
}
|
||||
|
||||
// === Toes 있는 아바타: 예측 기반 보정 ===
|
||||
Vector3 predictedToesWorld = anklePos + ankleTarget.rotation * localToesOffset;
|
||||
float predictedToesY = predictedToesWorld.y;
|
||||
|
||||
@ -172,7 +151,6 @@ namespace KindRetargeting
|
||||
|
||||
if (ankleY < groundHeight + footHeight + plantThreshold)
|
||||
{
|
||||
// PLANTED: 발 전체가 바닥 근처
|
||||
float minAnkleY = groundHeight + footHeight;
|
||||
if (ankleY < minAnkleY)
|
||||
{
|
||||
@ -189,7 +167,6 @@ namespace KindRetargeting
|
||||
}
|
||||
else
|
||||
{
|
||||
// TOE_PIVOT: 발끝 고정, 발목 올라감
|
||||
if (toesError > 0f)
|
||||
{
|
||||
adjustment = toesError * weight;
|
||||
@ -201,7 +178,6 @@ namespace KindRetargeting
|
||||
}
|
||||
else
|
||||
{
|
||||
// Toes 충분히 위 → 발목만 바닥 아래 방지
|
||||
float minAnkleY = groundHeight + footHeight;
|
||||
if (ankleY < minAnkleY)
|
||||
{
|
||||
@ -217,7 +193,7 @@ namespace KindRetargeting
|
||||
/// <summary>
|
||||
/// Pass 2: Post-IK — 잔차를 Foot 회전으로 미세 보정합니다.
|
||||
/// </summary>
|
||||
private void LateUpdate()
|
||||
public void OnLateUpdate()
|
||||
{
|
||||
if (!isInitialized || groundingWeight < 0.001f) return;
|
||||
|
||||
@ -227,10 +203,6 @@ namespace KindRetargeting
|
||||
AlignFootToGround(rightFoot, rightToes, ikSolver.rightLeg.positionWeight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IK 후 실제 Toes 위치를 확인하고, Foot 본을 pitch 회전하여 잔차 보정.
|
||||
/// 바닥 아래로 뚫린 경우만 보정합니다.
|
||||
/// </summary>
|
||||
private void AlignFootToGround(Transform foot, Transform toes, float ikWeight)
|
||||
{
|
||||
if (foot == null || toes == null) return;
|
||||
@ -245,7 +217,6 @@ namespace KindRetargeting
|
||||
|
||||
if (Mathf.Abs(error) < 0.001f) return;
|
||||
|
||||
// 바닥 아래로 뚫린 경우만 보정
|
||||
if (error > plantThreshold) return;
|
||||
|
||||
Vector3 footToToes = toes.position - foot.position;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user