Poglavlje 36: Audio, Physics i Gameplay Performance

Poglavlje 36: Audio, Physics i Gameplay Performance


Zasto ovo poglavlje postoji

Ako ste pratili prethodna poglavlja, verovatno ste stekli utisak da je performans u igrama pre svega pitanje renderinga. I nije cudno -- GPU, shaderi, draw call-ovi, LOD sistemi, occlusion culling -- sve to zauzima ogroman deo diskusije o optimizaciji. Ali evo jedne neprijatne istine koju svaki iskusan developer zna:

Rendering nije sve.

Mozete imati scenu koja renderuje 200 FPS na najjacoj grafickoj kartici, a da vam igra i dalje "stuca" -- jer vam physics simulacija guta CPU, jer imate 500 zvukova koji se istovremeno procesiraju, jer hiljade aktora tikuje svaki frejm bez potrebe, ili jer Garbage Collector odluci da napravi pauzu od 30 milisekundi bas u najgorem momentu.

Ovo poglavlje pokriva sve ono sto nije rendering, a moze vas unistiti performansno. Govoricemo o:

Hajde da krenemo.


36.1 Audio Performance

Zasto je audio uopste performansno pitanje?

Vecina pocetnika razmislja o zvuku ovako: "Pustim WAV fajl, on svira, gotovo." Ali u praksi, audio engine u UE5 radi mnogo vise od toga:

Sve ovo kosta CPU vreme, i to moze postati znacajno.

36.1.1 Sound Playback Cost -- Voice Count

Najosnovniji parametar audio performansa je voice count -- koliko zvukova istovremeno svira.

Svaki aktivan zvuk u UE5 predstavlja jedan voice (glas). Engine mora za svaki voice da:

  1. Dekodira audio podatke (ili cita iz memorije ako su vec dekodirani)
  2. Primeni pitch, volume i druge parametre
  3. Izracuna spatialization (gde se zvuk nalazi u 3D prostoru)
  4. Primeni efekte (reverb, EQ, itd.)
  5. Miksa ga u finalni output

Cena po voice-u nije ogromna, ali skalira linearno. Deset zvukova je jeftino. Sto zvukova je primetno. Trista zvukova moze biti problem.

Max Concurrent Sounds

UE5 ima podesavanje Max Concurrent Sounds (u Project Settings > Audio) koje ogranicava koliko zvukova moze istovremeno da svira. Default vrednost je tipicno 32.

Project Settings > Engine > Audio > Max Channels

Ovo znaci: bez obzira koliko SoundCue-ova ili MetaSounds instanci pokrenete, engine ce aktivno procesirati najvise toliko zvukova. Ostali se ili stavljaju u red cekanja (virtuelni zvukovi) ili se jednostavno ne pustaju.

Prakticni saveti:

Platforma Preporuceni Max Channels
PC (high-end) 64-128
PC (mid-range) 32-64
Konzole (PS5/Xbox Series X) 48-96
Mobilni uredjaji 16-24

Virtuelni zvukovi (virtual voices) su kljucan koncept -- UE5 moze da "prati" mnogo vise zvukova nego sto zapravo procesira. Virtuelni zvuk pamti svoju poziciju, volume i stanje, ali ne prolazi kroz audio procesiranje. Kada postane dovoljno glasan ili blizak igracu, engine ga promovise u pravi voice.

// U C++-u, mozete podesiti Sound Concurrency za specifican zvuk
USoundConcurrency* Concurrency = NewObject<USoundConcurrency>();
Concurrency->MaxCount = 4; // Maksimalno 4 instance ovog zvuka
Concurrency->ResolutionRule = EMaxConcurrentResolutionRule::StopLowestPriority;

36.1.2 Audio Occlusion -- Raycast Cost

Audio occlusion je sistem koji simulira kako zvuk biva blokiran fizickim objektima. Zamislite da ste u jednoj sobi, a eksplozija se desava u susednoj -- zvuk treba da bude prigusen i filtriran jer prolazi kroz zid.

Da bi ovo izracunao, engine mora da baca zrake (raycast) izmedju izvora zvuka i slusaoca (kamere/igraca):

[Izvor zvuka] ----raycast----> [Igrac/Listener]
                  ^
                  |
          Da li nesto blokira put?

Problem je sto ovo mora da se radi za svaki aktivan zvuk i to svaki frejm (ili u redovnim intervalima). Ako imate 50 aktivnih zvukova sa occlusion-om, to je 50 raycast-ova po frejmu, minimum.

Dodatno, napredni occlusion sistemi mogu da bacaju vise zraka po izvoru (na primer, 5-10 zraka u razlicitim pravcima) da bi procenili koliko je zvuk delimicno okluziran.

Cena raycast-a zavisi od:

Optimizacione strategije:

  1. Ogranicite occlusion samo na bitne zvukove -- nema smisla racunati okluziju za ambijentalni vetar
  2. Smanjite frekvenciju provere -- umesto svakog frejma, proveravajte svakih 3-5 frejmova
  3. Koristite Audio Volume-e umesto raycast-a gde je moguce -- rucno definisani volumeni su mnogo jeftiniji
  4. Ogranicite domet okluzije -- zvukovi dalje od odredjene distance ne trebaju okluziju
// Primer: Podesavanje occlusion intervala
UAudioComponent* AudioComp = /* ... */;
AudioComp->OcclusionCheckInterval = 0.2f; // Provera svakih 200ms umesto svakog frejma

36.1.3 Reverb i Spatial Audio Processing

Reverb (odjek) je efekat koji simulira kako se zvuk odbija od povrsina u zatvorenom prostoru. Mala soba ima kratak, oster reverb. Velika katedrala ima dug, difuzan reverb.

U UE5, reverb se tipicno primenjuje na dva nacina:

  1. Audio Volumes sa Reverb Effect-om -- najjeftiniji pristup, postavite volume u level i dodelite mu reverb preset
  2. Convolution Reverb -- koristi snimljeni impulse response (IR) realnog prostora. Mnogo realisticniji, ali i mnogo skuplji jer zahteva konvoluciju audio signala sa IR-om

Convolution reverb je posebno skup jer zahteva matematicku operaciju zvanu konvolucija koja je O(n*m) gde je n duzina audio buffer-a a m duzina impulse response-a. Za dugacke IR-ove (2-3 sekunde reverb-a), ovo moze biti znacajno.

Spatialization (prostorni zvuk) takodje kosta:

Cena spatialization-a (od najjeftinijeg do najskupljeg):

1. Stereo panning         -- trivijalno
2. Surround panning (5.1) -- jeftino
3. HRTF spatialization    -- umereno
4. Ambisonics (VR)        -- skupo

36.1.4 MetaSounds -- Proceduralni Audio

MetaSounds je UE5-ov sistem za proceduralni audio -- umesto da pustite unapred snimljen zvuk, vi generisete zvuk u realnom vremenu koristeci graf cvorova (node graph), slicno Blueprint-u.

MetaSounds je neverovatno mocan:

Ali ta moc dolazi sa cenom: compute cost.

Svaki MetaSounds graf koji se izvrsava u realnom vremenu trosira CPU cikluse. Sto je graf kompleksniji (vise oscilatora, filtera, envelope-a, modulacija), to je veci trosak.

Kljucni faktori cene MetaSounds grafa:

Element Relativna cena
Jednostavan oscillator (sine, saw) Niska
Filter (low-pass, high-pass) Niska-umerena
Delay line Umerena
Granular synthesis Visoka
FFT-based processing Vrlo visoka
Mnogo modulacija i feedback petlji Visoka (akumulativno)

Prakticni savet: Koristite MetaSounds za zvukove koji zaista trebaju proceduralnost (motor vozila, interaktivna muzika), a za obicne zvukove (koraci, pucnjevi) koristite klasicne SoundCue-ove ili obicne WAV fajlove.

// Pseudokod: MetaSounds graf za motor vozila
Input: RPM (float), Throttle (float), Load (float)

Oscillator1(Frequency = RPM * 0.5) --> Filter(Cutoff = Throttle * 2000)
Oscillator2(Frequency = RPM * 1.0) --> Filter(Cutoff = Load * 3000)
Noise() --> Filter(Cutoff = RPM * 0.3) --> Gain(Throttle * 0.1)

Mix(Osc1, Osc2, Noise) --> Output

36.1.5 Audio LOD -- Level of Detail za Zvuk

Bas kao sto koristimo vizuelni LOD (manje poligona za udaljene objekte), mozemo koristiti Audio LOD da smanjimo trosak zvuka za udaljene izvore.

Audio LOD strategije:

  1. Distance-based attenuation -- zvuk se gasi sa rastojanjem. Kada padne ispod praga cujnosti, engine ga prebacuje u virtualni voice (ne procesira ga)

  2. Smanjenje kvaliteta spatialization-a -- za daleke zvukove koristite jednostavan stereo panning umesto HRTF

  3. Iskljucivanje occlusion-a za daleke zvukove -- ionako se jedva cuju

  4. Smanjenje sample rate-a za daleke zvukove -- umesto 48kHz, koristite 22kHz ili 11kHz

  5. Pojednostavljivanje MetaSounds grafova -- za daleke instance, koristite jednostavniju verziju grafa

