UE C++常见结合形态树实现的AI敌人简单逻辑源码分享(最后附带第三人称玩家设计代码)

UE C++常见结合形态树实现的AI敌人简单逻辑源码分享(最后附带第三人称玩家设计代码)

一、获取玩家的当前位置

代码实现功能:在AI敌人运行过程中,持续不断地获取玩家的当前位置,并将这个坐标更新到AI敌人的黑板中。

BTService_PlayerLocation.h

#pragma once #include "CoreMinimal.h" #include "BehaviorTree/Services/BTService_BlackboardBase.h" #include "BTService_PlayerLocation.generated.h" UCLASS() class CRYPTRAIDER_API UBTService_PlayerLocation : public UBTService_BlackboardBase { GENERATED_BODY() public: UBTService_PlayerLocation(); protected: virtual void TickNode(UBehaviorTreeComponent &OwnerComp, uint8 *NodeMemory,float DeltaSeconds)override; }; 

BTService_PlayerLocation.cpp

#include "BTService_PlayerLocation.h" #include "BehaviorTree/BlackboardComponent.h" #include "Kismet/GameplayStatics.h" #include "GameFramework/Pawn.h" UBTService_PlayerLocation::UBTService_PlayerLocation() { NodeName = "Updata Player Location"; } void UBTService_PlayerLocation::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) { Super::TickNode(OwnerComp,NodeMemory,DeltaSeconds); APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(),0); if (PlayerPawn == nullptr) { return; } OwnerComp.GetBlackboardComponent()->SetValueAsVector(GetSelectedBlackboardKey(),PlayerPawn->GetActorLocation()); }

GetSelectedBlackboardKey():获取在UE编辑器中为这个节点指定的那个黑板 Key;

PlayerPawn->GetActorLocation():获取玩家当前的3D坐标。

二、检测是否“看见”玩家

代码实现功能:如果AI敌人看见了,就更新黑板记录玩家对象;如果没看见(被遮挡或超出视野),就清除黑板上的记录。

BTService_PlayerLocationIfSeen.h

#pragma once #include "CoreMinimal.h" #include "BehaviorTree/Services/BTService_BlackboardBase.h" #include "BTService_PlayerLocationIfSeen.generated.h" UCLASS() class CRYPTRAIDER_API UBTService_PlayerLocationIfSeen : public UBTService_BlackboardBase { GENERATED_BODY() public: UBTService_PlayerLocationIfSeen(); protected: virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)override; }; 

BTService_PlayerLocationIfSeen.cpp

#include "BTService_PlayerLocationIfSeen.h" #include "BehaviorTree/BlackboardComponent.h" #include "Kismet/GameplayStatics.h" #include "GameFramework/Pawn.h" #include "AIController.h" UBTService_PlayerLocationIfSeen::UBTService_PlayerLocationIfSeen() { NodeName = "Updata Player Location"; } void UBTService_PlayerLocationIfSeen::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) { Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds); APawn* PlayerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0); if (PlayerPawn == nullptr) { return; } if (OwnerComp.GetAIOwner() == nullptr) { return; } if (OwnerComp.GetAIOwner()->LineOfSightTo(PlayerPawn)) { OwnerComp.GetBlackboardComponent()->SetValueAsObject(GetSelectedBlackboardKey(), PlayerPawn); } else { OwnerComp.GetBlackboardComponent()->ClearValue(GetSelectedBlackboardKey()); } }

SetValueAsObject:这里存的是玩家对象本身,而不是单纯的坐标;

LineOfSightTo(PlayerPawn):AAIController自带,从AI敌人的眼睛位置向目标(玩家)发射射线;

ClearValue:LineOfSightTo返回false,黑板上的TargetActor就会被清空(设为 null)。

三、敌人简单巡逻逻辑

代码实现功能:让AI 在一定范围内围绕“出生点”,寻找的下一个目标点,并进行移动

BTTask_FindRandomLocation.h

