Jak udělat fake ragdoll pro tisíce postav v Unity DOTS

Jak odhodit tisíce nepřátel výbuchem v Unity DOTS bez fyziky kostí: tělo jako jeden bod, deterministický odpal, dopad na průchozí buňku a rotace.

Postavy vymrštěné výbuchem letí po obloucích a otáčejí se ve vzduchu

Když výbuch ve hře odhodí stovky nepřátel najednou, skutečný ragdoll pro každého z nich je příliš drahý. Fake ragdoll v Unity DOTS uděláte tak, že celé tělo nepřítele simulujete jako jeden bod s vlastní jednoduchou fyzikou a model jen otáčíte kolem středu těla. Pád a vstávání jsou obyčejné animační klipy, kosti se nesimulují vůbec.

Postup ukazujeme na tower defense hře pro PC, kterou děláme v Unity s ECS (více v referencích). Bomba nebo fireball tam vymršťuje desítky až stovky nepřátel najednou. Při 2 000 tělech ve vzduchu stojí simulace 0,53 až 0,59 ms na tik (tik je jeden krok herní simulace s pevnou délkou).

Proč ne skutečný ragdoll

Skutečný ragdoll je deset a více fyzikálních těles spojených klouby na každou postavu. Při tisících postav na obrazovce to není reálné.

Druhý důvod je determinismus: ze stejného stavu musí vyjít stejný výsledek. V naší hře na tom stojí opakování prohrané vlny z kontrolního bodu a kontrolujeme to hashem stavu simulace po 1 800 ticích.

Tělo jako jeden bod

Místo fyzikálního enginu použijte vlastní jednoduchou 2,5D fyziku: poloha na mřížce, výška nad zemí, balistický let, odraz od stěn a tření. V naší hře se takto pohybuje i bomba a nepřítel zasažený výbuchem se na chvíli stane stejným tělesem. Inspirací bylo, jak odlétají vojáci ve hře Diplomacy is Not an Option.

Simulace zná jen polohu, výšku a úhel natočení. Otáčení modelu do ní nezapisujte. Patří do dodatečné transformační matice entity, kterou balík Entities (ECS v Unity) podporuje pro nerovnoměrné měřítko (dokumentace).

Fáze sraženého nepřítele

Sražený nepřítel projde třemi fázemi a pak se vrátí do davu:

při zásahu:
    spočítej rychlost odpalu (síla, vzdálenost od výbuchu, náhoda ze seedu)
    najdi dopad na průchozí buňku (viz níže)
    zapni stav "sražený", uvolni místo, které měl obsazené u cíle útoku
let:       těleso letí po parabole, model se otáčí kolem středu těla
dopad:     poloha podle natočení (záda nebo břicho), krátké doklopení
leh:       čeká pevný počet tiků
vstávání:  klip vstávání ze zad nebo z břicha, pak zpět do davu

Stav „sražený“ udělejte jako komponentu, kterou lze zapnout a vypnout bez přesouvání entity mezi chunky (paměťovými bloky, ve kterých ECS drží entity se stejnou sadou komponent). Navigace, odtlačování v davu i rozhodování o útoku pak sražené jednoduše přeskočí. Jak funguje navigace a odtlačování velkého davu, popisuje článek o navigaci hordy v Unity DOTS.

Odpal: výš, ne dál

Nejjednodušší odpal je pod úhlem 45° ± 10°. Při síle bomby 18 m/s a gravitaci 20 m/s² vychází vrchol letu kolem 4 m. Tělo přeletí nízko nad davem a efekt se v něm ztratí.

Vyšší síla nepomůže, protože s ní roste i dolet. Rozdělte rychlost na dvě složky a každou násobte zvlášť: úhel 60° ± 5°, vodorovná složka × 0,85, svislá × 1,3. Vrchol je pak kolem 10 m, dolet zůstane kolem 15,5 m a let trvá přibližně 2 sekundy.

Boční pohled: nízký přerušovaný oblouk a vysoký oblouk přes dům se siluetami postavy, která se během letu otáčí
Přerušovaně odpal pod 45°, plnou čarou odpal pod 60° s oddělenými násobky. Dům má 5 m.

Směr se od výbuchu odchyluje o ± 15°, síla o ± 20 % a na okraji výbuchu klesá na polovinu. Náhodu neberte z globálního generátoru, ale z hashe seedu levelu, identifikátoru nepřítele a pořadového čísla výbuchu. Požadavky na sražení zpracovávejte seřazené, protože joby běžící na více vláknech je vytvoří v libovolném pořadí. Dva běhy téže vlny tak dají stejné oblouky.

Odhození je ano, nebo ne. Každý sražitelný nepřítel při zásahu aspoň upadne (rychlost je zdola omezená na 2 m/s), odolnost mění jen dolet. Imunitu dejte samostatným příznakem, v naší hře ho má největší typ nepřítele. Kdyby imunita vyplývala z odolnosti (při 0,95 vychází rychlost pod 2 m/s) a slabé odhození by se rušilo, byl by nepřítel imunní jen náhodou.

