IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

10. diel - 3D bludisko v XNA - Kolízia tretíkrát a snáď naposledy

Vitajte po dvadsiatej. Pôvodne som dúfal, že si v tomto diele prejdeme bludisko už konečne ako hru s kolíziami o múru, počítaním času, ktorý bol potrebný pre cestu bludiskom. Skrátka že bude všetko hotové a len si v ďalšom diele pridáme menu ak tomu zmenu levelov a hra bude prakticky hotová. Bohužiaľ to tak nie je, máme síce hotové kolízie sa múrmi, ale chýba nám jeden malý detail. Prejdime si ešte raz aké máme nároky na kolízie. Chceme, aby nešlo prechádzať múrov a zároveň chceme, aby sa hra ukončila keď sa dostaneme do cieľa. Prvá požiadavka spĺňame, ale ten druhý nie. Máme opäť niekoľko možností. Môžeme srabsky vytvoriť krabicu na dané miesto a zakaždým kontrolovať kolízii mimo napísaný systém. A alebo môžeme upraviť existujúci systém, a tu rozumieme úpravou prepísať skoro všetko, a tým dostať opäť univerzálne riešenie tohto problému. Dostaneme potom automatický systém kde sa kolízie budú samy na seba upozorňovať volaním udalostí. To je myslím dobrý cieľ, pokúsim sa to napchať do jedného dielu, aby sme sa pohli vpred, takže ideme na to.

Ak sa pozrieme do kolízneho manažéra, zistíme, že máme v poli uložené krabice priamo. Aby bolo možné detekovať či ak kolidujú teraz prvýkrát a alebo či už aj minule sme kolidoval, potrebujeme rovnako ako u klávesnice a myši uchovať predchádzajúci stav. Prvú zmenu, ktorú musíme vykonať, je pridať indikáciu tohto stavu. Najlepšie bude obaliť krabicu do nového objektu. Pridáme si teda novú triedu CollisionSkin. Alebo treba si ju pomenujte inak. Urobíme si ju verejnú, upravíme menný priestor ako obyčajne. Pridáme opäť dve premenné pre krabice, rovnako ako sme mali u Modelu. Prakticky všetko, čo budeme robiť, bude presúvať veci sem :-)

public BoundingBox ZakladniKrabice;
public BoundingBox TransformedKrabice;

Ďalej pridáme odkaz na kolízne manažér, rovnako ako sme robili inde, je to náš bežný zvyk:

private CollisionManager fManager;

public CollisionManager Manager{
  get{
    return fManager;
  }
  set{
    fManager = value;
  }
}

Pridáme tiež konštruktor, ako parameter odovzdáme krabicu a tú si uložíme:

public CollisionSkin(BoundingBox box){
  ZakladniKrabice = box;
  TransformedKrabice = box;
}

Ďalej prenesieme do tejto triedy metódu TransformBox z triedy s kolíznym modelom, bude teraz potrebné tu. Zmenu merítka, pozície a tiež rotácie odovzdáme v parametri, takže telegraficky:

public void TransformBox(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;
}

V triede pre model taktiež zašpiníme všetky volania tejto metódy. Dodáme tiež metódu Draw, ktorá nám ako inak než vykreslí krabici:

public void Draw(){
  BoundingRenderer.Render(TransformedKrabice, Manager.Parent.Kamera.View, Manager.Parent.Kamera.Projection, Color.Black);
}

Pridáme tiež metódu pre kontrolu kolízií, tu si ju pomenujeme CheckCollision a bude vracať true a alebo false. Prakticky len vraciame výsledok metódy Intersets:

public bool CheckCollision(BoundingSphere koule){
  if (koule.Intersects(TransformedKrabice)){
    return true;
  }
  return false;
}

Až doteraz sme si len presunuli do tejto triedy všetko čo sme mali minule decentralizované. Novinky prídu až teraz. Musíme pridať indikáciu či ak sme už nekolidovala minule, takže:

public bool LastCollision{
  get;
  protected set;
}

Je pravdou, že aj tu by sa hodil nejaký objekt, ktorý by ukladal aký útvar presne kolidoval, ale v našom prípade máme len jednu guľu, ktorá môže kolidovať a preto si vystačíme s týmto zjednodušením. Kolízia budeme vybavovať vo virtuálnych a chránených metódach:

protected virtual void OnCollision(BoundingSphere sphere){

}

protected virtual void OnUnCollision(BoundingSphere sphere){

}

a tiež skrze volania udalostí. Predpokladám že udalosti budú využívané častejšie, ale aj tak je dobré tie metódy mať, umožňuje to jednoduchú špecializáciu potomkov. Kto sa s udalosťami ešte nestretol tak si môže prečítať tento článok. Nebudeme ale používať vstavaný delegát ale delegát náš vlastný:

public delegate void CollisionBegin(BoundingSphere koule);
public delegate void CollisionEnd(BoundingSphere koule);

Do triedy pridáme príslušnej udalosti:

public event CollisionBegin Collided;
public event CollisionEnd UnCollided;

