UEC++实现人物按照A星路线移动

遇到的问题:

  1. 如何使用UEC++实现UE蓝图中的方法
  2. 世界中存在一个不是自己放置的类
  3. 在一个C++类中调用另一个C++类中的变量或者函数
  4. Get All Actors of Class的替代
  5. 蓝图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,自己写

45cc20eedf263cb9d4100dca430d8478

2. 关键函数

只举例了代码中用到的部分函数,其他函数请自己参考BTDecorator.hBTTaskNode.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 OnNodeActivationbNotifyDeactivationOnNodeDeactivation

如何在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* BlackboardCompUBlackboardData* BlackboardAsset的区别

  • UBlackboardComponent:这是一个实际的游戏性组件(Actor Component),你可以将其添加到你的AI控制器或者需要访问黑板数据的角色上。它负责运行时存储所有黑板键值对(Key-Value Pairs),这些键值对代表了影响AI决策的数据点。例如,目标位置、最近看到的敌人等。简单来说,UBlackboardComponent 就是AI用来在游戏过程中实时记录和查询各种状态的地方。
  • UBlackboardData:这是一个资产(Asset),定义了黑板的结构或模式。它描述了可以在UBlackboardComponent中使用的不同键(Keys)以及每个键的类型(比如Object, Vector, Bool等)。这个资产主要用于设计阶段,在编辑器中设置,决定了哪些键可用及其默认值。因此,UBlackboardData更像是一个模板或蓝图,指定了你希望在黑板上跟踪的所有信息的种类。

image-20250629142652730

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() 函数时,它会执行以下操作:

  1. 初始化黑板组件:基于提供的 BlackboardAsset,初始化关联的 BlackboardComp,使其准备好根据定义的模式存储和管理数据。
  2. 配置黑板键:根据 BlackboardAsset 中定义的键值对,配置 BlackboardComp 以确保所有必要的键都已准备就绪,并可以被行为树中的任务和服务所使用。
  3. 返回值:如果初始化成功,则返回 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行为树的配置如下

image-20250629143948740

代码

继承BTDecoratorUBTD_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;
}

继承BTTaskNodeUUBTT_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;
}

继承AIControllerAMyAIController

// .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("无法获取黑板组件"));
	}

}

参考文章:

UE4 C++自定义AI行为树的Decorator和Task节点

【UEC++】AI行为树及其相关蓝图部分在C++中的实现

UEC++学习(十一)AI行为树

UE5&C++ 在 AIController中绑定行为树,和黑板初始化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值