パーティクル特殊効果の基礎知識については、 「【UE4】特殊効果パーティクルシステム詳細解説(1) - 概要」 を参照してください。
1. 特殊効果プールは何のためにありますか?
パーティクルを使用してプールを説明してみましょう。
たとえば、射手であれば矢を射ることができ、地面 (メモリ) から枝を拾って弓と矢 (NewObject) を作ることができます。
- 矢筒がないと
、矢を射るたびに地面の枝を拾って弓矢を作る必要があり、非常に手間がかかるため効率が非常に悪いです。 - 背中に矢筒を持っている場合は
、地面の枝を拾って弓矢を作り、射った後、弓矢を拾って矢筒に差し込みます。矢筒の中に弓と矢が入っているので、取り出してください。ただ撃つだけです。作り直す必要はありません。
上記は私の個人的な理解です。質問がある場合は、話し合ってください
。
- その根拠は、弓矢は射られるたびに、やがて拾われるということであり、矢筒がなくなっていない限り(つまり、弓矢の寿命は矢筒によって完全に管理されるべきである)、矢筒は拾わないと意味がないので持ち歩かなければなりません。。
- 矢筒には大きさがあり、100本の矢を入れると101本目の矢が入る余地がなくなります(なぜ101本目の矢があるかというと、矢を射るたびに矢が届くわけではないからです)その矢を元に戻します。おそらく 1 回です。5 本の矢を放ち、さらに 3 本を放ち、しばらくしてから 8 本の矢を元に戻す必要があります。したがって、このプロセス中に、地面にある矢の数と地面にある矢の合計が計算されます。矢筒は矢筒内の矢の数を超える場合があります。容量があり、拾うときにいっぱいになっている場合は拾えません)
- 矢筒から矢を取り出して矢筒に戻すという作業は面倒ではないが、少なくとも地面から枝を拾って弓矢を作るよりは簡単でなければならない、そうしないと毎年新しい矢を手に入れるのはもったいない。時間。
1.1 特殊効果プールの使用目的
ParticleSystem (一般にパーティクル エフェクトとして知られている) のリリースではCreateParticleSystem
、最終的に以下に示すように関数が呼び出されます。
UParticleSystemComponent* CreateParticleSystem(
UParticleSystem* EmitterTemplate,
UWorld* World, AActor* Actor,
bool bAutoDestroy,
EPSCPoolMethod PoolingMethod)
{
//Defaulting to creating systems from a pool. Can be disabled via fx.ParticleSystemPool.Enable 0
UParticleSystemComponent* PSC = nullptr;
if (FApp::CanEverRender() && World && !World->IsNetMode(NM_DedicatedServer))
{
if (PoolingMethod != EPSCPoolMethod::None)
{
//If system is set to auto destroy the we should be safe to automatically allocate from a the world pool.
PSC = World->GetPSCPool().CreateWorldParticleSystem(EmitterTemplate, World, PoolingMethod);
}
else
{
PSC = NewObject<UParticleSystemComponent>((Actor ? Actor : (UObject*)World));
/// PSC->xxx = xx 等一些初始化操作 blablabla...
}
}
return PSC;
}
中核となるロジックは、PoolMethod が でない場合はNone
プールから取得され、 である場合はNone
となるNewObject
、つまり特殊効果がリリースされるたびに新しい効果が作成されるというものです。
NiagaraSystem (通称 Naigua 特殊効果) は同じで、インターフェイスはCreateNiagaraSystem
毎回 NewObject です。
注:両方の特殊効果が判定されることに注意してください
World && !World->IsNetMode(NM_DedicatedServer)
。つまり、特殊効果はサーバー上で作成されないため、サーバー上の特殊効果を気にする必要はありませんが、マウントされている ParticleSystemComponentNewObject
である場合は、
アクター (コードまたはブループリント リソースのいずれからのものか) を使用すると、コンポーネントがサーバー上に作成されます。
NewObjectは一連の操作を実行するため、確実にCPU(GameThread)を消費します(1つではないかもしれませんが、多数をサポートすることはできません)。そのため、キャッシュ用のプールを使用できる場合は、作成されません毎回新しいですが、プールから取得すると、CPU パフォーマンスが向上します (時間のためのスペースを使用)。
1.2 UE の特殊効果プール
ParticleSystem と NiagaraSystem は 2 つの完全に異なる特殊効果であるため、これら 2 つの特殊効果 (リリース、プーリングなど) のサポートは 2 つの完全に独立したコード セットですが、ロジックは一般的に似ています。
ParticleSystem のプールは と呼ばれFWorldPSCPool
、NiagaraSystem のプールは と呼ばれますUNiagaraComponentPool
。以下に詳しくまとめます。
2. 特殊効果プールの使用
ここでは、ParticleSystem の特殊効果プールの使用方法のみを記録します。最初に知っておく必要があるのは、すべての特殊効果リソースはコード内で 1 つであるということです。UParticleSystem*
すべての実際の効果 (炎、爆発、閃光、煙、パーティクルなど) はUParticleSystemComponent
によって実装されます。つまり、UParticleSystem はデータです。 UParticleSystemComponent はエンティティです。
たとえば、下を見ると 3 人が血を流しているのが見えた場合、3 つの UParticleSystemComponents が同時に再生されていますが、データソースとして同じ UParticleSystem が使用されていることがわかります。これを理解すると、次の説明が簡単になります。
特殊効果プールの目的は、同じデータ ソースから作成された複数のエンティティをキャッシュすることにより、血の効果が合計 1,000 回再生されたにもかかわらず、新しいオブジェクトが 3 つだけであるという効果を実現することです。
2.1 主要なデータ構造
詳細を参照してくださいEngine\Source\Runtime\Engine\Classes\Particles\WorldPSCPool.h
。
1.EPSCPoolメソッド
このエンジンは、合計 3 つのプーリング操作を提供します (実際、EPSCPoolMethod
列挙型には 5 つの値がありますが、注目に値するのは最初の 3 つだけです)。
None
つまり、プールには入れられず、毎回新しいプールが作成されます。AutoRelease
自動的にプールに割り当てられ、自動的にプールにリサイクルされます。ワンショットエフェクト(ワンショットfx)に適した特殊効果は、保存することを考える必要はなく(参考)、入れるだけで完了です。ただし、自動的にリサイクルされるため、この PSC のプロパティを変更する場合は安全ではない可能性があります (ユーザーがインターフェイスの戻り値を受け取るかどうかが不明であるため、この値はデフォルトでは指定できません)。特殊効果の解除と実行される操作)。ManualRelease
リサイクルするには ReleaseToPool を手動で呼び出す必要があります (このオプションは無効です)。これは、自分で制御する必要がある「永続的な」特殊効果AutoDestroy
に適しています(この種の特殊効果は手動でリサイクルする必要があり、そうしないとメモリ リークが発生するためです)。したがって、これをデフォルト値にすることはできません)。
要約すると、エンジンはデフォルトでは特殊効果をプールに追加しませんが、それを使用したい場合は、SpawnEmitter インターフェイスのデフォルトのパラメータを必要なものに変更するだけで済みます (一度だけ使用することをお勧めします)爆発特殊効果などの効果; 使用;AutoRelease
永続性 体に燃える炎効果などのバフのような効果を使用し、ManualRelease
炎の時間が経過して燃焼効果が消えたら手動で使用しますReleaseToPool
)。
特殊効果プールは非常に単純です。各パーティクル特殊効果 ( UParticleSystem*
、つまり特殊効果リソース) は配列 ( TArray<FPSCPoolElem> FreeElements
) に対応します。この構造も次のように非常に単純です。
USTRUCT()
struct FPSCPoolElem
{
GENERATED_BODY()
UPROPERTY(transient)
UParticleSystemComponent* PSC;
float LastUsedTime;
// 还有两个构造函数
};
2.FWorldPSCPool
USTRUCT()
struct ENGINE_API FWorldPSCPool
{
GENERATED_BODY()
private:
UPROPERTY()
TMap<UParticleSystem*, FPSCPool> WorldParticleSystemPools;
float LastParticleSytemPoolCleanTime;
/** Cached world time last tick just to avoid us needing the world when reclaiming systems. */
float CachedWorldTime;
public:
FWorldPSCPool();
~FWorldPSCPool();
void Cleanup();
UParticleSystemComponent* CreateWorldParticleSystem(UParticleSystem* Template, UWorld* World, EPSCPoolMethod PoolingMethod);
/** Called when an in-use particle component is finished and wishes to be returned to the pool. */
void ReclaimWorldParticleSystem(UParticleSystemComponent* PSC);
/** Call if you want to halt & reclaim all active particle systems and return them to their respective pools. */
void ReclaimActiveParticleSystems();
/** Dumps the current state of the pool to the log. */
void Dump();
};
UE は、デフォルトでFWorldPSCPoolと呼ばれるパーティクル特殊効果のプールを提供します (ナイアガラのプールはUNiagaraComponentPoolと呼ばれます)。ただし、 UGameplayStatics でパーティクル特殊効果を解放するためのインターフェイスを使用する場合 (かどうかに関係なくSpawnEmitterAtLocation
) SpawnEmitterAttached
、特殊効果はプールに入れられません。 (つまりEPSCPoolMethod::None
、エンジンがどのようなデフォルト ロジックを与えるべきかを認識していないことが原因である可能性があります)。
FWorldPSCPoolのライフサイクルは World によって管理されると考えることができ、World には次の変数があります。
UPROPERTY()
FWorldPSCPool PSCPool;
World では、特殊効果プールをクリーンアップするUWorld::CleanupWorldInternal
ために呼び出されます。 World が破棄されると、デストラクターが呼び出されて実行されます。 のときに呼び出されます。PSCPool.Cleanup()
FWorldPSCPool
Cleanup()
CreateParticleSystem
World->GetPSCPool().CreateWorldParticleSystem
FWorldPSCPoolで最も重要なことはTMap<UParticleSystem*, FPSCPool> WorldParticleSystemPools;
、これが、リリースされたすべての特殊効果データ ソース (UParticleSystem*) と、対応する作成されたエンティティ (UParticleSystemComponent*) の配列を格納するマップであるということです。
なぜ配列なのかというと、前述した血を生み出す 3 本の剣など、それぞれが別個のコンポーネントである多くの特殊効果を同時に再生する必要があるためです。
3.FPSCプール
FWorldPSCPool::CreateWorldParticleSystem
この特殊効果をキーとして使用して、WorldParticleSystemPools からこの特殊効果の小さなプールを見つけ、そこから利用可能なエンティティを見つけます。
FPSCPool& PSCPool = WorldParticleSystemPools.FindOrAdd(Template);
PSC = PSCPool.Acquire(World, Template, PoolingMethod);
USTRUCT()
struct FPSCPool
{
GENERATED_BODY()
//Collection of all currently allocated, free items ready to be grabbed for use.
//TODO: Change this to a FIFO queue to get better usage. May need to make this whole class behave similar to TCircularQueue.
UPROPERTY(transient)
TArray<FPSCPoolElem> FreeElements;
//Array of currently in flight components that will auto release.
UPROPERTY(transient)
TArray<UParticleSystemComponent*> InUseComponents_Auto;
//Array of currently in flight components that need manual release.
UPROPERTY(transient)
TArray<UParticleSystemComponent*> InUseComponents_Manual;
/** Keeping track of max in flight systems to help inform any future pre-population we do. */
int32 MaxUsed;
public:
FPSCPool();
void Cleanup();
/** Gets a PSC from the pool ready for use. */
UParticleSystemComponent* Acquire(UWorld* World, UParticleSystem* Template, EPSCPoolMethod PoolingMethod);
/** Returns a PSC to the pool. */
void Reclaim(UParticleSystemComponent* PSC, const float CurrentTimeSeconds);
/** Kills any components that have not been used since the passed KillTime. */
void KillUnusedComponents(float KillTime, UParticleSystem* Template);
int32 NumComponents() {
return FreeElements.Num(); }
};
主要なメンバー変数と関数:
FreeElements
- この特殊効果に使用できるエンティティ コンポーネントを格納します。現在使用されているコンポーネントはここになく、プールにリサイクルされていません。InUseComponents_Auto
,InUseComponents_Manual
- 放置しても問題ありませんが、デバッグに使用することも考えられます (ENABLE_PSC_POOL_DEBUGGING
)MaxUsed
- 最大で何個くらい使われますか?Acquire()
- 独自の配列から利用可能なコンポーネントを抽出するために使用されるメソッドReclaim()
- プールに戻す方法
4.FPSCプールエレム
TArray<FPSCPoolElem> FreeElements;
配列に格納されるのは、このデータ ソースによって作成された各エンティティです。
特殊効果リソースで設定される配列のサイズには制限がありますMaxPoolSize
(詳細についてはコードを参照してくださいFPSCPool::Reclaim
。その場合FreeElements.Num() < (int32)PSC->Template->MaxPoolSize
、このコンポーネントはリサイクルされず、直接 DestroyComponent になります)。
USTRUCT()
struct FPSCPoolElem
{
GENERATED_BODY()
UPROPERTY(transient)
UParticleSystemComponent* PSC;
float LastUsedTime;
// 两个构造函数
};
FPSCPoolElem には、エンティティ (PSC) と、このエンティティが最後に使用された時刻 (LastusedTime) のみが含まれます。これは、タイムアウトの解消などに使用されます (詳細を参照FPSCPool::KillUnusedComponents
)。
2.2 主要なプロセス
2.2.1 特殊効果の再生/プールからの取得
2.2.2 特殊効果を終了/プールに戻す
定期清掃機能もあるので、プールに戻すプロセスは少し面倒です。
void FWorldPSCPool::ReclaimWorldParticleSystem(UParticleSystemComponent* PSC)
{
// Check blablabla
if (GbEnableParticleSystemPooling)
{
float CurrentTime = PSC->GetWorld()->GetTimeSeconds();
//Periodically clear up the pools.
if (CurrentTime - LastParticleSytemPoolCleanTime > GParticleSystemPoolingCleanTime)
{
LastParticleSytemPoolCleanTime = CurrentTime;
for (TPair<UParticleSystem*, FPSCPool>& Pair : WorldParticleSystemPools)
{
Pair.Value.KillUnusedComponents(CurrentTime - GParticleSystemPoolKillUnusedTime, PSC->Template);
}
}
// Check blablabla
PSCPool->Reclaim(PSC, CurrentTime);
}
else
{
PSC->DestroyComponent();
}
}
コンポーネントがリサイクルされるたびに、最後にクリーンアップされてからどれくらいの時間が経過したかが判断され、それを超える場合(GParticleSystemPoolingCleanTime
デフォルト値は 30.f、つまり 30 秒)、すべての要素がクリーンアップされます(デフォルト値WorldParticleSystemPools
は30 秒ではありません)。現在のもののみ) この特殊効果のキャッシュ)、プロセスが 2.2.1 のプロセスと似ている点が異なります。
2.3 特殊効果プールのデータを表示する
エディターのコマンド ライン ウィンドウで と入力すると、fx.DumpPSCPoolInfo
出力ウィンドウに現在のプール サイズと、各 PS での空きプールの数と使用中のプールの数が表示されます。
上の図からわかるように、現在のプールは合計 0.7 MB のメモリを占有しており、各特殊効果リソース (ParticleSystem) は対応するデータを出力します。
Free
- 現在のプールで使用可能なコンポーネント エンティティUsed
- 現在使用中のComponentエンティティ(AutoとManualはリリース時に設定したプーリング方法に対応します)MaxUsed
- 同時に一緒に使用されるコンポーネント エンティティの最大数 (つまり、FreeElements
配列のサイズ)System
- 特殊効果リソースへのパス
文句を言いますが、出力の最初の行を別の行に変更すべきではないでしょうか。。。。。。とても不快に見えます。。
そして、NewObjectが何回削減されたかを見ることはできません
3. 特殊効果プールで注意が必要な問題
3.1 ライフサイクル管理
FPSCPool::Acquire
中間のステップではRetElem.PSC->Rename(nullptr, World, REN_ForceNoResetLoaders);
、コンポーネントOwnerPrivate
(GwtOwner()) をワールドに設定します。公式コメントは次のとおりです。
PSC の名前を変更して、現在の PersistentLevel に移動します。PSC は、あるレベルで生成された可能性がありますが、現在は別のレベルで必要になっています。
これは、この特殊効果コンポーネントを 1 つのレベルで作成し、それを別のレベルで使用することを防ぐためであり、その結果、コンポーネントはすべてワールド内に存在しますPSCPool
。
ただし、特殊効果のフレーム (つまり、ティックの DeltaTime は に関連していますOwnerPrivate
。詳細を参照FActorComponentTickFunction::ExecuteTickHelper
) のため、特殊効果の速度を文字 A と一致させたい場合は、文字 A にPSC->SpawnedParticle->Rename(nullptr, Actor);
設定する必要があります。 OwnerPrivate
A.
これにより、キャラクター A が破壊されると、その上のコンポーネントも破壊され、FPSCPool::Acquire
チェックインがトリガーされます。
check(!RetElem.PSC->IsPendingKill());
考えられる解決策は次のとおりです。
- この
ManualRelease
方法であれば直接Renameできますが、ReleaseToPool
その前にRenameで現在のワールドに戻る必要があります。 - この
AutoRelease
方法の場合は、別の方法でフレームを変更する必要があります。
3.2 リセット
リサイクルおよび再利用のメカニズムを設計するときは常に、リセットの必要性を避けることはできません。しかし
要約すると、プールを使用する必要がある場合は、戻り値 PSC に対して何もしないでください。
もう一度文句を言いますが、Owner を変更する場合、SetOwner のような関数はなく、Rename が使用されます。。奇妙に感じますが、SetOwner を実行できるようにしたくないのかもしれません。