Replicated

** [Drag Down] Network Object Pooling (Subsystem + Interface + GameMode + DataAsset) ** 본문

언리얼 엔진/Drag Down

** [Drag Down] Network Object Pooling (Subsystem + Interface + GameMode + DataAsset) **

라구넹 2025. 4. 16. 18:22

생각나는 구현 방법은 세가지

- 액터 기반

- 게임인스턴스 서브시스템 기반

- 월드 서브시스템 기반

 

액터 기반 -> 전역 풀 매니저인데 액터보단 서브시스템 기반이 더 적절. 레벨마다 액터 가져다 놓기도 좀 별로다

게임인스턴스 서브시스템 기반 -> 게임 내내 유지되어서 메모리 낭비가 있을 듯 함

월드 서브시스템 기반 -> 월드마다 원하는 풀링 오브젝트 초기화 시킬 수 있으니 메모리 낭비도 없고 좋은듯?

=> WorldSubsystem 기반으로 결정

 

최대한 확장성을 고려했고, 데이터 기반으로 개발하고자 했다

DataAsset 기반의 PooledObjectData의 BP를 GameMode가 읽어서 풀링 초기화

그 이후 다른 오브젝트들이 알아서 서브시스템에서 Get, Return하도록 한다

 

게임 모드도 BP라서 그냥 PooledObjectData만 갈아끼우면 월드마다 풀링 다르게되게 가능

 

단, 어쩔 수 없는 의존성이 하나 생긴다

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "DDPoolable.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UDDPoolable : public UInterface
{
	GENERATED_BODY()
};

/**
 * 
 */
class DRAGDOWN_API IDDPoolable
{
	GENERATED_BODY()

public:
	/**
	* Usage:
	* void ClassName::OnRetrievedFromPool()
	{
		if ( HasAuthority() )
		{
			~~~ Timer Setting (if need) ~~~, timer needs to be activated in server, not client

			NetMulticastOnRetrievedFromPool(GetActorLocation(), GetActorRotation()); // for multi sync
		}
	}
	void ClassName::NetMulticastOnRetrievedFromPool_Implementation(FVector NewLocation, FRotator NewRotation)
	{
		~~~ Specific Setting (if need) ~~~

		SetActorHiddenInGame(false);
		SetActorEnableCollision(true);
		SetActorTickEnabled(true);

		SetActorLocationAndRotation(NewLocation, NewRotation);
	}
	*/
	virtual void OnRetrievedFromPool() = 0;

	/*
	* void ClassName::OnReturnedToPool()
	{
		if ( HasAuthority() )
		{
			NetMulticastOnReturnedToPool(); // for multi sync
		}
	}
	void ClassName::NetMulticastOnReturnedToPool_Implementation()
	{
		SetActorHiddenInGame(true);
		SetActorEnableCollision(false);
		SetActorTickEnabled(false);
	}
	*/
	virtual void OnReturnedToPool() = 0;
};

서브시스템은 RPC가 없다

즉, 개별 액터에게 알아서 풀링 상태 관리를 맡겨야 한다

그렇기에 일단 풀 매니저는 인터페이스 IPoolable만 알면 되도록해서 최대한 분리하고,

사용 방법을 주석으로 적어두었다

 

// Fill out your copyright notice in the Description page of Project Settings.


#include "Actor/DDBulletBase.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include "Components/SphereComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Physics/DDCollision.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Subsystem/DDNetworkObjectPoolingSubsystem.h"
#include "DragDown.h"

ADDBulletBase::ADDBulletBase()
{
	bReplicates = true;

	Trigger = CreateDefaultSubobject<USphereComponent>(TEXT("Trigger"));
	Trigger->InitSphereRadius(5.0f);
	Trigger->SetCollisionProfileName(CPROFILE_DDTRIGGER);
	RootComponent = Trigger;
	Trigger->OnComponentBeginOverlap.AddDynamic(this, &ADDBulletBase::OnComponentBeginOverlapCallback);

	StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComp"));
	StaticMeshComp->SetupAttachment(RootComponent);
	StaticMeshComp->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
	StaticMeshComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);

	Movement = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("Movement"));
	Movement->InitialSpeed = 3000.0f;
	Movement->MaxSpeed = 3000.0f;
	Movement->bRotationFollowsVelocity = true;
	Movement->bShouldBounce = false;
	Movement->ProjectileGravityScale = 0.0f;
	
	Power = 3000.0f;

	BulletLivingTime = 1.0f;
}

