IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

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

kolízia - 3D bludisko v XNA

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#

 

Predchádzajúci článok
3D bludisko v XNA - Hardvérové instancování druhýkrát
Všetky články v sekcii
3D bludisko v XNA
Preskočiť článok
(neodporúčame)
3D bludisko v XNA - Instancujeme kolízne objekty
Článok pre vás napísal vodacek
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Vodáček dělá že umí C#, naplno se již pět let angažuje v projektu ŽvB. Nyní studuje na FEI Upa informatiku, ikdyž si připadá spíš na ekonomice. Není mu také cizí PHP a SQL. Naopak cizí mu je Java a Python.
Aktivity