当前位置: 首页 > news >正文

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 values
AMinionBarrack::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 spawned
void AMinionBarrack::BeginPlay()
{Super::BeginPlay();// 仅在服务器上定时生成小兵if (HasAuthority()){// 设置定时器,定时批量生成小兵GetWorldTimerManager().SetTimer(SpawnIntervalTimerHandle, this, &AMinionBarrack::SpawnNewGroup, GroupSpawnInterval, true);}
}// Called every frame
void 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,关闭蓝条的字体和进度条显示
在这里插入图片描述
关于大小的调整
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

http://www.lryc.cn/news/585415.html

相关文章:

  • Go语言WebSocket编程:从零打造实时通信利器
  • Script Error产生的原因及解法
  • 鸿蒙app 开发中的 map 映射方式和用法
  • STM32F103之存储/启动流程
  • R² 决定系数详解:原理 + Python手写实现 + 数学公式 + 与 MSE/MAE 比较
  • MCU芯片内部的ECC安全机制
  • 上位机知识篇---Docker
  • 新型变种木马正在伪装成Termius入侵系统
  • OpenCV多种图像哈希算法的实现比较
  • 什么是IP关联?跨境卖家如何有效避免IP关联?
  • DOM编程实例(不重要,可忽略)
  • 从Excel到PDF一步到位的台签打印解决方案
  • 扫描文件 PDF / 图片 纠斜 | 图片去黑边 / 裁剪 / 压缩
  • cnpm exec v.s. npx
  • Java基础-String常用的方法
  • 用AI做带货视频评论分析【Datawhale AI 夏令营】
  • 进程管理中的队列调度与内存交换机制
  • MinIO配置项速查表【五】
  • 云原生周刊:镜像兼容性
  • 「Linux命令基础」Shell命令基础
  • 从零到一:深度解析汽车标定技术体系与实战策略
  • React 的常用钩子函数在Vue中是如何设计体现出来的。
  • WinForm三大扩展组件:ErrorProvider、HelpProvider、ToolTipProvider详解
  • Apache Cloudberry 向量化实践(二):如何识别和定位向量化系统的性能瓶颈?
  • 资源分享-FPS, 矩阵, 骨骼, 绘制, 自瞄, U3D, UE4逆向辅助实战视频教程
  • Oracle 数据库 Dblink
  • PySpark中python环境打包和JAR包依赖
  • tensor
  • Word表格默认格式修改成三线表,一劳永逸,提高生产力!
  • 上位机知识篇---高效下载安装方法