FSM

Pengertian Finite State Machine ( FSM )

Finite state machine adalah suatu perangkat atau model perangkat yang memiliki sejumlah state dan pada satu waktu dapat berada dalam salah satu state tersebut. Dia dapat memproses input dan menghasilkan transisi dari state satu ke state lain atau menghasilkan output berupa aksi.
Finite State Machines (FSM) adalah sebuah metodologi perancangan sistem kontrol yang menggambarkan tingkah laku atau prinsip kerja sistem dengan menggunakan tiga hal berikut: State (Keadaan), Event (kejadian) dan Action (aksi). Pada satu saat dalam periode waktu yang cukup signifikan, sistem akan berada pada salah satu state yang aktif. Sistem dapat beralih atau bertransisi menuju state lain jika mendapatkan masukan atau event tertentu, baik yang berasal dari perangkat luar atau komponen dalam sistemnya itu sendiri (misal interupsi timer). Transisi keadaan ini umumnya juga disertai oleh aksi yang dilakukan oleh sistem ketika menanggapi masukan yang terjadi. Aksi yang dilakukan tersebut dapat berupa aksi yang sederhana atau melibatkan rangkaian proses yang relative kompleks.
Berdasarkan sifatnya, metode FSM ini sangat cocok digunakan sebagai basis perancangan perangkat lunak pengendalian yang bersifat reaktif dan real time. Salah satu keuntungan nyata penggunaan FSM adalah kemampuannya dalam mendekomposisi aplikasi yang relative besar dengan hanya menggunakan sejumlah kecil item state. Selain untuk bidang kontrol, Penggunaan metode ini pada kenyataannya juga umum digunakan sebagai basis untuk perancangan protokol-protokol komunikasi, perancangan perangkat lunak game, aplikasi WEB dan sebagainya.
Dalam bahasa pemrograman prosedural seperti bahasa C, FSM ini umumnya direalisasikan dengan menggunakan statemen kontrol switch case atau/dan if..then. Dengan menggunakan statemen-statemen kontrol ini, aliran program secara praktis akan mudah dipahami dan dilacak jika terjadi kesalahan logika.

DIAGRAM KEADAAN

Diagram keadaan pada dasarnya merupakan salah satu bentuk representasi dari FSM. Diagram ini secara visual menggambarkan tingkah laku yang dimiliki oleh sistem kontrol yang kompleks kedalam bentuk yang lebih sederhana dan relative mudah dipahami.




Dalam diagram ini, state-state yang terdapat pada sebuah sistem digambarkan sebagai lingkaran yang diberi label unik, sedangkan transisi state yang diakibatkan oleh event tertentu direpresentasikan sebagai anak panah yang berasal dari state yang ditinggalkan menuju state yang aktif. Setiap transisi yang terjadi umumnya juga diikuti oleh aksi yang dilakukan oleh sistem yang dirancang. Secara praktis setiap diagram state yang dirancang akan selalu memiliki sebuah transisi awal (inisial) yang menuju salah satu state sejak sistem kontrol tersebut mulai dihidupkan. Gambar berikut memperlihatkan contoh penggambaran diagram state:
Diagram tersebut memperlihatkan FSM dengan dua buah state (S0 dan S1) dan dua buah input (e1 dan e2) serta dua buah aksi (a1 dan a2) output yang berbeda : seperti terlihat pada gambar, ketika sistem mulai dihidupkan, sistem akan bertransisi menuju state0, pada keadaan ini sistem akan menghasilkan Action2 jika terjadi masukan Event2, sedangkan jika terjadi Event1 maka Action1 akan dieksekusi kemudian sistem selanjutnya bertransisi ke keadaan State1 dan seterusnya.
Berikut adalah potongan program untuk gambar tersebut
//program gambar diatas
….
#define TRUE 1
enum {state0,state1}
……
unsigned char state;
void Action1(void);
void Action2(void);
…..
main()
{
//init
………
While(TRUE)
{
switch(state)
{
case state0: If(input==Event2)
{Action2();
state=state0;}
break;
case state1: If(input==Event1)
{Action1()
state=State1;}
break;
}
}
…….
}

