Rabu, 30 April 2008

Membuat Animasi Sederhana (PacMan)

Animasi 2D mau bagaimana bentuknya pada dasarnya sama saja yaitu menampilkan gambar-gambar secara bergiliran. Untuk membuat animasi pada game 2D pada umumnya, sedikitnya ada tiga class yang terlibat. Ketiga class tersebut adalah:

  1. Class yang merepresentasikan citra/image yang akan dianimasikan. Biasanya disebut sprite. Dalam hal ini adalah TSprite.

  2. Container untuk menampung sprite objects. Container ini juga berperan sebagai sprite manager. Pada contoh code diberi nama TSpriteContainer.

  3. Class yang melakukan animasi. Bisa disebut sprite engine. Pada contoh code disebut TSpriteEngine.

Berikut adalah contoh code yang menampilkan teknik untuk melakukan animasi sederhana. Kelihatannya ribet yah. Mungkin iya karena code berikut adalah implementasi sederhana dari sebuah sprite engine untuk game 2D. Walaupun sederhana, mestinya sih sudah cukup untuk menjelaskan konsep animasi dan memperlihatkan bagaimana animasi berjalan tanpa flicker (bug paling menyebalkan dalam animasi). Contoh berikut terdiri atas dua unit, yaitu Sprite.pas dan Main.pas. Unit Sprite berisi empat buah class, yaitu:

  1. TUtils yang berisi method untuk yang bersifat utilisasi dan dipakai bersama-sama oleh semua class.

  2. TSprite yang merupakan class yang berisi informasi image yang akan digambar.

  3. TSpriteContainer yang berfungsi sebagai sprite container dan sprite manager.

  4. TSpriteEngine yang berfungsi melakukan animasi. Berisi sebuah method, yaitu OpenMouth.

Gambar-gambar yang akan di-raster di-load dan ditampung pada TSpriteContainer. Misalkan keempat file gambar tersebut bernama pac-1.bmp, pac-2.bmp, pac-3.bmp, pac-4.bmp, dan bg.bmp. Semuanya disimpan dalam directory “img\” yang path-nya relative terhadap .exe file.

File Sprite.pas:


unit Sprite;

interface

uses Windows, Graphics, Classes;

const
BACKGROUND_PATH = ‘img\\bg.bmp’;

type

TUtils = class
public
class procedure ReleaseItems(AList: TList);
end;

TSprite = class

private
FName: string;
FImage: TGraphic;
FTop: Integer;
FLeft: Integer;
FWidth: Integer;
FHeight: Integer;
public

constructor Create(AImgPath: string);
destructor Destroy; override;

property Name: string read FName write FName;
property Image: TGraphic read FImage;
property Top: Integer read FTop write FTop;
property Left: Integer read FLeft write FLeft;
property Width: Integer read FWidth write FWidth;
property Height: Integer read FHeight write FHeigth;
end;

TSpriteContainer = class

private
FSprites: TList;
public
constructor Create;
destructor Destroy; override;
function Add(ASprite: TSprite): Integer;
function Remove(ASprite: TSprite): Integer;
function GetSprite(ASpriteName: string): TSprite;
procedure Clear;

property Sprites: TList read FSprites;
end;

TSpriteEngine = class

private
FContainer: TSpriteContainer;
FScreen: TCanvas;
FBackground: TGraphic;
public
constructor Create(AContainer: TSpriteContainer);
destructor Destroy; override;
procedure OpenMouth(ADuration: Integer);

property Container: TSpriteContainer read FContainer;
property Screen: TCanvas read FScreen write FScreen;
end;

implementation

{TUtils}

class procedure TUtils.ReleaseItems(AList: TList);
begin
while AList.Count > 0 do

begin
Dispose(AList.First);
AList.Delete(0);
end;
AList.Capacity := 0;
end;

{TSprite}

constructor TSprite.Create(AImgPath: string);

begin
FImage := TBitmap.Create;
FImage.LoadFromFile(AImgPath);

Fwidth := FImage.Width;
FHeigth := FImage.Height;
FTop := 0;
FLeft := 0;
end;

destructor TSprite.Destroy;
begin
FImage.Free;
inherited;

end;

{TSpriteContainer}

constructor TSpriteContainer.Create;
begin
FSprites := TList.Create;
end;

