2026-05-18 GroundSlam
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user