Ako spraviť fake ragdoll pre tisíce postáv v Unity DOTS
Ako odhodiť tisíce nepriateľov výbuchom v Unity DOTS bez fyziky kostí: telo ako jeden bod, deterministický odpal, dopad na priechodnú bunku a rotácia.

Keď výbuch v hre odhodí stovky nepriateľov naraz, skutočný ragdoll pre každého z nich je príliš drahý. Fake ragdoll v Unity DOTS spravíte tak, že celé telo nepriateľa simulujete ako jeden bod s vlastnou jednoduchou fyzikou a model iba otáčate okolo stredu tela. Pád a vstávanie sú obyčajné animačné klipy, kosti sa nesimulujú vôbec.
Postup ukazujeme na tower defense hre pre PC, ktorú robíme v Unity s ECS (viac v referenciách). Bomba alebo fireball tam vymršťuje desiatky až stovky nepriateľov naraz. Pri 2 000 telách vo vzduchu stojí simulácia 0,53 až 0,59 ms na tik (tik je jeden krok hernej simulácie s pevnou dĺžkou).
Prečo nie skutočný ragdoll
Skutočný ragdoll je desať a viac fyzikálnych telies spojených kĺbmi na každú postavu. Pri tisíckach postáv na obrazovke to nie je reálne.
Druhý dôvod je determinizmus: z rovnakého stavu musí vyjsť rovnaký výsledok. V našej hre na tom stojí opakovanie prehratej vlny z kontrolného bodu a kontrolujeme to hashom stavu simulácie po 1 800 tikoch.
Telo ako jeden bod
Namiesto fyzikálneho enginu použite vlastnú jednoduchú 2,5D fyziku: poloha na mriežke, výška nad zemou, balistický let, odraz od stien a trenie. V našej hre sa takto pohybuje aj bomba a nepriateľ zasiahnutý výbuchom sa na chvíľu stane takým istým telesom. Inšpiráciou bolo, ako odletujú vojaci v hre Diplomacy is Not an Option.
Simulácia pozná len polohu, výšku a uhol natočenia. Otáčanie modelu do nej nezapisujte. Patrí do dodatočnej transformačnej matice entity, ktorú balík Entities (ECS v Unity) podporuje pre nerovnomernú mierku (dokumentácia).
Fázy zrazeného nepriateľa
Zrazený nepriateľ prejde tromi fázami a potom sa vráti do davu:
pri zásahu:
vypočítaj rýchlosť odpalu (sila, vzdialenosť od výbuchu, náhoda zo seedu)
nájdi dopad na priechodnú bunku (pozri nižšie)
zapni stav "zrazený", uvoľni miesto, ktoré mal obsadené pri cieli útoku
let: teleso letí po parabole, model sa otáča okolo stredu tela
dopad: poloha podľa natočenia (chrbát alebo brucho), krátke doklopenie
ľah: čaká pevný počet tikov
vstávanie: klip vstávania z chrbta alebo z brucha, potom späť do davu
Stav „zrazený“ spravte ako komponent, ktorý sa dá zapnúť a vypnúť bez presúvania entity medzi chunkmi (pamäťovými blokmi, v ktorých ECS drží entity s rovnakou sadou komponentov). Navigácia, odtláčanie v dave aj rozhodovanie o útoku zrazených jednoducho preskočia. Ako funguje navigácia a odtláčanie veľkého davu, popisuje článok o navigácii hordy v Unity DOTS.
Odpal: vyššie, nie ďalej
Najjednoduchší odpal je pod uhlom 45° ± 10°. Pri sile bomby 18 m/s a gravitácii 20 m/s² vychádza vrchol letu okolo 4 m. Telo preletí nízko nad davom a efekt sa v ňom stratí.
Vyššia sila nepomôže, lebo s ňou rastie aj dolet. Rozdeľte rýchlosť na dve zložky a každú násobte zvlášť: uhol 60° ± 5°, vodorovná zložka × 0,85, zvislá × 1,3. Vrchol je potom okolo 10 m, dolet ostane okolo 15,5 m a let trvá približne 2 sekundy.

