Help us understand the problem. What is going on with this article?

UE4 C++ でビヘイビアツリーのタスクを書く

やあ

前回、UE4 C++ プレイヤーを視認する敵を作るの続きだよ。

@4_mio_11 さんの
【UE4】味方AIの作り方!AIとは何かを学びながら、ブループリントで味方キャラクターを実装しようを参考にしたよ。

前回、Enemyフォルダを作った場所に、AIフォルダを作っておいてね。

info3.0.png

AI作成?

AIフォルダの中に、BT_EnemyBB_Enemyを作っておいてね。
info3.1.png

info3.2.png

BB_Enemyを開いて、PlayerActorを作成、
info3.3.png

とりあえずプレイヤーを見つけたら追いかけるようにする

Visual Studioに移動して、MyEnemyAIController.cppMyEnemyAIController.hにそれぞれ追加してね。

MyEnemyAIController.h
#pragma once

#include "CoreMinimal.h"
#include "AIController.h"
#include "MyProjectCharacter.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BehaviorTreeComponent.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "MyEnemyAIController.generated.h"

UCLASS()
class MYPROJECT_API AMyEnemyAIController : public AAIController
{
    GENERATED_BODY()

    AMyEnemyAIController(const class FObjectInitializer& ObjectInitializer);

    UBehaviorTreeComponent* BehaviorComp;

    UBlackboardComponent* BlackboardComp;

    /* Called whenever the controller possesses a character bot */
    virtual void OnPossess(class APawn* InPawn) override;

    virtual void OnUnPossess() override;

    UPROPERTY(EditDefaultsOnly, Category = AI)
        FName PlayerActorKeyName;

public:
    void SetPlayerActorKey(APawn* Goal);

    AMyProjectCharacter* GetPlayerActorKey();

protected:
    virtual void BeginPlay() override;

    UPROPERTY(EditDefaultsOnly, Category = AI)
        class UBehaviorTree* BehaviorTree;

    FORCEINLINE UBehaviorTreeComponent* GetBehaviorComp() const { return BehaviorComp; }

    FORCEINLINE UBlackboardComponent* GetBlackboardComp() const { return BlackboardComp; }
};

MyEnemyAIController.cpp
#include "MyEnemyAIController.h"
#include "UObject/ConstructorHelpers.h"

AMyEnemyAIController::AMyEnemyAIController(const class FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
    BehaviorComp = ObjectInitializer.CreateDefaultSubobject<UBehaviorTreeComponent>(this, TEXT("BehaviorComp"));
    BlackboardComp = ObjectInitializer.CreateDefaultSubobject<UBlackboardComponent>(this, TEXT("BlackboardComp"));

    ConstructorHelpers::FObjectFinder<UBehaviorTree> BTFinder(TEXT("/Game/ThirdPersonCPP/AI/BT_Enemy"));
    BehaviorTree = BTFinder.Object;

    PlayerActorKeyName = "PlayerActor";
}

void AMyEnemyAIController::BeginPlay()
{
    Super::BeginPlay();
}

void AMyEnemyAIController::OnPossess(class APawn* InPawn)
{
    Super::OnPossess(InPawn);
    BlackboardComp->InitializeBlackboard(*BehaviorTree->BlackboardAsset);

    BehaviorComp->StartTree(*BehaviorTree);
}


void AMyEnemyAIController::OnUnPossess()
{
    Super::OnUnPossess();
    BehaviorComp->StopTree();
}

void AMyEnemyAIController::SetPlayerActorKey(APawn* Pawn)
{
    if (BlackboardComp)
    {
        BlackboardComp->SetValueAsObject(PlayerActorKeyName, Pawn);
    }
}

AMyProjectCharacter* AMyEnemyAIController::GetPlayerActorKey()
{
    if (BlackboardComp)
    {
        return Cast<AMyProjectCharacter>(BlackboardComp->GetValueAsObject(PlayerActorKeyName));
    }
    return nullptr;
}

次に、MyEnemy.cppを少し改造。

MyEnemy.cpp
#include "MyEnemy.h"
#include "Perception/PawnSensingComponent.h"
#include "Engine.h"
#include "MyEnemyAIController.h"
#include "MyProjectCharacter.h"

AMyEnemy::AMyEnemy()
{
    PawnSensingComp = CreateDefaultSubobject<UPawnSensingComponent>(TEXT("PawnSensingComp"));
    PawnSensingComp->SetPeripheralVisionAngle(60.0f);
    PawnSensingComp->SightRadius = 2000;
    PawnSensingComp->OnSeePawn.AddDynamic(this, &AMyEnemy::OnSeePlayer);
}

