AI를 만들고 KillCount를 UI에 표시
MainHud.h
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "MainHud.generated.h"
UCLASS()
class BASIS_API UMainHud : public UUserWidget
{
GENERATED_BODY()
public:
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UProgressBar> HPGauge;
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UTextBlock> HPPercent;
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UTextBlock> KC;
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UTextBlock> SC;
UFUNCTION()
void SetHP(float Value);
UFUNCTION()
void SetKillCount(int32 Value);
UFUNCTION()
void SetStageCount(int32 Value);
};
MainHud.cpp
#include "MainHud.h"
#include "Components/ProgressBar.h"
#include "Components/TextBlock.h"
void UMainHud::SetHP(float Value)
{
if (IsValid(HPGauge))
{
HPGauge->SetPercent(Value);
}
if (IsValid(HPPercent))
{
int32 Hp = Value * 100;
FText Text = FText::FromString(FString::Printf(TEXT("%d"), Hp));
HPPercent->SetText(Text);
}
}
void UMainHud::SetKillCount(int32 Value)
{
if (IsValid(KC))
{
FText Text = FText::FromString(FString::Printf(TEXT("KillCount: %d"), Value));
KC->SetText(Text);
}
}
void UMainHud::SetStageCount(int32 Value)
{
if (IsValid(SC))
{
FText Text = FText::FromString(FString::Printf(TEXT("StageCount: %d"), Value));
SC->SetText(Text);
}
}
BTT_Attack.h
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "BTT_Attack.generated.h"
UCLASS()
class BASIS_API UBTT_Attack : public UBTTaskNode
{
GENERATED_BODY()
public:
UBTT_Attack();
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& BTC, uint8* NodeMemory) override;
UPROPERTY(EditAnywhere)
float AttackRange;
UPROPERTY(EditAnywhere)
float SightRange;
UPROPERTY(EditAnywhere)
float SightAngle;
};
BTT_Attack.cpp
#include "BTT_Attack.h"
#include "CharacterBase.h"
#include "PlayerBase.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "AIController.h"
#include "Kismet/KismetMathLibrary.h"
UBTT_Attack::UBTT_Attack()
{
NodeName = TEXT("Attack");
}
EBTNodeResult::Type UBTT_Attack::ExecuteTask(UBehaviorTreeComponent& BTC, uint8* NodeMemory)
{
AAIController* Controller = Cast<AAIController>(BTC.GetOwner());
if (!IsValid(Controller))
{
return EBTNodeResult::Failed;
}
ACharacterBase* CB = Cast<ACharacterBase>(Controller->GetPawn());
if (!IsValid(CB))
{
return EBTNodeResult::Failed;
}
UWorld* World = GetWorld();
if (!IsValid(World))
{
return EBTNodeResult::Failed;
}
APlayerController* PC = World->GetFirstPlayerController();
if (!IsValid(PC))
{
return EBTNodeResult::Failed;
}
APlayerBase* PB = Cast<APlayerBase>(PC->GetPawn());
if (!IsValid(PB))
{
return EBTNodeResult::Failed;
}
FVector Distance = PB->GetActorLocation() - CB->GetActorLocation();
if (Distance.Length() > SightRange)
{
return EBTNodeResult::Failed;
}
FVector AIForward = CB->GetActorForwardVector();
Distance.Normalize();
AIForward.Normalize();
float DotResult = AIForward.Dot(Distance);
float AngleBetweenVector = FMath::Acos(DotResult);
if (SightAngle < AngleBetweenVector)
{
return EBTNodeResult::Failed;
}
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(CB);
FHitResult HitResult;
World->LineTraceSingleByChannel(HitResult, CB->GetActorLocation(), PB->GetActorLocation(), ECollisionChannel::ECC_Camera, QueryParams);
if (!HitResult.bBlockingHit)
{
return EBTNodeResult::Failed;
}
if (HitResult.GetActor() != PB)
{
return EBTNodeResult::Failed;
}
UBlackboardComponent* BBC = BTC.GetBlackboardComponent();
if (!IsValid(BBC))
{
return EBTNodeResult::Failed;
}
BBC->SetValueAsVector(TEXT("TargetPosition"), PB->GetActorLocation());
Distance = PB->GetActorLocation() - CB->GetActorLocation();
if (Distance.Length() > AttackRange)
{
return EBTNodeResult::Failed;
}
BTC.GetAIOwner()->StopMovement();
FRotator Rot = UKismetMathLibrary::FindLookAtRotation(CB->GetActorLocation(), PB->GetActorLocation());
CB->SetActorRotation(Rot);
CB->Attack();
return EBTNodeResult::Succeeded;
}
BTT_Move.h
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "BTT_Move.generated.h"
UCLASS()
class BASIS_API UBTT_Move : public UBTTaskNode
{
GENERATED_BODY()
public:
UBTT_Move();
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& BTC, uint8* NodeMemory) override;
};
BTT_Move.cpp
#include "BTT_Move.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "AIController.h"
#include <NavigationSystem.h>
#include "Engine/OverlapResult.h"
UBTT_Move::UBTT_Move()
{
NodeName = TEXT("Move");
}
EBTNodeResult::Type UBTT_Move::ExecuteTask(UBehaviorTreeComponent& BTC, uint8* NodeMemory)
{
UBlackboardComponent* BBC = BTC.GetBlackboardComponent();
if (!IsValid(BBC))
{
return EBTNodeResult::Failed;
}
FVector TargetPosition = BBC->GetValueAsVector(TEXT("TargetPosition"));
AAIController* AIC = BTC.GetAIOwner();
if (!IsValid(AIC))
{
return EBTNodeResult::Failed;
}
APawn* Pawn = AIC->GetPawn();
if (!IsValid(Pawn))
{
return EBTNodeResult::Failed;
}
if (IsValid(GetWorld()))
{
TArray<FOverlapResult> result;
GetWorld()->OverlapMultiByChannel(result, Pawn->GetActorLocation(), FQuat::Identity, ECollisionChannel::ECC_Camera, FCollisionShape::MakeSphere(1000));
for (int i = 0; i < result.Num(); i++)
{
AActor* Actor = result[i].GetActor();
if (!IsValid(Actor))
{
continue;
}
if (Actor->GetActorLabel().Equals(TEXT("HealActor")))
{
BBC->SetValueAsVector(TEXT("TargetPosition"), Actor->GetActorLocation());
AIC->MoveToLocation(TargetPosition);
return EBTNodeResult::Succeeded;
}
}
}
if ((TargetPosition - Pawn->GetActorLocation()).Length() < 100)
{
UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(GetWorld());
FNavLocation LOC;
NavSystem->GetRandomPoint(LOC);
BBC->SetValueAsVector(TEXT("TargetPosition"), LOC.Location);
}
AIC->MoveToLocation(TargetPosition);
return EBTNodeResult::Succeeded;
}
AIControllerBase.h
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "AIControllerBase.generated.h"
UCLASS()
class BASIS_API AAIControllerBase : public AAIController
{
GENERATED_BODY()
public:
virtual void OnPossess(APawn* InPawn) override;
public:
UPROPERTY(EditAnywhere)
class UBehaviorTree* BT;
};
AIControllerBase.cpp
#include "AIControllerBase.h"
#include "BehaviorTree/BehaviorTreeComponent.h"
void AAIControllerBase::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
if (BT == nullptr)
{
return;
}
RunBehaviorTree(BT);
}
StartUI.h
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "StartUI.generated.h"
UCLASS()
class BASIS_API UStartUI : public UUserWidget
{
GENERATED_BODY()
public:
virtual void NativeOnInitialized() override;
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UButton> GameStart;
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UButton> Exit;
UFUNCTION(BlueprintCallable)
void OnGameStart();
UFUNCTION(BlueprintCallable)
void OnExit();
};
StartUI.cpp
#include "StartUI.h"
#include "Kismet/KismetSystemLibrary.h"
#include <Kismet/GameplayStatics.h>
#include "Components/Button.h"
void UStartUI::NativeOnInitialized()
{
Super::NativeOnInitialized();
if (!IsValid(GetWorld()))
{
return;
}
if (!IsValid(GetWorld()->GetFirstPlayerController()))
{
return;
}
GetWorld()->GetFirstPlayerController()->SetShowMouseCursor(true);
if (IsValid(GameStart))
{
GameStart->OnClicked.AddDynamic(this, &UStartUI::OnGameStart);
}
if (IsValid(Exit))
{
Exit->OnClicked.AddDynamic(this, &UStartUI::OnExit);
}
}
void UStartUI::OnGameStart()
{
UGameplayStatics::OpenLevel(this, TEXT("MainLevel2"));
}
void UStartUI::OnExit()
{
UKismetSystemLibrary::QuitGame(this, nullptr, EQuitPreference::Quit, false);
}
MainGameMode.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameMode.h"
#include "MainGameMode.generated.h"
UCLASS()
class BASIS_API AMainGameMode : public AGameMode
{
GENERATED_BODY()
public:
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
void ChangeToEnd();
UPROPERTY(EditAnywhere)
TSubclassOf<class UUserWidget> WidgetClass;
UPROPERTY()
TObjectPtr<class UUserWidget> Widget;
UPROPERTY(EditAnywhere)
TSubclassOf<class UUserWidget> EndWidgetClass;
UPROPERTY()
TObjectPtr<class UUserWidget> EndWidget;
UPROPERTY(EditAnywhere)
TSubclassOf<AActor> Enemy;
int32 Time;
int32 Stage;
int32 KillCount;
float HP;
};
MainGameMode.cpp
#include "MainGameMode.h"
#include "Blueprint/UserWidget.h"
#include "PlayerBase.h"
#include "MainHud.h"
#include <NavigationSystem.h>
void AMainGameMode::BeginPlay()
{
Super::BeginPlay();
if (WidgetClass != nullptr)
{
Widget = CreateWidget<UUserWidget>(GetWorld(), WidgetClass);
}
if (EndWidgetClass != nullptr)
{
EndWidget = CreateWidget<UUserWidget>(GetWorld(), EndWidgetClass);
}
if (IsValid(Widget))
{
Widget->AddToViewport();
}
if (!IsValid(GetWorld()))
{
return;
}
if (!IsValid(GetWorld()->GetFirstPlayerController()))
{
return;
}
GetWorld()->GetFirstPlayerController()->SetShowMouseCursor(false);
Time = 0;
Stage = 0;
}
void AMainGameMode::Tick(float DeltaTime)
{
Time += DeltaTime;
UWorld* World = GetWorld();
if (!IsValid(World))
{
return;
}
if (!IsValid(Enemy))
{
return;
}
if ((int)(Time / 3 + 1) != Stage)
{
Stage++;
for (int i = 0; i < Stage; i++)
{
UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(GetWorld());
FNavLocation LOC;
NavSystem->GetRandomPoint(LOC);
FActorSpawnParameters param;
param.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
World->SpawnActor<AActor>(Enemy, LOC.Location, FRotator::ZeroRotator, param);
}
}
APlayerController* PC = World->GetFirstPlayerController();
if (!IsValid(PC))
{
return;
}
APlayerBase* PB = Cast<APlayerBase>(PC->GetPawn());
if (!IsValid(PB))
{
return;
}
HP = (float)(PB->CurrentHP) / PB->FullHP;
KillCount = PB->KillCount;
UMainHud* Hud = Cast<UMainHud>(Widget);
if (!IsValid(Hud))
{
return;
}
Hud->SetHP(HP);
Hud->SetKillCount(KillCount);
Hud->SetStageCount(Stage);
}
void AMainGameMode::ChangeToEnd()
{
if (!IsValid(GetWorld()))
{
return;
}
if (!IsValid(GetWorld()->GetFirstPlayerController()))
{
return;
}
GetWorld()->GetFirstPlayerController()->SetShowMouseCursor(true);
Widget->RemoveFromParent();
EndWidget->AddToViewport();
}
AIPlayerBase.h
#pragma once
#include "CoreMinimal.h"
#include "CharacterBase.h"
#include "InputMappingContext.h"
#include "AIPlayerBase.generated.h"
class AWeapon;
UCLASS()
class BASIS_API AAIPlayerBase : public ACharacterBase
{
GENERATED_BODY()
public:
virtual void BeginPlay() override;
virtual void Hit(int32 Damage, AActor* ByWho) override;
virtual void Attack() override;
UPROPERTY(EditAnywhere)
TSubclassOf<AWeapon> Weapon;
UPROPERTY()
TObjectPtr<AWeapon> WeaponActor;
void Move(const FInputActionValue& Value);
};
AIPlayerBase.cpp
#include "AIPlayerBase.h"
#include "Weapon.h"
#include "EnhancedInputSubsystems.h"
void AAIPlayerBase::BeginPlay()
{
Super::BeginPlay();
WeaponActor = GetWorld()->SpawnActor<AWeapon>(Weapon);
if (IsValid(WeaponActor))
{
FAttachmentTransformRules TransformRules(EAttachmentRule::SnapToTarget, true);
WeaponActor->AttachToComponent(GetMesh(), TransformRules, TEXT("WeaponSocket"));
WeaponActor->SetOwner(this);
}
}
void AAIPlayerBase::Hit(int32 Damage, AActor* ByWho)
{
Super::Hit(Damage, ByWho);
if (CurrentHP > 0) {
return;
}
if (IsValid(WeaponActor))
{
WeaponActor->Destroy();
}
Destroy();
}
void AAIPlayerBase::Attack()
{
Super::Attack();
if (IsValid(WeaponActor))
{
WeaponActor->Fire();
}
}
void AAIPlayerBase::Move(const FInputActionValue& Value)
{
FVector2D MovementVector = Value.Get<FVector2D>();
if (Controller != nullptr)
{
AddMovementInput(GetActorForwardVector(), MovementVector.Y);
AddMovementInput(GetActorRightVector(), MovementVector.X);
}
}
플레이 영상
