Pierwsza gra

Jest sobotni wieczór. Właśnie skończyłem się kuć do morderczej klasówki z biologii, która będzie w poniedziałek. Postanowiłem nie obejrzeć kiepskich i napisać ten oto artykuł. O czym on będzie? Będzie o pierwszej grze, którą można zacząć wielką przygodę z programowaniem gier. Więc usiać wygodnie,
skup się i przeczytaj mój artykuł.

Na początek proponuje trochę refleksji filozoficznych na temat programowania gier. Większość osób chce zacząć programowanie gier od jakiegoś prostego tetrisa. Ja uważam inaczej! Zacznijmy od czegoś oryginalnego! Proponuje zacząć od dupy strony... najpierw tytuł hmm... wiem! "SQUASH"!!! YEAH!!! zapowiada się nieźle. Teraz na czym będzie polegać. Będzie to gra dla, dwóch graczy (żywych). Gra polega odbijaniu piłki o ścianę (na zmianę przez dwóch graczy). Ten kto nie odbije traci punkt. Gra się do piętnastu. Zasady są proste ale właśnie one zapewnią miodność.

Czas przejść do spraw trudniejszych... programowanie. Postanowiłem, że kod przedstawię w Pascalu. Naszą grę już napisałem w Delphi używając komponentu DelphiX. Najważniejsze  żeby w tym momencie przyjąć założenia odnośnie kodu. W grze dostępne będą trzy obiekty:
1. Piłka
2. Paletka

Obydwa są jakimiś obiektami o wspólnych cechach takich jak:
1. Położenie
2. Wektor przemieszczenia (wektor czyli jak się zmienia jego położenie)

Powinno się zdefiniować trzy klasy: TSprite, TPaletka, TBall. TSprite czyli obiekt podstawowy dla
TPaletka i TBall.

Wstępne założenia przyjęte. Czas zabrać się za pisanie kodu. Są dwie metody pisania. Od ogółów do
szczegółów lub odwrotnie. Osobiście polecam pisanie od szczegółów, ponieważ daje to nam możliwość wczesnego wykrywania i usuwania błędów. Zaczynamy!

Najpierw trzeba utworzyć nowy projekt wstawić do niego następujące komponenty:
-TDXDraw (ustaw property align na alClient)
-TDXTmier (ustaw property interval na 0)
-TDXInput (ustaw property mouse/enabled na true)

Ponadto utwórz nowy moduł engine.pas i dodaj go do głównego unitu. Teraz bierzemy się za engine.pas.
Najpierw klasa TSprite :
TSprite=class // klasa podstawowa dla wszystkich obiektów
x,y:real;
vx,vy:real;
sx,sy:integer; // rozmiary
mx,my:integer; // rozmiary pola po jakim mozna sie poruszac
deadx:integer; // linia za ktora pilke uwaza sie martwa
points:integer; // jakies punkty
procedure ustaw(wx,wy,wvx,wvy:real;wsx,wsy,wmx,wmy,wdeadx:integer);virtual;
function xx:integer;virtual; // zwraca calkowita wartosc x
function yy:integer;virtual; // zwraca calkowita wartosc y
procedure draw(dxdraw:TDXDraw); virtual;
end;

implementation

/////////////
TSPRITE
/////////////
procedure TSprite.ustaw(wx,wy,wvx,wvy:real;wsx,wsy,wmx,wmy,wdeadx:integer);
begin
x:=wx;
y:=wy;
vx:=wvx;
vy:=wvy;
sx:=wsx;
sy:=wsy;
mx:=wmx;
my:=wmy;
deadx:=wdeadx;
points:=0;
end;

function TSprite.xx:integer;
begin
xx:=trunc(x);
end;

function TSprite.yy:integer;
begin
yy:=trunc(y);
end;

procedure TSprite.draw(dxdraw:TDXDraw);
begin
dxdraw.surface.canvas.Rectangle(xx-sx div 2,yy-sy div 2,xx+sx div 2,yy+sy div 2);
end;

