BT5-Dev #27: Layer 8(JumpThrough) + Raycast 동적 IgnoreCollision (PD 제안)

PD 제안 채택 — PlatformEffector2D 폐기 + 표준 Drop-Through Platform 패턴:
- Player(13) ↔ JumpThrough(8) 기본 통과 (Physics2D.IgnoreLayerCollision)
- Player 발 Raycast (Layer 8 mask, 0.1m) + 하강 시 → IgnoreCollision(false) 임시 활성 = 착지
- 상승 시 → IgnoreCollision(true) 복구 = 통과
- 발판 떠남 → IgnoreCollision(true) 복구

변경:
- GameOptimizer: SetupOneWayPlatforms → SetupJumpThroughPlatforms (Layer 8 변환·PlatformEffector2D 잔존 제거)
- PlatformDropThrough.cs (신설): 발 Raycast + 동적 IgnoreCollision
- PlayerController.Awake: PlatformDropThrough 자동 부착
This commit is contained in:
깃 관리자 2026-05-07 17:04:25 +09:00
parent ef97fb4291
commit 98bfbd74a2
3 changed files with 97 additions and 22 deletions

View File

@ -16,30 +16,29 @@ namespace Platformer.Mechanics
QualitySettings.vSyncCount = 0;
Time.fixedDeltaTime = 1f / 60f;
// BT5-Dev #22 — Layer 영역 분리: Player(13) ↔ Enemy(14) 충돌 영구 OFF.
// IgnoreCollision 영역 instance 의존 X = 모든 Player·Enemy 영역 자동 통과 영역.
// BT5-Dev #22 — Player(13) ↔ Enemy(14) 충돌 영구 OFF
Physics2D.IgnoreLayerCollision(13, 14, true);
Debug.Log($"[BT22-LayerSep] Player(13) ↔ Enemy(14) collision OFF");
// BT5-Dev #27 — Player(13) ↔ JumpThrough(8) 충돌 기본 OFF (Raycast 시점만 동적 활성)
Physics2D.IgnoreLayerCollision(13, 8, true);
Debug.Log($"[BT27-LayerSep] Player(13) ↔ Enemy(14)·JumpThrough(8) collision OFF");
}
/// <summary>
/// PD 지시 2026-05-07 — 모든 지형 Collider2D를 One-Way Platform으로 자동 변환.
/// 위에서만 착지·측면·아래 통과. Player·Enemy·DeathZone·VictoryZone·TokenInstance·Trigger Collider 영역 제외.
/// BT5-Dev #27 — PD 제안: Layer 8(JumpThrough) + Raycast 동적 충돌.
/// PlatformEffector2D 폐기. 모든 일반 BoxCollider2D를 Layer 8로 변환 → 기본 통과 + Player 발 Raycast 시점만 충돌 활성.
/// 제외: Tilemap·Player·Enemy·DeathZone·VictoryZone·TokenInstance·AttackHitbox·Trigger Collider.
/// </summary>
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static void SetupOneWayPlatforms()
static void SetupJumpThroughPlatforms()
{
// BT5-Dev #25 — Tilemap 영역 OneWay 영역 폐기 (모든 Tile 영역 OneWay = 일반 지면 영역도 영역 = 게임 영역 깨짐)
// 별개 BoxCollider2D 영역 영역 영역 영역 영역 영역 발판 영역 영역 OneWay 영역 영역 적용
int applied = 0;
int excludedTrigger = 0, excludedActor = 0, excludedTilemap = 0;
int applied = 0, excluded = 0;
var allColliders = Object.FindObjectsByType<Collider2D>(FindObjectsSortMode.None);
var appliedNames = new System.Collections.Generic.List<string>();
foreach (var c in allColliders)
{
if (c == null) continue;
if (c.isTrigger) { excludedTrigger++; continue; }
if (c.GetComponent<UnityEngine.Tilemaps.TilemapCollider2D>() != null) { excludedTilemap++; continue; } // BT25 — Tilemap 영역 영역 영역 일반 충돌 영역
if (c.isTrigger) { excluded++; continue; }
if (c.GetComponent<UnityEngine.Tilemaps.TilemapCollider2D>() != null) { excluded++; continue; }
if (c.GetComponent<PlayerController>() != null
|| c.GetComponent<EnemyController>() != null
|| c.GetComponent<DeathZone>() != null
@ -47,23 +46,22 @@ namespace Platformer.Mechanics
|| c.GetComponent<VictoryZone>() != null
|| c.GetComponent<AttackHitbox>() != null)
{
excludedActor++;
excluded++;
continue;
}
c.usedByEffector = true;
// PlatformEffector2D 잔존 영역 제거 (BT25 누적 영향 차단)
var effector = c.GetComponent<PlatformEffector2D>();
if (effector == null) effector = c.gameObject.AddComponent<PlatformEffector2D>();
effector.useOneWay = true;
effector.surfaceArc = 180f;
effector.rotationalOffset = 0f;
effector.useSideFriction = false;
effector.useSideBounce = false;
if (effector != null) Object.Destroy(effector);
c.usedByEffector = false;
// Layer 8 변환 → IgnoreLayerCollision로 기본 통과
c.gameObject.layer = 8;
applied++;
if (appliedNames.Count < 8) appliedNames.Add($"{c.gameObject.name}({c.GetType().Name})");
}
Debug.Log($"[BT25-OneWay] applied={applied} trigger={excludedTrigger} tilemap={excludedTilemap} actor={excludedActor} total={allColliders.Length}");
Debug.Log($"[BT25-OneWay] appliedSamples=[{string.Join(", ", appliedNames)}]");
Debug.Log($"[BT27-JumpThrough] applied={applied} excluded={excluded} total={allColliders.Length}");
Debug.Log($"[BT27-JumpThrough] appliedSamples=[{string.Join(", ", appliedNames)}]");
}
}
}

