> DirectX > DirectInput Programlama – Bölüm 1

DirectInput Programlama – Bölüm 1

Bir bilgisayar oyununda, en önemli noktlardan birisi de kullanıcı ile iletişim kurmaktır. Yoksa, grafikler ne kadar harkulade ve sesler de ne kadar şahane olsa da, oyun bir filim izlemekten farkı kalmayacaktır. Bir oyun, klavye, mouse veya joystick gibi aygıtlardan veri almak zorundadır. Geçmişte, bu aygıtları, istenilen tarzda programlayabilmek için çok fazla efor sarfedilirdi. Özellikle Joystick, saçlarınızın yolunmasına yol açabilirdi. Bu yüzden Microsoft®, DirectX kütüphanesine DirectInput’u dahil etmiştir.

Bu makalemizde, DirectInput kullanarak klavye ve mouse ile nasıl iletişim kurabileceğimizi göreceğiz. Bununla birlikte neden DirectInput’u kullanmamız gerektiğinden de bahsedeceğiz. Ancak bu yazıda Joystick programlamasından bahsedilmeyecektir. Ama Joystick de, göstereceklerimizden daha zor veya daha kolay değildir.

İçindekiler

Bölüm 1

Giriş

DirectInput Nesnesi

Olayları Dinlemek

Aygıtın Etkinliği

Verilerin Formatı

Aygıt Nesnemizi Oluşturuyoruz

Bölüm 2

Giriş

DirectInput, kullanıcının bilgisayarında bulunan giriş aygıtları ile doğrudan bağlantı kurar. Windows’u devreden çıkardığı için, giriş aygıtından gelen veriler ikinci bir katmana uğramadan direk olarak size gelir. Bu da en küçük gecikmenin bile istenmediği oyun gibi uygulamalarda, size avantaj sağlayacaktır. DirectInput, sistemde bulunan DirectInput sürücüsüne sahip aygıtları listeler ve bu aygıtların özelliklerini elde eder. Tabi ki, DirectInput’u kullanabilmek için, aygıtın DirectInput sürücüsüne ihtiyacı vardır. Günümüzde çıkan hemen hemen her aygıt DirectInput’u desteklemektedir. Bu yüzden endişelenmenize gerek yok.

DirectInput’da force feedback gibi birçok güzel özellik mevcuttur. Ama bu makalemizde, joystick konusunu girmeyeceğiz. İlerleyen günlerde bununla ilgili de bir makaleyi burada görebileceksiniz. Eğer burada yazılanları anlar ve uygularsanız, joystick’ lerin de o kadar zor olmadıklarını göreceksiniz.

İsterseniz şimdi, neden DirectInput kullanmamız gerektiğine bir göz atalım. Win32 API’leri, bünyesinde mouse hareketi, klavye tuşuna basılması gibi bir çok pencere mesajını içerir. Hatta joystick için bile gelişmemiş mesajlara da sahiptir. Peki öyleyse, DirectInput’un standart API çağırımlarından avantajları nelerdir, görelim:

  • Win32 API’ leri, oyunlar için veya hız için tasarlanmamışlardır

  • Joystick desteği, en iyi halde bile dökülmektedir. Daha kompleks joysticklerde Win32’yi kullanamazsınız. Mesela 8-10 tuşlu, POV tekerleği, vs..

  • Mouse desteği en fazla, üç düğmeli, iki eksenli, varsa bir tekerlekten oluşur. Bu günün mouse’ları daha fazlasına sahiptir.

  • Klavye desteği, standart klavye girişli uygulamalar için tasarlanmıştır. Otomatik tekrar eden tuşlar, tuş kodlarından ASCII karakterlerine çevirim gibi bir çok fonksiyon ile donatılmıştır. Halbuki oyunların bunlara ihtiyacı yoktur ve bunlar çok fazla işlemci saykılı gerektirir.

  • Bazı klavye kodları özel işlemler gerektirir (Windows tuşu, alt tuşu gibi..) Bu da mesajın işlenmesini zorlaştırır.

  • Mesaj işleme, dünyadaki en hızlı yöntem değildir. Uygulamalar bir çok mouse mesajı ile dolup taşar. Bir mesaj kuyruğu bitmeden, sahnenizi renderleyemezsiniz. Bu da uygulamanızın hızını düşürecektir.

