16. diel - 3D bludisko v XNA - Kolízia štvrtýkrát a naozaj nie naposledy
Vitajte po dvadsiatej šiestej. Už bolo celkom na čase sa pozrieť konečne na kolízie a vykonať konečné riešenie. Aj keď uznávam, že na neho bude ešte potrebné mnohokrát siahnuť, ale zatiaľ sa s ním uspokojíme. Urobíme si taky niekoľko úprav kolíznu kože, aby bolo v budúcnosti možné mať viac druhov. Tým tak dotiahneme kolízne systém do prijateľnejšieho stavu, než je teraz a bludisko sa posunie o veľký krok dopredu. Poďme teda na to.
Trieda CollisionSkin
nám predstavuje kolíznu kožu, zatiaľ
máme iba len a len jednu. Nemusím snáď hovoriť, že to nie je moc
šikovné. Čo keď bude treba kože, ktorá bude obsahovať guľu a alebo
niekoľko gulí. Preto urobíme z tejto triedy abstraktné triedu, od ktorej
budeme dediť:
public abstract class CollisionSkin
V triede musí zostať len to, čo majú všetky kože spoločné. Bude teda
potrebné vykonať sťahovanie všetko čo tam nepatrí. Pridáme si teda novú
triedu SimpleBoxCollisionSkin
, ktorá nám bude predstavovať kože
s jednou krabicou. Opäť ako vždy triedu urobíme verejnú, dediť budeme od
triedy CollisionSkin
. Presunieme do nej premennej
public BoundingBox ZakladniKrabice; public BoundingBox TransformedKrabice;
Upravíme konštruktor aby prijímal krabici:
public SimpleBoxCollisionSkin(BoundingBox box){
ZakladniKrabice = box;
TransformedKrabice = box;
}
V abstraktné triede s kožou to naopak odoberieme a urobíme konštruktor
bezparametrický. Zameriame sa na metódu Draw
, urobíme ju
virtuálne a jej obsah prekopíruje do kože s krabicou:
public override void Draw(){ BoundingRenderer.Render(TransformedKrabice, Manager.Parent.Kamera.View, Manager.Parent.Kamera.Projection, LastCollision ? Color.Red : Color.Black); }
Ďalšie metódu, ktorú je potrebné vymaniť, je metóda
TransformBox
. Tú premenujeme na obyčajné Transform
,
urobíme ju virtuálne a jej obsah preložíme do novej triedy:
public override void Transform(Vector3 meritko, Vector3 pozice, Matrix rotace){ Matrix transform = Matrix.CreateScale(meritko) * Matrix.CreateTranslation(pozice); BoundingBox transformed = ZakladniKrabice; transformed.Min = Vector3.Transform(transformed.Min, transform); transformed.Max = Vector3.Transform(transformed.Max, transform); Vector3[] body = new Vector3[8]; transformed.GetCorners(body); for (int i = 0; i < body.Length; i++){ body[i] = Vector3.Transform(body[i], rotace); } transformed = BoundingBox.CreateFromPoints(body); TransformedKrabice.Min = transformed.Min; TransformedKrabice.Max = transformed.Max; }
Posledný miestečko, kde nám svieti chyba, je v metóde
CheckCollision
. Len pre pripomenutie, táto metóda nám volá
udalosti spojené s kolízií. Ešte než sa zbavíme chyby, vykonáme niekoľko
menších opatrení, ktoré by sa mohli hodiť v budúcnosti. Pridáme si dve
metódy:
protected void InvokeCollided(BoundingSphere sp){ OnCollision(sp); if (Collided != null) Collided(sp); } protected void InvokeUnCollided(BoundingSphere sp){ OnUnCollision(sp); if (UnCollided != null) UnCollided(sp); }
Obe metódy nerobia nič iné, než volajú príslušné udalosti. V metóde
CheckCollision
je potom zavoláme, zatiaľ bude vyzerať takto:
public bool CheckCollision(BoundingSphere koule){ if (koule.Intersects(TransformedKrabice)){ if (!LastCollision){ LastCollision = true; InvokeCollided(koule); } return true; } if (LastCollision){ InvokeUnCollided(koule); } LastCollision = false; return false; }
Už nám zostáva doriešiť len jednu vec. Ako riešiť kolízie. Budeme musieť pridať metódu, ktorú prepíšeme v novej triede. Do pôvodnej triedy teda pridáme:
public virtual bool Intersects(BoundingSphere sp){ return false; }
A do novej:
public override bool Intersects(BoundingSphere sp){ return TransformedKrabice.Intersects(sp); }
A potom len v metóde CheckCollision
nahradíme:
koule.Intersects(TransformedKrabice)
za
Intersects(koule)
Hotovo. Poslednou vecou, ktorú potrebujeme zmeniť, je zameniť v triede CollidableModel3D starú triedu za novú takže teda iba len:
CollisionSkin = new SimpleBoxCollisionSkin(box);
A potom tu ešte máme dva drobné problémky u štartovej a cieľovej podlahy, ale tie hravo vyriešime přetypováním:
((SimpleBoxCollisionSkin)(this.CollisionSkin)).ZakladniKrabice.Max.Y = 15;
Výborne! Ak teraz hru pustíte, tak by malo všetko optimálne fungovať ako
predtým. Úprava bola síce len systémová, ale opäť je pre budúcnosť.
Naplánované máme ešte ďalší problém a tým je narážanie do stien. Iste
ste si to všimli. Riešenie som hľadal dosť dlho, ale pritom je to veľmi
jednoduché. Ako to už tak býva. Otvoríme si triedu
CollisionManager
. Nájdite si metódu Collide
. Všetka
kúzla budeme robiť v nej. Prípady, kedy nedochádza ku kolízii fungujú ako
majú, s tými teda nič robiť nemusíme. Avšak keď ku kolízii dôjde,
môže sa stať, že pohyb po jednej osi je možný, ale my ho vôbec
neprevedieme. Pozrime sa na nasledujúci obrázok
Máme na ňom oranžovú kolízne guľu s modrým vektorom pohybu. Červeno je naznačené miesto, kde dochádza ku kolízii. V súčasnom systéme sa guľa nikam nepohne. Zostane proste stáť na mieste. Ako to riešiť? Odpoveď je jednoduchá. Každý vektor možno rozdeliť na jednotlivé zložky. V dvojrozmernom priestore na dve, na obrázku sú naznačené zelene a fialovo. Pokúsime sa s guľou pohnúť po oboch osiach samostatne. Ak aj tak dôjde ku kolízii, tak daným smerom nepohneme a skúsime druhú. Ak sa ani s tou nepodarí, tak sme niekde v rohu a hýbať sa nebudeme vôbec.
Jednoduché, pusťme sa do toho. Najprv nastavíme stred testovací gule na
východiskovú polohu a potom nastavíme osu X
na novú
hodnotu:
sphere.Center = old; sphere.Center.X = nova.X;
A potom novú hodnotu otestujeme. Nemusíme však testovať úplne so všetkými kožami, postačí, keď testujeme s tými, s ktorými sme kolidoval pri úplnom teste a tie máme uložené v liste. Je to do istej miery veľké zjednodušenie a nie vždy bude platné, ale keďže pohybové vektory sú pomerne malé vzhľadom k veľkosti kolíznych krabíc, tak by to nemalo príliš vadiť. Pokiaľ dôjde s kolíziou hoci len s jednou kožou, tak cyklus ukončíme a nastavíme starú hodnotu súradnice:
foreach (CollisionSkin skin in colliding){ if (skin.CheckCollision(sphere) && skin.Solid){ // pokad bude kolidovat s jednou tak koncime sphere.Center.X = old.X; break; } }
Rovnako naložíme aj s druhou osou. Potrebné upozorniť, že sa jedná o
osu Z
, Y
nám mieri nahor:
sphere.Center.Z = nova.Z; foreach (CollisionSkin skin in colliding){ if (skin.CheckCollision(sphere) && skin.Solid){ // pokad bude kolidovat s jednou tak koncime sphere.Center.Z = old.Z; break; } }
A na záver vrátime miesto kde je teraz guľa:
return sphere.Center;
A to je napodiv všetko. Práve sme odstránili obrie slabinu celej hry a
vlastne aj enginu ako takého. A keďže zostáva ešte veľa znakov a ja som
provokatér tak tu máme ešte jedno také vylepšenie. Zostaneme v kolíznom
správcovi a vlastne taky v metóde Collide
. Úplne hore
prechádzame zoznam všetkých, ale úplne všetkých kožou. Čo keď ale
budeme chcieť, aby sa pri detekovaní kolízie kože odobrala. Napríklad
rovnako ako to robia Hviezdičky v hotovej hre. Ak sa o to pokúsime teraz, tak
nám hra spadne. Prečo? Pretože počas prehliadania listu zmeníme jeho obsah.
Preto budeme musieť zakaždým kože nakopírovať do pomocného listu a
prechádzať je pomocou neho. Ale aby sa nevytváral zoznam zakaždým nový,
čo je celkom nákladná operácia, tak vytvoríme premennú v triede a pred
naplnením ju len vyčistíme:
private List<CollisionSkin> updating;
V konstruktoru ju vytvoríme:
updating = new List<CollisionSkin>();
A potom následne v metóde Collide
ju vyčistíme a následne
naplníme novými kožami:
updating.Clear(); updating.AddRange(Boxes);
A vo foreach cyklu nahradíme kolekciu. A máme hotovo. Viac toho zatiaľ s kolíziami robiť nebudeme. Vyliečili sme ich z detské choroby, ale ešte nie sú úplne optimálne. Na čo sa môžete tešiť nabudúce? Sám neviem. Sľuboval som grafickú kartu, ale skôr by som si dal niečo oddychového, trebárs taký 3D text.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 112x (1.76 MB)
Aplikácia je vrátane zdrojových kódov v jazyku C#