Criando jogos em Delphi – Parte II

25 06 2008

- Imagens -

    No primeiro tutorial eu expliquei um pouco sobre como usar as funções da GDI para desenvolver um pequeno "jogo". Agora vou explicar como usar imagens nos seus jogos. Desenhar imagens com a GDI do windows é muito mais rápido do que usar as funções de desenho, já que não é preciso nenhum cálculo, basta copiar os pixels para a tela.

    Vamos usar para este exemplo imagens no formato bitmap padrão do windows. Exceto pelo fato de não serem comprimidas, elas são a melhor opção para jogos usando a GDI (imagens no formato JPEG podem ser usadas para fundos, mas nunca para sprites).

» Baixe aqui o arquivo com as imagens

- Considerações sobre resolução e número de cores -

    Desenhar bitmaps de 640 x 480 pixels com 256 cores é mais rápido do que desenhar um bitmap de 800 x 600 com o mesmo número de cores. Assim como desenhar um bitmap de 640 x 480 pixels com 256 cores é mais rápido do que desenhar um bitmap do mesmo tamanho com 24 ou 32 bits de cores. Além disso, o número de cores do backbuffer (o bitmap em que desenhamos antes de enviar para a tela) tem que ter o mesmo número de cores que os bitmaps que serão desenhados nele (do contrário será preciso converter os bitmaps a se desenhar antes, o que pode demorar um pouco). Um backbuffer de 16 bits é uma boa escolha (mesmo para bitmaps de 256 cores, que tem 8 bits). Jogos para computadores antigos precisam ter uma resolução e um número de cores menor (no máximo 256). A resolução do computador atual também afeta o desempenho do jogo. Quanto maior, mais coisas para desenhar, portanto um jogo em fullscreen será mais rápido do que um jogo rodado em janela.

- Carregando as imagens -

    Antes de usar as imagens precisamos carregá-las para a memória. O motivo? O acesso à memória é muuuito mais rápido do que o acesso ao disco. Mesmo assim temos que tomar cuidado para não carregar coisas demais para a memória. Como esse será um jogo bem simples e vamos usar apenas algumas imagens iremos carregá-las todas de uma vez para a memória, mas em um jogo maior é melhor carregar as imagens correspondentes à cada parte apenas quando for necessário (ex.: ao iniciar uma fase, ao começar um novo mapa, etc.)

    Antes de encher a página de códigos (nem são tantos assim) vou explicar um pouco sobre as diferentes maneiras de carregar e gerenciar imagens na memória.

- Utilizando TImages -

    A forma mais simples de carregar imagens para a memória no Delphi (mas não tão boa ou flexível) é usar TImages. Quando você adiciona um TImage ao formulário e coloca uma imagem nele o Delphi salva essa imagem em um arquivo de recurso (.RES) e carrega a imagem para a memória assim que o formulário é criado. Esse arquivo .RES é embutido no final do arquivo .EXE, então tudo o que você precisa fazer é distribuir um único .EXE e usar a propriedade Graphic do TPicture para desenhar a imagem. A vantagem desse método é que você não precisa se preocupar em carregar ou liberar a imagem da memória, o Delphi fará isso para você, além disso fica mais difícil um usuário comum copiar as imagens do seu jogo (embora tenha vários editores de resources por aí). A desvantagem é não poder usar esta técnica para muitas imagens ou imagens muito pesadas.


Usando TImages para carregar as imagens.

- Carregando imagens do disco -

    Essa é a técnica mais utilizada pelos jogos (e a que vamos usar neste tutorial). Consiste em manter algumas variáveis e carregar as imagens do disco para estas variáveis quando for preciso utilizá-las. Aqui vamos usar uma matriz de bitmaps para armazenar todas as imagens na memória (não se esqueça de deixar todas as imagens na mesma pasta do programa).

    Além dessas duas técnicas existem outras, que utilizam arquivos de recursos dinâmicos, dlls ou mesmo um grande arquivo com todas as imagens nele, mas eu não vou falar deles aqui.

- Um pequeno exemplo -

    Vamos construir um pequeno joguinho de pong bem simples. Crie um formulário e deixe a ClientWidth em 640 e a ClientHeight em 480 (a ClientWidth e ClientHeight se referem apenas à área útil do formulário, sem contar as bordas ou a barra de título), essa será a resolução do nosso joguinho. Declare as seguintes variáveis globais:

var
  Form1: TForm1;
  //backbuffer
  bbuffer: TBitmap;
  //matriz de bitmaps do jogo
  bitmaps: array of TBitmap;

    Declaramos uma matriz de bitmaps para carregar todas as imagens do jogo para a memória, agora precisamos carregar estes gráficos. Lembre-se que temos que inicializar os bitmaps antes de carregar as imagens e liberá-los da memória quando não precisarmos mais deles.

//função que carrega uma nova imagem na matriz
procedure loadImage(imagem: string);
begin
  //aloca um novo espaço na memória para a imagem
  //como aqui estamos usando matrizes dinâmicas
  //fica bem fácil fazer isso.
  SetLength(bitmaps, Length(bitmaps) + 1);
  //cria e carrega a imagem
  bitmaps[high(bitmaps)]:= TBitmap.Create;
  with bitmaps[high(bitmaps)] do
  begin
    //carrega do arquivo no disco para a memória
    LoadFromFile(imagem);
    //adiciona a transparência
    Transparent:= true;
    //modifica a cor de transparência para magenta
    TransparentColor:= clFuchsia;
  end;
end;
 
//função que libera todos os bitmaps da matriz
procedure freeBitmaps;
var i: Integer; //variável de controle do laço
begin
  //percorre toda a matriz...
  for i:= to High(bitmaps) do
  begin
    //libera o bitmap da memória..
    FreeAndNil(bitmaps[i]);
  end;
  //libera o espaço alocado para a matriz
  bitmaps:= nil;
end;
 
procedure LoadGameGraphics;
begin
  //carrega todas as imagens do jogo
  LoadImage('0.bmp');
  LoadImage('1.bmp');
  LoadImage('2.bmp');
  LoadImage('3.bmp');
  LoadImage('4.bmp');
  LoadImage('5.bmp');
  LoadImage('6.bmp');
  LoadImage('7.bmp');
  LoadImage('8.bmp');
  LoadImage('9.bmp');
  LoadImage('ball.bmp');
  LoadImage('bar.bmp');
  LoadImage('cpu.bmp');
  LoadImage('cts_text.bmp');
  LoadImage('field.bmp');
  LoadImage('player.bmp');
end;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  //quando o form for criado carregamos as imagens
  LoadGameGraphics;
end;
 
procedure TForm1.FormDestroy(Sender: TObject);
begin
  //quando o form for destruído, liberamos as imagens
  freeBitmaps;
end;

    Acho que vocês viram como a coisa funciona. Temos uma função que carrega as imagens na matriz, e chamamos ela quando precisamos carregar alguma imagem, no caso do nosso joguinho, assim que o formulário for criado.

    Vamos colocar agora algumas variáveis para controlar os objetos do nosso jogo.

type
  //objeto com as informações sobre a bolinha
  TBall = record
    //posição no plano do jogo
    X, Y: Single;
    //largura e altura da bolinha
    W, H: Integer;
    //velocidade horizontal e vertical (determina a
    //direção (ângulo) da bolinha
    Vx, Vy: Single;
    //velocidade da bolinha
    Speed: Single;
  end;
 
  //objeto com as informações da barrinha da AI
  TCPUBar = record
    //posição da barrinha no plano do jogo
    X, Y: Single;
    //largura e altura
    W, H: Integer;
    //velocidade máxima da barrinha
    Speed: Single;
  end;
 
  //objeto com as informações da barrinha do jogador
  TPlayerBar = record
    //posição da barrinha no plano do jogo
    X, Y: Single;
    //largura e altura
    W, H: Integer;
  end;
 
//precisamos criar algumas variáveis para manipular
//esses tipos e outras coisas no jogo
 
