KARADENİZ TEKNİK ÜNİVERSİTESİ BİLGİSAYAR MÜHENDİSLİĞİ BÖLÜMÜ BİLGİSAYAR GRAFİKLERİ LABORATUARI DirectX ile Tank Oyunu 1. Giriş Oyunlar, Bilgisayar Grafiklerinin en popüler uygulama alanlarından biridir ve kişisel bilgisayarlara ek olarak tabletler ve cep telefonları gibi farklı donanımları da destekleyen oyunların yaygınlaşmasıyla birlikte artan oranda popüler olmaya devam edeceği öngörülmektedir. Bu deneyde DirectX 11 API ile basit bir Tank Oyununun nasıl geliştirilebileceğinden bahsedilecektir. 2. Tank Oyunu Fonksiyonları ve Görevleri Genel olarak DirectX uygulamalarında ve özellikle oyunlarda 3 temel fonksiyon kullanılır: InitDevice() : Oyuncular bufferlara (vertex, index) yüklenir. Bazı matris ve vektörler ilk değerlerine setlenir. Update() : Klavye/Mouse etkileşimine göre oyuncuların yeni konumu hesaplanır. Birbirlerine ateş etmişlerse isabet (kesişim) testleri yapılır. Render() : Oyuncular, World matrisleri yeni konumlarına göre güncellenerek çizilir. InitDevice(), uygulama çalıştırıldığında ilk koşan ve bir kere koşan fonksiyondur. Dolayısıyla oyuncuların yüklenmesi gibi uygulamada bir kez yapılacak işlemlere dair kodlar burada yazılmalıdır. Ayrıca vektörlere/matrislere ilk değer atama da burada yapılır. Deneyde yapılacak tank oyunu uygulamasında oyun ortamını oluşturan zeminin, duvarların, oyuncular olan tankların InitDevice() fonksiyonunda kendilerine ait köşe noktası, doku ve normal koordinatları bilgilerini içeren (.obj formatında) model dosyaları okunup ilgili vertex/index bufferlara yüklenir. InitDevice() fonksiyonunda ayrıca Eye, At ve Up vektörleri ilgili vektör değerlerine setlenir bu vektörler kullanılarak XMMatrixLookAtLH() ile View matrisi setlenir (934-937. satırlar arası). Eye = XMVectorSet(0.0f, 3.0f, -60.0f, 0.0f); At = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); Up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f); g_view = XMMatrixLookAtLH(Eye, At, Up); 1
InitDevice() fonksiyonunda son olarak, görüş açısı (field of view angle), pencerenin yatay/düşey oranı (aspect ratio), yakın ve uzak z-düzlemlerini (near/far z-planes) parametre olarak alan XMMatrixPerspectiveFovLH() ile perspektif projeksiyon matrisi setlenir (940. satır). Update işlemi iki fonksiyon ile gerçeklenmiştir: DetectInput() UpdateCamera() DetectInput() içinde klavye/mouse etkileşimine göre ilgili değişkenler setlenmektedir. Örneğin tankın W,A,S,D tuşları ile hareket etmesi için Translation vektörü hesaplanmaktadır. Bu tuşlarla oyun ortamında ilerlerken duvarların içinden geçilmemesi için ray tracing yöntemi kullanılır. Buna göre Ro ve Rd vektörleri hesaplanır ve IntersectTriangle() dan dönen t-uzaklığı belli bir değerin altına inince hareket engellenir. Tank düşmana (enemy) SPACE tuşu ile ateş etmektedir. Bu tuşa basıldığında namlunun ucundan top mermisi çıkmakta ve düşmana doğru hareket etmektedir. Merminin başlangıç noktası namlunun ucu olacak şekilde şöyle setlenir (programda 396-401. satırlar arası): XMVECTOR Namlu_Ucu = 6 * XMVector3Normalize(At - Eye); XMFLOAT4 Namlu_Ucu_F4; XMStoreFloat4(&Namlu_Ucu_F4, Namlu_Ucu); g_world_ball = g_world_tank; g_world_ball._41 = g_world_ball._41 + Namlu_Ucu_F4.x; g_world_ball._42 = g_world_ball._42 + 1.5 + Namlu_Ucu_F4.y; g_world_ball._43 = g_world_ball._43 + Namlu_Ucu_F4.z; Namlunun boyu 6 birim olduğundan merminin hareket doğrultusu olan (At-Eye) vektörü 6 ile çarpılıp merminin World matrisinin (g_world_ball) Translation bileşenlerine eklenmiştir. Merminin 4x4 World matrisinin Translate (öteleme) işlemi yapan bileşenleri _41, _42 ve _43 tür. _42 ye ayrıca 1.5 değeri eklenmiştir. Bu da namlunun yerden yüksekliğidir. Merminin (At-Eye) doğrultusu (balldirection) boyunca ilerlemesi Render() fonksiyonunda yine _41, _42 ve _43 bileşenleri benzeri şekilde setlenerek yapılmaktadır (1128-1133. satırlar arası). 2
UpdateCamera() içinde (DetectInput() ta) basılan tuşa göre Eye, At ve Up vektörleri setlenir. Sonra bu vektörler kullanılarak g_view matrisi setlenir: g_view = XMMatrixLookAtLH(Eye, At, Up); Hareket ettikçe g_view matrisi Eye, At ve Up vektörlerinin yeni değerlerine göre sürekli güncellenir. UpdateCamera() içinde ayrıca merminin düşmana isabet edip/etmediği de ray tracing ile test edilir. Yollanacak ışının başlangış noktası ve doğrultusu: Ro = Eye + XMVectorSet(0.0f, -1.5f, 0.0f, 0.0f); Rd = XMVector3Normalize(At - Eye); ile hesaplanır. Burada Ro, Eye vektörünün y-bileşeninden 1.5 çıkarılarak hesaplanmıştır. Çünkü Eye vektörünün y-bileşeni 3 tür. Namlunun yerden yüksekliği 1.5 olduğundan 3-1.5=1.5 olması için 1.5 çıkarılmıştır. IntersectTriangle() ile kesişim testleri yapılır ve dönen t-uzaklıklarının minumumu düşmana aitse ve mermi düşman tankına değmişse düşmanın canlı olup/olmadığını test eden renderenemy değişkeni false yapılarak düşman öldürülür (artık render edilmez). Aynı zamanda rendertankball de false yapılarak mermi de artık çizilmez (463-543. satırlar arası). Düşman tankı canlandırmak (tekrar çizmek) için sol mouse butonuna tıklanır. Merminin düşman tankına değip değmediğinin belirlenmesi için IntersectTriangle() dan dönen t-uzaklığı ile düşman üzerindeki kesişim noktasının konumu RedDotPosition hesaplanır. Değişken ismi bu şekilde verilmiştir çünkü bu kesişim noktası oyunda sarı bir daire (nişangah) ile işaret edilmektedir. RedDotPosition ile merminin o andaki konumu currentballposition ın fark vektörünün boyu belli bir değerin (0.5) altına düşünce merminin düşmana isabet ettiği sonucuna varılır ve yukarıda da söylendiği gibi renderenemy/rendertankball değişkenleri false yapılır. XMVectorGetX(XMVector3Length(RedDotPosition - currentballposition)) < 0.5) XMVector3Length() aslında skaler bir değer döndürmelidir ama DirectX in matematik kütüphanesindeki vektörel işlem fonksiyonlarının tamamı vektör döndürmektedir. Dönen bu vektörden örneğin yukarıdaki gibi fark vektörünün boyunu okuyabilmek için XMVectorGetX() kullanılmıştır. Bunun yerine XMVectorGetY() ya da XMVectorGetZ() de olabilirdi. DirectX in matematik kütüphanesi fonksiyonlarına buradan erişebilirsiniz. Bu föyde kullanılan fonksiyonlar Geometric Functions ve Transformation Functions linklerindedir. Render() fonksiyonu bilindiği gibi çizim işlemleri için kullanılır. Burada sırasıyla ışık kaynakları, nişangah (reddot), zemin, duvarlar, oyuncuyu temsil eden tank, o tankın ateş etmesi sonucu yollanacak mermi ve son olarak düşman (enemy) tankı çizilir. 3
Bakış noktası yukarıda da söylendiği gibi yerden 3 birim yüksekliktedir. W,A,S,D tuşları ile bakış noktası güncellenirken önünde tankın da görülmesi için bakış noktası tanktan 4 birim geride setlenmiştir (1072-1078. satırlar arası). Deney Hazırlığı ve Deney Uygulaması bölümleri ile ilgili videoya buradan erişebilirsiniz. 3. Deney Hazırlığı Tank düşmana ateş ettiğinde mermi dönerek ilerleyecek şekilde kodu güncelleyiniz. Sağ mouse butonu tıklanılmış olarak tutulurken zoom yapılacak ve mousedan el çekildiğinde zoom öncesine dönülecek şekilde 415 ve 420. satırlardaki if(){} bloklarına gerekli kodları yazınız. İpucu Projeksiyon matrisinin görüş açısı daha küçük bir değere (örneğin XM_PI/12) setlenerek zoom yapılabilir. Projeksiyon matrisinin yeni değerini constant buffera (cbuffer) atıp UpdateSubresource() ile vertex shadera yollamayı unutmayınız. W,A,S,D tuşlarıyla oyun ortamında ilerlerken duvarların içinden geçilmemesi için föyde anlatılan yönteme göre 360 ve 370. satırlardaki if(){} bloklarına gerekli kodları yazınız. 4
4. Deney Tasarımı ve Uygulaması Duvar arkasından ateş edildiğinde mermi duvarı delip geçmeyecek şekilde kodu güncelleyiniz. Düşmanı temsil eden tank oyuncuya dönüp, ateş edip vuracak şekilde kodu güncelleyiniz. İpucu Düşman tankının dönüp ateş etmesi ile ilgili kod Render() içindeki if(renderenemy){} kod bloğuna eklenebilir: 1. Her bir Render() çağrısında g_world_enemy matrisi 0.03 gibi küçük bir açı ile y-ekseninde dönme yapacak şekilde XMMatrixRotationY(0.03) matrisi ile çarpılarak güncellenir. 2. (0,0,1) değerine setlenmiş namluyu temsil eden doğrultu vektörü, g_world_enemy matrisine göre XMVector3TransformNormal() fonksiyonu ile transform edilir. 3. Düşmanı temsil eden tanktan oyuncuyu temsil eden tanka doğru olan vektör hesaplanır. XMVector3Dot() ile bu vektör ile yukarıda transform edilen vektör arasındaki açının kosinüsü hesaplanır. Bu değerin 1 e yakın olduğu andaki namlu doğrultu vektörü boyunca mermi yollanır yani merminin World matrisi föyün 2. sayfasında anlatılanlara benzer bir şekilde güncellenir. XMMatrixRotationY(0) a setlenerek artık düşman tankı döndürülmez. Yollanan mermi ile tankın kesişip/kesişmediğini test edecek kod föyün 3. sayfasında anlatılanlara benzer bir şekilde UpdateCamera() içine eklenebilir. Kesişim testi için yollanan ışının Ro başlangıç noktası merminin World matrisinin (_41,_42,_43) bileşenleri ile setlenebilir. Rd de namlunun doğrultusudur. 5. Deney Raporu Kaynak kodlardaki Rapor.docx adlı şablon belge deney saatine kadar grup adına doldurulup Deney Hazırlığı olarak istenen kod güncellemelerini içeren Proje (.sdf ve Debug klasörleri silinip.rar lanıp) ile birlikte 61omercakir@gmail.com adresine e-mail ile yollanacaktır. 5