- AI Possess할 때는 코드의 옵션과 블루프린트의 옵션이 동일해야 인식된다. - 언리얼 엔진에서는 C++의 TakeDamage가 호출될 때, 블루프린트 쪽에 Event AnyDamage가 자동으로 브로드캐스팅된다. - 적 ai 모션을 구현할 때도 Anim Instance를 만드는게 좋다 - 캐릭터가 죽었는지 살았는지 변수 받아서, 적 애니메이션 블루프린트에 캐릭터가 살았는지, 죽었는지 판단해서 처리하는게 좋음 - 공격이나 그런 연속적인 호출? 느낌의 모션은 몽타주가 맞고, idle 사망 같은거는 state를 나눠서 처리하는게 좋음 - additive를 설정해줘야하는 이유
마지막 말
적 사망을 구현할 때, 사망 모션이 잘 되다가 갑자기 기본 포즈로 바뀌는 현상을 발견했다.
원인은 몽타주로 사망을 구현했기 때문인데, 콤보 공격이나 피격 같은 부분은 연속적으로 특정 이벤트가 호출될 때마다 발생할 수 있기 때문에 몽타주로 구현하는 것이 유리하다.
하지만 Idle이나 사망 모션 같은 경우에는 하나의 애니메이션이 지속되는 형태이기 때문에 이런 경우에는 State Machine을 사용하는 것이 더 효율적이라는 것을 알게되었다.
저번 포스팅에서는 NavigaitonSystem을 사용해서 적의 랜덤 이동을 구현했는데, 이번에 Behavior 시스템을 공부하면서 얘를 이용하는 것으로 이동 방식을 바꿔주었다.
물론 NavigaitonSystem을 계속 써도 되지만 아직 코드로 접근하는 방법에 대해 익숙치 않아 에디터와 같이 사용하는게 더 이해가 잘 될 것 같기 때문이다.
먼저 BehaviorTree와 BlackBoard의 이론부터 공부했다.
빠르게 구현하면서 이해해도 좋지만 그러면 뭐부터 해야할 지 꼬일 것 같기도 하고 확실하게 이해를 하고 가야 다른 예제에서 한 것들을 나에게 맞게 응용할 수 있을 것 같았다.
BehaviorTree
BehaviorTree는 AI의 행동 로직을 계층적으로 구성할 수 있게 도와주는 시각적 스크립팅 도구이다.
AI가 상황에 따라 어떤 행동을 취할지 결정하는 노드인 Select, Sequence, Decorator 등을 배치하여 복잡한 행동패턴을 눈으로 쉽게 설계할 수 있다.
시퀀스(Sequence)
- 자식 노드를 순서대로 실행 - 모든 자식 노드가 성공해야 이 시퀀스 자체가 성공했다고 판정됨
셀렉터(Selector)
- 자식 노드를 순서대로 평가 - 하나라도 성공하면 전체가 성공했다고 판정
데코레이터(Decorator)
- 특정 조건을 검사 - 행동을 반복하거나, 반전 등의 추가 제어를 위해 다른 노드에 부착해서 실행하는 형태
서비스(Service)
- BehaviorTree에 특정 브랜치가 활성화되어 있는 동안 주기적으로 실행 - Blackboard 값을 업데이트하거나 지속적인 환경 체크를 수행
태스크(Task)
- 실제 행동을 실행하는 가장 기본적인 노드 - 이동, 공격, 애니메이션 재생 등의 원하는 행동에 대한 블루프린트 연결로 실행 - Success, Failure, Running 상태를 반환하여 상위 노드에 전달
BlackBoard
Blackboard는 BehaviorTree를 동작하는데 필요한 데이터를 저장하는 키-값 데이터 저장소 같은 역할이다.
예시로 위치나 상태, 감지 여부 등을 기록해두면 BehaviorTree의 각 노드가 이 데이터를 참조해서 조건을 판단하고 각 행동을 실행시키는데 활용한다.
이제 이론은 이해했으니 실제 프로젝트에 적용해볼 것이다.
베이스 세팅
Content 폴더 밑에 AI 폴더를 만들어서 BehaviorTree와 Blackboard를 생성해주었다. (폴더 위치는 자신의 폴더 구조에 맞게 알잘딱깔센)
그리고 NewKey를 눌러서 내가 사용할 값들을 추가해주었다.
나는 이렇게 IsOverRange와 타겟의 위치인 PlayerLocation, 시야를 벗어났는지를 확인하는 IsTargetFindRange를 추가해줬다. (IsOverRange와 DefaultLocation은 추후 적의 이동 반경을 벗어났을 때를 구현하기 위해 미리 만들어두었으니, 굳이 생성하지 않아도 된다)
그리고 BehaviorTree를 켜서 만들어둔 블랙보드를 연결시켜준다.
이제 BehaviorTree에서 블랙보드의 값을 알 수 있게 되었다!
추격을 구현할 시퀀스 노드를 하나 생성하였다. (파란색은 데코레이션 노드인데 지금은 일단 무시, 이따가 추가할 것이다)
전체 노드는 다음과 같다.(밑에서 과정을 설명 예정)
그리고 태스크를 추가할 때는 상단의 New Task를 선택하면 된다.
Patrol 구현
이렇게 먼저 시퀀스를 연결시켜주고 이름은 한 눈에 파악하기 쉽게 Patrol Sequence로 변경해줬다. (아 참고로 가장 왼쪽에 있는 move to 노드는 무시해도 된다. 지금 추가하는 내용인데 정리를 깜빡했다..ㅎㅎ)
그리고 순찰을 수행할 태스크를 하나 생성해주었다.
태스크 노드는 이렇게 구상하였다.
Get Random Reachable Point in Radius 라는 노드가 있어서 사용해보았다.
설정한 반경에 맞는 랜덤 포인트를 반환해주는 노드다.
그 반환값을 ai move to에 연결해주었고, 값이 잘 반환이 되었다면 Success를 반환시켜줬다.
그리고 자연스러운 움직임을 위해 적 AI가 움직이고 2초 동안 대기하는 Wait 노드를 추가해주었다.
Chase 구현
Chase하는 태스크를 하나 생성해주고,
블루프린트를 다음과 같이 연결해줬다.
플레이어 컨트롤러를 가져와서 Target Actor를 플레이어로 연결해줬다.
Acceptance Radius는 원하는 값으로 입력해주고 finish excute에서는 항상 부울 체크 확인하는 것을 잊지않기.
컴파일 후 저장한다 <- 이건 그냥 노드 추가할 때마다 하게 습관이 되었다..
이렇게 Chase 태스크를 시퀀스에 연결시켜준다.
애니메이션 몽타주
이제 공격을 구현할건데 트리를 연결하기 전에 공격 애니메이션 몽타주를 만들어주었다.
근데 그 전에 그냥 State Machine으로 애니메이션을 연결하면되는데 왜 몽타주를 쓸까?
State Machine은 캐릭터의 상태에 따라 애니메이션을 지속적으로 블렌딩해준다.
몽타주는 특정 애니메이션을 동적으로 재생하고 블렌딩, 인터럽트, 부분 애니메이션 등을 지원한다고 한다.
내가 구현할 공격같은 애니메이션은 특정 조건에서만 재생하고 돌아갈거기 때문에 몽타주를 선택했다.
그리고 On Completed와 On Interrupted에 Call On Attack End를 연결해주고 디버그를 위해 Print String을 연결해주었다.
Attack 태스크에 들어가서 다음과 같이 연결해주었다.
적 캐릭터에 있는 Attack 함수를 불러와서 실행시켜주었다.
만들어둔 공격 태스크를 behaviorTree로 가져와서 연결해줬다. (추격의 우선순위를 더 높게 잡았기 때문에 추격의 오른쪽으로 배치해줬다)
애니메이션 연결
애니메이션은 파라곤 에셋에 들어있던 블렌드 스페이스를 이용해주었다.
해당 애니메이션을 살펴보니까 Speed에 따라 idle, walk, run이 출력되는 것을 확인할 수 있었다.
그래서 speed 값만 연결시켜주고, 몽타주 슬롯을 연결해주었다.
몽타주 클릭해서 내가 만든 슬롯을 설정해주면 끝!
최종 Animation Graph
최종적으로 애니메이션은 다음과 같다.
이벤트 그래프는 언리얼의 기본 캐릭터인 마네퀸의 노드들을 그대로 가져오고 위의 부분에서 Shuld Move를 set 해주는 노드만 추가해주었다.
결과
이렇게 캐릭터가 시야에 보이지 않으면 천천히 순찰하다가 캐릭터가 시야에 들어오면 추격 후 공격하는 것을 볼 수 있다
트러블슈팅
이슈 1번 -> 공격 애니메이션 몽타주 실행안되는 현상
상황 : 블루프린트는 디버깅을 해봤을 때 모두 잘 연결되는 것을 확인했지만 공격 모션이 안뜸..
작업 순서 1. enemyCharacter에서 커스텀으로 Attack 이벤트 노드 생성(몽타주 연결해줌) 2. 생성한 Attack 이벤트 노드를 attack 태스크에 연결 3. 비헤이비어트리에 attack 태스크 연결(IsTargetFineRange)가 true이면 추격 후 공격
해결 : 적 애니메이션 그래프에 몽타주를 저장한 슬롯 연결로 해결..
이슈 2번 -> 'D:\UE5_project\2nd-Team9-CH3-Project\Intermediate\Build\BuildRulesProjects\HelloWorldModuleRules\..\..\..\..\Plugins\Developer\RiderLink\Source\RiderLink\RiderLink.Build.cs' 소스 파일을 찾을 수 없습니다. 라는 에러 상황
상황 : visual studio를 사용하고 있고, 다른 팀원의 깃 브랜치에 들어갔다가 아무것도 안하고 다시 내 브런치로 전환했더니 이런 에러가 떴다...
원인 : 브랜치 바꾸면서 날 수도 있고, 다른 엔진의 버전을 사용하고 있어서 그런 거일수도 있고, 원인은 다양하다고 한다. 나 같은 경우에는 라이더를 사용하고 있는 브랜치로 왔다갔다하면서 꼬인 것 같다.
해결 : 프로젝트 폴더 내에서 Intermediate 폴더, Binaries 폴더, 비주얼스튜디오 솔루션을 삭제한 뒤, 다시 솔루션을 생성하고 빌드해줬더니 해결되었다.
마지막 말
- 코드로 작업할 때 할 일들을 TODO로 주석처리해서 관리했는데, 이렇게 하니까 뭐부터 해야할 지 헷갈렸다. 그래서 오늘 이후부터는 우선순위를 정해서 TODO 1, TODO 2 이런식으로 넘버링해서 관리하는 것이 좋을 것 같다.
- 애니메이션 적용할 때, 기본적인 것들을 놓쳐서 시간을 많이 소모했다.. 몽타주를 만들어놓고서 연결을 안했다던지.. 적의 속도로 애니메이션 상태를 바꾸는데, 애니메이션 이벤트 노드에서 이벤트 노드가 이상하게 들어가 있었다던지.. 차근차근 설계한 내용대로 차분하게 보는 것이 좋을 것 같다.
그래서 0의 카운트도 0이 다 맞았을 경우, 0이 다 틀렸을 경우 이렇게 2개의 상황만 추가해주면된다.
- 1등부터 6등까지 몇 개를 맞추면 되는지 map을 사용해서 키, 밸류를 설정 - lottos를 순회하면서 win_nums의 번호가 있는지 확인하고 있다면 최소 카운트 증가 - 그리고 0이면 0을 관리하는 카운트 증가 - 순회가 끝나고 최대 갯수에 최소 개수 + 제로 카운트 - 각 최대, 최소 개수의 밸류값을 answer에 넣어줌
과제를 하면서 이미 구현된 부분이 많지만, 제대로 이해되지 않은 부분이나 시간 때문에 쫒겨 깊게 보지 못한 부분들을 다시 이해하고 공부한다는 마음으로 작성하였다.
블루프린트도 많이 쓰고 생소한 클래스들도 많이 다뤄서 재밌었던 경험이었다.
구현해야 할 목록들
- 캐릭터 - 캐릭터 애니메이션 - 아이템 스폰 - 게임 흐름 설계 - 웨이브 설계 - UI 제작
아이템 스폰에서 현재 스폰될 볼륨까지 구현해놓았다.
이제 구현한 기능을 연결시켜줘야한다.
아이템 랜덤 생성 구현 과정
1. C++ 클래스를 상속받은 블루프린트를 만들어준다.
Create Blueprint class based on "클래스이름"을 클릭하면 해당 클래스를 기반으로 한 블루프린트가 생성된다.
아 참고로 이렇게 만든 블루프린트에서 여러 변수나 프로퍼티들을 수정하려면 리플렉션에 등록해줘야하고, 관련 매크로들을 채워줘야한다.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Spawning") // 에디터에서 수정할 수 있지만, 블루프린트 내에서는 값을 변경할 수 없음 또는 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawning") // 에디터와 블루프린트 모두 값 읽고 변경 가능
2. 생성한 블루프린트를 Level에 올린 후 맵의 영역에 맞게 크기를 조절해준다.
3. 생성할 아이템의 정보와 확률을 저장하고 있는 Item Data 구조체를 적용한다.
아이템의 생성확률을 코드에서 적용하면 아이템의 종류가 추가될 때마다 계속 수정해줘야하고 굉장히 번거로운 작업이 될 것이다.
그래서 언리얼에서 데이터 테이블이라는 구조체를 제공한다.
이 데이터 테이블은 저장된 정보를 csv나 json으로 관리해서 간편하게 정보를 불러오고 저장할 수 있는 장점이 있다!