Prvá sa zavolá vtedy, keď je detekovaná prvýkrát kolízie. Druhá, keď sa teleso pekne povedané odkoliduje. Začneme prvým prípadom. Ten nastáva ak je hodnota premennej LastCollision false a teraz ku kolízii dochádza. Pridáme teda do metódy CheckCollision tento stav:

public bool CheckCollision(BoundingSphere koule){
  if (koule.Intersects(TransformedKrabice)){
    if (!LastCollision){
      LastCollision = true;
      OnCollision(koule);
      if (Collided != null) Collided(koule);
    }
    return true;
  }
  LastCollision = false;
  return false;
}

Ak sme minule nekolidovala, nastavíme premennú na true zavoláme miestnej metódu a ak je nejaká metóda v udalosti zaregistrovaná, tak ju tiež zavoláme. Rovnako vyriešime druhú možnosť, ktorá je presne opačný prípad, takže celá metóda vyzerá nasledovne:

public bool CheckCollision(BoundingSphere koule){
  if (koule.Intersects(TransformedKrabice)){
    if (!LastCollision){
      LastCollision = true;
      OnCollision(koule);
      if (Collided != null) Collided(koule);
    }
    return true;
  }
  if (LastCollision){
    OnUnCollision(koule);
    if (UnCollided != null) UnCollided(koule);
  }
  LastCollision = false;
  return false;
}

Hotovo, teraz už len triedu naimplementujeme do hotového systému. Prvý kupa zmien bude potrebné urobiť v kolíznom manažérovi. Prakticky všade kde pracujeme s krabicou budeme pracovať s kožami. Takže zmeníme kolekciu s krabicami na kolekciu s kožami:

protected List<CollisionSkin> Boxes;

Meno asi ponecháme. Upravíme tiež v konstruktoru a metódy pre pridávanie a odoberanie taktiež:

public void AddBox(CollisionSkin skin){
  if (!Boxes.Contains(skin)){
    Boxes.Add(skin);
    skin.Manager = this;
  }
}

public void RemoveBox(CollisionSkin skin){
  Boxes.Remove(skin);
  skin.Manager = null;
}

Vykreslenie prenecháme na koži:

public void Draw(Matrix View, Matrix Projection){
  if (DebugDraw){
    foreach (CollisionSkin skin in Boxes){
      skin.Draw();
    }
  }
}

Metóda pre kolíziu doznáva najväčšie zmeny. Prenecháme rozhodovanie o kolízii práve našej koži.

List<CollisionSkin> colliding = new List<CollisionSkin>();
foreach (CollisionSkin skin in Boxes){
  if(skin.CheckCollision(sphere))colliding.Add(skin);
}

Ak sa pozrieme teraz na chyby, ktoré nám VisualStudio vypisuje, zistíme, že sú už len v modeli. Otvoríme si ho. Prvé vecí, čo je potrebné zašpiníte, sú dve premenné pre krabice. Preč s nimi, sú už obsiahnuté v koži. Moc sme si nepomohli, počet chýb nám narástol :-) Pridáme privátne premennú pre kožu:

private CollisionSkin fCollisionSkin;

Getter a setter, ktorý slúžil pre transformovanú krabicu, nahradíme Getter a setter pre kožu:

public CollisionSkin CollisionSkin{
  get{
    return fCollisionSkin;
  }
  set{
    if (fCollisionSkin != value){
      if (Parent is CollidableGameScreen){
        CollidableGameScreen okno = Parent as CollidableGameScreen;
        if(fCollisionSkin!=null)okno.CollisionManager.RemoveBox(fCollisionSkin);
        okno.CollisionManager.AddBox(value);
        fCollisionSkin = value;
      }
    }
  }
}

V metóde Load koži vytvoríme:

BoundingBox box=Utility.VypoctiBoundingBox(Model, transformace);
CollisionSkin = new CollisionSkin(box);

Do metódy Update kožu transformujeme:

if (CollisionSkin != null) CollisionSkin.TransformBox(Meritko, Pozice, Rotace);

A konečne v metódach OnAdded a OnRemoved namiesto krabice pridáme kožu. Ak teraz hru spustíme, tak dostaneme rovnaký výsledok ako minule. Aby bol vidieť aspoň nejaký rozdiel, tak pri vykreslenie kože zmeníme farbu, vtedy ak sa s kožou kolidováno. Posledný parameter nahradíme ternárním operátorom:

LastCollision ? Color.Red:Color.Black

A to by bolo pre dnešok všetko, problém sa nám podarilo zdarne prekonať. V budúcom dieli si prídeme na ďalší problém :-) Ten je ale riešiteľný veľmi, veľmi ľahko. A to všetko len vďaka tomu, čo sme si v tomto diele vytrpeli. Nech to také memento toho, že je aj pridanie pár nových a veľmi obyčajných požiadaviek môže stať pomerne vysoké úsilie. Opäť by som bol rád za komentáre a tak podobne, ale asi sa nedočkám.


 

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é 179x (1.79 MB)
Aplikácia je vrátane zdrojových kódov v jazyku C#

 

Predchádzajúci článok
3D bludisko v XNA - Kolízia druhýkrát
Všetky články v sekcii
3D bludisko v XNA
Preskočiť článok
(neodporúčame)
3D bludisko v XNA - ČASU
Č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