Hogyan készítsen fake ragdollt ezernyi alakra Unity DOTS-ban

Hogyan hajítson el ezernyi ellenfelet robbanással Unity DOTS-ban csontfizika nélkül: test egy pontként, determinisztikus kilövés, járható landolás, forgás.

Robbanás által elhajított alakok íveken repülnek és pörögnek a levegőben

Ha egy robbanás a játékban egyszerre több száz ellenfelet repít el, mindegyiküknek igazi ragdollt adni túl drága. Fake ragdollt Unity DOTS-ban úgy készít, hogy az ellenfél teljes testét egyetlen pontként szimulálja saját egyszerű fizikával, a modellt pedig csak a test középpontja körül forgatja. Az esés és a felállás közönséges animációs klipek, csontokat egyáltalán nem szimulálunk.

A módszert egy PC-s tower defense játékon mutatjuk be, amelyet Unityben, ECS-sel fejlesztünk (bővebben a referenciáink között). Egy bomba vagy fireball ott egyszerre több tucat, akár több száz ellenfelet repít el. Ha 2000 test van a levegőben, a szimuláció tickenként 0,53-0,59 ms-ba kerül (a tick a játékszimuláció egy rögzített hosszúságú lépése).

Miért nem igazi ragdoll

Egy igazi ragdoll karakterenként tíz vagy több, ízületekkel összekötött fizikai test. Több ezer karakternél a képernyőn ez nem reális.

A másik ok a determinizmus: ugyanabból az állapotból ugyanannak az eredménynek kell kijönnie. A mi játékunkban erre épül egy elvesztett hullám újrajátszása egy mentési pontból, és ezt a szimuláció állapotának hashével ellenőrizzük 1800 tick után.

A test mint egyetlen pont

Fizikai motor helyett használjon saját, egyszerű 2,5D fizikát: pozíció a rácson, magasság a talaj felett, ballisztikus repülés, visszapattanás a falakról és súrlódás. A mi játékunkban a bomba is így mozog, és a robbanás által eltalált ellenfél egy pillanatra ugyanolyan testté válik. Az ötletet az adta, ahogyan a katonák repülnek a Diplomacy is Not an Option című játékban.

A szimuláció csak a pozíciót, a magasságot és az elforgatás szögét ismeri. A modell forgatását ne írja bele. Az az entitás kiegészítő transzformációs mátrixába tartozik, amelyet az Entities csomag (ECS a Unityben) a nem egyenletes skálázáshoz támogat (dokumentáció).

A ledöntött ellenfél fázisai

A ledöntött ellenfél három fázison megy át, aztán visszatér a tömegbe:

találatkor:
    számold ki a kilövési sebességet (erő, távolság a robbanástól, véletlen a seedből)
    keress landolást járható cellán (lásd lent)
    kapcsold be a "ledöntve" állapotot, szabadítsd fel a helyét a támadás célpontjánál
repülés:   a test parabolán repül, a modell a test középpontja körül forog
landolás:  testhelyzet a szögből (hát vagy has), rövid ráborulás
fekvés:    rögzített számú ticket vár
felállás:  felállási klip hátról vagy hasról, aztán vissza a tömegbe

A „ledöntve” állapotot olyan komponensként készítse el, amely ki- és bekapcsolható anélkül, hogy az entitást chunkok között kellene mozgatni (a chunk az a memóriablokk, amelyben az ECS az azonos komponenskészletű entitásokat tárolja). A navigáció, a tömegben való taszítás és a támadási döntés így a ledöntötteket egyszerűen kihagyja. Hogy hogyan működik a navigáció és a taszítás nagy tömegben, azt a horda navigációjáról szóló cikk Unity DOTS-ban írja le.

Kilövés: magasabbra, nem messzebbre

A legegyszerűbb kilövés 45° ± 10° szögben történik. 18 m/s bombaerőnél és 20 m/s² gravitációnál a repülés csúcspontja körülbelül 4 m. A test alacsonyan repül el a tömeg felett, és a hatás elvész benne.