Bununla beraber, eğer halen klasik Windows mesajlarını kullanıyorsanız, DirectInput kullandıktan sonra, performansın gözle görülür bir biçimde arttığını da göreceksiniz.

DirectInput Nesnesi

Eğer bu makaleyi okuyorsanız, bundan önce en azından bir kutuyu ekranda çizdirdiğiniz varsayıyorum. Çünkü burada anlatacağım nesneler ve fonksiyonların yapısı, Direct3D’nin yapısından pek farklı olmayacaktır. Ayrıca projenizin ek kütüphaneler kısmına, d3dx9.lib kütüphanesi ile beraber d3dguid.lib ve dinput8.lib dosyalarını da eklemeyi unutmayınız. Tabi ki dinput.h dosyasını da include etmelisiniz. Bunların nasıl yapılacağını bilmiyorsanız, lütfen Direct3D’ye giriş ile ilgili makaleleri okuyunuz.

Å?unu da eklemek istiyorum ki, DirectX programlamanın gereği olarak, gördüğünüz her yapı, her fonksiyonu ezberlemek akıl kârı iş değildir. Burada işin mantığını ve gidiş yolunu örendikten sonra, SDK yardım dosyalarını ya da bir kitabı referans olarak alabilirsiniz. Bu yüzden burada gösterilecek olan her fonksiyonu bir yerlere not etmeyin. İşin gidişat yolunu kapmaya çalışın.

Sistemde mevcut olan aygıtları bulup, bunların özelliğine göre aygıt arayüzleri oluşturabilmek için DirectInput nesnesine ihtiyacınız vardır. Aynen Direct3DDevice oluşturabilmek için Direct3D nesnesine ihtiyacınız olduğu gibi… Bu nesne ile, sistemde bulunan aygıtların arayüzlerini oluşturabileceğiniz gibi, bunlar hakkında bilgi de edinebilirsiniz. DirectInput, Direct3D’nin aksine 8’inci versiyonundan bu yana pek değişmedi. Bu yüzden yeni bir versiyon çıkana kadar 8 versiyonunu kullanıyoruz. Bir DirectInput nesnesi oluşturabilmek için aşağıdaki fonksiyonu çağırmalıyız:



		

Buradaki hInst, uygulamamız oluşturulurken elde ettiğimiz hInstance değeridir.

dwVersion olarak DirectInput’un versiyonunu giriyoruz. Buraya DIRECTINPUT_VERSION sabitini girmeliyiz.

riidltf olarak ise, hangi nesneyi oluşturmak istiyorsak onu girmeliyiz. Buraya IID_IDirectInput8 şeklinde giriyoruz. Tam listesi için SDK dosyasına göz atınız.

ppvOut ise bizim çıkış nesnemizdir. Nesnemizin referansını da buraya giriyoruz.

Son kısım ise COM işlemleri için özel olarak ayrılmıştır. Bu yüzden burayı es geçiyoruz. Aşağıda örnek bir kullanım görüyorsunuz:



		

Gördüğünüz gibi eğer DirectInput nesnemiz oluşturulamazsa bir hata dönderiyoruz. Bu aşamadan sonra artık aygıt(lar)ımızı oluşturabiliriz. Ama ondan önce bilmemiz gereken bir kaç husus daha var. Å?imdi onlara geçelim.

Olayları Dinlemek

Burada DirectInput’a ait bir çok fonksiyonu anlatmak yerine, işimize yarayacak bazı basit uygulamalarını göstermek istiyorum. Dediğim gibi, Joystick’ de o kadar zor değildir ama aygıta ait bir çok düğme ve fonksiyonun listesini elde edip bunlara görevler atamak uzun sürecektir.

DirectInput aygıt nesneleri, bizden klavye ya da mouse için hangi durumlarda ve hangi özellikte bilgi istediğimizi bilmesi gerekmektedir. Bir olay gerçekleştiğinde aygıt nesnesi, bunu bizim belirleyeceğimiz fonksiyona, bizim istediğimiz ölçülerde bildirmesi gerekmektedir. İşte bu olayları alabilen fonksiyona "listener"(dinleyici) diyoruz. Bu sistem, sadece DirectInput’da değil bir çok yerde kullanılmaktadır. Bu yapıyı isterseniz, normal fonksiyon tanımlaması yerine, sınıf mantığına göre düzenleyelim. Olay bilgilerinin yapısını belirleyecek iki adet arayüz(interface) hazırlayalım. Bunlardan biri klavye diğeri mouse için olacaktır. Böylelikle bu arayüzleri kullanarak istediğimiz sınıfa listener özelliğini ekleyebiliriz. Aşağıda örnek bir tanımlama görmektesiniz:



		

