UE5多人MOBA+GAS 18、用对象池来设置小兵的队伍的生成,为小兵设置一个目标从己方出生点攻打对方出生点,优化小兵的血条UI

发布于:2025-07-16 ⋅ 阅读:(18) ⋅ 点赞:(0)


根据小兵队伍更换小兵的皮肤

懒得开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:
	// 兵营所属队伍ID
	UPROPERTY(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);

		// 设置小兵的队伍ID
		NewMinion->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);
		
		// 设置小兵的队伍ID
		NewMinion->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 properties
	AMinionBarrack();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	
private:
	// 兵营所属队伍ID
	UPROPERTY(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);
		
		// 设置小兵的队伍ID
		NewMinion->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,关闭蓝条的字体和进度条显示
在这里插入图片描述
关于大小的调整
在这里插入图片描述
在这里插入图片描述

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


网站公告

今日签到

点亮在社区的每一天
去签到