Gra 2D, część 7: Diabeł tkwi w szczegółach

05.02.2010 - Marcin Milewski, Łukasz Milewski
Trudność

W tym artykule zajmiemy się doszlifowaniem naszej gry, gdyż, jak wiadomo, diabeł tkwi w szczegółach. Po wprowadzonych zmianach gra będzie lepiej się prezentować a także sprawniej działać.

Poprzedni artykuł - Wykrywanie kolizji i obsługa jednostek Następny artykuł - Dobrze mieć wybór... poziomu

Zmiany, które wystąpią w tym artykule będą dotyczyły tego kodu źródłowego, który jest efektem artykułu o wykrywaniu kolizji.

Pociski nie mogą żyć wiecznie

Pierwszą rzeczą, która doskwiera już po kilku pierwszych minutach gry, jest fakt występowania dużej ilości pocisków na ekranie. Z jednej strony jest to ułatwienie dla gracza - im więcej pocisków, tym większa szansa na ofiary wśród przeciwników. Jednak czy w ten sposób gra nie stanie się łatwa i nudna? I czy nie zabijemy w ten sposób jednostek, na których nam zależy? Kolejna sprawa to przejrzystość gry i "porządek" na ekranie. Dużo przyjemniej będzie się grać, jeśli na monitorze zobaczymy coś więcej niż masa pocisków. Taką sytuację przedstawia poniższy zrzut ekranu.

Jest kilka sposobów na uporanie się z tym problemem. Po pierwsze, możemy zezwolić graczowi na wystrzelenie tylko pewnej ilości pocisków. Pytanie brzmi: co się stanie po zużyciu ich wszystkich? Nie będzie czym strzelać. Druga możliwość to zliczanie odbić pocisku od pól na mapie, a po przekroczeniu pewnej wartości usunięcie go z gry. Jest to ciekawy pomysł, jednak bardziej wymagający niż trzecia koncepcja: usuwanie pocisku z gry po pewnym czasie. Tego chyba nie trzeba tłumaczyć, więc przejdźmy o jej realizacji. Wszystkie zmiany będą dotyczyły klasy PlayerBulletEntity. Na samym początku, dodajemy prywatne pole m_time_to_live odliczające czas do samozniszczenia:

1
2
3
4
private:
    double m_time_to_live; // czas, który pozostał do samozniszczenia
};
typedef boost::shared_ptr<PlayerBulletEntity> PlayerBulletEntityPtr;

Następnie dodajemy domyślną wartość, która będzie nadawana w momencie tworzenia nowego pocisku, czyli w konstruktorze:

1
2
3
4
5
6
7
8
9
10
11
12
13
class PlayerBulletEntity : public Entity {
    enum {
        DefaultXVelocity = 6, DefaultYVelocity = -2,
        DefaultXAcceleration = 0, DefaultYAcceleration = 0,
        DefaultTimeToLive = 2   // czas życia = 2s
    };
 
public:
    PlayerBulletEntity(double x, double y) :
        Entity(x, y, DefaultXVelocity, DefaultYVelocity,
               DefaultXAcceleration, DefaultYAcceleration),
        m_time_to_live(DefaultTimeToLive) {
    }

Zdefiniowaną wartość należy aktualizować razem z całym obiektem. Akcja ta jest wykonywana przez wywołanie na obiekcie metody Update, do której jako argument przekazujemy czas, jaki upłynął od ostatniego wywołania. Oto ciało tej metody po zmianach:

1
2
3
4
5
6
7
8
9
10
11
12
    void Update(double dt, LevelPtr level) {
        // usuń obiekt jeżeli żyje zbyt długo
        m_time_to_live -= dt;
        if (m_time_to_live < 0) {
            SetIsDead(true);
            return;
        }
 
        // sprawdź kolizje i ustaw
        CheckCollisionsWithLevel(dt, level);
        SetPosition(GetNextXPosition(dt), GetNextYPosition(dt));
    }

Poniższy zrzut przedstawia sytuację po wykonaniu tych kroków. Rozgrywka jest znacznie czytelniejsza.

5
Twoja ocena: Brak Ocena: 5 (2 ocen)

Copyright © 2008-2010 Wrocławski Portal Informatyczny

design: rafalpolito.com