DirectX11/Eternal Return 모작

[DirectX 11 Eternal Return 모작] 13. 전투 , 데미지 시스템

Vfly 2025. 9. 3. 04:21

전체 코드 : https://github.com/HyangRim/DirectX11-Engine-Client

 

 


데미지 시스템과 전투 매커니즘

1. 상태 기반 전투 제어 시스템

1.1 Player 전투 상태 관리

enum class PlayerStateType
{
    Wait, Run, Skill_1, Skill_2, Skill_3, Skill_4,
    Craft, Die, BaseAttack, Counter
};

void PlayerStateMachine::HandleRightClickInput()
{
    // 1. 공격 우선 판단
    auto attackTarget = GetPickedTargetAtMouse();
    
    if (attackTarget && IsValidAttackTarget(attackTarget))
    {
        // 공격 쿨타임 체크
        if (m_baseAttackDelayDuration >= m_baseAttackDelay)
        {
            m_baseAttackDelayDuration = 0.f;
            RequestStateChange(PlayerStateType::BaseAttack);
            GetState(PlayerStateType::BaseAttack)->SetTarget(attackTarget);
        }
    }
    else
    {
        // 공격 대상이 없으면 이동 처리
        HandleMovementInput();
    }
}

특징

  • Context-Aware 입력 처리 : 하나의 입력 ( 우클릭 ) 이 상황에 따라 공격 / 이동으로 분기
  • 쿨타임 기반 밸런싱 : 프레임 독립적 공격 속도 제어
  • 상태 전환 검증 : 현재 상태에서 가능한 액션만 허용

 

1.2 Monster AI 반응형 전투 로직

void MonsterStateMachine::ProcessAI()
{
    // 1. 체력 체크 (최우선)
    if (m_monsterInterface && m_monsterInterface->GetHP() <= 0)
    {
        if (!IsInState(MonsterStateType::Death))
        {
            RequestStateChange(MonsterStateType::Death);
            return; // 사망 처리 후 다른 AI 로직 중단
        }
    }

    // 2. 상황별 전투 상태 전환
    switch (currentState)
    {
    case MonsterStateType::Trace:
        if (distanceToTarget <= m_attackRange)
        {
            RequestStateChange(MonsterStateType::Attack);
        }
        break;
        
    case MonsterStateType::Attack:
        if (distanceToTarget > m_attackRange)
        {
            RequestStateChange(MonsterStateType::Trace);
        }
        break;
    }
}

 

1.3 타이밍 기반 카운터 시스템

void PlayerStateMachine::CheckCounterSkillCompletion()
{
    // Counter 상태일 때만 완료 체크
    if (IsInState(PlayerStateType::Counter))
    {
        // 이미 완료 체크를 했으면 건너뛰기 (중복 방지)
        if (m_counterSkillCompletionChecked)
            return;

        bool counterSkillCompleted = false;
        OnCounterSkillCompleted(counterSkillCompleted);  // 델리게이트 호출

        if (counterSkillCompleted)
        {
            cout << "카운터 스킬 완료 감지 - Wait 상태로 전환" << endl;

            m_counterSkillCompletionChecked = true;  // 플래그 설정

            // 대기 상태로 복귀
            RequestStateChange(PlayerStateType::Wait);

            if (m_animationStateMachine)
            {
                m_animationStateMachine->RequestStateChange(AnimationStateType::Wait);
            }
        }
    }
    else
    {
        // Counter 상태가 아니면 플래그 리셋
        m_counterSkillCompletionChecked = false;
    }
}

 

1.4 스킬 기반 방어 시스템 ( 비앙카 W 스킬 )

void BiancaWSkill::PlaySkill()
{
    if (m_isPlaying) {
        // 너무 빠르게 다시 눌러 해제되는 것 방지
        if (m_repeatKey < 0.25f)
            return;
            
        // 이미 실행중일 경우 -> W스킬 끝내기
        m_coffin->SetActive(false);
        m_isPlaying = false;

        // 방어력 보너스 해제
        PlayerStatus status = m_playerObject->GetStatus();
        m_playerObject->SetDefense(status.defense - 50);
        m_repeatKey = 0.f;
        m_elapsedTime = 0.f;

        SOUND->PlaySound(m_soundEnd, 1, 0.5f);
        SkillEnd();
    }
    else if(m_isPlaying == false && m_skillcurCooldown <= 0){
        // 스킬 활성화
        m_coffin->SetActive(true);
        m_isPlaying = true;

        // 방어력 보너스 적용 (+50 방어력)
        PlayerStatus status = m_playerObject->GetStatus();
        m_playerObject->SetDefense(status.defense + 50);
        SOUND->PlaySound(m_soundStart, 1, 0.5f);
    }
}

 

