Unreal 4引擎中,使用BehaviorTree控制AI行为的C++实现

        很多这样AI的实现都是使用蓝图,尤其在国内网站上,Unreal C++的资料少之又少。本文讲述如何用C++实现一个由BehaviorTree控制的AI,并提供源代码供读者参考。本文目标受众是有一定Unreal开发基础甚至Unreal C++开发基础的开发人员。

        从结构上,此模块可划分为AI、AIController和BehaviorTree三个部分。

一、AI

        根据实际需要,AI可以是Pawn类型或者Character类型。二者的主要区别在于,Character对物体运动功能的支持更好,自带了CharacterMovementComponent等。笔者使用的是Pawn。代码如下:
AGuide.h
#pragma once

#include "GameFramework/Pawn.h"
#include "Guide.generated.h"

UCLASS()
class TOOTH_API AGuide : public APawn
{
    GENERATED_BODY()

public:
    AGuide();

    virtual void BeginPlay() override;

    virtual void Tick(float DeltaSeconds) override;

    virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;

    // ...

    UPROPERTY(EditAnywhere, Category = "BehaviorTree")
    class UBehaviorTree *BehaviorTree;
    
};
        其中,UPROPERTY宏表明BehaviorTree属性可以在编辑器中编辑,如下图所示。

二、AIController

        AIController是AI的大脑,负责控制AI的行为。代码中较为重要的部分是初始化两个Component,即BehaviorTreeComponent和BlackboardComponent,如下:
GuideController.h
#pragma once

#include "AIController.h"
#include "GuideController.generated.h"


UCLASS()
class TOOTH_API AGuideController : public AAIController
{
    GENERATED_BODY()
    
public:
    AGuideController(const class FObjectInitializer& ObjectInitializer);

    virtual void Possess(class APawn* InPawn) override;

    virtual void UnPossess() override;

    UFUNCTION(BlueprintCallable, Category = "Guide")
    void GuideMoveToActor(AActor* DestinationActor, AActor* TurnToActor, float RelativeDistance);

    UFUNCTION(BlueprintCallable, Category = "Guide")
    void GuideMoveToLocation(FVector DestinationLocation, AActor* TurnToActor, float RelativeDistance);

    // ...

    //重要
    UBehaviorTreeComponent* BehaviorTreeComponent;

    //重要
    UBlackboardComponent* BlackboardComponent;
    
    const FName MoveToActorKeyName = "MoveToActor";
    const FName DestinationActorKeyName = "DestinationActor";
    const FName MoveToLocationKeyName = "MoveToLocation";
    
    // ...


private:
    AActor* PreDestinationActor;

    AActor* PreTurnToActor;
};
GuideController.cpp
#include "Tooth.h"
#include "Guide.h"
#include "GuideController.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BehaviorTreeComponent.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BehaviorTree/Blackboard/BlackboardKeyAllTypes.h"

AGuideController::AGuideController(const class FObjectInitializer& ObjectInitializer) 
    : Super(ObjectInitializer), PreDestinationActor(nullptr), PreTurnToActor(nullptr)
{
    BehaviorTreeComponent = ObjectInitializer.CreateDefaultSubobject<UBehaviorTreeComponent>(this, TEXT("BehaviorTree"));
    BlackboardComponent = ObjectInitializer.CreateDefaultSubobject<UBlackboardComponent>(this, TEXT("Blackboard"));
}


void AGuideController::Possess(APawn* InPawn)
{
    Super::Possess(InPawn);

    AGuide* Guide = Cast<AGuide>(InPawn);

    if (Guide)
    {
        if (Guide->BehaviorTree)
        {
            if (Guide->BehaviorTree->BlackboardAsset)
            {
		        //重要
                BlackboardComponent->InitializeBlackboard(*Guide->BehaviorTree->BlackboardAsset);
                BehaviorTreeComponent->StartTree(*Guide->BehaviorTree);
            }
            else
            {
                UE_LOG(GuideLog, Error, TEXT(LOG_HEADER"No blackboard is assigned to the guide's behavior tree."));
            }
        }
        else
        {
            UE_LOG(GuideLog, Error, TEXT(LOG_HEADER"No behavior tree is assigned to guide."));
        }
    }
    else
    {
        UE_LOG(GuideLog, Error, TEXT(LOG_HEADER"The pawn possessed is not an instance of AGuide."));
    }
}


void AGuideController::UnPossess()
{
    Super::UnPossess();
    BehaviorTreeComponent->StopTree();
}


void AGuideController::GuideMoveToActor(AActor* DestinationActor, AActor* TurnToActor, float RelativeDistance)
{
    if (BlackboardComponent)
    {
        BlackboardComponent->SetValueAsFloat(RelativeDistanceKeyName, RelativeDistance);
        if (!BlackboardComponent->GetValueAsBool(MoveToActorKeyName))
        {
            if (!PreDestinationActor || !DestinationActor->GetActorLabel().Equals(PreDestinationActor->GetActorLabel()))
            {
                PreDestinationActor = DestinationActor;
                BlackboardComponent->SetValueAsObject(DestinationActorKeyName, DestinationActor);
                BlackboardComponent->SetValueAsObject(TurnToActorKeyName, TurnToActor);
                BlackboardComponent->SetValueAsBool(MoveToActorKeyName, true);
            }
        }
    }
}


