Bundan önceki yazılarımızda oyun programcılığı ve xna üzerine bir çok kavramsal konudan bahsettik. Bu yazılar içinde zaman zaman çeşitli kod örnekleri görsekte aktif olarak hiç kod yazmadık. Dolayısıyla artık kod yazmaya başlamanın zamanı gelmiştir diyebiliriz. Bu yazımızın konusu ise xna dünyasında bulunan iki farklı nesenin birbirleri ile çarpışıp çarpışmadığınının kontrolüdür. Bu duruma yani iki veya daha çok nesnenin birbirleriyle çarpışmasına, oyun programcılığında collision denir. Bu durumu yönetebilmek adına her ne kadar bir çok farklı teknik geliştirilmiş olsada biz bugün bunlardan en basitini inceleyeceğiz. Bunu yaparken aynı zamanda xna dünyasında bir nesne ekranda nasıl gösterilir ve klavye kontrolü ile nasıl hareket ettirilir gibi farklı teknikleri de görmüş olacağız. Kod yazmaya başlamadan önce ilerde sık sık karşılacağımız ve sahneye konulacak nesneler ile ilgili bazı temel kavramları görmekte fayda var.
Sprite : 2 boyutlu olan imajlara genel olarak sprite denir.
Texture : 3 boyutlu bir modeli giydirmek için kullanılan imajlara denir.
Tiles : Küçük küçük imajların birleştirilip büyük bir imaj haline gelmesiyle oluşan yapıdır. Mesela bir çöl zemini düşünün. Bunun için çok büyük bir çöl imajı hazırlamak yerine çok daha küçük bir imaj hazırlayıp bunu yan yana ve altalta dizerek bir bütün olarak gösterebiliriz. Bu da bize sistem kaynakları gibi unsurlardan tasarruf sağlar. Genelde zemin, duvar veya gökyüzü gibi kendini tekrar eden yapılar için kullanılır.
Kodların Ekrana Yansıması...
Yazdığımız kodların ekranda nerede ve nasıl görüntüler oluşturacağını anlayabilmek için önce monitörümüzü tanımak gerekiyor. Kısaca özetlemek gerekirse; iki boyutlu bir düzlem olan monitör üzerinde görüntü elde edeceğimiz için öncelikle bize bu iki boyutu temsil edecek iki doğru gerekiyor. Bu iki doğrunun oluşturduğu düzlem hepimizin lisede gördüğü koordinat sisteminin ta kendisidir. Dolayısıyla bizim ekran üzerinde oluşturacağımız her nesnenin pozisyonu bu x ve y doğruları ile belirlenir. Fakat bizim kullanacağımız koordinat sistemi biraz daha farklıdır. Şöyle ki; standart koordinat sisteminde orjin (x ve y değerlerinin sıfır olduğu nokta), düzlemin tam ortasında x ve y doğrularının kesiştiği noktadır. Fakat monitörümüzün kullandığı koordinat sisteminde orjin, monitörümüzün sol üst köşesidir. Dolayısıyla görünürde eksi değer yoktur. Bu da nesnelerin hareketlerini daha kolay yönetmemizi sağlıyor diyebiliriz. Resim 1 'de iki farklı koordinat sistemi gösterilmiştir.

