UE5多人MOBA+GAS 18、用对象池来设置小兵的队伍的生成,为小兵设置一个目标从己方出生点攻打对方出生点,优化小兵的血条UI
文章目录
- 根据小兵队伍更换小兵的皮肤
- 管理小兵的生成
-
- 使用对象池来管理小兵的生成
- 为小兵设置一个目标
- 小兵生成完整代码
- 调整一下小兵的UI
根据小兵队伍更换小兵的皮肤
懒得开UE了,增加一个Minion
类继承基类角色CCharacter
// 幻雨喜欢小猫咪#pragma once#include \"CoreMinimal.h\"#include \"Character/CCharacter.h\"#include \"Minion.generated.h\"/** * 小兵AI角色类,继承自ACCharacter * 负责小兵的队伍分配、激活状态、目标设置、皮肤切换等功能 */UCLASS()class CRUNCH_API AMinion : public ACCharacter{GENERATED_BODY()public:virtual void SetGenericTeamId(const FGenericTeamId& NewTeamId) override;private:// 根据队伍ID切换小兵皮肤void PickSkinBasedOnTeamID();// 队伍ID同步时回调(用于网络同步后自动切换皮肤等)virtual void OnRep_TeamID() override;// 队伍ID到对应皮肤的映射表UPROPERTY(EditDefaultsOnly, Category = \"Visual\")TMap<FGenericTeamId, TObjectPtr<USkeletalMesh>> SkinMap;};
// 幻雨喜欢小猫咪#include \"Minion.h\"void AMinion::SetGenericTeamId(const FGenericTeamId& NewTeamId){Super::SetGenericTeamId(NewTeamId);PickSkinBasedOnTeamID();}void AMinion::PickSkinBasedOnTeamID(){TObjectPtr<USkeletalMesh>* Skin = SkinMap.Find(GetGenericTeamId());if (Skin){GetMesh()->SetSkeletalMesh(*Skin);}}void AMinion::OnRep_TeamID(){PickSkinBasedOnTeamID();}
打开小兵角色蓝图,修改父类
设置一下皮肤
换一个原始皮肤
管理小兵的生成
继承Actor
,命名为MinionBarrack
,用来管理小兵的生成
// 幻雨喜欢小猫咪#pragma once#include \"CoreMinimal.h\"#include \"GenericTeamAgentInterface.h\"#include \"Minion.h\"#include \"GameFramework/Actor.h\"#include \"MinionBarrack.generated.h\"/** * 小兵兵营类,负责批量生成和管理小兵 * 支持队伍分配、目标设置、定时批量生成等功能 */UCLASS()class AMinionBarrack : public AActor{GENERATED_BODY()public:AMinionBarrack();protected:virtual void BeginPlay() override;public:virtual void Tick(float DeltaTime) override;private:// 兵营所属队伍IDUPROPERTY(EditAnywhere, Category = \"Spawn\", meta = (DisplayName = \"队伍ID\"))FGenericTeamId BarrackTeamId;// 每组小兵生成的数量UPROPERTY(EditAnywhere, Category = \"Spawn\", meta = (DisplayName = \"每组小兵生成的数量\"))int32 MinionPerGroup = 3;// 兵营的生成间隔UPROPERTY(EditAnywhere, Category = \"Spawn\", meta = (DisplayName = \"小兵生成间隔\"))float GroupSpawnInterval = 5.f;// 小兵对象池UPROPERTY()TArray<TObjectPtr<AMinion>> MinionPool;// 小兵的目标点(如推进目标)UPROPERTY(EditAnywhere, Category = \"Spawn\", meta = (DisplayName = \"小兵的目标点\"))TObjectPtr<AActor> Goal;// 小兵的类(用于生成小兵实例)UPROPERTY(EditAnywhere, Category = \"Spawn\", meta = (DisplayName = \"小兵的类\"))TSubclassOf<AMinion> MinionClass;// 生成小兵的出生点列表UPROPERTY(EditAnywhere, Category = \"Spawn\", meta = (DisplayName = \"生成小兵的出生点列表\"))TArray<APlayerStart*> SpawnSpots;// 下一个出生点索引int32 NextSpawnSpotIndex = -1;// 获取下一个出生点(轮流分配)const APlayerStart* GetNextSpawnSpot();// 生成指定数量新小兵void SpawnNewMinions(int Amt);};
void AMinionBarrack::BeginPlay(){Super::BeginPlay();// 测试用SpawnNewMinions(5);}const APlayerStart* AMinionBarrack::GetNextSpawnSpot(){if (SpawnSpots.Num() == 0) return nullptr;++NextSpawnSpotIndex;if (NextSpawnSpotIndex >= SpawnSpots.Num()){NextSpawnSpotIndex = 0;}// 返回出生点return SpawnSpots[NextSpawnSpotIndex];}void AMinionBarrack::SpawnNewMinions(int Amt){if (Amt <= 0) return;for (int32 i = 0; i < Amt; ++i){// 获取出生点变换FTransform SpawnTransform = GetActorTransform();// 获取下一个出生点if (const APlayerStart* NextSpawnSpot = GetNextSpawnSpot()){SpawnTransform = NextSpawnSpot->GetActorTransform();}// 生成小兵AMinion* NewMinion = GetWorld()->SpawnActorDeferred<AMinion>(MinionClass, SpawnTransform, this, nullptr, ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn);// 设置小兵的队伍IDNewMinion->SetGenericTeamId(BarrackTeamId);// 完成小兵的生成NewMinion->FinishSpawning(SpawnTransform);// NewMinion->SetGoal(Goal);// 添加小兵到小兵池中MinionPool.Add(NewMinion);}}
放到场景中点击吸管再点击这个场景的出生点,来出生
然后你会发现怪叠了两层,应该是服务器和客户端都生成了,在生成处加个权威
使用对象池来管理小兵的生成
在角色基类CCharacter
中添加判断死亡函数和移除死亡标签函数
#pragma region 死亡和复活 (Death and Respawn)public:bool IsDead() const;void RespawnImmediately();private:#pragma endregion
bool ACCharacter::IsDead() const{return GetAbilitySystemComponent()->HasMatchingGameplayTag(TGameplayTags::Stats_Dead);}void ACCharacter::RespawnImmediately(){// 仅在服务器上执行:移除所有带有“死亡”标签的激活效果,实现立即复活if (HasAuthority()){GetAbilitySystemComponent()->RemoveActiveEffectsWithGrantedTags(FGameplayTagContainer(TGameplayTags::Stats_Dead));}}void ACCharacter::DeathMontageFinished(){if (IsDead()){SetRagdollEnabled(true);}}
到小兵角色类中添加判断小兵是否激活的函数
public:// 判断小兵是否处于激活状态bool IsActive() const;// 激活小兵(如复活、生成时调用)void Activate();
bool AMinion::IsActive() const{return !IsDead();}void AMinion::Activate(){// 移除死亡标签,复活RespawnImmediately();}
UCLASS()class AMinionBarrack : public AActor{GENERATED_BODY()private:// 生成一组小兵(优先用对象池)void SpawnNewGroup();// 从池中获取可用小兵AMinion* GetNextAvailableMinion() const;// 生成组的定时器句柄FTimerHandle SpawnIntervalTimerHandle;};
void AMinionBarrack::BeginPlay(){Super::BeginPlay();// 仅在服务器上定时生成小兵if (HasAuthority()){// 设置定时器,定时批量生成小兵GetWorldTimerManager().SetTimer(SpawnIntervalTimerHandle, this, &AMinionBarrack::SpawnNewGroup, GroupSpawnInterval, true);}}void AMinionBarrack::SpawnNewGroup(){// 需要生成的小兵数量int32 i = MinionPerGroup;while (i > 0){// 获取出生点变换FTransform SpawnTransform = GetActorTransform();// 获取下一个出生点if (const APlayerStart* NextSpawnSpot = GetNextSpawnSpot()){SpawnTransform = NextSpawnSpot->GetActorTransform();}// 优先复用对象池中的非激活小兵AMinion* NextAvailableMinion = GetNextAvailableMinion();// 对象池内没有可以用的小兵了就退出循环,生成一个新的小兵if (!NextAvailableMinion) break;NextAvailableMinion->SetActorTransform(SpawnTransform);NextAvailableMinion->Activate();--i;}// 如果对象池不够,则新建剩余数量的小兵SpawnNewMinions(i);}AMinion* AMinionBarrack::GetNextAvailableMinion() const{for (AMinion* Minion : MinionPool){if (!Minion->IsActive()){return Minion;}}return nullptr;}
想必以及发现了AI小兵的颜色跟自己设置的队伍颜色有点不对劲了吧,在AI控制器那里,已经强行设置了一个队伍ID.
void ACAIController::OnPossess(APawn* InPawn){Super::OnPossess(InPawn);IGenericTeamAgentInterface* PawnTeamInterface = Cast<IGenericTeamAgentInterface>(InPawn);if (PawnTeamInterface){SetGenericTeamId(PawnTeamInterface->GetGenericTeamId());ClearAndDisableAllSenses();EnableAllSenses();}UAbilitySystemComponent* PawnASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(InPawn);if (PawnASC){PawnASC->RegisterGameplayTagEvent(TGameplayTags::Stats_Dead, EGameplayTagEventType::NewOrRemoved).AddUObject(this, &ACAIController::PawnDeadTagUpdated);}}
创建一个持续时间为无限的死亡GE
把他给小兵用上
打死小兵之后的尸体消失,就是在对象池中复活了。
为小兵设置一个目标
public:// 设置小兵的目标(如推进目标、攻击目标等)void SetGoal(AActor* Goal);private:// 黑板中用于存储目标的Key名UPROPERTY(EditDefaultsOnly, Category = \"AI\")FName GoalBlackboardKeyName = \"Goal\";
void AMinion::SetGoal(AActor* Goal){if (AAIController* AIController = GetController<AAIController>()){if (UBlackboardComponent* BlackboardComponent = AIController->GetBlackboardComponent()){// 修改黑板组件中对应键目标的值BlackboardComponent->SetValueAsObject(GoalBlackboardKeyName, Goal);}}}
创建小兵的时候为其设置该键的值
void AMinionBarrack::SpawnNewMinions(int Amt){if (Amt <= 0) return;for (int32 i = 0; i < Amt; ++i){// 获取出生点变换FTransform SpawnTransform = GetActorTransform();// 获取下一个出生点if (const APlayerStart* NextSpawnSpot = GetNextSpawnSpot()){SpawnTransform = NextSpawnSpot->GetActorTransform();}// 生成小兵AMinion* NewMinion = GetWorld()->SpawnActorDeferred<AMinion>(MinionClass, SpawnTransform, this, nullptr, ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn);// 设置小兵的队伍IDNewMinion->SetGenericTeamId(BarrackTeamId);// 完成小兵的生成NewMinion->FinishSpawning(SpawnTransform);// 设置小兵的目标NewMinion->SetGoal(Goal);// 添加小兵到小兵池中MinionPool.Add(NewMinion);}}
到黑板中创建该健
可以让AI走向设定的目标
添加黑板装饰器
在值改变的时候就会重启行为树,不去追击设定的目标,转来打发现的敌人
目标跟小兵出生点一点,通过吸管吸取场景的物品
小兵发现你后就会来打你了
小兵生成完整代码
// 幻雨喜欢小猫咪#pragma once#include \"CoreMinimal.h\"#include \"GenericTeamAgentInterface.h\"#include \"Minion.h\"#include \"GameFramework/Actor.h\"#include \"MinionBarrack.generated.h\"/** * 小兵兵营类,负责批量生成和管理小兵 * 支持队伍分配、目标设置、定时批量生成等功能 */UCLASS()class AMinionBarrack : public AActor{GENERATED_BODY()public:// Sets default values for this actor\'s propertiesAMinionBarrack();protected:// Called when the game starts or when spawnedvirtual void BeginPlay() override;public:// Called every framevirtual void Tick(float DeltaTime) override;private:// 兵营所属队伍IDUPROPERTY(EditAnywhere, Category = \"Spawn\", meta = (DisplayName = \"队伍ID\"))FGenericTeamId BarrackTeamId;// 每组小兵生成的数量UPROPERTY(EditAnywhere, Category = \"Spawn\", meta = (DisplayName = \"每组小兵生成的数量\"))int32 MinionPerGroup = 5;// 兵营的生成间隔UPROPERTY(EditAnywhere, Category = \"Spawn\", meta = (DisplayName = \"小兵生成间隔\"))float GroupSpawnInterval = 15.f;// 小兵对象池UPROPERTY()TArray<TObjectPtr<AMinion>> MinionPool;// 小兵的目标点(如推进目标)UPROPERTY(EditAnywhere, Category = \"Spawn\", meta = (DisplayName = \"小兵的目标点\"))TObjectPtr<AActor> Goal;// 小兵的类(用于生成小兵实例)UPROPERTY(EditAnywhere, Category = \"Spawn\", meta = (DisplayName = \"小兵的类\"))TSubclassOf<AMinion> MinionClass;// 生成小兵的出生点列表UPROPERTY(EditAnywhere, Category = \"Spawn\", meta = (DisplayName = \"生成小兵的出生点列表\"))TArray<APlayerStart*> SpawnSpots;// 下一个出生点索引int32 NextSpawnSpotIndex = -1;// 获取下一个出生点(轮流分配)const APlayerStart* GetNextSpawnSpot();// 生成一组小兵(优先用对象池)void SpawnNewGroup();// 生成指定数量新小兵void SpawnNewMinions(int Amt);// 从池中获取可用小兵AMinion* GetNextAvailableMinion() const;// 生成组的定时器句柄FTimerHandle SpawnIntervalTimerHandle;};
// 幻雨喜欢小猫咪#include \"AI/MinionBarrack.h\"#include \"GameFramework/PlayerStart.h\"// Sets default valuesAMinionBarrack::AMinionBarrack(){ // Set this actor 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 spawnedvoid AMinionBarrack::BeginPlay(){Super::BeginPlay();// 仅在服务器上定时生成小兵if (HasAuthority()){// 设置定时器,定时批量生成小兵GetWorldTimerManager().SetTimer(SpawnIntervalTimerHandle, this, &AMinionBarrack::SpawnNewGroup, GroupSpawnInterval, true);}}// Called every framevoid AMinionBarrack::Tick(float DeltaTime){Super::Tick(DeltaTime);}const APlayerStart* AMinionBarrack::GetNextSpawnSpot(){if (SpawnSpots.Num() == 0) return nullptr;++NextSpawnSpotIndex;if (NextSpawnSpotIndex >= SpawnSpots.Num()){NextSpawnSpotIndex = 0;}// 返回出生点return SpawnSpots[NextSpawnSpotIndex];}void AMinionBarrack::SpawnNewGroup(){// 需要生成的小兵数量int32 i = MinionPerGroup;while (i > 0){// 获取出生点变换FTransform SpawnTransform = GetActorTransform();// 获取下一个出生点if (const APlayerStart* NextSpawnSpot = GetNextSpawnSpot()){SpawnTransform = NextSpawnSpot->GetActorTransform();}// 优先复用对象池中的非激活小兵AMinion* NextAvailableMinion = GetNextAvailableMinion();// 对象池内没有可以用的小兵了就退出循环,生成一个新的小兵if (!NextAvailableMinion) break;NextAvailableMinion->SetActorTransform(SpawnTransform);NextAvailableMinion->Activate();--i;}// 如果对象池不够,则新建剩余数量的小兵SpawnNewMinions(i);}void AMinionBarrack::SpawnNewMinions(int Amt){if (Amt <= 0) return;for (int32 i = 0; i < Amt; ++i){// 获取出生点变换FTransform SpawnTransform = GetActorTransform();// 获取下一个出生点if (const APlayerStart* NextSpawnSpot = GetNextSpawnSpot()){SpawnTransform = NextSpawnSpot->GetActorTransform();}// 生成小兵AMinion* NewMinion = GetWorld()->SpawnActorDeferred<AMinion>(MinionClass, SpawnTransform, this, nullptr, ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn);// 设置小兵的队伍IDNewMinion->SetGenericTeamId(BarrackTeamId);// 完成小兵的生成NewMinion->FinishSpawning(SpawnTransform);// 设置小兵的目标NewMinion->SetGoal(Goal);// 添加小兵到小兵池中MinionPool.Add(NewMinion);}}AMinion* AMinionBarrack::GetNextAvailableMinion() const{for (AMinion* Minion : MinionPool){if (!Minion->IsActive()){return Minion;}}return nullptr;}
调整一下小兵的UI
// 数值文本字体UPROPERTY(EditAnywhere, Category = \"Visual\")FSlateFontInfo ValueTextFont;// 是否显示数值文本UPROPERTY(EditAnywhere, Category = \"Visual\")bool bValueTextVisible = true; // 是否显示进度条UPROPERTY(EditAnywhere, Category = \"Visual\")bool bProgressBarVisible = true;
void UValueGauge::NativePreConstruct(){Super::NativePreConstruct();// 设置进度条颜色ProgressBar->SetFillColorAndOpacity(BarColor);ValueText->SetFont(ValueTextFont);ValueText->SetVisibility(bValueTextVisible ? ESlateVisibility::Visible : ESlateVisibility::Hidden);ProgressBar->SetVisibility(bProgressBarVisible ? ESlateVisibility::Visible : ESlateVisibility::Hidden);}
设置字体
复制原本的头部ui,关闭蓝条的字体和进度条显示
关于大小的调整