Zaawansowane szukanie | Nowości
Newsy | Artykuły | Programowanie: Pascal, Object/Delphi Pascal, C++, PHP, Javascript | Poradniki | Recenzje | FAQ | Mapa

Artykuł: C++ - Gra strzelanka

Gry w C++


C++ pozwala na tworzenie nie tylko aplikacji, ale także gier. Dzięki niemu można stworzyć aplikację, która będzie mogła zarządzać modelami 3D np. w OpenGL. W tym artykule opiszę jednak jak stworzyć grę 2D w oknie, korzystając z komponentów C++ Buildera. Ten artykuł nie ma na celu uczenia robienia gier, lecz pokażę w nim jak korzystać z wielu ważnych funkcji w języku C++. Gra będzie polegała na tym, że jeden lub więcej obrazków będą skakać po ekranie, a zadaniem gracza będzie zestrzelenie obrazków. Gra będzie liczyć ilość ruchów obrazków oraz ilość strzałów. Po urozmaiceniu będzie można wybrać prędkość ruchów, a także załadować własny obrazek lub więcej obrazków.

Tworzenie menu


Tworzymy nowy projekt. Na formę nakładamy dwa panele (komponent Panel) i oby dwóch usuwamy wszystko z wartości Caption. Panele ustawiamy jeden pod drugim, dla górnego właściwość Align w Inspektorze Obiektów ustawiamy na alBottom, a dla drugiego alClient co da nam efekt taki, że jeden panel będzie przylegał do dołu formy, a drugi będzie wypełniał resztę wolnego miejsca. Na formę nanosimy komponent MainMenu, dzięki któremu można tworzyć menu. Komponent ten wyróżnia się tym, że na formie zobaczymy jedynie jego ikonkę. Ikona ta oczywiście nie będzie widoczna po uruchomieniu programu. Efekt powinien być taki: Forma naszej gry
Następnie klikamy dwa razy na ikonkę menu i wyświetli nam się okno, w którym możemy tworzyć pozycje menu. Każde zaznaczone pole w nim możemy uaktywnić podpisując je dzięki zmienie pola Caption. Ja stworzyłem trzy pozycje: "Gra", "Poziom", "Informacje". W menu "Gra" utworzyłem pozycje: "Reset", "Start", "Stop" i "Zamknij", w "Poziom": "Łatwy", "Średni", "Trudny", "Niemożliwy", a w "Informacje": "O autorze". Oczywiście do każdej pozycji będziemy musieli pisać kod, sama nazwa w niczym nie pomoże. Do każdej pozycji możemy przyporządkować skrót klawiszowy, przyporządkowując go atrybutowi "ShortCut". Często spotykamy się ze zjawiskiem, że programy mają podkreślone dane litery w menu, aby można było użyć skrótu Alt + Litera dla ich wywołania. W komponencie MainMenu dodajemy w Caption przed daną literą znak "&" (np. G&ra spowoduje wyświetlenie "Gra). Aby wstawić tradycyjny separator należy w pole Caption wstawić myślnik. Programowaniem menu zajmiemy się na końcu.
Zbudowane menu

Wizualna część gry


Kolejne co trzeba zrobić to wstawić komponenty: Timer z zakładki "System" oraz OpenPictureDialog z zakładki Dialogs. Timer nazwijmy "czasomierz", a OpenPictureDialog "open". Jak widać te komponenty widnieją także w postaci ikon. Potrzeba jeszcze czegoś, co będzie liczyć wyświetlenia i strzały. W tym celu wstawiamy aż cztery etykiety, dwie podpisane "Ilość ruchów" i "Ilość strzałów". Drugie dwie etykiety ustawiamy obok nich i zmieniamy jedynie ich nazwy na "ruchy" i "strzaly". Potrzebne będą jeszcze przyciski i lista rozwijalna, co ułatwi nawigację. Przyciski nazwijmy "pGraj" i "pStop", a ich Caption zmienimy na "Graj" i "Zatrzymaj" lub jak komu się podoba. Listę rozwijalną nazwijmy "poziom" i dodajmy pozycje takie jak w menu ("Łatwy", "Średni", "Trudny" i "Niemożliwy"). Potrzebny jest jeszcze obrazek, który będzie służył nam jako postać do zestrzelenia. Wybieramy z zakładni "Addintional" komponent Image, nakładamy go na formę i zmienamy jego nazwę na "Postac". Teraz przydałaby się jakaś postać, w którą będziemy strzelać. Proponuję uruchomić program graficzny i zrobić coś oryginalnego. Zezwalam na skorzystanie z mojego Chucka Norrisa w mocherowym berecie :)
Chuck
Teraz trzeba tylko ustawić obrazek jako domyślną postać. Aby to zrobić wybieramy atrybut Picture, po czym klikamy na przycisk "Load", wyszukujemy nasz obrazek i zatwierdzamy go. Końcowy efekt u mnie wygląda tak:
Efekt końcowy


Czas zacząć


Pierwsze co trzeba zrobić to zadeklarować trzy zmienne globalne (jak to zrobić przeczytaj tu) - "predkosc", "strzalow" i "wyswietlen":
  int predkosc;
  int strzalow, wyswietlen;