Hierarchy Finite Machine  Strategi Menyerang
Hierarchy Finite State Machine untuk  strategi menyerang dibuat berdasarkan  skenario strategi. Secara garis besar  hirarki yang dimaksud terbagi menjadi
dua bagian, yaitu hirarki untuk NPC  Scout dan hirarki untuk NPC Sniper,
seperti dijelaskan pada Gambar 7.





Top Level Finite State Machine
Top level finite state machine untuk  gerak menghindar pada penelitian ini  merupakan gabungan antara FSM NPC  Angler dan FSM NPC Support,
sebagaimana di jelaskan pada gambar 8.




Contoh pengunaan finite state machine cukup banyak. Contoh yang gampang adalah demo aplikasi Animasi 2D dengan Direct3D. Di demo tersebut karakter spiderman dapat berjalan, memukul, menendang dan sebagainya. Berjalan, memukul dan menendang adalah beberapa state dimana spiderman dapat berada. State berjalan, memukul dan menendang di atas menghasilkan output berupa aksi yakni animasi berjalan, memukul dan animasi menendang.
Di artikel ini kita akan memperbaiki sistem manajemen state demo Animasi 2D dengan Direct3D menggunakan object-oriented finite state machine. Pada object-oriented finite state machine, state kita modelkan sebagai obyek yang dapat mengeksekusi aksi. Aksi yang dieksekusi dapat berupa aksi murni atau aksi yang menyebabkan terjadinya transisi state menjadi state lain.
Pada demo diatas, state spiderman kita simpan dalam variabel. Perhatikan potongan source code demonya.

procedure T2DEngine.Draw;
begin
  FBackground.Draw;

  FSprites.BeginDraw;

  //beri delay agar animasi
  //tidak terlalu cepat
  if delay>2 then
  begin
    case anim of
      animIdle:begin
                 //khusus animasi idle buat lebih slow
                 //soallnya framenya sedikit
                 if delay_idle>2 then
                 begin
                   Spiderman.Texture:=SpidermanIdleTexture[animIndex];
                   Spiderman.Y:=200;

                   inc(animIndex);
                   if animIndex>9 then
                      animIndex:=0;

                   delay_idle:=0;
                 end;
                 inc(delay_idle);
               end;
      animWalk:begin
                   Spiderman.Texture:=SpidermanWalk[animIndex];
                   Spiderman.Y:=205;
                   case dirType of
                     dirLeft:Spiderman.X:=Spiderman.X-6;
                     dirRight:Spiderman.X:=Spiderman.X+6;
                    end;

                   inc(animIndex);
                   if animIndex>11 then
                   begin
                    //animasi punch selesai
                    //kembalikan Spiderman ke animasi idle
                      anim:=animIdle;
                      animIndex:=0;
                   end;
               end;
      animPunch:begin
                  Spiderman.Texture:=SpidermanPunchTexture[animIndex];
                  Spiderman.Y:=202;

                  inc(animIndex);
                  if animIndex>5 then
                  begin
                    //animasi punch selesai
                    //kembalikan Spiderman ke animasi idle
                    anim:=animIdle;
                    animIndex:=0;
                  end;

                 end;
       animHeavyPunch:begin
                       Spiderman.Texture:=SpidermanHeavyPunchTexture[animIndex];
                       Spiderman.Y:=175;

                       inc(animIndex);
                       if animIndex>9 then
                       begin
                         //animasi heavy punch selesai
                         //kembalikan Spiderman ke animasi idle
                         anim:=animIdle;
                         animIndex:=0;
                       end;

                     end;
           animKick:begin
                      Spiderman.Texture:=SpidermanKickTexture[animIndex];
                      Spiderman.Y:=171;
                      inc(animIndex);
                      if animIndex>8 then
                      begin
                        anim:=animIdle;
                        animIndex:=0;
                      end;

                    end;
     end;
     delay:=0;
  end;
  Spiderman.Draw;
  FSprites.EndDraw;
  inc(delay);
