몬스터의 기본 전투 상태로는
- 대기상태: 한자리에 대기하거나 정해진 포인트를 순찰하는 상태. 불침번의 입초, 동초느낌
- 추적상태: AIPerception이나 그외의 요소를 통해 목표가 설정되어 목표를 추적하는 상태
- 공격상태: 추적포인트에 도착하여 Attack()함수를 호출한 상태이다.
- 피격상태: 공격을 받아 TakeDamage()를 통해서 체력이 감소되는 상태이다.
- 사망상태: 체력이 0아래로 내려가 사망한 상태이다.
이렇게 5가지 정도로 세분화가 가능할 것 같다. 세분화의 기준은 애니메이션이다.
이중 코드로 구현된 부분은 공격상태, 피격상태, 사망상태이다. 대기상태는 특별한 상호작용이 없는이상 굳이 코드가 필요없고 BP로 대기상태 애니메이션만 출력하면된다. 추적상태는 코드로 구현가능하지만 BP의 "AI Move TO" 함수를 통해서 더 쉽게 구현이 가능하다.
1. 추적상태 로직
몬스터가 AI Perception을 통해서 Target을 인식을 하려면 BP_Monster에 AIPerception Component가 있어야한다.
생성이 됬다면 어떤 방식으로 Perception을 시킬건지를 설정해야한다.
데미지를 받는다거나 소리를 듣는다거나 여러가지의 방법이 있고 AI Sight config는 시야안에 있는 적을 감지한는 것이다.
인식되는 거리, 인식이 해제되는 거리, 인식하는 시야각을 설정해줄 수 있다.
플레이어가 인식을 당하려면 AIPerception stimuli source 를 추가하여 인식당하는 형태를 설정해주거나
C++로 Interaction Component를 추가하면된다. 이건 상호작용하는 컴포넌트인데 왜 추적되는지는 모르겠다.
AI Sight(시야)를 통해서 로직이 실행되면 PlayerDetected 함수가 호출된다
void ANormalMonster::PlayerDetected(UObject* TargetCharacter)
{
AAIController* AIController = Cast<AAIController>(GetController());
UBlackboardComponent* BlackboardComp = AIController->GetBlackboardComponent();
BlackboardComp->SetValueAsBool(TEXT("PlayerDetectedKey"), true);
BlackboardComp->SetValueAsBool(TEXT("IsWatingKey"), false);
BlackboardComp->SetValueAsObject(TEXT("TargetActor"), (TargetCharacter));
}
BP의 AIPerception을 통해서 호출되는 함수이다.
몬스터 BehaviorTree에 있는 BlackBoard 키값을 바꿈으로서 대상을 추적하게 된다.
2. 공격상태 코드
void ANormalMonster::Attack(AActor* Target)
{
if (Target)
{
InstanceIsAttack = true;
RPCIsAttack(InstanceIsAttack);
PlayRandomSound(ENormalMonsterSoundCategory::AttackSound);
FVector MonsterLocation = GetActorLocation();
FVector TargetLocation = Target->GetActorLocation();
FVector DirectionToTarget = (TargetLocation - MonsterLocation).GetSafeNormal();
FVector MonsterForward = GetActorForwardVector();
float DotProduct = FVector::DotProduct(MonsterForward, DirectionToTarget);
float AngleDegrees = FMath::Acos(DotProduct) * (180.0f / PI);
float Distance = FVector::Dist(MonsterLocation, TargetLocation);
if (Distance <= AttackDistance && AngleDegrees <= AttackAngle) // 120도 범위 (60도 좌우)
{
UGameplayStatics::ApplyDamage(Target, AttackPower, GetController(), this, UDamageType::StaticClass());
}
}
}
UGamePlayStatics에 있는 ApplyDamage를 호출하면 1번째 인자인 Target의 TakeDamage를 자동으로 호출해주기에 편리하게 이용이 가능하다.
목적지에 도착했다고 무작정 공격이들어가게 판정이나면 안된다. 공격을 했는데 몬스터의 무기와 플레이어에 콜리전이 일어나거나 일정 범위안에 있는 적에게만 데미지를 넣어야한다.
콜리전은 코스트가 많이 들어갈 수 밖에 없어 간단하게 범위를 지정하고 그 범위안에 있는 Target에만 데미지를 주도록 설정이 되어있다.
3. 피격상태 코드
float ANormalMonster::TakeDamage(float Damage, FDamageEvent const& DamageEvent, AController* EventInstigator,
AActor* DamageCauser)
{
Super::TakeDamage(Damage, DamageEvent, EventInstigator, DamageCauser);
InstanceIsHit = true;
RPCIsHit(InstanceIsHit, DamageCauser);
PlayRandomSound(ENormalMonsterSoundCategory::HitSound);
return Damage;
}
본인의 체력을 감소시키는 코드는 부모클래스인 UnitBase.cpp 에 구현되어있지만 굳이 쓰진 않았다.
UnitBase에서는 체력을 모두에게 BroadCast하여 체력바가 서버에서 업데이트된다.
여기서는 서버 RPC를 통해서 애니메이션 출력, 소리출력만 구현된다.
4. 사망상태 코드
void ANormalMonster::OnDeath()
{
Super::OnDeath();
InstanceIsDeath = true;
RPCIsDeath(InstanceIsDeath);
PlayRandomSound(ENormalMonsterSoundCategory::DeathSound);
}
사망생태의 경우 대부분의 코드가 서버에서 작동하도록 구현되어있어 여기서는 그저 서버호출, 사운드 출력만 하게 된다. (그래서 사운드가 클라이언트에게만 들릴 것이다.
'UE5' 카테고리의 다른 글
언리얼 Monster AI 구현(3): Animation (0) | 2025.04.22 |
---|---|
언리얼 Monster AI구현(2): Behavior Tree (0) | 2025.04.15 |
C++ Behavior Tree (0) | 2025.04.08 |
AI(2): Behavior tree (0) | 2025.04.04 |
AI (1) (0) | 2025.04.01 |