Talán 15 éve próbálkoztam először Turbo Pascal nyelven az axonometrikus ábrázolással. Kb. ennyiben ki is merült a kapcsolatom a 3D-s képalkotással, egészen addig, amíg kaptam egy érdekes feladatot. A projekt elején azt hittem, egy jó library mindenre megoldás, a végére rájöttem, hogy kellő tapasztalat nélkül elég kínkeserves is lehet a folyamat. Okulás gyanánt és akár kedvcsinálóként ajánlom mindenkinek az írásomat :)
A brief
Kaptunk egy rövid brief-et egy esetleges projektről. A feladat egy egyszerű játék elkészítése, ahol forgó poháron lévő pöttyök eltűnnek, a játékosoknak pedig az a feladata, hogy a hiányzó területekre kattintva pótolják a pöttyöket. Ezáltal pontokat szereznek, és minden kattintáskor gyorsul a pohár forgása. Ha egy körön belül nem tudnak rákattintani a hiányzó részre, a játék véget ér.
Three.js
A leírást elolvasva azonnal rávágtam, hogy kellemes projekt, új kihívásokkal és a three.js kell nekünk! A three.js egy zseniális könyvtár, amivel relatíve egyszerűen lehet 3D-s környezeteket megjeleníteni (ahogy ők írják: "... for dummies"). További előnye, hogy webGL, Canvas, CSS3d és SVG renderelést is támogat. Korábban már ugyan próbálgattam a three.js lehetőségeit, így nem a nulláról indultam, de éles projektben még nem használtam.
Az alapkoncepció
A fentieket elolvasva, mi sem egyszerűbb, mint megcsinálni a poharat. Két elemből össze is állítható: Egy körgyűrű (tórusz) adja a karimáját, egy nem szabályos henger pedig a testét. Ezeket textúráztam, létrehoztam pár kattintható felületet a megfelelő helyen és meg is voltam. Legalábbis ezt gondoltam az elején.
És akkor elkezdtem kódolni...
Maga a three.js környezet létrehozása viszonylag egyszerű feladat. Behúzod a libet, pár kiegészítőt (mivel le kell kezelni azt az esetet is, ha nincs webGL támogatás), definiálod az alapértékeket és kezdődhet is a móka. Egyébként számos boilerplate és generátor elérhető erre a célra. A két geometriai elem létrehozása, méretezése és pozícionálása nem okozott nagyobb fejtörést, főleg, hogy a three.js kiváló dokumentációval rendelkezik. Kis játék a fényekkel és az anyagok színezésével, és elő is állt az alap.
Kell rá egy logó!
És el is érkeztem az első részhez, ami már kisebb fejtörést okozott. Mivel a logó maga is kattintható felület, úgy gondoltam, hogy külön geometriai elemként hozom létre azt, így könnyű lesz az interakciót és a láthatóságát lekezelni. Ez mind egyszerűen hangzik, de egy nem szabályos henger esetén már picit problémásabb. Mégsem oldhatom ugye meg egy egyszerű négyzettel, hiszen a felület, amire illeszteni akarom nem egyenes. Szerencsére a poharat felépítő háromszögek (face) koordinátáit könnyen meg lehet határozni, úgy gondoltam, célszerű azokból kiindulnom.
Örömmel konstatáltam, hogy az első felmerülő problémát rövid idő alatt meg is oldottam, egy újrafelhasználható függvény jött létre eredményeként, amit elő tudtam venni, ha a pohár testén kellett saját geometriai elemeket létrehoznom.
Már csak textúrázni kell és teljes az öröm, van logó a poháron. Na igen, csak textúrázni…
Előre definiált geometriai testek esetén nincs ezzel probléma, de ha egyedi elemet hozok létre már nem olyan egyszerű a helyzet. Néhány óra eredménytelen próbálkozás után kellett szembesülnöm azzal, hogy létezik az UV map fogalma:) Még szerencse, hogy hozzáértő kollégám a közelben volt és kisegített jótanácsaival. Ha Te sem ismered ezt a kifejezést, pár szóban annyi, hogy az UV map egy olyan mátrix, ami alapján a textúrát ráilleszted egy adott testre. Itt már előkerült a négyjegyű függvénytáblázat is és gyorsan fel kellett frissítenem trigonometriai ismereteimet. Mennyivel egyszerűbb lett volna, ha egy kocka alakú dobozt kell lemodellezni…
Pöttyözd be!
Az egész lényege, hogy dinamikusan eltüntethető pöttyök legyenek a poháron, így ez a rész korántsem elhanyagolható. Mielőtt bármit is csináltam volna, két lehetséges megoldás is az eszembe jutott:
- A pöttyözés szabályosságát szem előtt tartva, textúrával oldom meg és mindig eltakarok egyet
- Minden pöttyöt egyéni geometriai elemként viszek fel, amik közül egyet mindig eltűntetek
A logó kialakításakor szerzett tapasztalatok alapján jobbnak láttam, ha a számomra egyszerűbb, első verziót választom. Maga a pöttyös textúra ráhúzása a pohárra nem volt túl bonyolult, mivel „előre definiált” geometriai test, nem kellett bajlódnom az uv map generálásával, csupán a textúra megfelelő vágásával, hogy ismételhető legyen.
Felesleges lett volna minden pöttyhöz elkészíteni az azt takaró elemet, ezért csak a játékélmény biztosításához szükséges helyeken tettem ezt meg. Ismét a pohár testét felépítő háromszögekből indultam ki, hiszen a fehér poháron egy fehér takaróelem esetén elhanyagolható annak a körvonala, a lényeg, hogy a pötty ne látszódjon. Miután megcsináltam 8–10 ilyen testet, már rendelkezésre is álltak a játékhoz szükséges elemek.
Interaktivitás
Az már az kattintások lekezelésének bekötésekor kiderült, hogy mobilon nem lesz elég ha csak egy „pöttynyi” részre lehet tappolni, így minden takaró elemhez létrehoztam egy másik, nagyobb felületű elemet, ami átlátszó volt és a click/touch esemény ahhoz volt kötve.
A másik érdekesség, amit mindenképpen ki szeretnék emelni, a kattintható felületek viselkedése volt. Az utolsó pillanatig küzdöttem a problémával, hogy a pohár közepétől felfelé elhelyezkedő felületek esetén minden interakciót gond nélkül érzékeltem, a közepétől lefelé viszont már alig-alig sikerült úgy rákattintani egy pöttyre, hogy azt a script is érzékelje. Majdhogynem egy véletlen folytán jöttem rá a megoldásra, amikor a kamerát és a test elhelyezkedését módosítottam. Nem is gondoltam volna, hogy ennyit számít az a minimális eltérés és a pohár ferde vonala, de a középvonal alatti interaktív elemek „kifelé forgatásával” megoldódott ez a probléma is. Erről a problémáról itt olvashatsz egy részletesebb leírást.
Mindent összerakva
Íme fenti dolgok egy leegyszerűsített változata, hogy ne csak képeket linkeljek.
See the Pen three.js cup by Zoltan Toth (@totya24) on CodePen.
Konklúzió
Nem mondom, hogy ha újra el kellene kezdenem, ugyanígy csinálnám, mindenesetre a dolog működik. Azt egyértelműen leszűrtem a projekt kapcsán, hogy ez egy elég mély és bonyolult része a fejlesztésnek, sokat kell matekolni és a hatékony és elegáns megoldásokhoz elengedhetetlen a megfelelő elméleti tudás. De egy újabb sor a TODO listámon, hogy ne hagyjam ennyiben a témát, mert kifejezetten jó móka és csudajó dolgokat lehet összehozni. Megemlíteném, hogy sokat segít a helyzeten, ha van körülötted egy a témában jártas szakember, mert olyan speciális helyzetekkel is találkozhatsz, amikre nem egyszerű megoldást találni a netet böngészve.
Ha sikerült felkeltenem az érdeklődéset, annak csak örülök. Biztos vagyok benne, hogy vannak szebb és jobb megoldások a fenti problémára, ha tudsz ilyet, szívesen várom kommentben. Egyébiránt itt tudod kipróbálni a végeredményt.
Hozzászólások