Tworzenie projektu  Udostępnij na: Facebook

Autor: Dawid Pośliński

Opublikowano: 2011-01-26

Po poprawnym zainstalowaniu XNA Game Studio można już uruchomić Visual Studio.

Proces tworzenia projektu można przedstawić w kilku prostych krokach:

  1. Start Page ->NewProject (Ctrl + Shift + N).


    Rys. 1. Wybór nowego projektu w Microsoft Visual Studio.

  2. Następnie Instaled Templates ->Visual C# -> XNA Game Studio 4.0.

  3. W polu Templates należy wybrać Windows Game (4.0).

  4. W polu Name należy wpisać nazwę projektu, w tym przypadku PierwszaAplikacja.

  5. Opcjonalnie można zmienić Location wedle uznania (miejsce, w którym znajdą się pliki tworzonego projektu).

  6. Kliknąć OK, co sprawi, że aplikacja wygeneruje wszystkie niezbędne pliki projektu.


    Rys. 2. Tworzenie projektu XNA: PierwszaAplikacja.

Solution Explorer

Oczom użytkownika ukaże się struktura projektu (Solution Explorer), jaką widać po prawej stronie. Warto wiedzieć, po co jest to wszystko.

W folderze Properties znajduje się plik AssemblyInfo.cs, w którym są zarówno wszelkie informacje o autorze aplikacji, jak i opis samej aplikacji. Będą one dostępne, po skompilowaniu aplikacji, po kliknięciu prawym przyciskiem myszy na aplikacji -> Właściwości –> Podsumowanie.


Rys. 3. Widok struktury projektu.

W folderze References znajdują się wszelkie zewnętrzne powiązania z wytwarzaną aplikacją. Klasy te nie zostaną dołączone do projektu, a jedynie podczas uruchamiania środowisko .NET będzie próbowało je wywołać (będą musiały być zainstalowane w systemie).

Folder Content References zawiera referencje do dedykowanych projektów Visual Studio, w których umieszczona będzie wszelka zawartość na potrzeby gry. Te projekty to jedno z najistotniejszych miejsc w projekcie. Tam należy umieszczać wszystkie pliki, jakie twórca aplikacji chce dołączyć do aplikacji. Pliki zostaną zakodowane w sposób uniemożliwiający ich modyfikację (po kompilacji), co zapobiegnie wykradnięciu stworzonych przez twórcę aplikacji tekstur, dźwięków czy też modeli. Projekt zawartości można poznać po słowie kluczowym (Content) wyświetlanym w oknie Solution Explorera, na prawo od właściwej nazwy projektu. W naszym przypadku jest to PierwszaAplikacjaContent (Content).

Plik Game.ico reprezentuje ikonkę tworzonej aplikacji i można go zamienić na własną ikonkę. Natomiast plik GameThumbnail.png odpowiada za miniaturowy obrazek reprezentujący naszą aplikację w platformie XBOX Live (jeżeli gra trafi do społeczności, ta ikonka będzie wyświetlana przy naszej grze).

Kod inicjujący aplikację (metoda statyczna Main)  znajduje się w pliku Program.cs, natomiast w folderze Game1.cs – kod samej aplikacji z niezbędnymi do poprawnego działania metodami.

Omówienie podstawowego kodu

Kod znajdujący się w pliku Game1.cs został wygenerowany automatycznie. To tutaj tworzona będzie aplikacja i wywoływane będą odpowiednie metody przez XNA. Ponieważ główna klasa znajdująca się w tym pliku (Game1) dziedziczy po głównej klasie *.Game Framework XNA, a ta jest klasą abstrakcyjną, konieczne jest nadpisanie podstawowych metod:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

Słowo „using” jest używane w celu dołączenia tzw. namespace’ów, czyli przestrzeni nazw, w których znajdują się niezbędne klasy (omówienie przestrzeni nazw XNA znajduje się w rozdziale „Co to jest Microsoft XNA -> XNA Framework”).

namespace PierwszaAplikacja
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>

Tworzona jest przestrzeń nazw o nazwie naszego projektu „PierwszaAplikacja".

public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

Następnie wewnątrz tworzona jest klasa dziedzicząca po Microsoft.XNA.Framework.Game, w której konstruktorze (Game1()) tworzony jest obiekt klasy GraphicsDeviceManager – będzie on służył do rysowania po ekranie, oraz ustawiony zostanie główny folder, gdzie przechowywane będą tzw. assety projektu (modele, obrazki, dźwięki, filmy, czcionki itd.).

/// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

Nadpisana metoda Initialize() to pierwsze miejsce w procesie uruchamiania aplikacji, na które mamy wpływ. Tam umieszczamy kod ustawiający podstawowe parametry potrzebne do dalszego uruchomienia programu.

/// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
        }

Nadpisana metoda LoadContent() pozwala ładować niezbędne obiekty oraz zawartość z folderu Content.

/// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

Nadpisana metoda **UnloadContent()**wywoływana jest tylko podczas wyłączania aplikacji i służy do zrzucania wszelkich informacji z pamięci systemowej.

/// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // TODO: Add your update logic here

            base.Update(gameTime);
        }

Nadpisana metoda Update() jest uruchamiana wielokrotnie podczas działania aplikacji. Ilość wywołań w ciągu sekundy jest standardowo limitowana do maksymalnie 60 razy. Oczywiście jeżeli sprzęt, na którym uruchomiono aplikację, nie nadąża wywoływać metody 60 razy, wywołuje ją rzadziej. Innymi słowy, metoda ta jest uruchamiana tak często, jak tylko pozwala na to sprzęt.

/// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here

            base.Draw(gameTime);
        }
    }
}

Nadpisana metoda Draw() odpowiada za przerysowywanie ekranu i jest uruchamiana tak często, jak metoda Update(). W tej metodzie umieszczany jest cały kod związany z generowanym obrazem. Tutaj umieszcza się całą komunikację z kartą graficzną.

Po kliknięciu przycisku F5 uruchomiona zostanie aplikacja. Jak widać na rysunku 3, aplikacja nie robi nic poza narysowaniem okna i standardowego tła.


Rys. 4. Pierwsze uruchomienie aplikacji.

Teraz czas narysować coś w oknie aplikacji. Potrzebny będzie do tego dowolny obrazek, który będzie pełnił rolę Sprite’a2D. Co należy rozumieć pod tym pojęciem? Będzie to obiekt rastrowy bądź wectorowy, który zostanie narysowany w określonym przez programistę miejscu. Najczęściej wykorzystywane są one do tworzenia np. elementów interfejsu użytkownika w grach, ale nie tylko. Sprite’y mogą być zarówno obrazkami, jak i czcionkami. Warto pamiętać, że XNA uwzględnia kanał alpha w obrazach .png.

Aby dodać Asset do projektu w postaci obrazka, należy kliknąć prawym przyciskiem myszy na PierwszaAplikacjaContent (Content)->Add ->Existingitem… i wybrać dowolny obrazek. W prezentowanym przykładzie będzie to jedna ze standardowych tapet w systemie Windows.


Rys. 5. Nowy Asset w folderze Content.

Po kliknięciu w nowo dodany obrazek prawym przyciskiem myszy i wybraniu Properties, poniżej ukaże się okno, w którym najistotniejszą informacją na razie jest AssetName. W naszym przykładzie standardowo będzie to „Koala”. Jest to o tyle istotne, że pod tą nazwą obiekt będzie ładowany do XNA. Aby to zrobić, należy w pliku Game1.cs w metodzie LoadContent, zaraz po stworzeniu obiektu SpriteBatch wykorzystywanego w metodzie Draw do rysowania, należy dodać następującą linię kodu:

firstSprite = Content.Load<Texture2D>("Koala");

Załadowany zostanie asset Koal jako tekstura2D. Oczywiście należy jeszcze dla zmiennej firstSprite stworzyć publiczną zmienną klasową, co można najłatwiej zrealizować, klikając na firstSprite prawym klawiszem myszy i z menu Generate wybrać Property. Po jej wygenerowaniu przez Visual Studio powinna pojawić się na dole projektu. Dla uporządkowania projektu zaleca się zaleca się przenosić zmienne klasowe  w jedno miejsce, najlepiej na górę.