void ADDBulletBase::OnComponentBeginOverlapCallback(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepHitResult)
{
	if ( HasAuthority() )
	{
		ACharacter* Character = Cast<ACharacter>(OtherActor);
		if (Character)
		{
			PushCharacter(Cast<ACharacter>(OtherActor));
		}
	}
}

void ADDBulletBase::PushCharacter(ACharacter* Character)
{
	if ( Character && Character->GetCharacterMovement() )
	{
		FVector Dir = GetActorForwardVector();
		Character->LaunchCharacter(Dir * Power, true, true);
	}
}

void ADDBulletBase::OnRetrievedFromPool()
{
	if ( HasAuthority() )
	{
		GetWorld()->GetTimerManager().SetTimer(PoolingTimer, this, &ADDBulletBase::PoolBullet, BulletLivingTime, false);

		NetMulticastOnRetrievedFromPool(GetActorLocation(), GetActorRotation());
	}
}

void ADDBulletBase::OnReturnedToPool()
{
	if ( HasAuthority() )
	{
		NetMulticastOnReturnedToPool();
	}
}

void ADDBulletBase::NetMulticastOnRetrievedFromPool_Implementation(FVector NewLocation, FRotator NewRotation)
{
	if (Movement)
	{
		Movement->StopMovementImmediately();
		Movement->Velocity = GetActorForwardVector() * Movement->InitialSpeed;
	}

	SetActorHiddenInGame(false);
	SetActorEnableCollision(true);
	SetActorTickEnabled(true);

	SetActorLocationAndRotation(NewLocation, NewRotation);
}

void ADDBulletBase::NetMulticastOnReturnedToPool_Implementation()
{
	if ( Movement )
	{
		Movement->StopMovementImmediately();
		Movement->Velocity = FVector();
	}

	SetActorHiddenInGame(true);
	SetActorEnableCollision(false);
	SetActorTickEnabled(false);
}

void ADDBulletBase::PoolBullet()
{
	UDDNetworkObjectPoolingSubsystem* ObjectPool = GetWorld()->GetSubsystem<UDDNetworkObjectPoolingSubsystem>();
	if (ObjectPool)
	{
		ObjectPool->ReturnPooledObject(Cast<AActor>(this));
	}
}

void ADDBulletBase::FellOutOfWorld(const UDamageType& dmgType)
{
	PoolBullet(); 
}

예시로 풀링되는 오브젝트는 이렇게 구현 가능하다

 

void ADDBulletBase::PoolBullet()
{
	UDDNetworkObjectPoolingSubsystem* ObjectPool = GetWorld()->GetSubsystem<UDDNetworkObjectPoolingSubsystem>();
	if (ObjectPool)
	{
		ObjectPool->ReturnPooledObject(Cast<AActor>(this));
	}
}

풀에 넣는 건 ReturnPooledObject 호출

 

void ADDPeriodicSpawner::Spawn()
{
	FVector SpawnLocation = GetActorLocation();
	SpawnLocation.Z += Offset;
	FRotator SpawnRotation = SpawningRot;

	if ( GetWorld() )
	{
		UDDNetworkObjectPoolingSubsystem* PoolingSystem = GetWorld()->GetSubsystem<UDDNetworkObjectPoolingSubsystem>();

		PoolingSystem->GetPooledObject(ActorClass, SpawnLocation, SpawnRotation);
	}
}

풀에서 꺼내는 건 그냥 GetPooledObject만 해주면 된다

 

이제 다음부턴 여기서만 관리하면 된다