몬스터의 이동과 움직임을 구현하였습니다.
이동은 캐릭터와 유사하게 구현하였으며 행동트리는 블랙보드와 비헤이버트리를 이용하여 제작하였습니다.
// Sets default values
ASLEnemyBearCharacter::ASLEnemyBearCharacter()
{
//Controller
AIControllerClass = ASLBearAIController::StaticClass();
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
// Capsule
GetCapsuleComponent()->InitCapsuleSize(90.f, 80.0f);
GetCapsuleComponent()->SetCollisionProfileName(TEXT("Pawn"));
// Movement
GetCharacterMovement()->bOrientRotationToMovement = true;
GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f);
GetCharacterMovement()->JumpZVelocity = 500.f;
GetCharacterMovement()->AirControl = 0.35f;
GetCharacterMovement()->MaxWalkSpeed = 300.f;
GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;
// Mesh
GetMesh()->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, -90.0f), FRotator(0.0f, -90.0f, 0.0f));
GetMesh()->SetAnimationMode(EAnimationMode::AnimationBlueprint);
GetMesh()->SetCollisionProfileName(TEXT("EnemyMesh"));
// Mesh
static ConstructorHelpers::FObjectFinder<USkeletalMesh> CharacterMeshRef(TEXT("/Script/Engine.SkeletalMesh'/Game/InfinityBladeAdversaries/Enemy/Enemy_Bear/Enemy_Bear.Enemy_Bear'"));
if (CharacterMeshRef.Object)
{
GetMesh()->SetSkeletalMesh(CharacterMeshRef.Object);
}
//Animation
static ConstructorHelpers::FClassFinder<UAnimInstance> AnimInstanceClassRef(TEXT("/Game/Soulslike/Enemy/Bear/ABP_EnemyBear.ABP_EnemyBear_C"));
if (AnimInstanceClassRef.Class)
{
GetMesh()->SetAnimInstanceClass(AnimInstanceClassRef.Class);
}
}
float ASLEnemyBearCharacter::GetAIPatrolRadius()
{
return 800.0f;
}
float ASLEnemyBearCharacter::GetAIDetectRange()
{
return 400.0f;
}
float ASLEnemyBearCharacter::GetAIAttackRange()
{
return 200.0f;
}
float ASLEnemyBearCharacter::GetAITurnSpeed()
{
return 2.0f;
}
void ASLEnemyBearCharacter::SetAIAttackDelegate(const FAICharacterAttackFinished& InOnAttackFinished)
{
OnAttackFinished = InOnAttackFinished;
}
void ASLEnemyBearCharacter::AttackByAI()
{
// Movement Setting
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);
// Animation Setting
const float AttackSpeedRate = 1.0f;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
AnimInstance->Montage_Play(ActionMontage, AttackSpeedRate);
FOnMontageEnded EndDelegate;
EndDelegate.BindUObject(this, &ASLEnemyBearCharacter::ComboActionEnd);
AnimInstance->Montage_SetEndDelegate(EndDelegate, ActionMontage);
}
void ASLEnemyBearCharacter::NotifyActionEnd()
{
OnAttackFinished.ExecuteIfBound();
}
void ASLEnemyBearCharacter::ComboActionEnd(UAnimMontage* TargetMontage, bool IsProperlyEnded)
{
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking);
}
캐릭터를 구현할 때와 마찬가지로 Capsule/Mesh/Movement 등을 설정해주었습니다.
공격을 구현하여 주었으며 Montage는 BP에서 설정하여주었습니다.
EBTNodeResult::Type UBTTask_FindPatrolPos::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
if (nullptr == ControllingPawn)
{
return EBTNodeResult::Failed;
}
UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(ControllingPawn->GetWorld());
if (nullptr == NavSystem)
{
return EBTNodeResult::Failed;
}
ISLBearAIInterface* AIPawn = Cast<ISLBearAIInterface>(ControllingPawn);
if (nullptr == AIPawn)
{
return EBTNodeResult::Failed;
}
FVector Origin = OwnerComp.GetBlackboardComponent()->GetValueAsVector(BBKEY_HOMEPOS);
float PatrolRadius = AIPawn->GetAIPatrolRadius();
FNavLocation NextPatrolPos;
if (NavSystem->GetRandomPointInNavigableRadius(Origin, PatrolRadius, NextPatrolPos))
{
OwnerComp.GetBlackboardComponent()->SetValueAsVector(BBKEY_PATROLPOS, NextPatrolPos.Location);
return EBTNodeResult::Succeeded;
}
return EBTNodeResult::Failed;
}
랜덤으로 이동할 위치를 찾는 FindPatrolPos 입니다.
BTTask로 제작하였으며 이동할 위치를 찾으면 Patrol에 저장해 MoveTo를 이용하여 이동하게 만들었습니다.
void UBTService_Detect::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
if (nullptr == ControllingPawn)
{
return;
}
FVector Center = ControllingPawn->GetActorLocation();
UWorld* World = ControllingPawn->GetWorld();
if (nullptr == World)
{
return;
}
ISLBearAIInterface* AIPawn = Cast<ISLBearAIInterface>(ControllingPawn);
if (nullptr == AIPawn)
{
return;
}
float DetectRadius = AIPawn->GetAIDetectRange();
TArray<FOverlapResult> OverlapResults;
FCollisionQueryParams CollisionQueryParam(SCENE_QUERY_STAT(Detect), false, ControllingPawn);
bool bResult = World->OverlapMultiByChannel(
OverlapResults,
Center,
FQuat::Identity,
CCHANNEL_ABACTION,
FCollisionShape::MakeSphere(DetectRadius),
CollisionQueryParam
);
//Detect
if (bResult)
{
for (auto const& OverlapResult : OverlapResults)
{
APawn* Pawn = Cast<APawn>(OverlapResult.GetActor());
if (Pawn && Pawn->GetController()->IsPlayerController())
{
OwnerComp.GetBlackboardComponent()->SetValueAsObject(BBKEY_TARGET, Pawn);
DrawDebugSphere(World, Center, DetectRadius, 16, FColor::Green, false, 0.2f);
DrawDebugPoint(World, Pawn->GetActorLocation(), 10.0f, FColor::Green, false, 0.2f);
DrawDebugLine(World, ControllingPawn->GetActorLocation(), Pawn->GetActorLocation(), FColor::Green, false, 0.27f);
return;
}
}
}
OwnerComp.GetBlackboardComponent()->SetValueAsObject(BBKEY_TARGET, nullptr);
DrawDebugSphere(World, Center, DetectRadius, 16, FColor::Red, false, 0.2f);
}
적을 찾는 Detected 서비스 입니다.
Pawn을 중심으로 원을 그려 원 안에 플레이어가 없다면 붉은 색을, 있다면 초록색을 return하게 만들었습니다.