Teraz wystarczy w metodzie Draw wykorzystać SpriteBatch, który służy do rysowania sprite’ów, wywołując te trzy linie kodu, które należy umieścić zaraz po GraphicsDevice.Clear(Color.CornflowerBlue):

spriteBatch.Begin();

spriteBatch.Draw(firstSprite, newVector2(0, 0), null, Color.White, 0, newVector2(0, 0), 1f, SpriteEffects.None, 0);

spriteBatch.End();

Kolejne parametry metody Draw klasy SpriteBatch można znaleźć tutaj.

Po uruchomieniu projektu klawiszem F5, powinien ukazać się następujący efekt:


Rys. 6. Koala obserwator.

Aplikacja powinna reagować na akcje użytkownika, dlatego też opisany zostanie teraz najprostszy sposób na poruszanie załadowanym sprite’m. Do dyspozycji programisty, z przestrzeni nazw Microsoft.Xna.Framework.Input, są trzy główne klasy, tj.:

  1. Keyboard
  2. Mouse
  3. GamePad

W tym przykładzie wykorzystana zostanie komunikacja z aplikacją poprzez klawiaturę. Strzałkami na klawiaturze będzie można przesuwać sprite’a. Potrzebny będzie vector2D, dostępny w obrębie klasy Game1.cs, zatem na górze klasy przy istniejących już właściwościach należy stworzyć taki vector.

public Vector2 transition = new Vector2(0,0);

Przypisywanie wartości początkowej jest dobrym nawykiem, dzięki czemu nie trzeba już tego robić w obrębie metod (tylko modyfikować istniejącą już właściwość). Oczywiście, jeśli logika aplikacji na to pozwala (często wartość wektora początkowego jest wyliczana). Teraz w tym samym pliku, w metodzięDraw(), należy zmodyfikować wywołanie spriteBatch.Draw(), zmieniając drugi parametr na stworzony przed momentem transition. W nim będzie przechowywane przesunięcie obrazka.

spriteBatch.Draw(firstSprite, transition, null, Color.White, 0, newVector2(0, 0), 1f, SpriteEffects.None, 0);

Teraz najważniejsza część, czyli przechwytywanie akcji użytkownika. Wszelkie operacje tego typu, jak również obliczenia, które muszą być wykonywane na bieżąco (np. fizyka, detekcja kolizji itp.), należy wykonywać w metodzie Update(). O ile to możliwe, należy minimalizować ilość obliczeń i te, które muszą być wykonywane jednorazowo, przenosić do metody Initialize().

Przed base.Update(gameTime) należy dodać:

KeyboardState keyState = Keyboard.GetState();

if(keyState.IsKeyDown(Keys.Up))

         transition = new Vector2(transition.X, transition.Y - 5);

if(keyState.IsKeyDown(Keys.Down))

         transition = new Vector2(transition.X, transition.Y + 5);

if(keyState.IsKeyDown(Keys.Left))

         transition = new Vector2(transition.X - 5, transition.Y);

if(keyState.IsKeyDown(Keys.Right))

         transition = new Vector2(transition.X + 5, transition.Y);

Metoda GetState() zwraca aktualny stan klawiatury. Zwracany obiekt KeyboardState zawiera informacje o aktualnie wciśniętymi i zwolnionych klawiszach klawiatury. Metoda KeyboardState.IsKeyDown przyjmoje konkretny klawisz (z enumeratora Keys) jako parametr. Zwraca logiczną prawdę, jeśli klawisz jest aktualnie przyciśnięty.  Na podstawie tego warunku wartość zmiennej transition jest odejmowana lub dodawana o odpowiedni wektor przesunięcia po osi X lub Y. Dzięki temu, po uruchomieniu aplikacji, można przesuwać obrazek w obszarze okna aplikacji za pomocą kursorów na klawiaturze.

W przypadku Windows Phone do obsługi gestów korzysta się z obiektu TouchPanel  w przestrzeni nazw Microsoft.Xna.Framework.Input.Touch.

Zarówno SpriteBatch, jak i klasy z przestrzeni nazw Input mają znacznie więcej do zaoferowania niż w wyżej przedstawionym przykładzie, zatem przed przystąpieniem do kolejnego rozdziału, zachęcam do samodzielnego eksperymentowania z nimi.