Unreal Engine 4 —— GAS系统学习 (十) 为敌人增加简单AI功能

本节是为敌人增加简单的AI功能,并没有涉及UPawnSenseComponent,意思就是不可以用听觉或者视觉发现主角的存在。

也不会为主角增加UPawnNoiseEmitterComponent,来制造噪音,只是简单的走向主角并发动攻击。

第一步,我们先新建一个行为树和黑板。

在行为树中挂载黑板

在黑板中新建一个Object类型的变量,其父类选择为Actor。

第二步,在主角类中增加一个UBehaviorTree类型的变量,该变量不是给主角用的,因为敌人类继承自主角,所以为了让敌人类拥有行为树,在主角代码中需要增加。同时增加一个EnemyAttack函数(设置其在蓝图中完成),因为主角发动攻击是通过鼠标,敌人却不是EnemyAttack就是控制敌人如何发起攻击。

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Shinbi | AI")
	class UBehaviorTree* Trees;

	UFUNCTION(BlueprintImplementableEvent)
	void EnemyAttack();

第三步,新建一个C++控制器类,继承自AIController,该控制器的作用是控制敌人。

这是敌人控制器的头文件:

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "AIController.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BehaviorTreeComponent.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BehaviorTree\Blackboard\BlackboardKeyAllTypes.h"
#include "BehaviorTree/BlackboardData.h"
#include "EnemyController.generated.h"

/**
 * 
 */
UCLASS()
class SHIBI_API AEnemyController : public AAIController
{
	GENERATED_BODY()
public:

	AEnemyController();

	UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category = "Shinbe | AI")
	class UBehaviorTreeComponent* AIBehaviorTree;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Shinbe | AI")
	class UBlackboardComponent* AIBlackboard;

private:
	virtual void OnPossess(APawn* InPawn) override;
};

这是敌人控制器的CPP文件:

// Fill out your copyright notice in the Description page of Project Settings.


#include "EnemyController.h"
#include "Shinbi.h"
#include "Engine.h"


AEnemyController::AEnemyController() {
	AIBehaviorTree = CreateDefaultSubobject<UBehaviorTreeComponent>(TEXT("AIBehaviorTree"));
	AIBlackboard = CreateDefaultSubobject<UBlackboardComponent>(TEXT("AIBlackboard"));
}


void AEnemyController::OnPossess(APawn* InPawn)
{
	Super::OnPossess(InPawn);
	AShinbi* enemy = Cast<AShinbi>(InPawn);
	AIBlackboard->InitializeBlackboard(*enemy->Trees->BlackboardAsset);
	AIBehaviorTree->StartTree(*enemy->Trees);
	GEngine->AddOnScreenDebugMessage(-1, 10, FColor::Red, TEXT("AI Initialized!"));
}

在CPP文件中,我们初始化了黑板,运行了行为树。

我们新建一个BTService进行行为树的刷新。

这是BTService的头文件:需要重写父类的TickNode方法。

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "BehaviorTree/BTService.h"
#include "EnemyController.h"
#include "MyBTService.generated.h"

/**
 * 
 */
UCLASS()
class SHIBI_API UMyBTService : public UBTService
{
	GENERATED_BODY()
public:
	UMyBTService();
	virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)override;
};

这是CPP文件:

// Fill out your copyright notice in the Description page of Project Settings.


#include "MyBTService.h"
#include "Shinbi.h"

UMyBTService::UMyBTService() {
	bCreateNodeInstance = true;
}

void UMyBTService::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
	Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
	AEnemyController * EnemyController = Cast<AEnemyController>(OwnerComp.GetAIOwner());

	AShinbi* character = Cast<AShinbi>(GetWorld()->GetFirstPlayerController()->GetPawn());

	EnemyController->AIBlackboard->SetValueAsObject("Target", character);

	UE_LOG(LogTemp, Warning, TEXT("target is set!"));
}

功能目的是不断的将主角信息作为参数设置到黑板中。

在行为树中增加一个Sequence,在下面点击反键加载刚才写好的BTService。

第四步,让敌人走向主角。

新建一个BTTask_BlackboardBase任务。

以下是头文件:要重写父类的ExecuteTask方法。

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "BehaviorTree/Tasks/BTTask_BlackboardBase.h"
#include "BTT_ChaseActor.generated.h"

/**
 * 
 */
UCLASS()
class SHIBI_API UBTT_ChaseActor : public UBTTask_BlackboardBase
{
	GENERATED_BODY()
public:
	UBTT_ChaseActor();
	EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};

以下是CPP文件:

// Fill out your copyright notice in the Description page of Project Settings.