Mesela bir CApplication isimli bir sınıfınız var. Ve siz bu sınıfa klavye verilerini göndermek istiyorsunuz. Tek yapmanız gereken sınıfınızı bu arayüzlerden türetmektir. Örnek olarak CExample isimli ve CApplication sınıfından türeyen bir sınıfımız olsun. Ve biz buna klavye ve mouse verilerini ulaştırmak istiyoruz. Yani:



		

Gördüğünüz gibi, hangi Receiver arayüzünü kullanmak istiyorsak, sınıfımızı o arayüzden türetiyoruz. Burada önemli olan nokta, Arayüzlerin sahip oldukları fonksiyonları aynen sınıfınızda da tanımlamalısınız. Buraya kadar anlattıklarımız basit bir Interface kullanımıdır. Bundan sonra yapmamız gereken bu arayüzleri çağırmaktır. Tabi ki aygıt nesnemizi(IDirectInputDevice8) oluşturduktan sonra…

Yine önceki bölümün sonlarında dediğim gibi, aygıt nesnemizi oluşturmadan önce bir kaç şey daha öğrenmeliyiz. Ardından esas amacımız olan IDirectInputDevice8 aygıt nesnesini oluşturacağız.

Aygıtın Etkinliği

Tanımlayacağımız aygıtın, önceliği ve etkinliği bizim için önemlidir. Mesela uygulamamız arkaplana geçtiğinde, ya da görev çubuğuna küçültüldüğünde, hâla klavye ve mouse verilerini alıp işleyecek miyiz? Ya da, mesela Windows’un mouse mesajlarını toplamasını kesip, tüm verileri DirectInput’a göndermeli miyiz? İşte bu gibi öncelik değerlerini aygıt nesnemizin SetCooperativeLevel metodu ile gerçekleştiriyoruz:



		

Burada bulunan hWnd, aygıtın üzerinde işlem göreceği penceremizin Handle numarasıdır. İkinci parametre olarak dwFlags aşağıdaki değerleri alabilir.

  • DISCL_BACKGROUND: Uygulamamız aktif değilken bile, aygıttan veri almaya devam eder.

  • DISCL_EXCLUSIVE: Diğer uygulamalar tarafından, mesela Windows tarafından çokca kullanılan aygıtlar için bu değer ayarlanırsa, Windows, kendisi de dahil, diğer uygulamalara aygıt verilerini göndermeyi keser. Bu yüzden, mesela mouse için bu değer ayarlanmış ise, Windows dahi mouse mesajlarını alamayacağından, ekrandaki cursor kaybolur.

  • DISCL_FOREGROUND: Aygıt verileri, yanlızca uygulamamız aktif iken alınabilir.

  • DISCL_NONEXCLUSIVE: Bu ayarlandığında, Windows dahil diğer uygulamalar da aygıttan istifade edebilirler. Mesela bu özellik klavye için hayati önem taşır. Çünkü, uygulamanız donduğunda ctrl+alt+del tuş kombinasyonları da çalışmayacaktır.

  • DISCL_NOWINKEY: Bunu da siz tahmin edin 🙂

Bu değerleri kombinasyon halinde girebiliriz. Ama DISCL_BACKGROUND ve DISCL_FOREGROUND gibi birbirine zıt değerler, aynı anda kullanılamazlar. Aşağıda mouse için bir örnek görüyorsunuz:



		

Bu örnekte mouse’umuzun imleci uygulamamız aktif iken görünmeyecektir. Çünkü diğer uygulamalar dahil Windows da mouse mesajlarını alamayacaktır. Ayrıca NOWINKEY ile özel Windows tuşunu da pasif duruma getiriyoruz. FOREGROUND olarak ayarladığımızdan dolayı, bu söylediklerimiz sadece uygulama aktif iken gerçekleşecektir. Bir üst paragraftaki dwFlags değerlerinin açıklamalrını okuduysanız, bu dediklerimiz size yabancı gelmeyecektir.

Verilerin Formatı