Zacznijmy teraz programować Timer. Utwórzmy akcję OnTimer, a w niej:
 Postac->Top = random(panelGora->Height - Postac->Height);
 Postac->Left = random(panelGora->Width - Postac->Width);
 wyswietlen++;
 ruchy->Caption = IntToStr(wyswietlen);
Dzięki temu nasz "czasomierz", gdy go uaktywnimy, będzie poruszał obrazkiem, a dokładniej - obrazek będzie skakał po górnym panelu "panelGora" (oczywiście pozycja będzie losowa dzięki funkcji random). Za każdym razem liczba wyświetleń będzie się zwiększać - aby to zrobić dodajemy dwa plusy na końcu zmiennej (np. "zmienna++;"). Jest to właśnie instrukcja charakterystyczna dla języka C++. Na koniec Timer zmieni tekst etykiety "ruchy" na ilość ruchów. Następne co należy zrobić to zmienić wartość Visible dla Image ("Postac") z "True" na "False", dzięki czemu nasz obrazek nie będzie widoczny po uruchomieniu programu. Czas zaprogramować przycisk "pGraj":
void __fastcall Tforma::pGrajClick(TObject *Sender)
{
  strzalow = 0;
  wyswietlen = 0;
  strzaly->Caption = IntToStr(strzalow);
  ruchy->Caption = IntToStr(wyswietlen);
  czasomierz->Interval = predkosc;
  czasomierz->Enabled = true;
  Postac->Visible = true;
}
Po naciśnięciu na "Graj" zmienne globalne "strzalow" i "wyswietlen" ustawią się na 0, dzięki czemu nastąpi reset licznika. Potem etykieta "strzaly" zmieni Caption na ilość strzałów, a etykieta "ruchy" na ilość ruchów. Na koniec "czasomierz" zostaje włączony i jego cykl ustawiony jest na czas, który przechowuje zmienna "predkosc", a obrazek "Postac" zmienia wartość Visible na true, dzięki czemu zostanie widoczny i zacznie się poruszać. Kod przycisku "pStop" spowoduje jedynie zatrzymanie Timera i zniknięcie obrazka "Postac":
void __fastcall Tforma::pStopClick(TObject *Sender)
{
 czasomierz->Enabled = false;
 Postac->Visible = false;
}
Teraz najważniejszą instrukcją będzie akcja "OnShow" dla formy. W zakładce zdarzeń odszukujemy pozycji "OnShow" i tworzym procedurę, a w niej:
void __fastcall Tforma::FormShow(TObject *Sender)
{
 predkosc = 1000;
 czasomierz->Enabled = false;
 ruchy->Caption = "0";
 strzaly->Caption = "0";
}
Procedura ta ustawi domyślną prędkość na 1000ms (sekunda), wyłączy "czasomierz" oraz ustawi wartości Caption zmiennych "ruchy" i "strzaly" na 0.
Jak narazie jeszcze nic się nie dzieje po kliknięciu na obrazek. Tworzymy procedurę "OnClick" dla "Postac":
void __fastcall Tforma::PostacClick(TObject *Sender)
{
 strzalow++;
 strzaly->Caption = IntToStr(strzalow);
}
Procedura ta podniesie wartość zmiennej "strzalow" oraz zmieni ilość strzałów na etykiecie "strzaly". Pełni to rolę naszego licznika strzałów. Czas zaprogramować menu. Tworzymy akcje "OnClick" dla przycisku "Reset", "Start", "Stop" oraz "Zamknij":
"Reset":
void __fastcall Tforma::Reset1Click(TObject *Sender)
{
 strzalow = 0;
 wyswietlen = 0;
}
Tego kodu chyba nie muszę już tłumaczyć - zmiana wartości liczników na 0.
Start:
void __fastcall Tforma::Start1Click(TObject *Sender)
{
 pGraj->Click();
}
Skoro przycisk "Graj" odpowiada za to samo co "Start" w menu, to nie musimy pisać procedury ponownie. Wystarczy wywołać akcję kliknięcia przycisku "pGraj".
Stop:
void __fastcall Tforma::Stop1Click(TObject *Sender)
{
 pStop->Click();
}
Tutaj występuje podobna sytuacja jak wyżej.
Zamknij:
void __fastcall Tforma::Zamknij1Click(TObject *Sender)
{
 Close();
}
Zostało nam jeszcze zaprogramować wybór poziomu gry (będzie to prędkość ruchów obrazka). Dodajmy akcje "OnClick" dla wszystkich pozycji w menu "Poziom":
void __fastcall Tforma::atwy1Click(TObject *Sender)
{
 predkosc = 1000;
}
//---------------------------------------------------------------------------

void __fastcall Tforma::redni1Click(TObject *Sender)
{
 predkosc = 600;
}
//---------------------------------------------------------------------------

void __fastcall Tforma::Trudny1Click(TObject *Sender)
{
 predkosc = 300;
}
//---------------------------------------------------------------------------