// Primer: Audio LOD na osnovu distance
void AMyActor::UpdateAudioLOD(float DistanceToListener)
{
    if (DistanceToListener > 5000.0f)
    {
        // Daleko - minimalan audio procesiranje
        AudioComponent->SetIntParameter("AudioLOD", 2);
        AudioComponent->bEnableOcclusion = false;
    }
    else if (DistanceToListener > 2000.0f)
    {
        // Srednje rastojanje
        AudioComponent->SetIntParameter("AudioLOD", 1);
        AudioComponent->bEnableOcclusion = true;
        AudioComponent->OcclusionCheckInterval = 0.5f;
    }
    else
    {
        // Blizu - pun kvalitet
        AudioComponent->SetIntParameter("AudioLOD", 0);
        AudioComponent->bEnableOcclusion = true;
        AudioComponent->OcclusionCheckInterval = 0.1f;
    }
}

36.1.6 Audio Thread i Interakcija sa Game Thread-om

UE5 pokrece audio procesiranje na zasebnom thread-u (Audio Thread). To znaci da audio miksovanje i procesiranje ne blokiraju direktno Game Thread.

Ali interakcija izmedju ova dva thread-a i dalje postoji:

Game Thread                    Audio Thread
    |                              |
    |-- "Pusti zvuk eksplozije" -->|
    |-- "Promeni volume" -------->|
    |-- "Zaustavi zvuk" --------->|
    |                              |-- dekodiranje
    |                              |-- spatialization
    |                              |-- miksovanje
    |                              |-- output
    |<-- "Zvuk zavrsen" ----------|

Problemi koji mogu nastati:

  1. Previse komandi iz Game Thread-a -- ako svaki frejm saljete stotine audio komandi (play, stop, set parameter), to stvara overhead na Game Thread-u jer svaka komanda mora da se pakuje i salje

  2. Audio Thread preopterecen -- ako Audio Thread ne moze da zavrsi procesiranje pre nego sto treba da isporuci sledeci audio buffer, doceice do audio glitch-eva (pucketanje, preskakanje)

  3. Sinhronizacija -- neki audio dogadjaji moraju da budu sinhroni sa gameplay-em (na primer, zvuk koraka u ritam igri), sto zahteva pazljivu koordinaciju

Dijagnostika u UE5:

Koristite komandu stat Audio u konzoli da vidite:

// U konzoli igre
stat Audio
stat SoundCues
stat SoundWaves

Ako Audio Thread vreme prelazi vreme trajanja jednog audio buffer-a (tipicno 5-10ms za buffer od 256-512 semplova na 48kHz), imacete audio artefakte.


36.2 Physics Performance

Physics je cesto najskuplji non-rendering sistem u igri. Simulacija fizike zahteva kompleksne matematicke proracune koji se izvrsavaju svakog frejma, a troskovi mogu eksplodirati ako niste pazljivi.

UE5 koristi Chaos Physics engine (nasledjujuci PhysX u ranijim verzijama), koji je dizajniran za napredne simulacije ali takodje donosi nove performansne izazove.

36.2.1 Collision Detection Complexity

Kolizija (collision detection) je proces utvrdjivanja da li se dva objekta dodiruju ili presecaju. Ovo je fundamentalna operacija za fiziku, ali njena cena drasticno varira u zavisnosti od oblika kolizije koji koristite.

Simple Shapes -- Jeftino

Najjednostavniji i najjeftiniji oblici kolizije su primitivni geometrijski oblici:

Oblik Opis Relativna cena
Sphere (lopta) Najjeftiniji -- samo provera rastojanja izmedju centara 1x (bazna linija)
Box (kutija, AABB/OBB) Provera preklapanja po osama 1-2x
Capsule (kapsula) Cylinder sa zaobljenim krajevima, idealno za karaktere 1-2x

Provera kolizije izmedju dve sfere je bukvalno jedno oduzimanje vektora i jedno poredjenje:

// Kolizija dve sfere - neverovatno jeftino
bool SpheresCollide(FVector Center1, float Radius1, FVector Center2, float Radius2)
{
    float DistSquared = FVector::DistSquared(Center1, Center2);
    float RadiiSum = Radius1 + Radius2;
    return DistSquared <= (RadiiSum * RadiiSum);
}

Ovo je razlog zasto se za broad phase collision detection (prva faza provere) uvek koriste jednostavni oblici.

Convex Hull -- Umerena Cena

Convex hull je najmanji konveksni oblik koji obuhvata mesh. Zamislite da obmotate gumenu foliju oko objekta -- to je convex hull.

Vazno: UE5 dozvoljava "compound collision" -- vise konveksnih oblika kombinovanih da aproksimiraju kompleksan oblik. Na primer, sto moze imati 4 convex hull-a (jedan za povrsinu, cetiri za noge).

Jedan Convex Hull:        Compound (4 Convex Hulls):
   _________                 _________
  /         \               |_________|
 /           \              |  |   |  |
|             |             |  |   |  |
|_____________|             |__|   |__|

Jednostavniji,            Precizniji,
ali manje precizan         ali 4x vise provera

Triangle Mesh Collision -- Skupo

Triangle mesh collision (takodje zvana "complex collision") koristi stvarnu geometriju mesh-a za koliziju. Svaki trougao mesh-a postaje potencijalna koliziona povrsina.

Zasto je ovo skupo:

  1. Mesh sa 10.000 trouglova zahteva potencijalno 10.000 provera po kolizionom paru
  2. Cak i sa prostornim strukturama podataka (BVH -- Bounding Volume Hierarchy), cena je znacajno veca od konveksnih oblika
  3. Ne moze se koristiti za dinamicke (pokretne) objekte u simulaciji -- samo za staticnu geometriju (tereni, zidovi)
                          Cena kolizije
                          
Triangle Mesh  ████████████████████████████████  100x+
Convex Hull    ████████████                       10-20x
Capsule        ██                                  2x
Box            █                                   1-2x
Sphere         █                                   1x (bazna)

Zlatno pravilo: Koristite najjednostavniji kolizioni oblik koji daje prihvatljiv rezultat. Ne trebaju vam triangle mesh kolizije za metak -- sfera je sasvim dovoljna.

// U UE5, za Static Mesh Component mozete podesiti tip kolizije
UStaticMeshComponent* MeshComp = CreateDefaultSubobject<UStaticMeshComponent>("Mesh");

// DOBRO - koristite jednostavnu koliziju
MeshComp->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
MeshComp->BodyInstance.SetCollisionProfileName("BlockAll");
// Engine ce koristiti Simple Collision (auto-generisan box/convex) ako postoji

// LOSA PRAKSA za dinamicke objekte:
// MeshComp->bUseComplexAsSimpleCollision = true; // Triangle mesh za SVE - SKUPO!

36.2.2 Rigid Body Simulation Cost

Rigid body simulacija je proces izracunavanja kako se kruta tela krecu, rotiraju i medjusobno interaguju pod uticajem sila (gravitacija, impulsi, kontakti).

Cena simulacije zavisi od broja aktivnih tela. Kljucna rec je "aktivnih" -- UE5 (Chaos Physics) automatski stavlja tela u sleep stanje kada se dovoljno smire (prestanu da se krecu). Telo u sleep stanju ne kosta skoro nista.

Stanje tela:

ACTIVE (budno)     -- simulira se svaki frejm         -- KOSTA
SLEEPING (spava)   -- ne simulira se dok ga neko ne probudi -- BESPLATNO

Problem nastaje kada imate mnogo aktivnih tela:

Broj aktivnih tela Tipicna cena (ms) Situacija
10-50 < 0.5ms Normalan gameplay
50-200 0.5-2ms Akciona scena
200-500 2-5ms Velika destrukcija
500-1000 5-15ms Problematicno
1000+ 15ms+ Neodrzivo bez optimizacije

Tipicni problemi:

  1. Chain reaction -- jedan objekat udari u gomilu objekata, svi se probude, svi pocinju da interaguju. Broj aktivnih tela eksplodira.

  2. Nikad ne zaspe -- objekti koji se stalno pomeraju (na primer, na pokretnoj traci ili u blizini vibrirajuceg objekta) nikad ne ulaze u sleep. Resenje: podesite SleepThreshold na visu vrednost ili ih forsirajte u sleep.

  3. Jittering -- objekti koji bi trebalo da miruju ali se stalno blago pomeraju, drzeci se budnim. Ovo se desava kada su kontaktni solver-i nedovoljno precizni.

// Forsiranje sleep-a za fizicko telo
UPrimitiveComponent* PhysComp = /* ... */;
PhysComp->PutAllRigidBodiesToSleep();

// Podesavanje sleep threshold-a
FBodyInstance* BodyInst = PhysComp->GetBodyInstance();
if (BodyInst)
{
    BodyInst->SleepFamily = ESleepFamily::Sensitive; // Lakse zaspi
}

36.2.3 Collision Pair Count -- N-Body Problem

Kada imate N tela u sceni, potencijalni broj kolizionih parova je N*(N-1)/2. Za 100 tela, to je 4,950 mogucih parova. Za 1000 tela, to je skoro pola miliona.

Ocigledno, proveravati svaki par bi bilo suludo. Zato physics engine koristi dvofazni pristup:

Broad Phase

Broad phase brzo elimise parove koji sigurno ne mogu da se sudare. Koristi jednostavne bounding volume-e (AABB -- Axis-Aligned Bounding Box) i prostorne strukture podataka.

Chaos Physics u UE5 koristi prostornu hash mapu ili BVH stablo za broad phase. Rezultat je lista potencijalnih kolizionih parova -- znatno manja od ukupnog broja mogucih parova.

