setup npc visuals
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
#include "NPCTypeDefinition.h"
|
||||
#include "NPCAIController.h"
|
||||
#include "NPCTargetLocation.h"
|
||||
#include "NPCVisualsConfig.h"
|
||||
#include "BehaviorTree/BlackboardComponent.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
@@ -33,22 +34,22 @@ ANPC::ANPC()
|
||||
|
||||
ENPCType ANPC::GetNPCType() const
|
||||
{
|
||||
return NPCTypeDefinition ? NPCTypeDefinition->Type : ENPCType::Walker;
|
||||
return TypeDefinition ? TypeDefinition->Type : ENPCType::Walker;
|
||||
}
|
||||
|
||||
float ANPC::GetObservationWeight() const
|
||||
{
|
||||
return NPCTypeDefinition ? NPCTypeDefinition->ObservationWeight : 1.0f;
|
||||
return TypeDefinition ? TypeDefinition->ObservationWeight : 1.0f;
|
||||
}
|
||||
|
||||
bool ANPC::ShouldStopToObserve() const
|
||||
{
|
||||
return NPCTypeDefinition ? NPCTypeDefinition->bStopsToObserve : false;
|
||||
return TypeDefinition ? TypeDefinition->bStopsToObserve : false;
|
||||
}
|
||||
|
||||
float ANPC::GetObserveDuration() const
|
||||
{
|
||||
return NPCTypeDefinition ? NPCTypeDefinition->ObserveDurationSeconds : 0.0f;
|
||||
return TypeDefinition ? TypeDefinition->ObserveDurationSeconds : 0.0f;
|
||||
}
|
||||
|
||||
void ANPC::ActivateFromPool(const FVector& Location, const FRotator& Rotation)
|
||||
@@ -97,4 +98,21 @@ void ANPC::DeactivateToPool()
|
||||
|
||||
SetActorHiddenInGame(true);
|
||||
SetActorEnableCollision(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ANPC::Init(ENPCType InType)
|
||||
{
|
||||
if (InType == ENPCType::None)
|
||||
return;
|
||||
|
||||
Type = InType;
|
||||
|
||||
if (!NPCTypeDefinitions.Contains(Type))
|
||||
return;
|
||||
|
||||
UNPCTypeDefinition* Definition = NPCTypeDefinitions[Type];
|
||||
if (!Definition)
|
||||
return;
|
||||
|
||||
TypeDefinition = Definition;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "NPCType.h"
|
||||
#include "NPC.generated.h"
|
||||
|
||||
class UNPCVisualsConfig;
|
||||
class ANPCTargetLocation;
|
||||
class UNPCTypeDefinition;
|
||||
|
||||
@@ -21,7 +22,7 @@ public:
|
||||
// Behavioral template for this NPC (Walker / Stalker / …). Author one DA_* per type and assign
|
||||
// here (or on the NPC blueprint). Null falls back to Walker-ish defaults (GDD §10.2, §17.4).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "NPC")
|
||||
TObjectPtr<UNPCTypeDefinition> NPCTypeDefinition;
|
||||
TMap<ENPCType, TObjectPtr<UNPCTypeDefinition>> NPCTypeDefinitions;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "NPC")
|
||||
ENPCType GetNPCType() const;
|
||||
@@ -43,8 +44,16 @@ public:
|
||||
// layer restart and pick a fresh destination (BT startup lives in BP, §17.5).
|
||||
void ActivateFromPool(const FVector& Location, const FRotator& Rotation);
|
||||
void DeactivateToPool();
|
||||
void Init(ENPCType InType);
|
||||
|
||||
protected:
|
||||
UPROPERTY(BlueprintReadWrite, Category = "NPC")
|
||||
TObjectPtr<ANPCTargetLocation> TargetLocationActor;
|
||||
|
||||
private:
|
||||
UPROPERTY()
|
||||
ENPCType Type = ENPCType::None;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UNPCTypeDefinition> TypeDefinition;
|
||||
};
|
||||
@@ -14,9 +14,9 @@ USTRUCT(BlueprintType)
|
||||
struct FNPCSpawnEntry
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, Category = "NPC")
|
||||
TSubclassOf<ANPC> NPCClass;
|
||||
ENPCType Type = ENPCType::Walker;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, Category = "NPC", meta = (ClampMin = "0.0"))
|
||||
float Weight = 1.0f;
|
||||
@@ -59,4 +59,7 @@ public:
|
||||
// Weighted classes drawn into the pool at prewarm. Empty = director spawns nothing.
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Population")
|
||||
TArray<FNPCSpawnEntry> SpawnTable;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, Category = "NPC")
|
||||
TArray<TSubclassOf<ANPC>> NPCClasses;
|
||||
};
|
||||
@@ -67,11 +67,13 @@ void UNPCDirectorSubsystem::PrewarmPool()
|
||||
|
||||
for (int32 i = 0; i < Config->MaxNPCs; ++i)
|
||||
{
|
||||
const TSubclassOf<ANPC> NPCClass = PickWeightedClass();
|
||||
if (!NPCClass)
|
||||
const ENPCType NPCType = PickWeightedClass();
|
||||
if (NPCType == ENPCType::None)
|
||||
continue;
|
||||
|
||||
ANPC* NPC = World->SpawnActor<ANPC>(NPCClass, FVector::ZeroVector, FRotator::ZeroRotator, Params);
|
||||
TSubclassOf<ANPC> SelectedNPCClass = GetRandomNPCClass(Config->NPCClasses);
|
||||
ANPC* NPC = World->SpawnActor<ANPC>(SelectedNPCClass, FVector::ZeroVector, FRotator::ZeroRotator, Params);
|
||||
NPC->Init(NPCType);
|
||||
if (!NPC)
|
||||
continue;
|
||||
|
||||
@@ -169,31 +171,31 @@ void UNPCDirectorSubsystem::ReturnToPool(ANPC* NPC)
|
||||
}
|
||||
}
|
||||
|
||||
TSubclassOf<ANPC> UNPCDirectorSubsystem::PickWeightedClass() const
|
||||
ENPCType UNPCDirectorSubsystem::PickWeightedClass() const
|
||||
{
|
||||
const UNPCDirectorConfig* Config = GetConfig();
|
||||
if (!Config)
|
||||
return nullptr;
|
||||
return ENPCType::None;
|
||||
|
||||
float TotalWeight = 0.0f;
|
||||
for (const FNPCSpawnEntry& Entry : Config->SpawnTable)
|
||||
{
|
||||
if (Entry.NPCClass && Entry.Weight > 0.0f)
|
||||
if (Entry.Type != ENPCType::None && Entry.Weight > 0.0f)
|
||||
TotalWeight += Entry.Weight;
|
||||
}
|
||||
if (TotalWeight <= 0.0f)
|
||||
return nullptr;
|
||||
return ENPCType::None;
|
||||
|
||||
float Roll = FMath::FRandRange(0.0f, TotalWeight);
|
||||
for (const FNPCSpawnEntry& Entry : Config->SpawnTable)
|
||||
{
|
||||
if (!Entry.NPCClass || Entry.Weight <= 0.0f)
|
||||
if (Entry.Type == ENPCType::None || Entry.Weight <= 0.0f)
|
||||
continue;
|
||||
Roll -= Entry.Weight;
|
||||
if (Roll <= 0.0f)
|
||||
return Entry.NPCClass;
|
||||
return Entry.Type;
|
||||
}
|
||||
return nullptr;
|
||||
return ENPCType::None;
|
||||
}
|
||||
|
||||
bool UNPCDirectorSubsystem::FindSpawnPoint(const FVector& Around, FVector& OutLocation) const
|
||||
@@ -219,6 +221,12 @@ bool UNPCDirectorSubsystem::FindSpawnPoint(const FVector& Around, FVector& OutLo
|
||||
return false;
|
||||
}
|
||||
|
||||
TSubclassOf<ANPC> UNPCDirectorSubsystem::GetRandomNPCClass(TArray<TSubclassOf<ANPC>> InNPCClasses)
|
||||
{
|
||||
const int32 RandomIndex = FMath::RandRange(0, InNPCClasses.Num() - 1);
|
||||
return InNPCClasses[RandomIndex];
|
||||
}
|
||||
|
||||
APawn* UNPCDirectorSubsystem::GetPlayerPawn() const
|
||||
{
|
||||
return UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "NPCType.h"
|
||||
#include "Subsystems/WorldSubsystem.h"
|
||||
#include "NakedDesire/Global/TimeOfDaySubsystem.h"
|
||||
#include "NPCDirectorSubsystem.generated.h"
|
||||
@@ -40,8 +41,9 @@ private:
|
||||
|
||||
ANPC* TakeFromPool();
|
||||
void ReturnToPool(ANPC* NPC);
|
||||
TSubclassOf<ANPC> PickWeightedClass() const;
|
||||
ENPCType PickWeightedClass() const;
|
||||
bool FindSpawnPoint(const FVector& Around, FVector& OutLocation) const;
|
||||
TSubclassOf<ANPC> GetRandomNPCClass(TArray<TSubclassOf<ANPC>> InNPCClasses);
|
||||
|
||||
APawn* GetPlayerPawn() const;
|
||||
UNPCDirectorConfig* GetConfig() const;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
UENUM(BlueprintType)
|
||||
enum class ENPCType : uint8
|
||||
{
|
||||
None UMETA(DIsplayName = "None"),
|
||||
Walker UMETA(DisplayName = "Walker"),
|
||||
Stalker UMETA(DisplayName = "Stalker"),
|
||||
Blogger UMETA(DisplayName = "Blogger"),
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
|
||||
#include "NPCVisualsConfig.h"
|
||||
@@ -0,0 +1,17 @@
|
||||
// © 2025 Naked People Team. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "NPCVisualsConfig.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class NAKEDDESIRE_API UNPCVisualsConfig : public UPrimaryDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditDefaultsOnly, Category = "Visuals")
|
||||
TArray<TObjectPtr<USkeletalMesh>> BodyMeshes;
|
||||
};
|
||||
Reference in New Issue
Block a user