프로젝트 VV 개발일지(0)/다이어리

25.05.19 Ai기반 델리게이트와 PlayerControl을 함께 사용하고 싶다

누가 뭐래도 개발자 2025. 5. 20. 07:45

2025.05.19

오늘의 목표

Day 3 (5월 19일, 월): 핵심 상태 수치 시스템 (1/2) 해야하는데 어제 싼 똥 치움

작업 내용

  • 행동 - 애니메이션 설계 명세화
  • 가속도 문제 해결 -> 액션 상태 enum 만들기 ( ECharacterActionState )
  • 분리형 컨트롤러 구조 설계

버그 / 문제점

  • UHT001: Unable to find 'class', 'delegate', 'enum', or 'struct' with name 'FPathFollowingResult'

액션 상태 Enum - 델리게이트 구현 중, 델리게이트 수신함수를 선언한 부분에서 발생.

OnAIMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result

AddUObject  방식으로 델리게이트를 바인딩 할 경우, 위와 같이 선언해야 함.

 UFUNCTION()
 void OnAIMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result);

문제 원인: 인수 FPathFollowingResult 와 UFUNCTION() 같이 사용.

-> UFUNCTION() 는 UObject 시스템이 인지할 수 있는 타입이여야 하는데, FPathFollowingResult 는 c++의 일반 구조체이기에 불가한 것. 

( GC 추적 문제는, UFUNCTION  안써도 델리게이트가 UObject 에 바인딩 되면 가능해서 문제 없음)

 

  • 델리게이트 호출되지 않음

애니 블루프린트에서 SouldMove 조건을 Moving으로 바꾸니 동작을 하지 않음, Moving = false.

출력 돼야할 로그는 5개. 

시작 시: PlayerCharacter BeginPlay: CurrentActionState set to Idle.

FPathFollowingResult 이동 종료 시: APlayerCharacter::OnAIMoveCompleted - idle

SetCharacterActionState 호출 시: CharacterActionState Changed: From %d To %d

이동 조건 만족 상태에서 폰 캐스팅 성공: moving

이동중: VVPlayerController: SetDestination to %s. Character state set to Moving

APlayerCharacter::OnAIMoveCompleted - idle 누락

SetCharacterActionState 호출 시: CharacterActionState Changed: From %d To %d는 처음에만 출력. 

 

조건 제외하고 출력. 

1: idle, 2: moving

moving에서 idle 변환이 안됨.

-> void APlayerCharacter::OnAIMoveCompleted(FAIRequestID RequestID, const FPathFollowingResult& Result) 쪽에 문제가 있는 것.

 

플레이어 컨트롤러를 ai 컨트롤로 형변환하는 것이 불가능하기 때문에 실패한 것이었음. 

 

허나, 기본 설정은 ai컨트롤로 되어있다. 

 


그럼, 왜 문제가 있던걸까?

SimpleMoveToLocation의 주체가 this 본인이기 때문.

AVVPlayerController에게는  PathFollowingComponen가 없어서 델리게이트가 호출되지 않는 것. 

 

Aicon으로 변경.

 

하지만, 여전히 컨트롤러를 플레이어 컨트롤러로 잡고있다. 

 

빈 레벨에 bp플레이어 캐릭터 배치하고 테스트.

처음에는 ai로 잡고 델리게이트도 성공한다.

그러나, 다시 AVVPlayerController로 잡아서 문제 발생. 

 

현재, 엔진 제공 게임 모드인 게임모드 베이스를 상속한 게임 모드 사용 중.

이 경우, 게임모드의 로직이 디폴트 폰을 생성하면서 디폴트 컨트롤러를 매칭해준다고 한다. 

 

빈레벨에서 로그 2개가 띄워지고 하나는 성공, 하나는 실패한 이유

-> 직접 배치한 bp는 ai컨트롤이었지만, 게임모드에 의해 자동 생성된 bp는 플레이어 컨트롤이었기 때문. 

 

 

 

  • 플레이어 캐릭터의 컨트롤러 결정 문제
더보기

1. AVVPlayerController를 컨트롤러로 하여 이동 델리게이트를 직접 다룸:

엔진에서 Ai에 지원하는 기능을 사용해야 할 때, 알 수 없는 오류가 날 것 것 같다. 지금의 나는 그 상황을 해결할만큼의 실력이 아니고, 시간도 없다.

 

2. AAIController를 컨트롤러로 사용:

내가 구현하고자 하는 VV는 1인 조작이고, 플레이어의 상호작용에서 오는 현상들이 연계되어 있는데, ai를 통하게 되면 나의 의도가 제대로 구현되지 않을 것 같아서 걱정됨.

 

결국, 선택이 어려운 이유는 모르는 데에서 오는 공포인 것.

 

3. 기존 + AAIController를 전달용으로 사용. 

PlayerCharacter AAIController 의해 Possess되고

AVVPlayerController 입력/명령/UI 담당하는 대상 으로 PlayerCharacter 참조(게임모드 디폴트 컨트롤러)

언리얼은 이러한 분리된 제어구조가 허용된다고 한다. 

