Pengenalan Game Design

Transformasi

Dalam Processing, setiap bentuk ataupun gambar yang telah dibuat, bisa ditransformasikan lebih lanjut, atau diubah penampilannya. Jadi, kalau sejauh ini kita telah bermain-main dengan posisi dan warna bentuk, maka kita bisa melakukan transformasi lebih lanjut, dengan mengubah ukuran, memutar, serta menggerakkan objek-objek ini dalam sumbu x, y atau z. Tentu saja, dengan mengatakan objek, artinya prinsip-prinsip ini berlaku baik untuk bentuk (ellipse(), rect() atau triangle()) maupun gambar (PImage).

Matrix

Ketika Processing menggambar ke layar, yang sebenarnya dilakukan adalah menggambar setiap objek ini di atas sebuah matrix. Matrix ini bisa dibayangkan sebagai sebuah lapisan (layer) di atas layar. Apabila yang digambar adalah beberapa bentuk, maka setiap bentuk ini kemudian ditampilkan bertumpuk di lapisan yang sama, sesuai urutan pemanggilan objek tersebut.

Jika kita ingin menggambar di atas matrix yang berbeda, maka yang bisa kita lakukan adalah memanggil fungsi pushMatrix() terlebih dahulu, lalu memanggil fungsi apapun untuk menggambar dan kemudian keluar dari matrix itu dengan memanggil fungsi popMatrix(). Apabila kita kemudian untuk menggambar lagi, maka gambar akan ditampilkan di matrix yang berbeda. Apabila kedua fungsi matrix ini tidak dipanggil, maka Processing akan mengasumsikan bahwa semua gambar ditampilkan di matrix yang sama. Dengan demikian, maka source code berikut:

rect(40, 40, 40, 40);

akan menampilkan hal yang sama dengan

pushMatrix();
rect(40, 40, 40, 40);
popMatrix();

Fungsi pushMatrix() akan mendorong matrix yang sedang aktif, ke dalam tumpukan matrix (matrix stack). Fungsi ini lalu akan menyimpan sistem koordinat yang sedang digunakan ke dalam stack. Ketika fungsi popMatrix() dipanggil, maka matrix tadi akan keluar dari matrix stack dan sistem koordinat akan kembali ke keadaan semula. Kedua fungsi ini harus digunakan berpasangan. Sebuah pemanggilan pushMatrix() akan ditutup dengan popMatrix(). Processing cukup berbaik hati untuk memberikan pesan error apabila ada salah satu fungsi saja yang dipanggil.

Setiap kali kita akan melakukan translasi, rotasi atau mengatur ulang ukuran objek (scaling), dibutuhkan informasi-informasi yang tersimpan dalam sebuah tabel. Tabel inilah yang disebut matrix, dan dengan perhitungan matematis, ia mampu membuat transformasi terjadi. Sehingga, setelah kita melakukan pemanggilan pushMatrix() dan popMatrix(), nilai sistem koordinat akan kembali ke keadaan awal, korrdinat (0,0) berada di pojok kiri atas layar, tidak ada rotasi dan scaling. Ini juga terjadi setiap fungsi draw() dieksekusi.

Pemahaman akan matrix ini akan sangat membantu saat kita mulai melakukan transformasi. Ini karena setiap transformasi pada dasarnya akan bermain dengan matrix transformasi dan sistem koordinat. Lebih jelasnya akan dijelaskan di bagian berikutnya.

Translasi

Transformasi pertama yang akan kita bahas adalah translasi. Transformasi ini berarti bahwa kita akan melakukan pemindahan posisi menurut sumbu tertentu. Contoh ketika kita melakukan translasi di sumbu x sebanyak 20 pixel, artinya objek akan bergerak ke kanan sebanyak 20 pixel pula. Translasi ini dilakukan oleh fungsi translate().

Sintaks penulisan fungsi translate() adalah

translate(x,y,z);

