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

02.06.2011 - Marcin Milewski
TrudnośćTrudność

Refaktoring: polecenia SetField oraz AddEntity

Zaprezentujemy teraz kod klas dla nowych poleceń. Na pierwszy ogień pójdzie SetFieldCommand. Aby zmienić któreś z pól planszy, ta klasa potrzebuje znać jego współrzędne oraz rodzaj pola, które należy pod nimi umieścić. Dodatkowo powinna zapamiętać, jakie pole znajdowało się tam poprzednio. Dzięki temu będzie możliwe wycofanie tego polecenia. Przyjrzyjmy się deklaracji klasy SetFieldCommand:

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
// Plik: editor/EditorCommand.h
class SetFieldCommand;
typedef boost::shared_ptr<SetFieldCommand> SetFieldCommandPtr;
 
class SetFieldCommand : public EditorCommand {
public:
    explicit SetFieldCommand(const Position& pos, FT::FieldType field)
      : m_is_ready(false),
        m_pos(pos),
        m_field(field) {
    }
 
    explicit SetFieldCommand(size_t x, size_t y, FT::FieldType field)
      : m_is_ready(false),
        m_pos(Position(x, y)),
        m_field(field) {
    }
 
    virtual void Execute(Editor* editor);
    virtual void Undo(Editor* editor);
    virtual bool IsReady() const;
 
private:
    bool m_is_ready;         // Czy polecenie jest gotowe do wykonania
    Position m_pos;          //
    FT::FieldType m_field, m_saved_field;
};
  

Jak już zostało wspomniane, implementacja tego polecenia polega na prostej manipulacji wartością pola na wskazanych współrzędnych:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Plik: editor/SetFieldCommand.cpp
#include "Editor.h"
#include "EditorCommand.h"
 
void SetFieldCommand::Execute(Editor* editor) {
    m_saved_field = editor->GetFieldAt(m_pos);
    editor->SetFieldAt(m_pos, m_field);
}
 
void SetFieldCommand::Undo(Editor* editor) {
    editor->SetFieldAt(m_pos, m_saved_field);
}
 
bool SetFieldCommand::IsReady() const {
    if (m_pos.X() < 0 || m_pos.Y() < 0) return false;
    if (m_field == FT::COUNT) return false;
    return true;
}
  

Deklaracja klasy opisującej drugie polecenie również jest bardzo prosta. AddEntityCommand otrzymuje w konstruktorze informacje o jednostce którą należy dodać do edytowanego poziomu. Przekazane dane zostaną wykorzystane do utworzenia instancji jednostki, która także zostanie zapamiętana w poleceniu. Dzięki temu możliwe będzie wykonanie operacji Undo w celu usunięcia z poziomu zarówno opisu jak i samej jednostki.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Plik: editor/EditorCommand.h
class AddEntityCommand;
typedef boost::shared_ptr<AddEntityCommand> AddEntityCommandPtr;
 
class AddEntityCommand : public EditorCommand {
public:
    explicit AddEntityCommand(const LevelEntityData& entity_data)
      : m_entity_data(entity_data) {
    }
 
    virtual void Execute(Editor* editor);
    virtual void Undo(Editor* editor);
    virtual bool IsReady() const;
 
private:
    LevelEntityData m_entity_data;
    EntityPtr m_entity;
};
  

Metoda AddEntityCommand::Execute deleguje utworzenie jednostki do fabryki. Następnie dodaje do tworzonego poziomu zarówno opis jak i samą jednostkę.

1
2
3
4
5
6
7
8
9
10
11
12
// Plik: editor/AddEntityCommand.cpp
#include "Editor.h"
#include "EditorCommand.h"
#include "../EntityFactory.h"
#include <algorithm>
 
void AddEntityCommand::Execute(Editor* editor) {
    m_entity = EntityFactory().CreateEntity(m_entity_data);
    editor->m_entities_to_create.push_back(m_entity_data);
    editor->m_entities.push_back(m_entity);
}
  