Smer sa od výbuchu odchyľuje o ± 15°, sila o ± 20 % a na okraji výbuchu klesá na polovicu. Náhodu neberte z globálneho generátora, ale z hashu seedu levelu, identifikátora nepriateľa a poradového čísla výbuchu. Požiadavky na zrazenie spracúvajte zoradené, lebo joby bežiace na viacerých vláknach ich vytvoria v ľubovoľnom poradí. Dva behy tej istej vlny tak dajú tie isté oblúky.
Odhodenie je áno alebo nie. Každý zraziteľný nepriateľ pri zásahu aspoň spadne (rýchlosť je zdola obmedzená na 2 m/s), odolnosť mení len dolet. Imunitu dajte samostatným príznakom, v našej hre ho má najväčší typ nepriateľa. Keby imunita vyplývala z odolnosti (pri 0,95 vychádza rýchlosť pod 2 m/s) a slabé odhodenie by sa rušilo, nepriateľ by bol imúnny len náhodou.
Dopad na priechodnú bunku
Nepriateľ nesmie dopadnúť do rokliny, na útes ani do pôdorysu veže. Priechodnosť sa určuje po bunkách mriežky a pre každú veľkostnú triedu nepriateľa zvlášť.
Odraz od stien vo vzduchu nestačí
Prvé, čo napadne, je brať nepriechodné bunky ako steny, od ktorých sa letiace telo odrazí. Pri útese to vyzerá prirodzene, pri rokline nie: nad roklinou je vzduch, ale bunka je nepriechodná, takže sa telo odrazí od neviditeľného stĺpa nad jej okrajom. Navyše nič nezaručuje, že po odraze skončí na dobrom mieste.

Predikcia dráhy pri odpale
Spoľahlivejšie je zistiť dopad hneď pri odpale. Let nasimulujte dopredu tým istým krokom ako skutočnú fyziku a skontrolujte bunku dopadu pre veľkostnú triedu nepriateľa. Ak nie je priechodná, skúšajte ďalšie varianty v pevnom poradí:
pre uhol v [0, +25, -25, +50, -50, ... , +175, -175]:
pre násobok vodorovnej rýchlosti v [1, 0.7, 0.4, 0.1]:
nasimuluj let
ak je bunka dopadu priechodná pre triedu nepriateľa: použi tento smer a koniec
ak nič nevyšlo: vodorovná rýchlosť = 0 (výskok na mieste)

Najhorší prípad je 60 simulácií letu na jedného nepriateľa (15 smerov × 4 násobky). Zvislá rýchlosť sa nemení, takže výška letu ostáva rovnaká a skracuje sa len dolet. Ak nepriateľa počas letu zasiahne ďalší výbuch, predikciu spustite znova.
Aby telo po dopade neskĺzlo ani neodskočilo, nastavte mu koeficient odrazu od zeme 0 a veľké trenie. Ostane tam, kde predikcia povedala.
Rotácia naplánovaná podľa výšky letu
Najjednoduchšie je točiť model náhodnou rýchlosťou, napríklad 180 až 540 °/s, a po dopade ho za 0,2 s doklopiť na najbližší ľah. Doklopenie je však vidieť, keď telo dopadne takmer vzpriamene. Dorovnávať rýchlosť otáčania v každom tiku tak, aby uhol pri dopade vyšiel presne vodorovne, pomôže len čiastočne: aj nízky odhod sa točí ako salto a otáčanie nesúvisí s tým, ako vysoko telo letí.
Lepšie je naplánovať rotáciu pri odpale z výšky letu:
výška = vz² / (2 g), čas letu z vz a výšky
cieľ = najbližšia vodorovná poloha v náhodnom smere (štvrť otočky zo stoja)
cieľ += polotočka za každých 2,5 m výšky, najviac 3
rýchlosť otáčania = (cieľ - aktuálny uhol) / čas letu

Strana dopadu vyplýva z uhla: tvárou k zemi znamená brucho, inak chrbát. Podľa nej sa vyberie klip vstávania. Ležiaci alebo vstávajúci nepriateľ si pri ďalšom zásahu ponechá aktuálny uhol a neskočí späť do vzpriamenej polohy.
Model položený na zem
Model sa otáča okolo stredu tela, ale zem je pod jeho najnižším bodom a ten sa pri otáčaní mení. Odhad posunu z dvoch konštánt a sínusu uhla nesedí pre modely s rozhodenými rukami v klipe pádu: v našej hre sa model kostry (bežný nepriateľ) zanáral až 1,15 m pod zem a model nekromanta až 1,73 m.