Di mana ketiga parameter akan menentukan seberapa banyak dan di sumbu koordinat manakah translasi terjadi. Agar bisa berjalan, maka setidaknya salah satu parameter harus terisi, atau bernilai bukan 0. Apabila program Processing kita berjalan dalam sistem 2 dimensi, maka parameter untuk sumbu z tidak perlu diisi dan formatnya cukup dalam bentuk translate (x,y).

Contoh berikut ini menunjukkan penggunaan translasi untuk menggerakkan sebuah persegi

Contoh 8-1 Translasi Objek

int posX, posY;

void setup() {
    size (600, 600);
    noStroke();
}

void draw() {
    background (170);

    // lakukan translasi di dalam matrix baru
    pushMatrix();
    translate (posX, posY);
    fill (100, 0, 200);
    rect (0, 0, 40, 40);
    popMatrix();

    // ubah nilai, agar benda bergerak
    posX++;
    posY++;
}

Ketika kode tadi dijalankan, maka bisa kita lihat bahwa persegi kita bergerak diagonal dari pojok kiri atas ke arah kanan bawah layar. Perhatikan bagaimana kita menulis fungsi rect()

rect (0, 0, 40, 40);

Artinya, kita selalu menggambar persegi di koordinat (0,0). Namun, meski demikian, persegi tetap bisa bergerak. Mengapa demikian? Ini karena kita mentranslasikan sistem koordinat tempat menggambar persegi tersebut. Hingga, meskipun koordinat posisi persegi tidak berubah, persegi tetap bergerak, karena dunia tempat menggambarnya-lah yang berpindah.

Sekarang, coba kita ubah sedikit contoh tadi menjadi seperti berikut:

int posX, posY;

void setup() {
    size (600, 600);
    noStroke();
}

void draw() {
    background (170);

    fill (0, 200, 200);
    rect (0, 0, 40, 40);

    // tanpa membuat matrix baru
    translate (20, 20);
    fill (100, 0, 200);
    rect (0, 0, 40, 40);

    fill (0, 200, 0);
    rect (0, 0, 40, 40);
}

Kita bisa melihat bahwa meskipun kita memiliki 2 buah persegi yang dibuat dengan fungsi identik, rect (0, 0, 40, 40); ternyata kedua persegi ini digambar di titik yang berbeda. Ini dikarenakan persegi kedua digambar setelah sistem koordinat ditranslasikan. Apabila setelah membuat persegi kedua, yang berwarna ungu, kita putuskan untuk membuat persegi ketiga dengan perintah yang sama, maka persegi ini akan muncul di atas persegi yang kedua.

Namun bandingkan dengan source code berikut, di mana kita akan membuat persegi kedua dan mentranslasikannya di matrix baru:

int posX, posY;

void setup() {
    size (600, 600);
    noStroke();
}

void draw() {
    background (170);

    fill (0, 200, 200);
    rect (0, 0, 40, 40);

    // tanpa membuat matrix baru
    pushMatrix();
    translate (20, 20);
    fill (100, 0, 200);
    rect (0, 0, 40, 40);
    popMatrix();

    fill (0, 200, 0);
    rect (0, 0, 40, 40);
}

Ketika ini dilakukan, maka persegi ketiga akan muncul di atas persegi pertama. Ini karena sistem koordinat sudah kembali ke keadaan semula dan translasi hanya berlaku di nilai matrix yang dibuat sebelumnya. Pahami baik-baik konsep ini, karena akan sangat membantu dalam melihat apa yang terjadi saat transformasi dilakukan. Tentu saja, konsep ini akan berlaku juga dalam fungsi transformasi yang lain.

Selain untuk menggerakkan objek, karena translasi ini pada dasarnya adalah mengubah sistem koordinat, maka kita bisa menggunakannya untuk menggambar beberapa bentuk di posisi berbeda dengan lebih sedikit variabel.

