Bu makalemizde, sahnemizde yer alan nesneleri hareketlendireceğiz. Aslında bundan önce anlatılacak çok fazla konu vardı ama Three.JS gibi bir kütüphaneyi durağan şekillerle anlatmanın hiçbir zevki olmuyor. Şu animasyon konusunu bir aradan çıkartalım, sonrasında ağzımızın tadıyla diğer konulara devam ederiz inşallah.

İşin aslı, şu anda sahnedeki nesneleri hareketlendirmek için gerekli olan her şeyi biliyorsun sayılır. Sadece teoriyi pratiğe dökmemiz gerekiyor. Biraz geçmişe dönüp hatırlayalım. Bu serideki "Emeklemeye Başlıyoruz" makalesinin sonunda, şimdiye kadarki tüm örnek kodlarımızda yer alan "engine.runRenderLoop" fonksiyon çağrımı hakkında ne demiştik?

"Bu blok aslında sonsuz bir döngüyü ifade ediyor ve bu döngü içerisinde belirtilen kameranın gözünden belirtilen sahnenin fotoğrafı çekilerek, Canvas elemanına yansıtılıyor. Buradaki "renderer.render(scene, camera);" komutu, saniyede en fazla 60 kez olacak şekilde çağırılıyor."


Bu da demek oluyor ki, "animation" fonksiyonumuza yazdığımız kodlar, saniyede en fazla 60 kez çalıştırılacak anlamına geliyor. Biz şimdiye kadar bu blokta tek bir kod bulundurduk, o da "renderer.render(scene, camera);". Yani fotoğrafı çek ve Canvas'a yerleştir. O halde fotoğraf çekilmeden hemen öncesinde araya girip muzurluk yapsak nasıl olur acaba? Hemen görelim.

İlk olarak aşağıdaki kodlarla gerekli olan HTML sayfamızı hazırlayalım.

<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1254" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="three-r61.min.js"></script>
<script src="program.js"></script>
</head>
<body>
</body>
</html>

Ardından aşağıdaki kodlarla "program.js" dosyamızı hazırlayalım.

"use strict";

var renderer, scene, camera, box, direction = 1;

$(document).ready(function () {

    addContainer();

    setStyle();

    prepareScene();

    addBox();

    animation();

});

