Opakovanie častíc

3D Digitální hra Repeat Vývoj

15. 03. 2023

V našej pripravovanej hre Repeat pracujeme so svetom, ktorý sa hráčovi javí nekonečný. Implementácia tejto mechaniky, nech už znie akokoľvek jednoducho, sa postupom času stáva čoraz náročnejšia. Jedna vec, ktorá bola ošemetná, sú časticové efekty.

Naivný prístup

Častice a niektoré ďalšie objekty, ktoré nie sú meshe (decaly, svetlá, atď...), sú v hre implementované čo najjednoduchšie a to volaním Instantiate() na začiatku hry a následným posunutím objektov na správne pozície. Tento prístup funguje dobre pre decaly a svetlá. V prípade častíc však nastáva problém v tom, že majú svoj vlastný stav: simuláciu.

Zpočiatku sme používali základný časticový systém v Unity, ktorý beží na CPU. Neskôr, s prechodom na HDRP, sme migrovali na nový VFX, implementovaný na GPU. Tieto migrácie prebiehali ešte predtým, ako sme si všimli, že niečo nie je v poriadku: keď hráč prejde cez hranu levelu, simulácia častíc preskočí alebo blikne. Čo teraz?

Tento jav vzniká tým, že hráč sa pozerá na inú inštanciu efektu než tá, ktorá je v "root cube". "Root cube" je základná, "ozajstná" verzia levelu, ktorú hráč nikdy neopustí. Ak je to treba, hýbu sa objekty okolo.

Naivné riešenie tohoto problému bolo skúsiť implementovať systém inštancovania tak, aby sa pri tom ako hráč vizuálne prechádza hranou levelu prehodili inštancie tak, aby ich relatívna pozícia k hráčovi zostala rovnaká. Tento prístup by vyriešil problém blikania a potenciálne by mohol vyriešiť aj problém odlišného stavu simulácie (len vizuálne skoky, jednotlivé inštancie efektov by mali stále iný stav a boli by simulované oddelene). Po niekoľkých hodinách implementácie sa však ukázalo, že toto nie je správna cesta. Napísal som zhluk *#$! podmienok v 1 smere, ktoré by nakoniec museli fungovať v 6 smeroch.

Znovu a lepšie

V ideálnom prípade som chcel častice implementovať takým spôsobom, aby inštancie v opakovanom priestore použili na vykreslenie simulačný stav z "root cube". Po diskusii v tíme sme sa rozhodli tento prístup vyskúšať. A fungovalo to lepšie než som čakal.

V tomto bode som ešte uvažoval o možnosti systém implementovať ako predsimulované data, akúsi animáciu, ktorá by sa len prehrávala. Avšak po nejakej dobe som sa rozhodol implementovať simuláciu častíc ručne v kóde, primárne na zistenie komplexity. Pre ilustráciu som použil Debug.DrawLine, za ktorého pomoci som menil parametre a skúšal ako sa simulácia chová.

Kód efektu bol veľmi jednoduchý. Najskôr som ho napísal v Update() a po tom čo som s ním bol relatívne spokojný som ho preportoval do Job systému Unity s BurstCompile atribútom pre optimalizáciu. Vykreslovanie bolo trochu ťažšie kôli vlastnej implementácií transformácie. Štvoruholníky sú orientované v smere kamery. Vždy keď pracujem s shadermi v HDRP, zabudnem, že musím počítať s vykreslovaním relatívnym ku kamere. Výsledok však fungoval skvele a bol skoro totožný k originálu. Simulácia je veľmi lacná a beží pre každý efekt iba raz, zopakované inštancie používajú rovnaké data.

Simulované častice sú následne vykreslené na rôznych pozíciách nastavením transformácie s použítím MaterialPropertyBlock a funkciou DrawProcedural.

Čo sme stratili

V prvom rade sme definitívne stratili schopnosť rýchlych úprav efektu pre netechnických kolegov. Pre náš tím to však nie je až tak potrebné, pretože „nikto“ aj tak do hĺbky nepracoval s nástrojmi VFX v novom Unity.

Zároveň neprehadzujeme na tento system všetky efekty. V súčasnosti sú týmto spôsobom implementované iba častice vetra, ktoré fúkajú z potrubia, pretože iba tieto mali tento problém. Ak sa však problém vyskytne znova, viem, že ho môžem implementovať týmto spôsobom.

Osobne sa mi páčilo písať efekt ručne v kóde, na rozdiel od skladania logiky v GUI Unity.