UnderCode de casa nova =D

22 03 2009

Pois é, como eu tive que arranjar um host para mim e comprei um domínio, eu resolvi mudar o UnderCode pra http://www.mottaweb.com.br/. Os motivos são simples: Eu posso alterar o layout, adicionar plugins e mais uma infinidade de coisas que não dá pra fazer no WordPress.com

Eu também pretendo postar com mais freqüência aqui, não só coisas relacionadas à GameDev, mas sobre internet e outros inúmeros interesses meus. Aliás, pretendo falar sobre roteiros para games também. ^.-

So, look forward to it ^^





A web 2.0 e suas Rich Internet Applications

10 03 2009

Eu ainda me lembro do tempo em que RIA era um sinônimo para Flash. Hoje em dia, porém, existem diversos frameworks além do antigo (e ainda largamente usado) software da Macromedia (agora da Adobe). Microsoft e Adobe e Sun, também entraram nesta disputa com seus aplicativos para desenvolver aplicações para web com características de aplicativos desktop. M$ Silverlight, Adobe Air, e JavaFX são programas para gerar aplicativos ricos para internet, mas que ainda precisam de plugins no cliente para rodar suas aplicações.

Okay, isso não é surpresa para ninguém (ao menos, creio eu, não entre desenvolvedores/curiosos web), e não é sobre isso que eu queria falar também. Eu tenho alguns projetos de aplicações para internet em mente, e não gostaria de usar nenhum desses frameworks. Até porque, eu não estou a fim de aprender nenhuma nova linguagem atualmente (e nem tenho tempo para isso).

Enfim, como eu já tinha decidido usar Ajax e Javascript, eu estava procurando na net por alguns frameworks para facilitar minha vida (eu estava usando Prototype + Scriptaculous no projeto), e um framework me chamou muito a atenção: Pyjamas. Um framework para desenvolver RIA usando Python (como o GWT da Google).

Vou dar uma olhada nele e depois posto algumas coisinhas aqui ^-^





Criando jogos em Delphi – Parte IV

9 03 2009

- Animações -

    Quando várias imagens são mostradas em um curto intervalo de tempo, nosso cérebro interpreta isso como movimento. À freqüência de imagens mostradas damos o nome de FPS (Frames per Second, em inglês). Filmes normalmente utilizam 30 quadros (frames) por segundo, enquanto os jogos mais atuais utilizam 60 quadros. Note que 15 é o mínimo de quadros aceitável em uma animação, mas quanto mais quadros por segundo uma animação tiver, mais suave ela será.

    Teoria básica apresentada, vamos ao que interessa. Para montarmos uma animação no Delphi, tudo o que precisaremos é mostrar uma seqüência de imagens na tela. Mas como mostrar essa seqüência? Simples. Vamos usar variáveis para recordar qual quadro devemos mostrar na tela, e por quanto tempo ele ainda deve ser mostrado na tela.

- A classe TAnimation-

    Para controlar a animação vamos criar uma classe, nesse caso a TAnimation, que derivará apenas de TObject (como todas as classes em Delphi derivam de TObject, nós podemos deixar essa herança implícita). Essa classe precisa guardar uma matriz contendo as imagens de cada quadro, o quadro que está sendo mostrado atualmente e o tempo de espera entre um quadro e outro. Além disso, ela precisa de uma rotina que atualize a animação. Essa rotina recebe o tempo decorrido e atualiza a variável que guarda o tempo que o quadro atual foi exibido. Se esse tempo for maior do que o tempo de espera, alteramos o quadro da animação. Por fim, precisamos de uma rotina para desenhar o quadro atual na tela. Sabendo disso, podemos criar a nossa classe.

