Mathhelper | Engin Polat\'ın Windows 8 , Windows Phone 8 ve C# içerikli programcılık sitesi

Arşiv

Etiketlenen yazılar mathhelper

XNA Oyunu / Çanakkale Geçilmez – 2

27 June 2011 Yorum yapılmamış

XNA ile Oyun Programlama serimize, daha önce başlayıp tamamlamadığımız Çanakkale Geçilmez oyununu tamamlayarak devam ediyoruz.

Bu yazıyı okumadan önce XNA konusunda diğer makaleler için XNA kategorisine bir göz atmanızı tavsiye ederim.

Düşman gemilerine yaptığımız atış miktarını ve isabet adedini tutacağımız iki değişkeni sınıf seviyesinde tanımlayalım;

int SKOR_ATIS = 0;
int SKOR_PUAN = 0;

Oyuna, düşman gemileri ve düşman gemilerine atacağımız gülleler eklemek için, sınıf seviyesinde GameObject sınıfında diziler oluşturacağız;

public GameObject[] Gemiler;
public GameObject[] Gulleler;

Ayrıca, düşman gemilerine ve onlara atacağımız güllelere sınırlama getirmek için iki sabiti (const) sınıf seviyesinde tanımlıyoruz;

public const int DUSMAN_ADET = 3;
public const int GULLE_ADET = 6;

Bu sayede, oyunun zorluk derecesini değiştirebilmek için iki alanımız oluyor; aynı anda savaşacağımız düşman gemisi adedi ve onlara atabileceğimiz gülle adedi. Aynı anda attığımız güllelerden bir tanesi hedefi bulmadan veya ekran dışına çıkmadan yeni gülle atamıyoruz.

Bu oyunda, düşman gemisi adedini 3 olarak, onlara aynı anda atabileceğimiz gülle adedini de 6 olarak belirledik.

LoadContent() methodunda bu değişkenlere başlangıç değerlerini atamalıyız;

Gemiler = new GameObject[DUSMAN_ADET];
for (int iLoop = 0; iLoop < DUSMAN_ADET; iLoop++)
{
	Gemiler[iLoop] = new GameObject(Content.Load<Texture2D>("DusmanGemi"));
}

Gulleler = new GameObject[GULLE_ADET];
for (int iLoop = 0; iLoop < GULLE_ADET; iLoop++)
{
	Gulleler[iLoop] = new GameObject(Content.Load<Texture2D>("Gulle"));
}

Update() method'unda boşluk tuşuna (Space) basılınca gülle atılmasını sağlayacak methodu çağıracak kodu ekliyoruz;

if (pks.IsKeyDown(Keys.Space) && ks.IsKeyUp(Keys.Space))
	GulleAtesle();

GulleAtesle() method’unda 6 gülle’nin herbirine tek tek bakarak, ekranda olmayan var mı kontrol ediyoruz.

Eğer o anda ekranda olmayan bir gülle bulursak (IsAlive özelliğinin false değer içermesinden) düşman gemisine doğru fırlatıyoruz.

Fırlatma işlemini yaparken güllenin fırlatma anındaki yeri ve fırlatma güzergahına dikkat etmeliyiz.

Güllenin Position özelliğine Top’umuzun X ve Y eksenindeki yerini veriyoruz, böylece, gülle tam olarak top’umuzun ucundan fırlatılacak.

Velocity özelliği ile güllenin güzergahını belirliyoruz; Top’umuzun baktığı açının sinus‘unu ve kosinus‘unu alarak, hız sabiti olan 5 değeri ile çarpıyoruz.

Not : Top’umuz ekranın altında, gemiler üstünde olduğu için Y eksenindeki hız sabitini -1 ile çarpıp, -5 değerini elde ediyoruz.

Ayrıca, SKOR_ATIS değişkeninin değerini de bir yükseltiyoruz.

