文章目錄
- 根據小兵隊伍更換小兵的皮膚
- 管理小兵的生成
- 使用對象池來管理小兵的生成
- 為小兵設置一個目標
- 小兵生成完整代碼
- 調整一下小兵的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,關閉藍條的字體和進度條顯示
關于大小的調整