Microsoft XNA, część 1

10.05.2010 - Marcin Oczeretko
TrudnośćTrudność

     Na koniec uporządkujmy trochę nasz kod. Nieelegankim i niepraktycznym rozwiązaniem byłoby stworzenie całej gry za pomocą jednej tylko klasy Game1. Dodajmy dwie klasy do naszego projektu - będą one reprezentowały odpowiednio gracza i piłkę. Dzięki temu kod zyska na przejrzystości i łatwiej będzie nam go rozwijać. W Solution Explorer klikamy na nasz projekt prawym przyciskiem myszy i następnie: Add $ \to $ New Item...

Dodajemy nową klasę

Wybieramy:

    Categories: XNA Game Studio 3.1 (różowy)

    Templates: Game Component (zielony)

    Name: Player.cs (niebieski)

     Zatwierdzamy te ustawienia i w efekcie do naszego projektu dołącza nowy plik Player.cs. Zawiera on klasę Player, która dziedziczy po GameComponent. W związku z tym, że nasz gracz będzie rysowany na ekranie, zmieńmy jego nadklasę na DrawableGameComponent. Obiekty dziedziczące po GameComponent (DrawableGameComponent też zwyczajnie ją rozszerza), można dodawać do kolekcji Components obiektu Game, dzięki czemu ich metody (takie jak LoadContent() lub Update()) wywoływane będą w odpowiednich ku temu momentach. Podczas wywołania LoadContent(), Update(), Draw() i UnloadContent() klasy Game przeglądana jest cała kolekcja obiektów Components i na każdym z nich wywoływane są odpowiednie metody. Możemy zatem napisać własne wersje tych metod, dzięki czemu nasze klasy będą zachowywać się w pożądany przez nas sposób.

     Wykorzystajmy tę wiedzę, aby przenieść cały kod dotyczący postaci gracza do klasy Player. Usuń wszystko, co dotyczy gracza z klasy Game1.

     Teraz dodajmy pole w klasie Game1:

1
private Player player;

     A w konstruktorze klasy Game1 dopiszmy:

1
2
player = new Player(this);
Components.Add(player);

     Abyśmy mogli skorzystać z obiektu spriteBatch również w innych klasach niż w Game1 umieścimy go w specjalnym "pojemniku" - GameServiceContainer. Dzięki temu będziemy mogli użyć go w każdym miejscu kodu, czyli w szczególności wtedy, gdy zapragniemy narysować na ekranie postać gracza. Przepisz poniższą linijkę do metody LoadContent() klasy Game1:

1
Services.AddService(typeof(SpriteBatch),spriteBatch);

     Dodajmy do tej metody jeszcze odwołanie do metody LoadContent() w nadklasie. Całość powinna wyglądać tak:

1
2
3
4
5
6
7
8
9
10
protected override void LoadContent()
{
    spriteBatch = new SpriteBatch(GraphicsDevice);
    Services.AddService(typeof(SpriteBatch),spriteBatch);
 
    background = Content.Load<Texture2D>("background");
    ball = Content.Load<Texture2D>("ball");
 
    base.LoadContent();
}

     Ostatnią modyfikacją w klasie Game1 będzie usunięcie rysowania gracza i zamiana miejscami dwóch wierszy - wywołania spriteBatch.End() i base.Draw(). Dzięki temu rysowanie w obiektach z Components wykona się zanim wywołana zostanie metoda spriteBatch.End().

1
2
3
4
5
6
7
8
9
10
11
protected override void Draw(GameTime gameTime)
{
    spriteBatch.Begin();
 
    spriteBatch.Draw(background,Vector2.Zero,Color.White);
    spriteBatch.Draw(ball,ballPos,Color.White);    
 
    base.Draw(gameTime);
 
    spriteBatch.End();
}

     W klasie Player wpisujemy praktycznie to samo, co znajdowało się wcześniej w Game1. Powinniśmy otrzymać w efekcie coś takiego:

Pokaż/ukryj kod klasy Player
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Player : Microsoft.Xna.Framework.DrawableGameComponent
{
    private Texture2D texture;
 
    private Vector2 position = new Vector2(100,100);
 
    public Player(Game game)
        : base(game)
    {
    }
 
    protected override void LoadContent()
    {
        texture = Game.Content.Load<Texture2D>("player");
    }
 
    public override void Update(GameTime gameTime)
    {
        if (Keyboard.GetState().IsKeyDown(Keys.W))
            position += new Vector2(0, -4);
 
        if (Keyboard.GetState().IsKeyDown(Keys.S))
            position += new Vector2(0, 4);
 
        if (Keyboard.GetState().IsKeyDown(Keys.A))
            position += new Vector2(-4, 0);
 
        if (Keyboard.GetState().IsKeyDown(Keys.D))
            position += new Vector2(4, 0);
 
        base.Update(gameTime);
    }
 
    public override void Draw(GameTime gameTime)
    {
        SpriteBatch spriteBatch =
            (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
            
        spriteBatch.Draw(texture, position, Color.White);
    }
 
    protected override void UnloadContent()
    {
        texture.Dispose();
    }
}

     Chyba jedynymi fragmentami wymagającymi komentarza są linijki $ 14 $, $ 36 $ i $ 37 $ oraz $ 44 $. Zatem:

     W linijce $ 14 $ odwołujemy się do Content należącego do klasy Game1. Dlatego też pojawia się tam Game.Content.Load() zamiast po prostu Content.Load() (bo to było dopuszczalne tylko wewnątrz klasy Game1).

     Wiersze $ 36 $ i $ 37 $ to pobranie obiektu spriteBatch z "pojemnika", w którym go wcześniej umieściliśmy.

     Dla większej elegancji, w linijce $ 44 $ pozbywamy się wprost zasobu (w tym wypadku obrazka), który uprzednio załadowaliśmy. Dodaj analogiczny kod dla obiektów Texture2D w klasie Game1.

     I to już wszystko, co należało uczynić, by przenieść całą logikę obsługi postaci gracza do osobnej klasy. Postępując analogicznie, stwórz klasę Ball i przenieś do niej wszystko, co odnosi się do piłki.

Pobierz kod, z którym kończymy ten artykuł (w tym klasę Ball).


    Zapraszam do lektury drugiej części, gdzie dokończymy pisanie tej prostej gry.

4
Twoja ocena: Brak Ocena: 4 (4 ocen)

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com