UE4 C++ 多AI攻击
1.UE4 C++ AI实现跳跃(上下平台)
2.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);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律