Opis poszczególnych procedur i właściwości:
ustaw- inicjuje wszystkie zmienne
xx i yy: funkcje zwracają całkowitą wartość zmiennych x i y. Z pewnością
            ciekawi ciebie dlaczego to są typy real. Dzięki takiemu zabiegowi
            otzrymamy płynność ruchu. Należy także pamiętać, że efekt płynnego
            ruch otzrymamy dopiero dzięki funkcji trunc.
            UWAGA!!! Użycie funkcji round niweluje jaki kolwiek płynny ruch.
mx, my: rozmiary ,,boiska''
sx, sy: rozmiary sprita
draw: rysuje sprita w postaci zwykłego prostokąta

Teraz następna klasa:
TKontrol=(kKey,KMouse);
TPaletka=class(TSprite)
procedure paletka(dxdraw:TDXDraw;input:TDXInput;kontroler:TKontrol);
procedure move;
procedure draw(dxdraw:TDXDraw); overload;
end;

implementation

procedure TPaletka.paletka(dxdraw:TDXDraw;input:TDXInput;kontroler:TKontrol);
begin
vx:=0;
vy:=0;
case kontroler of
kKey:begin
if input.Keyboard.Keys[vk_up]=true then vy:=-input.Keyboard.ButtonCount / 10;
if input.Keyboard.Keys[vk_down]=true then vy:=input.Keyboard.ButtonCount / 10;
if input.Keyboard.Keys[vk_left]=true then vx:=-input.Keyboard.ButtonCount / 10;
if input.Keyboard.Keys[vk_right]=true then vx:=input.Keyboard.ButtonCount / 10;
end;
kMouse:begin
vx:=(input.Mouse.X*x) / x;
vy:=(input.Mouse.y*y) / y;
end;
end;
move;
draw(dxdraw);
end;

procedure TPaletka.move;
var
tx,ty:real;
begin
tx:=x+vx;
ty:=y+vy;
if (ty<=0) or (ty>=my) then
begin
vy:=0;
ty:=y;
end;
if (tx<=0) or (tx>=mx) then
begin
vx:=0;
tx:=x;
end;
x:=tx;
y:=ty;
end;

procedure TPaletka.draw(dxdraw:TDXDraw);
begin
dxdraw.surface.canvas.brush.Color:=RGB(0,0,255);
dxdraw.surface.canvas.pen.Color:=RGB(0,0,255);
dxdraw.surface.canvas.Ellipse(xx-sx div 2,yy-sy div 2,xx+sx div 2,yy+sy div 2);
end;

Opis poszczególnych procedur i właściwości:
paletka- wywołuje procki move i draw
move- dodaje vx i vy do x i y, inaczej przesuwa paletkę :)
draw: rysuje paletkę jako elipsę

Ostania klasa:
TBall=class(TSprite)
procedure ball(dxdraw:TDXDraw;var paletka:TPaletka;var who:byte);
procedure fire; // wystrzeliwuje pilke
procedure move(var paletka:TPaletka;var who:byte);
procedure draw(dxdraw:TDXDraw); overload;
end;

implementation

procedure TBall.ball(dxdraw:TDXDraw;var paletka:TPaletka;var who:byte);
begin
move(paletka,who);
draw(dxdraw);
end;

procedure TBall.fire;
begin
x:=deadx;
y:=my div 2;
vx:=-(random+5.2);
vy:=(random - random)*2;
end;

