Uma das principais características deste tipo de jogo é a sua forma de “contar uma estória”. Diferentemente dos adventures de interpretação (onde a ação do jogador é definida por uma frase), nos jogos point & click toda a narrativa está atrelada a ações claramente definidas no espaço e no tempo do jogo.

Como a proposta deste e-book é criar um adventure neste estilo, vamos definir logo de saída o tema. Isso não impede aqueles que desejarem, seguir construindo um modelo próprio. Mantendo a tradição, o jogo irá se passar numa selva e envolverá uma excursão exploratória a uma caverna, cuja entrada está disfarçada em uma grande árvore.

O objetivo não é apenas mostrar a parte técnica de programação do jogo, mas mostrar como construir um enredo que envolva o jogador e o conduza a uma aventura cheia de mistérios e surpresas. Pronto para a jornada? Então vamos...

Em primeiro lugar, abra o Delphi e crie um formulário novo, com as seguintes medidas:

ClientHeight = 468
ClientWidth = 552

Em seguida crie um TPanel, que chamaremos de Tela, com as seguintes propriedades:

BevelInner = bvNone
BevelOuter = bvNone
Caption = ""
Color = clBlack
Height = 280
Left = 5
Name = Tela
Top = 5
Width = 540

Essa será a tela de apresentação do jogo, ou visor, ou seja lá que nome a gente possa dar a ela. Será portanto a representação gráfica do que o jogador estaria vendo. Optei pelo TPanel e não por uma TImage por razões técnicas: o panel é bem mais eficiente para mostrar gráficos, além de servir como suporte para “tocar” arquivos AVIs. E é claro que vamos querer passar filminhos em nossos jogos, não é mesmo?

Mas não vamos “desenhar” direto no panel, então criamos um buffer (TBitmap) para usarmos como tela de trabalho. Na área de variáveis globais, colocamos a sua definição:

implementation
{$R *.DFM}

var
  Buff: TBitmap;

Criamos o evento OnCreate, do form1, para definir as propriedades do buffer:

procedure TForm1.FormCreate(Sender: TObject);
begin
  Buff:= TBitmap.Create;
  Buff.Height:= Tela.Height;
  Buff.Width:= Tela.Width;
  Buff.PixelFormat:= pf24bit;
end;

E, no embalo para não esquecer depois, criamos o evento OnDestroy, para liberar a memória usada para o buffer

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Buff.Free;
end;

Agora só falta uma procedure para transferir o conteúdo do buffer para o panel usado como visor (MostraTela):

procedure TForm1.MostraTela;
var
  Hpan: HBitmap;
begin
  Hpan:= GetDC(Tela.Handle);
  BitBlt(Hpan,0,0,Tela.Width,Tela.Height,
         Buff.Canvas.Handle,0,0,SRCCOPY);
  ReleaseDC(Tela.Handle,Hpan);
end;

Recapitulando: criamos um visor para o jogo (Tela) e um buffer (Buff) para usar como rascunho, ou seja, tudo aquilo que queremos mostrar ao jogador vai para esse buffer e no final do processo chamamos a procedure MostraTela para transferir esse conteúdo para o visor. Assim aceleramos ao máximo as operações gráficas e evitamos os famigerados flicks de tela.

Como eu disse “visor da posição”, podemos então imaginar que por alguma mágica compucabalistica o que o jogador vê é o cenário à sua frente e para que tal cenário realmente exista precisamos de um método de criação. Usaremos scripts de texto, para definir tudo o que for possível de ser feito em nosso jogo.

O componente mais óbvio para scripts de texto é o TMemo, então criamos um com as seguintes propriedades:

Height = 146
Left = 5
Name = Memo1
ScrollBars = ssVertical
Top = 320
Width = 471
WordWrap = false

Obviamente que, num jogo finalizado, não queremos que o jogador veja o script, mas por enquanto isso não é importante. Pelo contrário: nos permitirá analisar e entender melhor o que está acontecendo em nosso sistema.

Tendo o script, precisamos agora de alguma forma “executá-lo”. A minha proposta é para algo bem simples: cada linha será um comando e portanto, basta termos um indicador de linha em uso e pronto. Uma procedure definida para isso faria todo esse trabalho:

procedure TForm1.Executar;
var
  Tm,Tp: integer;
begin
  if Memo1.Lines.Count = 0 then exit;
  Tm:= 0;
  while Tm < Memo1.Lines.Count do begin
    Tp:= DecodeLin(Memo1.Lines[Tm]);
    if Tp = -1 then inc(Tm) else Tm:= Tp;
  end;
end;

Note que a procedure vai “percorrer” o script linha a linha e usar uma função (DecodeLin) para decodifica-la (reconhecer o comando ou instrução e fazer o que for preciso). Se ao retornar da função, encontrarmos o valor –1, então o script prosseguirá para a próxima linha. Se for outro valor, então a execução do script salta para a linha indicada.

Com vocês então a função DecodeLin:

function TForm1.DecodeLin(S: string): integer;
var
  Cmd: string;
  Tp,Px,Py: integer;
  Pc: TColor;
begin
  while S[1] = ' ' do S:= copy(S,2,length(S));
  result:= -1;
  Cmd:= copy(S,1,pos(' ',S)-1);
  S:= copy(S,pos(' ',S)+1,length(S));

end;

Ela começa simplesmente ignorando os espaços em branco que encontrar na linha. Assim pode-se usar identação para deixar o script mais claro. Em seguida obtém-se o comando (Cmd), que é definido pela primeira palavra da linha – o primeiro espaço encontrado é portanto um delimitador de campo. Tendo retirado o Cmd da linha, basta identifica-lo e construir a programação necessária.

Por exemplo: vamos criar um comando no script para transferir o conteúdo do buffer para o visor Tela. Já temos a procedure, então, basta adotar uma palavra para servir como comando. Proponho “refresh”:

  //Transfere o buffer para o visor
  if Cmd = 'refresh' then begin
    MostraTela;
    exit;
  end;

Mas isso não vai provocar nenhum resultado palpável (ou visível), então vamos criar mais um comando. Um que limpe o buffer com uma determinada cor. Digamos “clear”. A sintaxe do comando seria portanto “clear FFFFFF” onde FFFFFF é uma cor definida como um hexadecimal, representando os três componentes RGB (Red, Green, Blue) da cor:

  //Limpa o buffer com uma determinada cor
  if Cmd = 'clear' then begin
    try Pc:= StrToInt('$'+S); except Pc:= clBlack; end;
    Buff.Canvas.Brush.Color:= Pc;
    Buff.Canvas.FillRect(bounds(0,0,Tela.Width,Tela.Height));
    exit;
  end;

Para ver isso tudo funcionando, coloque no Memo1 o seguinte texto:

clear 00FF00
refresh

Crie um botão em algum lugar do form1 e coloque no evento OnClick dele a ordem para executar o script:

procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
  Executar;
end;

Agora basta clicar no botão e ver... nada acontecer.

Há um bug conceitual nessa estrutura e o desafio é descobri-lo. A correção do mesmo poderá provocar um segundo bug e daí seremos obrigados a tomar decisões drásticas. Mas é assim sempre que criamos sistemas interpretativos, ou seja, eles irão se comportar em função de um segundo nível de programação, que em nosso caso são os scripts.

Portanto, estude, pense e analise com calma o que está exposto aqui. Use a lista de discussão da TILT ou o workshop virtual para tirar suas dúvidas.


online