private void GulleAtesle()
{
	foreach (GameObject Gulle in Gulleler)
	{
		if (!Gulle.IsAlive)
		{
			SKOR_ATIS++;
			Gulle.IsAlive = true;
			Gulle.Position = new Vector2(Top.Position.X - Gulle.Center.X, Top.Position.Y);
			Gulle.Velocity = new Vector2((float)Math.Sin(Top.Rotation) * 5, (float)Math.Cos(Top.Rotation) * -5);
			break;
		}
	}
}

Böylece, her boşluk (Space) tuşuna basıldığında 6 gülleden birisi topumuzun ucundan düşman gemisine doğru gönderilecektir. Eğer o anda ekranda 6 gülle varsa, gülle gönderilmeyecektir.

Update() method’unda, düşman gemilerini de yönetiyoruz;

foreach (GameObject Gemi in Gemiler)
{
	if (Gemi.IsAlive)
	{
		Gemi.Position += Gemi.Velocity;

		if (Gemi.Position.X < 0)
			Gemi.IsAlive = false;
	}
	else
	{
		Gemi.IsAlive = true;
		Gemi.Position = new Vector2(PENCERE_GENISLIK, MathHelper.Lerp(0, (float)(PENCERE_YUKSEKLIK * 0.6), (float)Rastgele.NextDouble()));
		Gemi.Velocity = new Vector2(MathHelper.Lerp(-2, -4, (float)Rastgele.NextDouble()), 0);
	}
}

Tüm düşman gemileri (DUSMAN_ADET sabitinden dolayı 3 adet) içerisinde dönen bir foreach döngüsü ile ilgili düşman gemisinin o anda hayatta olup/olmadığına bakıyoruz;

Eğer hayatta ise; gemi'nin yerini, gidiş yönünde hızı kadar ilerletiyoruz. Eğer gemi'nin yeni yeri pencere dışında ise, bir sonraki ekran güncellemesinde gemi'yi tekrar kullanabilmek için öldürüyoruz.

Eğer hayatta değilse; Gemi'yi ekrana çıkartabilmek için hayatta durumuna getiriyoruz, rastgele bir yer ve hız veriyoruz.

Rastgele yer belirlerken, pencerenin en üstünden itibaren %60'lık kısmını kullanıyoruz.

Rastgele hız belirlerken, MathHelper sınıfının Lerp() methodundan faydalanıyoruz.