1000 tela u sceni
    |
    v
Broad Phase: AABB provere, prostorno hashiranje
    |
    v
~200 potencijalnih parova (umesto ~500,000)
    |
    v
Narrow Phase: precizne kolizione provere
    |
    v
~30 stvarnih kolizija

Narrow Phase

Za parove koji su prosli broad phase, engine radi detaljnu koliziju koristeci stvarne kolizione oblike (sfere, convex hull-ove, itd.). Ovo je skuplji korak, ali se radi na mnogo manjem broju parova.

Kako smanjiti collision pair count:

  1. Collision Channels -- ne moraju svi objekti da interaguju sa svim drugim objektima. Metak ne treba da kolizira sa drugim metcima. Koristite collision channel-e da iskljucite nepotrebne provere.

  2. Collision Presets -- UE5 ima unapred definisane kolizione profile (BlockAll, OverlapAll, BlockAllDynamic, itd.)

  3. Smanjite broj fizickih tela -- spojite male objekte u vece, koristite instanced static mesh gde je moguce

// Primer: Podesavanje collision channel-a da metak ignorise druge metke
void AProjectile::SetupCollision()
{
    CollisionComponent->SetCollisionObjectType(ECC_GameTraceChannel1); // Custom "Projectile" channel
    CollisionComponent->SetCollisionResponseToChannel(ECC_GameTraceChannel1, ECR_Ignore); // Ignorisi druge projektile
    CollisionComponent->SetCollisionResponseToChannel(ECC_Pawn, ECR_Block); // Blokiraj pawn-ove
    CollisionComponent->SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Block); // Blokiraj staticnu geometriju
}

36.2.4 Physics Substeps

Standardno, physics simulacija se izvrsava jednom po frejmu. Ali ovo moze dovesti do problema:

Physics substeps resavaju ovo tako sto izvrsavaju fiziku vise puta po frejmu, sa manjim vremenskim korakom.

Bez substeps-a (1 iteracija po frejmu na 60 FPS):
  Delta time = 16.67ms per step

Sa 4 substeps-a po frejmu:
  Delta time = 4.17ms per step
  = 4x preciznija simulacija
  = 4x skuplja simulacija

Kada koristiti substeps:

Podesavanje u UE5:

Project Settings > Physics > Substepping

bSubstepping = true
MaxSubstepDeltaTime = 0.008333  // Maksimalni delta time jednog substep-a (120Hz)
MaxSubsteps = 4                  // Maksimalan broj substepova po frejmu

Upozorenje: Substeps su skupi. Ako vec imate performansne probleme sa fizikom, dodavanje substeps-a ce ih pogorsati. Koristite ih samo kada su zaista potrebni, i samo za objekte koji ih zahtevaju.

36.2.5 Ragdoll Physics Cost

Ragdoll je sistem koji simulira ljudsko telo kao niz rigid body-ja povezanih zglobovima (constraints). Kada lik "umre" u igri, prelazi iz animiranog stanja u ragdoll stanje.

Zasto je ragdoll skup:

  1. Tipican ragdoll ima 15-25 rigid body-ja (za svaki deo tela: glava, gornji torzo, donji torzo, nadlaktica x2, podlaktica x2, saka x2, butina x2, potkolenica x2, stopalo x2)

  2. Svaki par susednih tela ima constraint (zglobnu vezu) koji solver mora da zadovolji

  3. Ragdoll-ovi se cesto medjusobno sudaraju (self-collision) sto dodaje jos kolizionih parova

  4. Ragdoll-ovi retko "zaspe" brzo jer se ljuljaju, klize, itd.

Prakticne optimizacije:

Strategija Usteda Opis
Ogranici broj ragdoll-ova Velika Max 5-10 aktivnih istovremeno
Smanji broj tela po ragdoll-u Umerena 10 tela umesto 20
Forsiraj sleep nakon N sekundi Velika Ragdoll zaspi 3-5 sekundi nakon aktivacije
Konvertuj u static mesh Velika Nakon zaustavljanja, zameni ragdoll staticnim mesh-om
Iskljuci self-collision Umerena Manje kolizionih parova
LOD ragdoll Velika Daleki ragdoll-ovi imaju manje tela ili se ne simuliraju
// Primer: Ogranicavanje broja aktivnih ragdoll-ova
class ARagdollManager
{
    TArray<AActor*> ActiveRagdolls;
    int32 MaxActiveRagdolls = 8;

    void ActivateRagdoll(AActor* Actor)
    {
        if (ActiveRagdolls.Num() >= MaxActiveRagdolls)
        {
            // Ugasi najstariji ragdoll
            AActor* OldestRagdoll = ActiveRagdolls[0];
            ConvertToStaticMesh(OldestRagdoll);
            ActiveRagdolls.RemoveAt(0);
        }
        
        Actor->GetMesh()->SetSimulatePhysics(true);
        ActiveRagdolls.Add(Actor);
    }
};

36.2.6 Cloth Simulation (Chaos Cloth)

Cloth simulacija simulira ponasanje tkanine -- kako se ogrtac vijori na vetru, kako se odeca deformise pri kretanju.

UE5 koristi Chaos Cloth sistem koji simulira tkaninu kao mrezu cestica (particles) povezanih oprugama (springs).

Cena cloth simulacije zavisi od:

  1. Broja cestica -- vise cestica = detaljnija simulacija = skuplje
  2. Broja constraint-a (opruga) -- svaka veza izmedju cestica kosta
  3. Broja solver iteracija -- vise iteracija = stabilnija simulacija = skuplje
  4. Kolizije sa telom -- cloth mora da zna gde je telo da ne prodre kroz njega
Tipicna cena cloth simulacije po instanci:

Jednostavan (200 cestica, 2 iteracije)  -- 0.1-0.3ms
Srednji (500 cestica, 4 iteracije)      -- 0.3-0.8ms
Kompleksan (1000+ cestica, 6 iteracija) -- 0.8-2.0ms+

Optimizacije za cloth:

  1. Cloth LOD -- na vecim rastojanjima, koristite cloth sa manje cestica ili ga potpuno iskljucite
  2. Smanjite solver iteracije za daleke instance
  3. Ogranicite broj cloth instanci -- 3-5 istovremeno je razumno za vecinu platformi
  4. Koristite cloth za igraca i par vaznih NPC-ova, ostali neka imaju staticne mesh-ove

36.2.7 Chaos Destruction System

Chaos Destruction je UE5-ov sistem za razaranje objekata. Omogucava kreiranje objekata koji se lome, krse, drobe na realistican nacin.

Kako funkcionise:

  1. Mesh se unapred "lomi" na fragmente koristeci Fracture tool u editoru
  2. Fragmenti su povezani constraint-ima koji imaju "prag lomljenja" (break threshold)
  3. Kada sila prekoraci prag, fragmenti se odvajaju i postaju nezavisna fizicka tela

Zasto je ovo performansno skupo:

Zamislite zid od cigli koji se razori. Jedan static mesh postaje 200 nezavisnih fizickih tela, svako sa svojim kolizionim oblikom, svako interaguje sa svim ostalim. Broj kolizionih parova eksplodira.

Pre destrukcije:           Nakon destrukcije:
                           
  [Jedan objekat]    -->   [200 fragmenata]
  1 rigid body             200 rigid body-ja
  0 collision pairs        200*199/2 = ~20,000 potencijalnih parova!

Chaos Destruction optimizacije:

Tehnika Opis
Hierarchical fracture Umesto da se sve lomi odjednom, koristite nivoe -- prvo se lomi na velike komade, pa tek oni na manje
Cluster constraint Grupisanje fragmenata koji se ponasaju kao jedno telo dok se ne razbiju
Max active clusters Ogranicite koliko cluster-a moze biti aktivno
Remove on break Fragmenti nestaju nakon nekoliko sekundi
Sleep threshold Agresivniji sleep -- fragmenti brze zaspe
Distance-based destruction LOD Daleki objekti se razore na manje fragmenata
Max simulated bodies Globalno ogranicenje za Destruction tela
// Primer: Podesavanje Geometry Collection za performans
UGeometryCollectionComponent* GCComp = /* ... */;

// Ogranicite broj aktivnih fragmenata
GCComp->SetMaxSimulatedClusters(50);

// Automatsko uklanjanje fragmenata nakon N sekundi
GCComp->bAutoRemove = true;
GCComp->RemoveOnMaxSleepTime = 5.0f; // Ukloni 5 sekundi nakon sto zaspe

36.2.8 Physics LOD -- Pojednostavljivanje Fizike za Daleke Objekte

Isto kao sto smanjujemo vizuelni kvalitet za daleke objekte (mesh LOD, texture streaming), mozemo i trebamo smanjiti kvalitet fizike.

Physics LOD strategije:

  1. Disable simulation za daleke objekte -- objekti dalji od odredjene distance se ne simuliraju uopste

  2. Pojednostavi collision shape -- blizak objekat koristi convex hull, dalek koristi box

  3. Smanji update rate -- blizak objekat se simulira svakog frejma, dalek svaki 3. frejm

  4. Iskljuci cloth i destruction na daljini

  5. Koristite Significance Manager (objasnicemo kasnije u poglavlju) da automatski upravljate prioritetima