end;

Kita menggunakan case..of untuk menghasilkan output masing-masing state. Jika jumlah statenya tidak terlalu banyak model seperti ini tidak buruk, tapi bayangkan jika jumlah statenya bertambah, misalnya 20 atau 30 state, maka kode case of ini akan semakin panjang dan dapat dipastikan semakin besar peluang kode menjadi ruwet dan susah dirawat, susah didebug, kontrol alir program menjadi ruwet dan lain-lain.
Desain dan Implementasi Finite State Machine Berorientasi Objek
Bayangkan situasi berikut: kita memiliki robot cerdas berbentuk anjing sebut saja Dog. Dog dapat melakukan atraksi seperti bermain bola, berguling-guling, makan dan tidur. Tiap atraksi dijalankan dengan menggunakan cartridge-cartridge berisi program yang dicolok ke sebuah slot dalam body Dog.
Cartridge ini mewakili state. Tanpa cartdridge, Dog hanyalah sebuah patung yang tidak bisa apa-apa. Kemampuan utama selain memproses cartridge dan menjalankan aksi yang diprogram di dalam cartridge (memproses input dan menghasilkan output berupa aksi) adalah kemampuan untuk mengganti cartridge dengan cartridge lain (menghasilkan transisi state ke state lain). Doog juga memiliki sensor untuk memantau level baterai. Jika level baterai turun, Dog akan mengubah cartridge yang sedang berada dalam slot.
Object-oriented finite state machine yang kita buat akan mirip dengan sistem cartridge di atas. Obyek state (TAIAction) dapat berisi perintah untuk melakukan aksi atau melakukan transisi ke obyek state lain. Untuk mengeksekusi perintah, obyek state akan dilengkapi metode ExecuteAction. Karena obyek state adalah obyek dasar, apa yang dikerjakan didalam ExecuteAction sepenuhnya menjadi tanggung jawab kelas turunan obyek state. Oleh karena itu ExecuteAction harus berupa metode virtual agar dapat di-override oleh kelas turunannya.
State juga memiliki metode yang akan dieksekusi ketika akan terjadi perubahan state yakni ketika hendak memasukan state yakni EnterAction dan ketika hendak keluar dari state yakni ExitAction. Kegunaan EnterAction dan ExitAction adalah sebagai berikut: bayangkan anda sedang membuat program game seperti WarCraft. Anda ingin karakter prajurit yang sedang berperang akan meneriakkan "Hore" ketika terjadi perubahan dari state berperang menjadi state stand by ketika musuh yang dihadapi telah kalah. Kode aksi teriakan "Hore" bisa diletakkan di dalam ExitAction state berperang atau EnterAction state stand by.

Jadi framework kita akan menjadi seperti dibawah ini:

  TAIAction=class(TObject)
  private
  public
    procedure EnterAction(entity:TAIEntity);virtual;
    procedure ExecuteAction(entity:TAIEntity);virtual;
    procedure ExitAction(entity:TAIEntity);virtual;
  end;

State-state dieksekusi oleh karakter dalam game yang selanjutnya kita sebut entity (TAIEntity). Entity menyimpan state dimana dia sedang berada. Jika state ini diubah, maka ExitAction state lama dieksekusi, kemudian state yang baru disimpan dan metode EnterAction state baru dieksekusi.
Entity juga memiliki property untuk menyimpan state sebelumnya. Kita membutuhkannya untuk contoh berikut: di game Warcraft, pekerja yang sedang menambang emas, ketika emas yang diambil sudah cukup akan meletakkan emas kedalam lumbung, kemudian akan kembali lagi ke tempat penambangan untuk melanjutkan pekerjaan menambang. Di sini terjadi transisi state dari state menambang menjadi state meletakkan emas. Setelah state meletakkan emas dieksekusi, state dikembalikan ke state sebelumnya (state menambang) dan mengeksekusi state tersebut.
Dengan memiliki kemampuan menyimpan state sebelumnya, proses reverse ke state sebelumnya jadi mudah.

type
  TAIAction=class;

  TAIEntity=class(TObject)
  private
    FPreviousAction: TAIAction;
    FCurrentAction: TAIAction;
    procedure SetCurrentAction(const Value: TAIAction);
    procedure SetPreviousAction(const Value: TAIAction);
  public
    procedure Update;
  published
    property CurrentAction:TAIAction read FCurrentAction write SetCurrentAction;
    property PreviousAction:TAIAction read FPreviousAction write SetPreviousAction;
  end;
Entity memiliki metode Update. Di metode ini, ExecuteAction state akan dieksekusi. Metode ini dimaksudkan untuk eksekusi ExcuteAction yang aman untuk menghindari access violation karena CurrentAction mungkin saja nil. Ok berikut ini adalah implementasi lengkap kodenya
{-------------------------------
Implementasi Finite State Machine
menggunakan object-oriented
programming
--------------------------------


-------------------------------}
unit u_ai;

interface
uses classes,sysutils;

type
  TAIAction=class;

  TAIEntity=class(TObject)
  private
    FPreviousAction: TAIAction;
    FCurrentAction: TAIAction;
    procedure SetCurrentAction(const Value: TAIAction);
    procedure SetPreviousAction(const Value: TAIAction);
  public
    procedure Update;
  published
    property CurrentAction:TAIAction read FCurrentAction write SetCurrentAction;
    property PreviousAction:TAIAction read FPreviousAction write SetPreviousAction;
  end;

  TAIAction=class(TObject)
  private
  public
    procedure EnterAction(entity:TAIEntity);virtual;
    procedure ExecuteAction(entity:TAIEntity);virtual;
    procedure ExitAction(entity:TAIEntity);virtual;
  end;

implementation

{ TAIEntity }

procedure TAIEntity.SetCurrentAction(const Value: TAIAction);
begin
  //run ExitAction aksi lama sebelum berpindah ke aksi baru
  if FCurrentAction<>nil then
    FCurrentAction.ExitAction(self);

  //set aksi baru
  FCurrentAction := Value;

  //run EnterAction aksi baru
  if FCurrentAction<>nil then
    FCurrentAction.EnterAction(self);
end;

procedure TAIEntity.SetPreviousAction(const Value: TAIAction);
begin
  FPreviousAction := Value;
end;

procedure TAIEntity.Update;
begin
  if FCurrentAction<>nil then
     FCurrentAction.ExecuteAction(self);
end;

{ TAIAction }

procedure TAIAction.EnterAction(entity: TAIEntity);
begin
  //do nothing
end;

procedure TAIAction.ExecuteAction(entity: TAIEntity);
begin
  //do nothing
end;

procedure TAIAction.ExitAction(entity: TAIEntity);
begin
  //do nothing
end;

end.

TAIAction defaultnya tidak melakukan apa-apa. Untuk menggunakan framework ini, aplikasi harus menurunkan TAIEntity dan TAIAction dan mendefinisikan aksi yang harus dijalankan. Kembali ke tujuan utama kita yakni memperbaiki demo Animasi 2D dengan Direct3D.
Kita belum menambahkan aksi lain untuk demo ini, jadi aksi spiderman hanya aksi berjalan, memukul, memukul dengan keras, menendang dan idle/stand by. Tidak berubah dari demo aslinya.


