๐ Project Galaga ์์ํฉ๋๋ค.
Unreal C++๊ณผ ๊ฒ์์ํ์ ์ ๋ชฉํด์ ํ๋ก์ ํธ๋ฅผ ์งํํ์ต๋๋ค.
2024.05.04 ~ 2024.05.21์ ๊ฐ๋ฐ ๊ธฐ๊ฐ์ผ๋ก, ์ด 17์ผ ๋์ ๊ฐ๋ฐ์ ์๋ฃํ์์ต๋๋ค.
๊ฑฐ์ฐฝํ๊ฒ ์์ํ ํ๋ก์ ํธ๋ณด๋จ ์ง๊ธ๊น์ง ๋ฐฐ์ด ๊ฒ๋ค์ ์ข ํฉ์ ์ผ๋ก ์ฌ์ฉํด์ ๊ฒฐ๊ณผ๋ฌผ์ ๋ง๋ค์ด ๋ด๋ณด๊ฒ ๋ค๋ ๋ง์์ผ๋ก ์์ฑํ์์ต๋๋ค.
ํด๋น ์๋ฆฌ์ฆ์๋ ๊ฐ๋ฐ ์ผ์ง๋ฅผ ์ ๋ฆฌํ๋ฉฐ, ๊ฐ๋ฐ ๋ด์ฉ์ ๋ํ ์ค๋ช ๊ณผ ์ด์๋ฅผ ์ ๋ฆฌํ ์์ ์ ๋๋ค.
์ด๋ฒ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉฐ ๊ฐ์ฅ ํต์ฌ์ด ๋๋ ๊ตฌํ ์ฌํญ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.
๊ฒ์์ ํต์ฌ ๊ธฐ๋ฅ์ธ ์ด์ ์ํ์ ์ต์์ ๊ตฌํ๋ถ์ด๊ธฐ ๋๋ฌธ์ด์ฃ .
์ผ๋ฐ ์ ์กํฐ๋ค์ด ํ๋ฐ ๋๋ฐ ์ด์์ ์๋ ๊ฑด ํฌ๊ฒ ๋ฌธ์ ๊ฐ ๋์ง ์์ ๊ฒ๋๋ค.
ํ์ง๋ง ๋ณด์ค๊ฐ ๋์์ ํ์ฐํ (Spread)๋ก 0.01์ด๋ง๋ค ์ด์์ ์ํํด์ ๋ฟ๋ฆฐ๋ค๋ฉด ์ด๋จ๊น์?
์์์ด ์กํฐ๊ฐ ์์ฑ๋์๋ค ์๋ฉธํ๋ฉฐ ํผํฌ๋จผ์ค๊ฐ ๊ณ์ํด์ ๋จ์ด์ง๊ฒ ๋ ๊ฒ์ ๋๋ค.
ํด๋น ๋ฌธ์ ๋ฅผ ์ค์ด๊ธฐ ์ํด ์กํฐ๋ฅผ ๋ฏธ๋ฆฌ ์์ฑํด๋์๋ค๊ฐ ํ์ํ ๋ ๊ฐ์ ธ๋ค ์ธ ์ ์๋๋ก ๋ง๋ ๊ฒ์ด Pool์ด๋ผ๋ ๊ฐ๋ ์ ๋๋ค.
ํ๋ก์ ํธ์์ 2๊ฐ์ง์ Bullet Pool์ด ํ์ํ์ต๋๋ค.
Player Bullet Pool๊ณผ Enemy Bullet Pool์ ๋๋ค.
๋๊ฐ์ง์ ๊ตฌํ ๋ด์ฉ์ ๊ฐ์ผ๋, Player๊ฐ ํผ์ ์ฌ์ฉํ๋ Player Bullet Pool์ ์กํฐ ์ปดํฌ๋ํธ๋ก ๋ง๋ค์ด ์ฃผ์์ต๋๋ค.
ํ์ง๋ง Enemy Bullet Pool์ ๋ชจ๋ ์ ๋ค์ด ์ ๊ทผํ์ฌ ์ด์์ ๊ฐ์ ธ๋ค ์จ์ผ ํจ์ผ๋ก ์กํฐ๋ก ๋ง๋ค์ด ๋ ๋ฒจ์ ํ๋ ๋ฐฐ์นํด์ฃผ์์ต๋๋ค.
Pool์ ์ฌ์ฉ๋ ๋ณ์๋ถํฐ ํ๋์ฉ ์ค๋ช ํ๊ฒ ์ต๋๋ค.
์ด๋ค ์กํฐ๋ฅผ Poolingํ ์ง ์ง์ ํด์ฃผ์ด์ผ ํ๋ TSubclassOf๋ฅผ ์ฌ์ฉํ์ฌ Bullet์ ๋ฃ์ด ์ฃผ์์ต๋๋ค.
UPROPERTY(EditDefaultsOnly, Category = ObjectPool)
TSubclassOf<class APlayerBullet> PooledObjectSubclass;
์ค๋ธ์ ํธ ํ๋ง์ ๋จ์ ์ ๋ฏธ๋ฆฌ ์กํฐ๋ค์ ์์ฑํด๋๊ธฐ ๋๋ฌธ์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ก์ ๋จน๋๋ค๋ ๊ฒ์ ๋๋ค.
๊ทธ๋์ ๋ค๋ค์ต์ ์ด๋ผ๋ ์๊ฐ์ผ๋ก ์ด์์ ๋ง๊ตฌ์ก์ด๋ก 5์ฒ๊ฐ๋ฅผ ์์ฑํด๋๋๊ฒ ์คํ๋ ค ํด๊ฐ ๋ ์ ์์ต๋๋ค.
๊ทธ๋์ ํ๋ ์ด์ ์ง์ฅ์ด ์๊ธฐ์ง ์๋ ์ ๋นํ ์ ์์ Pool Size์ ๊ฒฐ์ ํด์ฃผ์ด์ผ ํฉ๋๋ค.
ํด๋น ํ๋ก์ ํธ์ ๊ฒฝ์ฐ Player Bullet Pool Size๋ 50, Enemy Bullet Pool Size๋ 200์ผ๋ก ์ค์ ํด์ฃผ์์ต๋๋ค.
UPROPERTY(EditAnywhere, Category = ObjectPool)
int PoolSize = 50;
ํ๋ง๋ ์ค๋ธ์ ํธ๊ฐ ๋ค์ ๋นํ์ฑํ๋๊ธฐ๊น์ง์ ์๋ช ์ฃผ๊ธฐ ์๊ฐ ๋ณ์๋ฅผ ์ ์ธํด์ฃผ์์ต๋๋ค.
UPROPERTY(EditAnywhere, Category = ObjectPool)
float PooledObjectLifeSpan = 1.0f;
ํ๋งํ ์ค๋ธ์ ํธ๋ฅผ ๋ด์์ ๋ณด๊ดํ ํฌ์ธํฐ ๋ฐฐ์ด์
๋๋ค.
ํด๋น ๋ฐฐ์ด์ด ์ค์ง์ ์ธ Pool์ด ๋ฉ๋๋ค.
UPROPERTY(EditAnywhere, Category = ObjectPool)
TArray<APlayerBullet*> ObjectPool;
ํ๋ง๋ ์ค๋ธ์ ํธ ์ค์์ ํ์ฑํ๋ ์ค๋ธ์ ํธ๋ฅผ ๋ณด๊ดํ๋ ๋ฐฐ์ด์ ๋๋ค.
์ค์ ๋ก ์ค๋ธ์ ํธ์ ์ฐธ์กฐํ ํ์๋ ์์ต๋๋ค.
๋ค๋ง ์ด๋ค ์ค๋ธ์ ํธ๊ฐ ํ์ฑํ ๋์๋์ง๋ง ํ์ธ์ ํ๋ฉด ๋๋ Pool์ Index๋ฅผ ์ ์ฅํ ๋ฐฐ์ด์ด๋ฉด ์ถฉ๋ถํฉ๋๋ค.
UPROPERTY(EditAnywhere, Category = ObjectPool)
TArray<int> SpawnedPoolIndexes;
์๋ช ์ฃผ๊ธฐ ์๊ฐ์ด ๋์ด๊ฐ๋ฉด ํด๋น ์ค๋ธ์ ํธ๋ฅผ ๋ค์ Pool์ ๋ฃ์ด์ ๋นํ์ฑํ ํด์ฃผ์ด์ผ ํฉ๋๋ค.
๊ทธ๋์ Despawn()ํจ์๋ฅผ ์ ์ธํด์ฃผ์์ต๋๋ค.
ํด๋น ํจ์๋ Delegate๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ ์ด์๋ณ๋ก ํ์ฑํ ์ํ๋ฅผ ๋ณ๊ฒฝํ๊ณ , SpawnedPoolIndexes ๋ฐฐ์ด์์ ์ ๊ฑฐํด์ฃผ๊ธฐ ์ํด์ ๋ฐ์ธ๋ฉํด์ฃผ์์ต๋๋ค.
UFUNCTION()
void OnPlayerPooledBulletDespawn(APlayerBullet* PoolActor);
Bullet Class๋ ์ด์ ์ ์ ๋ฐฉ ์ด๋ ํจ์์ ํผ๊ฒฉ ์ด๋ฒคํธ ํจ์์ ๋ํด์ ์ค๋ช ํ์ต๋๋ค.
์ด์ ๋ ํ๋ง์ ์ํ ๋ด์ฉ์ ์ถ๊ฐํด์ฃผ์ด์ผ ํฉ๋๋ค.
ํ์ฑํ ์ฌ๋ถ๋ฅผ ์๋ ค์ฃผ๋ ๋ณ์์ ๋๋ค.
UPROPERTY(EditAnywhere, Category = BulletStat)
bool Active = false;
์ผ๋ง๋ ํ์ฑํ ๋ ๊ฒ์ธ๊ฐ๋ฅผ ๊ฒฐ์ ํ๋ ์๋ช ์ฃผ๊ธฐ ๋ณ์์ ๋๋ค.
UPROPERTY(VisibleAnywhere, Category = BulletStat)
float LifeSpan = 0.0f;
์ด์ ์ Pool์๋ 2๊ฐ์ง ๋ณ์๊ฐ ์์์ต๋๋ค.
์์ฑ๋ ์์์ ๋ฐ๋ผ PoolIndex๋ฅผ ๋ฃ์ด์ ๊ด๋ฆฌํ๊ธฐ ์ํด ๋ณ์๋ฅผ ์ถ๊ฐํด์ฃผ์์ต๋๋ค.
UPROPERTY(VisibleAnywhere, Category = BulletStat)
int PoolIndex;
์๋ช ์ฃผ๊ธฐ ์๊ฐ์ ๋ฐ๋ผ ๋นํ์ฑํ ํจ์๋ฅผ ํธ์ถํ๊ธฐ ์ํ ํ์ด๋จธ ๋ณ์์ ๋๋ค.
FTimerHandle LifeSpanTimer;
๊ฐ์ฅ ์ค์ํ ๋ด์ฉ์ ๋๋ค.
Delegate์ ๋๋ค.
๋นํ์ฑํ ์ Delegate๋ฅผ ํตํด ๋นํ์ฑํ ๋ ์์ (this)๋ฅผ ๋๊ฒจ์ฃผ์ด SpawnedPoolIndexes ๋ฐฐ์ด์์ ์ ๊ฑฐํด์ฃผ์ด์ผ ํฉ๋๋ค.
๋ธ๋ฃจํ๋ฆฐํธ์์ ์ฌ์ฉํ์ง ์์์ง๋ง ํน์ฌ๋ํ์ฌ DYNAMIC์ ์ฌ์ฉํ์์ต๋๋ค.
๋ํ Pool Size ๊ฐ์๋งํผ์ ์ด์๋ค์ด Pool์๊ฒ ๋ฐ์ธ๋ฉ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ MULTICAST๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
๋ง์ง๋ง์ผ๋ก ์๊ธฐ ์์ (this)๋ฅผ ๋งค๊ฐ๋ณ์๋ก์ ๋๊ฒจ์ฃผ์ด์ผ ํจ์ผ๋ก OneParam์ ์ถ๊ฐํ์ฌ BulletClass๋ฅผ ํ์ ์ผ๋ก ๋๊ฒจ์ฃผ์์ต๋๋ค.
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlayerPooledBulletDespawn, APlayerBullet*, PoolActor);
FOnPlayerPooledBulletDespawn OnPlayerPooledBulletDespawn;
ํ๋ก๊ทธ๋จ์ด ์์๋๋ฉด BeginPlay()์์ ๋น์ด์๋ Pool์ Pool Size๋งํผ ์ค๋ธ์ ํธ๋ฅผ ์ฑ์ ๋ฃ์ด์ผ ํฉ๋๋ค.
๋ฐ๋ณต๋ฌธ์ผ๋ก ์ด์์ ์์ฑํ๊ณ , Bullet Class์ ์ ์ธํ ๋ณ์ ๊ฐ์ ์ด๊ธฐํํ๋ฉฐ Pool์ ๋ฃ์ด ์ค๋๋ค.
์ด๋ AddDynamic์ ์ฌ์ฉํ์ฌ ๊ฐ ์ด์๋ง๋ค Pool์ BulletDespawn()ํจ์์ ๋ฐ์ธ๋ฉํด์ค๋๋ค.
// Called when the game starts
void UPlayerBulletPool::BeginPlay()
{
Super::BeginPlay();
// ...
me = Cast<AMyPlayer>(GetOwner());
if (me == nullptr)
{
UE_LOG(LogTemp, Warning, TEXT("No me"));
}
if (PooledObjectSubclass != nullptr)
{
FActorSpawnParameters param;
param.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
for (int i = 0; i < PoolSize; i++)
{
APlayerBullet* PoolableActor = GetWorld()->SpawnActor<APlayerBullet>(PooledObjectSubclass, FVector(-2500,-2500,0), FRotator::ZeroRotator, param);
if (PoolableActor != nullptr)
{
PoolableActor->SetActive(false);
PoolableActor->SetPoolIndex(i);
PoolableActor->OnPlayerPooledBulletDespawn.AddDynamic(this, &UPlayerBulletPool::OnPlayerPooledBulletDespawn);
this->ObjectPool.Add(PoolableActor);
}
}
}
}
์ด์์ ์ฌ์ฉํ ์ฃผ์ฒด๋ Pool์ SpawnPooledObject() ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ์ด์์ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.
์ด๋ Pool์ ์ ์ชฝ Index๋ถํฐ ์ด์์ด ์๋์ง, ๋นํ์ฑํ์ธ์ง๋ฅผ ๊ฒ์ฌํฉ๋๋ค.
๋ ์กฐ๊ฑด์ ๋ง์กฑํ๋ ์ด์์ด ์๋ค๋ฉด ํด๋น ์ด์์ ํ์ฑํํ๊ณ ๋ฐํํด์ค๋๋ค.
AEnemyBullet* AEnemyBulletPool::SpawnPooledObject(FVector newLocation, FRotator newRotation)
{
for (AEnemyBullet* PoolableActor : ObjectPool)
{
if (PoolableActor != nullptr && !PoolableActor->IsActive())
{
PoolableActor->TeleportTo(newLocation, newRotation, false, true);
PoolableActor->SetLifeSpan(PooledObjectLifeSpan);
PoolableActor->SetActive(true);
SpawnedPoolIndexes.Add(PoolableActor->GetPoolIndex());
return PoolableActor;
}
}
// ์๋ต
return nullptr;
}
์ฌ๊ธฐ์ ๋ชจ๋ ์ด์์ด ์ด๋ฏธ ํ์ฑํ๋ ์ํ์ผ ์๋ ์์ต๋๋ค.
์์ Pool๋ด๋ถ์์ ํ์ฑํํ์ฌ ๋ฐํํด์ค ์ ์๋ ์ด์์ด ์๋ค๋ฉด ์๋ ๋ก์ง์ด ์คํ๋ฉ๋๋ค.
๊ฐ์ฅ ๋จผ์ ํ์ฑํ๋ ์ด์์ ๊ฐ์ ธ์์ ์ฌํ์ฑํํด์ฃผ๋ ๊ฒ์ ๋๋ค.
AEnemyBullet* AEnemyBulletPool::SpawnPooledObject(FVector newLocation, FRotator newRotation)
{
// ์๋ต
if (SpawnedPoolIndexes.Num() > 0)
{
int PoolObjectIndex = SpawnedPoolIndexes[0];
SpawnedPoolIndexes.Remove(PoolObjectIndex);
AEnemyBullet* PoolableActor = this->ObjectPool[PoolObjectIndex];
if (PoolableActor != nullptr)
{
PoolableActor->SetActive(false);
PoolableActor->TeleportTo(newLocation, newRotation, false, true);
PoolableActor->SetLifeSpan(PooledObjectLifeSpan);
PoolableActor->SetActive(true);
SpawnedPoolIndexes.Add(PoolableActor->GetPoolIndex());
return PoolableActor;
}
}
return nullptr;
}
ํด๋น ๋ก์ง์ ์ค์ํ๋, ์ ๋ ์คํ๋ ค ์ ํ๋ก์ ํธ์๋ ํ์์๋ ๊ธฐ๋ฅ์ด๋ผ ์๊ฐํ์์ต๋๋ค.
์๋๋ฉด ์ ์ด ๋ฐ์ฌํ ์ด์์ ๋๊น์ง ๊ฐ์ ํ๋ ์ด์ด์๊ฒ ์ํ์ด ๋์ด์ผ ํฉ๋๋ค.
ํ์ง๋ง ์ด์์ด ๋ ์๊ฐ๋ค ์ญํ์ฑํ๋์ด ๋ฒ๋ฆฐ๋ค๋ฉด ํ๋ ์ด์ด๋ "์ด? ์ด์์ด ์ ์ค๋ค๋ง์ง?" ๋ผ๋ ์๋ฌธ์ ๊ฐ์ง๊ฒ ๋๊ฒ ์ฃ .
๋ํ ๋์ผํ๊ฒ ํ๋ ์ด์ด์ ์ ์ฅ์์๋ "์ ์ด์์ด ๊ฐ๋ค๊ฐ ์ฌ๋ผ์ง์ง?"๋ผ๋ ์๊ฐ์ ํ ๊ฒ๋๋ค.
๊ทธ๋์ ํด๋น ๋ก์ง์ Player ๊ตฌํ๋ถ์๋ ์๋์ ์ผ๋ก ์ฃผ์์ฒ๋ฆฌ๋ฅผ ํด์ฃผ์์ต๋๋ค.
ํ์ฑํ๊ฐ ๋๊ฒ ๋๋ค๋ฉด Bullet Class์ SetActive()ํจ์๊ฐ ์คํ๋ฉ๋๋ค.
๋์์ LifeSpan๋งํผ ๋๊ธฐ ํ์ ๋นํ์ฑํ ํจ์๊ฐ ์์ฝ๋ฉ๋๋ค.
void APlayerBullet::SetActive(bool IsActive)
{
Active = IsActive;
SetActorHiddenInGame(!IsActive);
GetWorldTimerManager().SetTimer(LifeSpanTimer, this, &APlayerBullet::Deactivate, LifeSpan, false);
}
Timer๋ฅผ ํตํด ์๋ช ์ฃผ๊ธฐ๊ฐ ๋๋๋ฉด Deactivate()๊ฐ ์คํ๋ฉ๋๋ค.
ํ์ฑํ ์ํ๋ฅผ ๋นํ์ฑํ ์ํ๋ก ๋ณ๊ฒฝํด์ค๋๋ค.
๋ํ Delegate์ ๋ฐ์ธ๋ฉ๋ ํจ์๋ฅผ ์คํํด์ค๋๋ค.
void APlayerBullet::Deactivate()
{
SetActive(false);
SetActorLocation(FVector(-2500,-2500,0));
GetWorldTimerManager().ClearAllTimersForObject(this);
OnPlayerPooledBulletDespawn.Broadcast(this);
}
Delegate๋ฅผ ํตํด OnPlayerPooledBulletDespawn() ํจ์๊ฐ ํธ์ถ๋๋ฉด ํ์ฑํ ๋ชฉ๋ก ๋ฐฐ์ด์์ ์ ๊ฑฐํด์ค๋๋ค.
void UPlayerBulletPool::OnPlayerPooledBulletDespawn(APlayerBullet* PoolActor)
{
SpawnedPoolIndexes.Remove(PoolActor->GetPoolIndex());
}
์ ๊ณผ์ ์ ํตํด ์ค๋ธ์ ํธ ํ๋ง์ ๊ตฌํ๊ณผ ์ฌ์ฉ์ ๋ํด์ ์ ๋ฆฌํ์์ต๋๋ค.
์ฌ๊ธฐ๊น์ง๊ฐ ์ค๋ธ์ ํธ ํ๋ง์ ๋ํ ๊ตฌํ๋ด์ฉ ์ ๋ฆฌ์์ต๋๋ค.
์๊ฐ์ ์ธ ์ด๋ฏธ์ง๋ก ๋ณด์ฌ์ฃผ๊ธฐ ํ๋ ๋ด์ฉ์ด๋ผ ํ
์คํธ๋ง ๊ฐ๋ํด์ ์ฃ์กํฉ๋๋ค.