Dopad na průchozí buňku

Nepřítel nesmí dopadnout do rokle, na útes ani do půdorysu věže. Průchodnost se určuje po buňkách mřížky a pro každou velikostní třídu nepřítele zvlášť.

Odraz od stěn ve vzduchu nestačí

První, co napadne, je brát neprůchozí buňky jako stěny, od kterých se letící tělo odrazí. U útesu to vypadá přirozeně, u rokle ne: nad roklí je vzduch, ale buňka je neprůchozí, takže se tělo odrazí od neviditelného sloupu nad jejím okrajem. Navíc nic nezaručuje, že po odrazu skončí na dobrém místě.

Boční pohled: tělo letí přes rokli, uprostřed letu se odrazí od neviditelného sloupu nad jejím okrajem a spadne zpět
Odraz od neprůchozí buňky. Tělo se odrazí od vzduchu nad okrajem rokle.

Predikce dráhy při odpalu

Spolehlivější je zjistit dopad hned při odpalu. Let nasimulujte dopředu stejným krokem jako skutečnou fyziku a zkontrolujte buňku dopadu pro velikostní třídu nepřítele. Pokud není průchozí, zkoušejte další varianty v pevném pořadí:

pro úhel v [0, +25, -25, +50, -50, ... , +175, -175]:
    pro násobek vodorovné rychlosti v [1, 0.7, 0.4, 0.1]:
        nasimuluj let
        pokud je buňka dopadu průchozí pro třídu nepřítele: použij tento směr a konec
pokud nic nevyšlo: vodorovná rychlost = 0 (výskok na místě)
Pohled shora na mřížku s roklí a vyvýšeným útesem, přeškrtnuté zamítnuté dopady a zvýrazněný první průchozí
Výbuch, nepřítel, zamítnuté dopady a vybraný. Šedé body jsou všechny kandidátní dopady.

Nejhorší případ je 60 simulací letu na jednoho nepřítele (15 směrů × 4 násobky). Svislá rychlost se nemění, takže výška letu zůstává stejná a zkracuje se jen dolet. Pokud nepřítele během letu zasáhne další výbuch, spusťte predikci znovu.

Ilustrace ve 2D: dva nepřátelé by dopadli do rokle (křížky), predikce jim zkrátí let.

Aby tělo po dopadu neklouzalo ani neodskočilo, nastavte mu koeficient odrazu od země 0 a velké tření. Zůstane tam, kde predikce řekla.

Rotace naplánovaná podle výšky letu

Nejjednodušší je točit model náhodnou rychlostí, například 180 až 540 °/s, a po dopadu ho za 0,2 s doklopit do nejbližšího lehu. Doklopení je ale vidět, když tělo dopadne téměř vzpřímeně. Dorovnávat rychlost otáčení v každém tiku tak, aby úhel při dopadu vyšel přesně vodorovně, pomůže jen částečně: i nízký odhoz se točí jako salto a otáčení nesouvisí s tím, jak vysoko tělo letí.

Lepší je naplánovat rotaci při odpalu z výšky letu:

výška = vz² / (2 g), čas letu z vz a výšky
cíl = nejbližší vodorovná poloha v náhodném směru (čtvrt otáčky ze stoje)
cíl += půlotáčka za každých 2,5 m výšky, nejvýš 3
rychlost otáčení = (cíl - aktuální úhel) / čas letu
Dva oblouky: nízký odhoz, při kterém se postava jen převalí, a vysoký, při kterém udělá tři a čtvrt otáčky
Nízký odhoz (vrchol kolem 2 m) udělá 90°, bomba (kolem 10 m) 90° a tři půlotáčky.

Strana dopadu vyplývá z úhlu: obličejem k zemi znamená břicho, jinak záda. Podle ní se vybere klip vstávání. Ležící nebo vstávající nepřítel si při dalším zásahu ponechá aktuální úhel a neskočí zpět do vzpřímené polohy.

Model položený na zem

Model se otáčí kolem středu těla, ale země je pod jeho nejnižším bodem a ten se při otáčení mění. Odhad posunu ze dvou konstant a sinu úhlu nesedí pro modely s rozhozenýma rukama v klipu pádu: v naší hře se model kostlivce (běžný nepřítel) zanořoval až 1,15 m pod zem a model nekromanta až 1,73 m.

OdhadŠest natočení postavy, části pod zemí jsou červené
ZměřenoŠest natočení postavy, každé se dotýká země nejnižším bodem
Stejná natočení s posunem z konstant a ze změřené tabulky.

Spolehlivější je nejnižší bod změřit. Při zapékání (bake) animací spočítejte pro klip pádu nejnižší bod těla při 72 natočeních po 5° a za běhu z tabulky interpolujte. Nejhlubší zanoření tak klesne na 4 mm. Jedno minimum přes všechny snímky klipu ale nestačí: tělo by po dopadu viselo 0,15 až 0,28 m nad zemí, dokud ho klip vstávání nestáhne dolů. Tabulka proto musí mít rozměr snímky × natočení.

