根据小兵队伍更换小兵的皮肤
懒得开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,关闭蓝条的字体和进度条显示
关于大小的调整