Sabtu, 12 Januari 2013

Animasi 2D dengan Direct3D Bagian 3

Animasi 2D dengan Direct3D Bagian 3


Tutorial bagaimana membuat animasi 2D dengan Direct3D. Disini akan dibahas bagaimana membuat animasi background scrolling.

Prasyarat

Artikel ini adalah sambungan Animasi 2D dengan Direct3D Bagian 1 dan Animasi 2D dengan Direct3D Bagian 2. Saya sarankan anda membaca artikel tersebut terlebih dahulu.

Pendahuluan

OK... kita lanjutkan dengan topic berikutnya, masih mengenai animasi 2D tentunya. Pada contoh aplikasi yang kita buat sebelumnya, karakter selalu digambar pada background polos. Pada game, background seperti ini tentunya sangat membosankan. Game 2D biasanya memiliki background yang discroll otomatis mengikuti pergerakan karakter dalam game. Pertanyaannya sekarang, bagaimana menampilkan background yang dapat discroll tersebut?
Sebelum menjawab partanyaan tersebut, kita perlu sedikit mengulas beberapa isu yang sedikit banyak akan mempengaruhi bagaimana desain background scroller yang akan kita buat.

Memory, barang mewah yang tidak selalu kita punya.

Background pada sebuah game terutama game-game bertipe petualangan, biasanya selalu lebih besar dari area layar, guna memberikan pemain kebebasan untuk mengekplorasi suatu level game. Jika seluruh background yang besar tersebut kita, simpan dalam sebuah blok memori yang besar (baik itu system RAM atau video RAM), ada kemungkinan memori bebas yang tersedia tidak cukup besar untuk menampung data seluruh background tersebut.
Kita akan memisah background menjadi keping-keping background yang lebih kecil yang nantinya akan kita tampilkan secara tiled. Dengan menggunakan keping-keping yang lebih kecil, peluang tersedianya memori menjadi lebih terbuka.

Keterbatasan Ukuran Texture

Beberapa VGA card hanya mampu menampilkan texture dengan ukuran dua pangkat n dan juga harus sama sisi, sehingga ukuran tekstur yang diijinkan biasanya 1x1, 2x2, 4x4, 8x8, 16x16, 32x32 dan seterusnya. VGA card lama semacam voodoo, memiliki keterbatasan maksmum ukuran teksture 256x256, sedangkan VGA card terbaru biasanya sanggup menyimpan teksture berukuran maksimum 1024x1024.
Pada aplikasi yang kita buat sebelumnya, kita menciptakan teksture menggunakan fungsi D3DXCreateTextureFromFileEx, fungsi ini (seperti juga fungsi D3DXCreateTexture*** lainnya) menciptakan tekstur dengan melakukan pengecekan terhadap ukuran teskture yang diijinkan oleh VGA card. Ukuran tekstur yang kita inginkan biasanya akan dibulatkan ke atas menjadi bilangan dua pangkat n terdekat, sehingga misalnya kita menciptakan texture dari gambar berukuran 120x60, maka tekstur yang kita buat sangat besar kemungkinannya akan dibulatkan menjadi tekstur berukuran 128x128, dimana selain data gambar sebesar 120x60, sisanya akan diisi 0. Tentunya hal ini sangat membuang-buang memori.
Jika kita beruntung, pemain mungkin memiliki VGA card yang mendukung tekstur dengan ukuran sembarang, sehingga tidak ada space memori terbuang sia-sia, namun tentunya kita sebagai developer aplikasi harus siap dengan kemungkinan terburuk.
Oleh karena itu untuk meminimalkan memori yang terbuang percuma. Kita akan mememecah-mecah keping background menjadi berukuran nxn dimana n adalah bilangan dua pangkat n. Untuk background scroller yang kita buat, sengaja dipilih ukuran 64x64, Ukuran ini tidak terlalu besar dan didukung oleh semua VGA card. Jika anda punya preference lain, bisa saja menggantinya.

Desain Background Scroller

