XNA Szybki start Kurs ITA-106 (Grafika i multimedia) – Moduł 1 Jacek Matulewski (e-mail: jacek@fizyka.umk.pl) http://www.fizyka.umk.pl/~jacek/dydaktyka/3d/ XNA Szybki start Wersja: 30 stycznia 2009
Visual Studio 2008/XNA GS 3.0 Typy projektów:
Visual Studio 2008/XNA GS 3.0 Własności projektu gry Nie ma biblioteki gotowych komponentów Dostęp do platformy .NET 3.5 Język programowania: C# 3.0 Game1.cs zamiast Form1.cs
Visual Studio 2008/XNA GS 3.0 Plik Program.cs (analog z projektów WF) Plik Game1.cs (analog Form1.cs), klasa Game1 pola typu GraphicsDeviceManager, SpriteBatch (pierwszy: kontrola okna gry, drugi: grafika 2D) konstruktor – inicjacja pól klasy gry metoda Initialize – inicjacja logiki gry metody LoadContent/UnloadContent – zasoby gry (tekstury, modele, dźwięki)
Visual Studio 2008/XNA GS 3.0 Plik Program.cs (analog z projektów WF) Plik Game1.cs (analog Form1.cs), klasa Game1 metoda Update, wywoływana cyklicznie f = 60Hz aktualizacja logiki gry (np. położenia obiektów) Metoda Draw, wywoływana cyklicznie f = 60Hz renderowanie sceny (tu instrukcje rysowania)
Pierwszy program XNA W klasie Game1 definiujemy pole – zbiór werteksów typu VertexPositionColor: private VertexPositionColor[] werteksyTrojkata = new VertexPositionColor[3]{ new VertexPositionColor(new Vector3(0.5f, -0.5f, 0), Color.White), new VertexPositionColor(new Vector3(-0.5f, -0.5f, 0), Color.White), new VertexPositionColor(new Vector3(0, 0.5f, 0), Color.White)}; Obiekt typu BasicEffect (filar prostej grafiki 3D) private BasicEffect efekt = null; protected override void Initialize() { efekt = new BasicEffect(graphics.GraphicsDevice, null); base.Initialize(); }
Pierwszy program XNA Metoda Draw (część 1): protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); GraphicsDevice gd = graphics.GraphicsDevice; gd.Clear(Color.Black); gd.VertexDeclaration = new VertexDeclaration(gd, VertexPositionColor.VertexElements); efekt.Begin(); … efekt.End(); base.Draw(gameTime); } Czyszczenie bufora (kolor tła) Typ użytych werteksów (lista własności: pozycja, kolor)
Pętla po efektach i przebiegach (stały fragment gry) Pierwszy program XNA Metoda Draw (część 1): protected override void Draw(GameTime gameTime) { … efekt.Begin(); foreach (EffectPass pass in efekt.CurrentTechnique.Passes) pass.Begin(); gd.DrawUserPrimitives<VertexPositionColor>( PrimitiveType.TriangleList, werteksyTrojkata, 0, 1); pass.End(); } efekt.End(); base.Draw(gameTime); Pętla po efektach i przebiegach (stały fragment gry)
Pierwszy program XNA Metoda Draw (część 2): protected override void Draw(GameTime gameTime) { … efekt.Begin(); foreach (EffectPass pass in efekt.CurrentTechnique.Passes) pass.Begin(); gd.DrawUserPrimitives<VertexPositionColor>( PrimitiveType.TriangleList, werteksyTrojkata, 0, 1); pass.End(); } efekt.End(); base.Draw(gameTime); Rysowanie serii prymitywów (listy trójkątów) Pozycja pierwszego werteksu w tablicy Typ prymitywów Tablica werteksów Ilość prymitywów (tu: 1 prym. = 3 wert.)
Pierwszy program XNA Metoda Draw (część 3): gd.DrawUserPrimitives<VertexPositionColor>( PrimitiveType.TriangleList, werteksyTrojkata, 0, 1); Zalety metody GraphicsDevice.DrawUserPrimitives: najprostsze możliwe rozwiązanie możliwość swobodnej zmiany własności werteksów tablicę werteksów można nawet zdefiniować inline Wada: brak buforowania werteksów w karcie graficznej!!!
Eksperymenty Nawijanie (gd.RenderState.CullMode) Prymitywy (PrimitiveType.PointList, .LineStrip, itd.) PrimitiveType.PointList PrimitiveType.LineList PrimitiveType.LineStrip Prim..Type.TriangleList Pri..Type.TriangleStrip Prim..Type.TriangleFan
Kolor i cieniowanie Kolory, cieniowanie Gourauda, tablica werteksów private VertexPositionColor[] werteksyTrojkata = new VertexPositionColor[3]{ new VertexPositionColor(new Vector3(0.5f, -0.5f, 0), Color.Red), new VertexPositionColor(new Vector3(-0.5f, -0.5f, 0), Color.Green), new VertexPositionColor(new Vector3(0, 0.5f, 0), Color.Blue)}; efekt.VertexColorEnabled = true; Do metody Initialize należy dodać instrukcję:
Transformacje Zmiana macierzy świata (metoda Initialize): Matrix macierzPrzeksztalcenia = Matrix.Identity; macierzPrzeksztalcenia *= Matrix.CreateScale(0.5f); macierzPrzeksztalcenia *= Matrix.CreateRotationZ(-MathHelper.PiOver2); macierzPrzeksztalcenia *= Matrix.CreateTranslation(0.5f, 0, 0); efekt.World = macierzPrzeksztalcenia; Matrix macierzPrzeksztalcenia = Matrix.Identity; macierzPrzeksztalcenia *= Matrix.CreateScale(0.5f); macierzPrzeksztalcenia *= Matrix.CreateRotationZ((float)-Math.PI/2.0f); macierzPrzeksztalcenia *= Matrix.CreateTranslation(0.5f, 0, 0); efekt.World = macierzPrzeksztalcenia; gd.RenderState.CullMode = CullMode.None; Jeżeli obrócimy trójkąt tyłem do nas: Spróbujmy zamiast macierzy świata zmieniać macierz widoku
Skalowanie do czasu rzeczywistego (wall time) Animacja Metoda Update: Obrót 60 razy na sekundę efekt.World *= Matrix.CreateRotationZ( gameTime.ElapsedRealTime.Milliseconds/1000.0f); Skalowanie do czasu rzeczywistego (wall time) Widoczna deformacja trójkąta (zła macierz rzutowania) Animacja kolorów Usuńmy skalowanie
Korekta uwględniająca proporcje okna Frustum Rzutowanie ortonormalne (izometryczne) Do metody Initialize: efekt.Projection = Matrix.CreateOrthographic( 2.0f, 0.0f, 100.0f); Do metody Initialize: efekt.Projection = Matrix.CreateOrthographic( 2.0f * graphics.GraphicsDevice.Viewport.AspectRatio, 2.0f, 0.0f, 100.0f); Korekta uwględniająca proporcje okna Szerokość frustum Wysokość frustum Bliż Dal
Korekta uwględniająca proporcje okna Frustum Rzutowanie perspektywiczne Do metody Initialize: efekt.Projection = Matrix.CreatePerspective( 2.0f * graphics.GraphicsDevice.Viewport.AspectRatio 2.0f, 1.0f, 100.0f); Do metody Initialize: efekt.Projection = Matrix.CreatePerspective( 2.0f * graphics.GraphicsDevice.Viewport.AspectRatio 2.0f, 1.0f, 100.0f); Korekta uwględniająca proporcje okna Szerokość frustum Wysokość frustum Bliż Dal Współrzędna Z werteksów musi być teraz mniejsza od -1. Lub musimy odsunąć kamerę!
Frustum Rzutowanie perspektywiczne Do metody Initialize: efekt.Projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver2, graphics.GraphicsDevice.Viewport.AspectRatio, 1.0f, 100.0f); Kąt widzenia Proporcje obrazu Bliż Dal Jawne uwzględnienie proporcji ekranu
Kamera Proste odsunięcie kamery: Zaawansowane ustawienie kamery: efekt.View = Matrix.CreateTranslation(0, 0, -1); Zaawansowane ustawienie kamery: efekt.View = Matrix.CreateLookAt( new Vector3(0, 0, 1), new Vector3(0, 0, 0), new Vector3(0, 1, 0)); efekt.View = Matrix.CreateLookAt( Vector3.UnitZ, Vector3.Zero, Vector3.Up); Położenie kamery Gdzie jest skierowana Polaryzacja
Buforowanie werteksów Bufor werteksów (pole klasy Game1): VertexBuffer buforWerteksowTrojkata; Inicjacja bufora (metoda Initialize): buforWerteksowTrojkata = new VertexBuffer( graphics.GraphicsDevice, werteksyTrojkata.Count()*VertexPositionColor.SizeInBytes, BufferUsage.WriteOnly); Wypełnienie bufora (metoda Initialize): buforWerteksowTrojkata.SetData<VertexPositionColor>(werteksyTrojkata);
Buforowanie werteksów Wykorzystanie bufora (metoda Draw): pass.Begin(); gd.Vertices[0].SetSource( buforWerteksowTrojkata, 0, VertexPositionColor.SizeInBytes); gd.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); pass.End(); pass.Begin(); gd.DrawUserPrimitives<VertexPositionColor>( PrimitiveType.TriangleList, werteksyTrojkata, 0, 1); pass.End(); Wskazanie bufora Inna metoda niż wcześniej Po zdefiniowaniu bufora werteksów tablica werteksów nie jest potrzebna (można ją definiować lokalnie w Initialize) Dynamiczny bufor werteksów (przykład w skrypcie)
Różności
Częstość wywoływania Update Częstość wywoływania metody Update: this.TargetElapsedTime = TimeSpan.FromSeconds(1.0f/75.0f); 75Hz (chyba, że gra się „zacina”) this.IsFixedTimeStep = false; albo Zawsze przed wywołaniem Draw
Dezaktywacja okna gry protected override void Update(GameTime gameTime) { if (!this.IsActive) return; … protected override void Draw(GameTime gameTime) { if (!this.IsActive) return; …
Tryb pełnoekranowy protected override void Initialize() { try graphics.PreferredBackBufferWidth = 800; graphics.PreferredBackBufferHeight = 600; graphics.IsFullScreen = true; graphics.ApplyChanges(); } catch (Exception exc) System.Windows.Forms.MessageBox.Show( "Błąd podczas przełączania w tryb pełnoekranowy:\n" + exc.Message, Window.Title, System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); Rozdzielczość ustalona na „sztywno” Tylko w Windows Odczytanie rozmiaru ekranu: graphics.GraphicsDevice.DisplayMode.Width graphics.GraphicsDevice.DisplayMode.Height
Zmiana wielkości okna protected override void Initialize() { try graphics.PreferredBackBufferWidth = 800; graphics.PreferredBackBufferHeight = 600; graphics.IsFullScreen = true; graphics.ApplyChanges(); } catch (Exception exc) System.Windows.Forms.MessageBox.Show( "Błąd podczas przełączania w tryb pełnoekranowy:\n" + exc.Message, Window.Title, System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); Rozdzielczość ustalona na „sztywno” Tylko w Windows Odczytanie rozmiaru ekranu: graphics.GraphicsDevice.DisplayMode.Width graphics.GraphicsDevice.DisplayMode.Height
Okno Zmiana tytułu okna: Klawisz Escape i spacja: Window.Title = "Moduł 1. XNA - Szybki start"; Klawisz Escape i spacja: if (Keyboard.GetState().IsKeyDown(Keys.Escape)) this.Exit(); if (Keyboard.GetState().IsKeyDown(Keys.Space)) graphics.ToggleFullScreen();
Jedna instancja gry static class Program { static void Main(string[] args) bool czyPierwszaInstancja; Mutex m = new Mutex(true,"MojaPierwszaGraXNA”,out czyPierwszaInstancja); if (!czyPierwszaInstancja) MessageBox.Show("Inna instancja tej gry jest już uruchomiona"); return; } using (Game1 game = new Game1()) game.Run(); m.ReleaseMutex();