From adcd69c53757ee4362db9b8f203568fe5328dc70 Mon Sep 17 00:00:00 2001 From: "DESKTOP-VVOCIJO\\PC" Date: Mon, 18 May 2026 11:54:10 +0900 Subject: [PATCH] =?UTF-8?q?2026-05-18=20=EA=B3=B5=EA=B2=A9=EC=97=90=20?= =?UTF-8?q?=EC=9D=98=ED=95=9C=20=EC=9D=B4=EB=8F=99=EB=9F=89=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/02_Scripts/Combat/AttackHitbox.cs | 6 +++--- Assets/02_Scripts/Enemy/Enemy.cs | 19 +++++++++++-------- Assets/02_Scripts/Player/PlayerController.cs | 20 ++++++++++---------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/Assets/02_Scripts/Combat/AttackHitbox.cs b/Assets/02_Scripts/Combat/AttackHitbox.cs index 27df236..52fc283 100644 --- a/Assets/02_Scripts/Combat/AttackHitbox.cs +++ b/Assets/02_Scripts/Combat/AttackHitbox.cs @@ -14,7 +14,7 @@ public class AttackHitbox : MonoBehaviour private void Awake() { _collider = GetComponent(); - // The player body does not collide with enemies; this trigger is the only attack contact. + // 플레이어 몸체는 적과 물리 충돌하지 않으므로, 공격 판정은 이 트리거만 사용한다. _collider.isTrigger = true; _collider.enabled = false; } @@ -30,7 +30,7 @@ public void Activate(ActionData data, Vector2 localPosition, Vector2 hitVelocity _alreadyHit.Clear(); _collider.enabled = true; - // Catch enemies already inside the hitbox on the same frame it opens. + // 판정이 켜진 순간 이미 범위 안에 있던 적도 같은 프레임에 잡아낸다. ScanImmediateOverlap(); } @@ -54,7 +54,7 @@ private void TryDamage(Collider2D other) { if ((_targetLayer.value & (1 << other.gameObject.layer)) == 0) return; - // Hurtboxes may live on child objects, while damage handling usually lives on the root. + // 피격 콜라이더는 자식에 있고, 데미지 처리는 루트에 있을 수 있다. if (!other.TryGetComponent(out var target)) target = other.GetComponentInParent(); if (target == null) return; diff --git a/Assets/02_Scripts/Enemy/Enemy.cs b/Assets/02_Scripts/Enemy/Enemy.cs index 3a1ba3b..0efa349 100644 --- a/Assets/02_Scripts/Enemy/Enemy.cs +++ b/Assets/02_Scripts/Enemy/Enemy.cs @@ -73,13 +73,18 @@ public void TakeDamage(int amount, Vector2 hitVelocity = default, string hitReac if (_anim != null && !string.IsNullOrEmpty(hitReactionAnimationState)) _anim.Play(hitReactionAnimationState); - // HitVelocity is an immediate launch/knockback velocity, not an additive force. + // 새 피격이 반응 속도를 전부 결정하므로, 이전 튕김/넉백 속도는 먼저 제거한다. if (_rb != null) { + _hitReactionTimer = 0f; + _rb.linearVelocity = Vector2.zero; + _lastVelocity = Vector2.zero; + Vector2 nextVelocity = GetHitReactionVelocity(hitVelocity); if (nextVelocity != Vector2.zero) { _rb.linearVelocity = nextVelocity; + _lastVelocity = nextVelocity; _hitReactionTimer = _hitReactionDuration; } } @@ -117,13 +122,11 @@ private void OnCollisionExit2D(Collision2D collision) private Vector2 GetHitReactionVelocity(Vector2 hitVelocity) { - // Airborne follow-up hits pop the enemy with a fixed Y velocity for stable combos. - if (_hitReactionTimer <= 0f || _isGrounded) - return hitVelocity; + // 공중 추가타는 고정된 세로 속도를 쓰되, 이전 물리 속도는 절대 이어받지 않는다. + Vector2 nextVelocity = hitVelocity; + if (!_isGrounded) + nextVelocity.y = _airborneHitYVelocity; - Vector2 currentVelocity = _rb.linearVelocity; - Vector2 nextVelocity = hitVelocity == Vector2.zero ? currentVelocity : hitVelocity; - nextVelocity.y = _airborneHitYVelocity; return nextVelocity; } @@ -141,7 +144,7 @@ private void UpdateGroundedState(Collision2D collision) private void BounceOffWall(Vector2 wallNormal) { - // While in hit reaction, side-wall impacts reflect the current knockback. + // 피격 반응 중 옆벽에 부딪히면 현재 넉백 속도를 반사한다. Vector2 incomingVelocity = _lastVelocity.sqrMagnitude > _rb.linearVelocity.sqrMagnitude ? _lastVelocity : _rb.linearVelocity; diff --git a/Assets/02_Scripts/Player/PlayerController.cs b/Assets/02_Scripts/Player/PlayerController.cs index 7679c2f..97214ae 100644 --- a/Assets/02_Scripts/Player/PlayerController.cs +++ b/Assets/02_Scripts/Player/PlayerController.cs @@ -114,7 +114,7 @@ private void OnDestroy() private void FixedUpdate() { - // Sample collision probes first; movement, wall slide, and jump decisions all use these flags. + // 이동, 벽타기, 점프 판단이 모두 이 값을 쓰므로 충돌 체크를 먼저 갱신한다. _isGrounded = Physics2D.OverlapCircle(_groundCheck.position, _groundCheckRadius, _groundLayer); _isTouchingLeftWall = Physics2D.OverlapCircle(_wallCheckLeft.position, _wallCheckRadius, _groundLayer); _isTouchingRightWall = Physics2D.OverlapCircle(_wallCheckRight.position, _wallCheckRadius, _groundLayer); @@ -142,7 +142,7 @@ private void FixedUpdate() if (IsTouchingWall && !_isGrounded && _rb.linearVelocity.y < -_wallSlideSpeed) _rb.linearVelocity = new Vector2(_rb.linearVelocity.x, -_wallSlideSpeed); - // Player and Enemy bodies do not physically collide; only ground/walls clamp player velocity. + // 플레이어와 적 몸체는 물리 충돌하지 않고, 땅/벽만 캐스트로 이동을 막는다. ClampVelocityToGround(); } @@ -215,7 +215,7 @@ private void HandleComboInput(ComboInputType input) private void ExecuteComboInput(ComboInputType input) { - // Continue from the current combo node while its window is open. + // 콤보 입력 가능 시간이 열려 있으면 현재 노드에서 다음 연계로 이어간다. if (_comboWindowTimer > 0f && _currentNode != null) { foreach (var transition in _currentNode.Transitions) @@ -231,7 +231,7 @@ private void ExecuteComboInput(ComboInputType input) } } - // No active combo route matched, so start from the input's root node. + // 이어갈 연계가 없으면 입력에 맞는 루트 노드부터 새로 시작한다. ComboNode root = input switch { ComboInputType.Punch => _punchRootNode, @@ -318,7 +318,7 @@ private async void PerformMotion(ActionData data) { if (data == null || IsMotionOnCooldown(data)) return; - // Motions such as dash/roll interrupt attacks and become the new combo node. + // 대시/구르기 같은 모션은 공격을 끊고 새로운 콤보 노드가 된다. CancelAttack(); _motionCts?.Cancel(); _motionCts?.Dispose(); @@ -387,7 +387,7 @@ private async Awaitable HitRoutine(ActionData data, CancellationToken token) if (data.HitTiming > 0f) await Awaitable.WaitForSecondsAsync(data.HitTiming, token); - // Hit windows are represented by a real trigger collider instead of overlap checks. + // 타격 가능 시간은 범위 검사 대신 실제 트리거 콜라이더를 켜서 표현한다. ActivateAttackHitbox(data); float activeTime = Mathf.Max(data.HitDuration, 0.02f); @@ -416,7 +416,7 @@ private void ActivateAttackHitbox(ActionData data) private void EnsureAttackHitbox() { - // Allow designers to assign a hitbox, but create one automatically for simple setup. + // 인스펙터에서 직접 넣을 수도 있고, 없으면 기본 공격 판정을 자동으로 만든다. if (_attackHitbox != null) { SetAttackHitboxLayer(); @@ -439,7 +439,7 @@ private void EnsureAttackHitbox() private void SetAttackHitboxLayer() { - // The hitbox must not inherit the Player layer, because Player vs Enemy physics is disabled. + // 플레이어와 적의 물리 충돌이 꺼져 있으므로 공격 판정은 플레이어 레이어를 쓰면 안 된다. int defaultLayer = LayerMask.NameToLayer("Default"); if (defaultLayer >= 0) _attackHitbox.gameObject.layer = defaultLayer; @@ -471,7 +471,7 @@ private void LockMovementIfNeeded(ActionData data, bool preserveHorizontalVeloci { if (data.CanMoveDuringAction) return; - // Non-moving attacks should not inherit walking velocity from the previous frame. + // 이동 없는 공격은 직전 프레임의 걷기 속도를 이어받지 않게 한다. if (!preserveHorizontalVelocity && !data.HasMotion) _rb.linearVelocity = new Vector2(0f, _rb.linearVelocity.y); @@ -660,7 +660,7 @@ private float GetClosestHitDistance(Vector2 direction, float distance, int layer useTriggers = false }; - // Cast every non-trigger body collider so high-speed motion cannot tunnel into level geometry. + // 빠른 이동이 지형을 뚫지 않도록 트리거가 아닌 몸체 콜라이더를 전부 캐스트한다. float closest = float.PositiveInfinity; for (int i = 0; i < _bodyColliders.Length; i++) {