Sebuah background akan dienkapsulasi dalam sebuah kelas bernama TBackground yang diturunkan dari T3DObject. Kita perlu meng-override metode Draw, dengan kode untuk manggambar keeping-keping background.
Karena background terdiri atas keping-keping kecil, kita membutuhkan instance TSpriteCollection dan juga TTextureCollection untuk menyimpan keping dan tekstur masing-masing keping.
Untuk metode public selain metode Draw dan Init yang dioverride dari T3DObject,TBackground akan memiliki constructor Create dan destructor Destroy, yang akan kita isi dengan kode untuk alokasi dan dealokasi instance TSpriteCollection dan TTextureCollection. Terakhir TBackground akan kita lengkapi dengan metode LoadFromFile untuk membaca file gambar background dengan format yang didukung sesuai format yang disupport D3DX. LoadFromFile akan otomatis memecah-mecah file gambar menjadi keping-keping tekstur.
TBackground akan kita lengkapi dengan property Translation, X, dan Y yang akan kita pergunakan untuk mengubah posisi background. Untuk dapat mengakses Sprite collection dan Texture kita tambahkan property SpriteCollection dan TextureCollection.

Desain Background Collection

Pada beberapa game 2D, background yang digunakan tidak cuma satu, dimana background-bakground ini ditumpuk dan digerakkan dengan kecepatan yang berbeda-beda untuk menghasilkan efek kedalaman pada game 2D.Kita juga akan mengimplementasikan fitur tersebut. Agar kita dapat mengatur background-background ini dengan mudah, background akan kita simpan dalam sebuah instance collection yang kita beri nama TBackgroundCollection. Kelas ini akan diturunkan dari T3DCollection, karena kita membutuhkan property Engine milik T3DCollection.
Ok kita buat implementasi TBackground. Disini hanya akan dijelaskan dua fitur utama TBackground yakni loading file gambar dan memecahnya menjadi keping-keping tekstur serta menggambar keping-keping tersebut.

Loading Gambar dan Memecah Gambar menjadi Tekstur-tekstur

Sebenarnya proses ini akan lebih mudah bila gambar background sudah dipecah-pecah menjadi potongan gambar-gambar kecil menggunakan tool editing gambar semacam Adobe Photoshop. Namun kecepatan loading file kecil-kecil yang banyak lebih lambat dibandingkan dengan loading sebuah file besar, selain itu adakalanya kita tidak bisa atau malas mengedit gambar tersebut. Metode LoadFromFile ditujukan untuk mengatasi hal ini. LoadFromFile otomatis akan memecah-mecah background yang besar menjadi kepingan tektur-tekstur yang lebih kecil.
secara umum, langkahnya adalah sebagai berikut:
  • 1.Ambil lebar dan tinggi gambar. Hitung jumlah keping yang diperlukan
  • 2.Load seluruh gambar ke temporary surface (IDirect3DSurface8).
  • 3.looping untuk tiap-tiap keping,
    • -kita buat tekstur berukuran 64x64,
    • -ambil surface tekstur
    • -Kopi sebagian temporary surface ke surface tekstur
Catatan: Surface adalah representasi blok data di memori. Definsi surface pada Direct3D mirip dengan definisi surface pada DirectDraw. Bedanya pada Direct3D, surface bertipe IDirect3DSurface8, sedangkan pada DirectDraw IDirectDrawSurface.
Untuk menciptakan surface kita menggunakan IDirect3DDevice8.CreateImageSurface
function CreateImageSurface(const Width, Height : Cardinal;
                   const Format : TD3DFormat;
                  out ppSurface : IDirect3DSurface8) : HResult; stdcall;
Untuk loading file gambar ke surface kita menggunakan fungsi D3DXLoadSurfaceFromFile(),
function D3DXLoadSurfaceFromFile(const pDestSurface : IDirect3DSurface8;
const pDestPalette : PPaletteEntry; const pDestRect : PRect;
const pSrcFile : PAnsiChar; const pSrcRect : PRect;
const Filter : LongWord; const ColorKey :TD3DColor;
pSrcInfo : PD3DXImageInfo) : HResult; stdcall;
Parameter:
  • pDestSurface adalah surface yang menerima data gambar
  • pDestPalette adalah pallete gambar, karena kita tidak menggunakan mode 8 bit, kita bisa isi dengan nil
  • pDestRect adalah target rectangle, isi nil bila kita ingin mengkopi gambar ke seluruh surface.
  • pSrcFile adalah target rectangle, isi nil bila kita ingin mengkopi gambar ke seluruh surface.
