2026-05-22 보스스킬 : 지진

This commit is contained in:
2026-05-22 16:17:35 +09:00
parent 20db4207fa
commit d95eb9df1d
18 changed files with 186 additions and 50 deletions

Binary file not shown.

View File

@@ -0,0 +1,43 @@
using System.Threading;
using UnityEngine;
public class EarthquakeSkill : BossSkill
{
[Header("EarthquakeSkill")]
[SerializeField] private float _windup = 0.5f; // 애니 시작 ~ 피해 판정 ON
[SerializeField] private float _activeDuration = 1.5f; // 피해 판정 유지 시간
[SerializeField] private float _recovery = 0.6f; // 판정 OFF 후 후딜
[SerializeField] private int _damage = 15;
[SerializeField] private float _liftForce = 0.5f; // 지면 피격 시 띄우는 세기
protected override async Awaitable RunSkill(CancellationToken token)
{
try
{
// 선딜
await Awaitable.WaitForSecondsAsync(_windup, token);
Earthquake();
// 후딜
await Awaitable.WaitForSecondsAsync(_recovery, token);
}
finally
{
}
}
private void Earthquake()
{
PlayerController player = GameManager.Instance.LocalPlayer;
if (player == null) return;
// 플레이어가 지면에 닿아 있을 때만 발동. 공중이면 아무 효과 없음.
if (!player.IsGrounded) return;
// 지면 접촉 중: 살짝 띄우면서 데미지.
player.Launch(new Vector2(0f, _liftForce));
player.TakeDamage(_damage);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d27a723f9206afc49a060c2875ccf3dc

View File

@@ -19,14 +19,10 @@ public class HazardSkill : BossSkill
[SerializeField] private GameObject _hazardHitboxOrigin; [SerializeField] private GameObject _hazardHitboxOrigin;
[SerializeField] private int _hitBoxDamage = 15; [SerializeField] private int _hitBoxDamage = 15;
private SkillSupport support;
private void Awake() private void Awake()
{ {
if (_animator == null) if (_animator == null)
_animator = GetComponentInChildren<Animator>(); _animator = GetComponentInChildren<Animator>();
support = Object.FindFirstObjectByType<SkillSupport>();
} }
protected override async Awaitable RunSkill(CancellationToken token) protected override async Awaitable RunSkill(CancellationToken token)
@@ -64,12 +60,7 @@ protected override async Awaitable RunSkill(CancellationToken token)
private void PredictHazards(int dirChoice) private void PredictHazards(int dirChoice)
{ {
if(support == null) SkillPredict[] predicts = dirChoice switch { 0 => GameManager.Instance.SkillSupporter.DownHazardPredicts, 1=> GameManager.Instance.SkillSupporter.RightHazardPredicts, _=> null};
{
Debug.Log("Null이니");
}
SkillPredict[] predicts = dirChoice switch { 0 => support.DownHazardPredicts, 1=> support.RightHazardPredicts, _=> null};
for (int i = 0; i < predicts.Length; i++) for (int i = 0; i < predicts.Length; i++)
{ {
@@ -80,7 +71,7 @@ private void PredictHazards(int dirChoice)
private void FireHazards(int dirChoice) private void FireHazards(int dirChoice)
{ {
Transform[] hazardPoss = dirChoice switch { 0 => support.DownHazardFirePos, 1=> support.RightHazardFirePos, _=> null}; Transform[] hazardPoss = dirChoice switch { 0 => GameManager.Instance.SkillSupporter.DownHazardFirePos, 1=> GameManager.Instance.SkillSupporter.RightHazardFirePos, _=> null};
Vector2 attackDir = dirChoice switch {0 => Vector2.down,1=>Vector2.right,_=>Vector2.left}; Vector2 attackDir = dirChoice switch {0 => Vector2.down,1=>Vector2.right,_=>Vector2.left};
if (hazardPoss == null) return; if (hazardPoss == null) return;

View File

@@ -8,7 +8,12 @@ public class SkillSupport : MonoBehaviour
[HideInInspector] public SkillPredict[] DownHazardPredicts; [HideInInspector] public SkillPredict[] DownHazardPredicts;
[HideInInspector] public SkillPredict[] RightHazardPredicts; [HideInInspector] public SkillPredict[] RightHazardPredicts;
void Start() private void Awake()
{
}
private void Start()
{ {
GameObject[] shineDownStartPoss = GameObject.FindGameObjectsWithTag("ShineDownStartPos"); GameObject[] shineDownStartPoss = GameObject.FindGameObjectsWithTag("ShineDownStartPos");
GameObject[] shineRightStartPoss = GameObject.FindGameObjectsWithTag("ShineRightStartPos"); GameObject[] shineRightStartPoss = GameObject.FindGameObjectsWithTag("ShineRightStartPos");

View File

@@ -0,0 +1,29 @@
using UnityEngine;
public class GameManager : MonoBehaviour,ISceneInitializable
{
public static GameManager Instance;
public SkillSupport SkillSupporter {get; private set;}
public PlayerController LocalPlayer {get; private set;}
private void Awake()
{
if (Instance == null)
{
Instance = this; //만들어진 자신을 인스턴스로 설정
}
else
{
Destroy(gameObject); //이미 인스턴스가 있으면 자신을 파괴
}
}
public void OnSceneLoaded()
{
Debug.Log("aaaa");
SkillSupporter = Object.FindFirstObjectByType<SkillSupport>();
LocalPlayer = Object.FindFirstObjectByType<PlayerController>();
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5b2c0a8cf5c55bc4da93c75f61309de6

View File

@@ -31,8 +31,7 @@ private void Update()
//씬이 로드되었을때 호출 //씬이 로드되었을때 호출
private void OnSceneLoaded(Scene scene, LoadSceneMode mode) private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{ {
if(scene.name == "GameScene")
{
MonoBehaviour[] allObjs = UnityEngine.Object.FindObjectsByType<MonoBehaviour>(FindObjectsSortMode.None); MonoBehaviour[] allObjs = UnityEngine.Object.FindObjectsByType<MonoBehaviour>(FindObjectsSortMode.None);
foreach (var obj in allObjs) foreach (var obj in allObjs)
@@ -43,7 +42,7 @@ private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
initializable.OnSceneLoaded(); initializable.OnSceneLoaded();
} }
} }
}
} }
public void SetSceneLoadingProgressValue(float value) public void SetSceneLoadingProgressValue(float value)

View File

@@ -27,6 +27,13 @@ public class PlayerController : MonoBehaviour,IDamageable
private string _activeBaseState; // 현재 재생 중인 locomotion State (중복 Play 방지용) private string _activeBaseState; // 현재 재생 중인 locomotion State (중복 Play 방지용)
private bool _isInActionAnimation; // 액션 애니메이션 재생 중인지 (locomotion 잠시 양보) private bool _isInActionAnimation; // 액션 애니메이션 재생 중인지 (locomotion 잠시 양보)
// 피격
[Header("Hit Animation")]
[SerializeField] private string _knockbackAnimationState = "Knockback";
[SerializeField] private float _knockbackDuration = 1f; // 넉백 경직 시간 (Knockback 클립 길이에 맞춰 설정)
private float _hitstunTimer; // 경직 남은 최소 시간
private bool _isStunned; // 경직 상태 (공중이면 착지까지 연장)
// ─── 점프 단계별 애니메이션 ─────────────────────────────────────────── // ─── 점프 단계별 애니메이션 ───────────────────────────────────────────
// 점프를 4단계(Rise/Mid/Fall/Land)로 분리해서 vy에 따라 자동 전환. // 점프를 4단계(Rise/Mid/Fall/Land)로 분리해서 vy에 따라 자동 전환.
[Header("Jump Animation")] [Header("Jump Animation")]
@@ -253,19 +260,21 @@ private void FixedUpdate()
TickComboWindow(); // 콤보 윈도우 카운트다운 TickComboWindow(); // 콤보 윈도우 카운트다운
// 입력 잠금이 없고, (액션 중이 아니거나 액션이 이동을 허용할 때) 좌우 입력으로 velocity 갱신. // 입력 잠금이 없고, (액션 중이 아니거나 액션이 이동을 허용할 때) 좌우 입력으로 velocity 갱신.
if (!IsMovementLocked() && (!IsActionActive() || _actionAllowsMovement)) // 경직 중에는 이동 입력을 무시한다 (Launch로 받은 속도 + 중력만 작용).
if (!IsStunned() && !IsMovementLocked() && (!IsActionActive() || _actionAllowsMovement))
{ {
// 백페달이면 느린 속도. 액션 중에도 동일하게 적용 (이동사격 = Down 변형 + CanMoveDuringAction). // 백페달이면 느린 속도. 액션 중에도 동일하게 적용 (이동사격 = Down 변형 + CanMoveDuringAction).
float moveSpeed = IsBackpedaling() ? _backpedalSpeed : _moveSpeed; float moveSpeed = IsBackpedaling() ? _backpedalSpeed : _moveSpeed;
_rb.linearVelocity = new Vector2(_moveInputX * moveSpeed, _rb.linearVelocity.y); _rb.linearVelocity = new Vector2(_moveInputX * moveSpeed, _rb.linearVelocity.y);
} }
if (!IsFacingLocked() && (!IsActionActive() || _actionAllowsTurn)) if (!IsStunned() && !IsFacingLocked() && (!IsActionActive() || _actionAllowsTurn))
UpdateFacingFromMoveInput(); UpdateFacingFromMoveInput();
//중력적용 //중력적용
ApplyGravity(); ApplyGravity();
ResolveKinematicCollisions(); ResolveKinematicCollisions();
TickHitstun(); // 피격 경직 타이머 카운트다운
UpdateLocomotionAnimation(); UpdateLocomotionAnimation();
@@ -307,7 +316,7 @@ private void OnMoveInput(Vector2 value)
{ {
_moveInputX = value.x == 0f ? 0f : Mathf.Sign(value.x); _moveInputX = value.x == 0f ? 0f : Mathf.Sign(value.x);
_moveInputY = value.y == 0f ? 0f : Mathf.Sign(value.y); _moveInputY = value.y == 0f ? 0f : Mathf.Sign(value.y);
if (_facingLockTimer <= 0f && (!IsActionActive() || _actionAllowsTurn)) if (!IsStunned() && _facingLockTimer <= 0f && (!IsActionActive() || _actionAllowsTurn))
UpdateFacingFromMoveInput(); UpdateFacingFromMoveInput();
} }
@@ -332,6 +341,8 @@ private bool IsBackpedaling()
// 점프 우선순위: 지상 점프 > 공중(2단) 점프 // 점프 우선순위: 지상 점프 > 공중(2단) 점프
private void OnJumpInput() private void OnJumpInput()
{ {
if (IsStunned()) return; // 경직 중 점프 불가
if (_isGrounded) if (_isGrounded)
{ {
PerformJump(); PerformJump();
@@ -374,6 +385,8 @@ private void OnGrabSmashInput()
// 3) 그 외에는 즉시 ExecuteComboInput으로 발화 // 3) 그 외에는 즉시 ExecuteComboInput으로 발화
private void HandleComboInput(ComboInputType input) private void HandleComboInput(ComboInputType input)
{ {
if (IsStunned()) return; // 경직 중 공격 불가
// 콤보 연계(현재 노드의 트랜지션) 가능 여부 — 연계는 애니메이션 중에도 즉시 캔슬-체인. // 콤보 연계(현재 노드의 트랜지션) 가능 여부 — 연계는 애니메이션 중에도 즉시 캔슬-체인.
bool canChain = CanTransitionFromCurrentNode(input); bool canChain = CanTransitionFromCurrentNode(input);
@@ -509,6 +522,7 @@ private bool IsWeaponRequirementMet(ActionData data)
private void ExecuteMotionNode(ComboNode root) private void ExecuteMotionNode(ComboNode root)
{ {
if (IsStunned()) return; // 경직 중 대시/구르기 불가
if (root == null || root.Action == null) return; if (root == null || root.Action == null) return;
if (IsMotionOnCooldown(root.Action)) return; if (IsMotionOnCooldown(root.Action)) return;
@@ -652,6 +666,7 @@ private async Awaitable MotionRoutine(ActionData data, CancellationToken token)
private async void PerformGroundPound() private async void PerformGroundPound()
{ {
if (IsStunned()) return; // 경직 중 그라운드 파운드 불가
if (_groundPoundData == null) return; if (_groundPoundData == null) return;
CancelAttack(); CancelAttack();
@@ -1531,7 +1546,9 @@ private bool TryGetGroundHit(float checkDistance, out RaycastHit2D closestHit)
float inset = Mathf.Min(Mathf.Max(_groundCheckRayInset, 0f), bounds.extents.x); float inset = Mathf.Min(Mathf.Max(_groundCheckRayInset, 0f), bounds.extents.x);
float leftX = bounds.min.x + inset; float leftX = bounds.min.x + inset;
float rightX = bounds.max.x - inset; float rightX = bounds.max.x - inset;
float originY = bounds.min.y; float originLift = bounds.size.y + Mathf.Max(_collisionSkinWidth, 0f) + 0.01f;
float originY = bounds.min.y + originLift;
float rayDistance = checkDistance + originLift;
float closestDistance = float.PositiveInfinity; float closestDistance = float.PositiveInfinity;
bool hasHit = false; bool hasHit = false;
@@ -1541,7 +1558,7 @@ private bool TryGetGroundHit(float checkDistance, out RaycastHit2D closestHit)
Vector2 origin = new Vector2(Mathf.Lerp(leftX, rightX, t), originY); Vector2 origin = new Vector2(Mathf.Lerp(leftX, rightX, t), originY);
_castResults.Clear(); _castResults.Clear();
int hitCount = Physics2D.Raycast(origin, Vector2.down, filter, _castResults, checkDistance); int hitCount = Physics2D.Raycast(origin, Vector2.down, filter, _castResults, rayDistance);
for (int j = 0; j < hitCount; j++) for (int j = 0; j < hitCount; j++)
{ {
RaycastHit2D hit = _castResults[j]; RaycastHit2D hit = _castResults[j];
@@ -1695,6 +1712,64 @@ private void DrawTimelineBar(ActionData data, float elapsed)
GUI.color = Color.white; GUI.color = Color.white;
} }
// 외부(보스 스킬 등)에서 플레이어의 지면 접촉 여부를 조회.
public bool IsGrounded => _isGrounded;
// 외부에서 플레이어를 강제로 띄우거나 밀어낼 때 사용 (지진 스킬 등).
// 넉백 = 지정 속도로 띄우고 넉백 모션으로 경직시킨다. 키네마틱 RB라 속도 직접 설정.
public void Launch(Vector2 velocity)
{
if (_rb == null) return;
_rb.linearVelocity = velocity;
EnterHitstun(_knockbackAnimationState, _knockbackDuration);
}
// 피격 경직 진행 여부. 경직 중에는 이동/방향전환/점프/공격/대시 입력을 모두 잠근다.
private bool IsStunned() => _isStunned;
// 피격 경직(범용). 지정 모션을 재생하고 duration초 동안 입력을 잠근다. 재호출 시 갱신(refresh).
// 진행 중이던 플레이어 액션은 취소한다 (피격이 우선).
// duration은 호출자가 모션 클립 길이에 맞춰 넘긴다 (넉백은 _knockbackDuration).
// 일반 경직 / 넉백 등 종류별로 animationState만 바꿔 호출하면 된다.
public void EnterHitstun(string animationState, float duration)
{
_isStunned = true;
_hitstunTimer = duration; // 경직 시간 (재호출 시 갱신/refresh)
CancelAttack(); // 진행 중이던 공격/모션을 끊는다 (피격이 우선)
CancelMotion();
// 경직 모션은 액션 애니메이션 취급 — _isInActionAnimation으로 locomotion이 못 덮게 막는다.
if (_anim != null && !string.IsNullOrEmpty(animationState))
{
_isInActionAnimation = true;
_activeBaseState = null;
_anim.speed = 1f;
_anim.Play(animationState);
_anim.Update(0f);
}
}
// 경직 타이머 카운트다운. 클립을 다 재생했고(타이머 종료) + 착지했을 때만 경직을 해제한다.
// 넉백으로 공중에 떠 있는 동안엔 경직/모션을 유지 → 점프 모션으로 안 끊긴다.
private void TickHitstun()
{
if (!_isStunned) return;
if (_hitstunTimer > 0f)
_hitstunTimer -= Time.fixedDeltaTime;
Debug.Log($"_hitstunTimer : {_hitstunTimer}, _isGrounded : {_isGrounded} ");
if (_hitstunTimer <= 0f && _isGrounded && !IsActionActive())
{
_isStunned = false;
_isInActionAnimation = false;
_activeBaseState = null;
_hitstunTimer = 0f;
}
}
public void TakeDamage(int amount, Vector2 hitVelocity = default, string hitReactionAnimationState = null, Vector2? hitTargetPosition = null, bool correctHitTargetY = false, int hitPositionSolidMask = 0, float hitPositionCorrectionDuration = 0, float hitStunDuration = -1f) public void TakeDamage(int amount, Vector2 hitVelocity = default, string hitReactionAnimationState = null, Vector2? hitTargetPosition = null, bool correctHitTargetY = false, int hitPositionSolidMask = 0, float hitPositionCorrectionDuration = 0, float hitStunDuration = -1f)
{ {
if (_health == null || _health.IsDead) return; if (_health == null || _health.IsDead) return;

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 5ca885ea8137440448a536a8e408352c guid: e8e33019b52d4774fb6be20b28f1c2eb
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@@ -1,6 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: b6e71f45f6a6eb047a72d095e2960e73 guid: 8206c9420439a754e9e9088c718c0902
DefaultImporter: PrefabImporter:
externalObjects: {} externalObjects: {}
userData: userData:
assetBundleName: assetBundleName:

Binary file not shown.

View File

@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 509cab3a1e54dbc4c880461706c573ee
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.