Skip to content

Instantly share code, notes, and snippets.

@SkylakeOfficial
Created March 17, 2024 16:13
Show Gist options
  • Select an option

  • Save SkylakeOfficial/743be7152c21207498539a347fa04a81 to your computer and use it in GitHub Desktop.

Select an option

Save SkylakeOfficial/743be7152c21207498539a347fa04a81 to your computer and use it in GitHub Desktop.
MyEnemySpawner_v2
// Skylake 2023 all rights reserved
#include "AFPEnemySpawnerActor.h"
#include "GameFramework/Pawn.h"
#include "Engine/World.h"
#include "Engine.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/KismetSystemLibrary.h"
#include "UObject/ConstructorHelpers.h"
#include "TimerManager.h"
#include "ArknightsFP/AFPGameInstance.h"
#include "ArknightsFP/Gameplay/GameDirector/AFPGameDirectorSubsystem.h"
DEFINE_LOG_CATEGORY(AFPEnemySpawner);
AAFPEnemySpawnerActor::AAFPEnemySpawnerActor()
{
PrimaryActorTick.bCanEverTick = false;
SceneRoot = CreateDefaultSubobject<UBillboardComponent>(TEXT("Root Comp"));
SetRootComponent(SceneRoot);
static ConstructorHelpers::FObjectFinder<UTexture2D> BillboardTex(TEXT("/Game/GameLogic/Spawner/Tx_Billboard"));
if (BillboardTex.Succeeded())
{
SceneRoot->Sprite = BillboardTex.Object;
}
}
void AAFPEnemySpawnerActor::BeginPlay()
{
Super::BeginPlay();
UAFPGameInstance* GameInstance = Cast<UAFPGameInstance>(UGameplayStatics::GetGameInstance(this));
EAFPMapType RoomType = GameInstance->GetSubsystem<UAFPGameDirectorSubsystem>()->GetRoomType();
if (RoomType == EAFPMapType::EmergencyCombat)
{
SpawnProfileList = SpawnProfileListEmergency;
}
if (!SpawnProfileList.IsEmpty())
{
SpawnProfile = SpawnProfileList[UKismetMathLibrary::RandomInteger(SpawnProfileList.Num())];
}
else
{
UE_LOG(AFPEnemySpawner, Warning, TEXT("Spawn profile list null, spawner destroyed! "));
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::White, TEXT("Spawn profile list null, spawner destroyed!"));
Destroy();
}
if (bAutoStart) StartSpawning();
}
void AAFPEnemySpawnerActor::StartSpawning()
{
if (SpawnStarted || !SpawnProfile) return;
SpawnStarted = true;
if (SpawnProfile->Streaks.IsEmpty())
{
UE_LOG(AFPEnemySpawner, Warning, TEXT("未填入波次信息,放弃生成!"));
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::White, TEXT("未填入波次信息,放弃生成!"));
return;
}
UAFPGameDirectorSubsystem* DirectorSys = UGameplayStatics::GetGameInstance(this)->GetSubsystem<UAFPGameDirectorSubsystem>();
DirectorSys->OnBattleStartFunc();
OnEnemyEliminated.AddDynamic(this, &AAFPEnemySpawnerActor::OnBattleEnd);
MakeStreakNodes(SpawnProfile->Streaks);
for (UStreakNode* Node : StreakNodes)
{
if (Node->DirectStart)
{
SpawnNode(Node);
}
}
}
bool AAFPEnemySpawnerActor::SpawnSpecificStreak(FName StreakName)
{
if (StreakNodes.IsEmpty())
{
return false;
}
for (int32 i = 0; i != StreakNodes.Num(); i++)
{
if (StreakNodes[i]->ID == StreakName&&(!StreakNodes[i]->IsSpawning))
{
SpawnNode(StreakNodes[i]);
return true;
}
}
return false;
}
TArray<FName> AAFPEnemySpawnerActor::GetListOfExternCallableStreaks()
{
TArray<FName> Result;
for(auto Node : StreakNodes)
{
if (!Node->IsSpawning&&Node->ExternalCall)
{
Result.Add(Node->ID);
}
}
return Result;
}
//返回存活的敌人
TArray<AAFPEnemyBase*> AAFPEnemySpawnerActor::GetSurvivingEnemies()
{
TArray<AAFPEnemyBase*> Result;
if (StreakNodes.IsEmpty())
{
return Result;
}
for (int32 i = 0; i != StreakNodes.Num(); i++)
{
Result.Append(StreakNodes[i]->SpawnedEnemies);
}
return Result;
}
//构造函数
void AAFPEnemySpawnerActor::OnConstruction(const FTransform& Transform)
{
}
//Generate streak nodes
void AAFPEnemySpawnerActor::MakeStreakNodes(TArray<FEnemyStreak> InStreak)
{
for (FEnemyStreak Streak : InStreak)
{
if (Streak.EnemyCount < 1)
{
continue;
}
UStreakNode* Node = NewObject<UStreakNode>();
Node->ID = Streak.StreakName;
Node->Delay = Streak.StreakDelay;
Node->DistributeTime = Streak.DistributeTime;
Node->EnemyType = Streak.EnemyType;
//TransformStuff
FVector Scale = Streak.EnemyTransform.GetScale3D() * FVector(1.0, 1.0, 0.0);
FVector Loc = Streak.EnemyTransform.GetLocation();
FRotator Rot = FRotator(Streak.EnemyTransform.GetRotation());
for (int i = 0; i != Streak.EnemyCount; ++i)
{
FVector2D OffsetXY = SobolVec2D(i); //生成二维sobol序列
FVector LocOffset = Streak.EnemyCount == 1 ? FVector(0, 0, 0) : UKismetMathLibrary::MakeVector(OffsetXY.X - 0.5f, OffsetXY.Y - 0.5f, 0) * Scale * 80;
FVector Location = Loc + LocOffset;
FTransform SelfTransform = GetTransform();
SelfTransform.SetScale3D(FVector(1, 1, 1));
Node->Transforms.Add(UKismetMathLibrary::MakeTransform(Location, Rot).operator+(SelfTransform));
}
//Link corresponding nodes
for (FEnemyStreak OtherStreak : InStreak)
{
if (OtherStreak.RefStreak == Node->ID && OtherStreak.StreakName != Node->ID)
{
if (OtherStreak.SpawnMethod == ESpawnMethod::StartAfter)
{
Node->CompleteExecute.Add(OtherStreak.StreakName);
}
else if (OtherStreak.SpawnMethod == ESpawnMethod::StartAfterEliminate)
{
Node->EliminateExecute.Add(OtherStreak.StreakName);
}
}
}
//Start condition
Node->DirectStart = Streak.SpawnMethod == ESpawnMethod::DirectStart;
Node->ExternalCall = Streak.SpawnMethod == ESpawnMethod::ExternalCall;
StreakNodes.Add(Node);
}
}
void AAFPEnemySpawnerActor::SpawnNode(UStreakNode* Node)
{
if (!Node)
{
return;
}
if (Node->IsSpawning)
{
return;
}
float DistributeTime = Node->DistributeTime;
float StreakDelay = Node->Delay;
if (DistributeTime <= 0)
{
DistributeTime = 0.5F;
}
if (StreakDelay < 0)
{
StreakDelay = 1;
}
FTimerHandle SpawnNodeHandle;
FTimerDelegate SpawnNodeDelegate = FTimerDelegate::CreateUObject(this, &AAFPEnemySpawnerActor::SpawnNodeOnce, Node);
GetWorldTimerManager().SetTimer(SpawnNodeHandle, SpawnNodeDelegate, DistributeTime / (Node->Transforms.Num() - 1), true, StreakDelay);
Node->SpawnTimer = SpawnNodeHandle;
Node->IsSpawning = true;
}
void AAFPEnemySpawnerActor::SpawnNodeOnce(UStreakNode* Node)
{
if (!Node)
{
return;
}
if (Node->Transforms.IsEmpty())
{
return;
}
AAFPEnemyBase* Spawned = SpawnSingle(Node->Transforms[0], Node->EnemyType);
Node->SpawnedEnemies.Add(Spawned);
EnemyBelongsTo.Add(Spawned, Node->ID);
Node->Transforms.RemoveAt(0);
//Node完成生成时
if (Node->Transforms.IsEmpty())
{
GetWorldTimerManager().ClearTimer(Node->SpawnTimer);
OnStreakSpawnComplete.Broadcast(Node->ID);
if (!Node->CompleteExecute.IsEmpty())
{
for (FName OtherNodeID : Node->CompleteExecute)
{
SpawnNode(FindNodeByID(OtherNodeID));
}
}
}
}
//生成Pawn
AAFPEnemyBase* AAFPEnemySpawnerActor::SpawnSingle(FTransform Transform, TSubclassOf<AAFPEnemyBase> EnemyType)
{
//位置
FVector SpawnLoc = Transform.GetLocation();
FRotator SpawnRot = FRotator(Transform.GetRotation());
//检测地面
//const TArray<TEnumAsByte<EObjectTypeQuery>> TraceQueryType = {EObjectTypeQuery::ObjectTypeQuery1, EObjectTypeQuery::ObjectTypeQuery2};
//FHitResult Result = FHitResult();
//TArray<AActor*> ActorsToIgnore;
//UKismetSystemLibrary::LineTraceSingleForObjects(GetWorld(), SpawnLoc, SpawnLoc.operator-(FVector(0, 0, 10000.0)), TraceQueryType, false, ActorsToIgnore, EDrawDebugTrace::None, Result, true);
//相关config
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = this;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
//生成
AAFPEnemyBase* SpawnedPawn = GetWorld()->SpawnActor<AAFPEnemyBase>(EnemyType, SpawnLoc, SpawnRot, SpawnParams);
//吸附到地面
//FVector ActorBounds;
//SpawnedPawn->GetActorBounds(true, SpawnLoc, ActorBounds, false);
//FVector DestLocation = Result.Location.operator+(FVector(0, 0, ActorBounds.Z));
//SpawnedPawn->SetActorLocation(DestLocation, true, nullptr, ETeleportType::TeleportPhysics);
/* 吸附地面的逻辑就不做了。因为加入了飞行敌人*/
//绑定到actor摧毁的委托
SpawnedPawn->OnDestroyed.AddDynamic(this, &AAFPEnemySpawnerActor::OnEnemyDestroy);
return SpawnedPawn;
}
//pawn销毁调用
void AAFPEnemySpawnerActor::OnEnemyDestroy(AActor* DestroyedActor)
{
AAFPEnemyBase* Destroyed = Cast<AAFPEnemyBase>(DestroyedActor);
if (EnemyBelongsTo.Contains(Destroyed))
{
FName ID = EnemyBelongsTo[Destroyed];
UStreakNode* NodeBelongsTo = nullptr;
for (int32 i = 0; i != StreakNodes.Num(); i++)
{
if (StreakNodes[i]->ID == ID)
{
NodeBelongsTo = StreakNodes[i];
break;
}
}
if (NodeBelongsTo)
{
NodeBelongsTo->SpawnedEnemies.Remove(Destroyed);
//如果销毁敌人的所属波次被清空了
if (NodeBelongsTo->SpawnedEnemies.IsEmpty() && NodeBelongsTo->Transforms.IsEmpty())
{
OnStreakEliminate.Broadcast(ID);
if (!NodeBelongsTo->EliminateExecute.IsEmpty())
{
for (FName OtherNode : NodeBelongsTo->EliminateExecute)
{
SpawnNode(FindNodeByID(OtherNode));
}
}
StreakNodes.Remove(NodeBelongsTo);
//检查是不是所有波次都被清空了
if (StreakNodes.IsEmpty())
{
OnEnemyEliminated.Broadcast();
}
//或者剩余的波次都是外部调用生成,且还没开始 —— 这种情况下也视为清空了
else
{
bool HasNonExternCallNode = false;
for (int32 i = 0; i != StreakNodes.Num(); i++)
{
if (!StreakNodes[i]->ExternalCall)//不是外部调用?
{
HasNonExternCallNode = true;
break;
}
else if (StreakNodes[i]->IsSpawning)//已经在生成中?
{
HasNonExternCallNode = true;
break;
}
}
if (!HasNonExternCallNode)
{
OnEnemyEliminated.Broadcast();
}
}
}
}
}
}
void AAFPEnemySpawnerActor::OnBattleEnd()
{
UAFPGameDirectorSubsystem* DirectorSys = UGameplayStatics::GetGameInstance(this)->GetSubsystem<UAFPGameDirectorSubsystem>();
DirectorSys->OnBattleEndFunc();
}
//生成第d个维度的第i个sobol数
float AAFPEnemySpawnerActor::Sobol(uint32 d, uint32 i)
{
const uint32 Matrix[8 * 32] = {
2147483648, 1073741824, 536870912, 268435456, 134217728, 67108864, 33554432, 16777216, 8388608, 4194304, 2097152, 1048576, 524288, 262144, 131072, 65536, 32768, 16384, 8192, 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1,
2147483648, 3221225472, 2684354560, 4026531840, 2281701376, 3422552064, 2852126720, 4278190080, 2155872256, 3233808384, 2694840320, 4042260480, 2290614272, 3435921408, 2863267840, 4294901760, 2147516416, 3221274624, 2684395520, 4026593280, 2281736192, 3422604288, 2852170240, 4278255360, 2155905152, 3233857728, 2694881440, 4042322160, 2290649224, 3435973836, 2863311530, 4294967295,
2147483648, 3221225472, 1610612736, 2415919104, 3892314112, 1543503872, 2382364672, 3305111552, 1753219072, 2629828608, 3999268864, 1435500544, 2154299392, 3231449088, 1626210304, 2421489664, 3900735488, 1556135936, 2388680704, 3314585600, 1751705600, 2627492864, 4008611328, 1431684352, 2147543168, 3221249216, 1610649184, 2415969680, 3892340840, 1543543964, 2382425838, 3305133397,
2147483648, 3221225472, 536870912, 1342177280, 4160749568, 1946157056, 2717908992, 2466250752, 3632267264, 624951296, 1507852288, 3872391168, 2013790208, 3020685312, 2181169152, 3271884800, 546275328, 1363623936, 4226424832, 1977167872, 2693105664, 2437829632, 3689389568, 635137280, 1484783744, 3846176960, 2044723232, 3067084880, 2148008184, 3222012020, 537002146, 1342505107,
2147483648, 1073741824, 536870912, 2952790016, 4160749568, 3690987520, 2046820352, 2634022912, 1518338048, 801112064, 2707423232, 4038066176, 3666345984, 1875116032, 2170683392, 1085997056, 579305472, 3016343552, 4217741312, 3719483392, 2013407232, 2617981952, 1510979072, 755882752, 2726789248, 4090085440, 3680870432, 1840435376, 2147625208, 1074478300, 537900666, 2953698205,
2147483648, 1073741824, 1610612736, 805306368, 2818572288, 335544320, 2113929216, 3472883712, 2290089984, 3829399552, 3059744768, 1127219200, 3089629184, 4199809024, 3567124480, 1891565568, 394297344, 3988799488, 920674304, 4193267712, 2950604800, 3977188352, 3250028032, 129093376, 2231568512, 2963678272, 4281226848, 432124720, 803643432, 1633613396, 2672665246, 3170194367,
2147483648, 3221225472, 2684354560, 3489660928, 1476395008, 2483027968, 1040187392, 3808428032, 3196059648, 599785472, 505413632, 4077912064, 1182269440, 1736704000, 2017853440, 2221342720, 3329785856, 2810494976, 3628507136, 1416089600, 2658719744, 864310272, 3863387648, 3076993792, 553150080, 272922560, 4167467040, 1148698640, 1719673080, 2009075780, 2149644390, 3222291575,
2147483648, 1073741824, 2684354560, 1342177280, 2281701376, 1946157056, 436207616, 2566914048, 2625634304, 3208642560, 2720006144, 2098200576, 111673344, 2354315264, 3464626176, 4027383808, 2886631424, 3770826752, 1691164672, 3357462528, 1993345024, 3752330240, 873073152, 2870150400, 1700563072, 87021376, 1097028000, 1222351248, 1560027592, 2977959924, 23268898, 437609937
};
uint32 result = 0;
uint32 offset = d * 32;
for (uint32 j = 0; i; i >>= 1, j++)
if (i & 1)
result ^= Matrix[j + offset];
return float(result) * (1.0f / float(0xFFFFFFFFU));
}
UStreakNode* AAFPEnemySpawnerActor::FindNodeByID(FName ID)
{
for (auto Node : StreakNodes)
{
if (Node->ID == ID)
{
return Node;
}
}
return nullptr;
}
//生成二维sobol坐标
FVector2D AAFPEnemySpawnerActor::SobolVec2D(uint32 i)
{
float u = Sobol(1, i ^ (i >> 1));
float v = Sobol(2, i ^ (i >> 1));
return FVector2D(u, v);
}
// Skylake 2023 all rights reserved
#pragma once
#include "CoreMinimal.h"
#include "AFPEnemySpawnProfile.h"
#include "Components/BillboardComponent.h"
#include "Engine/EngineTypes.h"
#include "TimerManager.h"
#include "ArknightsFP/Stats/AFPGameStatsSubsystem.h"
#include "AFPEnemySpawnerActor.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FAFPEnemyEliminatedSignature);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAFPSpawnCompleteSignature, FName, StreakName);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAFPEliminateStreakSignature, FName, StreakName);
DECLARE_LOG_CATEGORY_EXTERN(AFPEnemySpawner,Log,All);
UCLASS()
class UStreakNode : public UObject
{
public:
GENERATED_BODY()
FName ID;
float Delay = 1.0;
float DistributeTime = 0.5;
TSubclassOf<AAFPEnemyBase> EnemyType;
TArray<FTransform> Transforms;
TArray<FName> CompleteExecute;
TArray<FName> EliminateExecute;
bool DirectStart = false;
bool ExternalCall = false;
bool IsSpawning = false;
UPROPERTY()
TArray<AAFPEnemyBase*> SpawnedEnemies;
FTimerHandle SpawnTimer;
};
UCLASS()
class ARKNIGHTSFP_API AAFPEnemySpawnerActor : public AActor
{
GENERATED_BODY()
public:
AAFPEnemySpawnerActor();
UPROPERTY(EditAnywhere, Category = "AFPSpawn")
bool bAutoStart = false;
UPROPERTY(EditAnywhere, Category = "AFPSpawn" )
TArray< UAFPEnemySpawnProfile*> SpawnProfileList;
UPROPERTY(EditAnywhere, Category = "AFPSpawn" )
TArray< UAFPEnemySpawnProfile*> SpawnProfileListEmergency;
UFUNCTION(BlueprintCallable, Category = "AFPSpawn")
void StartSpawning();
UFUNCTION(BlueprintCallable, Category = "AFPSpawn")
bool SpawnSpecificStreak(FName StreakName);
/**
* 返回尚没被外部调用的波次
* @return 现在可从外部启动的敌人波次列表
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "AFPSpawn")
TArray<FName> GetListOfExternCallableStreaks();
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "AFPSpawn")
TArray<AAFPEnemyBase*> GetSurvivingEnemies();
UPROPERTY(BlueprintAssignable, Category = "AFPSpawn")
FAFPEnemyEliminatedSignature OnEnemyEliminated;
UPROPERTY(BlueprintAssignable, Category = "AFPSpawn")
FAFPSpawnCompleteSignature OnStreakSpawnComplete;
UPROPERTY(BlueprintAssignable, Category = "AFPSpawn")
FAFPEliminateStreakSignature OnStreakEliminate;
protected:
UPROPERTY(VisibleAnywhere, Category = "AFPSpawn")
UBillboardComponent* SceneRoot;
virtual void BeginPlay() override;
virtual void OnConstruction(const FTransform& Transform) override;
void MakeStreakNodes(TArray<FEnemyStreak> InStreak);
void SpawnNode(UStreakNode* Node);
FVector2D SobolVec2D(uint32 i);
float Sobol(uint32 d, uint32 i);
UStreakNode* FindNodeByID(FName ID);
UFUNCTION()
void SpawnNodeOnce(UStreakNode* Node);
AAFPEnemyBase* SpawnSingle(FTransform Transform, TSubclassOf<AAFPEnemyBase> EnemyType);
UFUNCTION()
void OnEnemyDestroy(AActor* DestroyedActor);
UFUNCTION()
void OnBattleEnd();
UPROPERTY()
UAFPEnemySpawnProfile* SpawnProfile = nullptr;
bool SpawnStarted = false;
int32 CurrentStreak = 0;
UPROPERTY()
FTimerHandle EnemySpawnTimer;
FTimerHandle TimeLimitTimer;
UPROPERTY()
TArray<UStreakNode*> StreakNodes;
UPROPERTY()
TMap<AAFPEnemyBase*, FName> EnemyBelongsTo;
};
// Skylake 2023 all rights reserved
#include "AFPEnemySpawnerEditor.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Kismet/KismetMathLibrary.h"
#include "UObject/ConstructorHelpers.h"
#include "Materials/Material.h"
#include "DrawDebugHelpers.h"
// Sets default values
AAFPEnemySpawnerEditor::AAFPEnemySpawnerEditor()
{
PrimaryActorTick.bCanEverTick = true;
LastStoredProfile = StoredProfile;
SceneRoot = CreateDefaultSubobject<UBillboardComponent>(TEXT("Root Comp")); //SceneRoot
SetRootComponent(SceneRoot);
AActor::SetActorHiddenInGame(true);
static ConstructorHelpers::FObjectFinder<UMaterial> TextMat(TEXT("/Engine/EngineMaterials/DefaultTextMaterialOpaque"));
if (TextMat.Succeeded())
{
TextMaterial = TextMat.Object;
}
static ConstructorHelpers::FObjectFinder<UTexture2D> BillboardTex(TEXT("/Game/GameLogic/Spawner/Tx_Billboard"));
if (BillboardTex.Succeeded())
{
SceneRoot->Sprite = BillboardTex.Object;
}
DebugColors = {FColor::FromHex("8A24FF"), FColor::FromHex("2323FA"), FColor::FromHex("308DFF"), FColor::FromHex("24E9FF"), FColor::FromHex("24FFAC")};
}
void AAFPEnemySpawnerEditor::Apply()
{
if (!StoredProfile)
{
GEditor->AddOnScreenDebugMessage(0, 5.0, FColor::Red,TEXT("没有编辑中的Profile!"));
return;
}
else
{
StoredProfile->Streaks = Streaks;
}
RerunConstructionScripts();
}
void AAFPEnemySpawnerEditor::OnConstruction(const FTransform& Transform)
{
int32 LastStreakNum = 0;
if (LastStoredProfile)
{
LastStreakNum = LastStoredProfile->Streaks.Num();
}
if (!StoredProfile)
{
Streaks = TArray<FEnemyStreak>();
}
if (StoredProfile)
{
if (LastStoredProfile != StoredProfile)
{
Streaks = StoredProfile->Streaks;
}
}
if (Streaks.Num()>LastStreakNum&&LastStoredProfile)
{
Streaks.Last().StreakName = FName(FString::FromInt(Streaks.Num()-1));
}
LastStoredProfile = StoredProfile;
UKismetSystemLibrary::FlushPersistentDebugLines(GetWorld());
TSubclassOf<UActorComponent> TextCompClass = UTextRenderComponent::StaticClass();
TArray<UActorComponent*> TextsGen;
GetComponents(TextCompClass, TextsGen);
for (int i = 0; i < TextsGen.Num(); i++)
{
if (TextsGen[i])
{
TextsGen[i]->UnregisterComponent();
TextsGen[i]->DestroyComponent();
}
}
if (!Streaks.IsEmpty())
{
for (int i = 0; i != Streaks.Num(); i++)
{
FVector Loc = Streaks[i].EnemyTransform.GetLocation().operator+(GetActorLocation()).operator+(FVector(0, 0, 30));
FVector Scale = Streaks[i].EnemyTransform.GetScale3D().operator*(FVector(40, 40, 0)).operator+(FVector(0, 0, 30));
FQuat Rot = Streaks[i].EnemyTransform.GetRotation();
DrawDebugBox(GetWorld(), Loc, Scale, Rot, DebugColors[i % DebugColors.Num()], true);
FString ClassName = Streaks[i].EnemyType->GetName();
FString StreakName = Streaks[i].StreakName.ToString();
FString TextToDisplay = FString::Printf(TEXT("%s\n%s x %d"), *StreakName, *ClassName, Streaks[i].EnemyCount);
UTextRenderComponent* Text = NewObject<UTextRenderComponent>(this);
AddInstanceComponent(Text);
Text->RegisterComponent();
Text->AttachToComponent(SceneRoot, FAttachmentTransformRules::KeepWorldTransform);
if (TextMaterial)
{
Text->SetMaterial(0, TextMaterial);
}
Text->SetText(FText::FromString(TextToDisplay));
Text->SetTextRenderColor(FColor::Red);
Text->SetWorldTransform(FTransform(Rot, Loc, FVector(1, 1, 1)));
Text->SetHorizontalAlignment(EHorizTextAligment::EHTA_Center);
Text->SetVerticalAlignment(EVerticalTextAligment::EVRTA_TextCenter);
}
}
}
void AAFPEnemySpawnerEditor::BeginPlay()
{
Super::BeginPlay();
}
void AAFPEnemySpawnerEditor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Skylake 2023 all rights reserved
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ArknightsFP/Spawn/AFPEnemySpawnProfile.h"
#include "Components/TextRenderComponent.h"
#include "Components/BillboardComponent.h"
#include "AFPEnemySpawnerEditor.generated.h"
UCLASS()
class ARKNIGHTSFPEDITOR_API AAFPEnemySpawnerEditor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AAFPEnemySpawnerEditor();
UPROPERTY(EditAnywhere, Category = "AFPSpawn", meta = (DisplayName = "ProfileEditing"))
UAFPEnemySpawnProfile* StoredProfile;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AFPSpawn")
TArray<FEnemyStreak> Streaks;
UFUNCTION(CallInEditor, Category = "AFPSpawn")
void Apply();
virtual void OnConstruction(const FTransform& Transform) override;
protected:
UPROPERTY(VisibleAnywhere, Category = "AFPSpawn")
UBillboardComponent* SceneRoot;
virtual void BeginPlay() override;
UPROPERTY()
UAFPEnemySpawnProfile* LastStoredProfile;
UPROPERTY()
UMaterial* TextMaterial;
TArray<FColor> DebugColors;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
// Skylake 2023 all rights reserved
#pragma once
#include "Engine/DataAsset.h"
#include "ArknightsFP/Gameplay/Enemies/AFPEnemyBase.h"
#include "AFPEnemySpawnProfile.generated.h"
UENUM(BlueprintType, Blueprintable, Category = "AFPSpawn", DisplayName = "触发模式")
enum class ESpawnMethod : uint8
{
DirectStart UMETA(DisplayName="直接开始"),
StartAfter UMETA(DisplayName="波次生成后开始"),
StartAfterEliminate UMETA(DisplayName="波次歼灭后开始"),
ExternalCall UMETA(DisplayName="外部调用开始")
};
USTRUCT(BlueprintType, Blueprintable, Category = "AFPSpawn", meta = (MakeEditWidget))
struct FEnemyStreak
{
GENERATED_BODY()
/**
* 这个波次的名称
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AFPSpawn", DisplayName = "波次名称")
FName StreakName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AFPSpawn", DisplayName = "参考波次")
FName RefStreak;
/**
* 波次的开始方式。
* 直接开始:在生成器开始时开始。考虑生成延迟。
* 波次生成后开始:在参考波次生成完毕后开始。需要有效参考波次名称。
* 波次歼灭后开始:在参考波次被消灭后开始。需要有效参考波次名称。
* 外部调用开始:接受外部调用而开始。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AFPSpawn", DisplayName = "触发模式")
ESpawnMethod SpawnMethod = ESpawnMethod::DirectStart;
/**
* 该敌人波次的生成延迟
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AFPSpawn", DisplayName = "初始延迟")
float StreakDelay = 1.0;
/**
* 敌人的生成并不在一帧内完成。此数决定该波次敌人的生成用时
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AFPSpawn", DisplayName = "分布时间")
float DistributeTime = 0.5;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AFPSpawn", meta = (MakeEditWidget), DisplayName = "生成位置")
FTransform EnemyTransform = FTransform();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AFPSpawn", DisplayName = "敌人类型")
TSubclassOf<AAFPEnemyBase> EnemyType = AAFPEnemyBase::StaticClass();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AFPSpawn", DisplayName = "敌人数量")
int32 EnemyCount = 1;
};
UCLASS(BlueprintType, Category = "AFPSpawn")
class ARKNIGHTSFP_API UAFPEnemySpawnProfile : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AFPSpawn")
TArray<FEnemyStreak> Streaks;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment