Podstawy XNA 24066 12

O temacie

Autor Ezzam

Zaczęty 3.08.2012 roku

Wyświetleń 24066

Odpowiedzi 12

Ezzam

Ezzam

Użytkownicy
posty391
Propsy367
  • Użytkownicy

Ezzam

Podstawy XNA
2012-08-03, 11:40(Ostatnia zmiana: 2012-08-16, 12:34)
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.

public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
Jest to miejsce, w którym deklarujemy pola klasy Game1.

       protected override void Initialize()
        {
            base.Initialize();
        }
Inicjalizacja gry. Kod, który tu wpiszemy zostanie wykonany przy uruchamianiu gry.

       protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
        }
Pełni taką samą funkcję jak Initialize(), ale jest przeznaczony do wczytywania zasobów graficznych.

       protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            base.Update(gameTime);
        }
Metoda Update() jest wywoływana co klatkę. To tutaj wykonywany jest ruch, wykrywanie kolizji etc.

       protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            base.Draw(gameTime);
        }
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:
       Texture2D playerTexture;
        Texture2D groundTexture;

        Rectangle playerRectangle;
        Rectangle groundRectangle;
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:
           playerRectangle = new Rectangle(0, 416, 32, 32);
            groundRectangle = new Rectangle(0, 448, 800, 32);
Zadeklarowanym wcześniej prostokątom zostały przypisane wartości X, Y, Width oraz Height.

Teraz pora na wczytanie tekstur:
           playerTexture = Content.Load<Texture2D>("player");
            groundTexture = Content.Load<Texture2D>("ground");
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ć:
           if (Keyboard.GetState().IsKeyDown(Keys.Right))
            {
                playerRectangle.X += 3;
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Left))
            {
                playerRectangle.X -= 3;
            }
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:
           spriteBatch.Begin();

            spriteBatch.End();
Między te dwie funkcje wpakujemy kod odpowiadający za rysowanie.

           spriteBatch.Draw(groundTexture, groundRectangle, Color.White);
            spriteBatch.Draw(playerTexture, playerRectangle, Color.White);
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 (DebugStart 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 StudioGame 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ć:
       Texture2D Texture;
        Rectangle Rectangle;
        Vector2 Position;
        float Gravity;
Jak widać, typy obiektów nie są rozpoznane. Żeby to zmienić, prawoklik na typie obiektuResolve...using [coś].

Skoro zadeklarowaliśmy już pola, pora napisać konstruktor klasy. To za jego pomocą można tworzyć nowe obiekty danej klasy.
       public Weight(Texture2D texture, Rectangle rectangle, Vector2 position, float gravity)
        {
            Texture = texture;
            Rectangle = rectangle;
            Position = position;
            Gravity = gravity;
        }
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':
       public Rectangle Rectangle;
        public Vector2 Position;
        public float Gravity;
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.
       public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(Texture, Rectangle, Color.White);
        }
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:
       List<Weight> weightList;

        Rectangle mouseRectangle;
        Boolean canClick;

        Texture2D weightTexture;
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().
           weightList = new List<Weight>();

            mouseRectangle = new Rectangle(0, 0, 1, 1);
            canClick = true;

            weightTexture = Content.Load<Texture2D>("weight");
            IsMouseVisible = true;
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:
           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;
            }
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:
           foreach (Weight weight in weightList)
            {
                weight.Draw(spriteBatch);
            }
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():
           foreach (Weight weight in weightList)
            {
                weight.Position.Y += weight.Gravity;
                weight.Rectangle.Y = (int)weight.Position.Y;
                weight.Gravity += 0.1f;
            }
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ć:
           foreach (Weight weight in weightList)
            {
                if (weight.Rectangle.Intersects(playerRectangle))
                {
                    playerRectangle.Y = -100;
                }
            }
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

Adanos

Administrator
Szara eminencja
posty5204
Propsy3870
ProfesjaProgramista
  • Administrator
  • Szara eminencja
Cytuj
public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
Jest to miejsce, w którym deklarujemy zmienne globalne.
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

Wowoz