procedure TBall.move(var paletka:TPaletka;var who:byte);
var
tx,ty:real;
x1,x2,y1,y2:real;
begin
tx:=x+vx;
ty:=y+vy;
begin
x1:=paletka.x - paletka.sx / 2 - vx;
y1:=paletka.y - paletka.sy / 2 - vy;
x2:=paletka.x + paletka.sx / 2 + vx;
y2:=paletka.y + paletka.sy / 2 + vy;
end;
if (tx>=x1) and (ty>=y1) and (tx<=x2) and (ty<=y2) then
begin
tx:=x;
vx:=-(2*vx-paletka.vx) / 2;
vy:=-(2*vy+paletka.vy) / 2;
tx:=tx+vx;
inc(paletka.points);
who:=abs(who-1);
end;
if (tx<=0) or (tx>=mx) then
begin
if (tx>=mx) and ((vx>0) or (vy>0)) then
begin
dec(paletka.points);
vx:=0;
vy:=0;
end;
vx:=-vx;
tx:=tx+vx;
end;
if (ty<=0) or (ty>=my) then
begin
vy:=-vy;
ty:=ty+vy;
end;
x:=tx;
y:=ty;
end;

procedure TBall.draw(dxdraw:TDXDraw);
begin
dxdraw.surface.canvas.brush.Color:=RGB(255,0,0);
dxdraw.surface.canvas.pen.Color:=RGB(255,0,0);
dxdraw.surface.canvas.Ellipse(xx-sx div 2,yy-sy div 2,xx+sx div 2,yy+sy div 2);
end;

Opis poszczególnych procedur i właściwości:
ball- wywołuje procki move i draw
move- dodaje vx i vy do x i y, inaczej przesuwa paletkę oraz sprawdza czy:)
draw: rysuje piłkę jako koło

Cóż mamy zrobiony engine. Powyższy kod jest trochę nie dopracowany ale działa.
W tej chwili najważniejszy jest mechanizm. Odrywamy się od modułu mapa. pas i wracamy
do głównego Teraz utwórz dwa zdarzenia:
-zdarzenie OnCreate głównego formularza
-zdarzenie OnTimer komponentu DXTimer

Dopisz także dwie trzy: paletka1, paletka2:TPaletka, who:byte i ball:TBall;

Teraz w procedurze obsługującej zdarzenie OnCreate:
procedure TForm1.FormCreate(Sender: TObject);
begin
paletka1:=TPaletka.Create;
paletka2:=TPaletka.Create;
ball:=TBall.Create;
paletka1.ustaw(dxdraw.width,dxdraw.height div 2,0,0,10,50,dxdraw.width,dxdraw.height,dxdraw.width);
paletka2.ustaw(dxdraw.width,dxdraw.height div 2,0,0,10,50,dxdraw.width,dxdraw.height,dxdraw.width);
ball.ustaw(dxdraw.width,random(dxdraw.height)
,0,0,10,10,dxdraw.width,dxdraw.height,dxdraw.width);
who:=0;
end;

Natomiast w timerze:
procedure TForm1.DXTimer1Timer(Sender: TObject; LagCount: Integer);
begin
if (ball.vx=0) and (ball.vy=0) and (dxinput.keyboard.keys[VK_RETURN]=true) then
ball.fire;
dxdraw.surface.Fill(clwhite);
dxinput.update;
paletka1.paletka(dxdraw,dxinput,kmouse);
paletka2.paletka(dxdraw,dxinput,kkey);
case who of
0:ball.ball(dxdraw,paletka1,who);
1:ball.ball(dxdraw,paletka2,who);
end;
dxdraw.surface.Canvas.Release;
dxdraw.Flip;
end;

Tym sposobem napisaliśmy najprostsza z możliwych gier. Możesz ściągnąć źródła  stąd [squash.zip].
Mam nadzieje, że coś załapałeś(aś)... oraz, że tknąłem w tobie tak cenne natchnienie twórcze Bez
niego nic nie da się napisać. Oto propozycje rozszerzenia:
- dodanie jakiś zasad punktowania
- bitmapy zamiast zwykłych figur
- tło
- dźwięki
- i cokolwiek zdołasz wymyślić

Jeżeli masz jakieś pytania pisz na moją skrzynkę. Chętnie pomogę...

Grzegorz GREG Tańczyk
warsztat@poczta.fm
 

 Copyright © 2000 PTiK. Wszystkie prawa zastrzeżone.
 Kopiowanie tekstów w całości lub we fragmentach bez zgody redakcji i autorów zabronione.