#pragma once #include "CoreMinimal.h" #include "BehaviorTree/Tasks/BTTask_BlackboardBase.h" #include "BTTask_FindRandomLocation.generated.h" UCLASS() class CRYPTRAIDER_API UBTTask_FindRandomLocation : public UBTTask_BlackboardBase { GENERATED_BODY() public: UBTTask_FindRandomLocation(); private: virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override; UPROPERTY(EditAnywhere, Category = "AI") float PatrolRadius = 1000.f; };

BTTask_FindRandomLocation.cpp

#include "BTTask_FindRandomLocation.h" #include "BehaviorTree/BlackboardComponent.h" #include "AIController.h" #include "NavigationSystem.h" UBTTask_FindRandomLocation::UBTTask_FindRandomLocation() { NodeName = "Find Random Location"; } EBTNodeResult::Type UBTTask_FindRandomLocation::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) { Super::ExecuteTask(OwnerComp, NodeMemory); if (OwnerComp.GetAIOwner() == nullptr) return EBTNodeResult::Failed; UNavigationSystemV1* NavSystem = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld()); if (NavSystem == nullptr) return EBTNodeResult::Failed; FVector Origin = OwnerComp.GetBlackboardComponent()->GetValueAsVector(TEXT("StartLocation")); FNavLocation ResultLocation; if (NavSystem->GetRandomReachablePointInRadius(Origin, PatrolRadius, ResultLocation)) { OwnerComp.GetBlackboardComponent()->SetValueAsVector(GetSelectedBlackboardKey(), ResultLocation.Location); return EBTNodeResult::Succeeded; } return EBTNodeResult::Failed; }

NavSystem->GetRandomReachablePointInRadius(Origin, PatrolRadius, ResultLocation):在半径内寻找随机导航点;

OwnerComp.GetBlackboardComponent()->SetValueAsVector(GetSelectedBlackboardKey(), ResultLocation.Location):如果找到了,将结果写入我们选中的Blackboard Key,并进行移动

四、命令AI执行“射击”

代码实现功能:连接AI的“大脑”(行为树)与“身体”(角色类),命令 AI 执行“射击”这个动作

BTTask_Shoot.h

#pragma once #include "CoreMinimal.h" #include "BehaviorTree/BTTaskNode.h" #include "BTTask_Shoot.generated.h" UCLASS() class CRYPTRAIDER_API UBTTask_Shoot : public UBTTaskNode { GENERATED_BODY() public: UBTTask_Shoot(); private: virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)override; }; 

BTTask_Shoot.cpp

#include "BTTask_Shoot.h" #include "AIController.h" #include "BP_MyCharacter.h" UBTTask_Shoot::UBTTask_Shoot() { NodeName = "Shoot"; } EBTNodeResult::Type UBTTask_Shoot::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) { Super::ExecuteTask(OwnerComp, NodeMemory); if (OwnerComp.GetAIOwner() == nullptr) { return EBTNodeResult::Failed; } ABP_MyCharacter* Character = Cast<ABP_MyCharacter>(OwnerComp.GetAIOwner()->GetPawn()); if (Character == nullptr) { return EBTNodeResult::Failed; } Character->Shoot(); return EBTNodeResult::Succeeded; }

return EBTNodeResult::Succeeded:行为树收到成功信号后,会继续执行后续的逻辑;

OwnerComp.GetAIOwner()->GetPawn():通过AI控制器获取它当前附身的Pawn;

Character->Shoot():调用写好的射击逻辑。

五、第三人称射击游戏角色类

代码实现功能:集成战斗、生存与高阶机动逻辑,通过动态生成与销毁Actor的方式构建了支持可扩展的多武器循环切换系统,利用重写的伤害处理函数与GameMode深度协作以管理角色的生命周期与死亡判定,同时通过多重射线检测技术实现了一个具备防穿墙和地形自适应能力的“智能瞬移”技能,从而在保证游戏物理稳定性的前提下赋予了角色灵活的战术位移手段。

BP_MyCharacter.h

#pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "BP_MyCharacter.generated.h" class AGun; UCLASS() class CRYPTRAIDER_API ABP_MyCharacter : public ACharacter { GENERATED_BODY() public: // Sets default values for this character's properties ABP_MyCharacter(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; public: void Dash(); UFUNCTION(BlueprintPure) bool IsDead() const; UFUNCTION(BlueprintPure) float GetHealthPercent() const; // Called every frame virtual void Tick(float DeltaTime) override; // Called to bind functionality to input virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; virtual float TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override; void Shoot(); void SwitchWeapon(); private: void MoveForward(float AxisValue); void MoveRight(float AxisValue); void LookUpRate(float AxisValue); UPROPERTY(EditAnywhere) float RotationRate =10; /*UPROPERTY(EditDefaultsOnly) TSubclassOf<AGun> GunClass;*/ UPROPERTY(EditDefaultsOnly, Category = "Combat") TArray<TSubclassOf<AGun>> GunClasses; int32 CurrentGunIndex = 0; UPROPERTY(EditDefaultsOnly) AGun* Gun; UPROPERTY(EditDefaultsOnly) float MaxHealth = 100; UPROPERTY(EditDefaultsOnly) float Health = 0; // 重置冷却的回调函数 void ResetDash(); // 瞬移距离 (默认 15米) UPROPERTY(EditAnywhere, Category = "Abilities") float DashDistance = 500.0f; // 冷却时间 UPROPERTY(EditAnywhere, Category = "Abilities") float DashCooldown = 5.0f; // 瞬移后的悬空高度 (增加落地感,建议 50-80) UPROPERTY(EditAnywhere, Category = "Abilities|Dash") float DashHeightOffset = 60.0f; // 是否可以使用 (冷却标志位) bool bCanDash = true; // 定时器句柄 (用于计算冷却) FTimerHandle DashTimerHandle; // 瞬移时的特效 (可选) UPROPERTY(EditDefaultsOnly, Category = "Abilities") UParticleSystem* DashEffect; UPROPERTY(EditDefaultsOnly, Category = "Abilities") USoundBase* DashSound; }; 

BP_MyCharacter.cpp

