7. díl - 3D bludiště v XNA - Krabice a koule

C# .NET XNA game studio 3D bludiště 3D bludiště v XNA - Krabice a koule

Vítejte po sedmnácté. V tomto díle si připravíme podhoubí pro kolizní manažer. Vytvoříme si třídu, která nám pomůže s vykreslováním kolizních koulí a krabic. Také se pokusíme kolizní krabice vydolovat z nahraných modelů. Pusťme se tedy do práce.

Krabice a koule

Velmi bude potřeba něco, co nám vykreslí jinak neviditelné pomocné kolizní tvary. Spolu si zde rozebereme pouze část pro krabice, tedy pro BoundingBox. Část pro kouli nechám na vás, ale není moc potřeba ji zkoumat.

Začněme tedy tradičně vytvořením třídy. Já jsem ji nazval BoundingRenderer. Nebude to komponenta, takže si ji necháme volně v projektu s enginem. Opět upravíme jmenný prostor a učiníme třídu veřejnou. Krabice budeme vykreslovat drátově. Přidáme tedy privátní proměnné pro efekt a GraphicsDevice skrze které budeme vykreslovat:

private static BasicEffect effect;
private static GraphicsDevice graphics;

Přidáme také statickou metodu Initialize, kde efekt vytvoříme a přiřadíme. Nastavíme mu neměnné parametry asi takto:

public static void Initialize(GraphicsDevice device){
  effect = new BasicEffect(device);
  effect.LightingEnabled = false;
  effect.VertexColorEnabled = true;

  graphics = device;
}

Zakážeme používání světel a naopak povolíme barvy u vertexů. Tuto metodu zavoláme v konstruktoru třídy s enginem:

BoundingRenderer.Initialize(graphics);

Přidáme opět statickou metodu Render, ale pojmenujte si ji třeba Draw nebo jakkoli jak se vám bude zdát příhodné. Právě skrze ni budeme později vykreslovat. V parametrech předáme naši krabici, matice View a Projection a barvu, kterou bude krabice vykreslena. Takže asi takto:

public static void Render(BoundingBox box, Matrix view, Matrix projection, Color color)

Z krabice lze získat body, které ji ohraničují skrze metodu GetCorners. Uložíme si je do pole a skrze for cyklus je nandáme do připraveného pole s vertexy, nezapomeneme přiřadit námi předanou barvu:

Vector3[] corners = box.GetCorners();
for (int i = 0; i < 8; i++){
  krabiceVerts[i].Position = corners[i];
  krabiceVerts[i].Color = color;
}

Nesmíme si zapomenout pole krabiceVerts vytvořit:

private static VertexPositionColor[] krabiceVerts = new VertexPositionColor[8];

K tomu přidáme i pole s indexy, podle kterých se budou body spojovat čárami, pole je jako ostatně všechno statické:

private static readonly int[] krabiceIndices = new int[]{
  0, 1,
  1, 2,
  2, 3,
  3, 0,
  0, 4,
  1, 5,
  2, 6,
  3, 7,
  4, 5,
  5, 6,
  6, 7,
  7, 4,
};

Jen pro kontrolu je vidět, že máme 12 hran a právě tolik jich krychle má mít. Je dobře, že jsme si prozíravě nacvičili kreslení čar na snadných útvarech, kdybych to měl vysvětlovat nyní a na tomto, tak teda nevím, nevím. Efektu nastavíme obě naše matice View a Projection.

effect.View = view;
effect.Projection = projection;

A čáry vykreslíme:

effect.CurrentTechnique.Passes[0].Apply();
      graphics.DrawUserIndexedPrimitives(PrimitiveType.LineList,krabiceVerts,0,8,krabiceIndices,0,krabiceIndices.Length / 2);

To je vše. Zkusíme si do herního okna propašovat vykreslení jedné pokusné krabice. Přepíšeme metodu Draw. Ponecháme volání base metody a naopak přidáme vykreslení krabice asi takto:

BoundingBox box = new BoundingBox(Vector3.Zero, new Vector3(-20, 20, -20));
BoundingRenderer.Render(box, Kamera.View, Kamera.Projection, Color.Purple);

Pokud nyní hru spustíte, měli byste vidět bludiště a vedle něj růžovou drátovou krabici. Pokud ne, tak máte někde chybu a nebojte se ozvat v komentářích.

Jak jsem již naznačil, kouli zde podrobně rozebírat nebudu, ale funguje to naprosto stejně. Pokud vás zajímá, jak je uděláme, tak se podívejte do zdrojového kódu, který jako obvykle najdete pod článkem.

Vytváříme krabice

Jak jsem již minule naznačil, budeme naše objekty obalovat krabicemi a hráče naopak koulí. Jak ale ta tělesa získáme? U našich modelů je to snadné. Samy jsou krychlemi. Ale u složitějších modelů bude potřeba se k nim propracovat skrze vrcholy. Pokusím se zde navrhnout dva možné způsoby, jak získat kolizní krabici, popřípadě kouli. Můžeme ji pochopitelně ručně určit, ale to jaksi není ono. Ti bystřejší z Vás si možná povšimli, že ve třídě ModelMesh je proměnná BoundingSphere. Tam nám už XNA předpočítalo kolizní kouli pro danou část modelu. To je panečku servis. Stačí je jen posčítat a máme hotovo. Pokud bychom to chtěli dělat kódem, tak by to vypadalo nějak následovně:

public BoundingSphere VytvorBoundigSphere(Model mod){
  BoundingSphere sphere = new BoundingSphere(Vector3.Zero, 0);
  foreach (ModelMesh mesh in mod.Meshes){
    BoundingSphere transformed = mesh.BoundingSphere.Transform(transformace[mesh.ParentBone.Index]);
    sphere = BoundingSphere.CreateMerged(sphere, transformed);
  }
  return sphere;
}

Projdeme všechny části modelu. Vyzvedneme si tam vypočtenou kouli. Transformujeme ji stejně jako při vykreslování. Koule pak postupně skládáme do jedné velké. A tu vrátíme. Tato koule je pak společná pro všechny, ale před jejím upotřebením ji ještě musíme znovu transformovat. Konkrétně posunout na místo, kde model ve světě stojí a případně jí změnit měřítko. Rotaci uplatňovat nemusíme, je snad jasno, že rotací koule dostáváme stále tu samou kouli. Kódem provedeno to vypadá následovně:

Matrix transform = Matrix.CreateScale(Meritko) * Matrix.CreateTranslation(Pozice);
BoundingSphere transformed = VytvorBoundigSphere(Model).Transform(transform);

S tímto již pak můžeme nakládat dále, je ale potřeba kouli přepočítat pokaždé, když se změní poloha a nebo měřítko modelu. Tohoto úkonu se nezbavíme tak jako tak. Vždy bude potřeba posunovat kolizní tvar současně s modelem. Změna polohy u koule znamená jen posunout její střed, je to tedy výpočetně velmi snadný úkol. Pokud měníme měřítko, tak jen daným číslem vynásobíme poloměr a ten se buď zvětší a nebo zmenší podle zadané hodnoty. Pokud se ale pokusíme zobrazit si vypočtenou kouli třeba pro onen kvádr, který posloužil pro demonstraci minule, budeme nemile překvapeni. Ostatně posuďte sami:

Kolizní 3D koule pro kvádr v C# XNA .NET

Celý model je pěkně uvnitř to sice ano, ale za jakou cenu. Kolize by zde byla detekována převážně na místech, kde model již dávno není. Rozhodně bych tento způsob nerad házel do koše. Lze jej uplatnit, ale ne vždycky. Pro výrazně hranatá tělesa se prostě nehodí. Když přepočteme kouli na krabici, tak si také mnoho nepomůžeme. Vlastně si nepomůžeme vůbec. Schválně si to zkuste.

Jak tedy krabici vytvořit a přitom ji neurčovat ručně? Vezmeme všechny body, ze kterých se model skládá a krabici z nich složíme. Najdeme nejmenší souřadnici X a Y a také Z. Najdeme maxima souřadnic a z nich krabici poskládáme. Zní to velmi snadně a také to tak snadné je. Způsob není z mé hlavy. Vypůjčil jsem si jej odtud. Tady nebylo bohužel nic moc k upravování. Jen pouze extrakce dat mi nefungovala a proto jsem ji trochu poupravil. Namísto řádku:

part.VertexBuffer.GetData(part.VertexOffset * stride, vertexData, 0, part.NumVertices, 1);

jsem dal tento:

part.VertexBuffer.GetData<byte>(vertexData);

Jinak vše zůstalo bez změny. Oba přístupy jsem zakomponoval do pomocné třídy Utility, kterou jsme si vytvořili již dříve. Možná vás při obhlídce kódu zaujme, že jako parametr předávám i pole s transformacemi. Je to taková příprava pro animace, posléze se nám to bude velmi hodit. Ještě dlužím metodu jak s krabicí manipulovat. Ta je o trochu více složitější, než u koule. Posun krabice na správné místo a její zmenšení je prakticky stejné jako u koule. Opět si vytvoříme transformační matici a aplikujeme jí na minimální a maximální bod krabice:

Matrix transform = Matrix.CreateScale(Meritko) * Matrix.CreateTranslation(Pozice);
BoundingBox transformed = VytvorBoundingBox(Model);
transformed.Min=Vector3.Transform(transformed.Min, transform);
transformed.Max = Vector3.Transform(transformed.Max, transform);

Potud je to jasné. Problémy ale čekají za dveřmi. Rotovat krabicí, jak jsme si ostatně ukázali již minule, není tak snadné. Nestačí pouze aplikovat rotaci na oba dva body, kterými je krabice určena. Schválně si to zkuste. Rotaci je potřeba aplikovat na všech osm vrcholů. Proto je tedy stejně jako při vykreslování získáme, pootočíme a z nich vytvoříme novou krabici:

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);