var
  Form1: TForm1;
 
  ball: TBall;
  bar_cp: TCPUBar;
  bar_p1: TPlayerBar;  
 
  //indica se o jogo está pausado ou não
  paused: Boolean = true;
  //pontos do jogador
  score_p1: Integer = 0;
  //pontos da AI
  score_cp: Integer = 0;
 
  //buffer de desenho do jogo
  bbuffer: TBitmap;
  //bitmaps do jogo
  bitmaps: array [0..15of TBitmap;

    Adicione um TTimer no formulário, assim como fizemos da última vez. É esse Timer que vai controlar o loop principal do jogo até ele terminar. O nosso jogo irá terminar apenas quando o usuário fechar a janela. No evento onTimer precisamos fazer algumas coisas:

  • Analisar as informações do jogo e mover os objetos

  • Verificar as entradas do usuário e processá-las no jogo (mover
    a barrinha)

  • Verificar colisões com a barrinha ou o cenário

  • Desenhar os objetos no backbuffer

  • Desenhar o backbuffer na tela

    Poderíamos colocar todas essas funções dentro do evento OnTimer, mas ao invés disso vamos usar algumas pequenas funções para cada uma dessas funcionalidades e chamá-las dentro do evento OnTimer. Assim o código fica mais organizado e menos repetitivo. Veja abaixo o código do evento OnTimer.

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  //se o jogo não estiver parado
  if not paused then
  begin
    //verifica colisões da bolinha
    ball_check_collision;
    //move a bolinha
    ball_move;
    //controla a AI da barrinha do computador
    bar_cp_handle;
  end;
 
  //limpa a tela anterior para desenharmos a nova tela
  bbuffer.Canvas.FillRect(Rect(0,0,640,480));
 
  //desenha o fundo do jogo
  bbuffer.Canvas.Draw(3777, bitmaps[14]);
 
  //pontuação do jogador (texto)
  bbuffer.Canvas.Draw(3710, bitmaps[15]);
  //chama a função que cria a imagem da pontuação
  CreateScore(19117, score_p1);
  //pontuação da AI (texto)
  bbuffer.Canvas.Draw(42410, bitmaps[12]);
  //chama a função que cria a imagem da pontuação
  CreateScore(50717, score_cp);
 
  //Desenha a barrinha do jogador
  bbuffer.Canvas.Draw(Round(bar_p1.x), Round(bar_p1.y), bitmaps[11]);
  //Desenha a barrinha da AI
  bbuffer.Canvas.Draw(Round(bar_cp.x), Round(bar_cp.y), bitmaps[11]);
  //Desenha a bolinha
  bbuffer.Canvas.Draw(Round(ball.x), Round(ball.y), bitmaps[10]);
 
  //temos uma imagem para indicar que o jogo está
  //parado, então, caso esteja desenhamos a imagem
  if paused then
    bbuffer.Canvas.Draw(156240, bitmaps[13]);
 
  //finalmente envia tudo isso para a tela
  Canvas.Draw(0,0,bbuffer);
end;

Função que checa se a bolinha colidiu com alguma coisa

//Função que verifica as colisões da bolinha
procedure ball_check_collision;
var bt: Single;
begin
  //distância da bolinha até o topo do cenário
  bt:= 77 + (ball.H / 2);
  //verifica se a bolinha colidiu com o topo do cenário
  if ball.Y < bt then
  begin
    //reflete a bolinha com o mesmo ângulo
    ball.Vy:= -ball.Vy;
    //move um pouco a bolinha para evitar outras colisões
    ball.Y:= bt + (bt - ball.Y);
  end;
 
  //distância da bolinha até o fundo do cenário
  bt:= 77 366 - ball.H - 6;
  //verifica se a bolinha colidiu com o fundo do cenário
  if ball.y > bt then
  begin
    //reflete a bolinha com o mesmo ângulo
    ball.Vy:= -ball.Vy;
    //move um pouco a bolinha para evitar outras colisões
    ball.y:= bt - (ball.y - bt);
  end;
 
  //distância da bolinha até a barrinha do jogador
  bt:= bar_p1.X + bar_p1.w - (ball.W / 2);
  //verifica se a bolinha colidiu com a barrinha do jogador
  if (ball.x < bt) and ((ball.y >= bar_p1.Y) and (ball.y + ball.h < bar_p1.Y + bar_p1.h)) then
  begin
    //reflete a bolinha com o mesmo ângulo
    ball.Vx:= -ball.Vx;
    //aumenta a velocidade da bolinha
    ball.Speed:= ball.Speed + 0.5;
    //move um pouco a bolinha para evitar colisões
    ball.X:= bt + (bt - ball.x);
  end;
 
  //distância da bolinha até a barrinha da AI
  bt:= bar_cp.X - ball.w + (ball.w / 2);
  //verifica se a bolinha colidiu com a barrinha da AI
  if (ball.x > bt) and ((ball.y >= bar_cp.y) and (ball.y + ball.h < bar_cp.Y + bar_cp.h)) then
  begin
    //reflete a bolinha com o mesmo ângulo
    ball.Vx:= -ball.Vx;
    //aumenta a velocidade da bolinha
    ball.Speed:= ball.Speed + 0.5;
    //move um pouco a bolinha para evitar colisões
    ball.x:= bt - (ball.x - bt);
  end;
 
  //verifica se a bolinha saiu à esquerda
  if ball.x < 37 then
  begin
    //ponto da AI
    Inc(score_cp);
    //reinicia o jogo
    InitGame;
  end;
  //verifica se a bolinha saiu à direita
  if ball.X + ball.w > 37 566 then
  begin
    //ponto do jogador
    Inc(score_p1);
    //reinicia o jogo
    InitGame;
  end;
end;

Função que move a bolinha

//função que move a bolinha de acordo com o ângulo e
//a velocidade
procedure ball_move;
begin
  //move a bolinha no eixo x (horizontal)
  ball.x := ball.x + ball.Vx*ball.speed;
  //move a bolinha no eixo y (vertical)
  ball.y := ball.y + ball.Vy*ball.speed;
end;

Função que controla a AI

//função que controla a AI do jogo
procedure bar_cp_handle;
begin
  //se for possível chegar à bolinha nesse frame
  if abs(ball.y - bar_cp.y) <= bar_cp.Speed then
    //move a barrinha para a posição da bolinha
    bar_cp.y:= ball.y
  else
    //do contrário move a barrinha na direção da bolinha
    //mas apenas o que a velocidade permite nesse frame
    bar_cp.y:= bar_cp.y + (bar_cp.speed * (ball.y - bar_cp.y)) / abs(ball.y - bar_cp.y);
 
  //verifica se a barrinha da AI passou dos limites do cenário
  //e coloca ela de volta nos limites do cenário
  if bar_cp.Y + bar_cp.h > 77 366 then
    bar_cp.y:= 77 366 - bar_cp.H;
  if bar_cp.Y < 77 then
    bar_cp.y:= 77;
end;

Função que desenha os pontos dos jogadores usando as imagens dos números do jogo

//função que desenha os pontos dos jogadores usando
//as imagens dos números do jogo
procedure CreateScore(x, y, score: Integer);
var i, d: Integer; //variáveis de controle do laço
    s: string;     //string com a pontuação a desenhar
begin
  //transforma a pontuação em string (para percorrer os caracteres)
  s:= IntToStr(score);
  //o máximo de pontos é 9999 (4 caracteres), se a string com os
  //pontos tiver menos de 4 caracteres preenche o resto com 0
  d:= - Length(s);
  for i := to d - do
  begin
    //desenha a imagem do 0 no backbuffer
    bbuffer.Canvas.Draw(x, y, bitmaps[0]);
    //aumenta a posição x para o próximo caractere
    Inc(x, 24);
  end;
  //desenha os caracteres na string dos pontos
  for I := to Length(s) do
  begin
    //desenha o caractere no backbuffer
    bbuffer.Canvas.Draw(x, y, bitmaps[StrToInt(s[i])]);
    //aumenta a posição x para o próximo caractere
    Inc(x, 24);
  end;
end;

Função que inicia as variáveis do jogo

//Função que inicia o jogo e reseta as variáveis
procedure InitGame;
begin
  //pausa o jogo
  paused:= true;
  //reseta a posição da bolinha
  Ball.X:= 37 + ((566 - Ball.W) / 2);
  Ball.Y:= 77 + ((366 - Ball.H) / 2);
  //reseta a velocidade e direção da bolinha
  Ball.Speed:= 8;
  Ball.Vx:= 0.5;
  Ball.Vy:= 0.5;
  //reseta a posição da barrinha do jogador
  Bar_p1.X:= 57;
  Bar_p1.Y:= 77 + ((366 - bar_p1.H) / 2);
  //reseta a posição da barrinha da AI
  Bar_cp.X:= 577;
  Bar_cp.Y:= bar_p1.Y;
end;

    Bem, precisamos mudar um pouco nossa rotina de criação do jogo (evento Form.OnCreate), pois atualmente tudo o que ela faz é carregar os bitmaps para a memória. Temos que criar o backbuffer e iniciar o jogo também. Então segue abaixo o código completo do OnCreate do form.

procedure TForm1.FormCreate(Sender: TObject);
begin
  //quando o form for criado carregamos as imagens
  LoadGameGraphics;
  //cria e inicializa o backbuffer
  bbuffer:= TBitmap.Create;
  bbuffer.Width:= 640;
  bbuffer.Height:= 480;
  bbuffer.Canvas.Brush.Color:= clBlack;
  //inicializa algumas variáveis da bolinha
  Ball.W:= 15;
  Ball.H:= 15;
  //inicializa algumas variáveis da barrinha do jogador
  Bar_p1.W:= 10;
  Bar_p1.H:= 80;
  //inicializa algumas variáveis da barrinha da AI
  Bar_cp.W:= 10;
  Bar_cp.H:= 80;
  Bar_cp.Speed:= 15;
  //inicia o jogo
  InitGame;
  //habilita o timer do jogo
  Timer1.Enabled:= true;
end;

    Você pode rodar o jogo agora e ver como está ficando. Se fizer isso você deverá ver as imagens desenhadas e um texto escrito “click to start”. Acontece que se você clicar não vai acontecer nada. Simplesmente porque nós não colocamos a rotina para pausar e continuar o jogo. Vamos então implementá-la agora. Para isso vamos usar o evento OnClick do formulário.

//quando o jogador clicar no formulário
//pausa ou continua o jogo
procedure TForm1.FormClick(Sender: TObject);
begin
  paused:= not paused;
end;

    Simples não? Ao invés de usar uma estrutura if…else nós colocamos tudo isso em uma linha usando o operador not para retornar o oposto do valor da variável paused. Agora o jogo inicia direitinho, mas… não é possível jogar porque não implementamos a função com os comandos do jogador. Bem, já que usamos o mouse para controlar o jogo até agora, vamos usálo de novo. Coloque esse código no evento OnMouseMove do formulário.

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  //move a barrinha do jogador para a posição do mouse
  bar_p1.Y:= y;
 
  //verifica se a barrinha do jogador passou dos limites do cenário
  //e coloca ela de volta nos limites do cenário
  if bar_p1.Y + bar_p1.h > 77 366 then
    bar_p1.y:= 77 366 - bar_p1.H;
  if bar_p1.Y < 77 then
    bar_p1.y:= 77;
end;

    Agora o jogo está prontinho. Experimente jogar e ver se consegue vencer a AI do jogo. Se achar que o jogo está muito difícil você pode diminuir um pouco o valor da variável bar_cp.speed. Ou aumentá-la ainda mais para tornar a AI imbatível.

» baixe aqui o código fonte completo

    Bastante códigos hoje ne? No próximo tutorial eu vou explicar um pouco sobre o uso de sons para melhorar os ambientes nos jogos, sem usar bibliotecas externas. Então até lá.





Criando jogos em Delphi – Parte I

17 06 2008

- Introdução -

Este é o primeiro de muitos tutoriais sobre desenvolvimento de jogos que eu pretendo postar neste blog. Aqui vamos tratar de desenvolvimento de jogos em Delphi sem usar nenhuma biblioteca externa (como o DirectX ou SDL), apenas a GDI do Windows.

Já que o assunto central desse tutorial é usar os conhecimentos em Delphi para criar jogos, é bom que você tenha um conhecimento prévio na linguagem.

- Um jogo simples -

Vamos criar um joguinho bem simples. Nesse joguinho haverá apenas uma bolinha percorrendo todo o cenário.

Abra o Delphi e crie um novo projeto. Altere o tamanho do form, coloque 640 de ClientWidth e 480 de ClientHeight. Mude a BorderStyle para bsSingle (assim o usuário não poderá redimensionar o form). E finalmente em BorderIcons deixe apenas biSystemMenu (o botão de fechar) e biMinimize (o botão de minimizar).

Vamos algumas variáveis para armazenar a posição e a direção da nossa bolinha. Eu escolhi o tipo single por que ele pode armazenar algumas casas decimais.

var
  Form1: TForm1;
  ball_x, ball_y, ball_vx, ball_vy: Single;

No OnCreate (assim que o form for criado) do form nós vamos inicializar as variáveis de velocidade.

procedure TForm1.FormCreate(Sender: TObject);
begin
  //movimento horizontal (1/2 pixel para a direita)
  ball_vx:= 2.5;
  //movimento vertical (1/2 pixel para baixo)
  ball_vy:= 2.5;
  //Coloca a bolinha no centro da tela...
  ball_x:= (ClientWidth - 20) / 2;
  ball_y:= (ClientHeight - 20) / 2;
end;

Agora adicione o objeto TTimer no form. Mude a propriedade interval do TTimer para 1. No evento OnTimer deste objeto nós vamos colocar todo o laço principal do jogo (analisar as informações e apresentá-las ao jogador).

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  //verifica se a bolinha colidiu com algum canto da tela...
  //nossa bolinha vai ter 20 x 20 pixels

  if (ball_x < 0) or (ball_x + 20 > ClientWidth) then
    ball_vx:= -ball_vx;
  if (ball_y < 0) or (ball_y + 20 > ClientHeight) then
    ball_vy:= -ball_vy;
 
  //move a bolinha...
  ball_x:= ball_x + ball_vx;
  ball_y:= ball_y + ball_vy;
 
  //limpa a tela anterior...
  Canvas.Brush.Color:= clBlack;
  Canvas.FillRect(Rect(0,0,ClientWidth,ClientHeight));
  //Desenha a bolinha
  //Aqui foi preciso o round porque os pixels são números inteiros
  //(não existe o pixel 20.5, 10.2 ne?

  Canvas.Brush.Color:= clWhite;
  Canvas.Ellipse(Round(ball_x), Round(ball_y), Round(ball_x + 20), Round(ball_y + 20));
end;

Wow! Funciona! Tem uma bolinha batendo na tela, mas… que falhas são essas??? Fiz alguma coisa errada??

Na verdade não, acontece que estamos desenhando diretamente na tela, então todas as etapas do desenho são mostradas para o jogador, por isso essas falhas. Mas se acalmem, existe um modo de contornar isso. Vamos desenhar em outra tela.

Esse recurso se chama BackBuffer (ou OffScreen) e consiste em desenhar todos os elementos do jogo em uma tela virtual na memória (aqui nós vamos usar um bitmap) e depois que tiver desenhado tudo enviar para o vídeo (desenhar no form).

Vamos criar mais uma variável, chamada BackBuffer, que será do tipo TBitmap.

var
  Form1: TForm1;
  ball_x, ball_y, ball_vx, ball_vy: Single;
  backbuffer: TBitmap;

Antes de iniciar o jogo precisamos criar nosso bitmap (do contrário vamos receber um erro ao tentar desenhar nele). No evento OnCreate do form adicione o seguinte código:

  //cria e inicializa o backbuffer...
  backbuffer:= TBitmap.Create;
  backbuffer.width:= ClientWidth;
  backbuffer.height:= ClientHeight;

Quando o jogo terminar nós temos que liberar esse bitmap da memória. Então no evento OnDestroy do form adicione o seguinte código:

procedure TForm1.FormDestroy(Sender: TObject);
begin
  //libera o backbuffer da memória...
  backbuffer.Free;
end;

Como não estamos mais desenhando diretamente no Canvas do form, nossa rotina de limpar a tela e desenhar nossa bolinha vai mudar um pouquinho. Abaixo segue o novo código do evento OnTimer.

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  //verifica se a bolinha colidiu com algum canto da tela...
  //nossa bolinha vai ter 20 x 20 pixels

  if (ball_x < 0) or (ball_x + 20 > ClientWidth) then
    ball_vx:= -ball_vx;
  if (ball_y < 0) or (ball_y + 20 > ClientHeight) then
    ball_vy:= -ball_vy;
 
  //move a bolinha...
  ball_x:= ball_x + ball_vx;
  ball_y:= ball_y + ball_vy;
 
  //Os eventos que seguem se referem ao canvas do backbuffer
  //aqui vamos desenhar todos os elementos visíveis para depois passá-los
  //para a tela

  with backbuffer.Canvas do
  begin
    //muda a cor do pincel para preto
    brush.Color:= clBlack;
    //preenche toda a tela com o pincel atual
    FillRect(Rect(0,0,ClientWidth,ClientHeight));
    //muda a cor do pincel para branco
    brush.Color:= clWhite;
    //desenha a bolinha
    Ellipse(Round(ball_x), Round(ball_y), Round(ball_x + 20), Round(ball_y + 20));
  end;
 
  //depois de desenhar tudo no backbuffer, envia o backbuffer para a tela
  Canvas.Draw(0,0,backbuffer);
end;

Download do código fonte deste tutorial

Nosso primeiro “joguinho” está pronto. A tela continua com algumas falhas porque a rotina de desenho da GDI do Windows não é muito boa, mas vamos usar imagens a partir do próximo tutorial e eliminar um pouco isso.

Na próxima parte vamos criar um pequeno clone do “pong”, aquele joguinho de ping pong da Atari, com gráficos e sons. Algo bem oldschool, mas espero que gostem.

See ya!