그래서 AVVPlayerController::GetPawn()은 PlayerCharacter를 반환하고, PlayerCharacter::GetController()는 AAIController를 반환하는 상황 성립.

 

이동: AVVPlayerController- 겟 폰 - 폰의 겟 컨트롤러 

그외 행동: AVVPlayerController에서 PlayerCharacter 함수 호출 방식 

 

3번 방식을 사용하고 싶지만 상충되는 문제가 있다. 

폰 설정에서 dissble하면 플레이어 컨트롤러는 폰을 참조못해서 get 하면 nullptr을 갖는다.

그런데 dissble안하면 ai컨트롤러가 빙의 하지못한다. 

3번의 분리형 컨트롤러 구조는 유효해 보인다.

게임모드 디폴트 컨트롤러: VVPlayerController(dissbled 이여도 플레이어의 입력은 여기서 받는다. )

Possess: AIController

여전히 이동은 AIController 이용한 간접 호출, 그외는 VVPlayerController에서 캐릭터 함수 직접 호출. 

우려되는 건, 언리얼 권장 방법이 아니라는 것 + 언리얼 사용법도 제대로 모르는 사람이라는 점이지만, 내 의도대로 동작할 다른 방법을 모르겠다. 사실 참조로 캐릭터 조작하는 건 일반적인 일이잖아 쫄지말자. 

 

문제 생기면, ai컨트롤러 기반 델리게이트인 moving만 tick에서 조건 체크하면 됨.


ai컨트롤러 기반 델리게이트 문제는 이렇게 해결 가능하다.

 

1. 게임모드 설정하여 Possess 재설정 방지, AIController  고정. 

Default Pawn Class: None설정 + 직접 배치 

 

 

추론이 맞았다.

 

 

2. VVPlayerController가 GetPawn()이 아닌, 다른 방법으로 캐릭터 참조하도록 설계.

APawn* MyPlayerPawn = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0); // 플레이어 인덱스 0의 캐릭터 가져오기

 

폰도 갖고있고, ai컨트롤러도 갖고있다. 

다만, 까만화면 + 이동 확인 x.

 

-> GetPlayerCharacter()는 PlayerIndex의 플레이어 컨트롤러가 직접 빙의하고 있는 폰을 반환하려고 시도하기에 nullptr이 반환 된 것. 

 

3. 맵에 있는 플레이어블을 가져오는 형식으로 바꿈 + SetViewTargetWithBlend(CommandedPlayerCharacter)

뷰가 캐릭터를 보여주고, 폰도 캐릭터로 잘 채워짐.

 

드디어 원하던 컨트롤러 적용됨.

근데 이동이 예사롭지 않아서 자고 일어나서 뷰 - 카메라 - 마우스 좌표 부근을 확인해봐야 겠다. 

 

 

 

 

배운 것

 

  • 가속도 체크 -> AI 이동 체크: 플레이어가 목적지를 찍고 그곳까지 AI이동 중임을 체크하는 플래그. 틱 방식은 비효율적이기에 델리게리트 이용.
  • 델리게이트: 어떤 이벤트가 발생할 때, 지정된 함수를 호출해주는 시스템 -> 이벤트 기반 동작 연결 가능. (C++ 함수 포인터 + 이벤트 시스템) 즉, 언리얼의 콜백 시스템. 

1. 이벤트 조건을 체크할 곳에서 델리게이트 타입 선언

(ex: StatusComponent에  DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeathDelegate);)

2. 델리게이트 변수 선언

FOnDeathDelegate OnDeath;

3. 이벤트 발생 시점에 델리게이트 발동

if (HP <= 0)
{
    OnDeath.Broadcast();  
}

<델리게이트가 내장된 클래스는 1~3단계를 자동으로 처리해주기에 변수만 가져다 사용하면 된다.>

 

4. 외부 클래스( OnDeath  시 연동 돼야할 클래스)에서 델리게이트 수신 함수 정의

void HandleDeath() {사망 시 연출 등}; -> 이벤트 발생 시 호출 할 함수

5. BeginPlay등에서 델리게이트 연결

StatusComponent->OnDeath.AddDynamic(this, &APlayerCharacter::HandleDeath); -> 콜백 등록

------

델리게이트 함수

AddDynamic(): DECLARE_DYNAMIC_XXX 선언, UFUNCTION()  필요, 느림. 

AddUObject(): DECLARE_EVENT  선언, UObject 클래스 함수, 빠름.

 

  • 애니메이션 SouldMove 조건 변경

기존의 SouldMove 조건: 스피드 + 가속도

변경한 ShouldMove 조건:  CurrentActionState == Moving

 

다음에 할 일:

  • 메쉬/애니 회전 적용
  • StatusComponent 클래스 생성 및 APlayerCharacter에 부착 ([설계]아키텍처 설계.txt, [시스템]스테이터스와 시야시스템_v10.txt 참고)
  • HP, 에너지(기력) 상태 수치 변수 선언 및 기본 로직 (증가, 감소) 구현
  • 관련 UI (UMG) 기초 작업: HP 바, 에너지 바 표시