Jak to vypadá když se krabice rotuje, to jste mohli vidět na videu u minulého článku. Obě metody zatím nikde v našem enginu nepoužijeme, budeme je ale potřebovat později.

To by bylo tak asi pro dnešek všechno. Příště si vytvoříme kolizní manažer, který se nám o provedení kolizí postará. Do té doby zkoušejte editor, hrajte si s tím co už máte a komentujte, kritizujte a nebo taky chvalte, to už ponechám na vás.


 

Stáhnout

Staženo 202x (1.05 MB)
Aplikace je včetně zdrojových kódů v jazyce C# XNA

 

  Aktivity (1)

Článek pro vás napsal vodacek
Avatar
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.

Jak se ti líbí článek?
Celkem (3 hlasů) :
55555


 


Miniatura
Všechny články v sekci
3D bludiště v XNA
Miniatura
Následující článek
3D bludiště v XNA - Kolize poprvé

 

 

Komentáře
Zobrazit starší komentáře (6)

Avatar
magic44
Redaktor
Avatar
magic44:

Nic se po přidání bohužel nezměnilo.:(

Odpovědět 23.5.2013 19:48
Moudrý člověk nechce být lepší než ostatní, ale lepší, než byl sám včera.
Avatar
vodacek
Redaktor
Avatar
Odpovídá na magic44
vodacek:

a fotka toho co to dělá by nebyla?

 
Odpovědět 23.5.2013 19:57
Avatar
magic44
Redaktor
Avatar
Odpovědět 24.5.2013 12:13
Moudrý člověk nechce být lepší než ostatní, ale lepší, než byl sám včera.
Avatar
vodacek
Redaktor
Avatar
vodacek:

no ale ono to je na správnym místě :-D

 
Odpovědět 24.5.2013 15:41
Avatar
magic44
Redaktor
Avatar
magic44:

Hmm... aha já to porovnával s bludištěm a ne s mřížkou. Ono je posunutý to bludiště a ne krabice.

Odpovědět 27.5.2013 9:16
Moudrý člověk nechce být lepší než ostatní, ale lepší, než byl sám včera.
Avatar
magic44
Redaktor
Avatar
Odpovídá na vodacek
magic44:

Nevis, proc je ta mapa posunutá?

Třída Mapa

public class Mapa:Component
   {
       private List<Component> Komponenty;
       public Vector3 Start;

       public Mapa()
       {
           Komponenty = new List<Component>();
       }

       public void Nacti(string cesta)
       {
           Promaz();
           Component c;

           string[] radky = File.ReadAllLines(cesta);
           for (int j = 0; j < radky.Length; j++)
           {
               string[] radek = radky[j].Split(',');

               for (int i = 0; i < radek.Length; i++)
               {
                   c = null;
                   int typ = -1;
                   int.TryParse(radek[i], out typ);

                   switch (typ)
                   {
                       case 0:
                           {
                               c = new Podlaha(i, j);
                               break;
                           }
                       case 1:
                           {
                               c = new Zed(i, j);
                               break;
                           }
                       case 99:
                           {
                               c = new StartovniPodlaha(i, j);
                               Start = new Vector3(10 + i * 20, 0, 10 + j * 20);
                               break;
                           }
                       case 100:
                           {
                               c = new CilovaPodlaha(i, j);
                               break;
                           }
                   }

                   if (c != null)
                   {
                       Parent.AddComponents(c);
                       Komponenty.Add(c);
                   }
               }
           }
       }

       public void Promaz()
       {
           foreach (Component c in Komponenty)
               Parent.RemoveComponent(c);
           Komponenty.Clear();
       }
   }

A v MojeHerniOkno:

protected override void Load()
       {
           AddComponents(new Pozadi(Color.Orange));
           Kamera = new FreeCamera(this, new Vector3(100, 100, 0), Vector3.Zero);

           Mapa mapa = new Mapa();
           AddComponents(mapa);
           mapa.Nacti("ddd.map");
       }
Odpovědět 27.5.2013 10:05
Moudrý člověk nechce být lepší než ostatní, ale lepší, než byl sám včera.
Avatar
vodacek
Redaktor
Avatar
Odpovídá na magic44
vodacek:

a konsturktor pro zed vypadá jak?

 
Odpovědět 27.5.2013 11:06
Avatar
magic44
Redaktor
Avatar
magic44:
public Zed(int x, int z)
           : base(new Vector3(x * 20 - 10, 0, z * 20 - 10), Matrix.Identity, new Vector3(1.34f), "zed")  //1.34-meritko.
       {

       }
Editováno 27.5.2013 11:33
Odpovědět 27.5.2013 11:32
Moudrý člověk nechce být lepší než ostatní, ale lepší, než byl sám včera.
Avatar
vodacek
Redaktor
Avatar
Odpovídá na magic44
vodacek:

místo mínus dej plus

 
Odpovědět 27.5.2013 11:54
Avatar
magic44
Redaktor
Avatar
Odpovědět 27.5.2013 13:39
Moudrý člověk nechce být lepší než ostatní, ale lepší, než byl sám včera.
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 16. Zobrazit vše