Jak zrobić grę

Potwoor_

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:

img3.JPG

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:

img1.jpg

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:

img2.jpg

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;

tutaj "marginesem" będzie średnica koła będącego graczem ( 32 )

      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

Autor

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

6 komentarzy

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

a gdzie ten gotowiec?

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

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

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 =]

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