Gra 2D, część 13: Edytor poziomów cz. 3

02.06.2011 - Marcin Milewski
TrudnośćTrudność

Nadszedł czas, aby dodać pędzel o specjalnej mocy. Nasz multipędzel będzie mógł narysować nawet kilkadziesiąt kafelków jednocześnie! Dzięki temu, podłoże w naszych poziomach będzie powstawać w zawrotnym tempie.

Rozwiniemy nasz edytor o możliwość wypełniania wielu pól planszy jednym ruchem myszy. Podczas tworzenia poziomu, gracz będzie mógł wybrać sposób w jaki ma zostać wypełniony zaznaczony obszar. W momencie definiowania zaznaczenia wyświetlany będzie podgląd wyniku działania programu. A co jeśli użytkownik się pomyli? Jak wycofać zmianę kilkudziesięciu pól? Zadbamy też o ten aspekt. Dzięki prostemu refaktoringowi nasz edytor będzie mógł cofnąć każdą wprowadzoną zmianę. Zabierzmy się więc do pracy.

Poprzedni artykuł - Edytor poziomów cz. 2

Kod źródłowy, który będziemy modyfikować w tym artykule można znaleźć tutaj.

Implementacja pędzla rysującego wiele kafelków

Dotychczas, w edytorze mogliśmy wybrać typ pędzla ze względu na rodzaj malowanego obiektu. Dokładniej, mieliśmy trzy możliwości: malowanie podłoża, malowanie jednostek oraz malowanie specjalne. Teraz chcemy mieć możliwość rysowania nie jednego, lecz wielu elementów jednym pociągnięciem pędzla. Wykorzystamy do tego celu dziedziczenie.

Przekształcenie klasy Brush w klasę bazową

Pisząc klasę Brush nie braliśmy pod uwagę, że może ona być w przyszłości klasą bazową. Dlatego teraz będziemy musieli wykonać kilka (drobnych) zmian, żeby ją do tego przystosować.

Stara (po lewej) oraz nowa (po prawej) hierarchia dziedziczenia dla pędzla.

Pierwszą modyfikacją jest oczywiście dodanie wirtualnego destruktora. Do tej pory nie było go wcale*, więc jako jego definicję podajemy pusty blok. Poniżej znajduje się kawałek kodu, który należy dodać do deklaracji klasy Brush. (*)Tak naprawdę to był, gdyż jeżeli nie zdefiniujemy go w sposób jawny, to kompilator wygeneruje go za nas. Podobnie dzieje się w przypadku konstruktora bezargumentowego czy kopiującego.

1
2
3
// Plik: editor/Brush.h
    virtual ~Brush() {}
  

Druga modyfikacja to dodanie możliwości zmiany implementacji rysowania pędzla. Obecnie pędzel jedynie przechowuje sprite'a, którym należy go narysować. Teraz chcemy zorganizować to nieco inaczej. Dodajemy metodę Draw do klasy Brush, aby pędzel mógł narysować się sam. Dzięki temu, z punktu widzenia klasy korzystającej z pędzla, sposób rysowania będzie zawsze taki sam – czyli będzie sprowadzał się do wywołania metody Draw.

Polecenie rysujące pędzel przenosimy z metody Editor::DrawBrushAndGui do Brush::DrawIcon, które zostanie wywołane przez metodę Brush::Draw. Tym samym, wszystko działa jak dawniej, a my możemy przejść do kolejnej modyfikacji czyli do dodania szkicu.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Plik: editor/Brush.h
public:
    virtual void Draw(Position scr_position, Size scr_size) const {
        DrawIcon(scr_position, scr_size);
    }
 
protected:
    // Rysuje mały kafelek obok kursora.
    void DrawIcon(Position scr_position, Size scr_size) const {
        GetSprite()->DrawCurrentFrame(scr_position, scr_size);
    }
 
// Plik Editor.cpp, metoda Editor::DrawBrushAndGui
// Zamiast:
   Engine::Get().GetRenderer()->DrawQuad(position, position+size, 1,1,1,.4);
   GetBrush()->GetSprite()->DrawCurrentFrame(position, size);
// Teraz jest:
   Engine::Get().GetRenderer()->DrawQuad(position, position+size, 1,1,1,.4);
   GetBrush()->Draw(position, size);
  

Trzecia modyfikacja to dodanie funkcji rysującej szkic, czyli pogląd działania pędzla. Do tej pory ikona rysowana obok pędzla dobrze obrazowała efekt jego działania (czyli dodanie pojedynczego elementu do planszy). Nowy pędzel będzie mógł postawić wiele elementów jednocześnie. Ponieważ jego działanie może być dość skomplikowane, to przyda się możliwość dostarczenia użytkownikowi podglądu. Do klasy Brush dodajemy wirtualną metodę DrawSketch. Każda klasa pochodna będzie mogła dostarczyć właściwą dla niej implementację. W Brush jej ciało zostawiamy puste.
Taki sposób implementacji (tj. gdy klasa pochodna może zmienić część algorytmu) nosi nazwę wzorca metody szablonowej (ang. template method pattern).

1
2
3
4
5
6
7
8
9
10
11
12
13
// Plik: editor/Brush.h
protected:
    // Metoda do przesłonięcia w klasach pochodnych.
    // Rysowanie szkicu/podglądu.
    virtual void DrawSketch(Position scr_position, Size scr_size) const {
    }
 
// Wywołanie dopisujemy do metody Draw:
    virtual void Draw(Position scr_position, Size scr_size) const {
        DrawSketch(scr_position, scr_size);
        DrawIcon(scr_position, scr_size);
    }
  

Ostatnią zmianą, którą wprowadzimy jest dodanie informacji o tym, że konkretna instancja pędzla jest tak naprawdę multipędzlem. Klasa korzystająca z pędzla powinna sprawdzić czy jest on specjalnego typu i w razie potrzeby wykonać rzutowanie na klasę multipędzla.
Dopisujemy stałą Multi do wyliczenia Brush::ST::SpecialType.

1
2
3
4
5
6
7
// Plik: editor/Brush.h
class Brush {
public:
    struct ST {
        enum SpecialType { UNKNOWN, Player, Eraser, Multi };
    };
  
Typy pędzli dla klasy Brush. Na obrazku widać miejsce, w którym znajduje się nowy typ.

Na początku pliku trzeba jeszcze dołączyć potrzebne pliki nagłówkowe. Początek pliku Brush.h wygląda teraz następująco:

1
2
3
4
5
6
7
8
9
// Plik: editor/Brush.h
#ifndef __BRUSH_H_INCLUDED__
#define __BRUSH_H_INCLUDED__
 
#include "../StdAfx.h"
#include "../Sprite.h"
 
class Brush;
  
0
Twoja ocena: Brak

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com