Makale Özeti

Bu makalemizde Windows Phone 7 ' de XNA ile oyun geliştirme adımlarını temel düzeyde inceleyeceğiz.

Makale

Merhabalar, bu makalemizde Windows Phone 7 ' de XNA ile oyun geliştirme adımlarını temel düzeyde inceleyeceğiz. XNA Game Studio 4 ile birlikte artık Windows Phone 7 platformu için de oyun geliştirebilmek mümkün olacak. XNA Framework, 2004 yılından bu yana farklı versiyonlar ile Microsoft tarafından sunulmaktadır. XNA Game Studio ile  oyun geliştirmenin C# bilen yazılım geliştiriciler için son derece kullanışlı ve keyifli olduğu bir gerçek. Özellikle de Windows Phone 7 platformu ile kullanılacak olması mobil teknolojiler ile ilgilenen ya da ilgilenecek olan yazılım geliştiricileri oldukça heyecanlandırdığını düşünüyorum. XNA' nın bu son versiyonu XBOX360, Windows 7 ve Windows Phone 7'de koşturacak.

XNA Nedir? XNA kütüphanesinde hangi sınıflar yer almaktadır? Bunlara ne zaman ihtiyaç duyarız? Nerede kullanırız? v.s gibi konu başlıklarının yanıtlarına şu an internet üzerinden arama ile rahatça erişebilir durumdayız.(İlgili linkleri makalenin sonunda vereceğim.) Fakat kısaca şöyle söyleyelim: XNA, Microsoft yazılım geliştiricileri tarafından üretilen ve yalnızca C# programlama dili ile yönetilebilen bir oyun platformudur, ya da oyun alt yapısı, kütüphanesi sağlayan bir Framework'dür diyebiliriz.

Bu yazımızda fazla detaya inmeden windows phone 7 platformu tabanlı örnek bir oyun üzerinde duracağız. Bu oyunumuzdaki amaç, Visual Studio 2010' da Windows Phone Game Template ile bir projeye başlarken, zaten bize hazır bir şekilde gelecek olan bir takım methodların kullanımlarını anlamak ve bu sırada XNA kütüphanesini daha iyi hissedebilmek olacaktır.

Tabiki bu makalemizin konusu içerisinde yazacağımız kodları kendi bilgisayarımızda uygulayabilmek için VS2010'a ek olarak Windows Phone Developer Tools'un da kurulu olması gerekmektedir. Kodlarımızı tamamladıktan sonra WP7 Emulator üzerinde çalıştıracağız.

Projemizi oluşturmaya Visual Studio 2010 / File / New Project / XNA Game Studio 4.0 / Windows Phone Game seçeneği ile başlayalım.

Windows Phone Game Projemize "PhoneGame1" ismini verelim. "Game1" isminde bir ana sınıf da otomatik olarak oluşturulacaktır. Bu projenin eklenmesiyle, Solution Explorer penceresi inceleyecek olursak 2 adet projenin eklenmiş olduğunu göreceğiz. Bunlar "PhoneGame1" ve "PhoneGame1Content" isimli projelerdir. Content projemiz, adından anlaşılacağı üzere oyunumuz içerisinde yer alacak, yüklenecek olan içeriklerin (image,text,ses,v.s...) ekleneceği bölüm, C# kodlarımızı da asıl derlenecek olan projemize ekleyeceğiz, bu proje de verdiğimiz isimle yer aldığını göreceğiz. Oyunumuzda kullanmak üzere 2 adet imaj dosyasını ve 1 adet spritefont dosyasını Content projemize ekleyebiliriz. Bunlar "/Makaleler/Resimler/1000002630_car.PNG", "/Makaleler/Resimler/1000002630_Ball-red-48.png" imaj dosyaları ve CarGameFont.spritefont dosyasıdır. Imaj dosyalarını (bu makale üzerinden) üzerine sağ tıklayıp bilgisayarınıza kaydederek kullanabilirsiniz. Spritefont dosyası oluşturmak için ise Content projesine sağ tıklayıp Add / New Item / Sprite Font seçeneğini kullanabiliriz. SpriteFont dosyasını açıp incelersek, font özelliklerinin xml formatında yer aldığını görürüz. İçinde FontName, Size, Style gibi özellikleri barındırmaktadır.

Content'e eklediğimiz imaj dosyalarını dilerseniz buradan kopyalayabilirsiniz. (Araba için /Makaleler/Resimler/1000002630_car.PNG ve toplar için /Makaleler/Resimler/1000002630_Ball-red-48.png)

Şimdi de otomatik olarak oluşturulan Game1 isimli ana sınıfımızı inceleyelim. Game1 isimli sınıfımızda çeşitli override methodlarımızı ve bir de constractor methodun yer aldığını göreceğiz. Sırasıyla bu methodlar;