#include "Gun.h" #include "BP_MyCharacter.h" #include "Components/CapsuleComponent.h" #include "SimpleShooterGameModeBase.h" #include "GameFramework/GameSession.h" #include "Kismet/GameplayStatics.h" // Sets default values ABP_MyCharacter::ABP_MyCharacter() { // Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; } // Called when the game starts or when spawned void ABP_MyCharacter::BeginPlay() { Super::BeginPlay(); Health = MaxHealth; // 隐藏原本模型的骨骼(保持不变) GetMesh()->HideBoneByName(TEXT("weapon_r"), EPhysBodyOp::PBO_None); //初始化生成第一把枪(如果有的话) CurrentGunIndex = 0; if (GunClasses.Num() > 0) { Gun = GetWorld()->SpawnActor<AGun>(GunClasses[CurrentGunIndex]); if (Gun) { Gun->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, TEXT("WeaponSocket")); Gun->SetOwner(this); } } } // Called every frame void ABP_MyCharacter::Tick(float DeltaTime) { Super::Tick(DeltaTime); } // Called to bind functionality to input void ABP_MyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); PlayerInputComponent->BindAxis(TEXT("MoveForward"), this , &ABP_MyCharacter::MoveForward); PlayerInputComponent->BindAxis(TEXT("LookUp"), this, &APawn::AddControllerPitchInput); PlayerInputComponent->BindAxis(TEXT("LookUpRate"), this, &ABP_MyCharacter::LookUpRate); PlayerInputComponent->BindAxis(TEXT("MoveRight"), this, &ABP_MyCharacter::MoveRight); PlayerInputComponent->BindAxis(TEXT("LookRight"), this, &APawn::AddControllerYawInput); PlayerInputComponent->BindAction(TEXT("Jump"), EInputEvent::IE_Pressed,this,&ACharacter::Jump); PlayerInputComponent->BindAction(TEXT("Shoot"), EInputEvent::IE_Pressed, this, &ABP_MyCharacter::Shoot); PlayerInputComponent->BindAction(TEXT("SwitchWeapon"), EInputEvent::IE_Pressed, this, &ABP_MyCharacter::SwitchWeapon); PlayerInputComponent->BindAction("Dash", IE_Pressed, this, &ABP_MyCharacter::Dash); } float ABP_MyCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) { float DamageToApplied = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser); DamageToApplied = FMath::Min(Health , DamageToApplied); Health -= DamageToApplied; UE_LOG(LogTemp, Warning, TEXT("Health is %f"), Health); if (IsDead()) { ASimpleShooterGameModeBase* GameMode = GetWorld()->GetAuthGameMode<ASimpleShooterGameModeBase>(); if (GameMode != nullptr) { GameMode->PawnKilled(this); } DetachFromControllerPendingDestroy(); GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision); } return DamageToApplied; } bool ABP_MyCharacter::IsDead() const { return Health <= 0; } void ABP_MyCharacter::MoveForward(float AxisValue) { AddMovementInput(GetActorForwardVector() * AxisValue); } void ABP_MyCharacter::MoveRight(float AxisValue) { AddMovementInput(GetActorRightVector() * AxisValue); } void ABP_MyCharacter::LookUpRate(float AxisValue) { AddControllerPitchInput(AxisValue * RotationRate * GetWorld()->GetDeltaSeconds()); } void ABP_MyCharacter::Shoot() { if (Gun != nullptr) { Gun->PullTrigger(); } } float ABP_MyCharacter::GetHealthPercent() const { return Health / MaxHealth; } void ABP_MyCharacter::SwitchWeapon() { //如果没配置枪,或者只有一把枪,就不切换 if (GunClasses.Num() <= 1) return; //销毁当前手中的枪 if (Gun) { Gun->Destroy(); Gun = nullptr; } //计算下一个索引 CurrentGunIndex = (CurrentGunIndex + 1) % GunClasses.Num(); //生成新枪 if (GunClasses[CurrentGunIndex]) { Gun = GetWorld()->SpawnActor<AGun>(GunClasses[CurrentGunIndex]); if (Gun) { Gun->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, TEXT("WeaponSocket")); Gun->SetOwner(this); // UE_LOG(LogTemp, Warning, TEXT("Switched to weapon index: %d"), CurrentGunIndex); } } } void ABP_MyCharacter::Dash() { if (!bCanDash) return; FVector StartLocation = GetActorLocation(); FVector ForwardVector = GetActorForwardVector(); FVector TargetLocation = StartLocation + (ForwardVector * DashDistance); FVector FinalLocation = TargetLocation; UWorld* World = GetWorld(); if (!World) return; // 防止卡墙检测 FHitResult WallHit; FCollisionQueryParams Params; Params.AddIgnoredActor(this); bool bHitWall = World->LineTraceSingleByChannel( WallHit, StartLocation, TargetLocation, ECollisionChannel::ECC_Visibility, Params ); if (bHitWall) { // 如果撞墙,停在墙壁前50单位处,防止模型穿插 FinalLocation = WallHit.Location - (ForwardVector * 50.0f); } FVector UpVector = FVector(0, 0, 1); FVector CeilingCheckStart = FinalLocation; // 检查头顶上方 FVector CeilingCheckEnd = FinalLocation + (UpVector * 100.0f); FHitResult CeilingHit; bool bHitCeiling = World->LineTraceSingleByChannel( CeilingHit, CeilingCheckStart, CeilingCheckEnd, ECollisionChannel::ECC_Visibility, Params ); // 只有头顶没撞到东西,才执行“抬高”操作 if (!bHitCeiling) { FinalLocation.Z += DashHeightOffset; } if (DashEffect) { UGameplayStatics::SpawnEmitterAtLocation(World, DashEffect, StartLocation, GetActorRotation()); } TeleportTo(FinalLocation, GetActorRotation()); if (DashEffect) { UGameplayStatics::SpawnEmitterAtLocation(World, DashEffect, FinalLocation, GetActorRotation()); } if (DashSound) { UGameplayStatics::PlaySoundAtLocation(this, DashSound, StartLocation); } bCanDash = false; World->GetTimerManager().SetTimer(DashTimerHandle, this, &ABP_MyCharacter::ResetDash, DashCooldown, false); } void ABP_MyCharacter::ResetDash() { bCanDash = true; } 

个人观点,学习过程的总结,有问题欢迎反馈,谢谢阅读

Read more

