Dev14 대화 시스템, 페이드인아웃, Enemy코드 리팩토링, 버그 고치기
대화 시스템
DialogueUI.h
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Engine/DataTable.h" // generated.h 위에 써야함
#include "DialogueUI.generated.h"
USTRUCT(BlueprintType)
struct FPlayerReplies // 플레이어 응답 구조체
{
GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly)
FText ReplyText;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
int32 AnswerIndex;
};
USTRUCT(BlueprintType)
struct FNPCDialogue : public FTableRowBase // 대화 데이터테이블 만들 때 행 구조 이거 선택해야함
{
GENERATED_USTRUCT_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FName CharacterName;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TArray<FText> Messages; // npc 대사
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TArray<FPlayerReplies> PlayerReplies;
};
UCLASS()
class YARO_API UDialogueUI : public UUserWidget
{
GENERATED_BODY()
protected:
UPROPERTY(meta = (BindWidget))
class UTextBlock* NPCText;
UPROPERTY(meta = (BindWidget))
class UTextBlock* CharacterNameText;
UPROPERTY(EditAnywhere, Category = "Dialogue")
float DelayBetweenLetters = 0.06f; // 다음 글자가 표시되는 텀
UPROPERTY(EditAnywhere, BlueprintReadWrite)
class USoundBase* SoundCueMessage; // npc 대사 나올 때 소리
public:
UFUNCTION(BlueprintImplementableEvent, Category = "Animation Events")
void OnAnimationShowMessageUI(); // 대화창 나타남 - 블루프린트에서 설정
UFUNCTION(BlueprintImplementableEvent, Category = "Animation Events")
void OnAnimationHideMessageUI(); // 대화창 사라짐 - 블루프린트에서 설정
UFUNCTION(BlueprintImplementableEvent, Category = "Animation Events")
void OnResetOptions(); // 플레이어 응답 리셋 후 안 보이게 - 블루프린트에서 설정
UFUNCTION(BlueprintImplementableEvent, Category = "Animation Events")
void OnSetOption(int32 Option, const FText& OptionText); // 플레이어 응답 보이게 - 블루프린트 설정
void SetMessage(const FString& Text); // 표시될 텍스트 설정
void SetCharacterName(const FString& Text); // 표시될 npc 이름 설정
void AnimateMessage(const FString& Text); // 텍스트 표시 시작
void InitializeDialogue(class UDataTable* DialogueTable); // 대화 테이블 초기화
UFUNCTION(BlueprintCallable)
void Interact(); // 다음 대사로 넘어가게 함
void DialogueEvents(); // 이벤트가 필요한 대사에 이용
private:
FString InitialMessage;
FString OutputMessage;
int32 iLetter;
TArray<FNPCDialogue*> Dialogue; // 대화의 행 배열
UFUNCTION()
void OnAnimationTimerCompleted(); // 글자 하나씩 표시함
public:
UPROPERTY(BlueprintReadWrite)
int32 CurrentState; // 0 = None, 1 = Animating, 2 = Text Completed, 3 = Dialogue is waiting for replies
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 SelectedReply; // 플레이어가 선택한 응답 번호
UPROPERTY(BlueprintReadWrite)
int32 RowIndex;
UPROPERTY(BlueprintReadWrite)
int32 MessageIndex;
//플레이어 대답 버튼들 Visibility 때문에 어쩔 수 없이 만든 변수
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int NumOfReply;
UPROPERTY(EditAnywhere)
class AMain* Main;
UPROPERTY(EditAnywhere)
class AMainPlayerController* MainPlayerController;
// 대화 끝나고 바로 대화 또 못하게끔(애니메이션 실행할 시간이 필요)
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bCanStartDialogue = true;
FTimerHandle TimerHandle;
};
DialogueUI.cpp
#include "DialogueUI.h"
#include "Components/TextBlock.h"
#include "Main.h"
#include "MainPlayerController.h"
#include "Kismet/GameplayStatics.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "YaroCharacter.h"
#include "AIController.h"
void UDialogueUI::SetMessage(const FString& Text)
{
if (NPCText == nullptr) return;
NPCText->SetText(FText::FromString(Text)); // FString을 FText로 변환해서 SetText의 매개변수로 넣음
}
void UDialogueUI::SetCharacterName(const FString& Text)
{
if (CharacterNameText == nullptr) return;
CharacterNameText->SetText(FText::FromString(Text));
}
void UDialogueUI::AnimateMessage(const FString& Text)
{
CurrentState = 1; // 텍스트 표시되는 중
InitialMessage = Text;
OutputMessage = "";
iLetter = 0;
NPCText->SetText(FText::FromString(""));
// 대화 데이터 테이블의 행에서 캐릭터 이름 가져오기
CharacterNameText->SetText(FText::FromString(Dialogue[RowIndex]->CharacterName.ToString()));
GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &UDialogueUI::OnAnimationTimerCompleted, 0.2f, false);
}
void UDialogueUI::OnAnimationTimerCompleted()
{
GetWorld()->GetTimerManager().ClearTimer(TimerHandle);
OutputMessage.AppendChar(InitialMessage[iLetter]); // 한 글자씩 뒤에 더함
NPCText->SetText(FText::FromString(OutputMessage)); // 대사 업데이트 -> 한글자씩 늘어나는 것처럼 보이게 됨
if (SoundCueMessage != nullptr) // 한글자씩 업데이트될 때마다 텍스트 소리 나옴
UAudioComponent* AudioComponent = UGameplayStatics::SpawnSound2D(this, SoundCueMessage);
if ((iLetter + 1) < InitialMessage.Len()) // 표시할 글자가 남았으면
{
iLetter += 1;
GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &UDialogueUI::OnAnimationTimerCompleted, DelayBetweenLetters, false);
}
else
{
CurrentState = 2; // 현재 대사 다 표시함
}
}
void UDialogueUI::InitializeDialogue(UDataTable* DialogueTable)
{
if (Main == nullptr)
Main = Cast<AMain>(UGameplayStatics::GetPlayerCharacter(this, 0));
if (MainPlayerController == nullptr)
MainPlayerController = Cast<AMainPlayerController>(Main->GetController());
CurrentState = 0; // 상태 초기화, 아무 상태도 아님
CharacterNameText->SetText(FText::FromString(""));
NPCText->SetText(FText::FromString(""));
OnResetOptions(); // 플레이어 응답 안 보이게
Dialogue.Empty(); // 원래 있던 (이전)대사 행 배열 비움
for (auto it : DialogueTable->GetRowMap())
{
FNPCDialogue* Row = (FNPCDialogue*)it.Value;
Dialogue.Add(Row); // 새로운 대사 행들 추가
}
if (Dialogue.Num() > 0)
{
RowIndex = 0;
if (Dialogue[RowIndex]->Messages.Num() > 0) // 대사 행의 메세지가 있으면
{
MessageIndex = 0;
OnAnimationShowMessageUI(); // 대화창 나타남
AnimateMessage(Dialogue[RowIndex]->Messages[MessageIndex].ToString()); // 대사 나타나기
}
}
}
void UDialogueUI::Interact() // 다음 대사로 넘어가기
{
if (CurrentState == 1) // The text is being animated, skip
{
GetWorld()->GetTimerManager().ClearTimer(TimerHandle);
NPCText->SetText(FText::FromString(InitialMessage)); // 현재 대사 바로 표시
CurrentState = 2;
}
else if (CurrentState == 2) // Text completed
{
// Get next message
if ((MessageIndex + 1) < Dialogue[RowIndex]->Messages.Num()) // 같은 npc의 다음 대사
{
MessageIndex += 1;
DialogueEvents(); // 대사 이벤트 확인 후 대사 표시
}
else // npc 대사 끝남
{
NPCText->SetText(FText::FromString(""));
if (Dialogue[RowIndex]->PlayerReplies.Num() > 0) // 플레이어 응답 있으면
{
OnResetOptions();
NumOfReply = Dialogue[RowIndex]->PlayerReplies.Num(); // 응답 개수
SelectedReply = 0; // 플레이어가 선택한 응답 초기화
for (int i = 0; i < Dialogue[RowIndex]->PlayerReplies.Num(); i++)
{
OnSetOption(i, Dialogue[RowIndex]->PlayerReplies[i].ReplyText); //응답 보이게 함
}
CurrentState = 3; // 플레이어 응답 기다림
}
else // 플레이어의 응답이 존재하지 않으면
{
RowIndex += 1; // 다음 대사 행
if ((RowIndex >= 0) && (RowIndex < Dialogue.Num())) // 다음 npc 대사
{
MessageIndex = 0;
DialogueEvents();
}
else // 플레이어 응답 없고 다음 npc 대사도 없으면 대화 종료
{
bCanStartDialogue = false; // (수동으로) 대화 시작 못함
MainPlayerController->RemoveDialogueUI(); // 대화창 없어짐
CurrentState = 0; // 상태 초기화
}
}
}
}
else if (CurrentState == 3) // 플레이어 응답 선택한 상태
{
// 플레이어 응답에 따라 RowIndex 바뀜
RowIndex = Dialogue[RowIndex]->PlayerReplies[SelectedReply].AnswerIndex;
OnResetOptions(); // 응답 리셋
if ((RowIndex >= 0) && (RowIndex < Dialogue.Num())) // npc 대사 있으면
{
NPCText->SetText(FText::FromString(""));
MessageIndex = 0;
DialogueEvents(); // 이벤트 확인 후 대사 표시
}
else // npc 대사 없으면 대화 종료
{
bCanStartDialogue = false;
CurrentState = 0;
MainPlayerController->RemoveDialogueUI();
}
}
}
void UDialogueUI::DialogueEvents() // 대사 이벤트 확인
{
int DNum = MainPlayerController->DialogueNum;
if (DNum == 0) // First Dialogue (cave)
{
if (RowIndex < 10 && Main->CameraBoom->TargetArmLength > 0) // 1인칭시점이 아닐 때
{
AnimateMessage(Dialogue[RowIndex]->Messages[MessageIndex].ToString());
return;
}
switch (RowIndex) // 1인칭 시점일 때 카메라 회전
{
case 1: // Momo, Set FollowCamera's Z value of Rotation
case 7:
Main->FollowCamera->SetRelativeRotation(FRotator(0.f, -15.f, 0.f));
break;
case 2: // Vivi
case 6:
Main->FollowCamera->SetRelativeRotation(FRotator(0.f, 0.f, 0.f));
break;
case 3: // Luko
case 8:
Main->FollowCamera->SetRelativeRotation(FRotator(0.f, -25.f, 0.f));
break;
case 4: // Zizi
Main->FollowCamera->SetRelativeRotation(FRotator(0.f, 13.f, 0.f));
break;
case 5: // Vovo
case 9:
Main->FollowCamera->SetRelativeRotation(FRotator(0.f, 30.f, 0.f));
if(RowIndex == 5 && MessageIndex == 2)
Main->FollowCamera->SetRelativeRotation(FRotator(0.f, 5.f, 0.f));
break;
case 10: // npc go
Main->FollowCamera->SetRelativeRotation(FRotator(0.f, 0.f, 0.f));
Main->CameraBoom->TargetArmLength = 500.f; // 3인칭 시점
for(int i = 0; i < Main->NPCList.Num(); i++) // 플레이어에게 NPCList라는 배열을 만들고 npc들을 배열에 추가했음
{
if (!Main->NPCList[i]->GetName().Contains("Luko")) // npc move except luko
{
Main->NPCList[i]->AIController->MoveToLocation(FVector(5200.f, 35.f, 100.f));
}
}
break;
case 11:
CurrentState = 0;
MainPlayerController->RemoveDialogueUI(); // 대화창 없어짐
FTimerHandle Timer;
GetWorld()->GetTimerManager().SetTimer(Timer, FTimerDelegate::CreateLambda([&]()
{
MainPlayerController->DisplayDialogueUI();
}), 1.5f, false); // 1.5초 뒤 루코 대화
return;
break;
}
}
if (DNum == 3) // Third Dialogue (first dungeon, after golem battle)
{
switch (RowIndex)
{
case 0:
Main->CameraBoom->TargetArmLength = 200.f; // 카메라 당김
break;
case 2:
if (MessageIndex == 1)
{
for (int i = 0; i < Main->NPCList.Num(); i++)
{
if (!Main->NPCList[i]->GetName().Contains("Vovo")) // npc move to the boat except vovo
{
Main->NPCList[i]->AIController->MoveToLocation(FVector(628.f, 946.f, 1840.f));
}
}
}
break;
case 4:
for (int i = 0; i < Main->NPCList.Num(); i++)
{
if (Main->NPCList[i]->GetName().Contains("Vovo")) // vovo moves to the boat
{
Main->NPCList[i]->AIController->MoveToLocation(FVector(628.f, 885.f, 1840.f));
}
}
CurrentState = 0;
MainPlayerController->RemoveDialogueUI();
return;
break;
}
}
AnimateMessage(Dialogue[RowIndex]->Messages[MessageIndex].ToString()); // 대사 표시
}
DialogueUI 블루프린트
디테일 패널의 카테고리 DialogueUI 안에서 SoundCueMessage에 TextSound(사운드웨이브) 넣음.
DialogueShow라는 이름의 애니메이션을 만듬. 2초동안 스케일 0에서 1이 되도록 함. (가운데서 뿅 나오게 함)
모든 텍스트를 한글 폰트로 바꿈. (앨리스폰트)
OnAnimationShowMessageUI() - DialogueShow애니메이션을 앞에서부터 실행(대화창이 나타남)
OnAnimationHideMessageUI - DialogueShow애니메이션을 뒤에서부터 실행(대화창이 사라짐), 애니메이션이 끝나고 0.3초 뒤 Visibility 히든으로 바꾸고 2초 뒤 불 변수 true(=수동으로 대화 시작 가능)
플레이어 응답 텍스트들을 빈 텍스트로 만들고, 응답 버튼들을 숨김.
플레이어 응답 텍스트들을 보여주는 이벤트. 응답 텍스트 내용들 세팅하고 일단 첫번째 응답은 무조건 존재할 것이므로 이름창 안 보이게 한 다음 첫번째 응답 버튼을 보이게 함. 그 후에는 NumOfReply의 값에 따라 두세번째 응답버튼을 보이게 할지 말지가 결정됨.
응답버튼을 눌렀을 때 SelectedReply에 값이 들어가고 Interact함수를 실행해서 다음 대사로 넘어감. 플레이어 응답 후에는 무조건 npc 대사이므로 이름창을 다시 보이게 한 뒤 RowIndex가 1이고 DialogueNum이 0일 때만
페이드인되게 함. 그리고 플레이어의 전투몽타주에서 일어나는 애니메이션을 실행하고 5초 뒤에 카메라붐의 길이를 -60까지 당김 = 1인칭 시점이 되게 함.
MainPlayercontroller.h
UFUNCTION(BlueprintCallable)
void DisplayDialogueUI();
void RemoveDialogueUI();
UPROPERTY(VisibleAnywhere)
bool bDialogueUIVisible; // 대화창이 보이는 상태면 참, 아니면 거짓
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dialogue")
class UDialogueUI* DialogueUI;
UPROPERTY(VisibleAnywhere, Category = "Dialogue")
TSubclassOf<class UUserWidget> DialogueUIClass;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Dialogue")
int DialogueNum; // 0 - intro
void DialogueEvents(); // 대화 종료 후의 이벤트
//페이드인아웃
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Widgets")
TSubclassOf<UUserWidget> WFadeInOut;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Widgets")
UUserWidget* FadeInOut;
UFUNCTION(BlueprintImplementableEvent, Category = "Fade Events")
void FadeOut();
void FadeAndDialogue();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Fade Events")
bool bFadeOn = false;
UFUNCTION(BlueprintCallable)
void SetPositions(); // npc&플레이어 위치 세팅
protected:
// Dialogue data tables
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dialogue")
class UDataTable* IntroDialogue;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dialogue")
class UDataTable* DungeonDialogue1;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dialogue")
class UDataTable* DungeonDialogue2;
MainPlayerController.cp
in BeginPlay()
if (DialogueUIClass != nullptr)
{
DialogueUI = CreateWidget<UDialogueUI>(this, DialogueUIClass); //위젯 생성해서 할당
}
if (DialogueUI != nullptr)
{
DialogueUI->AddToViewport(); //뷰포트 추가한 뒤
DialogueUI->SetVisibility(ESlateVisibility::Hidden); // 안 보이게
}
void AMainPlayerController::DisplayDialogueUI()
{
if (DialogueUI)
{
if (!DialogueUI->bCanStartDialogue) return; // (수동으로)대화 시작할 수 없는 상태면 리턴
bDialogueUIVisible = true; // 대화창이 보이는 상태
switch (DialogueNum)
{
case 0:
DialogueUI->InitializeDialogue(IntroDialogue);
break;
case 1: // onlu luko dialogue
case 4: // the boat move
DialogueUI->OnAnimationShowMessageUI();
GetWorld()->GetTimerManager().SetTimer(DialogueUI->TimerHandle, DialogueUI, &UDialogueUI::OnTimerCompleted, 0.1f, false);
break;
case 2:
DialogueUI->InitializeDialogue(DungeonDialogue1);
break;
case 3:
if (!bFadeOn)
{
FadeAndDialogue();
return;
}
DialogueUI->InitializeDialogue(DungeonDialogue2);
bFadeOn = false;
break;
}
DialogueUI->SetVisibility(ESlateVisibility::Visible); // 위젯 보이게 함
FInputModeGameAndUI InputMode;
SetInputMode(InputMode); //입력 모드 바꿈
bShowMouseCursor = true; // 마우스 커서 보이게
}
}
void AMainPlayerController::RemoveDialogueUI()
{
if (DialogueUI)
{
DialogueNum++;
DialogueEvents(); // 이벤트 확인
bDialogueUIVisible = false; // 대화창이 안 보이는 상태
bShowMouseCursor = false; // 마우스 커서 안 보이게
FInputModeGameOnly InputModeGameOnly;
SetInputMode(InputModeGameOnly); // 입력모드 바꿈
DialogueUI->OnAnimationHideMessageUI(); // 대화창 사라지는 애니메이션 실행
}
}
void AMainPlayerController::DialogueEvents()
{
switch (DialogueNum)
{
case 1: // luko moves to player
for (AYaroCharacter* npc : Main->NPCList)
{
if (npc->GetName().Contains("Luko"))
{
npc->MoveToPlayer();
}
}
break;
case 2:
SetCinematicMode(false, true, true); // 시네마틱 모드 해제
for (AYaroCharacter* npc : Main->NPCList)
{
if (npc->GetName().Contains("Luko"))
{
GetWorldTimerManager().ClearTimer(npc->MoveTimer); // 플레이어를 따라가지 않도록 타이머 제거
npc->AIController->MoveToLocation(FVector(5200.f, 35.f, 100.f));
}
}
break;
case 3:
case 4:
SetCinematicMode(false, true, true);
break;
}
}
void AMainPlayerController::FadeAndDialogue()
{
if (WFadeInOut)
{
FadeInOut = CreateWidget<UUserWidget>(this, WFadeInOut); // 위젯 생성 후 변수에 할당
if (FadeInOut)
{
bFadeOn = true; // 현재 페이드되는 상태
SetCinematicMode(true, true, true); // 시네마틱 모드로 설정, 플레이어 움직이기 불가능, 카메라 회전 불가능
SetControlRotation(FRotator(0.f, 57.f, 0.f)); // 시야 조정
FadeOut(); // 페이드아웃
FadeInOut->AddToViewport(); // 뷰포트에 위젯 추가
}
}
}
void AMainPlayerController::SetPositions()
{
if (DialogueNum == 3) // 골렘 전투 후
{
Main->SetActorLocation(FVector(646.f, -1747.f, 2578.f)); //플레이어의 위치와 회전값 설정
Main->SetActorRotation(FRotator(0.f, 57.f, 0.f)); // y(pitch), z(yaw), x(roll)
for (AYaroCharacter* npc : Main->NPCList)
{
npc->AIController->StopMovement(); // npc들 움직임 멈추고
GetWorldTimerManager().ClearTimer(npc->MoveTimer); // 타이머들 해제
GetWorldTimerManager().ClearTimer(npc->TeamMoveTimer);
// 각자 맞는 위치에 세팅
if (npc->GetName().Contains("Momo"))
{
npc->SetActorLocation(FVector(594.f, -1543.f, 2531.f));
npc->SetActorRotation(FRotator(0.f, 280.f, 0.f));
}
else if (npc->GetName().Contains("Luko"))
{
npc->SetActorLocation(FVector(494.f, -1629.f, 2561.f));
npc->SetActorRotation(FRotator(0.f, 6.f, 0.f));
}
else if (npc->GetName().Contains("Vovo"))
{
npc->SetActorLocation(FVector(903.f, -1767.f, 2574.f));
npc->SetActorRotation(FRotator(0.f, 165.f, 0.f));
}
else if (npc->GetName().Contains("Vivi"))
{
npc->SetActorLocation(FVector(790.f, -1636.f, 2566.f));
npc->SetActorRotation(FRotator(00.f, 180.f, 0.f));
}
else if (npc->GetName().Contains("Zizi"))
{
npc->SetActorLocation(FVector(978.f, -1650.f, 2553.f));
npc->SetActorRotation(FRotator(0.f, 187.f, 0.f));
}
}
}
}
데이터 테이블들
이런 식임. 행을 추가해서 캐릭터 이름과 대사를 작성하고, 플레이어 응답도 추가하고 싶으면 추가.
메인플레이어컨트롤러 블루프린트
디테일 패널에서 대화 데이터테이블 확인 가능
페이드인아웃 위젯의 페이드아웃이벤트 실행하고 2초 뒤 캐릭터들 위치 세팅한 뒤 페이드인이벤트 실행.
1.5초 뒤 대화 시작.
Main.h
UPROPERTY(VisibleAnywhere)
class AYaroCharacter* test; // 이거 없으면 밑에 배열 오류남.
UPROPERTY(VisibleAnywhere)
TArray<AYaroCharacter*> NPCList;
어떤 클래스로 배열을 만드려면 해당 클래스형의 변수가 있어야 하나 봄.. 확신은 없지만 아무튼..
YaroCharacter.cpp
in Tick()
if (!Player)
{
ACharacter* p = UGameplayStatics::GetPlayerCharacter(this, 0);
Player = Cast<AMain>(p);
if(Player) Player->NPCList.Add(this);
}
NPCList에 npc 본인 추가.
Fade_InOut 블루프린트(위젯)
FadeEffect라는 이름의 애니메이션을 만듬. 2초동안 알파값이 0에서 1로 증가.
페이드인 - 애니메이션을 처음부터 실행
페이드아웃 - 애니메이션을 뒤에서부터 실행(알파값1에서 0으로 감소)
둘 다 4초 뒤 위젯(본인) 삭제
Enemy코드 리팩토링
공격 멈춤 버그 때문에 보다가 너무 복잡한 거 같아서 쭉 살펴보고 정리함.
특히 공격이랑 인식 부분.
bHasValidTarget 변수 없애고 필요없는 코드 조금 정리함 + 공격 버그 고침
뭐 때문이었는지 이제 기억이 안 남.. 고친 지 좀 되어가지고…
파우스 메뉴를 이용해 타이틀로 돌아간 뒤 바로 이어하기 하면 강종되는 문제
분명히 플레이어 변수 관련한 문제인데 정확하게 뭐 때문이다! 라고 하기는 어려움. 나도 잘 모르겠음.
일단 모모/루코의 MoveToPlayer()에서 계속 오류가 나서 오류가 안 나는 비비/지지/보보네 조건문 안으로 같이 들어감.
YaroCharacter.cpp
in Tick()
if (!canGo && Player && Player->NpcGo)
{
canGo = true;
if (this->GetName().Contains("Momo") || this->GetName().Contains("Luko"))
{
GetWorldTimerManager().SetTimer(MoveTimer, this, &AYaroCharacter::MoveToPlayer, 1.f);
}
else // 비비, 지지, 보보
{
MoveToLocation();
}
}
그리고 함수 내의 타이머를 없애고 헤더에서 그냥 타이머핸들 만든 뒤 함수에서 SetTimer해서 돌림.
비비/지지/보보의 경우 아직 함수 내에서 계속 돌림. 타이머핸들만 헤더에서 선언한 거 씀.
in MoveToPlayer()
if (Player == nullptr) return;
함수 안에서 제일 먼저 검사함. 플레이어 없으면 리턴.
GetWorldTimerManager().SetTimer(MoveTimer, this, &AYaroCharacter::MoveToPlayer, 0.5f);
함수 맨 끝에서 타이머 설정함. 시간도 1초에서 0.5초로 감소함. 인식이 좀 느린 것 같길래..
영상은 다음 개발로그에서…