destructor TSpriteContainer.Destroy;

begin
FSprites.Free;
inherited;
end;

function TSpriteContainer.Add(ASprite: TSprite): Integer;
begin
Result := FSprites.Add(ASprite);

end;

function TSpriteContainer.Remove(ASprite: TSprite): Integer;
begin
Result := Fsprites.Remove(ASprite);
end;

function TSpriteContainer.GetSprite(ASpriteName: string): TSprite;

var
i: Integer;
sprite: TSprite;
begin
Result := nil;
for i := 0 to FSprites.Count-1 do

begin
sprite := FSprites.Items[i];
if sprite.Name = ASpriteName then
begin
Result := sprite;
Exit;
end;
end;

end;

procedure TSpriteContainer.Clear;
begin
TUtils.ReleaseItems(FSprites);
end;

{TSpriteEngine}

constructor TSpriteEngine.Create(AContainer: TSpriteCOntainer);

begin
FContainer := AContainer;
FBackground := TBitmap.Create;
FBackground.LoadFromFile(BACKGROUND_PATH);
end;

destructor TSpriteEngine.Destroy;
begin
FBackground.Free;
inherited;
end;

procedure TSpriteEngine.OpenMouth(APosition: TPoint;
ADuration: integer);
var
startTick, deltaTick: Cardinal
frameDuration: Cardinal;
spriteIndex: Integer;
buffer: TBitmap;

begin
startTick := GetTickCount;
spriteIndex := 0;
desltaTick := 0;
frameDuration := ADuration div FContainer.Sprites.Count;

buffer := TBitmap.Create;
buffer.Width := FBackground.Width;
buffer.Height := FBackground.Height;

while (deltaTick <= ADuration) and

(spriteIndex < color="#ff0066">-1) do
begin
deltaTick := GetTickCount - startTick;
spriteIndex := deltaTick div frameDuration;

{ Teknik double buffer, yaitu gambar diolah dulu dimemory
sebelum ditampilkan. Teknik ini digunakan untuk menghindari
flicker }

buffer.Canvas.Draw(0, 0, FBackground);
buffer.Canvas.Draw(APosition.X, APosition.Y,
TSprite(FContainer.Sprites.Items[spriteIndex]).Image);

{ Screening }
FScreen.Draw(0, 0, buffer);
end;

buffer.Free;
end;

end.

Syntax Highlighted with http://delphi-id.org/syntax


Unit Main.pas terdiri dari sebuah form yang berisi sebuah control bertipe TPainBox yang berfungsi sebagai screen, dan sebuah tombol bertipe TButton. Penggalan Main.pas adalah sebagai berikut:


unit Main;

interface

uses Windows, …, ExtCtrls, Sprite;

const
IMAGES_NUMBER = 4;
PLAYING_DURATION = 200;
LOOP_DELAY = 100;
X = 200;
Y = 100;

type
TformMain = class(TForm)
PaintBox: TPaintBox;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private

procedure LoadAllImages(AContainer: TSpriteContainer);


end;

implementation

{ TformMain }

constructor TformMain.Create(AOwner: TComponent);
begin
inherited;
FContainer := TSpriteContainer.Create;
{ Load semua image yang akan di-raster, Kemas menjadi object
bertipe TSprite, kemudian simpan di dalam container menjadi
data yang siap raster }
LoadAllImages(FContainer);

FSpriteEngine := TSpriteEngine.Create(FContainer);
FSpriteEngine.Screen := PaintBox.Canvas;

end;



procedure LoadAllImages(AContainer: TSpriteCOntainer);
var
i: Integer;
sprite: TSprite;
spriteName: string;
begin
for i := 0 to IMAGES_NUMBER-1 do

begin
spriteName := ‘pac-’ + IntToStr(i+1) + ‘.bmp’;

sprite := TSprite.Create(‘img\\’ + spriteName);
sprite.Name := spriteName;
AContainer.Add(sprite);
end;

end;

procedure TFormMain.Button1Click(Sender: TObject);
begin
while True do
begin
FSpriteEngine.OpenMouth(Point(X, Y), PLAYING_DURATION);
Sleep(LOOP_DELAY);
end;

end;

end.

Syntax Highlighted with http://delphi-id.org/syntax