Spoľahlivejšie je najnižší bod zmerať. Pri zapekaní (bake) animácií spočítajte pre klip pádu najnižší bod tela pri 72 natočeniach po 5° a za behu z tabuľky interpolujte. Najhlbšie zanorenie tak klesne na 4 mm. Jedno minimum cez všetky snímky klipu však nestačí: telo by po dopade viselo 0,15 až 0,28 m nad zemou, kým ho klip vstávania nestiahne dole. Tabuľka preto musí mať rozmer snímky × natočenia.
Pri pomalej naplánovanej rotácii sa ukáže ešte jeden efekt. Ak model kladiete najnižším bodom na výšku letu, stred tela pri prechode zo stoja do vodorovnej polohy klesne o rozdiel polovice výšky a polovice hrúbky postavy. Telo potom na začiatku letu stúpa len pomaly a až potom zrýchli. Model preto zdvihnite o rozdiel polovičného rozmeru v stoji a pri aktuálnom uhle, najviac o výšku letu. Stred tela potom ide po parabole.

Mŕtvi nepriatelia: telo, decal a prach
Nepriateľ zabitý výbuchom má odletieť tiež. Vytvorte preň letiace telo: kópiu entity bez herných komponentov (životy, odmena, cieľ). Telo preletí, dopadne, chvíľu leží a zmizne. Na jeho mieste ostane decal mŕtvoly (textúra premietnutá na zem) ako vo Warcrafte 3 a obláčik prachu. V našej hre je strop 1 000 letiacich tiel a 1 500 decalov.
Decal je pohľad zhora na snímku z klipu pádu, natočený rovnako ako ležiace telo, takže výmena tela za decal nie je vidieť. Prvá snímka klipu vstávania sa nehodí, lebo postavu z chrbta už posadí. Obláčiky prachu zlučujte v bunke 1,5 m. Bomba do davu tak vytvorí pár väčších oblakov namiesto stoviek malých.
Pasce v Unity Entities
- Matica z bakera zmizne. Ak dodatočnú maticu pridáte pri bake, transformačný systém ju pri rovnomernej mierke odstráni a zrazení sa nehýbu. Pri bake si preto vyžiadajte nerovnomernú mierku. Vtedy sa však do tej istej matice presunie celá mierka prefabu, takže ju neprepisujte samotnou rotáciou. Inak má zrazený nepriateľ so zväčšením 2× polovičnú veľkosť, aj keď vstane. Mierku si uložte zvlášť a maticu skladajte z mierky a rotácie.
- Výška koreňa pri importe klipov. Pri humanoid klipoch sa zvislý pohyb koreňa (root) zahodí a postavy pri vstávaní prepadnú pod podlahu. Výšku koreňa zapečte do pózy. Voľbu ukotviť pózu na chodidlá nepoužívajte, posunie celé telo o meter dole.
- Matica v uzavretom tvare. Posun, rotácia a spätný posun ako tri násobenia matíc sú v jobe merateľne pomalšie než ručne napísaný vzorec pre výslednú maticu.
- Burst so starou verziou. Po zmene polí komponentu, ktorý číta Burst job, môže editor padať alebo hlásiť nezmysly. Pomôže reštart editora.
Výkon
Merané v Unity Editore na M3 Pro, 10 000 kostier v benchmark scéne, sila odpalu 18. Časy sú súčet CPU času Burst jobov cez všetky vlákna, nie čas snímky. Graf a tabuľka sú bez predikcie dráhy a tabuliek dopadu.

| zrazených | simulácia (ms/tik) | prí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 |
- Simulácia rastie lineárne, zhruba o 0,25 ms na tisíc zrazených. S predikciou dráhy a tabuľkami dopadu je to pri 2 000 zrazených 0,58 až 0,59 ms, teda mierne nad cieľom 0,5 ms. Rozumný strop je preto 3 000 súčasne zrazených, čo je okolo 0,8 ms.
- Výbuch do skupiny 359 nepriateľov pri stene stojí v systéme požiadaviek na zrazenie 1,3 až 1,9 ms v jednej snímke. Väčšinu tvorí predikcia dráhy.
- FPS z editora sa porovnať nedá, medzi dvoma behmi tej istej konfigurácie bol rozdiel až 80 FPS.
Limity riešenia
Príprava vizuálu stúpla v inej testovacej scéne až na 1,3 ms, pravdepodobne pre čítanie tabuľky dopadu pre každé telo. Je to prvý kandidát na optimalizáciu. FPS v builde a záťaž pri hromadnom zabití 1 000 nepriateľov naraz (cieľ do 1,5 ms navyše na snímku) zatiaľ nemáme zmerané.
Ak riešite podobnú simuláciu davu alebo efektov v Unity a potrebujete pomoc, napíšte nám.


