Jumat, 27 November 2015

Program Animasi Game MoTer 3D dengan OpenGL

Membuat objek Game  MoTer 3D yang bisa berubah bentuk menjadi mobil atau MoTer (animasi lebih dari satu) dengan menggunakan Visual Studio 2013 Express dan OpenGL.




Selamat Pagi, akhirnya setelah sekian lama tertunda ogut berhasil menulis tentang animasi untuk objek game 3D. Kali ini ogut akan membahas cara membuat objek game 3D yang bisa berubah bentuk atau lebih tepatnya bisa beranimasi lebih dari satu.




Seperti yang Anda lihat di atas MoTer bisa berubah bentuk menjadi mobil dan kembali berubah menjadi MoTer, lalu ada juga animasi baling-baling berputar, total ada 5 animasi.

Untuk mempelajari pembuatan objek ini ogut sarankan rekan-rekan membaca artikel yang ogut buat sebelumnya, tentang Hely 3D.


Pada Hely 3D animasi hanya ada satu yaitu baling-baling yang berputar, sedangkan MoTer 3D mempunyai 5 animasi. Cara pembuatan keduanya hampir sama hanya berbeda di jumlah animasi yang bisa ditampilkan di layar monitor. Ogut tidak akan membahas ulang yang sudah dibahas pada artikel Hely 3D, jadi rekan-rekan harus membacanya terlebih dahulu untuk bisa lanjut membaca artikel ini.



Objek MoTer 3D dibuat dengan menggunakan 3ds max lalu di konversi menjadi file text dengan bantuan script (artikel membuat objek 3D dengan 3ds max). Hasil konversi berupa fie text dengan nama MoTer3D.MESH3DANIMX, jika Anda buka file tersebut dengan editor Notepad isinya akan seperti berikut.


Isi file di atas menggunakan konsep 3 daftar, apa itu konsep 3 daftar sudah ogut jelaskan di artikel Hely 3D. Di dalam file terdapat total face, total TK, total vertex per frame, dan yang membuat berbeda dengan Hely 3D adalah total frame animasi dan total animasi. Total frame animasi dan total animasi adalah tambahan di objek 3D MoTer yang tidak ada di Hely 3D. Di bawah baris pertama terdapat daftar animasi (list of animasi) dan baris berikutnya adalah daftar face (list of face).

Pada daftar animasi (list of animasi) terdapat angka 1 (looping) dan angka 0 (non looping), maksudnya adalah sebagai flag/tanda jika animasi bersifat looping atau tidak looping. Animasi looping adalah animasi yang terus menerus berulang contohnya putaran baling-baling pada objek MoTer 3D. Sedangkan contoh animasi yang tidak looping adalah animasi perubahan bentuk dari mobil ke MoTer. Kenapa tidak looping sebab setelah mobil berubah menjadi MoTer dia tidak bisa berubah lagi jadi mobil  lalu jadi MoTer kembali.


Isi berikutnya dari file MoTer3D.MESH3DANIMX adalah daftar TK (list of TK) seperti yang Anda lihat di gambar.


Dan terakhir adalah daftar vertex (list of vertex).

Untuk membuat animasi 3D dengan vektor R3 teorinya sudah ogut tulis di artikel ini, silahkan Anda baca terlebih dahulu supaya tidak bingung.

Teori sudah sekarang saat koding, pertama deklarasikan variabel dan struct yang dibutuhkan.

typedef struct
{
int indexV1;
int indexV2;
int indexV3;

int indexTK1;
int indexTK2;
int indexTK3;
}Face;

typedef struct
{
float X;
float Y;
float Z;
}Vertex;

typedef struct
{
float s;
float t;
}TK;

typedef struct
{
char AnimName[16];
int Looping_YN;
int FrameStart;
int FrameEnd;
}Animasi;


//===================
//variabel MoTer3D
//===================
int TotalF_MoTer3DAnim;
int TotalTK_MoTer3DAnim;
int TotalV_MoTer3DAnim;
int TotalFr_MoTer3DAnim;
int TotalAnim_MoTer3DAnim;

int CurrFr_MoTer3DAnim;
int NextFr_MoTer3DAnim;
float Skala_MoTer3DAnim = 0.0f;
int IndexAnimasi = 0;

Face *Face_MoTer3DAnim;
TK *TK_MoTer3DAnim;
Vertex *V_MoTer3DAnim;
Vertex DispV_MoTer3DAnim[3];
Animasi *Anim_MoTer3DAnim;

Lalu load objek animasi MoTer 3D dengan fungsi di bawah ini.