Gambar-gambar yang akan dianimasikan adalah:

Yang perlu diingat, code di atas hanyalah prototipe dari sprite engine sebuah dimensi dua yang memperlihatkan bagaimana memanipulasi sprite sehingga bisa menjadi gambar yang bergerak (animasi). Semoga bermanfaat dan silakan dimodifikasi sesuai dengan kebutuhan.

Tags: animasi, game, Object Pascals, pacman, programming, sprite
Posted in Object Pascals | No Comments »

Cara Mendapatkan MAC Address

Aku punya info lagi nich yaitu: fungsi untuk mengambil MAC Address dari ethernet card. Fungsinya beri nama GetMacAddress(AEthNumber: Byte). Kembalian dari fungsi ini adalah string dari MAC address ethernet card ber-index AEthNumber. Kalau PC hanya mempunyai sebuah ethernet card, AEthNumber bisa diisi dengan 0 (nol). Fungsi GetMacAddress memanfaatkan unit NB30 bawaan delphi yang berisi beberapa record serta fungsi untuk mengambil informasi network device melalui NetBios.


uses NB30;

function GetMacAddress(AEthNumber: Byte): string;
var
ncb: PNCB;
errorCode: Char;
adapter: PAdapterStatus;
begin

New(ncb);
{ Inisiasi isi memory pada pointer ncb dengan char 0 };
FillChar(ncb^, SizeOf(TNCB), 0);
ncb^.ncb_command := Char(NCBRESET);
ncb^.ncb_lana_num := Char(AEthNumber);
errorCode := NetBios(ncb);

{ Reset kembali isi memory pointer nbc dengan char 0 };
FillChar(ncb^, SizeOf(TNCB), 0);
ncb^.ncb_command := Char(NCBASTAT);
ncb^.ncb_lana_num := Char(AEthNumber);
{ Harus berisi 16 karakter }

StrPCopy(ncb^.ncb_callname, ‘* ‘);

New(adapter);
FillChar(adapter^, SizeOf(TAdapterStatus), 0);
ncb^.ncb_buffer := Pointer(adapter);
ncb^.ncb_length := SizeOf(TAdapterStatus);

errorCode := NetBios(ncb);
if (errorCode = Char(NRC_GOODRET)) or
(errorCode = Char(NRC_INCOMP)) then

Result := IntToHex(Ord(adapter^.adapter_address[0]), 2) + ‘-’ +
IntToHex(Ord(adapter^.adapter_address[1]), 2) + ‘-’ +
IntToHex(Ord(adapter^.adapter_address[2]), 2) + ‘-’ +
IntToHex(Ord(adapter^.adapter_address[3]), 2) + ‘-’ +
IntToHex(Ord(adapter^.adapter_address[4]), 2) + ‘-’ +
IntToHex(Ord(adapter^.adapter_address[5]), 2)
else

Result := ‘00-00-00-00-00-00′;

Dispose(ncb);
Dispose(adapter);
end;

Syntax Highlighted with http://delphi-id.org/syntax


Begitu saja, semoga bermanfaat. Kali aja ada yang mo ikutan nambahin.

Single Instance Application

Ada beberapa cara yang bisa digunakan untuk membuat single instance application. Saya hanya mau membagi sedikit info gratis yaitu dengan memanfaatkan API dari windows. Fungsi yang digunakan adalah CreateFileMapping dengan sedikit memodifikasi isi dari file project. Cara menggunakannya adalah sebagai berikut:


...

uses Windows, … ;

const
MAX_SIZE_HIGH = 0;
MAX_SIZE_LOW = 32;
{ Bisa diganti dengan string apa saja yang akan menjadi Id
dari aplikasi.}
APP_IDENTIFIER = ‘th3-4ppl1c4t10nk3y’;

var
hMap: THandle;

begin
Application.Initialize;

hMap := CreateFileMapping(0, nil, PAGE_READONLY, MAX_SIZE_HIGH,
MAX_SIZE_LOW, APP_IDENTIFIER);
if (hMap <> 0) and (GetLastError = ERROR_ALREADY_EXIST) then

begin
CloseHandle(hMap);
Application.Terminate;
end;



Application.Run;
end.

Syntax Highlighted with http://delphi-id.org/syntax


Semoga bermanfaat. :)