TBaseAction
Semua aksi akan diturunkan dari TBaseAction yang merupakan turunan TAIAction. Aksi yang dikerjakan pada ExecuteAction TBaseAction adalah mengupdate indeks animasi ke frame animasi berikutnya. EnterAction kita isi dengan kode untuk mereset animasi ke frame animasi pertama. Semua aksi melakukan hal yang sama oleh karena itu kita turunkan dari TBaseAction. TBaseAction akan dilengkapi dengan metode virtual Load yang berguna untuk melakukan loading frame-frame animasi. Karena jumlah frame animasi tiap aksi berbeda, Load sesungguhnya akan didelegasi ke turunan TBaseAction. TBaseAction akan menyimpan instance texture collection yang akan dipergunakan untuk membuat instance TTexture.

  TBaseAction=class(TAIAction)
  private
    FMaxAnim: integer;
    FTextures: TTextureCollection;
    procedure SetMaxAnim(const Value: integer);
    procedure SetTextures(const Value: TTextureCollection);
  public
    procedure EnterAction(aientity:TAIEntity);override;
    procedure ExecuteAction(aientity:TAIEntity);override;
    procedure Load(const images_dir:string);virtual;
  published
    property Textures:TTextureCollection read FTextures write SetTextures;
    property MaxAnim:integer read FMaxAnim write SetMaxAnim;
  end;
Berikut ini adalah implementasinya. Fungsi Load adalah fungsi virtual yang tidak melakukan apa-apa.
{TBaseAction}

procedure TBaseAction.Load;
begin
end;

Kita override EnterAction untuk memastikan semua animasi dimulai dari indeks ke-0 untuk semua aksi. Perhatikkan bahwa kita melakukan typecast TAIEntity ke T2DEntity. Typecast ini aman dengan asumsi bahwa, entity spiderman akan diturunkan dari T2DEntity. Kita akan membahas kelas T2DEntity segera.
procedure TBaseAction.EnterAction(aientity: TAIEntity);

var ent:T2DEntity;
begin
  ent:=T2DEntity(aiEntity);
  ent.AnimIndex:=0;
end;

ExecuteAction kita isi dengan kode yang akan menambah indeks animasi. Jika indeks animasi ini melebih jumlah total frame animasi (MaxAnim) berarti animasi telah selesai dan animasi spiderman kita kembalikan ke state sebelumnya (PreviousAction). Harap diperhatikan karena idle adalah default state spiderman, maka PreviousAction ini kita isi dengan state idle dan selama demo berlangsung PreviousAction tidak pernah berubah, dengan cara ini tiap kali animasi (misal animasi memukul) selesai maka spiderman akan otomatis masuk ke state idle.
ExecuteAction milik TBaseAction harus dijalankan terakhir oleh karena itu kelas-kelas turunan TBaseAction yang meng-override ExecuteAction harus memanggil ExecuteAction TBaseAction ini paling akhir setelah mengupdate animasi.
procedure TBaseAction.ExecuteAction(aientity:TAIEntity);

var ent:T2DEntity;
begin
  ent:=T2DEntity(aiEntity);

  ent.AnimIndex:=ent.AnimIndex+1;
  if ent.AnimIndex>MaxAnim then
  begin
    ent.CurrentAction:=ent.PreviousAction;
  end;
end;

TIdleAction
Aksi idle akan kita enkapsulasi dalam kelas TIdleAction. Dalam TIdleAction, akan disimpan sebuah variabel array TTexture (SpidermanIdleTexture) berisi animasi idle spiderman. Metode Load, EnterAction dan ExecuteAction kita override.

  TIdleAction=class(TBaseAction)
  private
    SpidermanIdleTexture:TSpidermanIdleTexture;
    FDelay: integer;
    procedure SetDelay(const Value: integer);
  public
    procedure EnterAction(aientity:TAIEntity);override;
    procedure ExecuteAction(aientity:TAIEntity);override;
    procedure Load(const images_dir:string);override;
  published
    property Delay:integer read FDelay write SetDelay;
  end;
Berikut ini adalah implementasinya
{TIdleAction}

procedure TIdleAction.EnterAction(aientity: TAIEntity);
begin
  inherited;
  Fdelay:=0;
