Předvánoční slevová akce PHP týden
Pouze tento týden sleva až 80 % na PHP e-learning!
Využij předvánočních slev a získej od nás 20 % bodů zdarma! Více zde

Lekce 7 - 3D bludiště v XNA - Krabice a koule

Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem.
Vydávání, hosting a aktualizace umožňují jeho sponzoři.

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];
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!

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 207x (1.05 MB)
Aplikace je včetně zdrojových kódů v jazyce C# XNA

 

 

Článek pro vás napsal vodacek
Avatar
Jak se ti líbí článek?
3 hlasů
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.
Předchozí článek
3D bludiště v XNA - Podlahy podruhé a kolize
Všechny články v sekci
3D bludiště v XNA
Miniatura
Následující článek
3D bludiště v XNA - Kolize poprvé
Aktivity (1)

 

 

Komentáře

Avatar
Michael Olšavský:26.3.2013 23:10

Tak tenhle díl mě totálně zmátl. Vůbec nevím, co jsem dělal. Jen útržky z kódu. Jak se např. používá ten VertexBuffer, které metody kam patří.... Nějak sem to zkopíroval, ale z té druhé části vážně nic moc nemám.

 
Odpovědět
26.3.2013 23:10
Avatar
vodacek
Redaktor
Avatar
Odpovídá na Michael Olšavský
vodacek:2.4.2013 20:57

matení nepřítele je jedna z mojich specialit, nicméně jsem o nich napsal článek, budou ještě docela potřeba

 
Odpovědět
2.4.2013 20:57
Avatar
magic44
Redaktor
Avatar
magic44:23.5.2013 11:31

Dobrej článek. Matení je opravdu tvoje specialita :). Mám dotaz, proč se mi drátová krabice zobrazuje v 1. kostce, která tvoří zeď (je to z počátečního pohledu šouplý o 1 do leva a dopředu).

Editováno 23.5.2013 11:33
Odpovědět
23.5.2013 11:31
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:23.5.2013 14:57

no to bez kodu těžko říct

 
Odpovědět
23.5.2013 14:57
Avatar
magic44
Redaktor
Avatar
magic44:23.5.2013 15:47

Nevim, žádnou chybu jsem tam nenašel (podle ukázky kódu).

public class BoundingRenderer
   {
       private static BasicEffect efekt;
       private static GraphicsDevice graphics;
       private static VertexPositionColor[] krabiceVerts = new VertexPositionColor[8];

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

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

           graphics = device;
       }

       public static void Render(BoundingBox box, Matrix view, Matrix projection, Color color)
       {
           Vector3[] corners = box.GetCorners();
           for (int i = 0; i < 8; i++)
           {
               krabiceVerts[i].Position = corners[i];
               krabiceVerts[i].Color = color;
           }

           efekt.View = view;
           efekt.Projection = projection;

           //Vykreslení čar.
           efekt.CurrentTechnique.Passes[0].Apply();
           graphics.DrawUserIndexedPrimitives(PrimitiveType.LineList, krabiceVerts, 0, 8, krabiceIndices, 0, krabiceIndices.Length / 2);
       }
   }

A MojeHerniOkno:

public override void Draw()
       {
           base.Draw();
           BoundingBox box = new BoundingBox(Vector3.Zero, new Vector3(-20, 20, -20));
           BoundingRenderer.Render(box, Kamera.View, Kamera.Projection, Color.Purple);
       }
Odpovědět
23.5.2013 15:47
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:23.5.2013 16:27

vypadá to celkem nornálně jen bych do vykreslení přidal pro jistotu:

efekt.World=Matrix.Identity;
 
Odpovědět
23.5.2013 16:27
Avatar
magic44
Redaktor
Avatar
Odpovídá na vodacek
magic44:23.5.2013 19:48

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:23.5.2013 19:57

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

 
Odpovědět
23.5.2013 19:57
Tento výukový obsah pomáhají rozvíjet následující firmy, které dost možná hledají právě tebe!
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:24.5.2013 15:41

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

 
Odpovědět
24.5.2013 15:41
Avatar
magic44
Redaktor
Avatar
magic44:27.5.2013 9:16

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:27.5.2013 10:05

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:27.5.2013 11:06

a konsturktor pro zed vypadá jak?

 
Odpovědět
27.5.2013 11:06
Avatar
magic44
Redaktor
Avatar
magic44:27.5.2013 11:32
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:27.5.2013 11:54

místo mínus dej plus

 
Odpovědět
27.5.2013 11:54
Avatar
magic44
Redaktor
Avatar
Odpovídá na vodacek
magic44:27.5.2013 13:39

Díky.

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 16 zpráv z 16.