특징

  • 우선순위 기반 판단 : 생존 -> 전투 -> 탐지 순서로 로직 처리
  • 거리 기반 상태 전환 : 실시간 거리 계산으로 동적 전투 상태 변경
  • 중복 방지 로직 : 이미 해당 상태일 때 불필요한 전환 요청 방지

 

2. 피킹 시스템

2.1 쿼드 트리 기반 타겟 선택

shared_ptr<GameObject> PlayerStateMachine::GetPickedTargetAtMouse()
{
    // SceneObjectManager의 쿼드트리 피킹 시스템 활용
    Ray ray = CURSCENE->GetObjectManager()->CreateRayFromScreen(Vec2(mousePos.x, mousePos.y), cam);
    
    auto quadTree = CURSCENE->GetQuadTree();
    vector<shared_ptr<GameObject>> candidates = quadTree->Query(ray, cam);

    float minDistance = FLT_MAX;
    shared_ptr<GameObject> closestTarget = nullptr;

    for (auto& obj : candidates)
    {
        // 화면 좌표 유효성 검사
        RECT objBounds = quadTree->GetObjectScreenBounds(obj, cam);
        if (screenCenterX < 0 || screenCenterY < 0) continue;

        // Ray 교차 검사로 정밀 피킹
        float distance = 0.f;
        if (obj->GetCollider()->Intersects(ray, distance))
        {
            if (distance < minDistance)
            {
                minDistance = distance;
                closestTarget = obj;
            }
        }
    }
    return closestTarget;
}

 

특징

  • 공간 분할 가속 : 쿼드 트리로 O(n) -> O(log n) 검색 최적화
  • 2단계 필터링 : 바운딩 박스 -> Ray 교차 검사
  • 거리 기반 우선순위 : 가장 가까운 대상 우선 선택

 

3. 데미지 처리 시스템

3.1 공식 기반 데미지 계산

void Player::Damaged(shared_ptr<Monster> _attacker, int _damage)
{
    // 카운터 어택 조건 검사
    if (m_playerStateMachine->GetCurrentState() == PlayerStateType::Skill_2)
    {
        // 방어 스킬 사용 중 피격시 카운터 발동
        m_playerStateMachine->GetState(PlayerStateType::Counter)->SetTarget(_attacker);
        m_playerStateMachine->RequestStateChange(PlayerStateType::Counter);
        GetAnimationStateMachine()->RequestStateChange(AnimationStateType::Counter);
    }

    // 데미지 계산 공식
    int32 baseAttack = _damage * 100;
    int32 baseDefense = info.defense + 100;
    int32 finalDamage = baseAttack / baseDefense;
    
    SetHP(info.hp - finalDamage);
}

void Monster::Damaged(shared_ptr<GameObject> _attacker, int _damage)
{
    // 피격시 즉시 어그로 설정
    if (m_monsterStateMachine && _attacker && 
        _attacker->GetType() == OBJECTTYPE::PLAYER)
    {
        m_monsterStateMachine->SetTarget(_attacker);
        
        // 대기 상태였다면 즉시 추적으로 전환
        if (!m_monsterStateMachine->IsInState(MonsterStateType::Attack) && 
            !m_monsterStateMachine->IsInState(MonsterStateType::Trace))
        {
            m_monsterStateMachine->RequestStateChange(MonsterStateType::Trace);
        }
    }
}

 

 

특징

  • 어그로 시스템 : 피격시 즉시 공격자를 타겟으로 지정

 

4. 경험치 및 성장 시스템

4.1 이벤트 기반 경험치 처리

void Player::Start()
{
    // 경험치 보상 이벤트 구독
    EVENT->Subscribe(EventType::MONSTER_EXP_REWARD, 
        [this](shared_ptr<EventData> eventData) {
            auto expData = dynamic_pointer_cast<ExpRewardEventData>(eventData);
            if (expData && expData->m_killer.get() == this) {
                // 자신이 처치한 몬스터일 때만 경험치 획득
                SetCurExp(m_status.curExp + expData->m_expAmount);
            }
        });
}