// ...

三、BehaviorTree

        BehaviorTree是AIController控制AI的一种方式。虽然BehaviorTree及Blackboard也可以用C++实现,但非常不推荐,因为二者实际上是对AI行为进行设计,设计人员会随时根据需求更改,并且不存在性能问题(事实是,所有蓝图能做的事情C++都能做,但反过来不成立)。所以这里我主要介绍BehaviorTree Task的实现。
        首先,我选用的父类是BTTask_BlueprintBase,基于此实现的Task就和蓝图实现的Task在使用方法上是相同的。代码中关键的部分是实现BlackboardKeySelector,如下所示:
GuidePlayAudio.h
#pragma once

#include "BehaviorTree/Tasks/BTTask_BlueprintBase.h"
#include "GuidePlayAudio.generated.h"


UCLASS()
class TOOTH_API UGuidePlayAudio : public UBTTask_BlueprintBase
{
    GENERATED_BODY()

public:
    UGuidePlayAudio(const FObjectInitializer& ObjectInitializer);

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

    //重要
    FName GetSelectedAudioNameKey() const;

    FName GetSelectedPlayAudioKey() const;

    //重要
    UPROPERTY(EditAnywhere, Category = "Blackboard")
    struct FBlackboardKeySelector AudioNameKey;

    UPROPERTY(EditAnywhere, Category = "Blackboard")
    struct FBlackboardKeySelector PlayAudioKey;

private:
    UFUNCTION()
    void SetSoundAndPlay();

    class UAudioComponent* AudioComponent;

    const FString GuideAudioPath = "SoundWave'/Game/StarterContent/Audio/AUDIO_NAME.AUDIO_NAME'";

    class USoundBase* Sound;

    const float FadeOutDuration = 1;
};


FORCEINLINE FName UGuidePlayAudio::GetSelectedAudioNameKey() const
{
    return AudioNameKey.SelectedKeyName;
}

FORCEINLINE FName UGuidePlayAudio::GetSelectedPlayAudioKey() const
{
    return PlayAudioKey.SelectedKeyName;
}
GuidePlayAudio.cpp
#include "Tooth.h"
#include "Guide.h"
#include "GuideController.h"
#include "GuideUtils.h"
#include "Runtime/Engine/Classes/Sound/SoundBase.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "GuidePlayAudio.h"


UGuidePlayAudio::UGuidePlayAudio(const FObjectInitializer& ObjectInitializer)
{

}


EBTNodeResult::Type UGuidePlayAudio::ExecuteTask(UBehaviorTreeComponent& OwnerComponent, uint8* NodeMemory)
{
    UBlackboardComponent* Blackboard = OwnerComponent.GetBlackboardComponent();
    if (Blackboard)
    {
        AGuideController* GuideController = Cast<AGuideController>(OwnerComponent.GetAIOwner());
        AGuide* Guide = Cast<AGuide>(GuideController->GetPawn());
        AudioComponent = Guide->Audio;
        FString AudioName = Blackboard->GetValueAsString(GetSelectedAudioNameKey());
        Sound = FGuideUtils::LoadAssetReference<USoundBase>(GuideAudioPath.Replace(*FString("AUDIO_NAME"), *AudioName));
        if (AudioComponent->IsPlaying())
        {
            USoundBase* PresentSound = AudioComponent->Sound;
            if (!Sound->GetName().Equals(PresentSound->GetName()))
            {
                AudioComponent->FadeOut(FadeOutDuration, 0);
                AudioComponent->OnAudioFinished.AddDynamic(this, &UGuidePlayAudio::SetSoundAndPlay);
            }
        }
        else
        {
            SetSoundAndPlay();
        }
        Blackboard->SetValueAsBool(GetSelectedPlayAudioKey(), false);
        UE_LOG(GuideLog, Log, TEXT(LOG_HEADER"Guide play audio finished."));
        return EBTNodeResult::Succeeded;
    }
    else
    {
        UE_LOG(GuideLog, Error, TEXT(LOG_HEADER"Blackboard of the behavior tree is null."));
        return EBTNodeResult::Failed;
    }
}


void UGuidePlayAudio::SetSoundAndPlay()
{
    AudioComponent->SetSound(Sound);
    AudioComponent->FadeIn(0);
    AudioComponent->OnAudioFinished.Clear();
}
        编译后,就会在BehaviorTree的蓝图中看到这个Task。

        所有代码写完并且编译通过后,设计人员就可以在蓝图中设计BehaviorTree了。运行之前,要指定AI的BehaviorTree和AIController Class。至于具体怎么操作AI,AIController已经给你留了许多接口,甚至你可以在BehaviorTree中设计AI的自主行为。

【附】
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值