Bu makalenin başlarında da bahsettiğimiz gibi, DirectInput bize çok çeşitli aygıtları tanımlamamıza imkan tanıyor. Mesela 10 tuşlu bir joystick gibi… Ama klavye ve mouse için ve bazı standart joystickler için hazır tanımlanmış formatlar mevcuttur. Klavye ve mouse için olan hazır veri formatları, genelde bizim için yeterli gelmektedir. Tabi kalkıp japonca klavyeler ve çok özel tuşlu mouselar için aygıt oluşturacaksanız, hazır veri formatları yerine kendiniz özel formatlar belirlemelisiniz. Ama biz buna değinmeyeceğiz. Aşağıda önceden tanımlanmış hazır veri formatlarını görüyorsunuz:

  • c_dfDIKeyboard: Standart klavye yapısıdır. Her tuşa karşılık gelen 256 karakterlik bir dizidir.

  • c_dfDIMouse: Standart mouse yapısıdır. 3 eksenli ve 4 tuşludur.

  • c_dfDIMouse2: 3 eksenli ve 8 tuşlu mouse yapısıdır.

Joystickler için de c_dfDIJoystick ve c_dfDIJoystick2 yapıları tanımlanmıştır. Bu veri formatlarını aygıtımıza belirtmemiz gerekmektedir. Çünkü aygıt nesnemiz neye göre veri alacağını biz bildirmeden bilemez. Aşağıda aygıt nesnesinin SetDataFormat metodunu kullanan bir örnek görüyorsunuz:



		

Gördüğünüz gibi sadece veri formatımızı, referanslı bir şekilde SetDataFormat fonksiyonuna geçiriyoruz. Bu fonksiyon HRESULT tipinde değer dönderir. İstersek, Bu işlemin başarılı olup olmadığını FAILED makrosu ile sınayabiliriz.

Aygıt Nesnemizi Oluşturuyoruz

DirectInput nesnesini doğru bir şekilde oluşturduktan hemen sonra aygıt nesnemizi oluşturmamız gerekior. Bunun için DirectInput nesnesinin CreateDevice

fonksiyonundan faydalanacağız. Aşağıdaki örneği inceleyelim:



		

CreateDevice fonksiyonu HRESULT tipinde değer dönderir. İstersek bunu FAILED makrosuna sokup, aygıt nesnemizin oluşup oluşmadığını da görebiliriz. İlk parametre olarak girdiğimiz GUID değeri klavye için tanımlanmış özel bir sabittir. Biz burada sadece GUID_SysKeyboard ve GUID_SysMouse sabitlerin bilsek yeterli olur. Aslında buraya sistemde bulunan aygıtların listesinden elde edeceğimiz GUID değerleri girilmelidir. Mesela joystick tanımlamaları için buraya aygıt listesini aldıktan sonra joystick’e ait GUID değerini girebiliriz. İkinci parametre ise, oluşturulacak olan aygıt nesnesinin referansıdır. üçüncü parametre COM toplaması için kullanılır ve bizi ilgilendirmiyor, (çoğu uygulamada da) biz buraya her zaman NULL gireceğiz.

Artık aygıt nesnenmizi oluşturduk. Tabi siz bu fonksiyonun çıktısını FAILED makrosuna sokup, nesnenin oluşup oluşmadığını da test etmelisiniz.

Å?imdi bu aygıtımızın ne çeşit bir veriye sahip olduğunu, nesnemize bildirmeliyiz. Bunun için önceden gördüğümüz SetDataFormat fonksiyonunu kullanacağız. Yukarıdaki kodların hemen ardına şunun gibi bir kod yerleştirmeliyiz.



		

Hemen ardından CooperativeLevel değerini belirleyelim:



		

Klavye için aygıt nesnemiz kullanıma hazır artık. Artık bundan sonra geriye verileri alıp işlemek kalıyor. Ama tabi ki bunu ikinci bölümde yapacağız.

İkinci bölüme buradan ulaşabilirsiniz.

Yorumlarınızı bekliyorum.

» Tags: , , , ,

1 Yorum

  • At 2008.01.11 19:20, İlhan said:

    Aslında bir iki yöntem daha var onlarda COM tabanlı. Fakat Coma güvenilmediği için böyle bir alışkanlık kullanılmıyor. Örnek kodu göndermek istedim:

    HRESULT hr = CoInitialize( NULL ),
    
    hr = CoCreateInstance( CLSID_DirectInput8, NULL, CLSCTX_INPROC, IID_IDirectInput8, reinterpret_cast( &g_pdi8 ) ),
    
    

    (Required)
    (Required, will not be published)