A high-performance, modular, and Blueprint-friendly object pooling system for Unreal Engine.
Read this in other languages: English, Turkish
Object Recycler is a robust solution for optimizing game performance by recycling spawned objects instead of destroying and recreating them. This significantly reduces CPU and memory overhead, especially in games with large numbers of frequently spawned objects.
- High Performance: Recycling objects can be up to 45x faster than spawning fresh actors
- Blueprint-Friendly: Full Blueprint support with an intuitive API
- Memory Management: Automatic pool size management with configurable settings
- Developer Settings Integration: Configure pools centrally via Project Settings
- WorldSubsystem Architecture: Isolated pools per level for better management
- Automatic Warmup: Pre-populate pools automatically at level start
- Garbage Collection Optimization: Reduces GC pressure and associated hitches
- Detailed Logging: Comprehensive debug information for troubleshooting
- Clone this repository
- Copy the
ObjectRecyclerfolder to your project'sPluginsdirectory - Regenerate project files and recompile your project
- Enable the plugin in your project settings
Alternatively, you can copy the source files directly into your project's source directory.
Object pools can be configured globally through the Unreal Editor UI:
- Go to Edit → Project Settings → Game → Object Recycler
- Enable Auto Warmup
- Set Warmup Start Delay (seconds to wait after level start)
- Add pool configurations for your object classes
- Configure Amount To Spawn, Delay Between Spawns, Soft Cap, and Pool Removal Frequency
# DefaultGame.ini example
[/Script/YourGame.ObjectRecyclerSettings]
bEnableAutoWarmup=True
WarmupStartDelay=1.0
+AutoWarmupPools=(ObjectClass="/Game/Blueprints/BP_Projectile.BP_Projectile_C",AmountToSpawn=50,DelayBetweenSpawns=0.05,SoftCap=500,PoolRemovalFrequency=0.1)Any actor that implements the IObjectPoolable interface can be managed by the pool system:
UCLASS()
class AMyPoolableActor : public AActor, public IObjectPoolable
{
GENERATED_BODY()
public:
// IObjectPoolable interface
virtual void AddedToPool_Implementation(FObjectPoolPayload Payload) override;
virtual void RemovedFromPool_Implementation(FObjectPoolPayload Payload) override;
virtual void ObjectRequestedForUsage_Implementation(FObjectPoolPayload Payload) override;
virtual void ReturnedToPool_Implementation(FObjectPoolPayload Payload) override;
virtual bool CanBeRecycled_Implementation() override;
};Implement the required functions to properly handle pooling:
void AMyPoolableActor::AddedToPool_Implementation(FObjectPoolPayload Payload)
{
// Hide and disable actor when added to pool
SetActorHiddenInGame(true);
SetActorEnableCollision(false);
SetActorTickEnabled(false);
}
void AMyPoolableActor::ObjectRequestedForUsage_Implementation(FObjectPoolPayload Payload)
{
// Set up actor when taken from pool (like BeginPlay)
SetActorLocation(Payload.SpawnLocation);
SetActorRotation(Payload.SpawnRotation);
SetActorHiddenInGame(false);
SetActorEnableCollision(true);
SetActorTickEnabled(true);
// Get custom parameters if provided
if (Payload.FloatParameters.Contains("Speed"))
{
float Speed = Payload.FloatParameters["Speed"];
// Use the speed parameter
}
}
void AMyPoolableActor::ReturnedToPool_Implementation(FObjectPoolPayload Payload)
{
// Clean up actor when returned to pool (like Destroy)
SetActorHiddenInGame(true);
SetActorEnableCollision(false);
SetActorTickEnabled(false);
}
bool AMyPoolableActor::CanBeRecycled_Implementation()
{
// Return true if this object can be recycled now
return true;
}// Get the pool manager from world subsystem
UObjectPoolManager* PoolManager = GetWorld()->GetSubsystem<UObjectPoolManager>();
if (PoolManager)
{
// Create payload with spawn parameters
FObjectPoolPayload Payload;
Payload.SpawnLocation = SpawnLocation;
Payload.SpawnRotation = SpawnRotation;
Payload.FloatParameters.Add("Speed", 2000.0f);
// Get object from pool (will automatically spawn if none available)
AActor* Actor = PoolManager->GetObjectFromPool(MyActorClass, Payload);
// Cast to your specific actor type if needed
AMyActor* MyActor = Cast<AMyActor>(Actor);
if (MyActor)
{
// Use the actor...
}
}// When object is no longer needed (e.g., projectile hit something)
void AMyActor::ReturnToPool()
{
UObjectPoolManager* PoolManager = GetWorld()->GetSubsystem<UObjectPoolManager>();
if (PoolManager)
{
FObjectPoolPayload Payload;
// Add any parameters needed for cleanup
// Return to pool instead of destroying
PoolManager->ReturnObjectToPool(this, Payload);
}
}Here's a complete example of a poolable projectile:
// PoolableProjectile.h (partial)
UCLASS()
class APoolableProjectile : public AActor, public IObjectPoolable
{
GENERATED_BODY()
public:
// Components
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UStaticMeshComponent* ProjectileMesh;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UProjectileMovementComponent* ProjectileMovement;
// Properties
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile")
float Damage = 20.0f;
UPROPERTY(BlueprintReadOnly, Category = "Pooling")
bool bIsActive = false;
// Methods
UFUNCTION()
void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, FVector NormalImpulse,
const FHitResult& Hit);
UFUNCTION(BlueprintCallable, Category = "Projectile")
void ReturnToPool();
};
// PoolableProjectile.cpp (partial)
void APoolableProjectile::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, FVector NormalImpulse,
const FHitResult& Hit)
{
if (bIsActive && OtherActor && OtherActor != this)
{
// Apply damage
UGameplayStatics::ApplyPointDamage(
OtherActor,
Damage,
Hit.ImpactPoint - Hit.TraceStart,
Hit,
GetInstigatorController(),
this,
UDamageType::StaticClass()
);
// Spawn hit effect
SpawnHitEffect(Hit.ImpactPoint, Hit.ImpactNormal);
// Return to pool
ReturnToPool();
}
}
void APoolableProjectile::ReturnToPool()
{
if (!bIsActive)
{
return;
}
UObjectPoolManager* PoolManager = GetWorld()->GetSubsystem<UObjectPoolManager>();
if (PoolManager)
{
FObjectPoolPayload Payload;
PoolManager->ReturnObjectToPool(this, Payload);
}
}void UWeaponComponent::Fire()
{
UObjectPoolManager* PoolManager = GetWorld()->GetSubsystem<UObjectPoolManager>();
if (PoolManager)
{
FObjectPoolPayload Payload;
Payload.SpawnLocation = MuzzleLocation;
Payload.SpawnRotation = MuzzleRotation;
Payload.FloatParameters.Add("Damage", ProjectileDamage);
Payload.FloatParameters.Add("Speed", ProjectileSpeed);
AActor* Actor = PoolManager->GetObjectFromPool(ProjectileClass, Payload);
APoolableProjectile* Projectile = Cast<APoolableProjectile>(Actor);
if (Projectile)
{
Projectile->FireProjectile(MuzzleRotation.Vector(), ProjectileSpeed);
}
}
}| Method | Description |
|---|---|
WarmupPool |
Pre-spawns a number of objects to avoid runtime spawning costs |
GetObjectFromPool |
Retrieves an object from the pool or spawns a new one |
ReturnObjectToPool |
Returns an object to the pool for later reuse |
AddObjectToPool |
Adds an existing object to the pool |
RemoveObjectFromPool |
Permanently removes an object from the pool |
LabelObjectAsBusy |
Marks an object as in-use |
LabelObjectAsRecyclable |
Marks an object as available for reuse |
CanObjectBeRecycled |
Checks if an object can be recycled |
ResetObjectPool |
Clears all objects from a specific pool |
ResetClassPool |
Removes an entire class pool |
| Method | Description |
|---|---|
AddedToPool |
Called when an object is first added to the pool |
RemovedFromPool |
Called when an object is permanently removed from the pool |
ObjectRequestedForUsage |
Called when an object is retrieved from the pool (similar to BeginPlay) |
ReturnedToPool |
Called when an object is returned to the pool (similar to Destroy) |
CanBeRecycled |
Returns whether the object can be recycled |
- Pre-warm pools for predictable object types at level start
- Keep objects simple when pooling - simpler objects recycle faster
- Properly reset state in
ObjectRequestedForUsageandReturnedToPool - Use custom parameters in
FObjectPoolPayloadto pass initialization data - Configure soft caps appropriately based on your game's needs
- Use the WorldSubsystem to get access to the pool manager
- Add detailed logging when debugging pooling issues
This project is licensed under the MIT License - see the LICENSE file for details.
Unreal Engine için yüksek performanslı, modüler ve Blueprint-dostu nesne havuzlama sistemi.
Object Recycler, spawn edilen nesneleri yok edip yeniden oluşturmak yerine geri dönüştürerek oyun performansını optimize eden sağlam bir çözümdür. Bu, özellikle sık sık nesne spawn edilen oyunlarda CPU ve bellek kullanımını önemli ölçüde azaltır.
- Yüksek Performans: Nesneleri geri dönüştürmek, yeniden spawn etmekten 45 kata kadar daha hızlı olabilir
- Blueprint-Dostu: Sezgisel bir API ile tam Blueprint desteği
- Bellek Yönetimi: Yapılandırılabilir ayarlarla otomatik havuz boyutu yönetimi
- Developer Settings Entegrasyonu: Proje Ayarları üzerinden merkezi havuz yapılandırması
- WorldSubsystem Mimarisi: Daha iyi yönetim için seviye bazında izole havuzlar
- Otomatik Warmup: Seviye başlangıcında havuzları otomatik olarak doldurma
- Garbage Collection Optimizasyonu: GC baskısını ve ilgili takılmaları azaltır
- Detaylı Günlük Kaydı: Sorun giderme için kapsamlı hata ayıklama bilgileri
- Bu repository'yi klonlayın
ObjectRecyclerklasörünü projenizinPluginsdizinine kopyalayın- Proje dosyalarını yeniden oluşturun ve projenizi derleyin
- Eklentiyi proje ayarlarınızda etkinleştirin
Alternatif olarak, kaynak dosyalarını doğrudan projenizin kaynak dizinine kopyalayabilirsiniz.
Nesne havuzları, Unreal Editor arayüzü üzerinden global olarak yapılandırılabilir:
- Düzenle → Proje Ayarları → Oyun → Object Recycler'a gidin
- Otomatik Isınma'yı etkinleştirin
- Isınma Başlangıç Gecikmesi'ni ayarlayın (seviye başlangıcından sonra beklenecek saniye)
- Nesne sınıflarınız için havuz yapılandırmaları ekleyin
- Spawn Edilecek Miktar, Spawn'lar Arası Gecikme, Soft Cap ve Havuz Kaldırma Sıklığı'nı yapılandırın
# DefaultGame.ini örneği
[/Script/YourGame.ObjectRecyclerSettings]
bEnableAutoWarmup=True
WarmupStartDelay=1.0
+AutoWarmupPools=(ObjectClass="/Game/Blueprints/BP_Projectile.BP_Projectile_C",AmountToSpawn=50,DelayBetweenSpawns=0.05,SoftCap=500,PoolRemovalFrequency=0.1)IObjectPoolable arayüzünü uygulayan herhangi bir aktör, havuz sistemi tarafından yönetilebilir:
UCLASS()
class AMyPoolableActor : public AActor, public IObjectPoolable
{
GENERATED_BODY()
public:
// IObjectPoolable arayüzü
virtual void AddedToPool_Implementation(FObjectPoolPayload Payload) override;
virtual void RemovedFromPool_Implementation(FObjectPoolPayload Payload) override;
virtual void ObjectRequestedForUsage_Implementation(FObjectPoolPayload Payload) override;
virtual void ReturnedToPool_Implementation(FObjectPoolPayload Payload) override;
virtual bool CanBeRecycled_Implementation() override;
};Havuzlamayı doğru şekilde işlemek için gerekli fonksiyonları uygulayın:
void AMyPoolableActor::AddedToPool_Implementation(FObjectPoolPayload Payload)
{
// Havuza eklendiğinde aktörü gizle ve devre dışı bırak
SetActorHiddenInGame(true);
SetActorEnableCollision(false);
SetActorTickEnabled(false);
}
void AMyPoolableActor::ObjectRequestedForUsage_Implementation(FObjectPoolPayload Payload)
{
// Havuzdan alındığında aktörü ayarla (BeginPlay gibi)
SetActorLocation(Payload.SpawnLocation);
SetActorRotation(Payload.SpawnRotation);
SetActorHiddenInGame(false);
SetActorEnableCollision(true);
SetActorTickEnabled(true);
// Özel parametreler sağlanmışsa bunları al
if (Payload.FloatParameters.Contains("Speed"))
{
float Speed = Payload.FloatParameters["Speed"];
// Hız parametresini kullan
}
}
void AMyPoolableActor::ReturnedToPool_Implementation(FObjectPoolPayload Payload)
{
// Havuza geri döndüğünde aktörü temizle (Destroy gibi)
SetActorHiddenInGame(true);
SetActorEnableCollision(false);
SetActorTickEnabled(false);
}
bool AMyPoolableActor::CanBeRecycled_Implementation()
{
// Bu nesnenin şu anda geri dönüştürülebilir olup olmadığını döndür
return true;
}// World subsystem'den havuz yöneticisini al
UObjectPoolManager* PoolManager = GetWorld()->GetSubsystem<UObjectPoolManager>();
if (PoolManager)
{
// Spawn parametreleriyle payload oluştur
FObjectPoolPayload Payload;
Payload.SpawnLocation = SpawnLocation;
Payload.SpawnRotation = SpawnRotation;
Payload.FloatParameters.Add("Speed", 2000.0f);
// Havuzdan nesne al (mevcut yoksa otomatik olarak spawn eder)
AActor* Actor = PoolManager->GetObjectFromPool(MyActorClass, Payload);
// Gerekirse belirli aktör tipine cast et
AMyActor* MyActor = Cast<AMyActor>(Actor);
if (MyActor)
{
// Aktörü kullan...
}
}// Nesne artık gerekli olmadığında (örn. mermi bir şeye çarptığında)
void AMyActor::ReturnToPool()
{
UObjectPoolManager* PoolManager = GetWorld()->GetSubsystem<UObjectPoolManager>();
if (PoolManager)
{
FObjectPoolPayload Payload;
// Temizlik için gerekli parametreleri ekle
// Yok etmek yerine havuza geri döndür
PoolManager->ReturnObjectToPool(this, Payload);
}
}İşte havuzlanabilir bir mermi için tam bir örnek:
// PoolableProjectile.h (kısmi)
UCLASS()
class APoolableProjectile : public AActor, public IObjectPoolable
{
GENERATED_BODY()
public:
// Bileşenler
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UStaticMeshComponent* ProjectileMesh;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UProjectileMovementComponent* ProjectileMovement;
// Özellikler
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Projectile")
float Damage = 20.0f;
UPROPERTY(BlueprintReadOnly, Category = "Pooling")
bool bIsActive = false;
// Metodlar
UFUNCTION()
void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, FVector NormalImpulse,
const FHitResult& Hit);
UFUNCTION(BlueprintCallable, Category = "Projectile")
void ReturnToPool();
};
// PoolableProjectile.cpp (kısmi)
void APoolableProjectile::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, FVector NormalImpulse,
const FHitResult& Hit)
{
if (bIsActive && OtherActor && OtherActor != this)
{
// Hasar uygula
UGameplayStatics::ApplyPointDamage(
OtherActor,
Damage,
Hit.ImpactPoint - Hit.TraceStart,
Hit,
GetInstigatorController(),
this,
UDamageType::StaticClass()
);
// Çarpma efekti oluştur
SpawnHitEffect(Hit.ImpactPoint, Hit.ImpactNormal);
// Havuza geri dön
ReturnToPool();
}
}
void APoolableProjectile::ReturnToPool()
{
if (!bIsActive)
{
return;
}
UObjectPoolManager* PoolManager = GetWorld()->GetSubsystem<UObjectPoolManager>();
if (PoolManager)
{
FObjectPoolPayload Payload;
PoolManager->ReturnObjectToPool(this, Payload);
}
}void UWeaponComponent::Fire()
{
UObjectPoolManager* PoolManager = GetWorld()->GetSubsystem<UObjectPoolManager>();
if (PoolManager)
{
FObjectPoolPayload Payload;
Payload.SpawnLocation = MuzzleLocation;
Payload.SpawnRotation = MuzzleRotation;
Payload.FloatParameters.Add("Damage", ProjectileDamage);
Payload.FloatParameters.Add("Speed", ProjectileSpeed);
AActor* Actor = PoolManager->GetObjectFromPool(ProjectileClass, Payload);
APoolableProjectile* Projectile = Cast<APoolableProjectile>(Actor);
if (Projectile)
{
Projectile->FireProjectile(MuzzleRotation.Vector(), ProjectileSpeed);
}
}
}| Metod | Açıklama |
|---|---|
WarmupPool |
Çalışma zamanı spawn etme maliyetlerini önlemek için belirli sayıda nesne önceden spawn eder |
GetObjectFromPool |
Havuzdan bir nesne alır veya yeni bir tane spawn eder |
ReturnObjectToPool |
Bir nesneyi daha sonra yeniden kullanılmak üzere havuza geri döndürür |
AddObjectToPool |
Mevcut bir nesneyi havuza ekler |
RemoveObjectFromPool |
Bir nesneyi kalıcı olarak havuzdan kaldırır |
LabelObjectAsBusy |
Bir nesneyi kullanımda olarak işaretler |
LabelObjectAsRecyclable |
Bir nesneyi yeniden kullanım için uygun olarak işaretler |
CanObjectBeRecycled |
Bir nesnenin geri dönüştürülebilir olup olmadığını kontrol eder |
ResetObjectPool |
Belirli bir havuzdaki tüm nesneleri temizler |
ResetClassPool |
Tüm sınıf havuzunu kaldırır |
| Metod | Açıklama |
|---|---|
AddedToPool |
Bir nesne ilk kez havuza eklendiğinde çağrılır |
RemovedFromPool |
Bir nesne kalıcı olarak havuzdan kaldırıldığında çağrılır |
ObjectRequestedForUsage |
Bir nesne havuzdan alındığında çağrılır (BeginPlay'e benzer) |
ReturnedToPool |
Bir nesne havuza geri döndüğünde çağrılır (Destroy'a benzer) |
CanBeRecycled |
Nesnenin geri dönüştürülebilir olup olmadığını döndürür |
- Tahmin edilebilir nesne türleri için havuzları önceden ısıtın (seviye başlangıcında)
- Havuzlanan nesneleri basit tutun - daha basit nesneler daha hızlı geri dönüştürülür
ObjectRequestedForUsageveReturnedToPooliçinde durumu düzgün şekilde sıfırlayın- Başlatma verisi geçirmek için
FObjectPoolPayloadiçindeki özel parametreleri kullanın - Oyununuzun ihtiyaçlarına göre soft cap'leri uygun şekilde yapılandırın
- Havuz yöneticisine erişmek için WorldSubsystem kullanın
- Havuzlama sorunlarını ayıklarken detaylı günlük kaydı ekleyin
Bu proje MIT Lisansı altında lisanslanmıştır - detaylar için LICENSE dosyasına bakın.