Lerp() method'u, 3 parametre alır;

  • Değer 1 (Kaynak değer)
  • Değer 2 (Kaynak değer)
  • Büyüklük (0 ile 1 arası değer, Değer 2'nin ağırlığı)

Büyüklük parametresine 0 değer vermek, Değer 1'i, 1 değer vermek, Değer 2'yi döndürür.

Biz bu örnek'te düşman gemilerinin ekranın sağında belirmesi ve ekranın soluna doğru ilerlemesi için, -2 ve -4 arası hız'a sahip olmalarını sağladık.

Update() method'unda, ekrandaki gülleleri de yönetiyoruz;

foreach (GameObject Gulle in Gulleler)
{
	if (Gulle.IsAlive)
	{
		Gulle.Position += Gulle.Velocity;
		if (!Pencere.Contains(Gulle.TextureRectangle))
			Gulle.IsAlive = false;

		foreach (GameObject Gemi in Gemiler)
		{
			if (Gemi.TextureRectangle.Contains(Gulle.TextureRectangle))
			{
				SKOR_PUAN++;
				GemiVurulduSes.Play();
				Gemi.IsAlive = false;
				Gulle.IsAlive = false;
				break;
			}
		}
	}
}

Sadece hayatta olan güllelere teker teker bakıyoruz; (GULLE_ADET sabitinden dolayı en fazla 6 adet)

Gülle’nin yerini, hızı ve doğrultusu kadar ilerletiyoruz. Eğer pencere dışına çıktı ise, gülle’yi öldürüyoruz.

Hayatta olan her gülle için, hayatta olan her gemi ile çarpışma testi yapıyoruz. Eğer ilgili gülle gemi’lerden bir tanesi ile çarpışıyorsa, gülle’yi ve gemi’yi öldürüp, SKOR_PUAN değişkenini bir yükseltiyoruz.

Draw() method’unda, Arkaplan, Top, hayatta olan gemiler ve hayatta olan gülleleri ekrana çizdiriyoruz;

GraphicsDevice.Clear(Color.CornflowerBlue);

spriteBatch.Begin(SpriteBlendMode.AlphaBlend);

spriteBatch.Draw(Arkaplan, Pencere, Color.White);

spriteBatch.Draw(Top.Texture, Top.Position, null, Color.White, Top.Rotation, Top.Center, 1, SpriteEffects.None, 0);

foreach (GameObject Gemi in Gemiler)
{
	if (Gemi.IsAlive)
	{
		spriteBatch.Draw(Gemi.Texture, Gemi.Position, Color.White);
	}
}

foreach (GameObject Gulle in Gulleler)
{
	if (Gulle.IsAlive)
	{
		spriteBatch.Draw(Gulle.Texture, Gulle.Position, Color.White);
	}
}

Skor tablosunu çizdirebilmek için, sınıf seviyesinde iki yeni değişken eklememiz lazım;

Texture2D Karakterler;
Texture2D Sayilar;

LoadContent() method’unda bu değişkenlere değer atıyoruz;

Karakterler = Content.Load("Karakterler");
Sayilar = Content.Load("Sayilar");

Karakterler imajı’nın (0, 0) noktasından itibaren (113, 25) büyüklüğündeki alanı arasında kalan kısmı ekrana çizdirmek için, Draw() method’una;

spriteBatch.Draw(Karakterler, new Vector2((float)(PENCERE_GENISLIK * 0.05), (float)(PENCERE_YUKSEKLIK * 0.90)), new Rectangle(0, 0, 113, 25), Color.White);

Karakterler imajı’nın (0, 25) noktasından itibaren (113, 25) büyüklüğündeki alanı arasında kalan kısmı ekrana çizdirmek için, Draw() method’una;

spriteBatch.Draw(Karakterler, new Vector2((float)(PENCERE_GENISLIK * 0.75), (float)(PENCERE_YUKSEKLIK * 0.90)), new Rectangle(0, 25, 113, 25), Color.White);

Atış adedimizi ve Skor adedimizi ekrana çizdirmek için gerekli method çağrılarını (DrawAtis(), DrawSkor()) Draw() method’unda yapıyoruz.

private void DrawAtis()
{
	string Atis = SKOR_ATIS.ToString();
	for (int iLoop = 0; iLoop < Atis.Length; iLoop++)
	{
		int Rakam = Convert.ToInt32(Atis.Substring(iLoop, 1));
		spriteBatch.Draw(Sayilar, new Vector2((float)(PENCERE_GENISLIK * 0.05) + (23 * iLoop) + 120, (float)(PENCERE_YUKSEKLIK * 0.90)), new Rectangle(Rakam * 23, 0, 23, 25), Color.White);
	}
}

private void DrawSkor()
{
	string Atis = SKOR_PUAN.ToString();
	for (int iLoop = 0; iLoop < Atis.Length; iLoop++)
	{
		int Rakam = Convert.ToInt32(Atis.Substring(iLoop, 1));
		spriteBatch.Draw(Sayilar, new Vector2((float)(PENCERE_GENISLIK * 0.75) + (23 * iLoop) + 120, (float)(PENCERE_YUKSEKLIK * 0.90)), new Rectangle(Rakam * 23, 0, 23, 25), Color.White);
	}
}

Böylece, oyunumuzun kodlamasını bitirmiş olduk.

XNA ile Oyun Programlama - Çanakkale Geçilmez Oyunu

Oyunu tüm kodlarını buradan indirebilirsiniz.

XNA Oyunu / Çanakkale Geçilmez – 1

11 May 2011 Yorum yapılmamış

Bu yazımı okumadan önce XNA konusundaki diğer makalelerimi okumanızı öneririm.

Bu sefer yapacağımız oyun, 30 Ağustos‘un ruhuna uygun olacak – Çanakkale Geçilmez.

Önce görsellerimizi verelim;

XNA - Çanakkale Geçilmez - Arkaplan XNA - Çanakkale Geçilmez - Düşman Gemisi XNA - Çanakkale Geçilmez - Top XNA - Çanakkale Geçilmez - Gülle XNA - Çanakkale Geçilmez - Karakterler XNA - Çanakkale Geçilmez - Sayılar

İki tane de ses dosyamız var;

Gemi Vuruldu Sesi

Arkaplan Müziği

Başlayalım oyunumuzu yazmaya; Çanakkale Geçilmez projemizi oluşturduktan ve Game1.cs‘in ismini GameLoop.cs olarak değiştirdikten sonra, class seviyesindeki değişkenlerimizi tanımlayalım;

public const int PENCERE_GENISLIK = 800;
public const int PENCERE_YUKSEKLIK = 600;
public const bool TAM_EKRAN = false;

Texture2D Arkaplan;

Oyun penceremizin sınırlarını tutacağımız Rectangle tipinde bir değişkeni class seviyesindeki değişkenlerimize ekleyelim;

Rectangle Pencere;

Gelelim bu değişkenleri kullanmaya, GameLoop class‘ımızın constructor methodunda aşağıdaki kodları yazalım;

graphics.PreferredBackBufferWidth = PENCERE_GENISLIK;
graphics.PreferredBackBufferHeight = PENCERE_YUKSEKLIK;
graphics.IsFullScreen = TAM_EKRAN;
Pencere = new Rectangle(0, 0, PENCERE_GENISLIK, PENCERE_YUKSEKLIK);

Ses dosyalarımız için class seviyesinde değişkenlerimiz tanımlayalım;

SoundEffectInstance ArkaplanSes;
SoundEffectInstance GemiVurulduSes;

LoadContent() method‘unda değişkenlerimize yükleme işlemlerini gerçekleştirelim;

Arkaplan = Content.Load<Texture2D>("Arkaplan");

ArkaplanSes = Content.Load<SoundEffect>("pong").CreateInstance();
GemiVurulduSes = Content.Load<SoundEffect>("ir_begin").CreateInstance();

ArkaplanSes.IsLooped = true;
ArkaplanSes.Volume = 0.3f;
ArkaplanSes.Play();

Ses dosyaları ile yaptığımız bu işlemleri XNA ile Pong oyunu yazalım – 2 yazımdan hatırlayacaksınız.

Bu oyunumuzda aktörleri Nesne Yönelimli Programlama (Object Orriented Programming) kurallarına uyarak oluşturalım; oyun projemize yeni bir class ekleyelim ve ismini GameObject.cs verelim.

Çanakkale Geçilmez oyunumuzda oluşturacağımız tüm nesneleri GameObject class’ından oluşturacağız.

public class GameObject
{
	public bool IsAlive;
	public Texture2D Texture;
	public Vector2 Position;
	public Vector2 Center;
	public Vector2 Velocity;
	public float Rotation;

	private Rectangle _TextureRectangle;
	public Rectangle TextureRectangle
	{
		get
		{
			_TextureRectangle.X = (int)Position.X;
			_TextureRectangle.Y = (int)Position.Y;

			return _TextureRectangle;
		}
	}

	public GameObject(Texture2D Texture)
	{
		this.IsAlive = false;
		this.Texture = Texture;
		this.Center = new Vector2(Texture.Width / 2, Texture.Height / 2);
		this.Position = Vector2.Zero;
		this.Velocity = Vector2.Zero;
		this.Rotation = 0;

		this._TextureRectangle = new Rectangle(0, 0, Texture.Width, Texture.Height);
	}
}

Yukarıdaki kodu incelediğimizde;

  • Oyun nesnesinin hala canlı olup olmadığını anlamak için IsAlive değişkeninin olduğunu
  • Oyun nesnesinin Texture‘unu almak için Texture nesnesinin olduğunu
  • Oyun nesnesinin ekrandaki yerini almak için Position değişkeninin olduğunu
  • Oyun nesnesinin kendi etrafında dönüş miktarını almak için Center ve Rotation değişkenlerinin olduğunu
  • Oyun nesnesinin hızını almak için Velocity değişkeninin olduğunu
  • Oyun nesnesinin ekranda kapladığı alanı bulmak için TextureRectangle özelliğinin olduğunu görürüz.

GameObject sınıfının constructor‘ı sadece bir Texture2D nesnesi alıyor ve bu parametreyi kendi iç değişkenlerinin değerlerini hesaplamada kullanıyor.

Artık GameLoop sınıfımızın class seviyesinde, Top nesnesi için, GameObject sınıfından değişken oluşturabiliriz.

public GameObject Top;

LoadContent() methodunda bu değişkenin atamasını yapalım;

Top = new GameObject(Content.Load<Texture2D>("Top"));

Update() methodunda Top nesnemizin yerini hesaplayalım;

KeyboardState ks = Keyboard.GetState();

if (ks.IsKeyDown(Keys.Left))
	Top.Position.X -= 5;
if (ks.IsKeyDown(Keys.Right))
	Top.Position.X += 5;

Aynı method içerisinde Top nesnemizin dönüş miktarını da hesaplayabiliriz; (Yukarı ve Aşağı tuşlarına basıldıkça 10 derece dönmesini sağlayacağız)

if (ks.IsKeyDown(Keys.Up))
	Top.Rotation += MathHelper.ToRadians(10);
if (ks.IsKeyDown(Keys.Down))
	Top.Rotation -= MathHelper.ToRadians(10);

MathHelper sınıfının static ToRadians fonksiyonu sayesinde, Top’umuzun dönme açısını radian cinsinden kolaylıkla hesaplayabiliyoruz.

Top‘umuzun dönme açısının -90 ile +90 dereceler arasında olduğundan ve ekrandan dışarı çıkmayıp, görünür olduğundan emin olalım;

if (Top.Position.X < 0)
	Top.Position.X = 0;
if (Top.Position.X > PENCERE_GENISLIK)
	Top.Position.X = PENCERE_GENISLIK;

if (Top.Rotation > MathHelper.ToRadians(90))
	Top.Rotation = MathHelper.ToRadians(90);
if (Top.Rotation < MathHelper.ToRadians(-90))
	Top.Rotation = MathHelper.ToRadians(-90);

Oyunun, Windows'ta başka bir pencereye geçilmesi esnasında (focus kaybetme) bekleme moduna geçmesini, tekrar oyuna dönüldüğünde ise (focus alma), kaldığı yerden devam etmesini sağlayalım;

Öncelikle sınıf seviyesindeki değişkenlerimize,

bool OYUN_DEVAM_EDIYOR = true;

ekliyoruz. Microsoft.Xna.Framework.Game sınıfından türetildiği için GameLoop sınıfının OnActivated ve OnDeactivated method’larını override edebiliriz.

OnDeactivated method’unda, oyun penceresinin focus kaybettiğini, OnActivated method’unda ise, oyun penceresinin focus aldığını anlayabiliriz.

protected override void OnActivated(object sender, EventArgs args)
{
  OYUN_DEVAM_EDIYOR = true;
  ArkaplanSes.Resume();
  base.OnActivated(sender, args);
}

protected override void OnDeactivated(object sender, EventArgs args)
{
  OYUN_DEVAM_EDIYOR = false;
  ArkaplanSes.Pause();
  base.OnDeactivated(sender, args);
}

Şu ana kadar oyunumuza hiç düşman eklemedik. Bir sonraki yazımda düşmanları ekleyeceğiz.

Top’umuzla da düşmanları vurup, puan kazanıyor olacağız.