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
+1 -1
View File
@@ -1,7 +1,7 @@
[/Script/EngineSettings.GameMapsSettings] [/Script/EngineSettings.GameMapsSettings]
EditorStartupMap=/Game/Test/Maps/TestLevel.TestLevel EditorStartupMap=/Game/Test/Maps/L_Test.L_Test
LocalMapOptions= LocalMapOptions=
TransitionMap=None TransitionMap=None
bUseSplitscreen=True bUseSplitscreen=True
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+23 -5
View File
@@ -7,6 +7,7 @@
#include "NPCTypeDefinition.h" #include "NPCTypeDefinition.h"
#include "NPCAIController.h" #include "NPCAIController.h"
#include "NPCTargetLocation.h" #include "NPCTargetLocation.h"
#include "NPCVisualsConfig.h"
#include "BehaviorTree/BlackboardComponent.h" #include "BehaviorTree/BlackboardComponent.h"
#include "Components/SkeletalMeshComponent.h" #include "Components/SkeletalMeshComponent.h"
#include "GameFramework/CharacterMovementComponent.h" #include "GameFramework/CharacterMovementComponent.h"
@@ -33,22 +34,22 @@ ANPC::ANPC()
ENPCType ANPC::GetNPCType() const ENPCType ANPC::GetNPCType() const
{ {
return NPCTypeDefinition ? NPCTypeDefinition->Type : ENPCType::Walker; return TypeDefinition ? TypeDefinition->Type : ENPCType::Walker;
} }
float ANPC::GetObservationWeight() const float ANPC::GetObservationWeight() const
{ {
return NPCTypeDefinition ? NPCTypeDefinition->ObservationWeight : 1.0f; return TypeDefinition ? TypeDefinition->ObservationWeight : 1.0f;
} }
bool ANPC::ShouldStopToObserve() const bool ANPC::ShouldStopToObserve() const
{ {
return NPCTypeDefinition ? NPCTypeDefinition->bStopsToObserve : false; return TypeDefinition ? TypeDefinition->bStopsToObserve : false;
} }
float ANPC::GetObserveDuration() const 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) void ANPC::ActivateFromPool(const FVector& Location, const FRotator& Rotation)
@@ -97,4 +98,21 @@ void ANPC::DeactivateToPool()
SetActorHiddenInGame(true); SetActorHiddenInGame(true);
SetActorEnableCollision(false); 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 "NPCType.h"
#include "NPC.generated.h" #include "NPC.generated.h"
class UNPCVisualsConfig;
class ANPCTargetLocation; class ANPCTargetLocation;
class UNPCTypeDefinition; class UNPCTypeDefinition;
@@ -21,7 +22,7 @@ public:
// Behavioral template for this NPC (Walker / Stalker / …). Author one DA_* per type and assign // 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). // here (or on the NPC blueprint). Null falls back to Walker-ish defaults (GDD §10.2, §17.4).
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "NPC") UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "NPC")
TObjectPtr<UNPCTypeDefinition> NPCTypeDefinition; TMap<ENPCType, TObjectPtr<UNPCTypeDefinition>> NPCTypeDefinitions;
UFUNCTION(BlueprintPure, Category = "NPC") UFUNCTION(BlueprintPure, Category = "NPC")
ENPCType GetNPCType() const; ENPCType GetNPCType() const;
@@ -43,8 +44,16 @@ public:
// layer restart and pick a fresh destination (BT startup lives in BP, §17.5). // layer restart and pick a fresh destination (BT startup lives in BP, §17.5).
void ActivateFromPool(const FVector& Location, const FRotator& Rotation); void ActivateFromPool(const FVector& Location, const FRotator& Rotation);
void DeactivateToPool(); void DeactivateToPool();
void Init(ENPCType InType);
protected: protected:
UPROPERTY(BlueprintReadWrite, Category = "NPC") UPROPERTY(BlueprintReadWrite, Category = "NPC")
TObjectPtr<ANPCTargetLocation> TargetLocationActor; 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 struct FNPCSpawnEntry
{ {
GENERATED_BODY() GENERATED_BODY()
UPROPERTY(EditDefaultsOnly, Category = "NPC") UPROPERTY(EditDefaultsOnly, Category = "NPC")
TSubclassOf<ANPC> NPCClass; ENPCType Type = ENPCType::Walker;
UPROPERTY(EditDefaultsOnly, Category = "NPC", meta = (ClampMin = "0.0")) UPROPERTY(EditDefaultsOnly, Category = "NPC", meta = (ClampMin = "0.0"))
float Weight = 1.0f; float Weight = 1.0f;
@@ -59,4 +59,7 @@ public:
// Weighted classes drawn into the pool at prewarm. Empty = director spawns nothing. // Weighted classes drawn into the pool at prewarm. Empty = director spawns nothing.
UPROPERTY(EditDefaultsOnly, Category = "Population") UPROPERTY(EditDefaultsOnly, Category = "Population")
TArray<FNPCSpawnEntry> SpawnTable; 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) for (int32 i = 0; i < Config->MaxNPCs; ++i)
{ {
const TSubclassOf<ANPC> NPCClass = PickWeightedClass(); const ENPCType NPCType = PickWeightedClass();
if (!NPCClass) if (NPCType == ENPCType::None)
continue; 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) if (!NPC)
continue; continue;
@@ -169,31 +171,31 @@ void UNPCDirectorSubsystem::ReturnToPool(ANPC* NPC)
} }
} }
TSubclassOf<ANPC> UNPCDirectorSubsystem::PickWeightedClass() const ENPCType UNPCDirectorSubsystem::PickWeightedClass() const
{ {
const UNPCDirectorConfig* Config = GetConfig(); const UNPCDirectorConfig* Config = GetConfig();
if (!Config) if (!Config)
return nullptr; return ENPCType::None;
float TotalWeight = 0.0f; float TotalWeight = 0.0f;
for (const FNPCSpawnEntry& Entry : Config->SpawnTable) 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; TotalWeight += Entry.Weight;
} }
if (TotalWeight <= 0.0f) if (TotalWeight <= 0.0f)
return nullptr; return ENPCType::None;
float Roll = FMath::FRandRange(0.0f, TotalWeight); float Roll = FMath::FRandRange(0.0f, TotalWeight);
for (const FNPCSpawnEntry& Entry : Config->SpawnTable) for (const FNPCSpawnEntry& Entry : Config->SpawnTable)
{ {
if (!Entry.NPCClass || Entry.Weight <= 0.0f) if (Entry.Type == ENPCType::None || Entry.Weight <= 0.0f)
continue; continue;
Roll -= Entry.Weight; Roll -= Entry.Weight;
if (Roll <= 0.0f) if (Roll <= 0.0f)
return Entry.NPCClass; return Entry.Type;
} }
return nullptr; return ENPCType::None;
} }
bool UNPCDirectorSubsystem::FindSpawnPoint(const FVector& Around, FVector& OutLocation) const bool UNPCDirectorSubsystem::FindSpawnPoint(const FVector& Around, FVector& OutLocation) const
@@ -219,6 +221,12 @@ bool UNPCDirectorSubsystem::FindSpawnPoint(const FVector& Around, FVector& OutLo
return false; 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 APawn* UNPCDirectorSubsystem::GetPlayerPawn() const
{ {
return UGameplayStatics::GetPlayerPawn(GetWorld(), 0); return UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
@@ -3,6 +3,7 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "NPCType.h"
#include "Subsystems/WorldSubsystem.h" #include "Subsystems/WorldSubsystem.h"
#include "NakedDesire/Global/TimeOfDaySubsystem.h" #include "NakedDesire/Global/TimeOfDaySubsystem.h"
#include "NPCDirectorSubsystem.generated.h" #include "NPCDirectorSubsystem.generated.h"
@@ -40,8 +41,9 @@ private:
ANPC* TakeFromPool(); ANPC* TakeFromPool();
void ReturnToPool(ANPC* NPC); void ReturnToPool(ANPC* NPC);
TSubclassOf<ANPC> PickWeightedClass() const; ENPCType PickWeightedClass() const;
bool FindSpawnPoint(const FVector& Around, FVector& OutLocation) const; bool FindSpawnPoint(const FVector& Around, FVector& OutLocation) const;
TSubclassOf<ANPC> GetRandomNPCClass(TArray<TSubclassOf<ANPC>> InNPCClasses);
APawn* GetPlayerPawn() const; APawn* GetPlayerPawn() const;
UNPCDirectorConfig* GetConfig() const; UNPCDirectorConfig* GetConfig() const;
+1
View File
@@ -11,6 +11,7 @@
UENUM(BlueprintType) UENUM(BlueprintType)
enum class ENPCType : uint8 enum class ENPCType : uint8
{ {
None UMETA(DIsplayName = "None"),
Walker UMETA(DisplayName = "Walker"), Walker UMETA(DisplayName = "Walker"),
Stalker UMETA(DisplayName = "Stalker"), Stalker UMETA(DisplayName = "Stalker"),
Blogger UMETA(DisplayName = "Blogger"), 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;
};