Untuk mengkopi surface ke surface lain kita bisa menggunakan fungsi D3DXLoadSurfaceFromSurface.
Langkah 1 akan dienkapsulasi dalam fungsi bernama GetImgInfo. Fungsi ini sebenarnya tergolong quick hack guna mendapatkan informasi gambar.
Caranya adalah kita buat surface bohong-bohongan dengan ukuran 2x2 pixel (ukurannya bebas tidak harus 2x2), lalu memanggil D3DXLoadSurfaceFromFile, untuk membaca gambar ke surface. D3DXLoadSurfaceFromFile mengembalikan informasi gambar di pSrcInfo, parameter inilah yang kita pergunakan.
Berikut ini adalah implementasi langkah untuk mendapatkan informasi gambar.
function TBackground.GetImgInfo(const filename: string): 

TD3DXImageInfo;

var adummy:IDirect3DSurface8;
    adummyRect:TRect;
begin
  ZeroMemory(@result,sizeof(TD3DXImageInfo));

  if (FCollection.Engine<>nil) and
     (FCollection.Engine.Direct3DDevice<>nil) then
  begin
    FCollection.Engine.Direct3DDevice.CreateImageSurface(2,2,
                                       D3DFMT_R5G6B5,adummy);
    if adummy<>nil then
    begin
      adummyRect:=Rect(0,0,1,1);
      D3DXLoadSurfaceFromFile(adummy,nil,
                              @adummyRect,
                              PChar(filename),
                              @adummyRect,
                              D3DX_FILTER_NONE,
                              0,
                              @result);
    end;
  end;

end;
Setelah kita mendapatkan informasi gambar, kita buat temporary surface yang kita pergunakan menampung gambar.
function TBackground.LoadSurfaceFromFile(
  const filename: string): IDirect3DSurface8;
var imgInfo:TD3DXImageInfo;
begin
  result:=nil;
  if (FCollection.Engine<>nil) and
     (FCollection.Engine.Direct3DDevice<>nil) then
  begin
    imgInfo:=GetImgInfo(filename);

    FWidth:=imgInfo.Width;
    FHeight:=imgInfo.Height;

    FNumSpriteX:=imgInfo.Width div BGRSIZE;
    FWidthExt:=imgInfo.Width mod BGRSIZE;
    if FWidthExt<>0 then
      inc(FNumSpriteX);

    FNumSpriteY:=imgInfo.Height div BGRSIZE;
    FHeightExt:=imgInfo.Height mod BGRSIZE;
    if FHeightExt<>0 then
      inc(FNumSpriteY);

    FCollection.Engine.Direct3DDevice.CreateImageSurface(
                                         imgInfo.Width,
                                         imgInfo.Height,
                                         D3DFMT_A8R8G8B8,
                                         result);
    if result<>nil then
    begin
      D3DXLoadSurfaceFromFile(result,
                              nil,
                              nil,
                              PChar(filename),
                              nil,
                              D3DX_FILTER_NONE,
                              FColorKey,
                              nil);
    end;
  end;

end;
Langkah-langkah yang dijalankan pada fungsi diatas adalah mendpatkan image info dengan memanggil fungsi GetImgInfo. setelah itu jumlah keping yang diperlukan dihitung. Berdasarkan lebar dan tinggi image kita buat surface yang berukuran sama. Tidak seperti tekstur, surface tidak memiliki batasan dalam hal ukuran lebar dan tinggi. Format surface kita tetapkan bertipe 32 bit (A=8 bit,R=8 bit B=8 bit, G=8 bit), jika anda punya preference lain, bisa saja menggantinya.
Jika surface berhasil dibuat, maka kita panggil D3DXLoadSurfaceFromFile unttuk membaca gambar ke surface.
Langkah ke-3 akan kita enkapsulasi menjadi sebuah metode LoadFromSurface. Karena LoadFromSurface ini fungsinya mengkopi surface ke tekstur tentunya sangat tepat bila diletakkan pada kelas TTexture.
procedure TTexture.LoadFromSurface(const srcSurface: IDirect3DSurface8;
  const srcRect: PRect);

var aTexCollection:TTextureCollection;
    surfDesc:TD3DSurface_Desc;
    texSurface:IDirect3DSurface8;
