setup npc visuals

This commit is contained in:
2026-06-03 15:12:04 +03:00
parent f6608c5f9a
commit 00fe452168
34 changed files with 142 additions and 38 deletions
+23 -5
View File
@@ -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;
}
+10 -1
View File
@@ -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;
};
+5 -2
View File
@@ -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;
};
+18 -10
View File
@@ -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;
+1
View File
@@ -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"
+17
View File
@@ -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;
};