Użytkownicy
Wowoźny
posty3699
Propsy4862
NagrodyVV
Profesjabrak
  • Użytkownicy
  • Wowoźny
Wlasnie Adanos, pokaz lamuskowi kto tu rzadzi!  :lol2:
 

Ezzam

Ezzam

Użytkownicy
posty391
Propsy367
  • Użytkownicy
Na przyszłość będę pamiętał. W tutorialu już poprawiłem :ok:
 

Wojak

Wojak

Użytkownicy
posty67
Propsy9
  • Użytkownicy
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"
if (Keyboard.GetState().IsKeyDown(Keys.Right))
            {
                playerRectangle.X += (int)playerspeed*deltatime;
            }
 if (Keyboard.GetState().IsKeyDown(Keys.Left))
            {
                playerRectangle.X -= (int)playerspeed*deltatime;
            }

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ę:
  weight.Position.Y += weight.Gravity*60*gameTime.ElapsedGameTime.TotalSeconds;

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
 

Ezzam

Ezzam

Użytkownicy
posty391
Propsy367
  • Użytkownicy
Fakt, ale XNA ma sposób na w miarę normalne działanie przy limitowanym FPS (trafiłeś, 60).

Link do artykułu: http://blogs.msdn.com/b/shawnhar/archive/2007/07/25/understanding-gametime.aspx
 

Wojak

Wojak

Użytkownicy
posty67
Propsy9
  • Użytkownicy
Fakt, ale XNA ma sposób na w miarę normalne działanie przy limitowanym FPS (trafiłeś, 60).

Link do artykułu: http://blogs.msdn.com/b/shawnhar/archive/2007/07/25/understanding-gametime.aspx

To wiele wyjaśnia, założyłem że XNA zawsze działa jak w sytuacji gdy Game.IsFixedTimeStep = false ...
 

mgr Fartuess

mgr Fartuess

Użytkownicy
Kiedyś to były czasy!
posty1485
Propsy890
ProfesjaProgramista
  • Użytkownicy
  • Kiedyś to były czasy!
i tak lepiej na klatkach niż na cyklach procesora liczyć czas. Różne śmieszne rzeczy wynikały z odpalania zabytkowych gierek na nico mocniejszych maszynach :) . No, ale koncepcję Delta time należy znać jak się myśli nieco poważniej o pisaniu gierek.
 
Popisuje się ciągle menda jedna...

Remix

Remix

Użytkownicy
Murzyn Internetów :O
posty291
Propsy41
ProfesjaProgramista
  • Użytkownicy
  • Murzyn Internetów :O
Mam mały problem, jak chcę włączyć gre w XNA (bez żadnej edycji) wyskakuje mi coś takiego: Film
 
https://www.youtube.com/watch?v=TNdy0ea6gc4

RafalBudzis

RafalBudzis

Użytkownicy
posty1967
Propsy808
ProfesjaSkrypter
  • Użytkownicy
W opcjach projektu 1 zakładka XNA coś tam zmieniasz profil graficzny z HiDef na ten drugi.

Remix

Remix

Użytkownicy
Murzyn Internetów :O
posty291
Propsy41
ProfesjaProgramista
  • Użytkownicy
  • Murzyn Internetów :O
Wielkie dzięki, działa.
 
https://www.youtube.com/watch?v=TNdy0ea6gc4

Ezzam

Ezzam

Użytkownicy
posty391
Propsy367
  • Użytkownicy
Wystarczyło uważnie przeczytać tutek, rozwiązanie jest w punkcie ósmym :)
 

Paser

Paser

Użytkownicy
Crazy Diamond
posty281
Propsy119
ProfesjaNierób
  • Użytkownicy
  • Crazy Diamond
Pytanko, czemu nie dąłeś w tucie:
           mouseRectangle.X = Mouse.GetState().X;
            mouseRectangle.Y = Mouse.GetState().Y;
?
Bez tego odważniki cały czas będą się pojawiały w  pozycji domyślnej dla retangle.
 


0 użytkowników i 1 Gość przegląda ten wątek.
0 użytkowników
Do góry