Bu düzlemler teoride sonuza kadar gider. Fakat biz sonlu bir ekranda çalışıyoruz. Yani kullandığımız ekranların fiziksel olarak sabit bir genişlik ve yükseklik değerleri vardır. Örneğin benim bilgisayarımın ekran boyutu 15.4" 'tir. Aynı zamanda bu ekranın desteklediği farklı çözünürlük değerleri de vardır. Örneğin 1280x800 gibi. Şimdi ekran üzerindeki en küçük noktaya piksel dediğimizi belirttikten sonra, biz bu çözünürlük değeriyle 1280 piksel genişliğe ve 800 piksel yüksekliğe sahip bir kullanım alanına sahip oluruz. İşte bizim ekran üzerindeki koordinat sistemimizin sınırları da bu çözünürlük değerlerine bağlıdır. Bu şu demektir, 1280 x 800 çözünürlüğe sahip bir ekranda dikey doğruda 801. piksele koyacağımız bir nokta ekranda görünmeyecektir.
Bu bilgiler ışığında, nesneler nasıl hareket ediyor sorusunun cevabı artık çok daha anlaşılır olacaktır sanırım. Şöyle ki; eğer nesneyi sağa doğru hareket ettirmek istiyorsak yapmamız gereken tek şey, nesnenin sahip olduğu x değerinini arttırmaktır. Aynı şekilde sola doğru gitmesini istiyorsak yapmamız gereken şey, nesnenin sahip olduğu x değerini küçültmektir. Bu x ve y değerlerini nesnemizi oluşturan sınıfın özellikleri olarak düşünebilirsiniz. Nitekim birazdan böyle bir örnek yapacağız.
Uygulamaya Başlangıç
Artık kod yazmaya başlayıp ilk uygulamamızı geliştirebiliriz. Bu uygulamada ilk olarak ekrana bir nesne yerleştirip ardından bunu klavye yardımı ile hareket ettireceğiz. Son olarak ta ikinci bir nesne ekleyip bunları hareket ettirip çarpışıp çarpışmadığını kontrol edeceğiz.
Şimdi Visual Studio 'yu açalım ve File>NewProject menüsünden Windows Game (3.0) şablonunu seçip yeni bir uygulama yaratalım. Karşımıza ilk olarak önceki makalemizde incelediğimiz Game1.cs sınıfı gelecektir. Dilerseniz standart olarak gelen yorum satırlarını silebilirsiniz.
Uygulamada kullanacağımız imajları kolay bir şekilde yönetebilmek için Content projesine Sprites adından bir klasör ekleyelim (Content sağ tık>Add>New Folder). Ardından resimleri sürükle bırak yöntemiyle Sprites klasörü içine atabilirsiniz.
İlk olarak en basit haliyle bir nesneyi ekranda gösterip sonra bunu daha modüler bir şekilde nasıl yaparız, buna bakacağız. Ayrıca burada nesne yönelimli programlamanın en basit örneklerinden birini de görmüş olacağız.
XNA 'de 2 boyutlu nesneleri yönetebilmek adına özelleştirilmiş Texture2D adında bir sınıf geliştirilmiştir. Dolayısıyla öncelikle yapmamız gereken bu tipte bir nesne tanımlamaktır.
Texture2D sprite1;Artık elimizde 2D Texture 'leri tutacak bir nesnemiz var. Peki biz bu nesne sayesinde herhangi bir imajı nasıl yükleceğiz. Bunun için LoadContent() methodu içine aşağıdaki kodu yazıyoruz.
sprite1 = Content.Load<Texture2D>("Sprites/sprite1");Burada parametre olarak verdiğmiz resim adresinde neden resmin uzantısını belirtmedik diye düşünebilirsiniz. Bunun nedeni ise parametre olarak verdiğimiz değerin aslında resmin adından oluşmuyor olmasıdır. Solution Explorer 'dan resmi seçip, Properties panelinden özelliklerine bakarsanız eğer aşağıda ki görüntü ile karşılaşırsınız.
Burada önemli olan değer AssetName özelliğidir. Bunu klasik Name özelliğine benzetebilirsiniz. İşte bizim Load methoduna parametre olarak vereceğimiz değer de bu isimdir.
Bir diğer önemli özellik ise ContentImporter ve ContentProcessor özellikleridir. Bu özelliklerde XNA 'in desteklediği çeşitli formatlar vardır. Seçtiğiniz dosya formatına uygun importer ve processor seçmek gerekmektedir. Aşağıdaki resimde Sprite1 için uygun Importer tipini görebilirsiniz.