end;

procedure TIdleAction.ExecuteAction(aientity:TAIEntity);
var ent:T2DEntity;

begin
  if FDelay>3 then
  begin
    ent:=T2DEntity(aiEntity);

    ent.Sprite.Texture:=SpidermanIdleTexture[ent.AnimIndex];
    ent.Sprite.Y:=200;
    FDelay:=0;
    inherited;
  end;
  inc(FDelay);
end;

procedure TIdleAction.Load(const images_dir:string);
var i:integer;
begin
  for i:=0 to 9 do
  begin
    SpidermanIdleTexture[i]:=FTextures.Add as TTexture;
    SpidermanIdleTexture[i].ColorKey:=0;
    SpidermanIdleTexture[i].LoadFromFile(images_dir+
                   'standbystandby'+inttostr(i)+'.png');
  end;
  MaxAnim:=9;
end;

procedure TIdleAction.SetDelay(const Value: integer);
begin
  FDelay := Value;
end;

Di kelas TIdleAction, kita override lagi EnterAction. Perhatikan bahwa animasi idle ini menggunakan delay tambahan selain delay animasi keseluruhan. Tanpa delay tambahan ini, animasi idle masih terlalu cepat sehingga terlihat jelek. Kita perlu mengoverride untuk memastikan delay ini direset ke 0, tiap kali spderman masuk state idle.
ExecuteAction kita override. Disini kita tambahkan pengecekan delay, jika delay lebih dari 2, animasi kita update dengan frame animasi berikutnya, delay kita kembalikan lagi ke 0 dan terakhir kita tambahkan pemanggilan ExecuteAction ansestor. Pemanggilan ExecuteAction ancestor (TBaseAction) ini penting agar AnimIndex diupdate, tanpa itu kita tidak akan melihat animasi idle spiderman.
Metode Load kita override dengan kode yang berguna untuk loading gambar-gambar animasi idle. Karena animasi idle hanya terdiri atas 10 frame animasi (dari 0-9) kita harus mengeset MaxAnim dengan 9. Jika kita lupa, animasi tidak akan terlihat.
Tekstur-tekstur yang tersimpan di SpidermanIdleTexture, tidak difree karena FTextures otomatis akan membuang memorinya ketika FTextures di free, tapi jika diinginkan, tekstur-tekstur tersebut dapat saja di free didalam TIdleAction.

TPunchAction
Aksi memukul dijalankan oleh TPunchAction
  TPunchAction=class(TBaseAction)
  private
    SpidermanPunchTexture:TSpidermanPunchTexture;
  public
    procedure ExecuteAction(aientity:TAIEntity);override;
    procedure Load(const images_dir:string);override;
  end;

Kita hanya mengoverride ExecuteAction dan Load, karena kita tidak membutuhkan aksi tertentu pada saat EnterAction maupun ExitAction
{TPunchAction}

procedure TPunchAction.ExecuteAction(aientity:TAIEntity);
var ent:T2DEntity;
begin
  ent:=T2DEntity(aiEntity);
  ent.Sprite.Texture:=SpidermanPunchTexture[ent.AnimIndex];
  ent.Sprite.Y:=202;
  inherited;
end;

procedure TPunchAction.Load(const images_dir:string);
var i:integer;
begin
  for i:=0 to 5 do
  begin
    SpidermanPunchTexture[i]:=FTextures.Add as TTexture;
    SpidermanPunchTexture[i].ColorKey:=0;
    SpidermanPunchTexture[i].LoadFromFile(images_dir+
                        'punchpunch'+inttostr(i)+'.png');
  end;
  MaxAnim:=5;
end;