Nagyobb erő nem segít, mert vele együtt nő a távolság is. Bontsa a sebességet két összetevőre, és mindegyiket külön szorozza: 60° ± 5° szög, vízszintes összetevő × 0,85, függőleges × 1,3. A csúcspont így körülbelül 10 m, a távolság marad nagyjából 15,5 m, a repülés pedig körülbelül 2 másodpercig tart.

Oldalnézet: egy alacsony szaggatott ív és egy magas ív egy ház felett, a repülés közben forgó alak sziluettjeivel
Szaggatottan a 45°-os kilövés, folytonos vonallal a 60°-os, külön szorzókkal. A ház 5 m magas.

Az irány ± 15°-kal tér el a robbanástól, az erő ± 20%-kal, a robbanás szélén pedig a felére csökken. A véletlent ne globális generátorból vegye, hanem a pálya seedjének, az ellenfél azonosítójának és a robbanás sorszámának hashéből. A ledöntési kéréseket rendezve dolgozza fel, mert a több szálon futó jobok tetszőleges sorrendben hozzák létre őket. Ugyanannak a hullámnak két futása így ugyanazokat az íveket adja.

A ledöntés igen vagy nem. Minden ledönthető ellenfél találatkor legalább elesik (a sebesség alulról 2 m/s-ra korlátozott), az ellenállás csak a távolságot változtatja. Az immunitást külön jelzővel adja meg, a mi játékunkban a legnagyobb ellenféltípusnak van ilyen. Ha az immunitás az ellenállásból következne (0,95-nél a sebesség 2 m/s alá esik), és a gyenge elhajítás törlődne, az ellenfél csak véletlenül lenne immunis.

Landolás járható cellán

Az ellenfél nem landolhat szakadékban, sziklán vagy egy torony alapterületén. A járhatóságot rácscellánként és minden ellenfél-méretosztályra külön határozzuk meg.

A levegőben falakról visszapattanás nem elég

Kézenfekvő ötlet a nem járható cellákat falként kezelni, amelyekről a repülő test visszapattan. Sziklánál ez természetesnek tűnik, szakadéknál nem: a szakadék felett levegő van, de a cella nem járható, így a test egy láthatatlan oszlopról pattan vissza a széle felett. Ráadásul semmi sem garantálja, hogy a visszapattanás után jó helyen ér földet.

Oldalnézet: egy test átrepül egy szakadék felett, repülés közben visszapattan egy láthatatlan oszlopról a széle felett, és visszaesik
Visszapattanás nem járható celláról. A test a szakadék széle feletti levegőről pattan vissza.

Pályaelőrejelzés a kilövéskor

Megbízhatóbb a landolást már a kilövéskor meghatározni. Szimulálja le előre a repülést ugyanazzal a lépéssel, mint a valódi fizikát, és ellenőrizze a landolási cellát az ellenfél méretosztályára. Ha nem járható, próbáljon további változatokat rögzített sorrendben:

a szögekre [0, +25, -25, +50, -50, ... , +175, -175]:
    a vízszintes sebesség szorzóira [1, 0.7, 0.4, 0.1]:
        szimuláld a repülést
        ha a landolási cella járható az ellenfél osztályának: használd ezt az irányt, és vége
ha semmi sem jött be: vízszintes sebesség = 0 (helyben ugrás)
Felülnézet egy rácsra szakadékkal és kiemelkedő sziklával, áthúzott elvetett landolásokkal és kiemelt első járható landolással
Robbanás, ellenfél, elvetett landolások és a kiválasztott. A szürke pontok az összes jelölt landolási hely.

A legrosszabb eset 60 repülésszimuláció ellenfelenként (15 irány × 4 szorzó). A függőleges sebesség nem változik, így a repülés magassága ugyanaz marad, csak a távolság rövidül. Ha repülés közben újabb robbanás éri az ellenfelet, futtassa újra az előrejelzést.

2D illusztráció: két ellenfél a szakadékba esne (keresztek), az előrejelzés lerövidíti a repülésüket.

Hogy a test landolás után ne csússzon és ne pattanjon, adjon neki 0 talajvisszapattanási együtthatót és nagy súrlódást. Ott marad, ahol az előrejelzés mondta.