bool LoadMesh3DAnimX(char *FileName, float skala)
{
FILE *file;
errno_t err;
int i;

if ((err = fopen_s(&file, FileName, "r")) != 0)
{
MessageBox(NULL, _T("Mesh3DAnim load error"), _T("File tidak ditemukan!"), MB_OK);
return false;
}

fscanf_s(file, "%d", &TotalF_MoTer3DAnim);
fscanf_s(file, "%d", &TotalTK_MoTer3DAnim);
fscanf_s(file, "%d", &TotalV_MoTer3DAnim);
fscanf_s(file, "%d", &TotalFr_MoTer3DAnim);
fscanf_s(file, "%d", &TotalAnim_MoTer3DAnim);



//siapkan tempat untuk data mesh
Face_MoTer3DAnim = new Face[TotalF_MoTer3DAnim];
TK_MoTer3DAnim = new TK[TotalTK_MoTer3DAnim];
V_MoTer3DAnim = new Vertex[TotalV_MoTer3DAnim * TotalFr_MoTer3DAnim];
Anim_MoTer3DAnim = new Animasi[TotalAnim_MoTer3DAnim];

//load animasi
for (i = 0; i < TotalAnim_MoTer3DAnim; i++)
{
fscanf_s(file, "%s", &Anim_MoTer3DAnim[i].AnimName, 16);
fscanf_s(file, "%d", &Anim_MoTer3DAnim[i].Looping_YN);
fscanf_s(file, "%d", &Anim_MoTer3DAnim[i].FrameStart);
fscanf_s(file, "%d", &Anim_MoTer3DAnim[i].FrameEnd);
}


//load Face
for (i = 0; i < TotalF_MoTer3DAnim; i++)
{
fscanf_s(file, "%d", &Face_MoTer3DAnim[i].indexV1);
fscanf_s(file, "%d", &Face_MoTer3DAnim[i].indexV2);
fscanf_s(file, "%d", &Face_MoTer3DAnim[i].indexV3);

fscanf_s(file, "%d", &Face_MoTer3DAnim[i].indexTK1);
fscanf_s(file, "%d", &Face_MoTer3DAnim[i].indexTK2);
fscanf_s(file, "%d", &Face_MoTer3DAnim[i].indexTK3);

Face_MoTer3DAnim[i].indexV1--;
Face_MoTer3DAnim[i].indexV2--;
Face_MoTer3DAnim[i].indexV3--;

Face_MoTer3DAnim[i].indexTK1--;
Face_MoTer3DAnim[i].indexTK2--;
Face_MoTer3DAnim[i].indexTK3--;
}

//load TK
for (i = 0; i < TotalTK_MoTer3DAnim; i++)
{
fscanf_s(file, "%f", &TK_MoTer3DAnim[i].s);
fscanf_s(file, "%f", &TK_MoTer3DAnim[i].t);
}

//load Vertex
for (i = 0; i < (TotalV_MoTer3DAnim * TotalFr_MoTer3DAnim); i++)
{
fscanf_s(file, "%f", &V_MoTer3DAnim[i].X);
fscanf_s(file, "%f", &V_MoTer3DAnim[i].Y);
fscanf_s(file, "%f", &V_MoTer3DAnim[i].Z);

V_MoTer3DAnim[i].X = skala * V_MoTer3DAnim[i].X;
V_MoTer3DAnim[i].Y = skala * V_MoTer3DAnim[i].Y;
V_MoTer3DAnim[i].Z = skala * V_MoTer3DAnim[i].Z;

RotasiX(-90.0, V_MoTer3DAnim[i].X, V_MoTer3DAnim[i].Y, V_MoTer3DAnim[i].Z);
}

fclose(file);

return true;
}

Terakhir tampilkan di layar monitor.

//...........................
//display object  di sini
//...........................
Sudut_Putar = Sudut_Putar + 0.1f;
if (Sudut_Putar >= 360.0f)
Sudut_Putar = 0.0f;

glTranslatef(0.0f, 0.0f, -8.0f);
glPushMatrix();
glRotatef(Sudut_Putar, 1.0f, 1.0f, 0.0f);

glBindTexture(GL_TEXTURE_2D, Tekstur1);

if (GetKeyState('0') & 0x80)
{
Baling2Sound.Stop();
BerubahSound.Stop();

IndexAnimasi = 0;
CurrFr_MoTer3DAnim = Anim_MoTer3DAnim[IndexAnimasi].FrameStart;
NextFr_MoTer3DAnim = CurrFr_MoTer3DAnim + 1;
}