Kodumuza geri dönersek eğer, anlaşılabilirliği kolaylaştırmak adına sprite1 adındaki değişkenimize imajımız atanmış diyebiliriz. Eğer şu anda uygulamayı F5 ile derleyip çalıştırırsanız ekranda hiçbir şey göremezsiniz. Aslında şu anda imajımız yüklenmiştir fakat henüz bunu ekrana çizdirmediğimiz için göremiyoruz. Hatırlarsanız daha önce SpriteBatch nesnesini tanıtırken çeşitli nesnelerin ekrana çizilmesinden sorumludur demiştik.
Dolayısıyla biz bu sorumluluğu önce;
- başlatmalıyız sonra,
- imajı ekrana çizdirmeliyiz ve en sonunda da,
- bu sorumluluğu sonlandımalıyız
spriteBatch.Begin();
spriteBatch.Draw(sprite1, new Vector2(10, 10), Color.White);
spriteBatch.End();Şimdi bu üç satır koda kısaca bir bakalım. İlk olarak bir çizim yapılacağı zaman bunu spriteBatch 'e Begin methodu ile bildirmek gerekiyor. Ardından çizme işleminin gerçekleşmesi için spriteBatch nesnesinin Draw methodunu çağırmak gerekiyor. Draw methodunun çeşitli overload edilebilir şekilleri bulunmaktadır. Biz burada 3 parametre alacak şekilde kullandık. 1.parametre, çizilecek olan nesneyi gösteriyor. 2. parametre, nesnenin ekranda ki pozisyonunu gösteriyor. 3. parametre olarakta renk değeri veriliyor(şu anda rengin bir geçerliliği yok) Son olarakta spriteBatch'e End methodu ile çizim işlemini sonlandırması gerektiğini söylüyoruz. Sonuçta programı derleyip çalıştırırsak nesnemizi oyun sahnesinde görebiliriz.
Şimdi yaptığımız bu işlemi bir adım daha ileri götürelim ve basitte olsa nesne yönelimli programlamanın faydalarını kullanmaya başlayalım.
Kendi Sınıfımızı Geliştirelim
İsterseniz buraya kadar yazdığımız kodları silebiliriz veya yeni bir proje üzerinden de gidebiliriz (Yeni proje için resimleri Sprites klasörü altına taşımayı unutmayalım).
İlk olarak sahne üzerindeki nesnelerimizi temsil edecek çok basit bir sınıf yazacağız. Bu sınıfın bazı temel özellikleri olacak. Bunlar ekranda hangi imajın gösterileceğini belirten, imajın boyutunu ve ekrandaki pozisyonunu belirten özellikler olacaktır. Ozaman projemize yeni bir Class ekleyelim ve adını da MyGameObject olarak verelim.
Sınıfımızı oluşturduktan sonra namespacelerimizi eklememiz gerekiyor. Aşağıdan da kopyalabilirsiniz.
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;Hemen ardından az önce belirttiğim özellikleri tutacak değişkenlerimizi tanımlayalım ve nesnemizi oluştururken rahat bir şekilde değer atayabilmek için default constructor 'ımızı 3 parametre alacak şekilde overload edelim.
public Texture2D image;
public Vector2 size;
public Vector2 position;public MyGameObject(Texture2D image, Vector2 size, Vector2 position)
{
this.image = image;
this.size = size;
this.position = position;
}Artık uygulamamızda kullanacağımız sınıf hazır. Şimdi Game1.cs dosyamıza geri dönelim ve global olacak şekilde aşağıdaki gibi bir nesne tanımlayalım.
MyGameObject gameObject1;Sonra bu nesnemizi LoadContent methodu içinde oluşturalım.
gameObject1 = new MyGameObject(Content.Load<Texture2D>("Sprites/sprite1"), new Vector2(48, 48), new Vector2(10, 10));1. parametre ile hangi imajın yükleneceğini belirtiyoruz. 2. parametre ile nesnemizin boyutlarını ve 3.parametre ile de nesnenin ekrandaki pozisyonunu belirtiyoruz.
Son olarak bu nesneyi Draw methodu içinde ekrana çizdiriyoruz.
spriteBatch.Begin();
spriteBatch.Draw(gameObject1.image, gameObject1.position, Color.White);
spriteBatch.End();Şimdi uygulamayı derleyip çalıştırırsanız eğer sonucu görebilirsiniz. Kısaca özetlemek gerekirse artık bizim bir çok imajı uygulama anında nesne olarak ele alabileceğimiz, onun temel 3 özelliğine erişebileceğimiz bir sınıfımız var. Bir başka deyişle, artık MyGameObject sınıfı sayesinde daha modüler bir şekilde uygulama geliştirebiliriz.
Ekrandaki Nesneyi Hareket Ettirmek
Şimdi ekrana çizdiğimiz bu nesneye hareket kazandırmamız gerekiyor. Dilerseniz önce işin arka plandaki mantığına kısaca bir bakalım.
Bir nesnenin ekranda hareket etmesi için gereken en basit şey bir tuşa basılmasıdır. Peki biz uygulama içinde herhangi bir zamanda herhangi bir tuşa basıldıgını nasıl anlayacağız ? Bu sorunun en basit cevabı tabiki bu durumu sürekli kontrol etmektir Peki bir işlemin oyun içinde sürekli yapılması gerekiyorsa bu işlemi yapacak kodlar nereye yazılabilir ? Update methodu içine tabiki. Bunun dışında mesela kullanıcı W tuşuna basınca ekrandaki nesnenin yukarı hareket etmesini istiyorsak, bu durumda yapmamız gereken şey nesnenin Y pozisyonunu azaltmaktır. Benzer şekilde sağa doğru gitmesini istiyorsak X değerini yükseltmemiz gerekiyor. Şimdi bu mantığı uyguladığımız somut örneği görmek için aşağıdaki kodları Update methodu içine yazıyoruz. Ardından da kısaca kodlarımızı inceleyeceğiz.
KeyboardState ks = Keyboard.GetState();
if (ks.IsKeyDown(Keys.W))
{
gameObject1.position.Y -= 5;
}
if (ks.IsKeyDown(Keys.S))
{
gameObject1.position.Y += 5;
}
if (ks.IsKeyDown(Keys.A))
{
gameObject1.position.X -= 5;
}
if (ks.IsKeyDown(Keys.D))
{
gameObject1.position.X += 5;
}Öncelikle KeyboardState türünden bir değişken tanımlıyoruz ve buna Keyboard sınıfının GetState() methodu ile bir değer atıyoruz. KeyboardState o anda klavye ile ilgili bir durumu tutar. Burada yapılan işlem de şudur; GetState() ile o anki durumu ks 'ye atıyoruz. Ardından if kontrolleri ile bu durumu kontrol ediyoruz. ks.IsKeyDown() 'a Keys tipinden bir enumerator yardımıyla parametre veriyoruz ve diyoruz ki, tuşlardan W 'ya basıldı mı, eğer basıldıysa, gameObject1'in yukarı dogru hareket etmesi için, position.Y değerini, sahip oldugu değerden 5 azalt. Bu nesnenin 5 pixel yukarı hareket etmesini sağlar. Diğer if satırlarıda aynı mantıkla çalışıyor tabiki.
Şimdi ikinci bir nesne ekleyip bunların çarpışıp çarpışmadığını kontrol etmemiz gerekiyor.
Çarpışma ( Collision) Kontrolü
Yine aynı mantıkla sprite2 isimli imajımızı da ekrana çizdirelim. Fakat bunu bu sefer ekranın sağ alt köşesine koyalım.
Sınıf düzeyinde;
MyGameObject gameObject2;LoadContent içinde;
gameObject2 = new MyGameObject(Content.Load<Texture2D>("Sprites/sprite2"), new Vector2(70f, 80f), new Vector2((graphics.PreferredBackBufferWidth - 70f), (graphics.PreferredBackBufferHeight - 80f)));Burada farklı iki kod göze çarpıyor.
graphics.PrefferedBackBufferHeight
graphics.PrefferedBackBufferWidthBu özellikler oyun sahnesinin genişlik ve yükseklik değerlerini tutarlar. Bunları neden kullanıyoruz diye düşünebilirsiniz. Bunun nedeni, ikinci nesnemizin ekranın sağ alt köşesinde göstermektir. Ekranın bu genişlik ve yükseklik değerlerinden, nesenin genişlik(48) ve yükseklik(48) değerlerini çıkartıp, bunu nesnemizin position özelliğine parametre olarak verirseniz istediğimiz şeyi gerçekleştirmiş oluruz. Şimdi ikinci nesnemizi ekrana çizdirmek için Draw methodu içine gerekenleri yazalım.
Draw içinde;
spriteBatch.Begin();
spriteBatch.Draw(gameObject1.image, gameObject1.position, Color.White);
spriteBatch.Draw(gameObject2.image, gameObject2.position, Color.White);
spriteBatch.End();
Artık uygulamayı derleyip çalıştırırsanız aşağıdaki gibi bir sonuç alırsınız.