// Primer: Physics LOD sistem
void APhysicsActor::UpdatePhysicsLOD(float DistToCamera)
{
    if (DistToCamera > 10000.0f)
    {
        // Potpuno iskljuci fiziku
        MeshComponent->SetSimulatePhysics(false);
        MeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
    }
    else if (DistToCamera > 5000.0f)
    {
        // Pojednostavljena fizika
        MeshComponent->SetSimulatePhysics(true);
        MeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
        // Koristimo samo query (raycast/overlap), ne punu fizicku simulaciju
    }
    else
    {
        // Puna fizika
        MeshComponent->SetSimulatePhysics(true);
        MeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
    }
}

36.2.9 Async Physics -- Fizika na Zasebnom Thread-u

UE5 podrzava pokretanje fizicke simulacije na zasebnom thread-u, paralelno sa Game Thread-om. Ovo se zove Async Physics.

Bez Async Physics:

Game Thread: [Input][Game Logic][PHYSICS][Animation][...]
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
             Sve sekvencijalno, duze ukupno vreme frejma

Sa Async Physics:

Game Thread:    [Input][Game Logic][Animation][...]
Physics Thread: [PHYSICS SIMULATION...............]
                ^
                Paralelno -- ustedelo smo vreme na Game Thread-u!

Prednosti:

Izazovi:

Ukljucivanje Async Physics u UE5:

Project Settings > Physics > Async Scene

bEnableAsyncScene = true

Ili per-actor:

// Per-actor async physics
UPrimitiveComponent* Comp = /* ... */;
Comp->BodyInstance.bUseAsyncScene = true;

36.3 Tick Rates i Component Optimization

Zasto je Tick toliko vazan?

Tick je funkcija koja se poziva svakog frejma za svaki aktor i svaku komponentu koja ima tick ukljucen. Na 60 FPS, to znaci 60 poziva u sekundi. Na 120 FPS, 120 poziva.

Ovo zvuci bezazleno, ali hajde da izracunamo:

1000 aktora x Tick svakog frejma x 60 FPS = 60,000 Tick poziva u sekundi

Ako svaki Tick traje 0.01ms:
60,000 x 0.01ms = 0.6ms -- razumno

Ako svaki Tick traje 0.1ms (sto je sasvim moguce za kompleksniju logiku):
60,000 x 0.1ms = 6ms -- znacajno!

Ako svaki Tick traje 0.5ms:
60,000 x 0.5ms = 30ms -- KATASTROFA (samo tick trosi ceo budget za 30 FPS)

36.3.1 Actor Tick

Svaki AActor u UE5 ima Tick funkciju koja se moze pozvati svakog frejma. Default-no, novi aktori imaju tick ukljucen.

// U konstruktoru aktora
AMyActor::AMyActor()
{
    // OVO JE DEFAULT -- tick je ukljucen!
    PrimaryActorTick.bCanEverTick = true;
    
    // Ovako iskljucujete tick:
    PrimaryActorTick.bCanEverTick = false;
}

// Tick funkcija
void AMyActor::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
    // Kod koji se izvrsava svakog frejma
}

Problem: Vecina aktora u tipicnoj sceni ne treba da tikuje svakog frejma. Drvo ne treba da tikuje. Kamen ne treba da tikuje. Zid definitivno ne treba da tikuje. Ali ako ste zaboravili da iskljucite tick, oni svi trose CPU vreme.

Pravilo: Tick bi trebalo da bude iskljucen po default-u za sve aktore koji ne trebaju konstantno azuriranje. Ukljucite ga samo kada je zaista potreban.

36.3.2 Component Tick

Svaka komponenta (UActorComponent i njeni potomci) takodje moze da tikuje nezavisno od svog vlasnika (aktora).

Ovo znaci da jedan aktor sa 5 komponenti moze imati 6 tick poziva po frejmu (1 za aktora + 5 za komponente).

Tipicne komponente i da li trebaju tick:

Komponenta Treba li Tick? Razlog
UStaticMeshComponent NE Mesh se ne menja
USkeletalMeshComponent MOZDA Samo ako ima aktivnu animaciju
UCharacterMovementComponent DA Mora da procesira kretanje
UWidgetComponent MOZDA Samo ako se UI menja
UAudioComponent NE (interni) Audio thread upravlja reprodukcijom
Custom logic komponenta ZAVISI Samo ako treba per-frame update
// Iskljucivanje tick-a za komponentu
UMyComponent::UMyComponent()
{
    PrimaryComponentTick.bCanEverTick = false;
    // ILI, ako ponekad treba tick:
    PrimaryComponentTick.bCanEverTick = true;
    PrimaryComponentTick.bStartWithTickEnabled = false; // Pocni sa iskljucenim tick-om
}

// Dinamicko ukljucivanje/iskljucivanje
MyComponent->SetComponentTickEnabled(true);  // Ukljuci
MyComponent->SetComponentTickEnabled(false); // Iskljuci

36.3.3 Smanjenje Tick Frequency

Cak i kada aktor ili komponenta mora da tikuje, ne mora to da radi svakog frejma. Mnogi sistemi savrseno rade sa azuriranjem svakih 3, 5, ili 10 frejmova.

Tick Interval

UE5 dozvoljava postavljanje minimalnog intervala izmedju tick-ova:

// Tick svakih 0.5 sekundi umesto svakog frejma
PrimaryActorTick.TickInterval = 0.5f;

// Tick 10 puta u sekundi
PrimaryActorTick.TickInterval = 0.1f;

Timer-Based umesto Tick-Based

Umesto koriscenja Tick() funkcije, koristite Timer-e za logiku koja ne mora da bude per-frame:

// Umesto Tick-a, koristite Timer
void AMyActor::BeginPlay()
{
    Super::BeginPlay();
    
    // Pozovi CheckForEnemies svakih 0.5 sekundi
    GetWorldTimerManager().SetTimer(
        EnemyCheckTimer,
        this,
        &AMyActor::CheckForEnemies,
        0.5f,  // Interval
        true   // Ponavljaj
    );
}

void AMyActor::CheckForEnemies()
{
    // Ova logika se izvrsava svakih 0.5 sekundi
    // umesto 60-120 puta u sekundi
}

Usteda je dramaticna:

Tick svakog frejma na 60 FPS:  60 poziva/sekundi
Timer svakih 0.5s:              2 poziva/sekundi

Usteda: 96.7%!

36.3.4 Disabling Tick za Daleke/Nevidljive Aktore

Aktori koji su daleko od igraca ili koji nisu vidljivi na ekranu cesto ne trebaju da tikuju uopste.

void AMyActor::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
    
    // Proveri da li je igrac dovoljno blizu
    float DistToPlayer = GetDistanceTo(GetWorld()->GetFirstPlayerController()->GetPawn());
    
    if (DistToPlayer > 5000.0f)
    {
        // Previse daleko -- iskljuci tick i cekaj
        SetActorTickEnabled(false);
        
        // Postavi timer da periodicno proverava distance
        GetWorldTimerManager().SetTimer(
            DistanceCheckTimer,
            this,
            &AMyActor::CheckDistanceToPlayer,
            2.0f, // Proveri svakih 2 sekunde
            true
        );
        return;
    }
    
    // Normalna logika
    DoGameplayLogic(DeltaTime);
}

void AMyActor::CheckDistanceToPlayer()
{
    float DistToPlayer = GetDistanceTo(GetWorld()->GetFirstPlayerController()->GetPawn());
    
    if (DistToPlayer <= 5000.0f)
    {
        // Igrac je dovoljno blizu -- ukljuci tick ponovo
        SetActorTickEnabled(true);
        GetWorldTimerManager().ClearTimer(DistanceCheckTimer);
    }
}

36.3.5 Significance Manager

UE5 ima ugradjeni sistem zvani Significance Manager (USignificanceManager) koji automatizuje upravljanje prioritetima za aktore na osnovu njihovog "znacaja" za igraca.

Kako radi:

  1. Svaki aktor moze da registruje funkciju koja racuna njegov "significance" (znacaj) -- tipicno bazirano na rastojanju od kamere, vidljivosti, gameplay relevantnosti

  2. Significance Manager sortira sve registrovane aktore po znacaju

  3. Na osnovu znacaja, sistemi mogu da odluce sta da rade -- da li da tikuju, koliko cesto, da li da simuliraju fiziku, itd.

// Registrovanje aktora u Significance Manager
void AMyActor::BeginPlay()
{
    Super::BeginPlay();
    
    if (USignificanceManager* SM = FSignificanceManagerModule::Get(GetWorld()))
    {
        SM->RegisterObject(
            this,
            "MyActorTag",
            // Significance funkcija
            [](USignificanceManager::FManagedObjectInfo* ObjectInfo, 
               const FTransform& ViewPoint) -> float
            {
                AActor* Actor = Cast<AActor>(ObjectInfo->GetObject());
                if (!Actor) return 0.0f;
                
                float Distance = FVector::Dist(Actor->GetActorLocation(), ViewPoint.GetLocation());
                
                // Veci znacaj za blize objekte (invertovano rastojanje)
                return FMath::Max(0.0f, 1.0f - (Distance / 10000.0f));
            },
            // Post-significance callback
            [](USignificanceManager::FManagedObjectInfo* ObjectInfo, 
               float OldSignificance, float NewSignificance, bool bFinal)
            {
                AActor* Actor = Cast<AActor>(ObjectInfo->GetObject());
                if (!Actor) return;
                
                if (NewSignificance > 0.7f)
                {
                    // Visok znacaj - pun tick
                    Actor->SetActorTickInterval(0.0f);
                }
                else if (NewSignificance > 0.3f)
                {
                    // Srednji znacaj - sporiji tick
                    Actor->SetActorTickInterval(0.2f);
                }
                else
                {
                    // Nizak znacaj - iskljuci tick
                    Actor->SetActorTickEnabled(false);
                }
            }
        );
    }
}

void AMyActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    if (USignificanceManager* SM = FSignificanceManagerModule::Get(GetWorld()))
    {
        SM->UnregisterObject(this);
    }
    
    Super::EndPlay(EndPlayReason);
}

Significance Manager je posebno koristan u igrama sa otvorenim svetom gde imate hiljade aktora, od kojih su samo desetine zaista relevantni za igraca u svakom trenutku.


36.4 Blueprint vs C++ Overhead

Fundamentalna razlika

Blueprint Visual Scripting u UE5 je neverovatno mocan alat za prototipizaciju i razvoj gameplay-a. Ali postoji cena.

Blueprint-i se izvrsavaju kroz Virtual Machine (VM):

Kada napravite Blueprint, UE5 ga kompajlira u bytecode koji se izvrsava kroz Blueprint VM. Ovo je slicno tome kako se Java izvrsava kroz JVM, ili Python kroz svoj interpreter.

C++ se kompajlira direktno u masinski kod:

C++ kod se kompajlira u native masinske instrukcije koje CPU direktno izvrsava. Nema interpretacije, nema VM overhead-a.

Blueprint izvrsavanje:

Blueprint Node --> Bytecode --> VM interpretacija --> Rezultat
                                    ^
                                    |
                             Overhead ovde!

C++ izvrsavanje:

C++ Source --> Kompajler --> Masinski kod --> CPU --> Rezultat
                                              ^
                                              |
                                        Direktno, bez overhead-a

36.4.1 Kolika je razlika u performansu?

Razlika zavisi od tipa operacije:

Operacija Blueprint C++ Razlika
Jednostavno racunanje (a + b) ~0.001ms ~0.00001ms ~100x
Poziv funkcije ~0.005ms ~0.0001ms ~50x
For petlja (1000 iteracija) ~0.5ms ~0.005ms ~100x
Array operacije (Sort 1000 elemenata) ~2ms ~0.02ms ~100x
Spawn jednog aktora ~1ms ~0.8ms ~1.25x
Pristup property-ju ~0.003ms ~trivijalno ~30x

Kljucna opservacija: Razlika je ogromna za ciste racunske operacije, ali se smanjuje za operacije koje dominira engine internalni rad (spawn, load, rendering pozivi). Spawn aktora kosta skoro isto iz Blueprint-a i C++-a jer vecinu vremena trose iste interne engine funkcije.

36.4.2 Kada Blueprint Overhead Zaista Boli

Blueprint overhead postaje problem u tri situacije:

1. Mnogo instanci

Ako imate jednog NPC-a sa Blueprint tick-om, overhead je zanemarljiv. Ali ako imate 500 NPC-ova sa Blueprint tick-om, svaki frejm se poziva 500 Blueprint tick funkcija, svaka sa VM overhead-om.

1 NPC Blueprint Tick:     0.01ms overhead    -- zanemarljivo
500 NPC Blueprint Tick:   5ms overhead       -- znacajno!
500 NPC C++ Tick:          0.05ms overhead   -- zanemarljivo

2. Kompleksna logika u Tick-u

Ako vas Blueprint tick ima 3 cvora, overhead je minimalan. Ali ako ima 200 cvorova sa petljama, grananjima, array operacijama -- overhead se akumulira.

Blueprint Tick sa 3 cvora:    ~0.01ms per instance
Blueprint Tick sa 200 cvorova: ~0.5ms per instance

500 instanci x 0.5ms = 250ms per frame -- KATASTROFA

3. Tight Loops (Petlje sa mnogo iteracija)

For-each petlja u Blueprint-u je posebno skupa jer svaka iteracija ima VM overhead:

// C++ -- jedna for petlja, trivijalna cena
for (int i = 0; i < 10000; i++)
{
    Sum += Array[i];
}
// Vreme: ~0.01ms

// Blueprint -- svaka iteracija prolazi kroz VM
// ForEachLoop na Array od 10000 elemenata
// Vreme: ~5ms (500x sporije!)

36.4.3 Nativization -- Konvertovanje Blueprint-a u C++

UE4 je imao funkcionalnost zvanu Blueprint Nativization koja je mogla da konvertuje Blueprint logiku u C++ kod tokom cook procesa. Ovo je znacajno smanjivalo Blueprint overhead.

U UE5, nativization je depreciran (uklonjen iz standardnog workflow-a). Razlog: Unreal-ov tim je fokusiran na poboljsanje Blueprint VM performansa i na koriscenje drugih pristupa.

Alternativni pristupi u UE5:

  1. Rucna konverzija -- prepisite kriticne delove iz Blueprint-a u C++
  2. Hybrid pristup -- koristite C++ za baznu klasu sa optimizovanom logikom, Blueprint za podklase sa specificnom konfiguracijom
  3. Blueprint Function Libraries u C++ -- napisite performansno kriticne funkcije u C++, pozovite ih iz Blueprint-a
// Primer: C++ bazna klasa sa optimizovanom logikom
UCLASS()
class AOptimizedNPC : public ACharacter
{
    GENERATED_BODY()

public:
    // Tick u C++ -- brz
    virtual void Tick(float DeltaTime) override
    {
        Super::Tick(DeltaTime);
        
        // Skupa logika u C++ -- brza
        UpdateAI(DeltaTime);
        UpdatePathfinding(DeltaTime);
        UpdateCombat(DeltaTime);
    }

protected:
    // Blueprint moze da override-uje specificno ponasanje
    UFUNCTION(BlueprintImplementableEvent, Category = "NPC")
    void OnStateChanged(ENPCState NewState);
    
    // Skupa interna logika ostaje u C++
    void UpdateAI(float DeltaTime);
    void UpdatePathfinding(float DeltaTime);
    void UpdateCombat(float DeltaTime);
};

36.4.4 Pravilo: Prototip u BP, Optimizuj u C++

Ovo je industrijski standard i valja ga ponoviti:

WORKFLOW:

1. PROTOTIPIZACIJA (Blueprint)
   - Brzo iterirajte na gameplay ideji
   - Testirajte mehanike
   - Ne brinite o performansu
   
2. VALIDACIJA
   - Da li mehanika radi kako treba?
   - Da li je zabavna?
   - Ako NE -- bacite i pocnite iznova (jeftino u BP!)
   
3. PROFILISANJE
   - Da li Blueprint overhead uzrokuje probleme?
   - stat game, stat Blueprint -- proverite
   - Ako NE -- ostavite u Blueprint-u, ne optimizujte nepotrebno!
   
4. OPTIMIZACIJA (C++)
   - Samo za delove koji su profilisanjem identifikovani kao problem
   - Prepisite hot path (najcesci/najskuplji put izvrsavanja) u C++
   - Ostavite retke/jednostavne operacije u Blueprint-u

Ne prepisujte sve u C++ "za svaki slucaj"! To je prematura optimizacija koja trosira vreme, komplikuje rad dizajnerima, i cesto donosi zanemarljivo poboljsanje. Optimizujte samo ono sto profiler pokaze kao problem.


36.5 Actor Lifecycle -- Spawn, Destroy i Object Pooling

36.5.1 Spawning Cost

Kreiranje (spawning) novog aktora u UE5 nije besplatno. Engine mora da:

  1. Alocira memoriju za aktor i sve njegove komponente
  2. Konstruise aktor (pozove konstruktor)
  3. Inicijalizuje sve komponente
  4. Registruje aktor u World-u
  5. Registruje sve komponente za tick, rendering, fiziku, itd.
  6. Pozove BeginPlay za aktor i sve komponente

Za jednostavan aktor (StaticMeshComponent + BoxCollision), spawn kosta mozda 0.1-0.5ms. Ali za kompleksan aktor (SkeletalMesh + AnimBlueprint + AIController + NavigationComponent + mnogo custom komponenti), spawn moze da kosta 2-10ms.

Tipicna cena spawn-a:

Prazan aktor:                    ~0.05ms
Aktor sa StaticMesh + Collision: ~0.1-0.5ms
Character sa AI:                 ~2-5ms
Kompleksan Vehicle:              ~5-10ms

Ako spawn-ujete 20 projektila odjednom (shotgun blast), to moze biti 2-10ms samo na spawn-ovanje.

36.5.2 Destroying Cost

Unistavanje aktora takodje nije besplatno:

  1. Poziva se EndPlay i BeginDestroy
  2. Sve komponente se deregistruju
  3. Fizicka tela se uklanjaju iz scene
  4. Aktor se uklanja iz World-a
  5. Memorija se oznacava za Garbage Collection (ali se ne oslobadja odmah)

Sam destroy je tipicno brzi od spawn-a, ali prave probleme pravi Garbage Collection koji se desava kasnije.

36.5.3 Garbage Collection Pauses

UE5 koristi Garbage Collector (GC) za upravljanje memorijom UObject-a (sto ukljucuje aktore, komponente, asset-e, itd.).

