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

Binary file not shown.

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)

View File

@@ -26,6 +26,32 @@ AnimatorState:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1102 &-9013432840856765531
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: GroundSlamEnd
m_Speed: 1
m_CycleOffset: 0
m_Transitions: []
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: 43a3597f5e5a1b646b018598197b92a6, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1102 &-8997814140322552695
AnimatorState:
serializedVersion: 6
@@ -832,6 +858,32 @@ AnimatorState:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1102 &-3426527744796024442
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: GroundSlamLoop
m_Speed: 1
m_CycleOffset: 0
m_Transitions: []
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: dfe5fd923a41a494399e84b126452f41, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1102 &-3380229326876372857
AnimatorState:
serializedVersion: 6
@@ -2076,6 +2128,32 @@ AnimatorState:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1102 &4277843955758000149
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: GroundSlamStart
m_Speed: 1
m_CycleOffset: 0
m_Transitions: []
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: 97161820e52e20b40bedabdf43c7d6f5, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1102 &4302945159732726270
AnimatorState:
serializedVersion: 6
@@ -3150,6 +3228,15 @@ AnimatorStateMachine:
- serializedVersion: 1
m_State: {fileID: -4203153932972611291}
m_Position: {x: 5280, y: 90, z: 0}
- serializedVersion: 1
m_State: {fileID: 4277843955758000149}
m_Position: {x: 2480, y: 440, z: 0}
- serializedVersion: 1
m_State: {fileID: -3426527744796024442}
m_Position: {x: 2690, y: 440, z: 0}
- serializedVersion: 1
m_State: {fileID: -9013432840856765531}
m_Position: {x: 2900, y: 440, z: 0}
m_ChildStateMachines: []
m_AnyStateTransitions: []
m_EntryTransitions: []

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dfe5fd923a41a494399e84b126452f41
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 97161820e52e20b40bedabdf43c7d6f5
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.