public Game1()
protected override void Initialize()
protected override void LoadContent()
protected override void Update(GameTime gameTime)
protected override void Draw(GameTime gameTime)
protected override void UnloadContent()

olarak hali hazırda yer alacaklar. Sırasıyla hangi methodun ne zaman kullanıldığını anlamaya çalışalım. "Game1" isimli sınıfımızda tabiki Game1 isminde bir constractor yer alacak ve uygulamamız ilk oluşturulduğunda öncelikle bu constraktor method çalışacak. Ctor. içerisinde uygulamamıza özgü olan ve uygulama süresince değişmeyecek olan özellikleri set edebiliriz. Constractorden sonra çalışacak olan Initialize() override methodu ile de grafiksel olmayan uygulama içeriğini henüz uygulama başlamadan hazır duruma getirmek amacı ile kullanabiliriz. 

Diğer methodlarımızı incelemeden önce sırasıyla hangi methodun çalışacağını aşağıdaki akış şeması üzerinden inceleyelim.

XNA Program Akış Şeması

Initialize Method

Constractor'den farklı olarak bir de initialize methodu ayrıca oluşturulmuştur. Bu method uygulamanın ayağa kaldırılışı sırasında çalışır. Constractor ile uygulama oluşturulduktan sonra ve sonraki her reset işleminde bu method ile uygulama başlayacaktır.

LoadContent

Adından da anlaşılacağı üzere, uygulamamıza ait olan içeriğin(dosyaların),objelerin yüklenmesi/oluşturulması, ilk ayarlarının yapılmasını (ilk lokasyonun belirlenmesi v.s) burada yaparız. Uygulamanın ayağa kalkışı ile bir defa çalışır. Bu method içerisinde imaj, ses, yazı v.s gibi içerikler yüklenerek oyun için hazır duruma getirilir.

Update