if (GetKeyState('1') & 0x80)
{
Baling2Sound.Stop();
BerubahSound.Play(false);

IndexAnimasi = 1;
CurrFr_MoTer3DAnim = Anim_MoTer3DAnim[IndexAnimasi].FrameStart;
NextFr_MoTer3DAnim = CurrFr_MoTer3DAnim + 1;
}

if (GetKeyState('2') & 0x80)
{
Baling2Sound.Stop();
BerubahSound.Play(false);

IndexAnimasi = 2;
CurrFr_MoTer3DAnim = Anim_MoTer3DAnim[IndexAnimasi].FrameStart;
NextFr_MoTer3DAnim = CurrFr_MoTer3DAnim + 1;
}

if (GetKeyState('3') & 0x80)
{
Baling2Sound.Stop();
BerubahSound.Stop();

IndexAnimasi = 3;
CurrFr_MoTer3DAnim = Anim_MoTer3DAnim[IndexAnimasi].FrameStart;
NextFr_MoTer3DAnim = CurrFr_MoTer3DAnim + 1;
}

if (GetKeyState('4') & 0x80)
{
Baling2Sound.Play(true);
BerubahSound.Stop();

IndexAnimasi = 4;
CurrFr_MoTer3DAnim = Anim_MoTer3DAnim[IndexAnimasi].FrameStart;
NextFr_MoTer3DAnim = CurrFr_MoTer3DAnim + 1;
}

Skala_MoTer3DAnim += 0.025f;
if (Skala_MoTer3DAnim > 1.0f)
{
Skala_MoTer3DAnim = 0.0f;
CurrFr_MoTer3DAnim++;
NextFr_MoTer3DAnim++;

if ((NextFr_MoTer3DAnim > Anim_MoTer3DAnim[IndexAnimasi].FrameEnd) &&
(Anim_MoTer3DAnim[IndexAnimasi].Looping_YN == 1))
{
NextFr_MoTer3DAnim = Anim_MoTer3DAnim[IndexAnimasi].FrameStart;
}

if ((CurrFr_MoTer3DAnim > Anim_MoTer3DAnim[IndexAnimasi].FrameEnd) &&
(Anim_MoTer3DAnim[IndexAnimasi].Looping_YN == 1))
{
CurrFr_MoTer3DAnim = Anim_MoTer3DAnim[IndexAnimasi].FrameStart;
}

if ((NextFr_MoTer3DAnim > Anim_MoTer3DAnim[IndexAnimasi].FrameEnd) &&
(Anim_MoTer3DAnim[IndexAnimasi].Looping_YN == 0))
{
NextFr_MoTer3DAnim = Anim_MoTer3DAnim[IndexAnimasi].FrameEnd;
}

if ((CurrFr_MoTer3DAnim > Anim_MoTer3DAnim[IndexAnimasi].FrameEnd) &&
(Anim_MoTer3DAnim[IndexAnimasi].Looping_YN == 0))
{
CurrFr_MoTer3DAnim = Anim_MoTer3DAnim[IndexAnimasi].FrameEnd;
}
} //if (Skala_MoTer3DAnim > 1.0f) 


