遇到的问题:
- 如何使用UEC++实现UE蓝图中的方法
- 世界中存在一个不是自己放置的类
- 在一个C++类中调用另一个C++类中的变量或者函数
- Get All Actors of Class的替代
- 蓝图Actor的名字和在世界中获取到的Actor中的名字不相同
详见博客文章-UE遇到的各种问题
补充:文章仅发表了代码部分,蓝图资产设置参考下面链接,A星算法规划路径参考:UE4-A星寻路制作
实现A星算法规划路径
代码
AStar_test2Character.h
// 定义的变量
public:
// 自定义定时器句柄
FTimerHandle TimerHandle;
UFUNCTION()
void ShowWay();
// 自定义函数 - 进入FuncAStarMoveCaculate
UFUNCTION(BlueprintCallable)
void AStarMoveCaculate();
// A星算法移动代价计算函数
void FuncAStarMoveCaculate();
// 曼哈顿距离计算函数
int32 DistanceToDistination(Aboard* board);
// 用于存储样条点的坐标
TArray<FVector> AllPositions;
// 开始移动的节点
Aboard* MoveStartBoard = nullptr;
// 终点的坐标
AMyDestination* Destination = nullptr;
// 存储已经扫描过的节点
TArray<Aboard*> BoardHasScanned;
// 计算已经走过的步数
int32 moveStep = 0;
// 盒体检测参数
UPROPERTY(EditAnywhere,Category="BoxTrace")
FVector HalfSize = FVector(50.0f, 50.0f, 10.0f);
// 盒体检测参数
UPROPERTY(EditAnywhere,Category="BoxTrace")
FRotator Orientation = FRotator(0.0f, 45.0f, 0.0f);
// 判断方向
int32 isChange = -1;
//上一个坐标
FVector LastPosition;
// 加入指定位置的点于数组中
void AddSpecialPositionToArray();
AStar_test2Character.cpp
void AAStar_test2Character::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// 绑定输入:键盘1 形成A星算法路径;键盘2 根据A星算法路径形成样条点;键盘3 AI开始按照A星算法规划路径移动(实际上是按照样条点移动)
PlayerInputComponent->BindAction(TEXT("showWay"), IE_Pressed, this, &AAStar_test2Character::ShowWay);
PlayerInputComponent->BindAction(TEXT("createSpine"), IE_Pressed, this, &AAStar_test2Character::SpineSet);
PlayerInputComponent->BindAction(TEXT("AiMove"), IE_Pressed, this, &AAStar_test2Character::AIMove);
}
void AAStar_test2Character::ShowWay()
{
// 获取World
UWorld* World = GetWorld();
if (!World) return;
// Step 1: 获取指定类的所有实例
TArray<AActor*> FoundActors;
// 获取StartActor 起始点 获取DestinationActor 终点
UGameplayStatics::GetAllActorsOfClass(World, AMyStart::StaticClass(), FoundActors);
AActor* StartActor = FoundActors.Num() > 0 ? FoundActors[0] : nullptr;
UGameplayStatics::GetAllActorsOfClass(World, AMyDestination::StaticClass(), FoundActors);
AActor* DestinationActor = FoundActors.Num() > 0 ? FoundActors[0] : nullptr;
if (!StartActor || !DestinationActor) return;
// 将终点的坐标放入变量 Destination
Destination = Cast<AMyDestination>(DestinationActor);
// 获取起始点的坐标位置 并添加到AllPositions(样条点数组)中
FVector StartActorLocation = StartActor->GetActorLocation();
AllPositions.Add(StartActorLocation);
// 初始化LastPosition,用于之后判断是否需要添加新的样条点
LastPosition = StartActorLocation;
// 自定义线条追踪穿过的Z轴
float StartZ = 100.0f;
float EndZ = -100.0f;
FVector StartTraceLocation = FVector(StartActorLocation.X, StartActorLocation.Y, StartZ);
FVector EndTraceLocation = FVector(StartActorLocation.X, StartActorLocation.Y, EndZ);
// Step 2: 进行射线追踪
FHitResult HitResult;
// 定义 trace 的行为参数
FCollisionQueryParams TraceParams(FName(TEXT("")), false, this);
// 定义 trace 的对象参数,即需要检测的对象类型
FCollisionObjectQueryParams ObjectQueryParams;
// 设置查询世界中动态的物体
ObjectQueryParams.AddObjectTypesToQuery(ECC_WorldDynamic);
// 进行 trace 射线检测
bool bHit = GetWorld()->LineTraceSingleByObjectType(
HitResult,
StartTraceLocation,
EndTraceLocation,
ObjectQueryParams,
TraceParams
);
// Step 3: 处理命中结果
if (!bHit) UE_LOG(LogTemp, Error, TEXT("射线没有命中!"));
AActor* HitActor = HitResult.GetActor();
UE_LOG(LogTemp, Warning, TEXT("命中的Actor是: %s"), *HitActor->GetClass()->GetName());
// Step 4: 类型转换为 BP_Board
Aboard* Board = Cast<Aboard>(HitActor);
if (!Board) UE_LOG(LogTemp, Warning, TEXT("命中了Actor,但不是BP_Board!"));
// 更新 MoveStartBoard 添加到 BoardHasScanned 中
MoveStartBoard = Board;
BoardHasScanned.Add(MoveStartBoard);
// 计算移动代价
AStarMoveCaculate();
}
void AAStar_test2Character::AStarMoveCaculate()
{
GetWorld()->GetTimerManager().SetTimer(TimerHandle,this,&AAStar_test2Character::FuncAStarMoveCaculate,0.01f,false);
}
void AAStar_test2Character::FuncAStarMoveCaculate()
{
// 初始化 当前位置至终点 的最小移动代价
int32 minMoveCost = 9999;
// Step 1: 初始化盒体追踪检测参数,获取前后左右四个方向的位置进行判断
TArray<FHitResult> OutHits;
// 设置追踪中心 的起点和终点的Z值
float StartZ = 100.0f;
float EndZ = -100.0f;
FVector Location = MoveStartBoard->GetActorLocation();
FVector StartTraceLocation = FVector(Location.X, Location.Y, StartZ);
FVector EndTraceLocation = FVector(Location.X, Location.Y, EndZ);
// 忽略的 Actor 数组和对象类型
TArray<AActor*> ActorsToIgnore;
// 指定要检测的对象类型数组
TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypes;
ObjectTypes.Add(UEngineTypes::ConvertToObjectType(ECC_WorldDynamic));
// Step 2: 执行盒体追踪
bool bHit = UKismetSystemLibrary::BoxTraceMultiForObjects(
this, //当前对象作为追踪发起者
StartTraceLocation,
EndTraceLocation,
HalfSize,
Orientation,
ObjectTypes,
false, // 是否检测复杂碰撞 false表示简单碰撞
ActorsToIgnore,
EDrawDebugTrace::None, // 是否绘制调试信息
OutHits,
true // 是否返回命中结果
);
// Step 3: 循环遍历检测结果,得到下一个前进的点
if(OutHits.Num() > 0){
// Step 3.1: 计算得到检测结果中距离最近的点
for (const FHitResult& HitResult : OutHits)
{
// 获取该方向的命中结果
AActor* HitActor = HitResult.GetActor();
if (!HitActor) continue;
// 类型转换为 BP_Board
Aboard* Board = Cast<Aboard>(HitActor);
if (!Board || BoardHasScanned.Contains(Board)) continue;
// 若为BP_Board且不包含在 BoardHasScanned 中,计算移动代价 和最小的移动方向进行比较
BoardHasScanned.Add(Board);
int32 moveCost = DistanceToDistination(Board) + moveStep;
if (minMoveCost > moveCost)
{
MoveStartBoard = Board;
minMoveCost = moveCost;
}
}
// Step 3.2: 判断检测结果中距离最近的点 是否在可以移动的范围内
if(MoveStartBoard != nullptr)
{
// 判断该位置是否可以加入样条点数组中,函数在下一个专题代码中展示
AddSpecialPositionToArray();
// 修改moveStartBoard的颜色,来示该位置已经在A星算法规划的路线中
MoveStartBoard->SetColor(MoveStartBoard);
// 更新移动步数
moveStep++;
// 判断是否到达终点
if (!(DistanceToDistination(MoveStartBoard) < 2))
{
AStarMoveCaculate();
}
else
{
// 将终点加入AllPositions
AllPositions.Add(Destination->GetActorLocation());
}
}
}
}
// 获取 目标点距离终点的 曼哈顿距离
int32 AAStar_test2Character::DistanceToDistination(Aboard* board)
{
FVector BoardLocation = board->GetActorLocation();
FVector DistinationLocation = Destination->GetActorLocation();
// 表示每个网格单元的大小,用于将实际距离转换为 格子 数量
float GridSize = 100.0f;
float Dt = FMath::Abs(BoardLocation.X - DistinationLocation.X) + FMath::Abs(BoardLocation.Y - DistinationLocation.Y);
Dt = FMath::GridSnap(Dt,GridSize);
Dt /= GridSize;
int32 Distance = FMath::CeilToInt(Dt);
return Distance;
}
}
A星算法路径存入样条点数组中
难点
如何获取样条线组件
优化前:在
AStar_test2Character.h
创建一个ASpine
类的变量SpineRef
,然后在AStar_test2Character.cpp
中创建了一个ASpine
类的对象,对该对象执行函数操作;此时Spine.cpp
类中的this
指的是系统创建的对象。优化后:直接在
AStar_test2Character.cpp
中使用GetAllActorsOfClass
方法获取到真正的ASpine
的引用,然后直接对ASpine
本体执行函数操作;此时Spine.cpp
类中的this
指的是Spine.cpp
本身。
优化前
AStar_test2Character
类和Spine
类
// AStar_test2Character.h
ASpine* SpineRef;
// AStar_test2Character.cpp
if (!SpineRef)
{
// 创建了一个Spine的动态对象,此步以后,由 GetAllActorsOfClass 找到的ASpine会有两个,一个是系统创建的,一个是自己创建的
SpineRef = GetWorld()->SpawnActor<ASpine>();
}
if (SpineRef)
{
UE_LOG(LogTemp, Warning, TEXT("进入执行SpineSet222"));
// 得到的结果是Spine0,即系统创建的ASpine类的动态对象
UE_LOG(LogTemp, Warning, TEXT("SpineRef是 %s"), *SpineRef->GetName());
SpineRef->setSpine();
}
===================================================
// Spine.cpp
// 查找名为 "BP_Spine" 的 Actor
ASpine* SpineActor = nullptr;
for (TActorIterator<ASpine> It(GetWorld()); It; ++It)
{
SpineActor = *It;
if (SpineActor && SpineActor->GetName().Contains("BP_Spine"))
{
UE_LOG(LogTemp, Warning, TEXT("Found BP_Spine: %s"), *SpineActor->GetName());
}
}
if (!SpineActor)
{
UE_LOG(LogTemp, Warning, TEXT("未找到名为 BP_Spine 的 Actor"));
return;
}
// 获取样条组件
USplineComponent* SplineComp = SpineActor -> FindComponentByClass<USplineComponent>();
if (!SplineComp) return;
优化后(减少了很多不必要的代码)
// AStar_test2Character.cpp
UWorld* World = GetWorld();
TArray<AActor*> FoundActors;
UGameplayStatics::GetAllActorsOfClass(World, ASpine::StaticClass(), FoundActors);
ASpine* SpineRefT = Cast<ASpine>(FoundActors[0]);
SpineRefT->setSpine();
===================================================
// Spine.cpp
// 获取样条组件
USplineComponent* SplineComp = this -> FindComponentByClass<USplineComponent>();
if (!SplineComp) return;
代码
// AStar_test2Character.cpp
// 判断是否可以加入样条点数组中,方向改变才加入
void AAStar_test2Character::AddSpecialPositionToArray()
{
// 判断当前点相对于上一个点要走的方向 只有横竖两个方向,故只判断X轴是否相等
FVector moveStartLocation = MoveStartBoard->GetActorLocation();
bool isXSame = moveStartLocation.X - LastPosition.X == 0.0f;
// isChange: -1 表示是加入的第一个点;0 表示上一个点的方向是Y轴的;1 表示上一个点的方向是X轴
if (isChange == -1)
{
if (isXSame)
{
isChange = 0;
}
else
{
isChange = 1;
}
LastPosition = moveStartLocation;
}
else // 不是第一个加入的点,判断方向有没有变化
{
if (isXSame)
{
if (isChange == 0) // 方向没有改变
{
LastPosition = moveStartLocation;
}
else
{
AllPositions.Add(LastPosition);
LastPosition = moveStartLocation;
isChange = 0;
}
}
else
{
if (isChange == 1) // 方向没有改变
{
LastPosition = moveStartLocation;
}
else
{
AllPositions.Add(LastPosition);
LastPosition = moveStartLocation;
isChange = 1;
}
}
}
}
人物实现AI行为树——按照样条线行走
难点
自定义实现AI行为树中的Decorator和Task节点
1. 创建C++类
Decorator应继承BTDecorator
,Task应继承BTTaskNode
;如果希望使用黑板键,可以继承自相应的有_BlackboardBase
后缀的类,这个类会提供一个黑板键成员变量,当然也可以不继承这个_BlackboardBase
,自己写
2. 关键函数
只举例了代码中用到的部分函数,其他函数请自己参考
BTDecorator.h
和BTTaskNode.h
中的函数以及注释
Decorator的判断函数为
virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
重写这个函数,返回值是能否执行子节点
Task的开始执行函数
/** starts this task, should return Succeeded, Failed or InProgress
* (use FinishLatentTask() when returning InProgress)
* this function should be considered as const (don't modify state of object) if node is not instanced! */
AIMODULE_API virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory);
可用的返回值由三种
EBTNodeResult::Failed
EBTNodeResult::Succeeded
EBTNodeResult::InProgress
前两种自然就是立刻返回当前节点执行成功与否,最后一种是表示节点虽然已经开始执行,但暂时还没执行结束,当希望结束执行InProgress的节点(以成功执行举例),就需要调用
FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
该函数一般在TickTask
中调用,也就是每个Tick都去检查是否执行完成。
TickTask函数原型
/** ticks this task
* this function should be considered as const (don't modify state of object) if node is not instanced!
* bNotifyTick must be set to true for this function to be called
* Calling INIT_TASK_NODE_NOTIFY_FLAGS in the constructor of the task will set this flag automatically */
AIMODULE_API virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds);
根据上面的注释,我们可以知道,只有当设置bNotifyTick
= true时TickTask
才会执行;同理还有bNotifyActivation
于OnNodeActivation
、bNotifyDeactivation
于OnNodeDeactivation
如何在AIController
中获取黑板,并修改其中的变量值
1. 定义变量
// 获取控制角色,初始化AI逻辑和状态
void OnPossess(APawn* possesPawn);
// AI移动的函数
UFUNCTION(BlueprintCallable, Category = "AI")
void goAIMove();
// 行为树
UPROPERTY(EditAnywhere, Category = "AI")
class UBehaviorTree* BehaviorTree;
// 黑板组件
UPROPERTY()
class UBlackboardComponent* BlackboardComp;
// 存储黑板资产的引用
UPROPERTY(EditDefaultsOnly, Category = "AI")
UBlackboardData* BlackboardAsset;
UBlackboardComponent* BlackboardComp
和UBlackboardData* BlackboardAsset
的区别
UBlackboardComponent
:这是一个实际的游戏性组件(Actor Component),你可以将其添加到你的AI控制器或者需要访问黑板数据的角色上。它负责运行时存储所有黑板键值对(Key-Value Pairs),这些键值对代表了影响AI决策的数据点。例如,目标位置、最近看到的敌人等。简单来说,UBlackboardComponent
就是AI用来在游戏过程中实时记录和查询各种状态的地方。UBlackboardData
:这是一个资产(Asset),定义了黑板的结构或模式。它描述了可以在UBlackboardComponent
中使用的不同键(Keys)以及每个键的类型(比如Object, Vector, Bool等)。这个资产主要用于设计阶段,在编辑器中设置,决定了哪些键可用及其默认值。因此,UBlackboardData
更像是一个模板或蓝图,指定了你希望在黑板上跟踪的所有信息的种类。
2. 在OnPossess
函数中初始化行为树、黑板组件
当一个 AIController
开始控制一个新的 Pawn
(通常是NPC或玩家角色的基类)时,就会调用 OnPossess
函数。这个函数的主要作用是初始化与新控制的角色相关的任何AI逻辑或状态。具体来说,它允许AI控制器执行一些设置操作,比如为该角色创建行为树、设置黑板数据、初始化感知系统等。
void AMyAIController::OnPossess(APawn* possesPawn)
{
Super::OnPossess(possesPawn);
// 检查黑板资产是否已设置
if (BlackboardAsset)
{
// 初始化 UBlackboardComponent 使用指定的 UBlackboardData 资产
if (UseBlackboard(BlackboardAsset, BlackboardComp))
{
UE_LOG(LogTemp, Warning, TEXT("黑板初始化成功"));
// 启动行为树(如果有)
if (BehaviorTree)
{
RunBehaviorTree(BehaviorTree);
}
}
else
{
UE_LOG(LogTemp, Error, TEXT("黑板初始化失败!请检查BlackboardAsset是否正确设置"));
}
}
else
{
UE_LOG(LogTemp, Error, TEXT("请设置BlackboardAsset!"));
}
}
UseBlackboard(BlackboardAsset, BlackboardComp)
函数调用的作用
具体来说,该函数的作用如下:
- 参数1 (
BlackboardAsset
): 这是一个指向UBlackboardData
类型对象的指针,定义了黑板中可用键(Keys)的结构和类型。它描述了哪些信息将被存储在黑板上,例如目标位置、最近发现的敌人等。 - 参数2 (
BlackboardComp
): 这是一个指向UBlackboardComponent
类型对象的引用,表示实际运行时用来存储和访问黑板数据的组件。通过这个组件,AI 系统可以在运行时读取和写入黑板上的数据。
当调用 UseBlackboard()
函数时,它会执行以下操作:
- 初始化黑板组件:基于提供的
BlackboardAsset
,初始化关联的BlackboardComp
,使其准备好根据定义的模式存储和管理数据。 - 配置黑板键:根据
BlackboardAsset
中定义的键值对,配置BlackboardComp
以确保所有必要的键都已准备就绪,并可以被行为树中的任务和服务所使用。 - 返回值:如果初始化成功,则返回
true
,否则返回false
。这允许你在初始化失败的情况下进行错误处理或日志记录。
3. 在AIController
类的函数中修改黑板中的变量值
// 设置黑板中的值
if (BlackboardComp)
{
UE_LOG(LogTemp, Warning, TEXT("获取到黑板中的值"));
// 修改黑板中的值,修改后,触发装饰器Decorator判断函数,执行Task中的执行函数
BlackboardComp->SetValueAsBool(FName("AI_Move"), true);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("无法获取黑板组件"));
}
修改黑板中的变量值后,会触发Decorator中的判断函数CalculateRawConditionValue
,若返回True,则执行Task中的ExecuteTask
函数,实现AI行为树的全部过程。
蓝图中AI行为树的配置如下
代码
继承BTDecorator
的UBTD_Judge
类
UBTD_Judge::UBTD_Judge(FObjectInitializer const& ObjectInitializer)
{
// 设置此装饰器在每次 tick 时检查条件
this->bNotifyBecomeRelevant = true;
}
bool UBTD_Judge::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
// 获取与行为树组件关联的黑板组件
UBlackboardComponent* BlackboardComp = OwnerComp.GetBlackboardComponent();
if (BlackboardComp == nullptr)
{
UE_LOG(LogTemp, Warning, TEXT("no BlackboardComp"));
return false;
}
// UE_LOG(LogTemp, Warning, TEXT("进入筛选器"));
// 假设我们要检查名为 "AI_Move" 的布尔键
bool bAI_Move = BlackboardComp->GetValueAsBool("AI_Move");
UE_LOG(LogTemp, Warning, TEXT("筛选器获得的AI_Move的值:%s"), bAI_Move ? TEXT("true") : TEXT("false"));
// 根据黑板中的值返回条件结果
return bAI_Move;
}
继承BTTaskNode
的UUBTT_MoveBySpine
Task的代码有问题,以后有时间会修改
UUBTT_MoveBySpine::UUBTT_MoveBySpine()
{
UE_LOG(LogTemp,Warning,TEXT("进入AI行为树中的Task"))
MoveKeySelector.AddBoolFilter(this,TEXT("AI_Move"));
}
void UUBTT_MoveBySpine::InitializeFromAsset(UBehaviorTree& Asset)
{
Super::InitializeFromAsset(Asset);
UE_LOG(LogTemp,Warning,TEXT("进入AI行为树中的Task的InitializeFromAsset"))
// 获取行为树使用的黑板
UBlackboardData* BBAsset = GetBlackboardAsset();
if (ensure(BBAsset))
{
// 根据黑板中的条目做初始化
MoveKeySelector.ResolveSelectedKey(*BBAsset);
}
}
EBTNodeResult::Type UUBTT_MoveBySpine::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
UE_LOG(LogTemp, Warning, TEXT("进入AI行为树的方法执行中"));
UWorld* World = GetWorld();
if (!World) return EBTNodeResult::Failed;
// 查找名为 "BP_Spine" 的 Actor
ASpine* SpineActor = nullptr;
for (TActorIterator<ASpine> It(GetWorld()); It; ++It)
{
SpineActor = *It;
if (SpineActor && SpineActor->GetName().Contains("BP_Spine"))
{
UE_LOG(LogTemp, Warning, TEXT("Found BP_Spine: %s"), *SpineActor->GetName());
}
}
if (!SpineActor)
{
UE_LOG(LogTemp, Warning, TEXT("未找到名为 BP_Spine 的 Actor"));
return EBTNodeResult::Failed;
}
// 获取样条组件
USplineComponent* SplineComp = SpineActor -> FindComponentByClass<USplineComponent>();
if (!SplineComp) return EBTNodeResult::Failed;
int32 NumberOfSplinePoints = SplineComp->GetNumberOfSplinePoints();
UE_LOG(LogTemp, Warning, TEXT("NumberOfSplinePoints的值为:%d"),NumberOfSplinePoints);
UE_LOG(LogTemp, Warning, TEXT("index值为:%d"),index);
// 获取 AI 控制器
AMyAIController* myAIController = Cast<AMyAIController>(OwnerComp.GetAIOwner());
if (!myAIController) return EBTNodeResult::Failed;
// 获取控制的角色
APawn* ControlledPawn = myAIController->GetPawn();
if (!ControlledPawn) return EBTNodeResult::Failed;
if (index >= NumberOfSplinePoints)
{
index = 1;
}
FVector Point = SplineComp->GetLocationAtSplinePoint(index, ESplineCoordinateSpace::World);
UE_LOG(LogTemp, Warning, TEXT("Points的值为:%s"),*Point.ToString());
UGameplayStatics::GetPlayerController(myAIController->GetWorld(), 0); // 确保世界上下文正确
UAIBlueprintHelperLibrary::SimpleMoveToLocation(myAIController, Point);
index ++;
UE_LOG(LogTemp, Warning, TEXT(""));
return EBTNodeResult::Succeeded;
}
继承AIController
的AMyAIController
// .h
// 获取控制角色,初始化AI逻辑和状态
void OnPossess(APawn* possesPawn);
// AI移动的函数
UFUNCTION(BlueprintCallable, Category = "AI")
void goAIMove();
// 行为树
UPROPERTY(EditAnywhere, Category = "AI")
class UBehaviorTree* BehaviorTree;
// 黑板组件
UPROPERTY()
class UBlackboardComponent* BlackboardComp;
// 存储黑板资产的引用
UPROPERTY(EditDefaultsOnly, Category = "AI")
UBlackboardData* BlackboardAsset;
.cpp
AMyAIController::AMyAIController()
{
BehaviorTree = nullptr;
BlackboardComp = nullptr;
}
void AMyAIController::BeginPlay()
{
Super::BeginPlay();
}
void AMyAIController::OnPossess(APawn* possesPawn)
{
Super::OnPossess(possesPawn);
// 检查黑板资产是否已设置
if (BlackboardAsset)
{
// 初始化黑板 初始化 UBlackboardComponent 使用指定的 UBlackboardData 资产
if (UseBlackboard(BlackboardAsset, BlackboardComp))
{
UE_LOG(LogTemp, Warning, TEXT("黑板初始化成功"));
// 启动行为树(如果有)
if (BehaviorTree)
{
RunBehaviorTree(BehaviorTree);
}
}
else
{
UE_LOG(LogTemp, Error, TEXT("黑板初始化失败!请检查BlackboardAsset是否正确设置"));
}
}
else
{
UE_LOG(LogTemp, Error, TEXT("请设置BlackboardAsset!"));
}
}
void AMyAIController::goAIMove()
{
UE_LOG(LogTemp, Warning, TEXT("AI开始移动"));
// 确保OnPossess已被调用
if (!BlackboardComp && !BlackboardAsset)
UE_LOG(LogTemp, Error, TEXT("BlackboardComp和BlackboardAsset均无法获取!"));
if (!BlackboardComp && BlackboardAsset)
{
UE_LOG(LogTemp, Warning, TEXT("黑板组件为空,尝试重新初始化"));
if (UseBlackboard(BlackboardAsset, BlackboardComp))
{
UE_LOG(LogTemp, Warning, TEXT("黑板重新初始化成功"));
}
else
{
UE_LOG(LogTemp, Error, TEXT("黑板重新初始化失败!"));
return;
}
}
UE_LOG(LogTemp, Warning, TEXT("尝试修改黑板值"));
// 设置黑板中的值
if (BlackboardComp)
{
UE_LOG(LogTemp, Warning, TEXT("获取到黑板中的值"));
// 修改黑板中的值,修改后,触发装饰器Decorator判断函数,执行Task函数
BlackboardComp->SetValueAsBool(FName("AI_Move"), true);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("无法获取黑板组件"));
}
}