type
  //Aqui nós declaramos a classe da nossa explosão
  TAnimation = class
  private
    fFrame: Integer; //o quadro atual da animação
    fDelay: Integer; //o tempo de espera entre um quadro e outro (em ms)
    fDelayed: Double; //o tempo que o frame atual foi exibido na tela (em ms)
    fSpeed: Double; //a velocidade da animação
    fX, fY: Double; //a posição da animação no formulário
    fFrames: array of TBitmap; //os frames da animação
 
    procedure setFrame(value: Integer);
    procedure setDelayed(value: Double);
    procedure setFrameImage(Index: Integer; value: TBitmap);
    function getFrame(Index: Integer): TBitmap;
  public
    property Frame: Integer read fFrame write setFrame;
    property Delay: Integer read fDelay write fDelay;
    property Delayed: Double read fDelayed write setDelayed;
    property Speed: Double read fSpeed write fSpeed;
    property X: Double read fX write fX;
    property Y: Double read fY write fY;
    property Image[Index: Integer]: TBitmap read getFrame write SetFrameImage;
 
    constructor Create;
    destructor Destroy; override;
    //desenha o quadro atual na tela
    procedure Paint(canvas: TCanvas);
    //atualiza a animação
    procedure Update(lag: Integer);
    //seta os frames a partir de uma array de bitmaps
    procedure SetFrames(values: array of TBitmap);
  end;
 
implementation
 
// --------------------------------------------------------------------------
// TAnimation
// -----------
 
// Inicializa a classe
constructor TAnimation.Create;
begin
  inherited Create;
  fFrame:= 0;
  fDelay:= 0;
  fDelayed:= 0;
  fX:= 0;
  fY:= 0;
  fSpeed:= 1;
  setLength(fFrames, 0);
end;
 
// Destrói a instancia
destructor TAnimation.Destroy;
var i: Integer;
begin
  for I := 0 to High(fFrames) do
    FreeAndNil(fFrames[I]);
  setLength(fFrames, 0);
  FreeAndNil(fFrames);
  inherited Destroy;
end;
 
// Retorna a imagem do frame atual
// Gera um erro se o índice (Index) estiver fora dos limites da matriz de
// frames.
function TAnimation.getFrame(Index: Integer): TBitmap;
begin
  if (Index < 0) or (Index > High(fFrames))  then
    raise Exception.Create('Índice da animação fora da faixa permitida.');
  Result:= fFrames[Index];
end;
 
// Desenha o frame atual no canvas
procedure TAnimation.Paint(canvas: TCanvas);
begin
  if fFrame < 0 then
    exit;
  canvas.Draw(Round(fX), Round(fY), fFrames[fFrame]);
end;
 
// Modifica o tempo que o frame atual foi exibida
// Se este tempo for maior ou igual ao tempo que precisamos esperar, passa
// para o próximo frame.
procedure TAnimation.setDelayed(value: Double);
var incFrame: Integer;
begin
  fDelayed:= value;
  if fDelayed >= fDelay then
  begin
    incFrame := floor(fDelayed / fDelay);
    fDelayed:= floor(fDelayed) mod fDelay;
    setFrame(fFrame + incFrame);
  end;
end;
 
// Modifica o frame da animação
procedure TAnimation.setFrame(value: Integer);
begin
  fFrame:= value;
  if fFrame < 0 then
    fFrame:= 0;
  if fFrame > High(fFrames) then
  begin
    fFrame:= -1;
    fSpeed:= 0;
  end;
end;
 
// Altera a imagem de um dos frames da matriz
procedure TAnimation.setFrameImage(Index: Integer; value: TBitmap);
begin
  if (Index < 0) or (Index > High(fFrames)) then
    raise Exception.Create('Índice da animação fora da faixa permitida.');
  fFrames[Index]:= value;
end;
 
// Seta os frames da matriz pela matriz de bitmaps passada
procedure TAnimation.SetFrames(values: array of TBitmap);
var
  I: Integer;
begin
  setLength(fFrames, Length(values));
  for I := 0 to High(values) do
  begin
    fFrames[I]:= TBitmap.Create;
    fFrames[I].Assign(values[I]);
  end;
end;
 
// Atualiza a animação
procedure TAnimation.Update(lag: Integer);
begin
  setDelayed(fDelayed + (lag * fSpeed));