function addContainer() {

    $("body").append("
<br>"); } function setStyle() { $("html, body, #Container").css("width", "100%"); $("html, body, #Container").css("height", "100%"); $("html, body, #Container").css("padding", "0"); $("html, body, #Container").css("margin", "0"); $("html, body, #Container").css("overflow", "hidden"); $("html, body, #Container").css("backgroundColor", "#000000"); } function prepareScene() { var container = $("#Container"); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(container.width(), container.height()); container.append(renderer.domElement); scene = new THREE.Scene(); var aspectRatio = container.width() / container.height(); camera = new THREE.PerspectiveCamera(45, aspectRatio, 0.1, 20000); camera.position.set(50, 70, 300); var ambientLight = new THREE.HemisphereLight(0xffffff, 0x222222, 0.9); scene.add(ambientLight); } function addBox() { box = new THREE.Mesh(new THREE.CubeGeometry(20, 20, 20), new THREE.MeshLambertMaterial({ color: 0xff0000 })); scene.add(box); } function animation() { checkDirection(); moveBox(); scaleBox(); renderer.render(scene, camera); requestAnimationFrame(animation); } function checkDirection() { if (box.position.x < -20) { direction = 1; } else if (box.position.x > 20) { direction = -1; } } function moveBox() { box.position.x += direction * 0.5; } function scaleBox() { box.scale.y += direction * 0.01; }

Bu örneğin hazır yazılmışını çalıştırmak için buraya tıklayabilirsin. Karşına aşağıdaki gibi bir sayfanın gelmesi gerekiyor.


Kodun büyük çoğunluğu, bir önceki makalede bulunan örneklere benziyor. Sahnemizde yine bir kutumuz var. Peki farklı neler var, onlara bakalım.


var renderer, scene, camera, box, direction = 1;


İlk değişiklik olarak, "direction" adında ve ilk değeri 1 olan bir değişken tanımladık. Bu değişkeni, kutunun hangi tarafa doğru hareket edeceğini belirlemek için kullanacağız. Şimdilik 1 sağ, -1 sol diyelim (Ama bu sağ-sol mevzusunu bir önceki makalede konuşmuştuk. Yok aslında öyle bir şey).


function checkDirection() {

    if (box.position.x < -20) {

        direction = 1;

    else if (box.position.x > 20) {

        direction = -1;

    }

}

"checkDirection" adında bir fonksiyonumuz var. Bu fonksiyon her çağırılışta, kutunun x ekseni üzerindeki yerini kontrol ediyor ve -20'den daha da aşağı düşerse (sola giderse) "direction" değişkeninin değerini 1'e, 20'den daha da yukarı çıkarsa (sağa giderse) "direction" değişkeninin değerini -1'e eşitliyor.


function moveBox() {

    box.position.x += direction * 0.5;

}

"moveBox" adında bir fonksiyonumuz var. Bu fonksiyon her çağırılışta, kutunun x ekseni üzerindeki yerini, "direction" değişkeninin değerine göre 0.5 arttırıyor veya azaltıyor (direction -1 ise -0.5, direction 1 ise +0.5).


function scaleBox() {

    box.scaling.y += direction * 0.01;

}

Bir de "scaleBox" adında bir fonksiyonumuz var. Bu fonksiyon her çağırılışta, kutunun y ekseni üzerindeki boyutunu, "direction" değişkeninin değerine göre 0.01 arttırıyor veya azaltıyor (direction -1 ise -0.01, direction 1 ise +0.01).


function animation() {

    checkDirection();

    moveBox();

    scaleBox();

    renderer.render(scene, camera);

    requestAnimationFrame(animation);

}

İşte zavallı dananın kuyruğunun koptuğu an. Şimdiye kadarki tüm örneklerimizde, burada sadece "renderer.render(scene, camera);" fonksiyonunu çağırıyorduk ve saniyede en fazla 60 kereye kadar çalışarak Render işlemini gerçekleştiriyordu. Şimdi ise her Render işleminden önce ilk olarak "checkDirection" fonksiyonunu çağırdık ve kutunun konumuna göre "direction" değişkenini ayarladık. Ardından "moveBox" fonksiyonunu çağırdık ve kutunun sahnedeki konumunu değiştirdik. Sonrasında "scaleBox" fonksiyonunu çağırdık ve kutunun boyutunu değiştirdik. Tüm bu işlemlerden sonra "renderer.render(scene, camera);" fonksiyonunu çağırdık ve güncel halin fotoğrafını çektirdik.

Tüm bu akış saniyede 60 kez tekrarlandığı için karşımıza basit bir animasyon olarak çıktı. Nasıl, sabit duran kutudan daha eğlenceli değil mi? Arabamızla virajda dönerken tekerleklerin asfaltta bıraktığı izden tut da, düşman uçağa güdümlü füzemizi gönderirken ekranda göreceğimiz her şey, yukarıdaki örnekte anlatılan temellere dayanıyor. Değiştir, fotoğraf çek, değiştir, fotoğraf çek. Buraya kadar anlattıklarımın mantığını anlaman çok önemli. Eğer takıldığın bir yer varsa, çık yukarı bir daha oku, sonrasında devam edersin.

Eğer ki amacımız, web sitemize 3D animasyon içeren görseller eklemek olsaydı, bu kadarı bize yeterdi. Ama biz oyun yapmak istiyoruz. Oyunda nesneler kendi kafalarına göre hareket etmezler. Kullanıcının komutlarına göre hareket ederler. Haydi o halde, şöyle bir şey yapalım. Kutumuz ekranda dursun. W-A-S-D-Q-E tuşları ile kutumuzu sahnede dolaştırabilelim. W-S z ekseni boyunca ileri-geri, A-D x ekseni boyunca ileri-geri, Q-E ise y ekseni boyunca ileri-geri hareket ettirsin. "program.js" dosyamızın içeriğini aşağıdaki kod ile değiştirelim.

"use strict";

var renderer, scene, camera, box, pressedKeys = {};

var keyCodes = { W: 87, S: 83, A: 65, D: 68, Q: 81, E: 69 };

$(document).ready(function () {

    addContainer();

    setStyle();

    prepareScene();

    keyBinding();

    addBox();

    animation();

});

function addContainer() {

    $("body").append("
<br>"); } function setStyle() { $("html, body, #Container").css("width", "100%"); $("html, body, #Container").css("height", "100%"); $("html, body, #Container").css("padding", "0"); $("html, body, #Container").css("margin", "0"); $("html, body, #Container").css("overflow", "hidden"); $("html, body, #Container").css("backgroundColor", "#000000"); } function prepareScene() { var container = $("#Container"); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(container.width(), container.height()); container.append(renderer.domElement); scene = new THREE.Scene(); var aspectRatio = container.width() / container.height(); camera = new THREE.PerspectiveCamera(45, aspectRatio, 0.1, 20000); camera.position.set(50, 70, 300); var ambientLight = new THREE.HemisphereLight(0xffffff, 0x222222, 0.9); scene.add(ambientLight); } function keyBinding() { $(document).keydown(function (e) { pressedKeys[e.which] = true; }); $(document).keyup(function (e) { delete pressedKeys[e.which]; }); } function addBox() { box = new THREE.Mesh(new THREE.CubeGeometry(20, 20, 20), new THREE.MeshLambertMaterial({ color: 0xff0000 })); scene.add(box); } function animation() { moveBox(); renderer.render(scene, camera); requestAnimationFrame(animation); } function moveBox() { if (pressedKeys[keyCodes.W]) { box.position.z -= 1; } if (pressedKeys[keyCodes.S]) { box.position.z += 1; } if (pressedKeys[keyCodes.A]) { box.position.x -= 1; } if (pressedKeys[keyCodes.D]) { box.position.x += 1; } if (pressedKeys[keyCodes.Q]) { box.position.y -= 1; } if (pressedKeys[keyCodes.E]) { box.position.y += 1; } }

Bu örneğin hazır yazılmışını çalıştırmak için buraya tıklayabilirsin. Karşına aşağıdaki gibi bir sayfanın gelmesi gerekiyor.


Haydi bakalım, neler yapmışız inceleyelim.

var renderer, scene, camera, box, pressedKeys = {};

Öncelikle "pressedKeys" adında yeni bir değişkenimiz bulunuyor. Bu değişken, hangi tuşların basılı olduğunu tutacak.

"keybinding" adında bir fonksiyon ekledik. buraya tekrar yapıştırmıyorum. Ama bu fonksiyon içerisinde sayfanın "keydown" ve "keyup" olaylarını yakalıyoruz. Ardından basılı olan tuşların "keyCode" değerleri üzerinden, yukarıdaki değişkenimize ekliyoruz veya çıkartıyoruz.

Burada dikkat edilmesi gereken bir konu, aslında bir soru ile başlamalı. "Kardeş, sen burada değişkenleri set ediyorsun. Belli ki animasyon metodu içerisinde bunlara göre kutuyu hareket ettireceksin. İyi de, burada tuşa basıldığı anda direk kutunun koordinatları ile oynasaydık ya. Ne diye kafa karıştırıyorsun?"

Anlatılanlara konsantre ol. Ne dedik? Her Render işleminden önce değiştir, fotoğraf çek. Bizim animasyon fonksiyonu saniyede kaç kez çalışıyor? En fazla 60 kez. Peki sen tuşa kaç kez basıyorsun? 1 kez. Bize, tuşa bastığın sürece her Render işleminden önce çalışacak bir şeyler lazım. Çeşitli taklalar atarak, tuşa bir kez basmana rağmen saniyede 60 kez çalışacak bir fonksiyon yazmak mümkün değil mi? Mümkün. İşe yarar mı? Hayır. Neden?

Tekrar ediyorum. Değiştir, fotoğraf çek. Bize, her render işleminden önce çalışacak bir şeyler lazım. Örneğin makine yavaş, sahne yoğun, 40 FPS'ye düştük. Oysa senin fonksiyon hala saniyede 60 kez çalışıyor. İki Render işlemi arasında sen değeri iki kez değiştirirsen ne olur? İlk değişikliğin görülmez, sadece son değişiklik dikkate alınır. Yani burada Render işlemi ile tam senkron bir fonksiyondan bahsediyoruz. Bunun da tek yolu, kodu buradaki "animation" fonksiyonunun içerisine yazmak. Browser seviyesindeki klavye, mouse, hatta joystick, kamera, hareket algılayıcılar, retina tarayıcılar ve envai çeşit olayları istediğimiz şekilde yakalayabiliriz. Ancak bu olaylara karşılık alınacak aksiyonlar, Render işlemi ile tam senkron bir fonksiyonun içerisinde yapılmak zorunda. İnşallah burada netizdir.

Pekala, "keybinding" fonksiyonunda sadece olayları yakaladık ve basılı tuşların değerlerini değişkenimize ekledik. Böylece bir tuşa bastığımızda, taa ki biz elimizi kaldırana kadar değişkenin içerisinde o tuşun keyCode değeri duracaktır.


function animation() {

    moveBox();

    renderer.render(scene, camera);

    requestAnimationFrame(animation);

}

"animation" fonksiyonumuzda ekstra tek bir fonksiyon var, ne güzel. Peki ne yapıyoruz bu fonksiyonda? (Uzun olduğu için yine buraya yapıştırmıyorum) "W" tuşuna basıldığı sürece, kutunun z eksenindeki koordinatını 1 azaltıyoruz. Aynı şekilde "S" tuşuna basıldığı sürece de arttırıyoruz. Benzer şekilde diğer tuşlarla da x ve y eksen koordinatlarını değiştiriyoruz. Böylece olaylar üzerinden değişkenin değerlerini, değişken üzerinden de kutunun sahnedeki koordinatlarını değiştirmiş oluyoruz.

Bu makale ile birlikte, oyun yapmaya bir adım daha yaklaşmış olduk. Henüz yolumuz var ama burada anlatılan temel mantıklar çok önemli. Makaleyi noktalarken öğrendiklerimizi özetleyelim.
  • "renderer.render(scene, camera);" fonksiyon çağrımından önce sahnedeki nesneler ile oynayarak ortaya bir animasyon çıkartabiliriz.
  • Değiştir, fotoğraf çek, değiştir, fotoğraf çek.
  • Tarayıcı olaylarını yakalayarak, bunlarla sahnedeki nesneleri ilişkilendirirsek, nesnelerin kullanıcı ile etkileşime girmesini sağlayabiliriz.
  • Sahnedeki nesnelere uygulanacak değişiklikler, Render işlemi ile tam senkron bir fonksiyon içerisinde olmalıdır.


Yorum Gönder

 
Top