W funkcji Undo musimy wykonać operacje odwrotne do tych z Execute. Dlatego usuwamy opis jednostki z listy jednostek do utworzenia a samą jednostkę (przy użyciu idiomu erase-remove) z kontenera jednostek. Wiemy, że każda jednostka występuje jako osobny obiekt, więc poniższe usunięcia zadziałają poprawnie. Aby usuwanie z kontenera m_entities_to_create było możliwe, musimy jeszcze dopisać funkcję stwierdzającą równość dwóch obiektów typu LevelEntityData.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Plik: editor/AddEntityCommand.cpp
void AddEntityCommand::Undo(Editor* editor) {
    editor->m_entities_to_create.remove(m_entity_data);
    editor->m_entities.erase(std::remove(editor->m_entities.begin(),
                                         editor->m_entities.end(),
                                         m_entity),
                             editor->m_entities.end());
}
 
 
// Plik: Level.h, struktura LevelEntityData
    bool operator== (const LevelEntityData& other) const {
       return name == other.name
           && abs(x-other.x) < 0.001
           && abs(y-other.y) < 0.001;
    }
  

Polecenie typu AddEntityCommand jest zawsze gotowe do wykonania. Ewentualnie moglibyśmy sprawdzić poprawność przekazanego opisu jednostki.

1
2
3
4
5
// Plik: editor/AddEntityCommand.cpp
bool AddEntityCommand::IsReady() const {
    return true;
}
  

Podczas wykonywania polecenia, czyli wywołania wirtualnej metody Execute z EditorCommand, przekazujemy do zadania wskaźnik do klasy Editor. Aby konkretne polecenie mogło modyfikować tę klasę, powinno mieć dostęp do metod typu ClearFieldAt, SetField, itp. Ponieważ są one prywatne, to najprostszym sposobem na ich udostępnienie będzie nawiązanie przyjaźni między klasami. Robimy to dopisując gdzieś w deklaracji klasy Brush następujące wiersze:

1
2
3
4
5
// Plik editor/Brush.h
    friend class SetFieldCommand;
    friend class AddEntityCommand;
    friend class PlatformEditorCommand;
  

Przy okazji modyfikacji pliku Brush.h, dopiszemy dodatkowe warianty wspomnianych funkcji, tak, aby zamiast dwóch parametrów (czyli x oraz y) można było przekazać jeden typu Position. Deklaracje podajemy w pliku editor/Editor.h:

1
2
3
4
5
6
7
8
9
10
// Plik: editor/Editor.h, klasa Editor
    void ClearFieldAt(double x, double y);
    void ClearFieldAt(const Position& pos);
 
    void SetFieldAt(double x, double y, FT::FieldType ft);
    void SetFieldAt(const Position& pos, FT::FieldType ft);
 
    FT::FieldType GetFieldAt(double x, double y) const;
    FT::FieldType GetFieldAt(const Position& pos) const;
  

Definicje powyższych deklaracji podajemy w pliku editor/Editor.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Plik: editor/Editor.cpp
void Editor::ClearFieldAt(double x, double y) {
   SetFieldAt(x, y, FT::None);
}
void Editor::ClearFieldAt(const Position& pos) {
   ClearFieldAt(pos.X(), pos.Y());
}
 
void Editor::SetFieldAt(double x, double y, FT::FieldType ft) {
   m_level->EnsureWidth(static_cast<size_t>(x+1));
   m_level->SetField(static_cast<size_t>(x), static_cast<size_t>(TopDown(y)), ft);
}
 
void Editor::SetFieldAt(const Position& pos, FT::FieldType ft) {
   SetFieldAt(pos.X(), pos.Y(), ft);
}
 
FT::FieldType Editor::GetFieldAt(double x, double y) const {
   return m_level->Field(static_cast<size_t>(x), static_cast<size_t>(TopDown(y)));
}
FT::FieldType Editor::GetFieldAt(const Position& pos) const {
   return GetFieldAt(pos.X(), pos.Y());
}
  
0
Twoja ocena: Brak

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com