Sebagai contoh, simak program berikut, di mana kita akan menggambar 5 lingkaran secara berurutan di sumbu x. di tengah layar. Dengan menggunakan apa yang sudah kita pelajari sebelumnya, kita bisa menuliskan kodenya seperti berikut:

void setup() {
  size (400, 400);
  smooth();
  noStroke();
  fill (240, 100, 0);
}

void draw() {
  background (200);
  for (int i=0; i

Maka dengan menggunakan translasi, kita bisa menuliskannya seperti ini:

Contoh 8-2 Menggambar Beberapa Bentuk dengan Translasi

void setup() {
  size (400, 400);
  smooth();
  noStroke();
  fill (240, 100, 0);
}

void draw() {
  background (200);
  for (int i=0; i

Kali ini, perhitungan matematis dengan variabel yang dijadikan argumen untuk menentukan posisi lingkaran pada sumbu x, kita pecah. Sebelumnya kita puunya argumen (i*75)+50). Kita ketahui bahwa i*75 adalah pertambahan nilai parameter koordinat sumbu x tempat menggambar lingkaran, yang juga berarti bahwa setelah menggambar 1 lingkaran, geser posisi menggambar sejauh 75 pixel. Tentu saja, ini berarti translate (75, 0). Penambahan nilai 50 di akhir, adalah penyesuain posisi agar sususnan lingkaran berada tepat di tengah layar, yang berarti posisi lingkaran pertama berada di koordinat x = 50. Maka dari itu, proses menggambar ini pun dipecah menjadi langkah-langkah berikut:

  1. Gambarkan lingkaran pada posisi koordinat x=50
  2. Geser koordinat tempat menggambar sejauh 75 pixel pada sumbu x
  3. Gambar lingkaran kedua dan ulangi langkah 1 dan 2
  4. Ulangi hingga lingkaran kelima

Simak bahwa sekarang kita tidak memerlukan tambahan variabel untuk menggambar beberapa lingkaran. Artinya, kita sudah mengurangi sedikit beban dalam memprogram. Ini akan membantu kita saat harus menguji coba program yang lebih panjang dan berat.

Rotasi

Rotasi adalah jenis transformasi yang memungkinkan kita memutar sebuah benda. Transformasi ini dilakukan dengan memanggi fungsi rotate(). Sintaks penulisan fungsi ini adalah:

rotate (sudut)

Secara default, fungsi ini akan melakukan rotasi pada objek dengan berorientasi pada sumbu Z (sumbu yang tegak lurus dari layar komputer, menghadap pengguna). Sudut yang digunakan mirip dengan yang dipakai oleh fungsi sin() di mana yang diharapkan adalah sudut dalam radian, besarnya mulai dari 0 sampai TWO_PI (2 kali PI). Untuk mengubah dari sudut ke dalam radian, kita bisa menggunakan fungsi radians(). Parameter fungsi rotate() dengan nilai positif berarti perputaran akan dilakukan searah jarum jam.

Dengan demikian, kita bisa memutar sebuah persegi sebanyak 45 derajat searah jarum jam, dengan menggunakan program seperti berikut:

Contoh 8-3 Melakukan Rotasi

void setup() {
    size (400, 400);
    noStroke();
}

void draw() {
    background (240);
    fill (100, 0, 0);
    pushMatrix();
    translate (100, 100);
    rotate (radians(45));
    rect (0, 0, 100, 100);
    popMatrix();
}

Hasilnya mungkin cukup intuitif untuk ditebak. Sebuah persegi yang diputar sebesar 45 derajat. Namun, untuk apa fungsi translate() digunakan di situ? Sebenarnya ia digunakan untuk menggeser sistem koordinat sebelum melakukan rotasi, agar persegi bisa terlihat penuh. Tanpa memanggil itu, maka hanya setengah persegi saja yang nampak.

Untuk melihat langsung efek dari memutar sistem koordinat, perhatikan contoh berikut di mana sebuah persegi seukuran layar akan digambar dan kemudian ia akan diputar sebesar 45 derajat arah jarum jam.

void setup() {
    size (400, 400);
    noStroke();
}

void draw() {
    background (240);
    fill (100, 0, 0);
    pushMatrix();
    rotate (radians(45));
    rect (0, 0, width, height);

    //lingkaran untuk menandakan sudut kiri atas layar
    fill (90);
    ellipse (0, 0, 10, 10);
    popMatrix();
}

Pertama-tama, coba jalankan program di atas tanpa melakukan rotasi, tentu akan didapat layar penuh berwarna merah bata. Bayangkan ini sebagai layar dalam keadaan default tanpa perubahan pada sistem koordinasi.

Setelah rotasi dilakukan, maka bisa kita lihat bahwa persegi tadi sudah berputar 45 derajat arah jarum jam, dengan lingkaran sebagai titik tumpunya tetap berada di kiri atas. Artinya, kini sistem koordinasi sudah berubah orientasinya. Maka dari itu apabila kita tetap menggambar persegi pada koordinat (0,0) seperti pada contoh sebelumnya, tentulah tidak akan nampak penuh di layar.

Apabila kita menggunakan renderer P3D untuk membuat gambar dalam dunia 3 dimensi, maka kita bisa melakukan rotasi pada sumbu x, y atau z. Secara berurutan, kita bisa menggunakan fungsi

rotateX(sudut)
rotateY(sudut)
rotateZ(sudut)

Sebagai catatan, ketiga fungsi ini hanya berlaku untuk rendered P3D. Apabila kita menggunakan renderer P2D, atau menggambar dalam bidang 2 dimensi, maka hanya fungsi rotate() yang bisa digunakan.

Scaling

Transformasi ketiga yang akan kita lakukan adalah scaling atau mengubah skala sistem koordinat yang berakibat ukuran objek yang digambar pun bisa digambar. Scaling ini dilakukan dengan memanggil fungsi scale() yang dituliskan dalam format sintaks berikut:

scale(ukuran)

Ukuran ini kita isi dengan data bertipe float dan akan mengatur berapa besar perubahan ukuran terjadi. Untuk memperbesar ukuran maka kita gunakan nilai yang lebih besar dari 1, dan nilai di bawah 1 sampai 0 akan memperkecil ukuran objek.

Contoh jelas penggunaan fungsi ini ditunjukkan dalam program di bawah ini:

Contoh 8-4 Scaling

void setup() {
  size (400, 400);
  noStroke();
  background (240);
}

void draw() {
  // menggambar persegi pertama sebelum scaling
  fill (150, 0, 0);
  rect (20, 20, 100, 100);

  // menggambar persegi kedua setelah scaling
  pushMatrix();
  scale (2);
  fill (0, 150, 0);
  rect (20, 20, 100, 100);
  popMatrix();

  // menggambar persegi ketiga setelah scaling
  fill (0, 0, 150);
  rect (40, 40, 100, 100);
}

Program tersebut mencontohkan bagaimana menggunakan scale dan apa perubahan yang dihasilkan olehnya. Di sini kita punya 3 persegi. Persegi pertama berwarna merah digambarkan di titik koordinat (20, 20) dengan ukuran 100 pixel. Selanjutnya kita lihat, setelah sistem koordinat di-scale maka persegi kedua yang berwarna hijau, yang digambar dengan parameter yang sama ternyata muncul lebih ke kanan bawah dari persegi sebelumnya. Ukurannya pun lebih besar, 2x lebih panjang dan 2x lebih lebar. Kita bisa simpulkan bahwa ini adalah efek dari melakukan scaling.

Contoh di atas melakukan scaling dengan memperbesar ukuran antar titik dalam sistem koordinat. Ini bisa dibuktikan dengan keberadaan persegi ketiga, berwarna biru, yang digambar pada koordinat (40, 40), atau 2 kali lebih besar dari titik tempat menggambar persegi kedua, ternyata muncul di titik yang sama. Artinya, setelah melakukan scale (2.0), titik koordinat (20, 20) menjadi berada di titik (40, 40) pada sistem koordinat normal. Tentu saja, apabila melakukan scaling di dalam matrix baru, kita bisa keluar dan kembali ke sistem koordinat normal dengan menggunakan pushMatrix() dan popMatrix() seperti di contoh-contoh sebelumnya.

Sejauh ini, scaling yang kita lakukan mengakibatkan perubahan yang seragam baik di koordinat x maupun y. Sebenarnya kita bisa melakukan perubahan terpisah, di sumbu-sumbu tersebut. Gunakan sintaks berikut:

scale (ukuranX, ukuranY, ukuranZ)

Dengan demikian, kita bisa menentukan sendiri besar perubahan ukuran di sumbu x, di sumbu y dan sumbu z, tidak harus seragam. Lagi-lagi, scaling pada sumbu z hanya bisa dilakukan jika kita menggunakan P3D. Contoh penggunaanya bisa dilihat di contoh berikut:

Contoh 8-5 Scaling Terpisah

void setup() {
  size (400, 400);
  noStroke();
  background (240);
}

void draw() {
  // menggambar ellipse pertama sebelum scaling
  fill (150, 0, 0);
  ellipse (75, 75, 50, 50);

  // menggambar ellipse kedua setelah scaling
  pushMatrix();
  scale (2.0, 1.5);
  fill (0, 150, 0);
  ellipse (75, 75, 50, 50);
  popMatrix();

  // menggambar ellipse ketiga setelah scaling
  fill (0, 0, 150);
  ellipse (150, 113, 50, 50);
}

Konsep yang ditulis dari contoh sebelumnya masih bisa diaplikasikan di sini. Hanya saja, kali ini perlu diingat bahwa secara default ellipse digambar dari tengah, bukan dari kiri atas seperti persegi.

Aplikasi Transformasi

Sebagai penutup bab ini, ada beberapa catatan yang layak untuk disimak dalam mengaplikasikan teknik transformasi ini. Poin pertama adalah, transformasi, seperti yang ditunjukkan dalam contoh 8-3, berlaku secara berantai sekuensial. Artinya transformasi apapun bisa digabungkan dalam satu matrix dan urutan pemanggilannya akan berpengaruh pada kondisi akhir sistem koordinasi. Dengan demikian, maka memanggil

scale (2.0);
rotate (radians(30));
translate (0, 2);

Akan menghasilkan perubahan yang berbeda dengan melakukan

rotate (radians(30));
translate (0, 2);
scale (2.0);

Silakan coba urutan transformasi tadi dan simak perbedaan yang dihasilkan.

Poin kedua adalah transformasi yang kita lakukan di sini, meski selalu dicontohkan menggunakan objek berupa bentuk, berlaku juga untuk gambar seperti yang pernah kita buat di bab sebelumnya. Sebagai contoh, untuk memutar dan membesarkan sebuah gambar, kita bisa membuat program kira-kira seperti ini:

Contoh 8-6 Transformasi Gambar

PImage gambar;

void setup() {
  size (400, 400);
  gambar = loadImage ("photo.JPG");
  noStroke();
  background (240);
}

void draw() {
  // gambar dalam kondisi awal
  image (gambar, 10, 10, 100, 100);

  // gambar setelah transformasi
  pushMatrix();
  translate (200, 0);
  rotate (radians (60));
  scale (2.0);
  image (gambar, 10, 10, 100, 100);
  popMatrix();

}

Kita akan bermain lebih banyak dengan menganimasikan gambar dalam bab berikutnya.

Latihan 8-1

Gunakan metode transformasi yang sudah diajarkan, untuk membuat animasi sederhana, di mana gambar akan berputar searah jarum jam sembari berjalan dari kiri ke kana