2026-05-19 충돌처리 오류
This commit is contained in:
BIN
Assets/01_Scenes/GameScene.unity
LFS
BIN
Assets/01_Scenes/GameScene.unity
LFS
Binary file not shown.
@@ -1,13 +1,14 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Serialization;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// PlayerController
|
// PlayerController
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// 플레이어 캐릭터의 모든 동작을 관리하는 중심 컨트롤러.
|
// 플레이어 캐릭터의 모든 동작을 관리하는 중심 컨트롤러.
|
||||||
// - Kinematic Rigidbody2D 기반 (중력/충돌 모두 코드에서 직접 처리)
|
// - Kinematic Rigidbody2D 기반 (중력/충돌 모두 코드에서 직접 처리)
|
||||||
// - 이동, 점프(2단), 벽 슬라이드/점프, 그라운드 파운드
|
// - 이동, 점프(2단), 그라운드 파운드
|
||||||
// - 콤보 공격 (Punch/Kick/Grab) — ActionData + ComboNode 그래프로 정의
|
// - 콤보 공격 (Punch/Kick/Grab) — ActionData + ComboNode 그래프로 정의
|
||||||
// - 모션 액션 (Dash/Roll/BackDash) — 단발 실행, 자체 쿨다운
|
// - 모션 액션 (Dash/Roll/BackDash) — 단발 실행, 자체 쿨다운
|
||||||
// - 입력 버퍼링: 쿨다운 중에도 다음 콤보 입력 받아서 자동 실행
|
// - 입력 버퍼링: 쿨다운 중에도 다음 콤보 입력 받아서 자동 실행
|
||||||
@@ -39,24 +40,13 @@ public class PlayerController : MonoBehaviour,IDamageable
|
|||||||
[Header("Jump")]
|
[Header("Jump")]
|
||||||
[SerializeField] private float _jumpForce = 8f; // 점프 시 vy 값
|
[SerializeField] private float _jumpForce = 8f; // 점프 시 vy 값
|
||||||
[SerializeField] private int _maxJumpCount = 2; // 최대 점프 횟수 (지상 + 공중 점프 포함)
|
[SerializeField] private int _maxJumpCount = 2; // 최대 점프 횟수 (지상 + 공중 점프 포함)
|
||||||
[SerializeField] private Transform _groundCheck; // 발 밑 그라운드 감지용 빈 오브젝트
|
[SerializeField] private float _groundCheckDistance = 0.1f; // GroundCheck 콜라이더 하향 캐스트 거리
|
||||||
[SerializeField] private float _groundCheckRadius = 0.1f; // 감지 반경
|
[SerializeField] private float _groundCheckRayInset = 0.02f; // 벽 옆면 접촉을 피하기 위한 좌우 안쪽 여백
|
||||||
|
[SerializeField, Range(0f, 1f)] private float _groundNormalMinY = 0.65f; // 이 값 이상 위를 보는 면만 지면으로 인정
|
||||||
[SerializeField] private LayerMask _groundLayer; // 지면/벽으로 취급할 레이어
|
[SerializeField] private LayerMask _groundLayer; // 지면/벽으로 취급할 레이어
|
||||||
private bool _isGrounded; // 현재 지면 접촉 여부
|
private bool _isGrounded; // 현재 지면 접촉 여부
|
||||||
private int _jumpsUsed; // 이번 공중 체류 동안 사용한 점프 수
|
private int _jumpsUsed; // 이번 공중 체류 동안 사용한 점프 수
|
||||||
|
|
||||||
// ─── 벽 슬라이드 / 벽 점프 ───────────────────────────────────────────
|
|
||||||
[Header("WallSlide")]
|
|
||||||
[SerializeField] private Transform _wallCheckLeft; // 좌측 벽 감지 위치
|
|
||||||
[SerializeField] private Transform _wallCheckRight; // 우측 벽 감지 위치
|
|
||||||
[SerializeField] private float _wallCheckRadius = 0.1f;
|
|
||||||
[SerializeField] private float _wallSlideSpeed = 2f; // 벽 슬라이드 시 낙하 속도 클램프
|
|
||||||
[SerializeField] private Vector2 _wallJumpForce = new Vector2(4f, 5f); // 벽 점프 시 (반대방향X, +Y) 속도
|
|
||||||
[SerializeField] private float _wallJumpInputLockDuration = 0.15f; // 벽 점프 후 좌우 입력 잠금 시간
|
|
||||||
private bool _isTouchingLeftWall;
|
|
||||||
private bool _isTouchingRightWall;
|
|
||||||
private bool IsTouchingWall => _isTouchingLeftWall || _isTouchingRightWall;
|
|
||||||
private int _wallDirection; // 닿은 벽 방향 (-1 왼쪽, +1 오른쪽, 0 없음)
|
|
||||||
private float _inputLockTimer; // 이동 입력 잠금 타이머
|
private float _inputLockTimer; // 이동 입력 잠금 타이머
|
||||||
private float _facingLockTimer; // 페이싱 잠금 타이머
|
private float _facingLockTimer; // 페이싱 잠금 타이머
|
||||||
private ActionData _movementLockAction; // 애니메이션 기반 잠금 시 참조 액션
|
private ActionData _movementLockAction; // 애니메이션 기반 잠금 시 참조 액션
|
||||||
@@ -84,7 +74,12 @@ public class PlayerController : MonoBehaviour,IDamageable
|
|||||||
[Header("Kinematic Physics")]
|
[Header("Kinematic Physics")]
|
||||||
[SerializeField] private float _gravity = -25f; // 중력 가속도 (units/sec^2, 음수)
|
[SerializeField] private float _gravity = -25f; // 중력 가속도 (units/sec^2, 음수)
|
||||||
[SerializeField] private float _maxFallSpeed = 20f; // 낙하 최대 속도 클램프
|
[SerializeField] private float _maxFallSpeed = 20f; // 낙하 최대 속도 클램프
|
||||||
[SerializeField] private float _skinWidth = 0.02f; // 충돌 캐스트의 안전 마진
|
[SerializeField] private Collider2D _groundCheckCollider; // 지면 판정 전용 콜라이더 (trigger여도 됨)
|
||||||
|
|
||||||
|
[FormerlySerializedAs("_wallCheckCollider")]
|
||||||
|
[SerializeField] private Collider2D _bodyCollider;
|
||||||
|
[FormerlySerializedAs("_wallCheckDistance")]
|
||||||
|
[SerializeField] private float _collisionSkinWidth = 0.02f;
|
||||||
|
|
||||||
// ─── 공격 (펀치/킥/잡기 콤보 시스템) ──────────────────────────────────
|
// ─── 공격 (펀치/킥/잡기 콤보 시스템) ──────────────────────────────────
|
||||||
[Header("Attack")]
|
[Header("Attack")]
|
||||||
@@ -121,7 +116,6 @@ public class PlayerController : MonoBehaviour,IDamageable
|
|||||||
private bool _hitFired; // 현재 액션에서 hit이 이미 발화됐는지
|
private bool _hitFired; // 현재 액션에서 hit이 이미 발화됐는지
|
||||||
|
|
||||||
private readonly List<RaycastHit2D> _castResults = new(); // Cast 결과 버퍼 (GC 회피용)
|
private readonly List<RaycastHit2D> _castResults = new(); // Cast 결과 버퍼 (GC 회피용)
|
||||||
private Collider2D[] _bodyColliders; // 캐릭터 몸체 콜라이더들 (cast 대상)
|
|
||||||
private Rigidbody2D _rb;
|
private Rigidbody2D _rb;
|
||||||
private Animator _anim;
|
private Animator _anim;
|
||||||
private SpriteRenderer _spriteRenderer; // 좌우 반전 + flipX 페이싱용
|
private SpriteRenderer _spriteRenderer; // 좌우 반전 + flipX 페이싱용
|
||||||
@@ -132,14 +126,29 @@ private void Awake()
|
|||||||
_rb = GetComponent<Rigidbody2D>();
|
_rb = GetComponent<Rigidbody2D>();
|
||||||
_anim = GetComponent<Animator>();
|
_anim = GetComponent<Animator>();
|
||||||
_spriteRenderer = GetComponentInChildren<SpriteRenderer>();
|
_spriteRenderer = GetComponentInChildren<SpriteRenderer>();
|
||||||
_bodyColliders = GetComponentsInChildren<Collider2D>();
|
ResolveBodyColliderReference();
|
||||||
EnsureAttackHitbox();
|
EnsureAttackHitbox();
|
||||||
// 공격이 enemy를 hit하면 _lastHitEnemy를 기억 → 다음 잡기에서 그 enemy를 우선 타겟팅.
|
// 공격이 enemy를 hit하면 _lastHitEnemy를 기억 → 다음 잡기에서 그 enemy를 우선 타겟팅.
|
||||||
_attackHitbox.OnHit += OnAttackHit;
|
_attackHitbox.OnHit += OnAttackHit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// InputManager의 이벤트들에 액션별 핸들러를 구독.
|
private void ResolveBodyColliderReference()
|
||||||
// (Awake에서 안 하는 이유: InputManager.Instance가 자기 Awake에서 세팅되어 Start 시점에 보장됨)
|
{
|
||||||
|
if (_bodyCollider != null && _bodyCollider.enabled) return;
|
||||||
|
|
||||||
|
Collider2D[] colliders = GetComponentsInChildren<Collider2D>(true);
|
||||||
|
for (int i = 0; i < colliders.Length; i++)
|
||||||
|
{
|
||||||
|
Collider2D candidate = colliders[i];
|
||||||
|
if (candidate == null || !candidate.enabled || candidate.isTrigger) continue;
|
||||||
|
if (candidate == _groundCheckCollider) continue;
|
||||||
|
if (candidate.GetComponent<AttackHitbox>() != null) continue;
|
||||||
|
|
||||||
|
_bodyCollider = candidate;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
InputManager.Instance.OnMove_Event += OnMoveInput;
|
InputManager.Instance.OnMove_Event += OnMoveInput;
|
||||||
@@ -178,25 +187,18 @@ private void OnDestroy()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 매 물리 프레임의 메인 흐름:
|
// 매 물리 프레임의 메인 흐름:
|
||||||
// 1) 충돌 상태 갱신 (지면/좌우 벽)
|
// 1) 충돌 상태 갱신 (지면)
|
||||||
// 2) 점프 카운트 리셋 (지면 + 낙하 중일 때만)
|
// 2) 점프 카운트 리셋 (지면 + 낙하 중일 때만)
|
||||||
// 3) 쿨다운/콤보 윈도우/버퍼 처리
|
// 3) 쿨다운/콤보 윈도우/버퍼 처리
|
||||||
// 4) 좌우 이동 입력을 velocity로 반영 (잠금/액션 중일 땐 스킵)
|
// 4) 좌우 이동 입력을 velocity로 반영 (잠금/액션 중일 땐 스킵)
|
||||||
// 5) 페이싱 갱신
|
// 5) 페이싱 갱신
|
||||||
// 6) 자체 중력 적용
|
// 6) 자체 중력 적용
|
||||||
// 7) 벽 슬라이드 시 낙하 속도 클램프
|
// 7) Locomotion 애니메이션 갱신 (Idle/Walk/Jump/Land)
|
||||||
// 8) Cast 기반으로 땅/벽 침투 방지
|
|
||||||
// 9) Locomotion 애니메이션 갱신 (Idle/Walk/Jump/Land)
|
|
||||||
private void FixedUpdate()
|
private void FixedUpdate()
|
||||||
{
|
{
|
||||||
// 이동, 벽타기, 점프 판단이 모두 이 값을 쓰므로 충돌 체크를 먼저 갱신한다.
|
_isGrounded = CheckGrounded();
|
||||||
_isGrounded = Physics2D.OverlapCircle(_groundCheck.position, _groundCheckRadius, _groundLayer);
|
|
||||||
_isTouchingLeftWall = Physics2D.OverlapCircle(_wallCheckLeft.position, _wallCheckRadius, _groundLayer);
|
|
||||||
_isTouchingRightWall = Physics2D.OverlapCircle(_wallCheckRight.position, _wallCheckRadius, _groundLayer);
|
|
||||||
_wallDirection = _isTouchingRightWall ? 1 : (_isTouchingLeftWall ? -1 : 0);
|
|
||||||
|
|
||||||
// 지면에 닿고 점프 중이 아니면(상승 중 아님) 점프 카운트 리셋.
|
// 지면에 닿고 점프 중이 아니면(상승 중 아님) 점프 카운트 리셋.
|
||||||
// vy>0 체크 안 하면 점프 직후 같은 프레임에 _isGrounded=true라 카운트가 0으로 되돌아가 무한 점프 가능.
|
|
||||||
if (_isGrounded && _rb.linearVelocity.y <= 0f)
|
if (_isGrounded && _rb.linearVelocity.y <= 0f)
|
||||||
_jumpsUsed = 0;
|
_jumpsUsed = 0;
|
||||||
|
|
||||||
@@ -214,16 +216,12 @@ private void FixedUpdate()
|
|||||||
if (!IsFacingLocked() && !IsActionActive())
|
if (!IsFacingLocked() && !IsActionActive())
|
||||||
UpdateFacingFromMoveInput();
|
UpdateFacingFromMoveInput();
|
||||||
|
|
||||||
|
//중력적용
|
||||||
ApplyGravity();
|
ApplyGravity();
|
||||||
|
ResolveKinematicCollisions();
|
||||||
// 벽에 매달려 낙하 중일 때 vy를 -_wallSlideSpeed로 클램프.
|
|
||||||
if (IsTouchingWall && !_isGrounded && _rb.linearVelocity.y < -_wallSlideSpeed)
|
|
||||||
_rb.linearVelocity = new Vector2(_rb.linearVelocity.x, -_wallSlideSpeed);
|
|
||||||
|
|
||||||
// 플레이어와 적 몸체는 물리 충돌하지 않고, 땅/벽만 캐스트로 이동을 막는다.
|
|
||||||
ClampVelocityToGround();
|
|
||||||
|
|
||||||
UpdateLocomotionAnimation();
|
UpdateLocomotionAnimation();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 쿨다운 직전에 버퍼된 콤보 입력을, 쿨다운이 풀리는 순간 자동 발화.
|
// 쿨다운 직전에 버퍼된 콤보 입력을, 쿨다운이 풀리는 순간 자동 발화.
|
||||||
@@ -265,20 +263,15 @@ private void UpdateFacingFromMoveInput()
|
|||||||
_spriteRenderer.flipX = _moveInputX < 0f;
|
_spriteRenderer.flipX = _moveInputX < 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 점프 우선순위: 지상 점프 > 벽 점프 > 공중(2단) 점프
|
// 점프 우선순위: 지상 점프 > 공중(2단) 점프
|
||||||
private void OnJumpInput()
|
private void OnJumpInput()
|
||||||
{
|
{
|
||||||
|
Debug.Log($"점프입력 _isGrounded: {_isGrounded}");
|
||||||
|
|
||||||
if (_isGrounded)
|
if (_isGrounded)
|
||||||
{
|
{
|
||||||
PerformJump();
|
PerformJump();
|
||||||
}
|
}
|
||||||
else if (IsTouchingWall)
|
|
||||||
{
|
|
||||||
// 벽 반대 방향으로 튕겨나가는 벽 점프. 벽 점프는 _jumpsUsed 카운트 영향 없음.
|
|
||||||
_rb.linearVelocity = new Vector2(-_wallDirection * _wallJumpForce.x, _wallJumpForce.y);
|
|
||||||
// 잠시 좌우 입력을 잠가서 즉시 같은 벽으로 돌아붙는 걸 방지.
|
|
||||||
_inputLockTimer = _wallJumpInputLockDuration;
|
|
||||||
}
|
|
||||||
else if (_jumpsUsed < _maxJumpCount)
|
else if (_jumpsUsed < _maxJumpCount)
|
||||||
{
|
{
|
||||||
// 공중 점프 (2단/3단). _maxJumpCount=2면 지상점프 + 공중 1회 가능.
|
// 공중 점프 (2단/3단). _maxJumpCount=2면 지상점프 + 공중 1회 가능.
|
||||||
@@ -288,6 +281,7 @@ private void OnJumpInput()
|
|||||||
|
|
||||||
private void PerformJump()
|
private void PerformJump()
|
||||||
{
|
{
|
||||||
|
Debug.Log($"_jumpForce : {_jumpForce}, _jumpsUsed : {_jumpsUsed}");
|
||||||
_rb.linearVelocity = new Vector2(_rb.linearVelocity.x, _jumpForce);
|
_rb.linearVelocity = new Vector2(_rb.linearVelocity.x, _jumpForce);
|
||||||
_jumpsUsed++;
|
_jumpsUsed++;
|
||||||
}
|
}
|
||||||
@@ -1227,8 +1221,8 @@ private void ApplyGravity()
|
|||||||
|
|
||||||
if (_isGrounded && vy <= 0f)
|
if (_isGrounded && vy <= 0f)
|
||||||
{
|
{
|
||||||
if (vy != 0f)
|
float groundedFallSpeed = Mathf.Max(_gravity * Time.fixedDeltaTime, -_maxFallSpeed);
|
||||||
_rb.linearVelocity = new Vector2(_rb.linearVelocity.x, 0f);
|
_rb.linearVelocity = new Vector2(_rb.linearVelocity.x, groundedFallSpeed);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1236,73 +1230,141 @@ private void ApplyGravity()
|
|||||||
_rb.linearVelocity = new Vector2(_rb.linearVelocity.x, newY);
|
_rb.linearVelocity = new Vector2(_rb.linearVelocity.x, newY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClampVelocityToGround()
|
private bool CheckGrounded()
|
||||||
|
{
|
||||||
|
return TryGetGroundHit(Mathf.Max(_groundCheckDistance, 0f), out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResolveKinematicCollisions()
|
||||||
{
|
{
|
||||||
int solidMask = _groundLayer.value;
|
int solidMask = _groundLayer.value;
|
||||||
if (solidMask == 0) return;
|
if (solidMask == 0) return;
|
||||||
|
if (_bodyCollider == null || !_bodyCollider.enabled) return;
|
||||||
|
|
||||||
|
float dt = Time.fixedDeltaTime;
|
||||||
|
if (dt <= 0f) return;
|
||||||
|
|
||||||
Vector2 velocity = _rb.linearVelocity;
|
Vector2 velocity = _rb.linearVelocity;
|
||||||
bool changed = false;
|
|
||||||
|
|
||||||
if (Mathf.Abs(velocity.x) > 0.001f)
|
velocity.x = ResolveAxisVelocity(velocity.x, Vector2.right * Mathf.Sign(velocity.x), solidMask, dt, out _);
|
||||||
|
|
||||||
|
float originalY = velocity.y;
|
||||||
|
velocity.y = ResolveAxisVelocity(velocity.y, Vector2.up * Mathf.Sign(velocity.y), solidMask, dt, out bool blockedY);
|
||||||
|
|
||||||
|
if (blockedY && originalY <= 0f)
|
||||||
{
|
{
|
||||||
float sign = Mathf.Sign(velocity.x);
|
_isGrounded = true;
|
||||||
float maxDist = Mathf.Abs(velocity.x * Time.fixedDeltaTime);
|
_jumpsUsed = 0;
|
||||||
float hitDist = GetClosestHitDistance(new Vector2(sign, 0f), maxDist + _skinWidth, solidMask);
|
|
||||||
if (hitDist < maxDist + _skinWidth)
|
|
||||||
{
|
|
||||||
float allowed = Mathf.Max(hitDist - _skinWidth, 0f);
|
|
||||||
velocity.x = sign * (allowed / Time.fixedDeltaTime);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Mathf.Abs(velocity.y) > 0.001f)
|
_rb.linearVelocity = velocity;
|
||||||
{
|
|
||||||
float sign = Mathf.Sign(velocity.y);
|
|
||||||
float maxDist = Mathf.Abs(velocity.y * Time.fixedDeltaTime);
|
|
||||||
float hitDist = GetClosestHitDistance(new Vector2(0f, sign), maxDist + _skinWidth, solidMask);
|
|
||||||
if (hitDist < maxDist + _skinWidth)
|
|
||||||
{
|
|
||||||
float allowed = Mathf.Max(hitDist - _skinWidth, 0f);
|
|
||||||
velocity.y = sign * (allowed / Time.fixedDeltaTime);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changed)
|
|
||||||
_rb.linearVelocity = velocity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private float GetClosestHitDistance(Vector2 direction, float distance, int layerMask)
|
private float ResolveAxisVelocity(float axisVelocity, Vector2 direction, int solidMask, float dt, out bool blocked)
|
||||||
{
|
{
|
||||||
if (_bodyColliders == null || _bodyColliders.Length == 0)
|
blocked = false;
|
||||||
_bodyColliders = GetComponentsInChildren<Collider2D>();
|
|
||||||
|
|
||||||
ContactFilter2D filter = new ContactFilter2D
|
if (Mathf.Abs(axisVelocity) <= 0.001f) return axisVelocity;
|
||||||
|
|
||||||
|
float skinWidth = Mathf.Max(_collisionSkinWidth, 0f);
|
||||||
|
float moveDistance = Mathf.Abs(axisVelocity) * dt;
|
||||||
|
float castDistance = moveDistance + skinWidth;
|
||||||
|
|
||||||
|
if (!TryGetBodyHit(direction, castDistance, solidMask, out RaycastHit2D hit))
|
||||||
|
return axisVelocity;
|
||||||
|
|
||||||
|
blocked = true;
|
||||||
|
float allowedDistance = Mathf.Max(hit.distance - skinWidth, 0f);
|
||||||
|
return Mathf.Sign(axisVelocity) * (allowedDistance / dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetBodyHit(Vector2 direction, float castDistance, int solidMask, out RaycastHit2D closestHit)
|
||||||
|
{
|
||||||
|
closestHit = default;
|
||||||
|
|
||||||
|
ContactFilter2D filter = CreateSolidContactFilter(solidMask);
|
||||||
|
float closestDistance = float.PositiveInfinity;
|
||||||
|
bool hasHit = false;
|
||||||
|
|
||||||
|
_castResults.Clear();
|
||||||
|
int hitCount = _bodyCollider.Cast(direction, filter, _castResults, castDistance);
|
||||||
|
for (int i = 0; i < hitCount; i++)
|
||||||
|
{
|
||||||
|
RaycastHit2D hit = _castResults[i];
|
||||||
|
if (!IsBodyBlockingHit(direction, hit)) continue;
|
||||||
|
if (hit.distance >= closestDistance) continue;
|
||||||
|
|
||||||
|
closestDistance = hit.distance;
|
||||||
|
closestHit = hit;
|
||||||
|
hasHit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasHit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsBodyBlockingHit(Vector2 direction, RaycastHit2D hit)
|
||||||
|
{
|
||||||
|
if (Mathf.Abs(direction.x) > 0f)
|
||||||
|
{
|
||||||
|
return direction.x > 0f
|
||||||
|
? hit.normal.x < -0.1f
|
||||||
|
: hit.normal.x > 0.1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction.y > 0f)
|
||||||
|
return hit.normal.y < -0.1f;
|
||||||
|
|
||||||
|
return hit.normal.y >= _groundNormalMinY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetGroundHit(float checkDistance, out RaycastHit2D closestHit)
|
||||||
|
{
|
||||||
|
closestHit = default;
|
||||||
|
|
||||||
|
int solidMask = _groundLayer.value;
|
||||||
|
if (solidMask == 0) return false;
|
||||||
|
if (_groundCheckCollider == null || !_groundCheckCollider.enabled) return false;
|
||||||
|
|
||||||
|
ContactFilter2D filter = CreateSolidContactFilter(solidMask);
|
||||||
|
Bounds bounds = _groundCheckCollider.bounds;
|
||||||
|
int rayCount = 3;
|
||||||
|
float inset = Mathf.Min(Mathf.Max(_groundCheckRayInset, 0f), bounds.extents.x);
|
||||||
|
float leftX = bounds.min.x + inset;
|
||||||
|
float rightX = bounds.max.x - inset;
|
||||||
|
float originY = bounds.min.y;
|
||||||
|
float closestDistance = float.PositiveInfinity;
|
||||||
|
bool hasHit = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < rayCount; i++)
|
||||||
|
{
|
||||||
|
float t = rayCount == 1 ? 0.5f : i / (float)(rayCount - 1);
|
||||||
|
Vector2 origin = new Vector2(Mathf.Lerp(leftX, rightX, t), originY);
|
||||||
|
|
||||||
|
_castResults.Clear();
|
||||||
|
int hitCount = Physics2D.Raycast(origin, Vector2.down, filter, _castResults, checkDistance);
|
||||||
|
for (int j = 0; j < hitCount; j++)
|
||||||
|
{
|
||||||
|
RaycastHit2D hit = _castResults[j];
|
||||||
|
if (hit.normal.y < _groundNormalMinY) continue;
|
||||||
|
if (hit.distance >= closestDistance) continue;
|
||||||
|
|
||||||
|
closestDistance = hit.distance;
|
||||||
|
closestHit = hit;
|
||||||
|
hasHit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasHit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ContactFilter2D CreateSolidContactFilter(int layerMask)
|
||||||
|
{
|
||||||
|
return new ContactFilter2D
|
||||||
{
|
{
|
||||||
useLayerMask = true,
|
useLayerMask = true,
|
||||||
layerMask = layerMask,
|
layerMask = layerMask,
|
||||||
useTriggers = false
|
useTriggers = false
|
||||||
};
|
};
|
||||||
|
|
||||||
// 빠른 이동이 지형을 뚫지 않도록 트리거가 아닌 몸체 콜라이더를 전부 캐스트한다.
|
|
||||||
float closest = float.PositiveInfinity;
|
|
||||||
for (int i = 0; i < _bodyColliders.Length; i++)
|
|
||||||
{
|
|
||||||
Collider2D bodyCollider = _bodyColliders[i];
|
|
||||||
if (bodyCollider == null || bodyCollider.isTrigger) continue;
|
|
||||||
|
|
||||||
_castResults.Clear();
|
|
||||||
int hitCount = bodyCollider.Cast(direction, filter, _castResults, distance);
|
|
||||||
for (int j = 0; j < hitCount; j++)
|
|
||||||
{
|
|
||||||
if (_castResults[j].distance < closest)
|
|
||||||
closest = _castResults[j].distance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return closest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDrawGizmos()
|
private void OnDrawGizmos()
|
||||||
@@ -1325,20 +1387,22 @@ private void OnDrawGizmos()
|
|||||||
|
|
||||||
private void OnDrawGizmosSelected()
|
private void OnDrawGizmosSelected()
|
||||||
{
|
{
|
||||||
if (_groundCheck != null)
|
if (_groundCheckCollider == null || !_groundCheckCollider.enabled) return;
|
||||||
|
|
||||||
|
Gizmos.color = _isGrounded ? Color.green : Color.red;
|
||||||
|
Bounds bounds = _groundCheckCollider.bounds;
|
||||||
|
int rayCount = Mathf.Max(3, 1);
|
||||||
|
float inset = Mathf.Min(Mathf.Max(_groundCheckRayInset, 0f), bounds.extents.x);
|
||||||
|
float leftX = bounds.min.x + inset;
|
||||||
|
float rightX = bounds.max.x - inset;
|
||||||
|
float bottomY = bounds.min.y;
|
||||||
|
float checkY = bottomY - Mathf.Max(_groundCheckDistance, 0f);
|
||||||
|
|
||||||
|
for (int i = 0; i < rayCount; i++)
|
||||||
{
|
{
|
||||||
Gizmos.color = _isGrounded ? Color.green : Color.red;
|
float t = rayCount == 1 ? 0.5f : i / (float)(rayCount - 1);
|
||||||
Gizmos.DrawWireSphere(_groundCheck.position, _groundCheckRadius);
|
float x = Mathf.Lerp(leftX, rightX, t);
|
||||||
}
|
Gizmos.DrawLine(new Vector3(x, bottomY, 0f), new Vector3(x, checkY, 0f));
|
||||||
if (_wallCheckLeft != null)
|
|
||||||
{
|
|
||||||
Gizmos.color = _isTouchingLeftWall ? Color.green : Color.red;
|
|
||||||
Gizmos.DrawWireSphere(_wallCheckLeft.position, _wallCheckRadius);
|
|
||||||
}
|
|
||||||
if (_wallCheckRight != null)
|
|
||||||
{
|
|
||||||
Gizmos.color = _isTouchingRightWall ? Color.green : Color.red;
|
|
||||||
Gizmos.DrawWireSphere(_wallCheckRight.position, _wallCheckRadius);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user