for (i = 0; i < TotalF_MoTer3DAnim; i++)
{
j = Face_MoTer3DAnim[i].indexV1 + (TotalV_MoTer3DAnim * CurrFr_MoTer3DAnim);
k = Face_MoTer3DAnim[i].indexV1 + (TotalV_MoTer3DAnim * NextFr_MoTer3DAnim);
DispV_MoTer3DAnim[0].X = V_MoTer3DAnim[j].X + (V_MoTer3DAnim[k].X - V_MoTer3DAnim[j].X) * Skala_MoTer3DAnim;
DispV_MoTer3DAnim[0].Y = V_MoTer3DAnim[j].Y + (V_MoTer3DAnim[k].Y - V_MoTer3DAnim[j].Y) * Skala_MoTer3DAnim;
DispV_MoTer3DAnim[0].Z = V_MoTer3DAnim[j].Z + (V_MoTer3DAnim[k].Z - V_MoTer3DAnim[j].Z) * Skala_MoTer3DAnim;

j = Face_MoTer3DAnim[i].indexV2 + (TotalV_MoTer3DAnim * CurrFr_MoTer3DAnim);
k = Face_MoTer3DAnim[i].indexV2 + (TotalV_MoTer3DAnim * NextFr_MoTer3DAnim);
DispV_MoTer3DAnim[1].X = V_MoTer3DAnim[j].X + (V_MoTer3DAnim[k].X - V_MoTer3DAnim[j].X) * Skala_MoTer3DAnim;
DispV_MoTer3DAnim[1].Y = V_MoTer3DAnim[j].Y + (V_MoTer3DAnim[k].Y - V_MoTer3DAnim[j].Y) * Skala_MoTer3DAnim;
DispV_MoTer3DAnim[1].Z = V_MoTer3DAnim[j].Z + (V_MoTer3DAnim[k].Z - V_MoTer3DAnim[j].Z) * Skala_MoTer3DAnim;

j = Face_MoTer3DAnim[i].indexV3 + (TotalV_MoTer3DAnim * CurrFr_MoTer3DAnim);
k = Face_MoTer3DAnim[i].indexV3 + (TotalV_MoTer3DAnim * NextFr_MoTer3DAnim);
DispV_MoTer3DAnim[2].X = V_MoTer3DAnim[j].X + (V_MoTer3DAnim[k].X - V_MoTer3DAnim[j].X) * Skala_MoTer3DAnim;
DispV_MoTer3DAnim[2].Y = V_MoTer3DAnim[j].Y + (V_MoTer3DAnim[k].Y - V_MoTer3DAnim[j].Y) * Skala_MoTer3DAnim;
DispV_MoTer3DAnim[2].Z = V_MoTer3DAnim[j].Z + (V_MoTer3DAnim[k].Z - V_MoTer3DAnim[j].Z) * Skala_MoTer3DAnim;

glBegin(GL_TRIANGLES);
//glBegin(GL_LINE_LOOP);
glTexCoord2f(TK_MoTer3DAnim[Face_MoTer3DAnim[i].indexTK1].s, TK_MoTer3DAnim[Face_MoTer3DAnim[i].indexTK1].t);
glVertex3f(DispV_MoTer3DAnim[0].X, DispV_MoTer3DAnim[0].Y, DispV_MoTer3DAnim[0].Z);

glTexCoord2f(TK_MoTer3DAnim[Face_MoTer3DAnim[i].indexTK2].s, TK_MoTer3DAnim[Face_MoTer3DAnim[i].indexTK2].t);
glVertex3f(DispV_MoTer3DAnim[1].X, DispV_MoTer3DAnim[1].Y, DispV_MoTer3DAnim[1].Z);

glTexCoord2f(TK_MoTer3DAnim[Face_MoTer3DAnim[i].indexTK3].s, TK_MoTer3DAnim[Face_MoTer3DAnim[i].indexTK3].t);
glVertex3f(DispV_MoTer3DAnim[2].X, DispV_MoTer3DAnim[2].Y, DispV_MoTer3DAnim[2].Z);
glEnd();
}
glPopMatrix();

display_text(My_ListBase1, -2.5, 1.6, "Tekan key '0', '1', '2', '3' atau '4'");
display_text(My_ListBase1, -2.5, 1.5, "Animasi    :");
display_text(My_ListBase1, -1.75, 1.5, Anim_MoTer3DAnim[IndexAnimasi].AnimName);

display_text(My_ListBase1, -2.5, 1.4, "Looping    :");

if (Anim_MoTer3DAnim[IndexAnimasi].Looping_YN == 1)
display_text(My_ListBase1, -1.75, 1.4, "Yes");
else
display_text(My_ListBase1, -1.75, 1.4, "No");

display_text(My_ListBase1, -2.5, 1.3, "FrameStart :");
_itoa_s(Anim_MoTer3DAnim[IndexAnimasi].FrameStart, buffer, 10);
display_text(My_ListBase1, -1.75, 1.3, buffer);

display_text(My_ListBase1, -2.5, 1.2, "FrameEnd   :");
_itoa_s(Anim_MoTer3DAnim[IndexAnimasi].FrameEnd, buffer, 10);
display_text(My_ListBase1, -1.75, 1.2, buffer);

Untuk memainkan sound effect Anda dapat membaca link artikel ini jika kurang jelas.

Ok rekan-rekan sekalian, tuntas sudah artikel kali ini, ngak nyangka ya selesai juga. Di artikel berikutnya ogut masih terus akan membahas tentang animasi 3D yang sangat berguna untuk membuat game 3D, jangan bosan ya, dan jangan lupa rekan-rekan harus perdalam ilmu matematika terutama vektor R3.

Salam MoTer 3D


Heriady
heriady.yoh@gmail.com




Artikel terkait
Membuat Terrain untuk Hely 3D

Membuat Animasi Hely 3D dengan OpenGL

Model / Objek 3D dengan 3ds max

Konsep Face dan Metode 3 Daftar

Teori Animasi 3D dengan Vektor R3

Contoh Program Animasi 3D dengan OpenGL

Animasi dengan Vektor Translasi

Animasi dengan Vektor Translasi 2

Animasi 3D dengan Objek MD2

Matematika Vektor R3

Matematika Vektor R2