2026-05-18 GroundSlam

This commit is contained in:
2026-05-18 15:53:09 +09:00
parent 239c66af5b
commit ebc5745a51
13 changed files with 232 additions and 10 deletions

View File

@@ -6,7 +6,7 @@ public class PlayerController : MonoBehaviour
{
[Header("Movement")]
[SerializeField] private float _moveSpeed = 5f;
[SerializeField] private string _walkAnimationState = "Walk";
[SerializeField] private string _walkAnimationState = "Run";
private float _moveInputX = 0f;
private string _activeBaseState;
private bool _isInActionAnimation;
@@ -44,6 +44,14 @@ public class PlayerController : MonoBehaviour
private ActionData _movementLockAction;
private ActionData _facingLockAction;
[Header("Ground Pound")]
[SerializeField] private ActionData _groundPoundData;
[SerializeField] private string _groundPoundFallAnimationState = "GroundSlamLoop";
[SerializeField] private string _groundPoundImpactAnimationState = "GroundSlamEnd";
[SerializeField] private float _groundPoundWindupDuration = 0.15f;
[SerializeField] private float _groundPoundFallSpeed = 25f;
private bool _isGroundPounding;
[Header("Motion")]
[SerializeField] private ComboNode _dashRootNode;
[SerializeField] private ComboNode _rollRootNode;
@@ -218,7 +226,15 @@ private void OnJumpInput()
private void OnPunchInput() => HandleComboInput(ComboInputType.Punch);
private void OnKickInput() => HandleComboInput(ComboInputType.Kick);
private void OnGrabSmashInput() => HandleComboInput(ComboInputType.Grab);
private void OnGrabSmashInput()
{
if (!_isGrounded && _groundPoundData != null)
{
PerformGroundPound();
return;
}
HandleComboInput(ComboInputType.Grab);
}
private void OnDashInput() => ExecuteMotionNode(_dashRootNode);
private void OnRollInput() => ExecuteMotionNode(_rollRootNode);
private void OnBackDashInput() => ExecuteMotionNode(_backDashRootNode);
@@ -400,6 +416,100 @@ private async Awaitable MotionRoutine(ActionData data, CancellationToken token)
}
}
private async void PerformGroundPound()
{
if (_groundPoundData == null) return;
CancelAttack();
CancelAndDispose(ref _motionCts);
_motionCts = CancellationTokenSource.CreateLinkedTokenSource(destroyCancellationToken);
CancellationToken token = _motionCts.Token;
_isMotionActive = true;
_isGroundPounding = true;
_attackStartTime = Time.time;
_lastAttackGizmoData = _groundPoundData;
_hitFired = false;
ClearActionLocks();
FaceMotionDirection(_groundPoundData);
PlayActionAnimation(_groundPoundData);
LockMovementIfNeeded(_groundPoundData);
LockFacingIfNeeded(_groundPoundData);
float fallSpeed = _groundPoundFallSpeed > 0f
? _groundPoundFallSpeed
: Mathf.Max(Mathf.Abs(_groundPoundData.Velocity.y), 25f);
try
{
// 1) 윈드업: 짧게 공중 정지 후 슬램. ApplyGravity는 _isGroundPounding 플래그로 우회.
float windupElapsed = 0f;
while (windupElapsed < _groundPoundWindupDuration && !_isGrounded)
{
token.ThrowIfCancellationRequested();
_rb.linearVelocity = Vector2.zero;
windupElapsed += Time.deltaTime;
await Awaitable.NextFrameAsync(token);
}
// 2) 낙하 애니메이션으로 전환 (루프). 윈드업이 끝났을 때만 실행.
if (_anim != null && !string.IsNullOrEmpty(_groundPoundFallAnimationState) && !_isGrounded)
{
CancelAndDispose(ref _animationSpeedCts);
_anim.speed = 1f;
_anim.Play(_groundPoundFallAnimationState);
_anim.Update(0f);
}
// 3) 지면 닿을 때까지 강제 낙하.
while (!_isGrounded)
{
token.ThrowIfCancellationRequested();
_rb.linearVelocity = new Vector2(0f, -fallSpeed);
await Awaitable.NextFrameAsync(token);
}
// 4) 착지 순간 임팩트 애니메이션으로 전환 (낙하 거리 불일치 해소)
if (_anim != null && !string.IsNullOrEmpty(_groundPoundImpactAnimationState))
{
CancelAndDispose(ref _animationSpeedCts);
_anim.speed = 1f;
_anim.Play(_groundPoundImpactAnimationState);
_anim.Update(0f);
}
// 착지 임팩트
if (_groundPoundData.HasHit && _attackHitbox != null)
{
ActivateAttackHitbox(_groundPoundData);
float hitDuration = Mathf.Max(_groundPoundData.HitDuration, 0.05f);
await Awaitable.WaitForSecondsAsync(hitDuration, token);
_attackHitbox.Deactivate();
}
// 후딜
float recovery = Mathf.Max(_groundPoundData.MotionDuration, 0.15f);
await Awaitable.WaitForSecondsAsync(recovery, token);
PlayIdleAnimation();
}
catch (System.OperationCanceledException)
{
_attackHitbox?.Deactivate();
}
finally
{
_isGroundPounding = false;
_isMotionActive = false;
ClearActionLocks();
// 그라운드 파운드 직후 Land 애니메이션 중복 재생 방지
_wasGroundedLastFrame = _isGrounded;
}
}
private void CancelAttack()
{
CancelAndDispose(ref _attackCts);
@@ -996,6 +1106,9 @@ private async Awaitable WaitForActionEnd(ActionData data, CancellationToken toke
private void ApplyGravity()
{
// 그라운드 파운드는 _maxFallSpeed에 막히지 않도록 자체 속도를 그대로 유지한다.
if (_isGroundPounding) return;
float vy = _rb.linearVelocity.y;
if (_isGrounded && vy <= 0f)