Programowanie w języku Delphi » Artykuły

Jak zrobić grę

  • 2009-01-08 16:19
  • 6 komentarzy
  • 2769 odsłon
  • Oceń ten tekst jako pierwszy

Jak zrobić grę ?


1. Co użyjemy


  -środowisko                 : Delphi ( praktycznie każda wersja )
  -komponenty                 : Timer
  -funkcje matematyczne : Sinus, Cosinus, potęgowanie i pierwiastkowanie drugiego stopnia, losowość
  -funkcje graficzne         : okrąg, wypełnianie, ustawianie pędzla i pióra
  -elementy kodu            : rekordy, funkcje, procedury, pętle, tablice dynamiczne
  -zdarzenia                    : zmiana stanu klawiszy

posiadanie choćby szczątkowych informacji o wyżej wymienionych rzeczach
bardzo ułatwi osiągniecie celu ( tutaj stworzenie prostej gry )

2. Jaka to będzie gra?


  -typ                           : strzelanka ( shooter )
  -widok                       : z góry ( Third Person Perspective )
  -poruszanie się           : względne - zależne od obranego kierunku
  -tryby gry                  : wieloosobowa ( hotseats )
  -urządzenie sterujące  : klawiatura

  Tak wygląda ukończona:



3. Co? Gdzie? Jak?


  Najpierw potrzebujemy nowy projekt, jeśli włączysz Delphi to od razu powinien być
  przygotowany, jeśli nie to z menu głównego wybierz "File\New\Application"
  Na początku masz dwa okna projektu:

  -Forma - w skrócie jest to okno jakie będzie miał program ( nie będziemy używać,
           możesz zminimalizować lecz nie zamykać! )

  -Kod   - okno z polem tekstowym gdzie będziemy wpisywać kod

  Przeglądnij sobie to co już masz tam wpisane, pogrubione słowa to słowa kluczowe
  Wyjaśnię kilka:

  -Unit  - deklaracja że ten plik jest modułem ( kawałek kodu wydzielony do osobnego pliku )
          tekst między Unit a średnikiem to nazwa tego modułu

  -interface - początek sekcji interface, tutaj będziemy deklarować zmienne globalne,
               procedury, funkcje i typy

  -type  - rozpoczyna deklarację typu, jak widzisz jeden typ jest już zadeklarowany, nazywa się TForm1,
          jest to typ opisujący naszą formę ( okno programu )

  -var   - rozpoczyna deklarację zmiennych, na razie mamy tylko jedną zmienną - Form1 typu TForm1,
           czyli okno programu

  -implementation - początek sekcji implementation, tutaj będziemy opisywać metody i funkcje

  -end.  - koniec kodu ( kropka oznacza że to jest koniec całości pliku, nic za end.
           nie będzie uwzględnione przy kompilacji )

4. Deklaracja typów


  Bierzemy się za robotę, na początku stworzymy typy odpowiadające postaci gracza i pocisku
 
  -Gracz  - najpierw trzeba wiedzieć co ma zawierać dany typ, więc co można powiedzieć o graczu w
            takiej grze ( patrz punkt drugi ) ?

           - ma jakąś pozycję
           - ma jakiś kierunek ruchu
           - ma ileś  życia
           - ma odstęp czasu pomiędzy strzałami

  -Pocisk - tutaj podobnie tyle że koloru nie użyjemy, a pocisk raczej nie ma życia

           - ma jakąś pozycję
           - ma jakiś kierunek ruchu

  Na pozycję ( punkt w układzie współrzędnych ) składają się dwa koordynaty: X i Y, możemy umieścić w typie
  oba w osobnych zmiennych lub użyć gotowego typu TPoint, czyli punkt

  W sekcji Interface ( patrz punkt 3 ) należy znaleźć miejsce gdzie zadeklarujemy typy, polecam gdzieś pomiędzy
  deklaracją typu formy ( TForm1, deklaracja kończy się na end; ) a deklaracją zmiennych ( var )

  Najpierw nazwa typu i czym on ma być, tutaj użyjemy rekordu

  Gracz:

type TPlayer = record


  Zmienne w tym typie:

  Pozycja
    Pos:TPoint;


  Kierunek
    Dir:integer;


  Życie
    Life:integer;

 
  Odstęp pomiędzy strzałami
    Fire:integer;


  i kończymy
  end;


  Pocisk:

type TBullet = record


  Zmienne w tym typie:

  Pozycja
    Pos:TPoint;


  Kierunek
    Dir:integer;


  dodajemy też zmienna która się przyda przy kasowaniu pocisków
    Del:boolean;


  i kończymy


  end;


5. Deklaracja zmiennych



  Żeby przechować dane o dwóch graczach zadeklarujemy jednowymiarową
  tablicę ( listę ) o dwóch komórkach ( stała długość )
  Do przechowania informacji o pociskach użyjemy jednowymiarowej tablicy
  dynamicznej ( długość będzie zmieniać się w trakcie działania programu )

  w sekcji interface, ale po deklaracji typów dopisujemy

var
  Players:array[0..1] of TPlayer;
  Bulelts:array of TBullet;


6. Ruch


  By gracze i pociski poruszały się w danym kierunku użyjemy
  trygonometrii, wyjaśnię to za pomocą rysunku:



  P1 to bieżąca pozycja, P2 to nowa, c to długość "kroku"
  ( odcinek o jaki przesuwamy ), a to odległość w pionie, b w
  poziomie obu pozycji, α to kierunek ruchu ( 0° to ruch w
  prawo, stopnie przyrastają w kierunku odwrotnym do ruchu
  wskazówek zegara )

  X2 i Y2 to koordynaty nowej pozycji, łatwo zauważyć że
  X2=X1+b
  Y2=Y1+a


  więc mając X1 i Y2 ( koordynaty bieżącej pozycji ) potrzebujemy
  jeszcze a i b do obliczenia nowej pozycji

  z podstaw trygonometrii wiemy że sinus kąta to przyprostokątna
  naprzeciw tego kąta ( a ) dzielona przez przez przeciwprostokątną ( c )
  a cosinus to przeciwprostokątna przy kącie ( b ) dzielona przez
  przeciwprostokątną ( c ), więc:
  sinα=a/c
  cosα=b/c


  mnożymy obustronnie przez c i otrzymujemy
  sinα*c=a
  cosα*c=b


  obracamy i mamy wzory na a i b
  a=sinα*c
  b=cosα*c


  jest jeszcze jeden mały szkopuł, początek układy współrzędnych ekranu
  leży w lewym górnym rogu, a oś Y jest obrócona ( gdyby nie była obrócona
  operowalibyśmy na wartościach ujemnych ), zmiany wyglądają tak:



  więc zamiast dodawać a do współrzędnej będziemy je odejmować
  X2=X1+b
  Y2=Y1-a


  wyprowadzamy wzór końcowy
  X2=X1+cosα*c
  Y2=Y1-sinα*c


  w kodzie będzie to wyglądać tak
  X2:=X1+Cos(DegToRad(α))*c;
  X2:=X1-Sin(DegToRad(α))*c;


  tylko że może się zdarzyć iż wynik będzie mieć rozwinięcie dziesiętne
  by tego uniknąć zaokrąglamy funkcją Trunc()
  X2:=X1+Trunc(Cos(DegToRad(α))*c);
  X2:=X1-Trunc(Sin(DegToRad(α))*c);


  jako że funkcje Sin i Cos przyjmują argumenty w radianach a nie stopniach musimy
  skonwertować stopnie na radiany ( funkcja DegToRad )

  funkcja DegToRad znajduje się w module Math, więc trzeba go dodać do listy uses
  ( patrz punkt #3 ), ja dodałem na końcu

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, Math;

 
  Teraz piszemy funkcję która zwróci nową pozycję gdy podamy jej bieżącą pozycję,
  kierunek i długość kroku ( c )

  w sekcji interface deklarujemy funkcję

Function Move(Pos:TPoint;Dir:integer;c:integer):TPoint;


  Jak widać funkcji podajemy pozycję w typie TPoint, kierunek i długość kroku w
  typie liczbowym, a funkcja zwróci jako wynik nową pozycję w typie TPoint

  Teraz w sekcji implementation opisujemy funkcję

  Najpierw nagłówek żeby kompilator wiedział jaką funkcję opisujemy
Function Move(Pos:TPoint;Dir:integer;c:integer):TPoint;


  Otwieramy blok poleceń
begin


  do składowej X wyniku przypisujemy wyliczony X
  Result.X:=Pos.X+Trunc(Cos(DegToRad(Dir))*c);


  do składowej Y wyniku przypisujemy wyliczony Y
  Result.Y:=Pos.Y-Trunc(Sin(DegToRad(Dir))*c);


  i kończymy funkcję
end;


7. Obliczanie odległości


  do obliczenia odległości między dwoma punktami można użyć wzoru Pitagorasa
  c*c=a*a+b*b;


  do obliczania kwadratu użyjemy funkcji Sqr() a do obliczania pierwiastka Sqrt()

  zapisujemy wzór Pitagorasa kodem Delphi
  Sqr(c):=Sqr(a)+Sqr(b);


  pozbywamy się potęgi z lewej strony równania
  c:=Sqrt( Sqr(a) + Sqr(b) );


  tu też dobrze jest zaokrąglić bo wynik pierwiastkowania też rzadko bywa liczbą całkowitą
  c:=Trunc( Sqrt( Sqr(a) + Sqr(b) ) );

 
  odcinki a i b trzeba najpierw obliczyć
  jest to różnica odpowiednich par współrzędnych dwóch punktów
  nawet jeśli wynik wyjdzie ujemny nie jest to przeszkodą bo przy podnoszeniu
  do kwadratu wynik zawsze jest dodatni
  c:=Trunc( Sqrt( Sqr( A.X - B.X ) + Sqr( A.Y - B.Y ) ) );


  teraz deklaracja funkcji w sekcji interface
function Range(A:TPoint;B:TPoint):integer;


  i opis funkcji w sekcji implementation
function Range(A:TPoint;B:TPoint):integer;
begin


  przypisanie do wyniku funkcji wyniku obliczeń
  Result:=Trunc( Sqrt( Sqr( A.X - B.X ) + Sqr( A.Y - B.Y ) ) );


8. Sterowanie


  Nie będziemy sprawdzać stale stanu klawiszy lecz użyjemy zdarzeń na
  wciśnięcie oraz puszczenie klawisza gdzie zmienimy odpowiednio stan
  zmiennych logicznych ( możliwe wartości True lub False )
 
  te zmienne logiczne będziemy sprawdzać, i zależnie od ich stanu wykonamy
  pewne czynności ( ruch, zmiana kierunku, strzał )

  Najpierw przyszykujemy typ w którym będą zmienne odpowiadające danym klawiszom

  w sekcji interface, lecz ważne by przed deklaracją typu TPlayer piszemy
type TKeys = record
 
  Left:boolean;
  Right:boolean;
  Up:boolean;
  Down:boolean
  Fire:boolean
 
end;


  teraz modyfikujemy typ TPlayer by zawierał zmienną typu TKeys
  czyli po prostu dopisujemy gdzieś pomiędzy record a end
  Keys:TKeys;


  Teraz użyjemy zdarzeń do modyfikowania stanu zmiennych
  w Inspektorze Obiektów ( małe okienko na dole z lewej strony ) zmieniamy
  zakładkę na Events i wybieramy OnKeyDown ( wciśnięcie klawisza ),
  klikamy dwa razy i zostaniemy automatycznie przeniesieni do
  procedury obsługi zdarzenia ( w oknie edytora kodu )
 
  tak więc mamy na razie
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 
end;


  trzeba rozpoznać jaki klawisz został wciśnięty
  użyjemy instrukcji case of, zależnie od stanu sprawdzanej
  zmiennej wykonamy różny czynności

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 
  case Key of // zmienna Key zawiera numer wciśniętego klawisza
 
    //cyfra przed dwukropkiem określa wartość w zmiennej Key
    //przy której instrukcja za dwukropkiem zostanie wykonana
 
    //klawisze pierwszego gracza: WSAD + spacja
    87:Players[0].Keys.Up:=true;    // klawisz W włącza ruch do przodu
    83:Players[0].Keys.Down:=true;  // klawisz S włącza ruch do tyłu
    65:Players[0].Keys.Left:=true;  // klawisz A włącza skręcanie w lewo
    68:Players[0].Keys.Right:=true; // klawisz D włącza skręcanie w prawo
    32:Players[0].Keys.Fire:=true;  // spacja włącza strzelanie
 
    //klawisze drugiego gracza: klawiatura numeryczna 4568 + enter
    //wpisane niżej kody oznaczają cyfry a nie klawisze funkcyjne
    //więc NumLock musi być włączony
    //kod 13 odpowiada obu enter'om
    104:Players[1].Keys.Up:=true;    // klawisz 8 włącza ruch do przodu
    101:Players[1].Keys.Down:=true;  // klawisz 5 włącza ruch do tyłu
    100:Players[1].Keys.Left:=true;  // klawisz 4 włącza skręcanie w lewo
    102:Players[1].Keys.Right:=true; // klawisz 6 włącza skręcanie w prawo
    13 :Players[1].Keys.Fire:=true;  // enter włącza strzelanie 
 
  end; // koniec case'a
 
end;


  teraz wybieramy zdarzenie OnKeyUp, dwukrotnie klikamy

  powinno być takie coś
procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 
end;


  instrukcja będzie prawie identyczna, z tym że zamiast
  włączać ( true ) będziemy wyłączać ( false )
 
procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 
  case Key of // zmienna Key zawiera numer wciśniętego klawisza
 
    //klawisze pierwszego gracza: WSAD + spacja
    87:Players[0].Keys.Up:=false;    // klawisz W wyłącza ruch do przodu
    83:Players[0].Keys.Down:=false;  // klawisz S wyłącza ruch do tyłu
    65:Players[0].Keys.Left:=false;  // klawisz A wyłącza skręcanie w lewo
    68:Players[0].Keys.Right:=false; // klawisz D wyłącza skręcanie w prawo
    32:Players[0].Keys.Fire:=false;  // spacja wyłącza strzelanie
 
    //klawisze drugiego gracza: klawiatura numeryczna 4568 + enter
    //wpisane niżej kody oznaczają cyfry a nie klawisze funkcyjne
    //więc NumLock musi być włączony
    //kod 13 odpowiada obu enter'om
    104:Players[1].Keys.Up:=false;    // klawisz 8 wyłącza ruch do przodu
    101:Players[1].Keys.Down:=false;  // klawisz 5 wyłącza ruch do tyłu
    100:Players[1].Keys.Left:=false;  // klawisz 4 wyłącza skręcanie w lewo
    102:Players[1].Keys.Right:=false; // klawisz 6 wyłącza skręcanie w prawo
    13 :Players[1].Keys.Fire:=false;  // enter wyłącza strzelanie
 
  end; // koniec case'a
 
end;


9. Przemieszczanie się, strzelanie, ginięcie


  Tutaj użyjemy w odpowiednich pętlach funkcje przygotowane wcześniej
  To co teraz napiszemy musi być wywoływane cyklicznie

  Użyjemy do tego komponentu Timer ( stoper,licznik czasu )
  -w górnym oknie Delphi znajduje się sporo zakładek
  -wybieramy zakładkę System
  -wybieramy Timer ( jedno kliknięcie na zegarek - pierwsza ikonka )
  -klikamy w dowolnym miejscu na formie
  -na formie pojawi się kwadracik z rysunkiem zegarka, nie będzie go
   widać po włączeniu programu
  -wybieramy Timer
 
  w zakładce Events w inspektorze obiektów jest tylko jedno
  zdarzenie ( OnTimer )  otwieramy je podwójnym kliknięciem

  w edytorze kodu zobaczymy takie coś
procedure TForm1.Timer1Timer(Sender: TObject);
begin
 
end;


  będziemy potrzebować liczniki dla dwóch pętli więc deklarujemy je
procedure TForm1.Timer1Timer(Sender: TObject);
var
  i,j:integer;
begin
 
end;


  nie będzie to najbardziej optymalny algorytm
  gdyż starałem się go zrobić w miarę czytelnie
  zostanie ulepszony a zatem utrudniony a następnych
  częściach tego kursu ( jeśli w ogóle będą )

  procedure SetLength ustawia ilość komórek w tablicy dynamicznej
  funkcja Length zwraca ilość komórek w tablicy
  funkcja High zwraca indeks ostatniego elementu w tablicy

  najpierw obsługa pocisków

  pamiętaj by pisać w bloku instrukcji danego zdarzenia
  czyli tym wygenerowanym automatycznie

  pętla po wszystkich pociskach
  lecz najpierw sprawdzimy czy są jakiekolwiek pociski
  jeśli długość tablicy pocisków jest różna od zero wtedy
  if Length(Bullets)<>0 then
  begin


  pętla po wszystkich pociskach
    for i:=0 to High(Bullets) do
    begin


  przesuwamy pocisk
      Bullet[i].Pos:=Move(Bullets[i].Pos,Bullets[i].Dir,20);


  sprawdzamy czy pocisk nie wyszedł poza planszę ( obszar roboczy okna )

  czy nie wyszedł poza lewą lub górną krawędź okna
      if (Bullets[i].Pos.X<0) or (Bullets[i].Pos.Y<0) then
        Bullets[i].Del:=true;


  czy nie wyszedł poza prawą lub dolną krawędź okna
      if (Bullets[i].Pos.X>Form1.ClientWidth) or (Bullets[i].Pos.Y>Form1.ClientHeight) then
        Bullets[i].Del:=true;


  zapętlamy po wszystkich graczach by sprawdzić odległośc pocisku od tych graczy
      for j:=0 to 1 do
      begin
  

  sprawdzamy czy pocisk zderzył się z jakimś graczem
  czyli czy odległość pocisku od środka gracza o kształcie okręgu
  jest mniejsza bądź równa promieniowi tego okręgu ( tutaj 16 pikseli)
              if Range(Players[j].Pos,Bullets[i].Pos)<=16 then
        begin


   ustawiamy pocisk do kasowania i ranimy gracza
          Bullets[i].Del:=true;
          Dec(Players[j].Life,5);


  zakańczamy bloki
        end;
      end;
    end;
  end;


  usuwamy "zużyte" pociski
 
  pętla po wszystkich pociskach
  i:=0;
  while i<Length(Bullet) do
  begin


  sprawdzenie czy pocisk jest przeznaczony do kasacji
    if Bullets[i].Del then
    begin


  usuwanie pocisku

  sprawdzenie czy usuwany pocisk jest na końcu tablicy
  jeśli tak to usuwamy ostatnią komórkę tablicy
  jeśli nie to...
      if i=High(Bullets) then
        SetLength(Bullets,Length(Bullets)-1)
      else
      begin


  do usuwanej komórki przypisujemy komórkę ostatnią i skracamy tablicę
        Bullets[i]:=Bullets[High(Bullets)];
        SetLength(Bullets,Length(Bullets)-1);


  skoro do usuwanej komórki przypisaliśmy komórkę której
  jeszcze nie sprawdziliśmy wypadało by ją sprawdzić
  zrobimy to "cofając" pętlę tak więc ten sam element ale
  z inną zawartością zostanie sprawdzony drugi raz
        Dec(i);


  zwiększanie licznika i koniec pętli
      end;
    end;
    Inc(i)
  end;


  teraz obsługa graczy ( ciągle w tym samym zdarzeniu! )

  pętla obejmująca każdego gracza ( tablica ma komórki o indeksach
  0 i 1 )
  for i:=0 to 1 do
  begin


  sprawdzamy czy obrót w lewo danego gracza jest włączony
    if Players[i].Keys.Left then


  jeśli tak to zwiększamy kąt o 10
      Inc(Players[i].Dir,10);


  teraz sprawdzamy obrót w prawo
    if Players[i].Keys.Right then


  jeśli tak to zmniejszamy kąt o 10
      Dec(Players[i].Dir,10);


  czy ruch do przodu jest włączony
  if Players[i].Keys.Up then


  jeśli tak to używamy funkcji Move do zmiany pozycji
 
      Players[i].Pos:=Move(Players[i].Pos,Players[i].Dir,10);


  czy ruch do tyłu jest włączony
  if Players[i].Keys.Down then


  jeśli tak to używamy funkcji Move do zmiany pozycji
  ruch do tyłu uzyskamy podając ujemną długośc kroku
 
      Players[i].Pos:=Move(Players[i].Pos,Players[i].Dir,-5);


  teraz trudnijesze - strzelanie

  sprawdzamy czy licznik odstęu między strzałami jest wyzerowany
    if Players[i].Fire=0 then
    begin


  sprawdzamy czy strzelanie jest włączone
      if Players[i].Keys.Fire then
      begin


  dodajemy nowa komórkę w tablicy pocisków
  pozycji nowego pocisku przypisujemy pozycję gracza z przesunięciem
  które oddali pocisk na tyle żeby gracz nie postrzelił sam siebie
  kierunek ruchu pocisku ustawiamy na kierunek ruchu gracza by pocisk
  mógł się sam poruszać
        SetLength(Bullets,Length(Bullets)+1);
        Bullets[High(Bullets)].Pos:=Move(Players[i].Pos,Players[i].Dir,20);
        Bullets[High(Bullets)].Dir:=Players[i].Dir;


  ustawiamy odstęp przed następnym strzałem
        Players[i].Fire:=2;



  zakańczamy bloki instrukcji i opisujemy co ma się dziać gdy licznik
  odstępu miezy strzałami nie jest wyzerowany
      end;
    end
    else


  jeśli nie jest wyzerowany to go zbliżamy o 1 do zera
  jeśli zmniejszamy ( Dec ) lub zwiększamy ( Inc ) o 1
  to nie trzeba podawać liczby o którą zmieniamy
      Dec(Players[i].Fire);


  teraz trzeba sprawdzić czy gracz jeszcze żyje
  sprawdzamy czy ilość życia jest mniejsza lub równa 0 ( niedodatnia )
    if Players[i].Life<=0 then
    begin


  wylosujemy nowa pozycję dla gracza

  procedura Randomize przygotowuje silnik losujący do użycia
  funkcja Random zwraca liczbę pomiędzy zero a podanym argumentem

  by losować z danego zakresu z pominięciem jego brzegu
  ( gdzie zakres zaczyna się od 0 )
  a -> "margines"
  max -> koniec zakresu
  Random(Max-a)+a/2;
<code>
 
  tutaj "marginesem" będzie średnica koła będącego graczem ( 32 )
<code=delphi>
      Randomize();
      Players[i].Pos.X:=Random(Form1.ClientWidth-32)+16;
      Players[i].Pos.Y:=Random(Form1.ClientHeight-32)+16;


  Skoro postać zginęła trzeba przwyrócić ją do życia
      Players[i].Life:=100;


  zakańczamy blok instrukcji
    end;

 
  konieć pętli
  end;


10. Rysowanie


  Rysowanie musi być przeprowadzane za każdą zmianą pozycji graczy i pocisków
  Czyli najlepiej je umieścić tuż za tymi zmianami, czyli robimy je w
  procedurze obsługi zdarzenia  Ontimer ( to jedyne zdarzenie Timer'a )
  możemy tworzyć kolejne pętle gdzie będziemy rysować lub robić to od razu
  po zmianie pozycji obiektów
 
  dla graczy zrobimy to przed końcem pętli ( po ewentualnej zmianie pozycji po
  "śmierci" )

  w pętli kasującej pociski będziemy je rysować , ale tylko jeśli nie zostaną
  skasowane, więc rysowanie będzie jeśli pocisk nie będzie przeznaczony
  do kasacji
 
  najpierw jednak trzeba wyczyścić okno z poprzednich rysunków
  czyszcenie umieścimy na początku procedury obsługi zdarzenia ( przed
  pierwszą pętlą )
  kolor jaki forma ma domyślnie jest zapisany w stałej clBtnFace
  ustawimy ją jako kolor pędzla
  procedura FillRect zamaluje podany prostokąt bieżącym pędzlem
  funkcja Rect "składa" podane wymiary do rekordu TRect
  my zamalowujemy od lewego górnego rogu do prawego dolnego
  Form1.Canvas.Brush.Color:=clBtnFace;
  Form1.Canvas.FillRect(Rect(0,0,Form1.ClientWidth,Form1.ClientHeight));


  teraz rysowanie pocisków
  kolor pędzla ustawimy przed pętlą by robić to tylko raz bo jak
  zrobimy to w pętli to pędzel będzie ustawiany za każdym razem, co jest zbędne

  więc przed pętlą while
  clYellow to oczywiście stała zawierająca kolor żółty
  Form1.Canvas.Brush.Color:=clYellow;


  rysowanie pocisków wymaga zmodyfikowania zawartości pętli
  blok instrukcji dla warunku ( IF ) wygląda jak dotąd tak:
    if Bullets[i].Del then
    begin
 
      if i=High(Bullets) then
        SetLength(Bullets,Length(Bullets)-1)
      else
      begin
 
        Bullets[i]:=Bullets[High(Bullets)];
        SetLength(Bullets,Length(Bullets)-1);
        Dec(i)
 
      end;
 
    end;


  dodajemy blok do wykonania gdy warunek nie zostanie spełniony ( else )
  procedura Ellipse rysuje elipsę wpisaną w prostokąt którego dwa
  przeciwległe kąty są podane ( pary współrzędnych )

  jako wszystkie współrzędne podamy pozycję pocisku lecz pierwszą parę
  zmniejszymy o promień rysowanego koła a druga zwiększymy
    if Bullets[i].Del then
    begin
 
      if i=High(Bullets) then
        SetLength(Bullets,Length(Bullets)-1)
      else
      begin
 
        Bullets[i]:=Bullets[High(Bullets)];
        SetLength(Bullets,Length(Bullets)-1);
        Dec(i)
 
      end
      else
      begin
 
        Form1.Canvas.Ellipse(Bullets[i].Pos.X-3,Bullets[i].Pos.Y-3,Bullets[i].Pos.X+3,Bullets[i].Pos.Y+3);
 
      end;
 
    end;


  teraz rysowanie graczy

  wybieramy kolor na niebieski ( clBlue )
  wstawiamy przed pętlą ( for i:=0 to 1 do )
  Form1.Canvas.Brush.Color:=clBlue;


  przed końcm pętli ( po losowaniu pozycji w przypadku śmierci )
  wstawiamy rysowanie okręgu podobnie jak przy pociskach
    Form1.Canvas.Ellipse(Players[i].Pos.X-16,Players[i].Pos.Y-16,Players[i].Pos.X+16,Players[i].Pos.Y+16);


11. Modyfikacje


  Jeśli uważasz że postacie ( lub pociski ) poruszają
  się za wolno ( lub za szybko ) zmień trzeci argument
  podawany w funkcji Move()
 
  By nie stracić na płynności zmień odstęp czasu pomiędzy
  wykonywaniem ruchu i rysowaniem
  wybierasz Timer i w inspektorze obiektów zmieniasz
  właściwość Interval ( ja zmieniłem na 100ms )

12. The End


  Zachęcam do wprowadzenia modyfikacji w kodzie
  Mam nadzieję że na tym nie poprzestaniesz =]

  Jeśli będą chętni a ja będę mieć czas i chęci
  zrobię następne części, gdzie kod z tej części
  zostanie zoptymalizowany ( ulepszony i przyśpieszony )
  Oczywiście pojawią się też nowe rzeczy, np gra sieciowa,
  ustawianie klawiszy, wiele broni, punktacja, bonusy itd

13. Gotowiec


  A oto gotowy projekt do którego możesz zerknąć jeśli
  nie dajesz sobie rady, lecz zachęcam do samodzielnego
  szukania błędów przez czytanie ( i zrozumienie )
  tego co mówi Ci Delphi

  Jak_Zrobić_Grę_W_Delphi.zip (201,47 KB)

Autor


Potwoor_ ( żebyście wiedzieli na kogo krzyczeć )

6 komentarzy

Sebaso.PL 2011-05-16 12:24

No właśnie .. Gdzie gotowiec ;p ?

norbert95 2011-04-21 09:11

a gdzie ten gotowiec?

Potwoor_ 2008-10-31 20:12

można dokładniej co nie działa? =]

Patrol27 2008-10-31 10:15

U mnie to nie działa.
Mam Delphi 7 Personal, ktoś pomoże ?

Potwoor_ 2008-07-22 10:25

no cóż... zdarza się że taki ktosik co nic nie umie chce zrobić grę, i to dla takich ktosików jest =]
starałem się by było jak najprostsze, i na początku wsadziłem do FAQ =]

lewymati 2008-07-21 15:15

jeśli chodzi o gry w delphi to używa się raczej jakiś bibliotek takich jak omega
wiele kursów na www.unit1.pl