Ardından ikinci eklediğimiz nesneye de hareket özelliği kazandıralım. Ozaman bunun için aşağıdaki kodları yukarda yazdığımız update methodu içindeki kodların altına yazmamız yeterlidir. Bu seferki hareket tuşlarımız ok tuşları olacak.
if (ks.IsKeyDown(Keys.Up))
{
gameObject2.position.Y -= 5;
}
if (ks.IsKeyDown(Keys.Down))
{
gameObject2.position.Y += 5;
}
if (ks.IsKeyDown(Keys.Left))
{
gameObject2.position.X -= 5;
}
if (ks.IsKeyDown(Keys.Right))
{
gameObject2.position.X += 5;
}Buraya kadar yaptıklarımıza bakarsak artık elimizde birbirinden bağımsız şekilde hareket edilebilen 2 tane nesne var. Şimdi bunların çarpışıp çarpışmadığını nasıl programlayabileceğimize bakalım.
İlk olarak çarpışma kontrolünün teorik olarak 2boyutlu bir ekranda nasıl gerçekleştiğini anlayabilmek için aşağıdaki resmi inceleyelim.

Şekilde görüldüğü gibi böyle bir çarpışmanın algoritması şu şekilde özetlenebilir;
beyaz cismin X pozisyonu + genişliğinin toplamı, gri cismin X pozisyonundan büyükse ve
beyaz cismin X pozisyonu, gri cismin X pozisyonu + genişliği, toplamından küçükse ve
beyaz cismin Y pozisyonu + yüksekliği, gri cismin Y pozisyonundan büyükse ve
beyaz cismin Y pozisyonu, gri cismin Y pozisyonu + yüksekliğinden küçükse eğer
bunlar çarpışmıştır.
değilse
çarpışmamıştır.
Şimdi bu durumu somut olarak görebilmek için koda dökmek gerekiyor.
Öncelike MyGameObject sınıfımıza geri dönüyoruz ve CollisionControl adında MyGameObject tipinden bir paremetre alan ve geriye bool değer döndüren bir fonksiyon tanımlıyoruz.
public bool CollisionControl(MyGameObject gameObject)
{
if (this.position.X + this.size.X > gameObject.position.X &&
this.position.X < gameObject.position.X + gameObject.size.X &&
this.position.Y + this.size.Y > gameObject.position.Y &&
this.position.Y < gameObject.position.Y + gameObject.size.Y)
return true;
else
return false;
}Artık sınıfımız içinde çarpışma kontolünü yapan fonksiyonumuz hazır. Peki bunu oyun içinde nasıl kontrol edeceğiz. Çarpışma kontrölü de nesnelerin hareketleri gibi sürekli kontrol edilmesi gereken bir işlemdir. Dolayısıyla bunu Update methodu içine yazmamız gerekiyor.
Şimdi Game1.cs sınıfımıza geri dönüyoruz ve global düzeyde bool tipinde IsCollide adında bir değişken tanımlıyoruz. Daha sonra Update methodu içinde çarpışma durumunu kontrol ediyoruz ve eğer çarpışma varsa IsCollide değişkenine true değerini atıyoruz.
Sınıf içinde;
bool IsCollide = false;Update methodu içinde;
if (gameObject1.CollisionControl(gameObject2))
{
IsCollide = true;
}
else
{
IsCollide = false;
}Bu kodları da uygulamamıza ekledikten sonra artık çarpışma kontrolümüz de yapılmaktadır. Fakat şu anda biz göremiyoruz. Dolayısıyla bu durumu görebilmek için çarpışma gerçekleştiği zaman ekranın ortasında bir tane kırmızı çarpı işareti gösterelim. Buna da sprite3 diyelim ve Sprites klasörümüz altına koyalım.
Son olarak Draw methodu içinde çarpışma gerçekleştiyse kırmızı çarpıyı gösterecek kodu yazıyoruz.
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);spriteBatch.Begin();
if (IsCollide == true)
{
spriteBatch.Draw(Content.Load<Texture2D>("Sprites/sprite3"),new Vector2(((graphics.PreferredBackBufferWidth / 2) - 50),((graphics.PreferredBackBufferHeight / 2) - 50)), Color.White);
}
spriteBatch.Draw(gameObject1.image, gameObject1.position, Color.White);
spriteBatch.Draw(gameObject2.image, gameObject2.position, Color.White);
spriteBatch.End();base.Draw(gameTime);
}Sonuç olarak uygulamayı çalıştırıp nesneleri çarpıştırmayı denerseniz eğer çarpışma anında ekranın ortasında çarpı işaretini görebilirsiniz. Böylece çarpışma olup olmadıgını net bir şekilde görmüş oluyoruz.
Örnek Uygulama
Mehmet Aydın Ünlü

0 yorum:
Yorum Gönder