UE Projects/Sigh Of Jay 개발일지 (심화)

9 - 3. FSM 기반으로 Attack, Evade, Jump 구조 변경

권재현의 포트폴리오 2025. 6. 8. 17:36

< 개요 >

  • 지금까지 만들었던 Attack, Evade, Jump는 FSM을 기반으로 하지 않았다
  • 그래서 현재 여러가지 부분들에 있어서 충돌이 발생하고 있다
  • 이러한 충돌하는 부분들을 깔끔하게 정리하기 위해, FSM 기반으로 구조를 조금 변경하려 한다

 

현재 발생 중인 충돌
공격 중 회피가 가능 - 공격이 정상적으로 종료되지 않아서 문제가 발생
- 공격 관련된 변수들이 초기화가 안되어서 이후 공격 기능이 막힘

- 보완하여 채택할 기능
점프 중 회피가 가능 - 점프 도중의 회피로 방향키 입력이 제대로 먹히지 않아서 회피 방향이 제멋대로임

- 삭제할 기능. 점프 중에는 회피가 안되도록 막아야 함
회피 중 공격이 가능 - 회피가 정상적으로 종료되지 않아서 문제가 발생
- 회피 관련된 변수들이 초기화가 안되어서 이후 회피 기능이 막힘

- 삭제할 기능. 회피 중에는 공격이 안되도록 막아야 함
회피 중 점프가 가능 - 삭제할 기능. 회피 중에는 점프가 안되도록 막아야 함

< 구조 변경 1 >

StateSyncComponent::OnStateChanged() / switch문에 Jump 추가

 

SOJCharacterBase / 헤더파일 / ECharacterState에 Jump, Attack 추가

 

SOJCharacterBase / 소스파일 / CanChangeState()에는 Evade, Hit일 때의 조건 추가, SetCharacterState()에는 상태 동기화 코드 추가

 

  • CanChangeState()
    • Evade, Hit 상태에서는 애니메이션 재생 후, 원래의 Idle 상태로 되돌아갈 수 있어야 하므로 Idle로의 전환은 열어둠

 

SOJPlayerCharacter / 소스파일 / Input_Jump(), Attack() 함수에 각각 실행 조건 추가

 

  • Input_Jump() : 회피 및 공격 중에는 점프가 불가능하도록 막아둠
  • Attack() : 피격, 회피 및 점프 중에는 공격이 불가능하도록 막아둠

 

SOJPlayerCharacter / 소스파일 / Evade() 함수에 조건 추가

 

  • Evade() : 점프 중에는 회피가 불가능하도록 막아둠

< 구조 변경 2 >

  • 위에서 구조를 변경함으로써, 충돌하던 문제들이 해결되었다
  • 그런데 여기서 다른 문제점이 또 존재하는 것을 발견하였다. 이를 수정해보자.

 

발생된 문제
Attack, Jump 이후 CharacterState 복귀 불가 - CharacterState가 Idle로 돌아가는 코드가 없어서, Attack과 Jump는 각각의 행동 이후, 그 상태 그대로 고정되어있음

- 이로 인해, 위에서 정해둔 조건들 때문에 그 다음 동작이 막혀있는 경우가 발생함

- 이를 해결하기 위해, Attack 몽타주와 Jump_Land 애니메이션에 SetStateIdle 노티파이를 박아넣자
보완할 부분
위에서 각 조건들이 너무 지저분함 - 조건들을 따로 각각 CanJump, CanEvade 등으로 묶어서 관리하여 가독성을 높여야 할 것 같다

 

SOJAN_SetStateIdle / 소스파일 / SetCharacterState 코드 추가

 

Attack 애니메이션 몽타주 / 노티파이에 트랙2를 추가하여 이름을 SetState Section으로 바꾸고, 각각의 공격들 끝에 SetStateIdle 노티파이를 추가해줌

 

  • Jump_Land 애니메이션에는 이미 SetStateIdle 노티파이가 박혀있으므로, 위에서 SetStateIdle 노티파이 클래스에 SetCharacterState코드를 추가한 것으로 자연스럽게 해결됨

 

SOJCharacterBase / 헤더파일 / bool 함수 3개 추가

 

SOJCharacterBase / 소스파일 / bool 함수들 작성

 

SOJPlayerCharacter / 소스파일 / 각 조건들을 위에 만든 bool 함수로 깔끔하게 바꿔줌


< 구조 변경 3 >

  • 이제 마지막으로 2가지만 더 수정하면 된다
    1. 캐릭터의 피격 상태를 FSM과 연결시키기
    2. 공격 도중에 회피 시, 공격과 관련된 변수들을 초기화 시켜주기

 

1. SOJBA_Common::PlayHitReaction()

 

  • 그런데 이 구조는 DIP 원칙에 어긋나지 않나? 하위 계층에서 인터페이스를 통하지 않고 구체적으로 상위 계층을 #include 하여 의존하고 있잖아?
    • 지금 구조에서는 지양할 필요는 있으나, 굳이 DIP를 지켜야만 하는 건 아님
    • 현재 구조는 SOJBA_Common::PlayHitReaction() 안에서 SetCharacterState() 딱 한 줄만 사용하고 있음
    • 이 한 줄을 위해서 인터페이스를 만들면 오히려 코드 분산, 복잡도 상승으로 안좋다
    • 그러므로 현재 상황은 유지보수의 비용이 낮고, 확장성 요구도 없으며, 호출도 제한적이라서 딱 허용 가능한 DIP 위반 케이스임
    • 이렇게 간단하고 국소적인 경우에는 DIP를 실용적으로 생략해도 됨
    • 그러나 구조가 복잡해지고 재사용이 많아지는 순간, 언제든 인터페이스로 리팩토링 할 준비는 해둬야 한다.

 

2-1. SOJAttackComponent / 헤더파일 / 공격 취소 함수를 public으로 선언

 

2-2. SOJAttackComponent / 소스파일 / OnAttackEnded()에서 공격 종료 시 초기화되는 변수들을 마찬가지로 OnAttackCanceled()에서도 초기화 시켜주자.

 

2-3. SOJCharacterBase::SetCharacterState() / 여기서 상태가 전환될 때, 공격이 취소된걸 감지하여 공격이 취소된 경우에는 OnAttackCanceled()를 호출해서 공격 관련된 변수들을 초기화시켜주자.