void __fastcall Tforma::Niemoliwy1Click(TObject *Sender)
{
 predkosc = 50;
}
Dla poziomu łatwego prędkość ruchu obrazka będzie wynosiła 1 s, dla średniego 600 ms, dla trudnego 300 ms i dla niemożliwego 50 ms. Oczywiście każdy może sobie te wartości dostosować jak chce. Niemożliwy poziom będzie niezłym wyzwaniem :)
Ostatnią rzeczą, która przyda się podczas grania jest zaprogramowanie listy rozwijalnej z poziomami. Ustawiamy akcję "OnChange" i piszemy procedurę:
void __fastcall Tforma::listaChange(TObject *Sender)
{
 if (lista->Text == "Łatwy") {
      atwy1->Click();
 }
 else if (lista->Text == "Średni") {
      redni1->Click();
 }
 else if (lista->Text == "Trudny") {
      Trudny1->Click();
 }
 else if (lista->Text == "Niemożliwy") {
      Niemoliwy1->Click();
 }
 else {
      ShowMessage("Wybierz pozycję z listy!");
 }
}
C++ Builder sam nazywa pozycje w menu według pola Caption, jednak usuwa polskie znaki, dlatego efekt jest taki.
Teraz możemy cieszyć się już naszą grą, która działa bez zarzutu.

Ładowanie obrazka


Dodajmy teraz możliwość ładowania obrazka przez użytkownika. Posłuży nam do tego "OpenPictureDialog", który już wcześniej nazwaliśmy "open". Dodajmy pozycję do menu "Gra" o nazwie "Załaduj obrazek". Dodajemy akcję "OnClick" i piszemy procedurę:
void __fastcall Tforma::Zaadujobrazek1Click(TObject *Sender)
{
 if (open->Execute()) {
    Postac->Picture->LoadFromFile(open->FileName);
 }
}
Teraz możemy przetestować grę - wczytywanie postaci działa. Co jednak, jeśli wybierzemy format graficzny, którego komponent Image nie akceptuje? Oczywiście wyskoczy błąd. Możemy procedurę udoskonalić ostrzegając użytkownika:
void __fastcall Tforma::Zaadujobrazek1Click(TObject *Sender)
{
 if (open->Execute()) {
    try {
    	Postac->Picture->LoadFromFile(open->FileName);
    }
    catch(Exception &e) {
    	ShowMessage("Nieprawidłowy format graficzny.");
    }
  }
}
Teraz dzięki try .. catch, gdy wybierzemy nieprawidłowy format graficzny, użytkownik zobaczy komunikat. Komunikat możemy udoskonalić o podanie szczegółów błędu dodając do funkcji ShowMessage " + e.Message". Razem będzie to wyglądało tak:
ShowMessage("Nieprawidłowy format graficzny." + e.Message);
Wczytywanie obrazków postaci działa już prawidłowo. Została jeszcze jedna rzecz do zrobienia.

Informacje o autorze - druga forma


W menu pozostała pozycja "Informacje". Moglibyśmy napisać krótki komentarz w komunikacie ShowMessage, jednak można zrobić to lepiej - dodać drugą formę, na której będziemy mogli umieścić dodatkowe informacje jak i np. zdjęcie czy adres strony www.
W tym celu wybieramy File -> New -> Form. Pojawia nam się nowa forma a wraz z nią nowy plik z kodem właściwym. Zmieniamy nazwę formy na "info", ustawiamy jej właściwości i zmieniamy Caption na np. "Strzelanka - Informacje". Umieszczamy tam różne komponenty według uznania. Moja forma informacyjna wygląda tak:

Moja forma informacyjna
Oprócz etykiety, przycisku i obrazka użyłem komponentu "GroupBox, który pozwala na grupowanie pewnej ilości obiektów i podpisanie ich.
Teraz trzeba dodać formę do naszego projektu. Odszukujemy:
#include "gra.h"
i odwołujemy się do nowego pliku - Unit1.h - "includujemy" go:
#include "gra.h"
#include "Unit1.h"
Potem nad lub pod:
Tforma *forma;
wstawiamy:
Tinfo *info;
Dzięki temu nasz projekt plik widzi już Unit1.h oraz formę "info". Aby Unit1.h widział formę główną, musimy zrobić to samo w tamtym pliku, jednak do tej aplikacji nam się to nie przyda.
Teraz dodajemy akcję "OnClick" dla pozycji "O autorze" w menu "Informacje":
info->ShowModal();
Odwołujemy się tutaj do formy "info", a funkcja ShowModal(); powoduje pokazanie się formy i zablokowanie jej (tzn. nie będziemy mogli wrócić na formę główną, dopóki nie zamknięta zostanie forma "info"). Aby pokazać formę i nie blokować jej, możemy użyć:
info->Show();


To już wszystko, końcowy efekt jest taki:


W następnym artykule napisałem jak stworzyć aplikację nie korzystając z formy (czyli pisać aplikację razem z oknem). Będzie można się przekonać, że jest to dużo bardziej czasochłonne. Napisałem także jak wprowadzić tzw. "wygląd Windows XP", czyli Lunę, dzięki czemu nasze przyciski i inne komponenty zmienią swój wygląd.

Autor: Michał Gacki
Główny programista i założyciel Bil Software.
Informacje o autorze | Kontakt