Při pomalé naplánované rotaci se ukáže ještě jeden efekt. Pokud model pokládáte nejnižším bodem na výšku letu, střed těla při přechodu ze stoje do vodorovné polohy klesne o rozdíl poloviny výšky a poloviny tloušťky postavy. Tělo pak na začátku letu stoupá jen pomalu a teprve potom zrychlí. Model proto zvedněte o rozdíl polovičního rozměru ve stoje a při aktuálním úhlu, nejvýš o výšku letu. Střed těla pak jde po parabole.

Graf výšky středu těla v čase: přerušovaná ideální parabola, červená křivka s poklesem a modrá, která parabolu sleduje
Výška středu těla během letu: bez zdvihu (červená) a se zdvihem (modrá).

Mrtví nepřátelé: tělo, decal a prach

Nepřítel zabitý výbuchem má odletět také. Vytvořte pro něj letící tělo: kopii entity bez herních komponent (životy, odměna, cíl). Tělo přeletí, dopadne, chvíli leží a zmizí. Na jeho místě zůstane decal mrtvoly (textura promítnutá na zem) jako ve Warcraftu 3 a obláček prachu. V naší hře je strop 1 000 letících těl a 1 500 decalů.

Decal je pohled shora na snímek z klipu pádu, natočený stejně jako ležící tělo, takže výměna těla za decal není vidět. První snímek klipu vstávání se nehodí, protože postavu ze zad už posadí. Obláčky prachu slučujte v buňce 1,5 m. Bomba do davu tak vytvoří pár větších mraků místo stovek malých.

Pasti v Unity Entities

  • Matice z bakeru zmizí. Pokud dodatečnou matici přidáte při bake, transformační systém ji při rovnoměrném měřítku odstraní a sražení se nehýbou. Při bake si proto vyžádejte nerovnoměrné měřítko. Tehdy se ale do stejné matice přesune celé měřítko prefabu, takže ho nepřepisujte samotnou rotací. Jinak má sražený nepřítel se zvětšením 2× poloviční velikost, i když vstane. Měřítko si uložte zvlášť a matici skládejte z měřítka a rotace.
  • Výška kořene při importu klipů. U humanoid klipů se svislý pohyb kořene (root) zahodí a postavy při vstávání propadnou pod podlahu. Výšku kořene zapečte do pózy. Volbu ukotvit pózu na chodidla nepoužívejte, posune celé tělo o metr dolů.
  • Matice v uzavřeném tvaru. Posun, rotace a zpětný posun jako tři násobení matic jsou v jobu měřitelně pomalejší než ručně napsaný vzorec pro výslednou matici.
  • Burst se starou verzí. Po změně polí komponenty, kterou čte Burst job, může editor padat nebo hlásit nesmysly. Pomůže restart editoru.

Výkon

Měřeno v Unity Editoru na M3 Pro, 10 000 kostlivců v benchmark scéně, síla odpalu 18. Časy jsou součet CPU času Burst jobů přes všechna vlákna, ne čas snímku. Graf a tabulka jsou bez predikce dráhy a tabulek dopadu.

Graf času na tik podle počtu sražených nepřátel pro simulaci a přípravu vizuálu, s cílovou čarou 0,5 ms
Simulace a příprava vizuálu bez predikce dráhy. Cíl 0,5 ms při 2 000 sražených.
sražených simulace (ms/tik) příprava vizuálu (ms)
500 0,28 až 0,30 0,15
2 000 0,53 0,30 až 0,55
5 000 0,74 až 0,75 0,49 až 0,91
10 000 0,96 až 1,01 0,68 až 1,24
  • Simulace roste lineárně, zhruba o 0,25 ms na tisíc sražených. S predikcí dráhy a tabulkami dopadu je to při 2 000 sražených 0,58 až 0,59 ms, tedy mírně nad cílem 0,5 ms. Rozumný strop je proto 3 000 současně sražených, což je kolem 0,8 ms.
  • Výbuch do skupiny 359 nepřátel u stěny stojí v systému požadavků na sražení 1,3 až 1,9 ms v jednom snímku. Většinu tvoří predikce dráhy.
  • FPS z editoru se porovnat nedá, mezi dvěma běhy stejné konfigurace byl rozdíl až 80 FPS.

Limity řešení

Příprava vizuálu stoupla v jiné testovací scéně až na 1,3 ms, pravděpodobně kvůli čtení tabulky dopadu pro každé tělo. Je to první kandidát na optimalizaci. FPS v buildu a zátěž při hromadném zabití 1 000 nepřátel najednou (cíl do 1,5 ms navíc na snímek) zatím nemáme změřené.

Pokud řešíte podobnou simulaci davu nebo efektů v Unity a potřebujete pomoc, napište nám.

← Další v kategorii Tower Defense