begin
  aTexCollection:=Collection as TTextureCollection;
  if (aTexCollection.Engine<>nil) and
     (aTexCollection.Engine.Direct3DDevice<>nil) and
     (srcSurface<>nil) then
  begin
    srcSurface.GetDesc(surfDesc);

    FWidth:=srcRect.Right-srcRect.Left;
    FHeight:=srcRect.Bottom-srcRect.Top;

    D3DXCreateTexture(aTexCollection.Engine.Direct3DDevice,
                      FWidth,FHeight,
                      0,
                      surfDesc.Usage,
                      surfDesc.Format,
                      D3DPOOL_MANAGED,
                      FTextureObj);

    FTextureObj.GetSurfaceLevel(0,texSurface);
    D3DXLoadSurfaceFromSurface(texSurface,
                               nil,
                               nil,
                               srcSurface,
                               nil,
                               srcRect,
                               D3DX_FILTER_NONE,
                               FColorKey);
  end;

end;
Pemanggilan D3DXCreateTexture digunakan untuk menciptakan sebuah tekstur kosong, Selanjutnya kita ambil surface yang ada pada mip level 0.
Catatan:Tekstur tidak selalu terdiri atas satu gambar saja, seringkali teksture terdiri atas satu atau lebih gambar dengan resolusi yang berbeda-beda (mipmap). Level 0 adalah gambar dengan detail paling jelas.
selanjutnya Surface sumber kita copy ke texSurface, dengan area yang kita copy ditentukan oleh srcRect. OK berikut ini adalah implementasi lengkap metode LoadFromFile.
Untuk tiap-tiap keping background, kita susun source rectangle yang bersesuaian dengan posisi keping. Jika keping adalah keping terakhir pada baris atau kolom, kita cek apakah lebar/tingginya merupakan kelipatan BGRSIZE, jika tidak, maka lebar/tinggi sisa yang ada pada FWidthExt atau FHeightExt kita pergunakan untuk menyusun rectangle sumber, jika kelipatan BGRSIZE, lebar/tinggi keping kita set sama dengan BGRSIZE (BGRSIZE=64)
procedure TBackground.LoadFromFile(const filename: string);

var asprite:TSprite;
    aTexture:TTexture;
    atmpSurface:IDirect3DSurface8;
    i,j:integer;
    srcRect:TRect;
begin
  if fileExists(filename) then
  begin
    atmpSurface:=LoadSurfaceFromFile(filename);

    for j:=0 to FNumSpriteY-1 do
    begin
      srcRect.Top:=j*BGRSIZE;
      if (j=FNumSpriteY-1) and (FHeightExt<>0) then
        srcRect.Bottom:=srcRect.Top+FHeightExt
      else
        srcRect.Bottom:=srcRect.Top+BGRSIZE;

      for i:=0 to FNumSpriteX-1 do
      begin
        srcRect.Left:=i*BGRSIZE;
        if (i=FNumSpriteX-1) and (FWidthExt<>0) then
          srcRect.Right:=srcRect.Left+FWidthExt
        else
          srcRect.Right:=srcRect.Left+BGRSIZE;

        aSprite:=FSprites.Add as TSprite;
        aTexture:=FTextures.Add as TTexture;

        aTexture.LoadFromSurface(atmpSurface,@srcRect);
        aSprite.Texture:=aTexture;
      end;
    end;

  end;

end;

Menampilkan Background

Background kita tampilkan menggunakan prosedur Draw yang kita override. Proses
procedure TBackground.Draw;
begin
  FSprites.BeginDraw;
  try
    DrawSprites;
  finally
    FSprites.EndDraw;
  end;

end;

procedure TBackground.DrawSprites;
var i,j,indx,offsetx,offsety:integer;
    aSprite:TSprite;
begin
  indx:=0;
  offsetY:=0;
  for j:=0 to FNumSpriteY-1 do
  begin
    offsetx:=0;
    for i:=0 to FNumSpriteX-1 do
    begin
      aSprite:=TSprite(FSprites.Items[indx]);
      asprite.X:=FTranslation.x+offsetx;
      asprite.Y:=FTranslation.y+offsety;
      aSprite.Draw;
      inc(offsetx,BGRSIZE);
      inc(indx);
    end;
    inc(offsety,BGRSIZE);
  end;

end;

Tidak ada komentar:

Posting Komentar