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ędziNa 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 projektuPo 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 koduCzas 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 klasyGra 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 teksturMusimy 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. RysowanieRysowanie 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. DebuggingW 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
9. Klasa Weight - ciąg dalszyWcześ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 obiektu →
Resolve... →
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 elementyKlasa 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łowieTego 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