Terlihat bahwa kodenya secara umum tidak terlalu jauh beda dengan kode implementasi TIdleAction, kecuali bahwa animasi tidak perlu didelay lagi.
THeavyPunchAction
Heavy punch adalah aksi memukul spiderman dengan tenaga penuh, dienkapsulasi dalam kelas

 THeavyPunchAction
  THeavyPunchAction=class(TBaseAction)
  private
    SpidermanHeavyPunchTexture:TSpidermanHeavyPunchTexture;
  public
    procedure ExecuteAction(aientity:TAIEntity);override;
    procedure Load(const images_dir:string);override;
  end;
Implementasi THeavyPunchAction adalah sebagai berikut:
{THeavyPunchAction}

procedure THeavyPunchAction.ExecuteAction(aientity:TAIEntity);
var ent:T2DEntity;
begin
  ent:=T2DEntity(aiEntity);
  ent.Sprite.Texture:=SpidermanHeavyPunchTexture[ent.AnimIndex];
  ent.Sprite.Y:=175;
  inherited;
end;

procedure THeavyPunchAction.Load(const images_dir: string);
var i:integer;
begin
  for i:=0 to 9 do
  begin
    SpidermanHeavyPunchTexture[i]:=FTextures.Add as TTexture;
    SpidermanHeavyPunchTexture[i].ColorKey:=0;
    SpidermanHeavyPunchTexture[i].LoadFromFile(images_dir+
          'heavy_punchhpunch'+inttostr(i)+'.png');
  end;
  MaxAnim:=9;
end;

TKickAction
Aksi menendang dijalankan dalam TKickAction, kodenya mirip dengan TPunchAction

  TKickAction=class(TBaseAction)
  private
    SpidermanKickTexture:TSpidermanKickTexture;
  public
    procedure ExecuteAction(aientity:TAIEntity);override;
    procedure Load(const images_dir:string);override;
  end;
.....
{ TKickAction }

procedure TKickAction.ExecuteAction(aientity: TAIEntity);
var ent:T2DEntity;
begin
  ent:=T2DEntity(aiEntity);
  ent.Sprite.Texture:=SpidermanKickTexture[ent.animIndex];
  ent.Sprite.Y:=171;
  inherited;
end;

procedure TKickAction.Load(const images_dir:string);
var i:integer;
begin
  for i:=0 to 8 do
  begin
    SpidermanKickTexture[i]:=FTextures.Add as TTexture;
    SpidermanKickTexture[i].ColorKey:=0;
    SpidermanKickTexture[i].LoadFromFile(images_dir+
                          'kickkick'+inttostr(i)+'.png');
  end;
  MaxAnim:=8;
end;

TWalkAction
Aksi berjalan dienkapsulasi oleh TWalkAction
  TWalkAction=class(TBaseAction)
  private
    SpidermanWalkTexture:TSpidermanWalkTexture;
  public
    procedure ExecuteAction(aientity:TAIEntity);override;
    procedure Load(const images_dir:string);override;
  end;

Implementasi TWalkAction adalah sebagai berikut. Pada ExecuteAction selain menampilkan animasi berjalan, kita juga harus memindahkan posisi spiderman berdasarkan arah mana spirderman menghadap. Jika menghadap ke kiri posisi x kita kurangi dan jika menghadap kanan posisi x kita tambah.
{TWalkAction}

procedure TWalkAction.ExecuteAction(aientity:TAIEntity);
var ent:T2DEntity;

begin
  ent:=T2DEntity(aiEntity);

  ent.Sprite.Texture:=SpidermanWalkTexture[ent.AnimIndex];
  ent.Sprite.Y:=205;
  case ent.Direction of
    dirLeft:ent.Sprite.X:=ent.Sprite.X-6;
    dirRight:ent.Sprite.X:=ent.Sprite.X+6;
  end;

  inherited;
end;

procedure TWalkAction.Load(const images_dir: string);
var i:integer;
begin
  for i:=0 to 11 do
  begin
    SpidermanWalkTexture[i]:=FTextures.Add as TTexture;
    SpidermanWalkTexture[i].ColorKey:=0;
    SpidermanWalkTexture[i].LoadFromFile(images_dir+'walkwalk'+inttostr(i)+'.png');
  end;
  MaxAnim:=11;