A repülési magasságból tervezett forgás

A legegyszerűbb a modellt véletlen sebességgel forgatni, például 180-540 °/s-mal, és landolás után 0,2 s alatt a legközelebbi fekvő helyzetbe billenteni. A billentés azonban látszik, ha a test szinte függőlegesen ér földet. Ha a forgási sebességet minden tickben úgy igazítja, hogy a szög landoláskor pontosan vízszintes legyen, az csak részben segít: még egy alacsony dobás is szaltóként pörög, és a forgásnak semmi köze ahhoz, milyen magasan repül a test.

Jobb a forgást a kilövéskor a repülési magasságból megtervezni:

magasság = vz² / (2 g), repülési idő vz-ből és a magasságból
cél = legközelebbi vízszintes helyzet véletlen irányban (negyed fordulat állásból)
cél += fél fordulat minden 2,5 m magasságért, legfeljebb 3
forgási sebesség = (cél - aktuális szög) / repülési idő
Két ív: egy alacsony dobás, amelynél az alak csak eldől, és egy magas, amelynél három és negyed fordulatot tesz
Az alacsony dobás (csúcs kb. 2 m) 90°-ot fordul, a bomba (kb. 10 m) 90°-ot és három fél fordulatot.

A landolás oldala a szögből következik: arccal a föld felé has, egyébként hát. Ez alapján választódik ki a felállási klip. A fekvő vagy felálló ellenfél újabb találatkor megtartja aktuális szögét, és nem ugrik vissza függőleges helyzetbe.

A modell a talajra helyezése

A modell a test középpontja körül forog, a talaj viszont a legalsó pontja alatt van, és ez a forgással változik. Az eltolás becslése két konstansból és a szög szinuszából nem illik azokhoz a modellekhez, amelyek az esési klipben szélesre tárják a karjukat: a mi játékunkban a csontváz modellje (gyakori ellenfél) akár 1,15 m-re, a nekromantáé akár 1,73 m-re süllyedt a föld alá.

BecslésEgy alak hat elforgatása, a föld alatti részek pirosak
MértEgy alak hat elforgatása, mindegyik a legalsó pontjával érinti a földet
Ugyanazok az elforgatások a konstansokból és a mért táblázatból számolt eltolással.

Megbízhatóbb a legalsó pontot megmérni. Az animációk sütésekor (bake) számolja ki az esési klipre a test legalsó pontját 72 elforgatásnál 5°-os lépésekben, futás közben pedig interpoláljon a táblázatból. A legmélyebb süllyedés így 4 mm-re csökken. Egyetlen minimum a klip összes képkockáján át azonban nem elég: a test landolás után 0,15-0,28 m-rel a talaj felett lebegne, amíg a felállási klip le nem húzza. A táblázatnak ezért képkockák × elforgatások méretűnek kell lennie.

A lassú, tervezett forgásnál még egy hatás előjön. Ha a modellt a legalsó pontjával a repülési magasságra teszi, a test középpontja az állásból vízszintesbe fordulás közben a fél magasság és a fél vastagság különbségével lesüllyed. A test ekkor a repülés elején csak lassan emelkedik, és csak utána gyorsul. Emelje meg ezért a modellt az állásbeli és az aktuális szögnél mért fél kiterjedés különbségével, legfeljebb a repülési magassággal. A test középpontja így parabolán halad.

A test középpontjának magassága az idő függvényében: szaggatott ideális parabola, egy behorpadó piros görbe és egy kék, amely követi a parabolát
A test középpontjának magassága repülés közben: emelés nélkül (piros) és emeléssel (kék).

Halott ellenfelek: test, decal és por

A robbanásban meghalt ellenfélnek is el kell repülnie. Hozzon létre számára repülő testet: az entitás másolatát játékmenet-komponensek nélkül (életerő, jutalom, célpont). A test elrepül, földet ér, egy ideig fekszik, majd eltűnik. A helyén holttest-decal (a talajra vetített textúra) marad, mint a Warcraft 3-ban, és egy kis porfelhő. A mi játékunkban a felső határ 1000 repülő test és 1500 decal.