Kako GC radi:

  1. GC povremeno skenira sve UObject-e da pronadje one koji vise nemaju reference (niko ih ne koristi)
  2. Objekti bez referenci se oznacavaju za brisanje
  3. GC brise te objekte i oslobadja memoriju

Problem: GC skeniranje je stop-the-world operacija -- Game Thread se pauzira dok GC ne zavrsi. Ova pauza moze biti od 1ms do 30ms+ u zavisnosti od broja objekata.

                     GC Pauza
                     |<------>|
Game Thread: [Frame][Frame][GC STOP][Frame][Frame]
                           ^^^^^^^^
                     Igrac vidi "hitch" ovde!

Faktori koji uticu na GC pauze:

Faktor Uticaj
Ukupan broj UObject-a u memoriji Vise objekata = duze skeniranje
Broj objekata za brisanje Vise brisanja = duze
Frekvencija spawn/destroy ciklusa Cesca destrukcija = cesce GC pauze
Kompleksnost referentnog grafa Dublje reference = sporije skeniranje

Kako minimizovati GC pauze:

  1. Koristite Object Pooling (vidite ispod)
  2. Smanjite frekvenciju spawn/destroy operacija
  3. Koristite incremental GC -- UE5 podrzava inkrementalni GC koji raspodjeljuje rad kroz vise frejmova
  4. Rucno kontrolisite GC timing -- pozovite GC u momentima kada igrac nece primetiti pauzu (loading screen, cutscena)
  5. Smanjite ukupan broj UObject-a -- koristite struct-ove (USTRUCT) umesto UObject-a gde je moguce, jer struct-ovi ne prolaze kroz GC
// Rucno pozivanje GC u pogodnom momentu
void AMyGameMode::OnLevelTransitionStart()
{
    // Prikazujemo loading screen, idealan moment za GC
    GEngine->ForceGarbageCollection(true);
}

// Podesavanje GC parametara
// U DefaultEngine.ini:
// [/Script/Engine.GarbageCollectionSettings]
// gc.TimeBetweenPurgingPendingKillObjects=30   // Koliko cesto se vrsi GC (sekunde)
// gc.MaxObjectsNotConsideredByGC=1             // Objekti ispod ovog broja se ne proveravaju

36.5.4 Object Pooling

Object pooling je tehnika gde umesto da spawn-ujete i destroy-ujete objekte, vi ih reciklirate.

Ideja je prosta:

  1. Na pocetku igre (ili nivoa), spawn-ujete odredjeni broj objekata unapred
  2. Kada vam treba objekat, umesto spawn-a, uzimate jedan iz "pool-a" i aktivirate ga
  3. Kada vam objekat vise ne treba, umesto destroy-a, deaktivirate ga i vracate u pool
BEZ POOLING-A:

Treba metak? --> Spawn() --> [Koristite] --> Destroy() --> GC brise
Treba metak? --> Spawn() --> [Koristite] --> Destroy() --> GC brise
Treba metak? --> Spawn() --> [Koristite] --> Destroy() --> GC brise
(Svaki ciklus kosta spawn + destroy + GC overhead)

SA POOLING-OM:

Inicijalizacija: Spawn 50 metaka, deaktiviraj ih

Treba metak? --> Pool.Get() --> Aktiviraj --> [Koristite] --> Deaktiviraj --> Pool.Return()
Treba metak? --> Pool.Get() --> Aktiviraj --> [Koristite] --> Deaktiviraj --> Pool.Return()
(Nema spawn/destroy/GC overhead-a tokom gameplay-a!)

Implementacija Object Pool-a u UE5:

UCLASS()
class AObjectPool : public AActor
{
    GENERATED_BODY()
    
public:
    UPROPERTY(EditAnywhere)
    TSubclassOf<AActor> PooledActorClass;
    
    UPROPERTY(EditAnywhere)
    int32 PoolSize = 50;
    
private:
    TArray<AActor*> AvailableActors;
    TArray<AActor*> ActiveActors;

public:
    void InitializePool()
    {
        for (int32 i = 0; i < PoolSize; i++)
        {
            AActor* NewActor = GetWorld()->SpawnActor<AActor>(PooledActorClass);
            NewActor->SetActorHiddenInGame(true);
            NewActor->SetActorEnableCollision(false);
            NewActor->SetActorTickEnabled(false);
            AvailableActors.Add(NewActor);
        }
    }
    
    AActor* GetFromPool(FVector Location, FRotator Rotation)
    {
        if (AvailableActors.Num() == 0)
        {
            UE_LOG(LogTemp, Warning, TEXT("Object Pool prazan! Razmatrajte povecanje PoolSize."));
            return nullptr; // Ili spawn novog kao fallback
        }
        
        AActor* Actor = AvailableActors.Pop();
        Actor->SetActorLocation(Location);
        Actor->SetActorRotation(Rotation);
        Actor->SetActorHiddenInGame(false);
        Actor->SetActorEnableCollision(true);
        Actor->SetActorTickEnabled(true);
        
        // Pozovite custom "OnActivated" ako postoji
        if (IPoolable* Poolable = Cast<IPoolable>(Actor))
        {
            Poolable->OnActivatedFromPool();
        }
        
        ActiveActors.Add(Actor);
        return Actor;
    }
    
    void ReturnToPool(AActor* Actor)
    {
        Actor->SetActorHiddenInGame(true);
        Actor->SetActorEnableCollision(false);
        Actor->SetActorTickEnabled(false);
        
        // Pozovite custom "OnDeactivated" ako postoji
        if (IPoolable* Poolable = Cast<IPoolable>(Actor))
        {
            Poolable->OnDeactivatedToPool();
        }
        
        ActiveActors.Remove(Actor);
        AvailableActors.Add(Actor);
    }
};

// Interface za poolable aktore
UINTERFACE(MinimalAPI)
class UPoolable : public UInterface
{
    GENERATED_BODY()
};

class IPoolable
{
    GENERATED_BODY()
public:
    virtual void OnActivatedFromPool() = 0;
    virtual void OnDeactivatedToPool() = 0;
};

Sta se tipicno pool-uje:

Objekat Razlog za pooling
Projektili (meci, rakete) Spawn/destroy stotine u sekundi
Particle efekti Cest spawn, kratak zivot
Sound actors Mnogo zvukova koji se kratkotrajno pustaju
Pickup items Spawn/collect ciklus
Decals (rupe od metaka, mrlje) Mnogo malih objekata
UI elementi (floating damage numbers) Cest spawn, kratak zivot
NPC-ovi (u nekim slucajevima) Recikliranje za open world igre

36.6 Async Loading -- Ucitavanje Asset-a Bez Zastoja

36.6.1 Problem sa Sinhronim Ucitavanjem

Kada UE5 mora da ucita asset (mesh, teksturu, zvuk, Blueprint) sa diska, default ponasanje je sinhrono ucitavanje -- Game Thread ceka dok se asset ne ucita.

Sinhrono ucitavanje:

Game Thread: [Frame][Frame][LOADING...CEKAM...][Frame][Frame]
                           ^^^^^^^^^^^^^^^^^^
                           HITCH! Igra se zamrznula!

Tipicni slucajevi kada se ovo desava:

  1. Spawn aktora koji referencira mesh koji nije u memoriji
  2. Ulazak u novu oblast gde su potrebni novi asset-i
  3. Pristup asset-u koji je soft-referenciran ali nije ucitan

Za male asset-e (par KB), ovo je neprimtno. Ali za veliki mesh (50MB) ili veliku teksturu (256MB), igra moze da se "zamrzne" na stotine milisekundi -- sto je apsolutno neprihvatljivo.

36.6.2 Async Loading

Resenje je asinhrono ucitavanje -- asset se ucitava u pozadini dok igra nastavlja da radi normalno.

Asinhrono ucitavanje:

Game Thread:    [Frame][Frame][Frame][Frame][Frame][Asset spreman!]
Loading Thread: [LOADING..........................]
                ^                                 ^
                Pocelo ucitavanje                 Ucitavanje zavrseno
                (ne blokira igru!)                (callback)

Kako koristiti async loading u UE5:

// Asinhrono ucitavanje pojedinacnog asset-a
void AMyActor::LoadWeaponAsync()
{
    // Soft reference na mesh -- NE ucitava automatski
    TSoftObjectPtr<UStaticMesh> WeaponMeshRef = 
        TSoftObjectPtr<UStaticMesh>(FSoftObjectPath("/Game/Weapons/Rifle.Rifle"));
    
    // Pokretanje async load-a
    FStreamableManager& Streamable = UAssetManager::GetStreamableManager();
    Streamable.RequestAsyncLoad(
        WeaponMeshRef.ToSoftObjectPath(),
        FStreamableDelegate::CreateUObject(this, &AMyActor::OnWeaponLoaded)
    );
}

void AMyActor::OnWeaponLoaded()
{
    // Asset je ucitan -- sada ga mozemo koristiti
    UStaticMesh* WeaponMesh = WeaponMeshRef.Get();
    if (WeaponMesh)
    {
        MeshComponent->SetStaticMesh(WeaponMesh);
    }
}

Async loading vise asset-a odjednom:

void AMyActor::LoadMultipleAssetsAsync()
{
    TArray<FSoftObjectPath> AssetsToLoad;
    AssetsToLoad.Add(FSoftObjectPath("/Game/Meshes/Building01.Building01"));
    AssetsToLoad.Add(FSoftObjectPath("/Game/Textures/Building01_Diffuse.Building01_Diffuse"));
    AssetsToLoad.Add(FSoftObjectPath("/Game/Sounds/Ambient_Wind.Ambient_Wind"));
    
    FStreamableManager& Streamable = UAssetManager::GetStreamableManager();
    Streamable.RequestAsyncLoad(
        AssetsToLoad,
        FStreamableDelegate::CreateUObject(this, &AMyActor::OnAllAssetsLoaded)
    );
}

void AMyActor::OnAllAssetsLoaded()
{
    // SVI asset-i su ucitani -- sada ih mozemo koristiti
    UE_LOG(LogTemp, Log, TEXT("Svi asset-i su ucitani!"));
}

36.6.3 Soft References vs Hard References

Razlika izmedju soft i hard referenci je kljucna za razumevanje loading-a u UE5.

Hard References

Hard reference (cvrsta referenca) znaci da se referencirani asset automatski ucitava kada se ucita vlasnik.

// Hard reference -- WeaponMesh se ucitava AUTOMATSKI kada se ucita ovaj aktor
UPROPERTY(EditAnywhere)
UStaticMesh* WeaponMesh;

// Hard reference na klasu -- Blueprint klasa se ucitava AUTOMATSKI
UPROPERTY(EditAnywhere)
TSubclassOf<AProjectile> ProjectileClass;

Problem sa hard referencama: Ako Actor A ima hard referencu na Mesh B, koji ima hard referencu na Material C, koji ima hard referencu na Texture D... ucitavanje Actor A povlaci ucitavanje svega u lancu. Ovo se zove reference chain i moze dovesti do toga da ucitavanje jednog malog Blueprint-a povuce ucitavanje stotine megabajta asset-a.

Hard Reference Chain (problem):

AMyActor
  |-- WeaponMesh (30MB)
  |     |-- Material (referencira 5 tekstura, 200MB)
  |
  |-- ProjectileClass
  |     |-- ProjectileMesh (5MB)
  |     |-- ExplosionEffect (50MB)
  |     |     |-- ExplosionSound (10MB)
  |
  Total: 295MB ucitano samo zato sto ste ucitali AMyActor!

Soft References

Soft reference (meka referenca) je samo putanja do asset-a. Asset se ne ucitava automatski.

// Soft reference -- NE ucitava automatski
UPROPERTY(EditAnywhere)
TSoftObjectPtr<UStaticMesh> WeaponMeshSoft;

// Soft reference na klasu
UPROPERTY(EditAnywhere)
TSoftClassPtr<AProjectile> ProjectileClassSoft;

Sa soft referencama, vi kontrolisete kada se asset ucitava:

void AMyActor::NeedWeapon()
{
    if (WeaponMeshSoft.IsValid())
    {
        // Vec je ucitan -- koristite ga odmah
        UStaticMesh* Mesh = WeaponMeshSoft.Get();
        MeshComponent->SetStaticMesh(Mesh);
    }
    else
    {
        // Nije ucitan -- pokrenite async load
        LoadWeaponAsync();
    }
}

Pravilo:

Situacija Koristite
Asset koji UVEK treba (mesh igraca) Hard reference
Asset koji PONEKAD treba (posebno oruzje) Soft reference + async load
Asset koji treba KASNIJE (sledeci nivo) Soft reference + preload
Asset koji treba samo u EDITORU Soft reference

Vise o streaming sistemu i kako on koristi soft reference mozete naci u Poglavlju 32 (World Partition i Streaming).


36.7 Multithreading Awareness

36.7.1 Zasto je multithreading vazan za performans

Moderni procesori imaju 8, 12, 16 ili cak vise jezgara. Ako vas program koristi samo jedno jezgro, gubite 87-94% raspolozive racunarske snage procesora.

UE5 je dizajniran da koristi vise thread-ova (niti izvrsavanja) paralelno. Razumevanje kako ovi thread-ovi rade i kako interaguju je kljucno za razumevanje performansa.

36.7.2 Glavni Thread-ovi u UE5

UE5 ima tri "velika" thread-a koji su uvek aktivni:

1. Game Thread

Game Thread je "glavni" thread. On izvrsava:

Ovo je tipicno najoptereceniji thread jer na njemu radi najvise razlicitih sistema.

2. Render Thread

Render Thread priprema rendering komande:

Render Thread radi 1 frejm iza Game Thread-a -- dok Game Thread priprema frejm N+1, Render Thread renderuje frejm N.

3. RHI Thread (Rendering Hardware Interface)

RHI Thread komunicira direktno sa grafickim API-jem (DirectX 12, Vulkan):

Vizuelizacija pipeline-a (3 frejma):

Vreme ---->
Game Thread:    [Frame 3 Logic][Frame 4 Logic][Frame 5 Logic]
Render Thread:  [Frame 2 Prep ][Frame 3 Prep ][Frame 4 Prep ]
RHI Thread:     [Frame 1 Submit][Frame 2 Submit][Frame 3 Submit]
GPU:            [Frame 0 Render][Frame 1 Render][Frame 2 Render]

36.7.3 Physics Thread

Kao sto smo pomenuli u sekciji o Async Physics (36.2.9), fizicka simulacija moze da radi na zasebnom thread-u.

Game Thread:     [Logic][Logic][Logic]
Physics Thread:  [Sim  ][Sim  ][Sim  ]  <-- paralelno sa Game Thread-om

Ovo je posebno korisno za igre sa intenzivnom fizikom (destruction, mnogo ragdoll-ova, simulacija vozila).

36.7.4 Audio Thread

Audio procesiranje se odvija na zasebnom thread-u (Audio Render Thread):

Game Thread:  [Logic + Audio Commands][...]
Audio Thread: [Decode + Mix + Spatialize][Output to Device]

Audio Thread ima strog vremenski rok -- mora da isporuci audio buffer pre nego sto zvucna kartica zatrazi sledeci. Ako ne stigne, cujete audio glitch (pucketanje, preskakanje).

36.7.5 Kada Thread-ovi "Takmice" za CPU Vreme

Svaki thread zahteva CPU jezgro za izvrsavanje. Na procesoru sa 8 jezgara, mozete imati 8 thread-ova koji rade istovremeno bez problema. Ali ako imate vise aktivnih thread-ova nego jezgara, operativni sistem mora da vrsi context switching -- prebacivanje izmedju thread-ova, sto ima svoj overhead.

Tipicanscenario problema:

8-core CPU:

Game Thread:     [jezgro 1]
Render Thread:   [jezgro 2]
RHI Thread:      [jezgro 3]
Physics Thread:  [jezgro 4]
Audio Thread:    [jezgro 5]
Task Graph (3):  [jezgro 6, 7, 8]

= Sva jezgra zauzeta, ali radi OK

Problem nastaje kada:
- Game Thread treba 15ms ali ima budget od 16.67ms (60 FPS)
- Physics Thread takodje treba 15ms
- Oba se takmic za CPU jezgra
- Context switching dodaje overhead
- Rezultat: frame time prelazi 16.67ms -> pad ispod 60 FPS

Dijagnostika:

Koristite UE5 Profiler ili stat Threading da vidite koliko svaki thread trosira vremena:

// U konzoli igre
stat Threading
stat Game        // Game Thread vreme
stat GPU         // Render/GPU vreme

Kljucno pitanje pri profilisanju:

Vas frame rate je ogranicen NAJSPORIJIM thread-om! Nema koristi od optimizacije GPU-a ako je Game Thread bottleneck.

36.7.6 UE5 Task Graph

Pored "velikih" thread-ova, UE5 koristi Task Graph sistem za paralelizaciju manjih poslova.

Task Graph je sistem gde:

  1. Razliciti sistemi kreiraju task-ove (male jedinice posla)
  2. Task-ovi definisu zavisnosti (task B ne moze da pocne dok task A ne zavrsi)
  3. Task Graph scheduler automatski rasporedjuje task-ove na dostupna CPU jezgra
Task Graph primer:

                 [Task A: Update AI za NPC grupe 1-100]
                          |
                +---------+---------+
                |                   |
    [Task B: Pathfinding       [Task C: Pathfinding
     za NPC 1-50]               za NPC 51-100]
                |                   |
                +---------+---------+
                          |
              [Task D: Finalize AI decisions]

Prednosti Task Graph-a:

  1. Automatska paralelizacija -- ne morate rucno upravljati thread-ovima
  2. Work stealing -- ako jedno jezgro zavrsi svoje task-ove, ono "krade" task-ove od drugih
  3. Definisane zavisnosti -- garantovano korektan redosled izvrsavanja
  4. Skalabilnost -- automatski koristi onoliko jezgara koliko je dostupno

Sistemi koji koriste Task Graph:

// Primer koriscenja Task Graph-a za custom paralelizaciju
void AMyManager::ProcessEntitiesParallel()
{
    const int32 NumEntities = Entities.Num();
    const int32 BatchSize = 64; // Svaki task procesira 64 entiteta
    
    // Kreiraj task-ove
    FGraphEventArray CompletionEvents;
    
    for (int32 i = 0; i < NumEntities; i += BatchSize)
    {
        int32 StartIndex = i;
        int32 EndIndex = FMath::Min(i + BatchSize, NumEntities);
        
        CompletionEvents.Add(FFunctionGraphTask::CreateAndDispatchWhenReady(
            [this, StartIndex, EndIndex]()
            {
                for (int32 j = StartIndex; j < EndIndex; j++)
                {
                    // Procesiranje entiteta -- ovo se izvrsava paralelno!
                    Entities[j]->UpdateLogic();
                }
            },
            TStatId(),
            nullptr, // Nema zavisnosti
            ENamedThreads::AnyThread
        ));
    }
    
    // Sacekaj da svi task-ovi zavrse
    FTaskGraphInterface::Get().WaitUntilTasksComplete(CompletionEvents);
}

