setup npc visuals

This commit is contained in:
2026-06-03 15:12:04 +03:00
parent fdb1366516
commit 11cc4ae538
34 changed files with 142 additions and 38 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
[/Script/EngineSettings.GameMapsSettings]
EditorStartupMap=/Game/Test/Maps/TestLevel.TestLevel
EditorStartupMap=/Game/Test/Maps/L_Test.L_Test
LocalMapOptions=
TransitionMap=None
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 "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;
};