void AMyEnemy::OnSeePlayer(APawn* Pawn)
{
    AMyEnemyAIController* AIController = Cast<AMyEnemyAIController>(GetController());
    AMyProjectCharacter* Player = Cast<AMyProjectCharacter>(Pawn);
    if (AIController && Player)
    {
        AIController->SetPlayerActorKey(Player);
    }
}

void AMyEnemy::BeginPlay()
{
    Super::BeginPlay();
}

プレイヤーが敵の視野に入ったら、BB_EnemyPlayerActorキーに、プレイヤーが設定されるようになりました。
Unreal Editorに戻ってコンパイルしておいてください。

Unreal Editor内の、モードからNav Mesh Bounds Volumをドラッグしてスケールを適当なものにします。
ナビメッシュが適用される部分を見たい場合はPキーを押してください。画像のようになります。

info3.4.png

BT_Enemyを開いて、CompositesからSelectorTaskからMove Toを配置してください。
Move ToBlackBoardKeyにはPlayerActorキーを設定します。

info3.30.png

再生して、プレイヤーが敵の視野に入ると、追いかけてくるはずだよ。

move to.gif

プレイヤーと敵の間隔を広げる

Visual Studioに移動して、xxx.Build.csGameplayTasksを追加

info3.5.1.png

追加したら、Unreal Editorに戻って、

新規C++クラスから、すべてのクラスを表示にチェックを入れてBTTaskNodeを作成。

info3.6.png

ここでは、BT_T_MoveToTargetにしたよ

BT_T_MoveToTarget.cppBT_T_MoveToTarget.hにそれぞれ追加してね

BT_T_MoveToTarget.h
#pragma once

#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "Engine.h"
#include "BehaviorTree/BTTaskNode.h"
#include "BehaviorTree/BehaviorTreeComponent.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BehaviorTree/Blackboard/BlackboardKeyAllTypes.h"
#include "MyEnemyAIController.h"
#include "BT_T_MoveToTarget.generated.h"

UCLASS()
class MYPROJECT_API UBT_T_MoveToTarget : public UBTTaskNode
{
    GENERATED_BODY()

    virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;

};
BT_T_MoveToTarget.cpp
#include "BT_T_MoveToTarget.h"

EBTNodeResult::Type UBT_T_MoveToTarget::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
    AMyEnemyAIController* Controller = Cast<AMyEnemyAIController>(OwnerComp.GetAIOwner());
    if (Controller == nullptr)
    {
        GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Green, "Controller is null");
        return EBTNodeResult::Failed;
    }

    ACharacter* Parent = Controller->GetCharacter();
    if (Parent == nullptr)
    {
        GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Green, "Parent is null");
        return EBTNodeResult::Failed;
    }

    UBlackboardComponent* BlackboardComp = OwnerComp.GetBlackboardComponent();
    if (!BlackboardComp)
    {
        GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Green, "Blackboard is null");
        return EBTNodeResult::Failed;
    }

    AMyProjectCharacter* Target = Controller->GetPlayerActorKey();
    if (Target == nullptr)
    {
        GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Green, "Target is null");
        return EBTNodeResult::Failed;
    }

    float Distance = Parent->GetDistanceTo(Target);

    if (Controller->MoveToActor(Target, 300) == EPathFollowingRequestResult::RequestSuccessful)
    {
        return EBTNodeResult::Succeeded;
    }
    return EBTNodeResult::Failed;

}

本当は、AIController

void AMyEnemyAIController::SetDistanceToTargetKey(float Distance)
{
    if (BlackboardComp)
    {
        BlackboardComp->SetValueAsFloat(DistanceToTargetKeyName, Distance);
    }
}

float AMyEnemyAIController::GetDistanceToTargetKey()
{
    if (BlackboardComp)
    {
        return BlackboardComp->GetValueAsFloat(DistanceToTargetKeyName);
    }
    return 0.0f;
}

こんなの作って、敵とプレイヤーの距離をUnreal Editor上で、データテーブルとか構造体使って、管理したほうがいいんだろうけど、今回は略したよ。やりたい人は参考を見てみるとできるよ。

さて、Unreal Editorに戻って、BT_EnemySelectorからつないでた、Move Toをさっき作ったBT_T_MoveToTargetに置き換えるよ。

info3.7.png

target.gif

できた。w

このタスク部分をいじるとなんでもできそうな気がする。

途中抜けがあったらごめんなさい

pto8913
なめくじびろーん
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away