View File

@ -0,0 +1,75 @@
using UnityEngine;
namespace Platformer.Mechanics
{
/// <summary>
/// BT5-Dev #27 — PD 제안 Layer + Raycast 동적 충돌.
/// Layer 8(JumpThrough) 발판은 기본 통과(IgnoreLayerCollision). Player 하강 + 발 Raycast 시점만 IgnoreCollision(false)로 충돌 활성 → 착지.
/// 점프(상승) 시 IgnoreCollision(true) 복구 → 다시 통과.
/// PlayerController.Awake에서 자동 부착.
/// </summary>
[RequireComponent(typeof(Collider2D))]
public class PlatformDropThrough : MonoBehaviour
{
const int JUMP_THROUGH_LAYER = 8;
[Tooltip("발 raycast 거리. 0.05~0.15 권장.")]
public float footRayDistance = 0.1f;
Collider2D _self;
KinematicObject _ko;
Collider2D _activePlatform;
int _jumpThroughMask;
void Awake()
{
_self = GetComponent<Collider2D>();
_ko = GetComponent<KinematicObject>();
_jumpThroughMask = 1 << JUMP_THROUGH_LAYER;
}
void Update()
{
if (_self == null || _ko == null) return;
// Player 발 위치 (collider 하단 중심)
Vector2 footPos = new Vector2(_self.bounds.center.x, _self.bounds.min.y + 0.02f);
// Layer 8 발판 raycast (footRayDistance 영역만 검사)
RaycastHit2D hit = Physics2D.Raycast(footPos, Vector2.down, footRayDistance, _jumpThroughMask);
bool falling = _ko.velocity.y <= 0.01f; // 하강 또는 정지
bool rising = _ko.velocity.y > 0.5f; // 점프 상승
if (rising)
{
// 점프 → 활성 발판 통과 복구
if (_activePlatform != null)
{
Physics2D.IgnoreCollision(_self, _activePlatform, true);
_activePlatform = null;
}
}
else if (falling && hit.collider != null && hit.collider != _activePlatform)
{
// 하강 + 발판 raycast hit → 충돌 활성 (착지)
Physics2D.IgnoreCollision(_self, hit.collider, false);
_activePlatform = hit.collider;
}
else if (falling && hit.collider == null && _activePlatform != null)
{
// 발판 떠남 (낙하) → 활성 해제
Physics2D.IgnoreCollision(_self, _activePlatform, true);
_activePlatform = null;
}
}
void OnDrawGizmosSelected()
{
if (_self == null) return;
Vector2 footPos = new Vector2(_self.bounds.center.x, _self.bounds.min.y + 0.02f);
Gizmos.color = Color.cyan;
Gizmos.DrawLine(footPos, footPos + Vector2.down * footRayDistance);
}
}
}

View File

@ -82,6 +82,8 @@ namespace Platformer.Mechanics
// PD 지시 2026-05-07 — 동반 컴포넌트 자동 부착 (Inspector 부착 불요)
if (GetComponent<PlayerInvulnerabilityFlash>() == null) gameObject.AddComponent<PlayerInvulnerabilityFlash>();
if (GetComponent<Platformer.UI.ResurrectPromptUI>() == null) gameObject.AddComponent<Platformer.UI.ResurrectPromptUI>();
// BT5-Dev #27 — Layer 8 발판 동적 충돌 (PD 제안 Raycast 방식)
if (GetComponent<PlatformDropThrough>() == null) gameObject.AddComponent<PlatformDropThrough>();
// PD 지시 2026-05-07 — 사망 시 입력 차단 / 부활 시 입력 복원
if (health != null)