BT5-Dev #40: ContactFilter2D mask 동적 갱신 (개발팀장 진단·KinematicObject raycast 정합)

PD 지시: 개발팀과 논의 후 보고
개발팀장 Opus 직접 진단:

근본 원인:
- KinematicObject.Start()에서 contactFilter.SetLayerMask() 한 번 캐싱
- 이후 Physics2D.IgnoreLayerCollision으로 Layer Matrix 토글해도 contactFilter는 갱신 X
- body.Cast() raycast 영역 contactFilter 활용 = Layer Matrix 영역 무관
- 본 PM 19회 시도 모두 raycast 영역 미적용 = 발판 충돌 그대로 감지

해결 (Unity Kinematic2D 표준 Drop-Through 패턴):
- PlayerController.UpdateContactFilterForDropThrough — velocity.y > 0 (상승) 영역 Layer 16 mask 비활성
- contactFilter.SetLayerMask() 매 프레임 동적 갱신 = raycast가 발판 영역 무시
- GameOptimizer Physics2D.IgnoreLayerCollision(13, 16, false) 라인 폐기 (Layer Matrix 항시 ON 유지)
- BT39 Coroutine 영역 폐기

본 PM 자인:
- KinematicObject body.Cast() vs Rigidbody2D OnCollisionEnter 별도 경로 미인지
- ContactFilter2D 캐싱 동작 미인지
- 19회 시도 모두 Rigidbody collision callback 영역 (raycast 영역 무관)

동작:
- 점프 상승 (velocity.y > 0.01) → contactFilter mask Layer 16 비트 제거 → raycast 발판 영역 무시 → 통과
- 하강·정지 → mask 복원 → raycast 발판 영역 감지 → 착지
This commit is contained in:
깃 관리자 2026-05-07 18:16:01 +09:00
parent 48f1084504
commit a6e0c0d56d
2 changed files with 14 additions and 14 deletions

View File

@ -60,10 +60,9 @@ namespace Platformer.Mechanics
applied++; applied++;
if (appliedNames.Count < 8) appliedNames.Add($"{c.gameObject.name}({c.GetType().Name})"); if (appliedNames.Count < 8) appliedNames.Add($"{c.gameObject.name}({c.GetType().Name})");
} }
Debug.Log($"[BT38-DropThrough] applied={applied} excluded={excluded} total={allColliders.Length}"); Debug.Log($"[BT40-DropThrough] applied={applied} excluded={excluded} total={allColliders.Length}");
Debug.Log($"[BT38-DropThrough] appliedSamples=[{string.Join(", ", appliedNames)}]"); Debug.Log($"[BT40-DropThrough] appliedSamples=[{string.Join(", ", appliedNames)}]");
// 기본 = Player(13) ↔ JumpThrough(16) 충돌 ON. PlayerController.Update에서 점프 시 동적 토글 // BT40 — Layer Matrix는 항시 ON. PlayerController.UpdateContactFilterForDropThrough에서 raycast contactFilter mask 동적 갱신
Physics2D.IgnoreLayerCollision(13, 16, false);
} }
} }
} }

View File

@ -157,9 +157,9 @@ namespace Platformer.Mechanics
if (IsGrounded) LastGroundedPosition = transform.position; if (IsGrounded) LastGroundedPosition = transform.position;
} }
// BT5-Dev #39 — 점프 시작 시 Coroutine으로 0.3초 동안 Layer 16 통과 유지 (Physics step 지연 차단) // BT5-Dev #40 — 개발팀장 진단: KinematicObject.Start() contactFilter 캐싱 우회
// Physics2D.IgnoreLayerCollision은 raycast contactFilter 영역 무관. SetLayerMask 직접 갱신 의무.
const int JUMP_THROUGH_LAYER = 16; const int JUMP_THROUGH_LAYER = 16;
bool _jumpThroughActive;
void UpdateJumpState() void UpdateJumpState()
{ {
@ -170,8 +170,6 @@ namespace Platformer.Mechanics
jumpState = JumpState.Jumping; jumpState = JumpState.Jumping;
jump = true; jump = true;
stopJump = false; stopJump = false;
// BT39 — 점프 시작 시 즉시 IgnoreLayerCollision 활성 + Coroutine으로 0.3초 유지
if (!_jumpThroughActive) StartCoroutine(JumpThroughRoutine());
break; break;
case JumpState.Jumping: case JumpState.Jumping:
if (!IsGrounded) if (!IsGrounded)
@ -191,15 +189,18 @@ namespace Platformer.Mechanics
jumpState = JumpState.Grounded; jumpState = JumpState.Grounded;
break; break;
} }
// BT40 — Drop-Through: velocity.y > 0(상승) 영역 Layer 16 mask 비활성, 그 외 활성
UpdateContactFilterForDropThrough();
} }
System.Collections.IEnumerator JumpThroughRoutine() void UpdateContactFilterForDropThrough()
{ {
_jumpThroughActive = true; int baseMask = Physics2D.GetLayerCollisionMask(gameObject.layer);
Physics2D.IgnoreLayerCollision(13, JUMP_THROUGH_LAYER, true); bool ascending = velocity.y > 0.01f;
yield return new WaitForSeconds(0.3f); int mask = ascending ? (baseMask & ~(1 << JUMP_THROUGH_LAYER)) : baseMask;
Physics2D.IgnoreLayerCollision(13, JUMP_THROUGH_LAYER, false); contactFilter.SetLayerMask(mask);
_jumpThroughActive = false; contactFilter.useLayerMask = true;
} }
protected override void ComputeVelocity() protected override void ComputeVelocity()