Lekce 2 - 3D bludiště v XNA - Kamera a mřížka
Vítejte po čtrnácté. V minulé lekci, 3D bludiště v XNA - Bludiště poprvé, jsme si rozebrali ukázkovou hru.
V tomto díle se připravíme na bludiště kameru, i když ne úplně přesně tu, kterou použijeme ve výsledku. Budeme se potřebovat při tvorbě v prostoru pohybovat, prohlížet výsledky a podobně. Potom si také vytvoříme debugovací mřížku, která se bude hodit i do editoru, ale i pro měření vzdáleností.
Free --camera-- kamera
Jak jsem naznačil v článku o kamerách, máme tu jednu, co se jmenuje free a nebo také někdy často fps kamera. Znáte ji určitě z každé střílečky, funguje naprosto snadno. Postava se pohybuje tam, kam kamera míří, nepočítáme-li různé úskoky stranou a podobně. My si pro naše zkoušecí účely uděláme kameru, která se bude pohybovat nahoru i dolů bez jakýchkoliv omezení, nebude tedy realistická. Pro potřeby vývoje se bez ní neobejdeme. Dívat se kolem sebe budeme myší a pohybovat se třebas šipkami na klávesnici.
Jako základ použijeme naši třídu s target kamerou. Do složky s kamerama
si tedy přidáme novou třídu a nazveme ji FreeCamera
. Učiníme
ji veřejnou, upravíme jmenný prostor, ale na to jste si snad už doufám
zvykli a budeme dědit od třídy TargetKamera
. Vytvoříme si dvě
float
proměnné, kde budeme skladovat natočení kamery. Nazveme
je Yaw
a Pitch
, jistě si pamatujete na obrázek s
rotacemi, tak přesně stejně se jmenovaly dvě z nich, třetí nebudeme
potřebovat:
float fYaw, fPitch; public float Yaw{ get{ return fYaw; } set{ fYaw = value; } } public float Pitch{ get{ return fPitch; } set{ fPitch = value; } }
V konstruktoru je vynulujeme:
public FreeKamera(GameScreen okno, Vector3 pozice, Vector3 target) : base(okno, pozice, target){ fYaw = 0; fPitch = 0; }
Nyní stačí už jen a pouze kameru rozpohybovat. Zde přijde na řadu
metoda Update
a trocha té vektorové matematiky. V tomto
případě nebudeme považovat třídu Vector
3 jako bod, ale jako
vektor. Kdo neví co to vektor je tak mu to nic moc neřekne, ale je to taková
veličina, které kromě své velikosti mí také svůj směr. Pěkným
příkladem je kupříkladu síla působící na těleso. Nejenže ji můžeme
změřit jak je veliká, ale také určit jakým směrem působí. Přesně toho
využijeme. K modifikaci vektorů použijeme naše oblíbené matice. Je to
velmi výhodné, protože nám pak odpadne veškeré počítání, o které se
postarají metody uvnitř XNA
. Kód je to už trošku
složitější, ale doufám, že to zvládnete. Přepišme si tedy metodu
Update
. Volání metody Update
u našeho předka si
ponecháme a budeme psát nad něj. Bude se nám hodit. Prvně si určíme
natočení kamery z pozici myši. K tomu nám slouží proměnné
DeltaX
a DeltaY
ve třídě Input
, kterou
jsme si předminule vytvořili.
fYaw -= Parent.Engine.Input.DeltaX*0.01f; fPitch -= Parent.Engine.Input.DeltaY * 0.01f;
Mínus je použito proto, aby pohyb korespondoval s pohybem myši. Dále si vytvoříme matici pro rotaci. Metodu jsme používali již dříve u natáčení modelů, překvapivě je to úplně stejné:
Matrix rotace = Matrix.CreateFromYawPitchRoll(fYaw, fPitch, 0);
Poslední parametr necháme na nule, protože rotaci kolem osy Z
nebudeme používat. Přepočteme si také cíl, kam se kamera dívá, protože
právě ten otáčením kamery měníme. Nový cíl získáme tak, že k
současné pozici kamery přičteme vytvořený natočený vektor:
Target = Pozice + Vector3.Transform(Vector3.Forward,rotace);
Vector3.Forward je vektor (0,0,-1)
a když se znovu zamyslíte,
tak jakoby vedl od nás před obrazovkou do ní, vede tedy dopředu. Provedeme
transformaci naší maticí a to metodou Transform
. Tato metoda
jednoduše vynásobí daný vektor maticí a tím jej natočí. Otáčení
kamery máme hotovo, ale co její posun? Uděláme jej velmi podobně. Hned pod
vytvoření naší matice přidáme novou proměnnou posun a vynulujeme ji:
Vector3 posun = Vector3.Zero;
Pak již jen skrze podmínky na stisknutí klávesy k tomuto vektoru přičteme jednotlivé směry, stejně jako jsem to udělal já:
if (Parent.Engine.Input.DrzenaKlavesa(Keys.Up)) posun += Vector3.Forward; if (Parent.Engine.Input.DrzenaKlavesa(Keys.Down)) posun += Vector3.Backward; if (Parent.Engine.Input.DrzenaKlavesa(Keys.Left)) posun += Vector3.Left; if (Parent.Engine.Input.DrzenaKlavesa(Keys.Right)) posun += Vector3.Right;
Backward
míří směrem z obrazovky k nám, Left
míří doleva a Right
doprava. Výsledný vektor ještě
zvětšíme, respektive zmenšíme a tím nastavíme rychlost pohybu:
posun *= 0.1f;
A výsledný vektor natočíme a přičteme k současné pozici:
Pozice += Vector3.Transform(posun,rotace);
To je vše. Nyní máme kameru vytvořenu a zkusíme si jí přidat do
našeho herního okna. Měl by tam být od minula model koberce, což se nám
akorát hodí. Namísto target kamery dáme Free
kameru a to je
veškerá změna co potřebujeme udělat. Pokud nyní hru spustíte, měla by
kamera reagovat na pohyb myši na stisky kláves s šipkami
Popsané řešení ale není stoprocentně dobré. Tato kamera bude značně
při svém pohybu reagovat na FPS
, což není zrovna žádoucí. Z
tohoto vlivu se můžeme snadno vymanit a to tak, že při posouvání
zohledníme čas, který utekl od posledního zavolání metody
Update
a to následovně:
posun *= 0.1f * (float)Parent.Engine.GameTime.ElapsedGameTime.TotalMilliseconds;
Blahopřeji, kamera je hotová. Dalším prvkem který se bude hodit je...
Pomocná mřížka
Hodí se všude. Proto si ji uděláme i my. Není to ale můj výmysl,
sebral jsem to tady
odtud, upravil pro XNA
, a mírně vytunil, jak je mým dobrým
zvykem. Udělejme si tedy třídu Mrizka
ve složce s komponentami.
Opět dědíme od komponenty, opět je třída veřejná. Jako ostatně
vždycky. Vytvoříme si pole vertexů, kde si budeme ukládat jednotlivé body
mřížky, použijeme VertexPositionColor
. Potřeba bude také
instance třídy BasicEffect
, pomocí kterého vše budeme
vykreslovat jako v prvních dílech našeho seriálu:
VertexPositionColor[] body; BasicEffect effect;
V konstruktoru, který bude přijímat počet čar a jejich vzdálenost od sebe, si toto pole inicializujeme:
int vzdalenost; int pocet; public Mrizka(int pocet, int vzdalenost){ this.vzdalenost=vzdalenost; this.pocet = pocet; body=new VertexPositionColor[pocet*4]; }
Ptáte se proč 4x víc? Jeden počáteční bod, jeden koncový pro každou
přímku a k tomu přidejme že máme dva směry. Dále si přepíšeme metodu
Load
, kde si body vytvoříme. V algoritmu pro výpočet opět
použijeme vektory. Vytvoříme si instanci efektu a povolíme barvy:
effect = new BasicEffect(Parent.Engine.GraphicsDevice); effect.VertexColorEnabled = true;
Určíme barvu mřížky, tu si ostatně můžeme posílat i z konstruktoru, ale pro jednoduchost:
Color Color = Color.Black;
Založíme si také pomocnou proměnnou k, která nám bude počítat index v poli s vrcholy:
int k = 0;
Následuje celý kód pro generování:
for (int i = 0; i < pocet; i++){ Vector3 tmp = Vector3.Left * vzdalenost * i; if (pocet / 2 == i){ body[k] = new VertexPositionColor(tmp, Color.Red); body[k + 1] = new VertexPositionColor(tmp + Vector3.Forward * (vzdalenost * (pocet - 1)), Color.Red); tmp = Vector3.Forward * vzdalenost * i; body[k + 2] = new VertexPositionColor(tmp, Color.Green); body[k + 3] = new VertexPositionColor(tmp + Vector3.Left * (vzdalenost * (pocet - 1)), Color.Green); } else{ body[k] = new VertexPositionColor(tmp, Color); body[k + 1] = new VertexPositionColor(tmp + Vector3.Forward * (vzdalenost * (pocet - 1)), Color); tmp = Vector3.Forward * vzdalenost * i; body[k + 2] = new VertexPositionColor(tmp, Color); body[k + 3] = new VertexPositionColor(tmp + Vector3.Left * (vzdalenost* (pocet - 1)), Color); } k += 4; }
Kód je složitější než u kamery, přesto jej lze snadno rozebrat. A
víte co? Zkuste si na přijít sami, jak to vlastně funguje. Zbývá nám už
jen a jenom vykreslení. Přepíšeme si tedy metodu Draw
,
tentokrát ale tu s více parametry.
public override void Draw(Matrix View, Matrix Projection, Vector3 CameraPosition)
Uvnitř nastavíme našemu efektu nezbytné matice View
a
Projection
:
effect.View = View; effect.Projection = Projection;
Jako obvykle pošleme nastavení do grafické karty:
effect.CurrentTechnique.Passes[0].Apply();
A vše známou metodou vykreslíme, používáme seznam čar, proto tedy počet vykreslovaných objektů je o polovinu menší než je kapacita pole:
Parent.Engine.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineList, body, 0, body.Length / 2);
Hotovo, přidáme si naši mřížku do herního okna:
AddComponent(new Mrizka(20,30));
20 čar bude stačit a budou od sebe vzdáleny 30 bodů. Pokud hru spustíme, tak by měl být výstup podobný tomuto:

Že ne? Opravdu ne. Mřížka není pod kobercem, ale za ním. Máme pár
možností jak to vyřešit. Můžeme se obligátně tvářit, že je vše v
pořádku. Můžeme to zatajit nebo zamlčet. Můžeme všechny body
přepočítat a posunout do středu souřadnic, ale to je nudné. Co když
budeme chtít polohu mřížky změnit? Nejsnadněji to provedeme maticí
World
. Takže do metody Draw
umístíme posun středu
mřížky do nuly:
effect.World = Matrix.CreateTranslation(new Vector3(pocet * (vzdalenost / 2),0, pocet * (vzdalenost / 2)));
Skvěle. Nyní je to už konečně hotové.
Příště, 3D bludiště v XNA - Editor map, se pokusíme vytvořit si bludiště z jednoduchých kostek. Opět očekávám komentáře dole pod články a případné otázky, dotazy, nejasnosti mi určitě zvednou náladu, takže se je nebojte psát.
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 330x (1.71 MB)
Aplikace je včetně zdrojových kódů v jazyce C# XNA