end;

    Nossa classe agora está criada. É uma classe que pode controlar qualquer animação usando imagens (quadros) no formato bitmap. Para esse tutorial, vamos criar um pequeno programa que mostra várias animações de explosão em lugares aleatórios da tela.

    Antes de continuar, faça o download do código fonte e imagens da animação aqui.

    Agora que você já tem as imagens, vai perceber que elas estão no formato jpeg (aliás, um formato que não deve ser usado para animações/sprites), que eu usei aqui só pra reduzir o tamanho do arquivo. =D Mas, enfim, como as imagens estão em jpeg, temos três alternativas: A primeira é converter cada imagem manualmente para bitmap, já que nossa classe só aceita esse formato. A segunda, é alterar nossa classe para funcionar com imagems em jpeg ao invés de bitmap, mas como eu disse, jpeg deve ser evitado em animações e sprites. A terceira, é criar uma função para converter as imagens de jpeg para bitmap. Vamos usar a terceira, já que eu sou um programador garoto preguiçoso demais para converter as imagens manualmente ^.-

//transforma JPEG em BMP
function jpeg2bmp(imagem: TJPEGImage): TBitmap;
 
implementation
 
function jpeg2bmp(imagem: TJPEGImage): TBitmap;
begin
  // instancia o bitmap
  result:= TBitmap.Create;
  // altera as propriedades (largura/altura) para desenhar a imagem jpeg nele
  result.Width:= imagem.Width;
  result.Height:= imagem.Height;
  // desenha a imagem jpeg no canvas
  result.Canvas.Draw(0, 0, imagem);
end;

    Agora nós temos imagens que servem como quadros em nossa classe de animação, podemos começar a criar nosso programa. Primeiro, vamos precisar de algumas variáveis para controlar as animações e, claro, nosso backbuffer.

var
  Form1: TForm1;
  // variável do backbuffer
  bbuffer: TBitmap;
 
  // array dos frames usados na animação
  // Note que essa variável global só foi adicionada para não carregar
  // a mesma animação do HD várias vezes.
  AnimFrames: array of TBitmap;
 
  // Número de frames mostrados.
  Frames: Integer;
  // Número de frames por segundo.
  FPS: Integer = -1;
  // Número de milisegundos para cálculo de FPS.
  FPSTime: Integer;
 
  // Uma lista das animações ativas
  RunningAnims: TList;
 
  // O tempo gasto na última atualização da tela
  // Necessário para que as animações rodem em uma velocidade constante,
  // independente da velocidade da máquina
  OldTime: Integer = -1;

    AnimFrames é uma variável útil, já que ela vai permitir carregar as imagens do HD no formato jpeg e guardá-las no formato bitmap, assim nós só precisamos convertê-las uma vez. Já RunningAnims é uma lista com ponteiros para instâncias das animações mostradas na tela. Enfim, nós carregamos as imagens e inicializamos essas variáveis quando o form é criado, e liberamos os recursos quando ele é destruído.

    Além disso, precisaremos de 2 objetos TTimer. Um com o intervalo de 1 milisegundo, que vai controlar a atualização das explosões, e o outro com intervalo de 100 milisegundos, que vai criar animações aleatoriamente na tela.

procedure TForm1.FormCreate(Sender: TObject);
var
  I: Integer;
  image: TJPEGImage;
begin
  randomize;
 
  // Cria o backbuffer
  bbuffer:= TBitmap.Create;
  bbuffer.Width:= 640;
  bbuffer.Height:= 480;
  bbuffer.Canvas.CopyMode:= cmSrcPaint;
  bbuffer.Canvas.Brush.Color:= clBlack;
 
  // Carrega a animação do HD
  setLength(AnimFrames, 36);
  for I := 0 to 35 do
  begin
    image:= TJPEGImage.Create;
    image.LoadFromFile('explosion/screen' + IntToStr(I+1) + '.jpg');
    AnimFrames[I]:= jpeg2bmp(image);
    FreeAndNil(image); // libera a imagem da memória
  end;
 
  // Inicializa a lista de animações
  RunningAnims:= TList.Create;
 
  // Habilita o timer
  Timer1.Enabled:= True;
  Timer2.Enabled:= True;
end;
 
