Developing > XNA
Podstawy XNA
Ezzam:
Witam w moim pierwszym tutorialu na temat XNA. Obejmie on stworzenie prostej gierki. Do pełnego zrozumienia tutoriala przyda się znajomość podstaw C#, chociaż będę próbował tłumaczyć zagadnienia z języka dla tych, którzy go nie znają. Link do szybkiego kursu C#: http://rbwhitaker.wikidot.com/c-sharp-tutorials. A więc zaczynamy.
1. Przygotowanie potrzebnych narzędzi
Na początek należy ściągnąć Visual C# 2010 Express (http://www.microsoft.com/visualstudio/en-us/products/2010-editions/visual-csharp-express) oraz XNA Game Studio 4.0 (http://www.microsoft.com/en-us/download/details.aspx?id=23714). Najpierw trzeba zainstalować Visual C# Express, ponieważ XNA Game Studio jest dodatkiem do niego.
2. Tworzenie nowego projektu
Po odpaleniu Visual C#, klikamy na New Project.... Wyświetla się okno. Wybieramy Windows Game (4.0), ustawiamy nazwę dla swojego projektu (ja wybrałem XnaTut) i wybieramy jego lokalizację na dysku. Po kliknięciu OK przechodzimy do kolejnego punktu.
3. Przegląd kodu
Czas na omówienie sekcji kodu.
--- Kod: ---public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
--- Koniec kodu ---
Jest to miejsce, w którym deklarujemy pola klasy Game1.
--- Kod: --- protected override void Initialize()
{
base.Initialize();
}
--- Koniec kodu ---
Inicjalizacja gry. Kod, który tu wpiszemy zostanie wykonany przy uruchamianiu gry.
--- Kod: --- protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
}
--- Koniec kodu ---
Pełni taką samą funkcję jak Initialize(), ale jest przeznaczony do wczytywania zasobów graficznych.
--- Kod: --- protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
base.Update(gameTime);
}
--- Koniec kodu ---
Metoda Update() jest wywoływana co klatkę. To tutaj wykonywany jest ruch, wykrywanie kolizji etc.
--- Kod: --- protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
base.Draw(gameTime);
}
--- Koniec kodu ---
Jak nazwa wskazuje, ta metoda odpowiada za rysowanie.
4. Tworzenie nowej klasy
Gra stworzona podczas tego tutoriala będzie polegała na unikaniu spadających przedmiotów. Każdy z tych przedmiotów musi posiadać parę parametrów, takich jak prędkość, pozycja etc. Jako że tych przedmiotów musi być wiele, należy stworzyć nową klasę. W tym celu prawoklik na nazwie projektu w Solution Explorer (w moim przypadku XnaTut) → Add... → Class.... Nazywamy swoją klasę (ja nazwałem ją 'Weight') i klikamy OK. Zostawmy na chwilę nową klasę i przejdźmy do kolejnego punktu.
5. Przypisywanie wartości i wczytywanie tekstur
Musimy teraz zadeklarować dwie tekstury i dwa prostokąty. W tym celu piszemy w Game1.cs:
--- Kod: --- Texture2D playerTexture;
Texture2D groundTexture;
Rectangle playerRectangle;
Rectangle groundRectangle;
--- Koniec kodu ---
Do zadeklarowania tekstur będą nam potrzebne same tekstury. Oto te arcydzieła:
Kolejno: ground, player, weight. Żeby dodać je do naszego projektu, prawoklik na [nazwa projektu]Content (Content) → Add... → Existing Item..., szukamy plików i lewoklik na Add, żeby znalazły się w naszym projekcie.
Stwórzmy teraz prostokąty. W funkcji LoadContent() wpisujemy:
--- Kod: --- playerRectangle = new Rectangle(0, 416, 32, 32);
groundRectangle = new Rectangle(0, 448, 800, 32);
--- Koniec kodu ---
Zadeklarowanym wcześniej prostokątom zostały przypisane wartości X, Y, Width oraz Height.
Teraz pora na wczytanie tekstur:
--- Kod: --- playerTexture = Content.Load<Texture2D>("player");
groundTexture = Content.Load<Texture2D>("ground");
--- Koniec kodu ---
Nic skomplikowanego, przypisujemy nazwę tekstury siedzącej w Content Pipeline do pola Texture2D zadeklarowanej wcześniej.
6. Metoda Update() - poruszanie postacią
Skoro mamy już gotowe pola dotyczące postaci, pora puścić ją w ruch. W tym celu w funkcji Update() należy napisać:
--- Kod: --- if (Keyboard.GetState().IsKeyDown(Keys.Right))
{
playerRectangle.X += 3;
}
if (Keyboard.GetState().IsKeyDown(Keys.Left))
{
playerRectangle.X -= 3;
}
--- Koniec kodu ---
Co oznacza: jeżeli wciśnięty jest dany przycisk (w tym przypadku strzałki lewa i prawa), do parametru X naszego prostokąta są dodawane lub odejmowane 3 piksele.
Oczywiście to nie wystarczy, żebyśmy mogli biegać naszą postacią, trzeba ją jeszcze narysować.
7. Rysowanie
Rysowanie sprite'ów w XNA odbywa się za pomocą SpriteBatcha. Żeby cokolwiek narysować, trzeba mu wyznaczyć granice działania:
--- Kod: --- spriteBatch.Begin();
spriteBatch.End();
--- Koniec kodu ---
Między te dwie funkcje wpakujemy kod odpowiadający za rysowanie.
--- Kod: --- spriteBatch.Draw(groundTexture, groundRectangle, Color.White);
spriteBatch.Draw(playerTexture, playerRectangle, Color.White);
--- Koniec kodu ---
Podstawowa metoda spritebatch.Draw wymaga trzech parametrów: tekstury, prostokąta na który ma nałożyć teksturę oraz koloru (biały jest często najodpowiedniejszy, ponieważ nie zmienia kolorów tekstury).
8. Debugging
W tym momencie można odpalić projekt (Debug → Start Debugging), by zobaczyć efekt dotychczasowej pracy.
Uwaga: jeśli podczas próby debugowania wyskakuje okno mówiące o tym, że karta graficzna nie wspiera profilu HiDef, Project → [Nazwa projektu] Properties... → zakładka XNA Game Studio → Game profile: Use Reach.
W tej chwili można biegać gościem po szarym podłożu, ale niedługo zaczną na niego spadać odważniki :lol:
9. Klasa Weight - ciąg dalszy
Wcześniej stworzyliśmy klasę, lecz zostawiliśmy ją pustą. Pora to zmienić. W tym celu w klasie należy wpisać:
--- Kod: --- Texture2D Texture;
Rectangle Rectangle;
Vector2 Position;
float Gravity;
--- Koniec kodu ---
Jak widać, typy obiektów nie są rozpoznane. Żeby to zmienić, prawoklik na typie obiektu → Resolve... → using [coś].
Skoro zadeklarowaliśmy już pola, pora napisać konstruktor klasy. To za jego pomocą można tworzyć nowe obiekty danej klasy.
--- Kod: --- public Weight(Texture2D texture, Rectangle rectangle, Vector2 position, float gravity)
{
Texture = texture;
Rectangle = rectangle;
Position = position;
Gravity = gravity;
}
--- Koniec kodu ---
Stworzyliśmy konstruktor klasy Weight, który wymaga podania czterech parametrów: tekstury, prostokąta, dwuwymiarowego wektora oraz floata. W kodzie konstruktora pola (np. Texture) zostały przypisane do parametrów (np. texture).
Przed definicją prostokąta, wektora i floata dopisujemy keyword 'public':
--- Kod: --- public Rectangle Rectangle;
public Vector2 Position;
public float Gravity;
--- Koniec kodu ---
Teraz te trzy pola można zmienić w innych plikach projektu. To ważne, ponieważ update stanu gry będzie wywoływany w Game1.cs.
Żeby ułatwić sobie rysowanie obiektów danej klasy, można napisać w klasie funkcję Draw.
--- Kod: --- public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(Texture, Rectangle, Color.White);
}
--- Koniec kodu ---
Ta metoda jest dostępna wszędzie (public) i nie zwraca wartości (void). Kod rysowania jest identyczny jak w przypadku rysowania w głównym pliku gry.
10. Wzbogacenie Game1.cs o potrzebne elementy
Klasa odważnika jest już gotowa, teraz trzeba przygotować Game1.cs do współpracy z nią. Zadeklarujmy nowe pola:
--- Kod: --- List<Weight> weightList;
Rectangle mouseRectangle;
Boolean canClick;
Texture2D weightTexture;
--- Koniec kodu ---
Stworzyliśmy listę odważników, nowy prostokąt i Booleana (będą potrzebne do sterowania myszką) oraz nową teksturę. Teraz pora przypisać do nich wartości w LoadContent().
--- Kod: --- weightList = new List<Weight>();
mouseRectangle = new Rectangle(0, 0, 1, 1);
canClick = true;
weightTexture = Content.Load<Texture2D>("weight");
IsMouseVisible = true;
--- Koniec kodu ---
Lista jest zrobiona bez parametrów, nowy prostokąt ma 1 piksel szerokości i 1 piksel wysokości. Dzięki ostatniej linijce kursor jest widoczny w naszej grze.
Teraz czas na dostosowanie funkcji Update(). Dopiszmy następujące rzeczy:
--- Kod: --- if (Mouse.GetState().LeftButton == ButtonState.Pressed && canClick == true)
{
Weight weight = new Weight(weightTexture, new Rectangle(mouseRectangle.X, mouseRectangle.Y, 64, 64), new Vector2(mouseRectangle.X, mouseRectangle.Y), 0f);
weightList.Add(weight);
canClick = false;
}
if (Mouse.GetState().LeftButton == ButtonState.Released)
{
canClick = true;
}
--- Koniec kodu ---
Tutaj dzieje się dość sporo. Jeżeli lewy przycisk myszki jest wciśnięty i jest możliwość kliknięcia (pole canClick), wtedy jest tworzony nowy obiekt klasy Weight. Obiekt ten jest później dodawany do stworzonej wcześniej listy, a pole canClick przyjmuje wartość false. Z kolei jeśli lewy przycisk nie jest wciśnięty, to canClick staje się true, dzięki czemu pierwszy warunek znowu jest wykonalny.
Przejdźmy teraz do funkcji Draw().
Żeby narysować wszystkie elementy z listy weightList, należy zastosować pętlę for lub foreach. W tym przypadku posłużymy się tą drugą. W miejscu, w którym rysowaliśmy inne rzeczy dopisujemy:
--- Kod: --- foreach (Weight weight in weightList)
{
weight.Draw(spriteBatch);
}
--- Koniec kodu ---
Oznacza to: dla każdego obiektu klasy Weight w liście weightList wykonywana jest metoda Draw stworzona w klasie Weight. jako argument do niej dostarczamy spriteBatch. Mała uwaga: w pętli foreach, weight (z małej litery) jest zmienną lokalną, więc można nazwać ją jakkolwiek się chce.
Jeśli teraz odpalimy grę, będzie można ustawiać unoszące się w powietrzu odważniki. Pora zrobić coś, by spadały jak na odważniki przystało.
11. Update() dla odważników oraz wykrywanie kolizji
Żeby odważniki spadały jak należy, trzeba je oczywiście zaktualizować. W tym celu piszemy w funkcji Update():
--- Kod: --- foreach (Weight weight in weightList)
{
weight.Position.Y += weight.Gravity;
weight.Rectangle.Y = (int)weight.Position.Y;
weight.Gravity += 0.1f;
}
--- Koniec kodu ---
Znów pętelka foreach. Dla każdego odważnika (konkretnie jego wektora) aplikujemy grawitację oraz aktualizujemy pozycję prostokąta, by siedział on tam, gdzie wektor. Mała uwaga: (int) to cast, jest on potrzebny, ponieważ prostokąt przyjmuje jedynie wartości int, a wektor operuje na wartościach float. Jest to więc nic innego niż doraźna konwersja jednostek.
Jeśli teraz odpalimy grę, zobaczymy efekt - postawione odważniki spadają.
Pora zabrać się za wykrywanie kolizji. W funkcji Update() należy wpisać:
--- Kod: --- foreach (Weight weight in weightList)
{
if (weight.Rectangle.Intersects(playerRectangle))
{
playerRectangle.Y = -100;
}
}
--- Koniec kodu ---
Kolejna pętla foreach. Dla każdego odważnika sprawdzamy, czy wystąpiła kolizja z prostokątem gracza. Jeśli tak, przemieszczamy gracza tam, gdzie jest niewidoczny (tylko dlatego, że w tym tutorialu nie chce mi się bawić w boolean flag dla gracza).
12. Posłowie
Tego by było na tyle. Jeśli będę robił następne tutoriale, wytłumaczę to, na co zabrakło mi chęci dzisiaj. Komentarze i krytyka mile widziane.
Jeszcze jedno - dla ćwiczeń możecie próbować rozbudować ten projekt.
Pliki projektu, w razie gdyby ktoś się gdzieś zgubił: https://dl.dropbox.com/u/67704253/Xna%20Tutorial/1/source1.zip
Adanos:
--- Cytuj ---
--- Kod: ---public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
--- Koniec kodu ---
Jest to miejsce, w którym deklarujemy zmienne globalne.
--- Koniec cytatu ---
Nie używaj tak brzydkich słów. ;p W programowaniu obiektowym (OOP) nie używa się zmiennych globalnych. W przykładzie powyżej są to pola klasy.
W dalszej części tutoriala używasz słów: zmienna i funkcja. W OOP używa się słów pole ("zmienna wchodząca w skład klasy") i metoda.
Wowoz:
Wlasnie Adanos, pokaz lamuskowi kto tu rzadzi! :lol2:
Ezzam:
Na przyszłość będę pamiętał. W tutorialu już poprawiłem :ok:
Wojak:
Mam drobną uwagę, nie jest to coś specyficznego dla C# i XNA, ale raczej wiedza która przyda się każdemu kto myśli o tworzeniu girr, mianowicie chodzi o poruszanie gracza - w tym tutorialu poruszasz gracza o 3 piksele co klatkę co napewno będzie się sprawdzało przy tak prostej grze i przy założenie że FPS jest limitowany (zgaduję 60FPS), w takiej sytuacji może być traktowany jako stała wartość.
Jednak gdy mamy do czynienia z wymagającą sprzętową grą, lub gry FPS nie jest limitowany, napewno będzie on inny dla każdego kompótera, a co za tym idzie prędkość gracza będzie różna...
I tutaj pojawia się koncepcja "delta time"
--- Kod: --- if (Keyboard.GetState().IsKeyDown(Keys.Right))
{
playerRectangle.X += (int)playerspeed*deltatime;
}
if (Keyboard.GetState().IsKeyDown(Keys.Left))
{
playerRectangle.X -= (int)playerspeed*deltatime;
}
--- Koniec kodu ---
playerspeed przy założeniu 60FPS i 3 pikseli na klatkę będzie wynosiło 180 pikseli na sekundę
deltatime z tego co udało mi się znaleźć w XNA wygląda tak: gameTime.ElapsedGameTime.TotalSeconds
Główna zaleta - prędkość gracza będzie niezależna od FPS
Główna wada - przy bardzo niskim FPS mogą być problemy z wyktywaniem kolizji
oczywiście powinno też się znormalizować grawitację:
--- Kod: --- weight.Position.Y += weight.Gravity*60*gameTime.ElapsedGameTime.TotalSeconds;
--- Koniec kodu ---
chciał bym tu zaznaczyć że nie znam się na C#, więc jeśli powyższe przykłady kodu to głupota, to przepraszam, ale zasada powinna być jasna
Nawigacja
[#] Następna strona
Idź do wersji pełnej