void Monster::Death(shared_ptr<GameObject> killer)
{
    // 경험치 보상 이벤트 발생
    int expReward = CalculateExpReward();
    auto expEvent = make_shared<ExpRewardEventData>(killer, expReward, shared_from_this());
    EVENT->QueueEvent(expEvent);
}

void Player::LevelUp()
{
    // 다중 레벨업 처리
    int shouldLevelUpValue = 0;
    while (m_status.curExp >= m_status.curExpLimit)
    {
        m_status.curExp -= m_status.curExpLimit;
        m_status.curExpLimit += m_growStatus.ExpLimit;
        shouldLevelUpValue++;
    }
    
    // 스킬 포인트 및 스탯 증가
    m_status.availableSkillPoints += shouldLevelUpValue;
    ApplyLevelUpStats(shouldLevelUpValue);
}

 

특징

  • 이벤트 기반 분리 : 이벤트 매니저를 통해 경험치 획득과 레벨 업 로직 분리
  • 다중 레벨업 지원 : 대량 경험치 획득시 한번에 여러 레벨 처리
  • 실시간 UI 동기화 : 상태 변경시 UI 업데이트

 

5. Delegate 기반 이벤트 시스템

// PlayerStateMachine.h - 델리게이트 선언
public:
    // 스킬 완료 체크 델리게이트들
    Delegate::Delegate<bool&> OnQSkillCompleted;
    Delegate::Delegate<bool&> OnWSkillCompleted;
    Delegate::Delegate<bool&> OnESkillCompleted;
    Delegate::Delegate<bool&> OnRSkillCompleted;
    Delegate::Delegate<bool&> OnCounterSkillCompleted;

// LumiaIsland.cpp - 델리게이트 등록 예시
m_player->GetPlayerStateMachine()->OnCounterSkillCompleted.Push([this](bool& completed) {
    // 카운터 스킬 완료 상태를 확인하는 로직
    completed = IsCounterSkillCompleted();
});

 

 

6. 통합 입력 처리 시스템

void PlayerStateMachine::ProcessInput()
{
    // 1. 스킬 입력 (최우선)
    HandleSkillInput();
    
    // 2. 제작 입력
    HandleCraftInput();
    
    // 3. 통합 우클릭 처리 (공격 vs 이동)
    HandleRightClickInput();
}

bool PlayerStateMachine::IsCurrentStateMovable() const
{
    if (!m_currentState)
        return true;
    
    return m_currentState->IsMovable();  // 각 상태별 이동 가능 여부
}

bool PlayerStateMachine::IsValidAttackTarget(shared_ptr<GameObject> target)
{
    if (!target) return false;
    if (!target->GetCollider()) return false;
    return target->GetType() == OBJECTTYPE::MONSTER;
}

 

입력 시스템 특징

  • 우선순위 기반 처리 : 스킬 -> 제작 -> 이동 / 공격 순서로 입력 해석
  • 상태 인식 입력 : 현재 상태에 따라 허용되는 액션 제한
  • 타입 안전 타겟팅 : 컴파일 타임 타입 검증으로 잘못된 타겟팅 방지

 

결론

  1. 상태 기반 전투 아키텍쳐
    1. Player와 Monster 각각 독립적인 상태 머신
    2. 이벤트 기반 상태 전환으로 느슨한 결합
    3. 상태별 허용 액션 제한으로 안정성 보장
  2. 성능 최적화된 피킹 시스템
    1. 쿼드트리 공간 분할로 O(log n) 검색
    2. 2단계 필터링으로 정확도와 성능 균형
  3. 지능형 전투 AI
    1. 피격시 즉시 어그로 시스템
    2. 거리 기반 동적 상태 전환
    3. 우선순위 기반 행동 결정
  4. 이벤트 기반 확장성
    1. 데미지 , 경험치 , 레벨업 모두 이벤트로 분리
    2. 느슨한 결합으로 시스템 간 독립성 확보
    3. 쉬운 기능 확장
  5. UI 도익화
    1. 상태 변경 즉시 UI 자동 업데이트
    2. 비율 기반 자원 관리로 일관성 유지