// Destrói o form e libera os recursos
procedure TForm1.FormDestroy(Sender: TObject);
var i: Integer;
begin
  FreeAndNil(bbuffer);
  for I := 0 to 35 do
    FreeAndNil(AnimFrames[I]);
end;

    O modo de cópia cmSrcPaint em conjunto com o fundo preto, faz com que a cor preta das imagens desenhadas no backbuffer fique transparente.

    Agora que inicializamos tudo, falta criar as animações. Então no evento OnTimer do Timer2 vamos colocar um pequeno código que cria animações aleatórias pela tela.

// cria animações em intervalos regulares em posições aleatórias
procedure TForm1.Timer2Timer(Sender: TObject);
var anim: TAnimation;
begin
  // instancia a animação
  anim:= TAnimation.Create;
  // coloca a animação em um lugar aleatório da tela
  anim.X:= Random(ClientWidth);
  anim.Y:= Random(ClientHeight);
  // carrega os frames da matriz AnimFrames na animação
  anim.SetFrames(AnimFrames);
  // seta um tempo de espera aleatório
  anim.Delay:= random(10)+20;
  // adiciona o ponteiro da instância na lista de animações ativas
  RunningAnims.Add(anim);
end;

    Por fim, precisamos escrever o código do evento OnTimer do Timer1, que irá controlar todas as animações ativas na tela. Isso é possível pois temos uma lista de todas as animações ativas guardadas em RunningAnims, então basta percorrer esta lista e atualizar as animações nela.

// Timer que controla a atualização da tela
procedure TForm1.Timer1Timer(Sender: TObject);
var
  I: Integer;
  at: TAnimation;
  Lag: Integer;
  t: Integer;
begin
  // como o timer tem um intervalo de 1 milisegundo, esperamos que a tela
  // seja atualizada neste intervalo, mas se demorarmos mais para atualizar
  // a tela, verifica quanto tempo se passou para que a animação não seja
  // prejudicada
 
  t:= getTickCount;
  if OldTime < 0 then
    OldTime:= t;
  Lag:= (t - OldTime);
  OldTime:= t;
  if Lag < 0 then
    Lag:= 1;
  I:= 0;
 
  Inc(FPSTime, Lag);
 
  if FPSTime > 1000 then
  begin
    FPSTime:= FPSTime - 1000;
    FPS:= Frames;
    Frames:= 0;
  end;
 
  Inc(Frames);
 
  if FPS > -1 then
    caption:= 'Parte IV - Animações - FPS: ' +  IntToStr(FPS);
 
  // limpa o canvas
  bbuffer.Canvas.FillRect(Rect(0,0,640,480));
 
  // desenha todas as animações da lista
  while I < RunningAnims.Count-1 do
  begin
    at:= TAnimation(RunningAnims.Items[I]);
 
    // se a animação tiver acabado, retira ela da lista
    if at.fFrame = -1 then
    begin
      RunningAnims.Remove(at);
      FreeAndNil(at);
    end
 
    // do contrário, atualiza e desenha a animação
    else begin
      at.Update(lag);
      at.Paint(bbuffer.Canvas);
      Inc(I);
    end;
  end;
 
  // envia o backbuffer pra tela
  Canvas.Draw(0,0, bbuffer);
end;

    Para ver o efeito das animações basta rodar o programa e voílà! Você deve ver várias animações sendo criadas aleatóriamente pela tela em intervalos regulares.

- Conclusão -

    Esse foi um pequeno tutorial para desenvolvimento de jogos em Delphi, utilizando apenas a GDI do Windows. Claro, você não deve utilizar a GDI do Windows para desenvolver jogos, mas o conceito aqui se aplica a qualquer outra framework. Eu pretendo escrever mais tutoriais de GameDev em Delphi, utilizando frameworks como Asphyre e SDL (apesar de estar um pouco mais ocupado agora i.i). Até lá o(^-^)/”





[in]utilidades

28 11 2008

Eu estou aprendendo java agora e estou gostando bastante (sim, eu gosto de linguagens tipadas — nem tanto assim também — e que tenham padrões) apesar de até agora não ter feito nada realmente legal. Bem, paciência, pelo menos o logo de java me anima lol