#include "BTT_ChaseActor.h"
#include "EnemyController.h"
#include "Shinbi.h"
#include "Engine.h"

UBTT_ChaseActor::UBTT_ChaseActor() {
	bCreateNodeInstance = true;
}


EBTNodeResult::Type UBTT_ChaseActor::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	AEnemyController* enemyController = Cast<AEnemyController>(OwnerComp.GetAIOwner());

	if (enemyController) {
		AShinbi* target = Cast<AShinbi>(enemyController->AIBlackboard->GetValueAsObject("Target"));
		enemyController->MoveToActor(target,20);
		return EBTNodeResult::Succeeded;
	}
	else {
		return EBTNodeResult::Failed;
	}
}

第五步,让敌人攻击主角。

再新建一个BTTask_BlackboardBase任务。

以下是头文件:

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "AIAttack.generated.h"

/**
 * 
 */
UCLASS()
class SHIBI_API UAIAttack : public UBTTaskNode
{
	GENERATED_BODY()
public:
	UAIAttack();
	EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};

以下是CPP文件:

// Fill out your copyright notice in the Description page of Project Settings.


#include "AIAttack.h"
#include "Shinbi.h"
#include "EnemyController.h"

UAIAttack::UAIAttack() {
	bCreateNodeInstance = true;
}

EBTNodeResult::Type UAIAttack::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	AEnemyController* enemyController = Cast<AEnemyController>(OwnerComp.GetAIOwner());
	AShinbi* enemy = Cast<AShinbi>(enemyController->GetPawn());
	if (enemy) {
		enemy->EnemyAttack();
		return EBTNodeResult::Succeeded;
	}
	else
	{
		return EBTNodeResult::Failed;
	}
	
	
}

此时通过任务文件挂载敌人的攻击事件,之前说过敌人蓝图是继承自主角的,这就是为什么需要在主角中增加一个EnemyAttack方法。

第六步,回到行为树,挂载这些任务。

 

第七步,完善激活敌人的EnemyAttack功能。

进入敌人的蓝图,增加以下连接。

这里没有写WaitGameplayEvent,是因为我们用的招同样是主角的。我们将主角的招式复制一份,挂载到敌人的TryActivateAbilityByClass模块中。

这时候,敌人已经可以完成寻找主角到攻击主角的这一过程。

但是主角不会有任何损伤。

为了更加清晰标记主角与敌人,在主角中增加一个属性,TeamID,用于区别敌我身份。

将本地控制器控制的主角TeamID设置为0。其他的设置为255或者其他数值(该赋值过程在构造函数中完成,这里省略)。

void AShinbi::ArrangeIDbyController()
{
	if (GetController() && GetController()->IsPlayerController()) {
		TeamID = 0;
	}
}

再创建一个方法,用于根据TeamID属性来判断敌我。


bool AShinbi::isOtherHostage(AActor* other)
{
	AShinbi* others = Cast<AShinbi>(other);
	return this->TeamID != others->TeamID;
}

当两者TeamID数值不同的时候,返回真值。意思就是不同的人物角色。

 

第八部,进入主角蓝图进行修改。

增加主角RootComponent碰撞体的Overlap事件。让主角受到攻击时候主角承担伤害。

此时主角已经可以承担伤害了。

 

但是又出现新问题,当主角被杀之后,抛出错误。是因为控制器丧失了要控制的角色造成的。

最后一步,在主角中增加一个方法。

void Dead();

该方法就是用于判断主角控制器与AI控制器在控制的角色死亡消失之后该怎么办的。


void AShinbi::Dead()
{
	APlayerController* localController = Cast<APlayerController>(GetController());
	if (localController) {
		localController->DisableInput(localController);
	}

	AEnemyController* enmeyController = Cast<AEnemyController>(GetController());
	if (enmeyController) {
		enmeyController->GetBrainComponent()->StopLogic("Dead");
	}
}

该方法中禁止了玩家的控制器,以及AI控制器。

这里需要导入相关头文件,例如BrainComponent.h等,可以根据自己情况进行添加。


void AShinbi::onDamage(float Health, float MaxHealth) {
	float temp = Health / MaxHealth;
	UUserWidget* tobeCast = widget->GetUserWidgetObject();
	UHealthWidget* bp123 = Cast<UHealthWidget>(tobeCast);
	bp123->healthBar->SetPercent(temp);
	if (Health <= 0) {
		isDead = true;
		Dead();
		onDie();
	}
}

将Dead方法放置在执行死亡动画之前。

 

全部步骤完成,效果如下:

主角死亡,并未报错。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值