Napomena o thread safety: Kada koristite Task Graph (ili bilo koji vid paralelizma), morate paziti na race condition-e -- situacije gde dva thread-a istovremeno pristupaju istim podacima. UE5 nudi razne mehanizme za zastitu:

Ali idealno, dizajnirajte task-ove tako da svaki radi na svojim podacima bez deljenja, cime se izbegava potreba za sinhronizacijom.


36.8 Prakticni Primeri i Scenarija

Scenario 1: "Igra stuca ali GPU je na 30%"

Simptomi: Frame rate je nizak ili nestabilan. GPU utilizacija je niska.

Dijagnoza: Problem je na CPU strani -- Game Thread ili Physics Thread.

Koraci:

  1. stat Game -- proverite Game Thread vreme
  2. stat Physics -- proverite Physics vreme
  3. stat SceneRendering -- proverite Render Thread vreme
  4. Ako je Game Thread problem:
    • stat Tick -- koliko tick-ova se izvrsava?
    • stat Blueprint -- da li Blueprint-i trose previse?
    • Koristite Unreal Insights za detaljniji profiling

Scenario 2: "Kada ima mnogo neprijatelja, igra uspori"

Verovatni uzroci:

  1. Previse Actor Tick-ova (svaki NPC tikuje)
  2. Blueprint AI logika je preskupa za mnogo instanci
  3. Navigation/Pathfinding preopterecen
  4. Previse skeletal mesh animacija

Resenja:

  1. Significance Manager -- daleki NPC-ovi tikuju redje
  2. Prebacite AI hot path u C++
  3. Ogranicite istovremene pathfinding zahteve
  4. Animation LOD -- daleki NPC-ovi koriste jednostavnije animacije ili se ne animiraju

Scenario 3: "Audio pucka i preskace"

Uzrok: Audio Thread ne stize da procesira audio buffer na vreme.

Resenja:

  1. Smanjite Max Channels (manje istovremenih zvukova)
  2. Iskljucite skupe audio efekte (convolution reverb)
  3. Smanjite audio occlusion frekvenciju
  4. Pojednostavite MetaSounds grafove
  5. Proverite da li Audio Thread nema dovoljno CPU vremena (previse drugih thread-ova)

Scenario 4: "Igra se zamrzne na pola sekunde kada udjem u novu oblast"

Uzrok: Sinhrono ucitavanje asset-a.

Resenja:

  1. Prebacite na async loading
  2. Koristite soft reference umesto hard reference
  3. Preload-ujte asset-e pre nego sto igrac stigne u oblast
  4. Koristite Level Streaming (Poglavlje 32) za streaming celih poddlevoa

Scenario 5: "Destrukcija zidova ubija frame rate"

Uzrok: Chaos Destruction generise previse fizickih tela.

Resenja:

  1. Smanjite broj fragmenata (krupnije lomljenje)
  2. Koristite hijerarhijsko lomljenje
  3. Fragmenti nestaju nakon par sekundi
  4. Ogranicite maksimalan broj aktivnih destruction tela
  5. Daleki objekti se lome na jos manje fragmenata

36.9 Checklist za Non-Rendering Performance

Pre nego sto zavrsite optimizaciju, prodjite kroz ovaj checklist:

Audio Checklist

Physics Checklist

Tick Checklist

Blueprint vs C++ Checklist

Actor Lifecycle Checklist

Loading Checklist

Multithreading Checklist


36.10 Tabela Kljucnih Pojmova

Pojam (EN) Opis (SR)
Voice Count Broj istovremeno aktivnih zvukova u audio engine-u
Audio Occlusion Simulacija blokiranja zvuka fizickim objektima (koristeci raycast)
MetaSounds UE5 sistem za proceduralno generisanje zvuka u realnom vremenu
Audio LOD Smanjenje kvaliteta audio procesiranja za daleke zvucne izvore
Collision Shape Geometrijski oblik koji se koristi za detekciju kolizije (sphere, box, capsule, convex hull, triangle mesh)
Broad Phase Prva faza detekcije kolizije -- brza eliminacija parova koji se sigurno ne dodiruju
Narrow Phase Druga faza detekcije kolizije -- precizna provera za parove koji su prosli broad phase
Physics Substeps Vissestruko izvrsavanje fizicke simulacije po frejmu za vecu preciznost
Ragdoll Simulacija tela kao niza rigid body-ja povezanih zglobovima
Chaos Cloth UE5 sistem za simulaciju tkanine kao mreze cestica i opruga
Chaos Destruction UE5 sistem za razaranje objekata lomljenje mesh-ova na fragmente
Physics LOD Pojednostavljivanje ili iskljucivanje fizicke simulacije za daleke objekte
Async Physics Pokretanje fizicke simulacije na zasebnom thread-u, paralelno sa Game Thread-om
Actor Tick Funkcija koja se poziva svakog frejma za aktora -- moze biti znacajan performansni trosak
Component Tick Tick funkcija na nivou komponente, nezavisna od tick-a aktora
Tick Interval Minimalno vreme izmedju dva tick poziva -- sluzi za smanjenje frekvencije tick-a
Significance Manager UE5 sistem koji automatski upravlja prioritetom aktora na osnovu njihovog znacaja za igraca
Blueprint VM Virtualna masina koja interpretira Blueprint bytecode -- sporija od native C++ koda
Nativization Proces konverzije Blueprint koda u C++ tokom cook procesa (depreciran u UE5)
Object Pooling Tehnika recikliranja objekata umesto spawn/destroy ciklusa, eliminise GC overhead
Garbage Collection (GC) Automatski sistem za upravljanje memorijom UObject-a -- moze uzrokovati pauze
Hard Reference Direktna referenca na asset koja uzrokuje automatsko ucitavanje tog asset-a
Soft Reference Referenca kao putanja (string) -- asset se ne ucitava automatski, vec na zahtev
Async Loading Ucitavanje asset-a u pozadini bez blokiranja Game Thread-a
Game Thread Glavni thread UE5 koji izvrsava gameplay logiku, Blueprint-e, AI, input
Render Thread Thread koji priprema rendering komande za GPU
RHI Thread Thread koji komunicira sa grafickim API-jem (DX12, Vulkan)
Task Graph UE5 sistem za paralelno izvrsavanje manjih poslova na dostupnim CPU jezgrima
Context Switching Prebacivanje CPU-a izmedju thread-ova -- ima overhead i usporava izvrsavanje
Race Condition Bug gde dva thread-a istovremeno pristupaju istim podacima sa nepredvidljivim rezultatom

36.11 Korisni Linkovi i Dalje Citanje

Zvanicna Unreal Engine Dokumentacija

Unreal Engine Blog i Video Resursi

Povezana Poglavlja u Ovoj Knjizi


Rezime Poglavlja

Performans igre nije samo pitanje renderinga. U ovom poglavlju smo pokrili sve glavne non-rendering performansne faktore:

Audio moze da iznenadi svojom cenom -- posebno kada imate mnogo istovremenih zvukova sa okluzijom, reverb-om i proceduralnim generisanjem kroz MetaSounds. Audio LOD i pazljivo upravljanje voice count-om su kljucni.

Fizika je cesto najskuplji non-rendering sistem. Izbor kolizionih oblika (uvek najjednostavniji moguci!), upravljanje brojem aktivnih tela, ogranicavanje ragdoll-ova i destruction fragmenata -- sve su to kriticne optimizacije.

Tick sistem moze da postane nocna mora ako se ne upravlja pazljivo. Iskljucite tick za sve sto ne mora da tikuje, smanjite frekvenciju gde je moguce, koristite Timer-e umesto Tick-a za periodicnu logiku, i implementirajte Significance Manager za velike svetove.

Blueprint vs C++ -- ne prepisujte sve u C++, ali prepoznajte hot path-ove (mnogo instanci, kompleksna logika, svaki frejm) i prebacite ih u C++ kada profiler pokaze da je potrebno.

Actor lifecycle -- object pooling je obavezan za objekte koji se cesto kreiraju i unistqvaju. GC pauze mogu da budu razarajuce za gameplay, i jedini nacin da ih kontrolisete je da smanjite pritisak na GC.

Async loading -- nikada ne ucitavajte velike asset-e sinhrono tokom gameplay-a. Koristite soft reference i async loading za sve sto nije apsolutno neophodno od samog pocetka.

Multithreading -- razumejte koji thread-ovi postoje, koji je bottleneck, i optimizujte taj thread. Nema svrhe optimizovati rendering ako je Game Thread bottleneck.

Svi ovi sistemi medusobno uticu jedni na druge. Fizika trosira Game Thread vreme, sto ostavlja manje vremena za Blueprint tick-ove, sto moze da natera Render Thread da ceka... Razumevanje celokupne slike je kljuc za pravi performans.

U sledecem poglavlju cemo nastaviti sa daljim aspektima optimizacije. A za detaljniju CPU optimizaciju, pogledajte Poglavlje 49.