Oyunlarda nesnelerin çarpışması (ya da kesişmesi diyebiliriz) kritik öneme sahiptir. Özellikle yarış oyunlarında oyuncuların çarpışmamak, ya da yoldan çıkmamak için ne ölçüde çaba harcadıklarını, hırslandıklarını gözlemleyebiliriz. Böyle bir oyunda elbette çarpışmanın kalitesi ve gerçeğe yakın olması da oyuncuyu daha az çileden çıkaracaktır.
Bu makalemizde de örnek bir senaryo üzerinden devam edeceğiz. Ekran üzerinde bulunacak 2 adet kırmızı topun ekranın farklı yerlerinden rastgele çıkarak aracımızın üzerine doğru farklı hızlarda hareket etmesini sağlayacağız. Aracımızı (car.png) ekranın üzerinde parmağımızla hareket ettirerek bu toplardan kaçırmaya çalışacağız. Toplar ile aracın çarpışması durumunda oyundan 1 hakkımız eksilecek. Toplam 3 hakkımız olacak ve 3 hakkımızı da kaybedersek oyun sonlanmış olacak.
2D Rectangle Collision
Aşağıdaki araç ve kırmızı top resimlerinde açıkça görüldüğü gibi dikdörtgenlerin kesişmesinde, asıl kesişim noktaları nesnelerin ekranda görünen yanlarından farklı olarak imajın dikdörtgen olarak belirlenen kısımlarının kesişmesidir.
Araç için car.png, toplar için ball-red-48.png ve yol kenar çizgileri içinde blackLine.png imajlarını kullanacağız. Aşağıdaki dosyaları sağ tıklayarak bilgisayarınıza kaydedebilirsiniz.
Şimdi C# ile ön hazırlığımızı yaparak, oyunumuz için gerekli olacak kodlarımızı yazmaya başlayalım. Öncelikle private olarak Game1.cs sınıfımızda kullanacağımız Texture2D , Vektor2D ve SpriteFont nesnelerimizi oluşturalım.
GraphicsDeviceManager graphics; SpriteBatch spriteBatch; // 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 bilgi için SpriteFont ve Vektörü SpriteFont textFont; Vector2 textPosition; const string TEXT_TEMPLATE = "Gokhan Manduz Car Game - {0} "; string TEXT = ""; //Yolu belirtmek için kullanılacak Texture ve Vektörler Texture2D line1Texture; Vector2 line1Position; Texture2D line2Texture; Vector2 line2Position; bool collisionDetected; short game = 3; Random random = new Random(); int ballSpeed = 10;Game1 constractor içerisinde herhangi bir değişiklik yapmıyoruz. Default olarak belirtilen TargetElapsedTime zaman aralığı ile update ve draw methodlarımız 30 fps zaman aralığı ile sürekli çalışacaktır.
public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; TargetElapsedTime = TimeSpan.FromTicks(333333); }Şimdide Content projemize eklediğimiz nesnelerimizi LoadContent override methodumuz içerisinde oyunumuzda kullanmak üzere hazırlayarlayalım oyun içerisinde bu nesnelerin hangi pozisyonda (koordinatta) başlayacağını belirleyelim.
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_2Texture = ball_1Texture = Content.Load<Texture2D>("Ball-red-48"); ball_1Position = new Vector2(-150, 30); ball_2Position = new Vector2(-150, 260); textFont = this.Content.Load<SpriteFont>("CarGameFont"); textPosition = new Vector2(2, 25); line2Texture = line1Texture = Content.Load<Texture2D>("blackLine"); line1Position = new Vector2(-800, 0); line2Position = new Vector2(-800, 470); }Tabiki oyunumuzun kilit kodları Update override methodu içerisinde yer alacaktır. Update methodu her çalıştırıldığında nesnelerimizin yeni pozisyonunu, yer değiştirmelerden sonra herhangi bir çarpışma olup olmadığını ve bir takım hesaplamalarımızı buraya yazacağız. Öncelikle Update methodumuz içerisinde 2D Rectangle ile çarpışma olup olmadığını kontrol edelim. Eğer çarpışma meydana gelmiş ise ekranımızın arka plan rengini de kırmızıya boyayalım. 3 çarpışmadan sonra oyunumuz sonlanacaktır. Kırmızı toplarımız X koordinatında soldan sağa doğru farklı hızlarda ve her defasında farklı Y koordinatından gelerek aracımıza doğru ilerleyecektir. Aracımıza bir yol üzerinde gidiyormuş izlenimi kazandımak için de yol kenarlarında bulunan çizgilerimizi sürekli olarak X koordinatında soldan sağa doğru hareket ettireceğiz. Update methodu içerisindeki kod satırlarını comment satırlarına dikkat ederek inceleyelim.
protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); switch (game) { case 3: //-- 3 oyun hakkımız var. TEXT = string.Format(TEXT_TEMPLATE, "|||"); break; case 2: //-- 2 oyun hakkımız kaldı. TEXT = string.Format(TEXT_TEMPLATE, "||"); break; case 1: //-- 1 oyun hakkımız kaldı. TEXT = string.Format(TEXT_TEMPLATE, "|"); break; case 0: //-- 0 oyun hakkı. Oyundan çıkılacak :) this.Exit(); break; } //-- 13 ve 20 birim arasında rastgele topların hızlarını değiştirelim. // (X koordinatında) ballSpeed = random.Next(13, 20); ball_1Position.X += ballSpeed; ball_2Position.X += (ballSpeed - 3); //-- //-- X koordinatındaki pozisyon 900'den fazla ise toplar X'de tekrar başa dönsün. // ve Y ekseninde toplar rasgele bir pozisyonda başlasın. if (ball_1Position.X > 900) { ball_1Position.X = -60; int ball1_Y = random.Next(ball_1Texture.Height, (480 - ball_1Texture.Height)); ball_1Position.Y = ball1_Y; } if (ball_2Position.X > 900) { ball_2Position.X = -60; int ball2_Y = random.Next(ball_2Texture.Height, (480 - ball_1Texture.Height)); ball_2Position.Y = ball2_Y; } line1Position.X += 10; line2Position.X += 10; if (line1Position.X > -2) { line1Position.X = -800; line2Position.X = -800; } //-- Ekrana dokunulduysa hangi pozisyona dokunulduğu bilgisi alınır. // ve aracı o yönde ve mesafeye göre hızı ayarlanarak aracın X,Y değerleri arttırılır. 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)); } //-- Collision Detaction kod satırları Rectangle carRectangle = new Rectangle((int)carPosition.X, (int)carPosition.Y, carTexture.Width, carTexture.Height); Rectangle ball1Rectangle = new Rectangle((int)ball_1Position.X, (int)ball_1Position.Y, ball_1Texture.Width, ball_1Texture.Height); Rectangle ball2Rectangle = new Rectangle((int)ball_2Position.X, (int)ball_2Position.Y, ball_2Texture.Width, ball_2Texture.Height); if (carRectangle.Intersects(ball1Rectangle) || carRectangle.Intersects(ball2Rectangle)) { collisionDetected = true; } else { if (collisionDetected) { //Oyun hakkımızı 1 azaltıyoruz game--; } collisionDetected = false; } //-- base.Update(gameTime); }Yukarıdaki kod satırlarına dikkat edecek olursak çarpışma olup olmadığını rectangle nesnemizin Intersects methodunu kullanarak gerçekleştiriyoruz.
Şimdi de aynı örneğimiz üzerinden yalnızca Collision Detection kod satırlarımızı düzenleyerek 2D Per-Pixel Collision Detaction ile çarpışma olup olmadığını kontrol edeceğiz.
2D Per-Pixel Collision
Piksel piksel bölünmüş olan nesnelerimizin aslında bazı piksel renklerinin transparent, bazılarının ise transparent dan farlı olarak bir renge sahip olduğunu görebiliyoruz. Per-Pixel Collision da nesnelerimizin kesişmesini piksellerindeki renk farklılıklarını kontrol ederek yakalayacağız. Imajlar kesiştiğinde kesişen bölgelerdeki renklerin transparent ya da saydam olması muhtemeldir. Bu sebepten dolayı imajların kesişiminde transparent renkten farklı olan renklerin aynı piksel üzerinde yer almasını dikkate alacağız. C# kodlarımızı bu özelliğe uygun şekilde hazırlayalım.
Öncelikle private olan değişkenlerimizin olduğu kısıma Color array olarak carTextureData, ball1TextureData ve ball2TextureData isimli nesnelerimizi tanımlıyoruz. Hemen ardından LoadContent override methodumuz içerisinde de color array nesnelerimizi aşağıdaki gibi oluşturalım.
private değişkenler;
Color[] carTextureData; Color[] ball1TextureData; Color[] ball2TextureData;LoadContent methoduna eklememiz gereken c# kod satırlarımız.
carTextureData = new Color[carTexture.Width * carTexture.Height]; ball2TextureData = ball1TextureData = new Color[ball_1Texture.Width * ball_1Texture.Height]; carTexture.GetData(carTextureData); ball_1Texture.GetData(ball1TextureData); ball_2Texture.GetData(ball2TextureData);Ve Update override methodumuzun son hali ile static bool IntersectPixel (Piksel çakışması) methodumuzu aşağıdaki gibi hazırlayalım.
protected override void Update(GameTime gameTime) { if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); switch (game) { case 3: //-- 3 oyun hakkımız var. TEXT = string.Format(TEXT_TEMPLATE, "|||"); break; case 2: //-- 2 oyun hakkımız kaldı. TEXT = string.Format(TEXT_TEMPLATE, "||"); break; case 1: //-- 1 oyun hakkımız kaldı. TEXT = string.Format(TEXT_TEMPLATE, "|"); break; case 0: //-- 0 oyun hakkı. Oyundan çıkılacak :) this.Exit(); break; } //-- 13 ve 20 birim arasında rastgele topların hızlarını değiştirelim. // (X koordinatında) ballSpeed = random.Next(13, 20); ball_1Position.X += ballSpeed; ball_2Position.X += (ballSpeed - 3); //-- //-- X koordinatındaki pozisyon 900'den fazla ise toplar X'de tekrar başa dönsün. // ve Y ekseninde toplar rasgele bir pozisyonda başlasın. if (ball_1Position.X > 900) { ball_1Position.X = -60; int ball1_Y = random.Next(ball_1Texture.Height, (480 - ball_1Texture.Height)); ball_1Position.Y = ball1_Y; } if (ball_2Position.X > 900) { ball_2Position.X = -60; int ball2_Y = random.Next(ball_2Texture.Height, (480 - ball_1Texture.Height)); ball_2Position.Y = ball2_Y; } line1Position.X += 10; line2Position.X += 10; if (line1Position.X > -2) { line1Position.X = -800; line2Position.X = -800; } //-- Ekrana dokunulduysa hangi pozisyona dokunulduğu bilgisi alınır. // ve aracı o yönde ve mesafeye göre hızı ayarlanarak aracın X,Y değerleri arttırılır. 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)); } //-- Per-Pixel Collision Detaction kod satırları Rectangle carRectangle = new Rectangle((int)carPosition.X, (int)carPosition.Y, carTexture.Width, carTexture.Height); Rectangle ball1Rectangle = new Rectangle((int)ball_1Position.X, (int)ball_1Position.Y, ball_1Texture.Width, ball_1Texture.Height); Rectangle ball2Rectangle = new Rectangle((int)ball_2Position.X, (int)ball_2Position.Y, ball_2Texture.Width, ball_2Texture.Height); if (IntersectPixels(carRectangle, carTextureData, ball1Rectangle, ball1TextureData) || IntersectPixels(carRectangle, carTextureData,ball2Rectangle, ball2TextureData)) { collisionDetected = true; } else { if (collisionDetected) { //Oyun hakkımızı 1 azaltıyoruz game--; } collisionDetected = false; } //-- base.Update(gameTime); } static bool IntersectPixels(Rectangle rectangleCar, Color[] dataCar, Rectangle rectangleBall, Color[] dataBall) { int top = Math.Max(rectangleCar.Top, rectangleBall.Top); int bottom = Math.Min(rectangleCar.Bottom, rectangleBall.Bottom); int left = Math.Max(rectangleCar.Left, rectangleBall.Left); int right = Math.Min(rectangleCar.Right, rectangleBall.Right); for (int y = top; y < bottom; y++) { for (int x = left; x < right; x++) { Color colorA = dataCar[(x - rectangleCar.Left) + (y - rectangleCar.Top) * rectangleCar.Width]; Color colorB = dataBall[(x - rectangleBall.Left) + (y - rectangleBall.Top) * rectangleBall.Width]; // Eğer her 2 piksel rengide transparent dan farklı ise kesişme var if (colorA.A != 0 && colorB.A != 0) { return true; } } } // Kesişme bulunamadı ise false return false; }Oyunumuzda aracımızı hareket ettirirken TouchCollection nesnesinden faydalandık. Eğer istersek Windows Phone 7 cihazlarında bulunan Akselerometreden faydalanarak da aracımızı hareket ettirebiliriz. Akselerometre ile ilgili detaylı bilgiye buradan erişilebilir.
Son olarak Draw override methodumuzda nesnelerimizi ekrana çizdirecek olan C# kodlarımızı yazarak makalemizi sonlandıralım. Eğer kodları ilgili methodlarımızın içerisine yazıp F5 ile Windows Phone 7 emulator üzerinde çalıştıracak olursak aşağıdaki ekran görüntüsünü elde edeceğiz.
protected override void Draw(GameTime gameTime) { if(collisionDetected) GraphicsDevice.Clear(Color.Red); else GraphicsDevice.Clear(Color.White); spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend); spriteBatch.Draw(carTexture, carPosition, collisionDetected ? Color.Red : 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(); spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend); spriteBatch.Draw(line1Texture, line1Position, Color.White); spriteBatch.End(); spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend); spriteBatch.Draw(line2Texture, line2Position, Color.White); spriteBatch.End(); base.Draw(gameTime); }
Windows Phone 7 de keyifli oyunlar... Kolay gelsin.
gokhanmanduz.blogspot.com
gokhanmanduz@hotmail.com
gmanduz@gmail.com
Hiç yorum yok:
Yorum Gönder