end;

TChangeDirectionAction
Aksi mengubah arah kiri/kanan spiderman dijalankan TChangeDirectionAction. Karena TChangeDirection tidak membutuhkan animasi, metode Load tidak perlu kita overide.
  TChangeDirectionAction=class(TBaseAction)
  public
    procedure ExecuteAction(aientity:TAIEntity);override;
  end;

Implementasi TChangeDirectionAction terlihat dipotongan kode berikut. Penjelasan bagaimana membuat pencerminan sprite yang menghadap kearah berbeda telah kita bahas di artikel Animasi 2D dengan Direct3D Part 2. Yang harus diperhatikan bahwa kita tidak perlu memanggil ExecuteAction ancestor karena aksi memindahkan arah tidak memerlukan animasi.
{ TChangeDirectionAction }

procedure TChangeDirectionAction.ExecuteAction(aientity: TAIEntity);
var ent:T2DEntity;
begin
  ent:=T2DEntity(aiEntity);
  case ent.Direction of
    dirLeft:begin
              ent.Sprite.scaling:=SetD3DXVector2(-1,1);
              ent.Sprite.X:=ent.Sprite.X+ent.Sprite.Texture.Width;
            end;
    dirRight:begin
              ent.Sprite.scaling:=SetD3DXVector2(1,1);
              ent.Sprite.X:=ent.Sprite.X-ent.Sprite.Texture.Width;
             end;
  end;
  inherited;
end;

TWalkOrChangeDirAction
Aksi berjalan atau mengubah arah dijalankan menggunakan penekanan tombol yang sama yakni tombol panah kiri/kanan. Jika pada saat tombol panah kiri ditekan, spiderman menghadap ke kanan, maka aksi yang dijalankan adalah mengubah arah spiderman ke kiri. Jika spiderman sudah menghadap ke kiri, aksi yang dijalankan adalah berjalan ke kiri. Hal yang sama terjadi pada saat penekanan tombol panah kanan. Dengan cara ini, spiderman selalu akan berjalan maju.
Aksi menentukan aksi berjalan atau mengubah arah dienkapsulasi dalam TWalkOrChangeDirAction. Kelas ini adalah contoh implementasi state yang menghasilkan output berupa transisi ke state yang lain.

  TWalkOrChangeDirAction=class(TBaseAction)
  private
    FChangeDir: TChangeDirectionAction;
    FWalk: TWalkAction;
    FNewDirection: TDirectionType;
    procedure SetChangeDir(const Value: TChangeDirectionAction);
    procedure SetWalk(const Value: TWalkAction);
    procedure SetNewDirection(const Value: TDirectionType);
  public
    procedure ExecuteAction(aientity:TAIEntity);override;
  published
    property NewDirection:TDirectionType read FNewDirection write SetNewDirection;
    property Walk:TWalkAction read FWalk write SetWalk;
    property ChangeDir:TChangeDirectionAction read FChangeDir write SetChangeDir;
  end;

Implementasinya adalah sebagai berikut. Perhatikan bahwa Load tidak perlu di override karena tidak relevan, karena aksi ini tidak menghasilkan output berupa animasi. Kita perlu menambahkan property Walk dan ChangeDir untuk menyimpan instance kelas TWalkAction dan TChangeDirectionAction, serta NewDirection yang akan berisi arah menghadap spiderman.
{ TWalkOrChangeDirAction }

procedure TWalkOrChangeDirAction.ExecuteAction(aientity: TAIEntity);
var ent:T2DEntity;

begin
  ent:=T2DEntity(aiEntity);
  if ent.Direction<>FNewDirection then
  begin
    ent.Direction:=FNewDirection;
    ent.CurrentAction:=FChangeDir;
  end else
  begin
    ent.CurrentAction:=FWalk;
  end;
end




Tidak ada komentar:

Posting Komentar