Uygulama içerisindeki değişimler burada yakalanır(Touch Event'lar, nesne yerlerinin/koordinatlarının zamanla değişmesi,v.s).  Constractor içerisinde set edilen TargetElapsetTime zaman aralığının, yani bir görüntü karesi ile sonraki görüntü karesi arasında geçen sürenin dolması ile bu method tekrar tekrar, çok hızlı bir şekilde(fps hızında), uygulama sonlandırılana kadar çalışacaktır. Uygulama süresince sürekli olarak ve çok kısa zaman aralıkları ile bu method çalışacağı için, istenilen değişiklikleri, matematiksel hesaplamaları, yani oyunun iş mantığını buraya yazıyoruz.

Draw

Update methodu ile senkronize bir şekilde çalışır ve update methodundan hemen sonra devreye girer. Update içerisinde yapılan değişikliklerin ekrana yansıtılması için kullanılan methoddur.

UnloadContent

Uygulamamızın sonlandırılması durumunda çalışacaktır. LoadContent içerisinde yüklenen içeriğin hafızadan silinmesi amacıyla kullanılır.

Şimdi vakit kaybetmeden sırasıyla content projeye eklediğimiz dosyalarımızı kullanmaya başlayalım. "/Makaleler/Resimler/1000002630_car.PNG" isimli araba nesnemizi temsilen Texture2D sınıfından faydalanacağız. Arabamızı koordinat sisteminde 2 boyutlu (2D, X ve Y'de) olarak düşünüyoruz. Bu sebeple Texture2D nesnesi araba objemizi temsilen kullanılacak. Arabamızı ekranda hareket ettirebilmek için de Vector2 (tabiki vektörel olarak) sınıfından faydalanacağız. Arabamıza vereceğimiz hareketler koordinat ekseninde X ve Y yönlerinde olacak. X'i yatay hareketlerde, Y'yi ise dikey eksen olacak.

Constractor methodumuzun üst kısmına öncelikli olarak oyunumuzda kullanacağımız objelerimizi tanımlayarak programımızı geliştirmeye başlayalım. "Game1" isimli sınıfımıza aşağıdaki tanımlamaları private değişken olarak ekleyelim.

// Araba objesi için kullanılacak Texture ve Vektör
Texture2D carTexture;
Vector2 carPosition;
 
//Top 1 objesi için kullanacağımız Texture ve Vektör
Texture2D ball_1Texture;
Vector2 ball_1Position;

//Top 2 objesi için kullanacağımız Texture ve Vektör
Texture2D ball_2Texture;
Vector2 ball_2Position;
 
//Ekrana ekleyeceğimiz text bilgisi için SpriteFont ve Vektörü
SpriteFont textFont;
Vector2 textPosition;
const string TEXT_TEMPLATE = "Gokhan Manduz Car Game - Zaman(Sn.)={0} - Car Coord.={1}";
string TEXT = "";

Oyunuzdaki objelere ekranımızda (koordinat ekseninde) vektörel hareketler vereceğimizden dolayı değişken isimlendirmelerimizi "Position" kelimesi ile sonlandıralım. Windows Phone 7 ekranımızda "carTexture", "ball_1Texture", "ball_2Texture" ve textFont isimli nesnelerimiz ve bu nesnelerimize yer değiştirme kabiliyeti kazandıracak "carPosition", "ball_1Position", ball_2Position" ve "textPosition" isimli değişkenlerimizi tanımladık. Bu oyunumuzda ekranımıza her dokunulduğunda arabamızı o yönde hareket ettirirken aynı zamanda toplarımızı da sabit ama farklı hızlarda ekranımız üzerinde hareket ettireceğiz. Aynı zamanda bir text bilgi ile arabamızın koordinat ekseninde aldığı X ve Y değerlerini izleyeceğiz. Burada önemli olan oyunumuzun mantığından çok objelerimizi nasıl hareketlendirdiğimiz ve ekrana nasıl çizildiğimiz olacaktır.

Şimdi de yukarıda tanımladığımız nesnelerimizi LoadContent methodu içerisinde oyunumuz için hazır duruma getirelim. Texture'lareımızı ve Vektörlerimizi aşağıdaki gibi oluşturuyoruz.

 protected override void LoadContent()
 {
    spriteBatch = new SpriteBatch(GraphicsDevice);
 
    carTexture = Content.Load<Texture2D>("car");
    Viewport viewPort = graphics.GraphicsDevice.Viewport;
    carPosition = new Vector2( (viewPort.Width - carTexture.Width) / 2,  (viewPort.Height - carTexture.Height) / 2 );
 
    ball_1Texture = Content.Load<Texture2D>("Ball-red-48");
    ball_1Position = new Vector2(-60, 30);
 
    ball_2Texture = Content.Load<Texture2D>("Ball-red-48");
    ball_2Position = new Vector2(-60, 260);
 
    textFont = this.Content.Load<SpriteFont>("CarGameFont");
    textPosition = new Vector2(2, 2);
 
 }

Arabamızın oyunun başında ekranın ortasında olmasını istediğimiz için carPosition vektör nesnesi constraktörüne ilgili parametreleri merkezde olacak şekilde gönderiyoruz. Burada dikkat edecek olursak Content.Load<T> jenerik methodunu kullanarak istediğimiz objeleri yükledik. Oyunumuz başlarken 2 adet kırmızı top objelerini, ekranın dışından oyuına dahil edilmesini istediğimiz için koordinat ekseninde X değerlerini -60 olarak verdik.

ball_1Position = new Vector2(-60, 30);  ball_2Position = new Vector2(-60, 260);

Ekranın üst kısmına da oyun ile ilgili bilgilendirme text metnimizi başlangıç noktası koordinat ekseninde (x,y) (2,2) olacak şekilde belirttik.

textPosition = new Vector2(2, 2);

Başlangıç değerlerini belirttikten sonra Update methodumuzun içinde gerçekleştireceğimiz koordinat ekseninde yer değiştirmeye yarayacak kodlarımızı da ekleyelim. Update methodu default olarak her 30fps'de (30 frames per second) bir tekrar tekrar çalışacaktır. Buradaki 30fps değeri default olarak constractorde verilmiştir. (TargetElapsedTime)

 // Frame rate is 30 fps by default for Windows Phone.
 TargetElapsedTime = TimeSpan.FromTicks(333333);
 protected override void Update(GameTime gameTime)
 {
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
        this.Exit();
 
    ball_1Position.X += 10;
    ball_2Position.X += 5;
 
    if (ball_1Position.X == 900) {
       ball_1Position.X = -60;
    }
    if (ball_2Position.X == 900)
    {
       ball_2Position.X = -60;
    }
 
    TEXT = string.Format(TEXT_TEMPLATE, gameTime.TotalGameTime.Seconds, string.Concat((int)carPosition.X,",",(int)carPosition.Y));
            
    TouchCollection touchCollection = TouchPanel.GetState();
    if (touchCollection.Count > 0) {
       TouchLocation t1 = touchCollection[0];
 
       double x = t1.Position.X - (carPosition.X + (carTexture.Width / 2) );
       double y = t1.Position.Y - (carPosition.Y + (carTexture.Height / 2));
       double speed = Math.Sqrt(x * x + y * y) / 10;
       double angle = (float)Math.Atan2(y, x);
       carPosition.X += (float)(speed * Math.Cos(angle));
       carPosition.Y += (float)(speed * Math.Sin(angle));
     }
     base.Update(gameTime);
 }

Toplarımızın Y koordinat ekseninde (dikeyde) yerlerini 30 ve 260 olarak belirlemiştik. Topların Y değerleri sabit kalacak, istediğimiz topların yalnızca yatayda (X ekseninde) hareket etmesi. Update methodumuz içerisinde ball_1Position için +10 ve ball_2Position vektörünün X koordinatı değerine de +5 ekleyerek 1. topun, 2. topa göre daha hızlı (2 kat) ilerlemesini sağlayacağız. Belirli bir süre sonra X koordinat ekseninde toplar kaybolacaktır çünkü X koordinat eksenimiz 800 ile sınırlıdır. Biz de update methodu her çalıştığında topların X değerlerini kontrol ederek 800'den fazla ise (topların tamamen ekrandan kaybolması için 900 olarak belirledik.) tekrar başlangıç konumlarına geri çekmiş olduk.

 if (ball_1Position.X == 900) {
    ball_1Position.X = -60;
 }

Update methoduna parametre olarak gelen gameTime değişkeni ile de oyunun zamanını da yakalamak mümkün olacaktır. Oyunun başından itibaren kaç sn veya dakika geçtiğini bu değişken ile elde ederek string TEXT isimli değişkenimize set ediyoruz. Update methodu içerisinde ayrıca TouchPanel.GetState() methodu ile o ana denk gelen dokunmaları da TouchCollection nesnesi yardımı ile elde edebiliyoruz ve ekrana dokunulan noktanın koordinat eksenindeki karşılığı  ile arabamızın o an bulunduğu nokta arasındaki mesafeyi ve bu mesafe ile temel matematiksel hesaplamalarla hızı ya da vektörel büyüklüğü ayrıca araç ile dokunulan nokta arasındaki açıyı v.s elde etmek kolay olacaktır. Elde edilen vektörel değerlerle arabamızın yeni pozisyonunu carPosition.X ve carPosition.Y özelliklerine ekleyerek değiştiriyoruz.

Update methodu ile senkronize olarak ve bu methodun hemen ardından çalışacak olan Draw methodu ile ekrana çizmek istediğimiz nesnelerimizi belirtiyoruz. Aşağıdaki Draw methoduna dikkatle bakacak olursak imaj Texture için spriteBatch.Draw ve SpriteFont için ise spriteBatch.DrawString methodlarını kullandık.

 protected override void Draw(GameTime gameTime)
 {
     GraphicsDevice.Clear(Color.White);
 
     spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
     spriteBatch.Draw(carTexture, carPosition, Color.White);
     spriteBatch.End();
 
     spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
     spriteBatch.Draw(ball_1Texture, ball_1Position, Color.White);
     spriteBatch.End();
 
     spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
     spriteBatch.Draw(ball_2Texture, ball_2Position, Color.White);
     spriteBatch.End();
 
     spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
     spriteBatch.DrawString(textFont, TEXT, textPosition, Color.Blue);
     spriteBatch.End();
 
     base.Draw(gameTime);
 }

Son olarak F5 ile projemizi çalıştıralım ve WP7 Emulatorümüz üzerinde mouse ile ekranın çeşitli yerlerine tıklayarak Update methodu içerisindeki TouchCollection nesnemizin beslenmesini sağlayalım. Update ve Draw methodları biz oyunu sonlandırana kadar sürekli olarak çalışacaktır. Projemizi debug modda çalıştıracağımız için uygulamanın çalıştığı herhangi bir anda Update methodumuzdaki bir satıra bir Breakpoint ekleyerek, çalışma satırının direk olarak bu breakpoint üzerinde konumlanacağını görebiliriz ve bu sırada nesnelerin aldığı değerleri de rahatlıkla gözlemleyebiliriz.

2 adet kırmızı topun sabit hızlarda soldan sağa doğru hareketini izlerken, aynı zamanda aracımızı da ekranda dokunduğumuz noktaya doğru hareketini sağlamış olduk ve bu sırada ekranın üst kısmında yer alan text'imizin de değiştiğini göreceğiz. Emulator ekranı üzerinde arabamızdan ne kadar uzak bir noktaya dokunursak aracın o noktaya doğru ve daha hızlı bir şekilde ilerlediği hissedilecektir.

Bizde hareket hissi uyandıran şey, aslında belirli zaman aralıkları ile geçen film kareleri gibi ekran üzerindeki objelerin koordinat ekseni üzerinde yalnızca yerlerinin değiştirilmesidir.

Bir sonraki makelemizde görüşmek üzere.. Kolay gelsin.

http://msdn.microsoft.com/en-us/library/bb417503.aspx

http://en.wikipedia.org/wiki/XNA_(Microsoft)

Gökhan Manduz  -  gokhanmanduz@hotmail.com