일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 유니티
- photon fusion2
- CTF
- Rr
- animation
- Security
- 메카님
- 언리얼엔진
- MLFQ
- gas
- MAC
- Replication
- ability task
- 언리얼 엔진
- unity
- DP
- 게임 개발
- Multiplay
- stride
- 게임개발
- ret2libc
- gameplay effect
- dirty cow
- 유스케이스
- gameplay ability system
- Race condition
- Delegate
- DSP
- 운영체제
- Unreal Engine
- Today
- Total
Replicated
[ChronoSpace] NavMesh와 Behavior Tree를 이용한 AI NPC (Clockwork Labyrinth) 본문
[ChronoSpace] NavMesh와 Behavior Tree를 이용한 AI NPC (Clockwork Labyrinth)
라구넹 2025. 2. 16. 06:24Key를 지키는 수호자로 CSCharacterPatrol을 만들 것임
NavMesh를 쓰면 길찾기 알아서 한대서 써볼 거임
AIController 상속 받아서 하나 만들고
ACSCharacterPatrol::ACSCharacterPatrol()
{
AIControllerClass = ACSAIController::StaticClass();
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
}
Patrol에서 설정
NavMeshBoundsVolume을 쓰면 알아서 해당 범위 내에서 알아서 길찾기 한다고 함
뷰포트에서 P누르면 범위 확인 가능
근데 문제가 발생!
AI가 MoveToActor로 움직이게 만들어놨는데, 움직이면서 애니메이션이 안보임
https://forums.unrealengine.com/t/ai-not-walking-running-animation-when-given-aimoveto/550624/5
AI not walking/running animation when given AIMoveTo
In your AI’s movement component, tick Use Acceleration for Paths and it should work with animation blueprint. You might need Requested Move Use Acceleration enabled as well.
forums.unrealengine.com
해결법을 찾았다
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Character/CSCharacterBase.h"
#include "CSCharacterPatrol.generated.h"
/**
*
*/
UCLASS()
class CHRONOSPACE_API ACSCharacterPatrol : public ACSCharacterBase
{
GENERATED_BODY()
public:
ACSCharacterPatrol();
virtual void BeginPlay() override;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/CSCharacterPatrol.h"
#include "ChronoSpace.h"
#include "Player/CSAIController.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/CharacterMovementComponent.h"
ACSCharacterPatrol::ACSCharacterPatrol()
{
AIControllerClass = ACSAIController::StaticClass();
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
GetCharacterMovement()->GetNavMovementProperties()->bUseAccelerationForPaths = true;
}
void ACSCharacterPatrol::BeginPlay()
{
Super::BeginPlay();
if ( GetController() )
{
AActor* Player = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0);
ACSAIController* AIController = Cast<ACSAIController>(GetController());
if (AIController)
{
AIController->MoveToActor(Player, 5.0f, false, true, true, nullptr, true);
}
}
}
일단 임시로 플레이어를 쫓아오게 했다
* BeginPlay에 있다 보니, 처음 Nav 볼륨에 플레이어가 없으면 인식 못해서 안쫓아옴 주의
해결!
잘 쫓아온다
https://unreal6.tistory.com/209
AIController와 내비게이션 시스템
● 플레이어가 조종하지 않지만 레벨에 배치돼 스스로 행동하는 캐릭터를 'NPC(Non Player Character)'라고 한다. ● 언리얼 엔진은 컴퓨터가 인공지능으로 NPC를 제어하도록 AI 컨트롤러를 제공한
unreal6.tistory.com
이제 Patrol로서의 역할을 구현해야 한다
행동 트리 모델을 이용하여 개발해볼 것인데, 잘 정리된 블로그가 있어서 링크 첨부한다
일단 기본 Position들이 있고, 그 포지션들을 향해 이동 (순회)
그러다 트리거 안에 들어오면 해당 플레이어 캐릭터를 타겟 삼아 쫓아가기
각 Patrol마다 주어진 NavMeshBoundsVolume이 사실상의 도그 파일이 될 것임
+ 그렇게 어느 정도 벗어나면 원래 위치로
기본 구조는 이렇다
Selector로 플레이어를 쫓아가거나, 아니면 주어진 경로를 순회하거나
자세한 구조 설명은 후술한다
이건 블랙보드이다
// Fill out your copyright notice in the Description page of Project Settings.
#include "BT/BTTask_FindPlayerPos.h"
#include "Interface/CSCharacterPlayerFinderInterface.h"
#include "Character/CSCharacterPlayer.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Player/CSAIController.h"
UBTTask_FindPlayerPos::UBTTask_FindPlayerPos()
{
NodeName = TEXT("FindPlayerPos");
}
EBTNodeResult::Type UBTTask_FindPlayerPos::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
ICSCharacterPlayerFinderInterface* Finder = Cast<ICSCharacterPlayerFinderInterface>(OwnerComp.GetAIOwner()->GetPawn());
if ( Finder )
{
ACSCharacterPlayer* Player = Finder->GetCharacterPlayer();
if ( Player )
{
OwnerComp.GetBlackboardComponent()->SetValueAsVector(ACSAIController::PlayerPosKey, Player->GetActorLocation());
return EBTNodeResult::Succeeded;
}
}
return EBTNodeResult::Failed;
}
FindPlayerPos이다
플레이어 찾는 일은 많을 수도 있을 것 같아서 인터페이스로 만들어뒀다
// Fill out your copyright notice in the Description page of Project Settings.
#include "BT/BTTask_FindPatrolPos.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Player/CSAIController.h"
#include "Character/CSCharacterPatrol.h"
#include "ChronoSpace.h"
UBTTask_FindPatrolPos::UBTTask_FindPatrolPos()
{
NodeName = TEXT("FindPatrolPos");
}
EBTNodeResult::Type UBTTask_FindPatrolPos::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
// Owner Is Controller
ACSCharacterPatrol* Patrol = Cast<ACSCharacterPatrol>(OwnerComp.GetAIOwner()->GetPawn());
if ( Patrol )
{
//UE_LOG(LogCS, Log, TEXT("UBTTask_FindPatrolPos ExecuteTask - %f, %f, %f"), Patrol->GetPatrolPos().X, Patrol->GetPatrolPos().Y, Patrol->GetPatrolPos().Z);
Cast<ACSAIController>(OwnerComp.GetAIOwner())->ActiveMove(true);
OwnerComp.GetBlackboardComponent()->SetValueAsVector(ACSAIController::PatrolPosKey, Patrol->GetPatrolPos());
return EBTNodeResult::Succeeded;
}
return EBTNodeResult::Failed;
}
FindPatrolPos이다
Patrol에서 Position 받아서 사용한다
ACSCharacterPlayer* ACSCharacterPatrol::GetCharacterPlayer()
{
return CharacterPlayer;
}
void ACSCharacterPatrol::OnTriggerBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult)
{
ACSCharacterPlayer* OverlappedPlayer = Cast<ACSCharacterPlayer>(OtherActor);
if ( OverlappedPlayer )
{
CharacterPlayer = OverlappedPlayer;
Cast<ACSAIController>(GetController())->ActiveMove(false);
++OverlappedPlayerCount;
}
}
void ACSCharacterPatrol::OnTriggerEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
ACSCharacterPlayer* OverlappedPlayer = Cast<ACSCharacterPlayer>(OtherActor);
if (OverlappedPlayer)
{
--OverlappedPlayerCount;
if ( OverlappedPlayerCount > 0 )
{
OverlappedPlayerCount = 0;
Trigger->SetCollisionEnabled(ECollisionEnabled::NoCollision);
Trigger->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
}
else
{
Cast<ACSAIController>(GetController())->ActiveMove(true);
CharacterPlayer = nullptr;
}
}
}
Patrol의 PlayerPos 관련 부분이다
설정해둔 트리거에서 캐릭터를 감지하면 설정한다
외부에서 Player를 요청 시 있으면 줄 것이고 없으면 nullptr 리턴할 거다
멀티 게임이니까 동시에 여러 플레이어가 감지되었을 수 있다
그래서 강제로 재감지 시키는 코드를 넣어뒀다
이거 안하면 아마 두 플레이어가 이용하는 일도 있을 것이다
Patrol Pos는 미리 설정해둔 값을 받아서 이용한다
기본적으로 이렇게 선언되어 있는데
인스턴스 배치해두고 순회시키고 싶은 포인트 좌표 찍어주면 된다
그래서 이게 무슨 생각으로 만들어진 것이냐'
첫번째 시퀀스에서 PlayerPos 얻는데에 성공하면(Player가 nullptr 아니면) 그냥 쫓아가면 됨
만약 모종의 이유로 실패 시 (Player가 null이거나, Player가 NavMesh 벗어난 경우) 두번째 시퀀스로 넘어감
그럼 Patrol Pos를 받아서 이동하는데, 데코레이터로 MoveTo가 감싸져 있음
그 이유는?
저 MoveTo 중에는 캐릭터가 감지되어도 바로 캐릭터 캐릭터를 쫓아오지 않음
할당된 MoveTo는 해야 하니까
그래서 Abort 시켜야 함 => 데코레이터로 감싸기
그래서 이런 설정을 해주고
void ACSAIController::ActiveMove(bool bIsActive)
{
UBlackboardComponent* BBComponent = GetBlackboardComponent();
if ( BBComponent )
{
BBComponent->SetValueAsBool(bShouldStopMoveKey, !bIsActive);
}
}
AI 컨트롤러에 이런 함수도 추가해서 움직여도 되는지 설정할 수 있게 하고
EBTNodeResult::Type UBTTask_FindPatrolPos::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
// Owner Is Controller
ACSCharacterPatrol* Patrol = Cast<ACSCharacterPatrol>(OwnerComp.GetAIOwner()->GetPawn());
if ( Patrol )
{
//UE_LOG(LogCS, Log, TEXT("UBTTask_FindPatrolPos ExecuteTask - %f, %f, %f"), Patrol->GetPatrolPos().X, Patrol->GetPatrolPos().Y, Patrol->GetPatrolPos().Z);
Cast<ACSAIController>(OwnerComp.GetAIOwner())->ActiveMove(true);
OwnerComp.GetBlackboardComponent()->SetValueAsVector(ACSAIController::PatrolPosKey, Patrol->GetPatrolPos());
return EBTNodeResult::Succeeded;
}
return EBTNodeResult::Failed;
}
PatrolPos 시작 시에는 바로바로 ActiveMove 풀어주게 했다
애초에 같은 시퀀스에 있으니, PatrolPos 찾는 시점에선 풀어줘야 한다
(안풀어주면 MoveTo 못해서 캐릭터 없으면 이제 못움직이게 됨)
EndOverlap에도 일단 이 설정을 해주긴 했는데, Patrol Pos에 있으니 굳이 필요는 없다
사이즈 디버그할 때 잘 안보여서 bVisualizeComponent 설정 해줬다
그럼 이렇게 보여준다
그런데 현재 상태론 중간에 PlayerPos 저장했던 부분에 도착하면 잠깐 멈추고 다시 플레이어를 쫓아간다
MoveTo 특성 상, 도착할 때까지 PlayerPos 업데이트를 해주진 않기 때문이다
해결법은 MoveTo가 액터를 참조하도록 하는 것이다
Object 기반으로 키를 만들고 베이스 클래스를 쓰려고 하는 클래스를 해주거나, 적어도 액터 기반으로 해준다
그 후 트리 변경
이제 잘 쫓아간다
기본 AI 설정은 끝났다
이제 다음은 Patrol에 ASC 붙여주고, 플레이어와 충돌 시 에너지를 깎도록 게임플레이 이펙트 설정을 할 것이다
'언리얼 엔진 > ChronoSpace' 카테고리의 다른 글
[ChronoSpace] ** Attribute Replication ** (0) | 2025.02.17 |
---|---|
[ChronoSpace] Behavior Tree + Ability & Effect + Camera Shake (0) | 2025.02.17 |
[ChronoSpace] Key 흩뿌리기 (Clorkwork Labyrinth) (0) | 2025.02.15 |
[ChronoSpace] Change Level in Multiplay (0) | 2025.02.15 |
[ChronoSpace] TActorIterator를 이용한 LabyrinthKey 활성화 (Random Activation of Actor) (ClockworkLabyrinth) (0) | 2025.02.13 |