UE4 C++ 多AI攻击

原理:利用Token限制同一时间AI的攻击

即为Character设置一个属性Token,来限制Character同时最多可以被多少个AI攻击,当AI执行Attack攻击时,将对该Token进行修改(竞争?),如果Token不满足消耗那么将不会攻击

处理Token函数

消耗Token

当AI进行攻击之前会通过该函数检测Character的Token是否足够用来消耗,如果足够那么将会返回true,表示可以进行攻击

bool UXPlayerStatsComponent::ReserveAttackToken(int Amount)
{
	if (AttackTokenCount >= Amount)
	{
		AttackTokenCount -= Amount;
		return true;
	}
	else return false;
}

返回Token

当AI攻击结束时调用,将该AI占用的Token返回给Character

void UXPlayerStatsComponent::ReturnAttackToken(int Amount)
{
	AttackTokenCount += Amount;
}

存储Token -- 目前AI独有

当AI死亡时,如果其占用了多个Character的Token需要通过该函数控制的TMap结构返回

void AXAI_Character::StoreAttackToken_Implementation(AActor* AttackTarget, int TokenNeeded)
{
	if (CostTokenForTarget.Find(AttackTarget))
	{
		CostTokenForTarget[AttackTarget] += TokenNeeded;
	}
	else
	{
		CostTokenForTarget.Add(AttackTarget, TokenNeeded);
	}
}

攻击判断

在AI的接口中,将为攻击功能增加两个函数,一个是攻击之前检测Token是否满足以决定是否攻击,一个是攻击结束后,将返回所占用的Token。

bool AXAI_Character::AttackStart_Implementation(AActor* AttackTarget, int TokenNeeded)
{
	//校验是否有足够的Token可以供该AI使用攻击
	if (AttackTarget && AttackTarget->Implements<UXDamageInterface>())
	{
		if (IXDamageInterface::Execute_ReserveAttackToken(AttackTarget, TokenNeeded))
		{
			//当有足够的Token将进行攻击,并且存储Token值
			IXAIInterface::Execute_StoreAttackToken(this, AttackTarget, TokenNeeded);
			CurAttackNeedToken = TokenNeeded;
			return true;
		}
		else return false;
	}
	return false;
}

void AXAI_Character::AttackEnd_Implementation(AActor* AttackTarget)
{
	if (AttackTarget && AttackTarget->Implements<UXDamageInterface>())
	{
		IXDamageInterface::Execute_ReturnAttackToken(AttackTarget, CurAttackNeedToken);
		int NeedRemoveTokenFromMap = -1 * CurAttackNeedToken;
		IXAIInterface::Execute_StoreAttackToken(this, AttackTarget, NeedRemoveTokenFromMap);
		bAttacking = false;
		CallOnAttackEndCall.Broadcast();
	}
}

重写AI的攻击行为树任务

如果在攻击之前不对Token进行检测,即如果在Attack函数中对Token进行检测

if (IXDamageInterface::Execute_ReserveAttackToken(AttakTarget, 1))

将会出现AI移动到Actor的攻击范围位置,然后在播放攻击动画前,进行检测发现Token不够,然后再退回到警戒位置。
现在是将Token检测放在行为树任务中,即在Attack之前执行AttackStart函数,该函数会返回一个bool值,如果为false,可以直接返回行为结果,然后跳到行为树的下个分支执行。
最终表现为当AI在警戒状态准备攻击,会检测Token,如果发现Token不足,那么会直接跳过攻击,维持警戒状态,如果Token满足,那么会从警戒状态调整为攻击状态执行攻击

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


#include "AITask/XBTTask_SwordAttack.h"
#include "AI/XAIController.h"
#include "AI/XAI_Character.h"
#include "BehaviorTree/BlackboardComponent.h"

EBTNodeResult::Type UXBTTask_SwordAttack::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	AICon = Cast<AXAIController>(OwnerComp.GetAIOwner());
	//单播绑定,为了实现攻击完成或者移动失败后,返回行为树的结果
	FinishSuccessDelegate.BindUObject(this, &UXBTTask_SwordAttack::FinishSuccessEnd, &OwnerComp);
	FinishFailedDelegate.BindUObject(this, &UXBTTask_SwordAttack::FinishFailedEnd, &OwnerComp);

	if (AICon)
	{
		ControlledPawn = Cast<AXAI_Character>(AICon->GetPawn());
		AttackActor = Cast<AActor>(AICon->GetBlackboardComponent()->GetValueAsObject(AttackTargetKey.SelectedKeyName));
		float Radius = AICon->GetBlackboardComponent()->GetValueAsFloat(AttackRadiusKey.SelectedKeyName);

		if (ControlledPawn && ControlledPawn->Implements<UXAIInterface>())
		{
			//通过AttackStart检测Token条件,如果不满足直接返回false,跳过攻击
			if (IXAIInterface::Execute_AttackStart(ControlledPawn, AttackActor, TokenNeeded))
			{
				//满足后,首先移动AI到攻击范围
				IXAIInterface::Execute_SetMovementSpeed(ControlledPawn, EAIMovement::EAM_Sprinting);
				AICon->ClearFocus(EAIFocusPriority::LastFocusPriority);
				AICon->ReceiveMoveCompleted.Clear();
				//绑定函数,处理AI是否成功到攻击范围
				AICon->ReceiveMoveCompleted.AddDynamic(this, &UXBTTask_SwordAttack::MoveEndCall);
				AICon->MoveToActor(AttackActor, Radius, false);
			}
			else
			{
				return EBTNodeResult::Failed;
			}
		}
	}
	return EBTNodeResult::InProgress;
}

void UXBTTask_SwordAttack::MoveEndCall(FAIRequestID RequestID, EPathFollowingResult::Type Result)
{
	//如果没有成功到达,那么直接调用AttackEnd函数返回占用的Token
	if (Result == EPathFollowingResult::Aborted)
	{
		if (ControlledPawn && ControlledPawn->Implements<UXAIInterface>())
		{
			UE_LOG(LogTemp, Warning, TEXT("MoveFaild"));
			IXAIInterface::Execute_AttackEnd(ControlledPawn, AttackActor);
			FinishFailedDelegate.ExecuteIfBound();
		}
	}
	//如果成功到达,那么执行攻击接口函数
	else
	{
		AICon->SetFocus(AttackActor, EAIFocusPriority::LastFocusPriority);
		if (ControlledPawn && ControlledPawn->Implements<UXAIInterface>())
		{

			ControlledPawn->CallOnAttackEndCall.Clear();
			ControlledPawn->CallOnAttackEndCall.AddDynamic(this, &UXBTTask_SwordAttack::FinishAttack);
			IXAIInterface::Execute_Attack(ControlledPawn, AttackActor);
		}
	}
}

void UXBTTask_SwordAttack::FinishAttack()
{
	FinishSuccessDelegate.ExecuteIfBound();
}

void UXBTTask_SwordAttack::FinishSuccessEnd(UBehaviorTreeComponent* OwnerComp)
{
	FinishLatentTask(*OwnerComp, EBTNodeResult::Succeeded);
}

void UXBTTask_SwordAttack::FinishFailedEnd(UBehaviorTreeComponent* OwnerComp)
{
	FinishLatentTask(*OwnerComp, EBTNodeResult::Failed);
}

作者:XTG111

出处:https://www.cnblogs.com/XTG111/p/18254221

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   XTG111  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
more_horiz
keyboard_arrow_up light_mode palette
选择主题
点击右上角即可分享
微信分享提示