A decal felülnézet az esési klip egy képkockájáról, ugyanúgy elforgatva, mint a fekvő test, így a test decalra cserélése nem látszik. A felállási klip első képkockája nem jó, mert azon az alak a hátáról már felül. A porfelhőket vonja össze 1,5 m-es cellán belül. Egy bomba a tömegbe így néhány nagyobb felhőt hoz létre több száz apró helyett.

Csapdák a Unity Entitiesben

  • A bakerből származó mátrix eltűnik. Ha a kiegészítő mátrixot bake közben adja hozzá, a transzformációs rendszer egyenletes skálánál eltávolítja, és a ledöntött ellenfelek nem mozognak. Bake közben ezért kérjen nem egyenletes skálát. Ekkor viszont a prefab teljes skálája ugyanabba a mátrixba kerül, tehát ne írja felül puszta forgatással. Különben egy 2×-es méretű ledöntött ellenfél fele akkora lesz, felállás után is. A skálát tárolja külön, a mátrixot pedig skálából és forgatásból állítsa össze.
  • A gyökér magassága a klipek importjakor. Humanoid klipeknél a gyökér (root) függőleges mozgása elvész, és a karakterek felálláskor átesnek a padlón. Süsse bele a gyökér magasságát a pózba. A póz lábfejhez rögzítésének opcióját ne használja, az egy méterrel lejjebb tolja az egész testet.
  • Zárt alakú mátrix. Az eltolás, a forgatás és a visszatolás mint három mátrixszorzás egy jobban mérhetően lassabb, mint az eredő mátrix kézzel megírt képlete.
  • Burst régi verzióval. Ha módosítja egy Burst job által olvasott komponens mezőit, az editor összeomolhat vagy értelmetlen hibákat jelezhet. Az editor újraindítása segít.

Teljesítmény

Mérve a Unity Editorban M3 Prón, 10 000 csontvázzal egy benchmark jelenetben, 18-as kilövési erővel. Az idők a Burst jobok CPU-idejének összegei az összes szálon, nem a képkocka ideje. A grafikon és a táblázat pályaelőrejelzés és landolási táblázatok nélkül készült.

A tickenkénti idő grafikonja a ledöntött ellenfelek száma szerint a szimulációra és a vizuális előkészítésre, 0,5 ms-os célvonallal
Szimuláció és vizuális előkészítés pályaelőrejelzés nélkül. Cél 0,5 ms 2000 ledöntöttnél.
ledöntött szimuláció (ms/tick) vizuális előkészítés (ms)
500 0,28-0,30 0,15
2000 0,53 0,30-0,55
5000 0,74-0,75 0,49-0,91
10 000 0,96-1,01 0,68-1,24
  • A szimuláció lineárisan nő, ezer ledöntöttenként nagyjából 0,25 ms-mal. Pályaelőrejelzéssel és landolási táblázatokkal 2000 ledöntöttnél 0,58-0,59 ms, vagyis kicsivel a 0,5 ms-os cél felett. Észszerű felső határ ezért egyszerre 3000 ledöntött ellenfél, ami körülbelül 0,8 ms.
  • Egy robbanás egy fal melletti 359 fős csoportba a ledöntési kérések rendszerében egyetlen képkockában 1,3-1,9 ms-ba kerül. Ennek nagy részét a pályaelőrejelzés teszi ki.
  • Az editor FPS-értékei nem hasonlíthatók össze: ugyanannak a konfigurációnak két futása akár 80 FPS-sel is eltért.

A megoldás korlátai

A vizuális előkészítés egy másik tesztjelenetben akár 1,3 ms-ra is nőtt, valószínűleg a landolási táblázat minden testnél történő olvasása miatt. Ez az első jelölt az optimalizálásra. Az FPS-t buildben és 1000 ellenfél egyidejű megölésének terhelését (cél: legfeljebb 1,5 ms többlet képkockánként) még nem mértük.

Ha hasonló tömeg- vagy effektszimuláción dolgozik Unityben, és segítségre van szüksége, írjon nekünk.

← Továbbiak: Tower Defense