Unreal Engine C++ The Ultimate Shooter Course (6)
카테고리: Unreal Engine
Chapter 15 AI and Behavior Trees
15-272 Unreal Engine AI
- AI Character, Use AI Controller
- Behavior Tree, Makes decisions
- Blackboard, Stores data for the Behavior Tree
- AI Controller, Moves the Character
- Navigation Mesh, Enables AI navigation
- Now, remember, the sequence( In Behavior Tree Nodes ) will execute the child tasks until one of them fails
15-273 Adding AI Modules
// Shooter.Build.cs
public class Shooter : ModuleRules
{
public Shooter(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
// Add AI Module
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "UMG", "PhysicsCore", "NavigationSystem", "AIModule" });
PrivateDependencyModuleNames.AddRange(new string[] { });
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}
class SHOOTER_API AEnemy : public ACharacter, public IBulletHitInterface
{
/** Behavior tree for the AI Character */
UPROPERTY(EditAnywhere, Category = "Behavior Tree", meta = (AllowPrivateAccess = "true"))
class UBehaviorTree* BehaviorTree;
}
15-274 AI Controller
class SHOOTER_API AEnemy : public ACharacter, public IBulletHitInterface
{
//..
FORCEINLINE UBehaviorTree* GetBehaviorTree() const { return BehaviorTree; }
//..
}
void AEnemyController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
if (InPawn == nullptr) return;
AEnemy* Enemy = Cast<AEnemy>(InPawn);
if (Enemy)
{
if (Enemy->GetBehaviorTree())
{
BlackboardComponent->InitializeBlackboard(*(Enemy->GetBehaviorTree()->BlackboardAsset));
}
}
}
15-275 Creating a Blackboard and Behavior Tree
15-276 Patrol Point
void AEnemy::BeginPlay()
{
Super::BeginPlay();
GetMesh()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Visibility, ECollisionResponse::ECR_Block);
// Converted from local space to worldspace
const FVector WorldPatrolPoint = UKismetMathLibrary::TransformLocation(
GetActorTransform(),
PatrolPoint);
DrawDebugSphere(
GetWorld(),
WorldPatrolPoint,
25.f,
12,
FColor::Red,
true
);
}
15-277 Move to Task
void AEnemy::BeginPlay()
{
//..
if (EnemyController)
{
EnemyController->GetBlackboardComponent()->SetValueAsVector(
TEXT("PatrolPoint"),
WorldPatrolPoint);
EnemyController->RunBehaviorTree(BehaviorTree);
}
}
15-278 Enemy Run Animation
15-279 Second Patrol Point
15-280 Wait Task
15-281 Agro Sphere
void AEnemy::AgroSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor == nullptr) return;
auto Character = Cast<AShooterCharacter>(OtherActor);
if (Character)
{
// Set the value of the Target Blackboard Key
EnemyController->GetBlackboardComponent()->SetValueAsObject(
TEXT("Target"),
Character);
}
}
15-282 Move To Actor
15-283 Stun Enemy
15-284 Stunned Blackboard Decorator
void AEnemy::SetStunned(bool Stunned)
{
bStunned = Stunned;
if (EnemyController)
{
EnemyController->GetBlackboardComponent()->SetValueAsBool(
TEXT("Stunned"),
Stunned);
}
}
- A decorator node behaves like a conditional It’s like an F check that says if a particular condition is true, then allow the node to execute, otherwise prevent the node from executing
15-285 Selector Node
- The selector will continue executing children until one of them succeeds
15-286 In Attack Range
void AEnemy::CombatRangeOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (OtherActor == nullptr) return;
auto ShooterCharacter = Cast<AShooterCharacter>(OtherActor);
if (ShooterCharacter)
{
bInAttackRange = true;
if (EnemyController)
{
EnemyController->GetBlackboardComponent()->SetValueAsBool(
TEXT("InAttackRange"),
true
);
}
}
}
void AEnemy::CombatRangeEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
if (OtherActor == nullptr) return;
auto ShooterCharacter = Cast<AShooterCharacter>(OtherActor);
if (ShooterCharacter)
{
bInAttackRange = false;
if (EnemyController)
{
EnemyController->GetBlackboardComponent()->SetValueAsBool(
TEXT("InAttackRange"),
false
);
}
}
}
15-287 Enemy Attack Montage
void AEnemy::PlayAttackMontage(FName Section, float PlayRate)
{
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && AttackMontage)
{
AnimInstance->Montage_Play(AttackMontage);
AnimInstance->Montage_JumpToSection(Section, AttackMontage);
}
}
15-288 Get Attack Section Name
FName AEnemy::GetAttackSectionName()
{
FName SectionName;
const int32 Section{ FMath::RandRange(1, 4) };
switch (Section)
{
case 1:
SectionName = AttackLFast;
break;
case 2:
SectionName = AttackRFast;
break;
case 3:
SectionName = AttackL;
break;
case 4:
SectionName = AttackR;
break;
}
return SectionName;
}
15-289 Custom Behavior Tree Task
- Now, remember, a sequence node will execute its children from left to right until one of them fails
void AEnemy::BeginPlay()
{
//..
// Ignore the camera for Mesh and Capsule
GetMesh()->SetCollisionResponseToChannel(
ECollisionChannel::ECC_Camera,
ECollisionResponse::ECR_Ignore);
GetCapsuleComponent()->SetCollisionResponseToChannel(
ECollisionChannel::ECC_Camera,
ECollisionResponse::ECR_Ignore
);
//..
}
15-290 Weapon Collision Volumes
void AEnemy::BeginPlay()
{
//..
// Bind functions to overlap events for weapon boxes
LeftWeaponCollision->OnComponentBeginOverlap.AddDynamic(
this,
&AEnemy::OnLeftWeaponOverlap);
RightWeaponCollision->OnComponentBeginOverlap.AddDynamic(
this,
&AEnemy::OnRightWeaponOverlap);
// Set collision presets for weapon boxes
LeftWeaponCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
LeftWeaponCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
LeftWeaponCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
LeftWeaponCollision->SetCollisionResponseToChannel(
ECollisionChannel::ECC_Pawn,
ECollisionResponse::ECR_Overlap);
RightWeaponCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
RightWeaponCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
RightWeaponCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
RightWeaponCollision->SetCollisionResponseToChannel(
ECollisionChannel::ECC_Pawn,
ECollisionResponse::ECR_Overlap);
//..
}
15-291 Activate and Deactivate Collision
15-292 Enemy Damage
void AEnemy::DoDamage(AActor* Victim)
{
if (Victim == nullptr) return;
auto Character = Cast<AShooterCharacter>(Victim);
if (Character)
{
UGameplayStatics::ApplyDamage(
Character,
BaseDamage,
EnemyController,
this,
UDamageType::StaticClass()
);
}
}
float AShooterCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
if (Health - DamageAmount <= 0.f)
{
Health = 0.f;
}
else
{
Health -= DamageAmount;
}
return DamageAmount;
}
15-293 Character Health Bar
15-294 Enemy Weapon Attack Sounds
15-295 Melee Impact Sound
void AEnemy::DoDamage(AActor* Victim)
{
if (Victim == nullptr) return;
auto Character = Cast<AShooterCharacter>(Victim);
if (Character)
{
UGameplayStatics::ApplyDamage(
Character,
BaseDamage,
EnemyController,
this,
UDamageType::StaticClass()
);
if (Character->GetMeleeImpactSound())
{
UGameplayStatics::PlaySoundAtLocation(
this,
Character->GetMeleeImpactSound(),
GetActorLocation());
}
}
}
15-296 Enemy Vocal Attack Sounds
15-297 Weapon Trails
15-298 Blood Particles
class SHOOTER_API AShooterCharacter : public ACharacter
{
//..
/** Blood splatter particles for melee hit */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = "true"))
UParticleSystem* BloodParticles;
//..
}
void AEnemy::SpawnBlood(AShooterCharacter* Victim, FName SocketName)
{
const USkeletalMeshSocket* TipSocket{ GetMesh()->GetSocketByName(SocketName) };
if (TipSocket)
{
const FTransform SocketTransform{ TipSocket->GetSocketTransform(GetMesh()) };
if (Victim->GetBloodParticles())
{
UGameplayStatics::SpawnEmitterAtLocation(
GetWorld(),
Victim->GetBloodParticles(),
SocketTransform
);
}
}
}
15-299 Character Stun State
UENUM(BlueprintType)
enum class ECombatState : uint8
{
ECS_Unoccupied UMETA(DisplayName = "Unoccupied"),
ECS_FireTimerInProgress UMETA(DisplayName = "FireTimerInProgress"),
ECS_Reloading UMETA(DisplayName = "Reloading"),
ECS_Equipping UMETA(DisplayName = "Equipping"),
// Stunned 상태 추가
ECS_Stunned UMETA(DisplayName = "Stunned"),
ECS_MAX UMETA(DisplayName = "DefaultMAX")
};
15-300 End Stun Anim Notify
15-301 Stun Character on Hit
void AEnemy::StunCharacter(AShooterCharacter* Victim)
{
if (Victim)
{
const float Stun{ FMath::FRandRange(0.f, 1.f) };
if (Stun <= Victim->GetStunChance())
{
Victim->Stun();
}
}
}
15-302 Limit Enemy Attacks
void AEnemy::PlayAttackMontage(FName Section, float PlayRate)
{
//..
bCanAttack = false;
GetWorldTimerManager().SetTimer(
AttackWaitTimer,
this,
&AEnemy::ResetCanAttack,
AttackWaitTime
);
if (EnemyController)
{
EnemyController->GetBlackboardComponent()->SetValueAsBool(
FName("CanAttack"),
false);
}
}
15-303 Delay Before Chasing Player
15-304 Agro Enemy when Shot
float AEnemy::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
// Set the Target Blackboard Key to agro the Character
if (EnemyController)
{
EnemyController->GetBlackboardComponent()->SetValueAsObject(
FName("Target"),
DamageCauser);
}
if (Health - DamageAmount <= 0.f)
{
Health = 0.f;
Die();
}
else
{
Health -= DamageAmount;
}
return DamageAmount;
}
15-305 Enemy Death Montage
void AEnemy::Die()
{
if (bDying) return;
bDying = true;
HideHealthBar();
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && DeathMontage)
{
AnimInstance->Montage_Play(DeathMontage);
}
if (EnemyController)
{
EnemyController->GetBlackboardComponent()->SetValueAsBool(
FName("Dead"),
true
);
EnemyController->StopMovement();
}
}
15-306 Destroy Enemy
15-307 Polish Up Enemy Death
void AEnemy::BulletHit_Implementation(FHitResult HitResult)
{
if (ImpactSound)
{
UGameplayStatics::PlaySoundAtLocation(this, ImpactSound, GetActorLocation());
}
if (ImpactParticles)
{
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ImpactParticles, HitResult.Location, FRotator(0.f), true);
}
// 죽었는데 스턴되는것 방지
if (bDying) return;
ShowHealthBar();
// Determine whether bullet hit stuns
const float Stunned = FMath::FRandRange(0.f, 1.f);
if (Stunned <= StunChance)
{
// Stun the Enemy
PlayHitMontage(FName("HitReactFront"));
SetStunned(true);
}
}
// 몬스터가 죽고나서 잠시뒤 사라진다
void AEnemy::FinishDeath()
{
GetMesh()->bPauseAnims = true;
GetWorldTimerManager().SetTimer(
DeathTimer,
this,
&AEnemy::DestroyEnemy,
DeathTime
);
}
15-308 Character Death
void AShooterCharacter::Die()
{
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && DeathMontage)
{
AnimInstance->Montage_Play(DeathMontage);
}
}
void AShooterCharacter::FinishDeath()
{
GetMesh()->bPauseAnims = true;
APlayerController* PC = UGameplayStatics::GetPlayerController(this, 0);
if (PC)
{
DisableInput(PC);
}
}
15-309 Stop Enemy Attack on Death
float AShooterCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
if (Health - DamageAmount <= 0.f)
{
Health = 0.f;
Die();
auto EnemyController = Cast<AEnemyController>(EventInstigator);
if (EnemyController)
{
EnemyController->GetBlackboardComponent()->SetValueAsBool(
FName(TEXT("CharacterDead")),
true
);
}
}
else
{
Health -= DamageAmount;
}
return DamageAmount;
}
15-310 Retargeting New Montages
15-311 New Type of Enemies
class SHOOTER_API AEnemy : public ACharacter, public IBulletHitInterface
{
//..
// EditAnywhere 으로 설정해 Blueprint에서 컨트롤 가능하게 하자
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Combat, meta = (AllowPrivateAccess = "true"))
float Health;
//..
}
15-312 Showcasing Different Grux Enemies
Chapter 16 Khaimera
16-313 Adding Khaimera
16-314 Khaimera Animation Blueprint
16-315 Khaimera Attack Montage Notifies
16-316 Khaimera Death and Hit Montages
16-317 Khaimera Anim Notifies
16-318 Finishing Khaimera AnimBP
16-319 Different Khaimera Skins
16-320 Explosive Get Overlapping Actors
AExplosive::AExplosive()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
ExplosiveMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ExplosiveMesh"));
SetRootComponent(ExplosiveMesh);
OverlapSphere = CreateDefaultSubobject<USphereComponent>(TEXT("OverlapSphere"));
OverlapSphere->SetupAttachment(GetRootComponent());
}
void AExplosive::BulletHit_Implementation(FHitResult HitResult)
{
//..
// Apply explosive damage
TArray<AActor*> OverlappingActors;
GetOverlappingActors(OverlappingActors, ACharacter::StaticClass());
for (auto Actor : OverlappingActors)
{
UE_LOG(LogTemp, Warning, TEXT("Actor damaged by explosive: %s"), *Actor->GetName());
}
Destroy();
}
16-321 Explosive Agro Enemy
// Shooter, ShooterController 매개 변수가 추가되었다
void AExplosive::BulletHit_Implementation(FHitResult HitResult, AActor* Shooter, AController* ShooterController)
{
//..
// Apply explosive damage
TArray<AActor*> OverlappingActors;
GetOverlappingActors(OverlappingActors, ACharacter::StaticClass());
for (auto Actor : OverlappingActors)
{
UE_LOG(LogTemp, Warning, TEXT("Actor damaged by explosive: %s"), *Actor->GetName());
UGameplayStatics::ApplyDamage(
Actor,
Damage,
ShooterController,
Shooter,
UDamageType::StaticClass()
);
}
Destroy();
}
16-322 Health Pickup
Chapter 17 Level Creation and Finishing the Game!
17-323 Level Prototyping Tools
17-324 Level Prototype - Starting Area
17-325 Level Prototype - Courtyard
17-326 Level Prototype - Courtyard2
17-327 Level Prototype - Using Paragon Props
17-328 Ruins Assets - Floor
17-329 Ruins Assets - Walls
- make walls
17-330 Ruins Assets - Walls 2
- make walls
17-331 Ruins Assets - Corners
17-332 Ruins Assets - Levels
- make levels
17-333 Ruins Assets - Levels 2
17-334 Ruins Assets - Levels 3
17-335 Ruins Assets - Arches
- make arches
17-336 Ruins Assets - Arches2
- make arches2
17-337 Ruins Assets - Large Stairs
- 액션 모빌리티에 대한 설명은 (참고)를 확인하자
17-338 Ruins Assets - Boss Room
- make boss room
17-339 Ruins Assets - Finishing Up the Level
17-340 Level Design - Enemies
17-341 Polishing Gameplay
class SHOOTER_API AShooterCharacter : public ACharacter
{
//..
/** true when Character dies */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Combat, meta = (AllowPrivateAccess = "true"))
bool bDead;
}
17-342 Polishing Gameplay 2
17-343 Prevent Attack when Enemy Dead
17-344 Sphere Reflection Capture
- LightmassImportanceVolume 은 라이트매스가 광자를 방출하는 영역을 제한시켜, 디테일한 간접광이 필요한 지역에 집중시킬 수 있습니다 (참고)
17-345 Post Process Effects
- 색수차(Chromatic Aberration)는 광선이 렌즈의 여러 지점으로 들어가면서 RGB 컬러의 분리를 유발하는 현상입니다 (참고)
- Ambient Occlusion은 빛의 차폐로 인한 감쇠 근사치를 구하는 이펙트입니다 현실에서도 방의 구석 부분은 훨씬 더 어둡듯이, 구석이나 틈 같은 곳을 더 어둡게 하여 더욱 자연스럽고 사실적인 느낌을 낼 수 있도록, 표준 글로벌 일루미네이션에 더해 미묘한 이펙트로 사용하는 것이 보통 가장 좋습니다 (참고)
댓글남기기