Quanto ao resto do tutorial de desenvolvimento de jogos em Delphi, ele sai sim, aliás, eu estou trabalhando no próximo agora, mas como eu estou estudando quase o dia inteiro (não, não é java, eu estou estudando pro vestiba mesmo) i.i, deve demorar um pouquinho.

E pensar que eu tenho que comprar um novo HD >.< já que o HD do outro pc definitivamente morreu, e o HD deste PC está praticamente lotado (músicas + coisinhas relacionadas à programação + Visual Novels lol aliás, a maior parte do HD está ocupado por visual novels, eu acho que estou ficando viciado nisso). Eu preciso de 1TB, ia realmente resolver a maioria dos meus problemas *-*





Tile Mapping

8 10 2008

    Técnicas de tile mapping são bastante utilizados em jogos que precisem usar imagens muito grandes em dispositivos com uma memória reduzida. Neste artigo eu vou falar um pouco sobre a estrutura destes arquivos e sua respectiva implementação em Delphi (Object Pascal).

- O que é tile mapping? -

    Tile Mapping é uma técnica que consiste em formar imagens 2d grandes a partir de blocos menores, chamados tiles. Uma imagem formada por esta técnica tende a ter várias partes que se repetem, portanto seria desperdício alocar memória para esta mesma parte várias vezes.

    Tiles são, além de blocos menores de imagem, estruturas que guardam várias informações sobre a imagem que será desenhada, como o tipo de bloco que ele representa, se ele é um obstáculo ou não, se ele possui animações, etc.

    Você deve estar acostumado a ver jogos usando esta técnica em RPGs, jogos de plataforma 2d e mesmo jogos desenvolvidos para plataformas móveis (GB, GBA, NDS, celulares, …).

    Exemplo de jogo usando técnicas de tile mapping:


Mario é um exemplo de jogo que utiliza a técnica de tile mapping

- Estrutura -

    Como foi dito antes, a técnica de tile mapping consiste em formar uma imagem grande a partir de blocos menores de imagem. Para isso nós usamos uma matriz bidimensional, o tile map, que indica qual posição cada um desses blocos ocupa.

    A estrutura abaixo representa um tipo comum de tilemap:

  //essas flags são usadas para determinar algumas
  //informações sobre o tile
  TTileFlag = (tfObstaculo, tfPlataforma, tfAgua, tfEspinho, tfFogo);
 
  //aqui eu usei o "set" porque é mais conveniente.
  //mas você pode usar inteiros também.
  TTileFlags = set of TTileFlag;
 
  //Estrutura dos tiles. Varia muito de acordo com as
  //funções que você quer que seu mapa tenha. Este
  //é apenas um exemplo básico 
  TTile = record
    //o índice da imagem que será desenhada
    Imagem: Integer;
 
    //outras informações sobre o tile
    Flags: TTileFlags;
  end;
 
  //Estrutura do mapa de tiles
  TTileMap = record
    //Matriz bidimensional que indica a posição de
    //cada tile no mapa
    tiles: array of array of TTile;
 
    //O tileset que vamos usar para guardar as imagens
    //dos tiles.
    TileSet: TBitmap;
 
    //A largura e altura de cada tile armazenado no
    //tileset.
    Width, Height: Integer;
  end;

    Como vocês podem ver, as imagens não são armazenadas diretamente nos tiles. Isto acontece porque vários tiles diferentes costumam usar a mesma imagem, neste caso é melhor colocar todas as imagens em um outro lugar e usar apenas um índice para identificá-las. Para isso usamos os tilesets (conjunto de tiles).

- Tile sets -

    Os tilesets normalmente são um grande arquivo bitmap (ou outro formato de imagem) contendo as imagens. Como todos os tiles têm o mesmo tamanho não há problema em colocá-los todos juntos em uma única e grande imagem.

    Dê uma olhada nesta imagem:


Um tileset utilizado por RPGs

    Quando o programa precisa desenhar umtile, ele pega o índice da imagem, procura por esta imagem no tileset e copia a imagem do tileset para a tela.

    O código abaixo é apenas um exemplo de uma rotina de desenho de tilemaps.

  //percorre todas as linhas da matriz de tiles 
  //(tilemap)
  for Y:= to High(TileMap.tiles) do
  begin
    //percorre todas as colunas da linha Y da
    //matriz de tiles (tilemap)
    for X := to High(TileMap.Tiles[Y]) do
    begin
      //pega a distância do canto esquerdo no tileset
      //note que aqui as imagens usam um índice que
      //começa em 1
      Ix:= ((TileMap.Tiles[Y][X].Imagem - 1mod TILESET_COLS) * TileMap.Width;
 
      //pega a distância do canto direito do tileset
      Iy:= ((TileMap.Tiles[Y][X].Imagem div TILESET_COLS) - 1) * TileMap.Height;
 
      //variável com a área a ser copiada do tileset
      TileRect:= Rect(Ix, Iy, Ix + TileMap.Width, Iy + TileMap.Height);
 
      //variável com a área de destino na tela
      DestRect:= Rect(X * TileMap.Width, Y * TileMap.Height, (X+1) * TileMap.Width,
        (Y+1) * TileMap.Height);
 
      //copia do tileset para a tela...
      Canvas.CopyRect(DestRect, TileMap.TileSet.Canvas, TileRect);
    end;
  end;

    As coisas devem começar a fazer sentido agora. Quando o programa vai desenhar o mapa, ele percorre a matriz, pega a imagem correspondente a cada tile no tileset e copia para a tela. Mas imaginem um mapa de 100 x 100, ou 1000 x 1000, ou até maior! Seria muito demorado percorrer um mapa deste tamanho e seria desperdício de tempo e recursos, afinal, nós não precisamos percorrer um mapa inteiro de 100 x 100 tiles se em nossa tela só cabem 10 x 10 tiles.

    Então vamos otimizar um pouquinho o código, percorrendo apenas os tiles que estarão visíveis na tela.

  //calcula o número de tiles visíveis horizontalmente
  //Ceil faz com que o número seja arredondado para cima
  //1.3 vira 2, 7.1 vira 8, etc.
  TilesX:= Ceil(ClientWidth / TileMap.Width);
  //calcula o número de tiles visíveis verticalmente
  TilesY:= Ceil(ClientHeight / TileMap.Height);
 
  //ViewX e ViewY são os pontos de onde o mapa começará
  //a ser desenhado.
 
  //percorre as linhas visíveis da matriz de tiles
  //(tilemap)
  for Y:= ViewY to ViewX + TilesX do
  begin
    //percorre as colunas visíveis da linha Y da matriz
    //de tiles (tilemap)
    for X := ViewX to ViewY + TilesY do
    begin
      //pega a distância do canto esquerdo no tileset
      //note que aqui as imagens usam um índice que
      //começa em 1
      Ix:= ((TileMap.Tiles[Y][X].Imagem - 1mod TILESET_COLS) * TileMap.Width;
 
      //pega a distância do canto direito do tileset
      Iy:= ((TileMap.Tiles[Y][X].Imagem div TILESET_COLS) - 1) * TileMap.Height;
 
      //variável com a área a ser copiada do tileset
      TileRect:= Rect(Ix, Iy, Ix + TileMap.Width, Iy + TileMap.Height);
 
      //variável com a área de destino na tela
      DestRect:= Rect(X * TileMap.Width, Y * TileMap.Height, (X+1) * TileMap.Width,
        (Y+1) * TileMap.Height);
 
      //copia do tileset para a tela...
      Canvas.CopyRect(DestRect, TileMap.TileSet.Canvas, TileRect);
    end;
  end;

    Como vocês puderam ver, o código percorre apenas os tiles visíveis e os desenha na tela.

- Finalizando –

    É isso por hoje, abaixo eu deixo um pequeno código fonte em Delphi de com desenhar esses mapas usando a GDI do windows.

Download do código fonte

    Pretendo escrever outros artigos sobre outros tipos de mapas (como os isométricos) e aprofundar um pouco nos tilemaps, usando diversos layers (camadas).