Ez megint ismeretterjesztő jellegű bejegyzés lesz, szóval akit annyira nem köt le, az görgessen az aljára, van videóm is :)
Elsőnek, pár szóban, mi is az az ütközésérzékelés? (Jó, tudom, a nevéből ered, de itt picit mást értünk rajta azért). Neve alapján az a kód, ami képes érzékelni adott objektumok ütközését. Ez pedig számtalan ok miatt fontos: legtriviálisabb ok az, hogy ne lehessen átmenni a falon, például. De igazából a legtöbb interakciót ütközésérzékelésre lehet levezetni grafikában, hiszen attól, hogy nem mész át a falon, keresztül a lelökhető poháron (a lökés a lényeg, nem az esés :) ) át egészen addig, hogy kinyitsz egy ajtót, számtalan helyzet van, ahol így oldhatóak meg a feladványok.
Na, de nézzük részleteiben! Mivel az ütközésérzékelés az egyik legerőforrás igényesebb művelet (már ha szeretnénk realisztikus pontosak lenni.) Annál több számításigénye talán csak a fényeknek van. Mivel egy komplexebb modell/objektum akár pár százezer háromszögből is felépülhet, és nekünk ezeket mind meg kell vizsgálnunk, hogy érintkeznek-e. Ez két modell esetében nem probléma (annyira) hiszen pár millió műveletben megoldható a dolog. Igen, de a legritkább esetben fordul elő olyan, hogy csupán két objektum van a képernyőn. És ahogy több testet adunk hozzá a kereséshez, úgy növekszik egyre gyorsabban azon pontok száma, amiket egymáshoz képest vizsgálni kell. Ez pedig lehetetlen.
Tehát, a fent vázolt modell, pontosabban megfogalmazva, a pixel-szintű összehasonlítás egy teljes tájra levetítve közel kivitelezhetetlen, bár az tény, hogy rendkívül pontos. A valóságot nem éri el, de nagyon megközelíti. Igen, de mi marad helyette? Jönnek a valóságot egyszerűsítő modellek, vagyis az "ütköző testek" normálisabb szóval "bounding volumes" (de tippem sincs, hogy van ez magyarul).
Ezekre már egy korábbi bejegyzésben kitértem, így csak pár szóval: létrehozhatunk egy gömböt (pont és attól való távolság matematikailag) és egy téglatestet. Ezek ütközését vizsgálni nagyon egyszerű dolog - viszont nagyon pontatlan is, hiszen alapállapotban azt tudjuk csak meg, hogy egyik a másikban van-e, érintkeznek, vagy sem. Három állapot néhány esetben elég, sokban viszont nem.
Elsőnek nézzük az "általános" eseteket, mikor tökéletesen elég. Ez volt az, amin az elmúlt napokban (vagy hétben?) dolgoztam, vagyis az ajtó kinyitása. (Az animáció igazából mellékhatás volt, de lényegtelen.) Ehhez ugyanis elsőnek kellett egy doboz, amivel körbevehetem az ajtót - ugyanis gömböt létrehozni egyszerű, de a legtöbb tárgyunk legpontosabban téglatesttel írható körbe, anélkül, hogy rendkívül sok fölösleges teret zárnánk mellé. Mint például az ajtófélfa, vagy az ajtó maga. Itt tehát egyszerű esetem van: ha ütközés van a modellt körbevevő testtel, akkor ütközik, vagyis nem mehetsz tovább, ha nincs, mehetsz. Eddig egyszerű, és remek.
A probléma ott kezdődik, amikor megjelenek a dinamikus objektumok: ezek folyton mozognak, és velük együtt forognak is. Tehát folyton-folyvást kénytelen vagyok a dobozaikat változtatni ahhoz, hogy rendben legyen a későbbiekben az ütközés-érzékelés. Erre a legegyszerűbb megoldás az az elv, hogy: "Amit nem kell kiszámolni, gyorsabb, mint bármi, ami ki kell" (vagy valami ilyesmi). Tehát egyszerűen felosztottam mezőkre a terepet: a középsőben van a játékos, és a környező nyolcat jelenítem még meg. Ezek mérete szépen változtatható, és az összes többivel nem kell foglalkozni. Tehát nagyon szépen le tudom csökkenteni a lehetséges ütközések számát mindössze a pálya kis szeletére. Kevés modell + kevés lehetséges ütközés = gyors ellenőrzés.
Vagyis az ajtómra levetítve: az algoritmus összeszedi azon modelleket, amiket meg kell jeleníteni az adott 9 négyzetben. Ezekből összeszedi azokat a boundingbox-okat, melyek esélyesek, hogy ütközhetnek (vagyis jelenleg a középsőt, ahol a játékos van, vagyis az "aktív" négyzet), majd, amikor updatelem a modellt (egészen pontosan animáció történik) akkor szépen megvizsgálja, hogy az animáció KÖVETKEZŐ lépése okoz-e ütközést. Azért emeltem ki, mert egy fontos lépés: ugyanis ha elsőnek lépne a modell, és csak utána frissülne, akkor nagyon szépen egymásba "ragadnak" és csak megfelelő "nagyságú" lépés után szabadulnak ki egymásból. (Ismerős néhány játékból talán... :) ) Tehát elsőnek megnézem, mi lenne a következő lépésben: ha ütközés, akkor nem történik meg a lépés - ha nem, akkor a kiszámolt állapotot elmentem.
És máris megvan az ajtó-animáció-elakadás megvalósítása. Na igen ám, de hogyan nyitom ki? Honnan tudja az ajtó, hogy éppen ránézek, éppen azt nyitnám ki? Ismételten egyszerűen: húzok egy vonalat a kamera pozíciójából kiindulva át a kamera célpontján (amerre néz). Mint matekból tudjuk, két pont meghatároz egy egyenest - én pedig fogom, és megnézem, hogy ütközik-e az adott ajtóval, illetve hol. Ha ütközik valamelyik ajtóval (értelemszerűen csak azzal, amelyikre nézek éppen) akkor ellenőrzöm, hogy a karakter adott távolságra (kb kéznyújtásnyira, de ez igazából jelenleg hasraütés érték, majd pontosítom) történt-e az ütközés. És valóban az ajtóval történt-e az ütközés, nem mással. Ha igen, aktiválom az animációt, ami elvégzi a fenn vázolt lépéseket.
Ez szép és jó, immáron tudjuk, hogyan lehet leverni egy poharat az asztalról, de például hogyan lehet lelőni valakit így? A vicc az, hogy játékban ugyanis lősz fejbe valakit, mint ahogy kinyitod az ajtót (jó, technikailag): húzol a fegyver csövétől a célpont irányába egy egyenest, és megnézed, találkozik-e a modellt körbevevő box-al, illetve, azzal találkozik-e elősször. Ha igen, akkor találat. A modellt több ilyen dobozra felosztva pedig azt is megtudod mondani, hogy egészen pontosan hol találtad el a modellt. (igazából így működik a legtöbb játékban, annyi módosítással, hogy ez így túl könnyű lenne: tehát a gomb megnyomásakor nem végzi el ezeket, hanem csak akkor, ha végetért az animáció. Tehát a legtöbb játékban a játékos megnyomja a gombot, eltelik 1-3 másodperc [vagy akármennyi] és csak utána húzza meg az egyenest - játékos szemszögből ez olyan, mintha időbe kerülne a golyó haladása - pedig "igazából" nem is :)
És, végül, ahogy ígértem, a videó - bár twitteren már kiraktam :)