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í.

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

Vítejte po sedmnácté. V minulé lekci, 3D bludiště v XNA - Podlahy podruhé a kolize, jsme se blíže podívali na zbylé podlahy a kolize.

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 - 3D bludiště v XNA

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ě, 3D bludiště v XNA - Kolize poprvé, 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.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

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

 

Předchozí článek
3D bludiště v XNA - Podlahy podruhé a kolize
Všechny články v sekci
3D bludiště v XNA
Přeskočit článek
(nedoporučujeme)
3D bludiště v XNA - Kolize poprvé
Článek pro vás napsal vodacek
Avatar
Uživatelské hodnocení:
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.
Aktivity