libmd 实现详解:仓颉语言中的哈希算法库开发实践

libmd 实现详解:仓颉语言中的哈希算法库开发实践

libmd 实现详解:仓颉语言中的哈希算法库开发实践 前言 密码学哈希函数是现代信息安全的基石,广泛应用于数据完整性验证、数字签名、用户认证和数据安全存储等领域。在仓颉语言生态中,libmd库提供了完整的密码哈希算法实现,支持多种主流哈希算法,包括经典的MD2、MD4、MD5,以及SHA系列(SHA-1、SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/256)和RIPEMD-160等算法。同时,该库还提供了HMAC功能,支持消息认证码的生成,为数据提供了额外的安全保障。 本文将从库的设计思路、核心实现、技术挑战、性能优化等多个维度,深入解析libmd库的开发过程,为仓颉语言开发者提供库开发的实践参考。 一、库概述 1.1 项目背景 在软件开发的众多领域,数据完整性验证和安全性保障是至关重要的需求。哈希算法因其单向性、抗碰撞性和雪崩效应等特性,成为解决这些问题的理想工具。从文件校验到用户认证,从区块链技术到数字签名,哈希算法的应用无处不在。 libmd库旨在为仓颉语言提供一套完整、高效、易用的哈希算法解决方案,支持多种主流哈希算法,

By Ne0inhk
算法基础篇:(二十一)数据结构之单调栈:从原理到实战,玩转高效解题

算法基础篇:(二十一)数据结构之单调栈:从原理到实战,玩转高效解题

目录 前言 一、什么是单调栈?先打破 “栈” 的常规认知 1.1 单调栈的核心特性 1.2 如何实现一个单调栈? 实现单调递增栈 实现单调递减栈 1.3 核心操作解析:为什么要 “弹出元素”? 二、单调栈能解决什么问题?四大核心场景全覆盖 2.1 场景 1:找左侧最近的 “更大元素” 问题描述 解题思路 代码实现 测试用例验证 2.2 场景 2:找左侧最近的 “更小元素” 问题描述 解题思路 代码实现 测试用例验证 2.3 场景 3:找右侧最近的 “更大元素” 问题描述

By Ne0inhk
直流无刷电机FOC控制算法

直流无刷电机FOC控制算法

文章目录 * 1、FOC概述 * 1.1 FOC控制算法介绍 * 2、无刷电机 * 2.1 无刷电机介绍 * 2.2 无刷电机和永磁同步电机的区别 * 2.3 无刷电机的控制原理 * 2.3.1 无刷电机工作原理 * 2.3.2 直流无刷电机驱动原理 * 2.3.2.1 有感直流无刷电机六步换相驱动原理 * 2.3.2.2 直流无刷电机FOC控制原理 * 3、无刷电机FOC控制算法 * 3.1 FOC控制算法整体流程 * 3.2 FOC算法Clarke变换 * 3.2.1 Clarke变换公式推导 * 3.2.2

By Ne0inhk
Flutter 三方库 async_extension 的鸿蒙化适配指南 - 实现具备高级异步编排算法与流操作扩展的并发工具集、支持端侧复杂业务流的函数式处理实战

Flutter 三方库 async_extension 的鸿蒙化适配指南 - 实现具备高级异步编排算法与流操作扩展的并发工具集、支持端侧复杂业务流的函数式处理实战

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 async_extension 的鸿蒙化适配指南 - 实现具备高级异步编排算法与流操作扩展的并发工具集、支持端侧复杂业务流的函数式处理实战 前言 在进行 Flutter for OpenHarmony 的大规模异步业务系统(如实时行情刷新、多源数据聚合)开发时,如何更优雅地处理 Future 的超时竞争、Stream 的防抖(Debounce)或复杂的并发队列控制?虽然 Dart async 包提供了基础功能,但 async_extension 进一步扩展了异步编程的边界,提供了更符合函数式范式的工具。本文将探讨如何在鸿蒙端构建极致、高效的异步处理链路。 一、原直观解析 / 概念介绍 1.1 基础原理 该库通过对 Dart 核心异步类的非侵入式扩展(Extensions)

By Ne0inhk