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

Arşiv

Etiketlenen yazılar spriteeffects

Windows Phone 8 – XNA Oyunu / Savaşa Hayır

12 January 2014 1 yorum

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

Önce görseller;

Savaşa Hayır : BackgroundSavaşa Hayır : Drop 1 0Savaşa Hayır : Drop 1 1Savaşa Hayır : Drop 2 0Savaşa Hayır : Drop 2 1Savaşa Hayır : Plane 0Savaşa Hayır : Plane 1Savaşa Hayır : Plane 2Savaşa Hayır : Plane 3Savaşa Hayır : Plane 4Savaşa Hayır : Plane 5

İlk olarak XNA Game Studio 4.0 grubundaki Windows Phone Game şablonundan SavasaHayir isimli projeyi oluşturalım;

Windows Phone : XNA Game Project Template

Game1.cs dosyasının ismini GameLoop.cs olarak değiştirdikten sonra, Plane isminde yeni bir class ekleyelim;

public class Plane
{
	public Texture2D Texture;

	public int Location;

	public int Speed;
}

Plane sınıfı sayesinde, ekrana getireceğimiz uçakların telefon ekranındaki konumlarını, hızlarını ve görsellerini bileceğiz.

Drop isminde yeni bir class daha ekleyelim ve aşağıdaki kod parçası ile güncelleyelim;

public class Drop
{
	public bool IsBox;

	public bool IsParachute;

	public Vector2 Location;
}

GameLoop sınıfına geri dönelim ve sınıf seviyesindeki değişkenlere aşağıdakileri ekleyelim;

GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;

Random r = new Random();

Texture2D BackgroundTexture;

Texture2D BoxTexture;
Texture2D BoxParachuteTexture;

Texture2D HumanTexture;
Texture2D HumanParachuteTexture;

Texture2D[] PlaneTextures = new Texture2D[6];

List<Plane> PlaneList = new List<Plane>();

List<Drop> DropList = new List<Drop>();

TimeSpan LastPlaneDate = TimeSpan.Zero;

int PlaneCount;
int DropCount;
int HelpCount;

Yukarıdaki kodlar için daha önce yazmış olduğum Windows Phone ve XNA konusundaki diğer makalelerimi okumanızı öneririm.

GameLoop sınıfının constructor‘ında aşağıdaki atama işlerini yapalım;

public GameLoop()
{
	graphics = new GraphicsDeviceManager(this);
	Content.RootDirectory = "Content";

	TargetElapsedTime = TimeSpan.FromTicks(333333);

	InactiveSleepTime = TimeSpan.FromSeconds(1);

	graphics.PreferredBackBufferWidth = 480;
	graphics.PreferredBackBufferHeight = 800;

	graphics.IsFullScreen = true;
}

LoadContent method’unda Texture2D tipindeki değişkenlerimize değer atayalım;

spriteBatch = new SpriteBatch(GraphicsDevice);

BackgroundTexture = Content.Load<Texture2D>("Background");

for (int iLoop = 0; iLoop < 6; iLoop++)
{
	PlaneTextures[iLoop] = Content.Load<Texture2D>("Plane" + iLoop);
}

BoxTexture = Content.Load<Texture2D>("Drop1_0");
BoxParachuteTexture = Content.Load<Texture2D>("Drop1_1");

HumanTexture = Content.Load<Texture2D>("Drop2_0");
HumanParachuteTexture = Content.Load<Texture2D>("Drop2_1");

Update method'unda Plane ve Drop listesindeki elemanların yerlerini güncelliyoruz;

foreach (var plane in PlaneList)
{
	plane.Location += plane.Speed;
}

foreach (var drop in DropList)
{
	drop.Location.Y += 4;
}

Son uçak üretme zamanımızdan itibaren 5000ms (5sn) geçtiyse yeni uçak üretme kodunu ekliyoruz;

LastPlaneDate += gameTime.ElapsedGameTime;

if (LastPlaneDate > TimeSpan.FromMilliseconds(5000))
{
	var plane = new Plane();

	plane.Texture = PlaneTextures[r.Next(0, 6)];

	plane.Speed = r.Next(3, 8);

	PlaneList.Add(plane);

	PlaneCount++;

	LastPlaneDate = TimeSpan.Zero;
}

Windows Phone oyunlarında, oyuncunun ekrana dokunduğu noktaların listesini TouchPanel sınıfının static GetState methodundan dönen TouchCollection ile alabilmekteyiz.

TouchCollection koleksiyonunun her bir elemanı TouchLocation tipindedir, State özelliğinin TouchLocationState enum’ından Pressed değerinde olduğunu kontrol ederek, ilgili noktaya dokunulduğu durumu yakalayabiliriz.

Eğer dokunulan nokta bir Plane ile kesişiyorsa, rastgele yeni bir Drop düşürebiliriz, Drop ile kesişiyorsa paraşütünün açılmasını sağlayabiliriz;

foreach (var tl in TouchPanel.GetState())
{
	if (tl.State == TouchLocationState.Pressed)
	{
		var touchArea = new Rectangle((int)tl.Position.X, (int)tl.Position.Y, 1, 1);

		foreach (var plane in PlaneList)
		{
			var planeArea = new Rectangle(plane.Location, 20, plane.Texture.Width, plane.Texture.Height);

			if (planeArea.Intersects(touchArea))
			{
				var drop = new Drop();

				drop.Location = new Vector2(tl.Position.X, 20);

				drop.IsBox = (r.Next(0, 2) == 0);

				DropList.Add(drop);

				DropCount++;

				return;
			}
		}

		foreach (var drop in DropList)
		{
			var dropArea = new Rectangle((int)drop.Location.X, (int)drop.Location.Y, 100, 200);

			if (!drop.IsParachute && dropArea.Intersects(touchArea))
			{
				drop.IsParachute = true;

				HelpCount++;

				return;
			}
		}
	}
}

Son olarak Draw method’unda elimizdeki arkaplan görselini, ekrandaki uçakları, paraşütü açılmış ve açılmamış nesneleri ekrana çizdireceğiz;

GraphicsDevice.Clear(Color.CornflowerBlue);

spriteBatch.Begin();

spriteBatch.Draw(BackgroundTexture, Vector2.Zero, Color.White);

foreach (var plane in PlaneList)
{
	spriteBatch.Draw(plane.Texture, new Vector2(plane.Location, 20), Color.White);
}

foreach (var drop in DropList)
{
	if (drop.IsBox && drop.IsParachute)
	{
		spriteBatch.Draw(BoxParachuteTexture, drop.Location, Color.White);
	}
	else if (drop.IsBox && !drop.IsParachute)
	{
		spriteBatch.Draw(BoxTexture, drop.Location, Color.White);
	}
	else if (!drop.IsBox && drop.IsParachute)
	{
		spriteBatch.Draw(HumanParachuteTexture, drop.Location, Color.White);
	}
	else
	{
		spriteBatch.Draw(HumanTexture, drop.Location, Color.White);
	}
}

spriteBatch.End();

Oyun’dan bir ekran görüntüsü;

Savaşa Hayır : Screenshot

Windows Phone 8 – XNA Oyunu / PlaneHunter

18 February 2013 3 yorum

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

Her zamanki gibi önce görseller;

PlaneHunter : BackgroundPlaneHunter : CoinPlaneHunter : Target1PlaneHunter : Target2PlaneHunter : Target3PlaneHunter : Target4PlaneHunter : Target5PlaneHunter : Target6PlaneHunter : Target7

Bir tane de Sprite Font dosyamız var, HitCountFont.spritefont ismini verdiğim dosyanın, “yorum satırları kaldırılmış halini” aşağıdaki gibi düzenledim;

<?xml version="1.0" encoding="utf-8"?>
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
	<Asset Type="Graphics:FontDescription">
		<FontName>Segoe UI Mono</FontName>
		<Size>14</Size>
		<Spacing>0</Spacing>
		<UseKerning>true</UseKerning>
		<Style>Regular</Style>
		<CharacterRegions>
			<CharacterRegion>
				<Start> </Start>
				<End>~</End>
			</CharacterRegion>
		</CharacterRegions>
	</Asset>
</XnaContent>

İlk olarak XNA Game Studio 4.0 grubundaki Windows Phone Game şablonundan PlaneHunter isimli projeyi oluşturalım;

Windows Phone : XNA Game Project Template

Game1.cs dosyasının ismini GameLoop.cs olarak değiştirdikten sonra, Target isminde yeni bir class ekleyelim;

public class Target
{
	public Texture2D Texture;

	public Vector2 Position;

	public Vector2 Velocity;

	public Rectangle Area;

	public SpriteEffects Effect;

	public bool IsAlive;

	public Target(Texture2D texture)
	{
		this.Texture = texture;

		this.IsAlive = true;

		this.Area = new Rectangle(0, 0, texture.Width, texture.Height);
	}
}

Target sınıfı sayesinde, ekrana getireceğimiz hedef‘lerin ekranın neresinden çıkıp, hangi yöne doğru gideceğini, hedefin vurulup/vurulmadığını, çarpışma testi yapabilmek için ekranda kapladığı alanı ve hızını bileceğiz.

GameLoop sınıfına geri dönelim ve sınıf seviyesindeki değişkenlere aşağıdakileri ekleyelim;

private const int PENCERE_GENISLIK = 800;
private const int PENCERE_YUKSEKLIK = 480;

private Texture2D backgroundTexture;

private readonly Texture2D[] targetTextures = new Texture2D[7];

private Texture2D coinTexture;
private Rectangle coinPart;
private int coinPartIndex = 0;

private SpriteFont hitCountFont;
private int targetHitCount = 0;

private readonly List targetList = new List();

private TimeSpan lastTargetSpawnTime = TimeSpan.Zero;

private readonly Rectangle screenArea = new Rectangle(0, 0, PENCERE_GENISLIK, PENCERE_YUKSEKLIK);

private readonly Random r = new Random();

Yukarıdaki kodlar için daha önce yazmış olduğum Windows Phone ve XNA konusundaki diğer makalelerimi okumanızı öneririm.

GameLoop sınıfının constructor‘ında aşağıdaki atama işlerini yapalım;

graphics.PreferredBackBufferWidth = PENCERE_GENISLIK;
graphics.PreferredBackBufferHeight = PENCERE_YUKSEKLIK;
graphics.IsFullScreen = true;

graphics.SupportedOrientations = DisplayOrientation.LandscapeLeft | DisplayOrientation.LandscapeRight;

SupportedOrientations özelliğine DisplayOrientation enum’ından LandscapeLeft ve LandscapeRight değerlerini atayarak, oyun ekranının telefon dikey değil, yatay tutulduğunda düzgün görüntüleneceğini tanımlamış olduk.

LoadContent method’unda Texture2D tipindeki değişkenlerimize değer atayalım;

backgroundTexture = Content.Load<Texture2D>("PlaneHunterBackground");

coinTexture = Content.Load<Texture2D>("PlaneHunterCoin");
hitCountFont = Content.Load<SpriteFont>("PlaneHunterHitCountFont");

for (int i = 0; i < targetTextures.Length; i++)
{
	targetTextures[i] = Content.Load<Texture2D>("PlaneHunterTarget" + (i + 1));
}

Update method’unda son hedef üretilme zamanından itibaren yeterli süre geçmişse, yeni hedef oluşturmamız lazım;

lastTargetSpawnTime += gameTime.ElapsedGameTime;

if (lastTargetSpawnTime > TimeSpan.FromMilliseconds(1500))
{
	Target t = new Target(targetTextures[r.Next(0, 7)]);
	switch (r.Next(0, 4))
	{
		case 0:
		default:
			t.Position = new Vector2(r.Next(100, 300), 480);
			t.Velocity = new Vector2((float)r.NextDouble() * 8 + 2, (float)r.NextDouble() * -4 - 2);
			t.Effect = SpriteEffects.FlipHorizontally;
			break;
		case 1:
			t.Position = new Vector2(r.Next(500, 700), 480);
			t.Velocity = new Vector2((float)r.NextDouble() * -8 - 2, (float)r.NextDouble() * -4 - 2);
			t.Effect = SpriteEffects.None;
			break;
		case 2:
			t.Position = new Vector2(r.Next(100, 300), 0);
			t.Velocity = new Vector2((float)r.NextDouble() * 8 + 2, (float)r.NextDouble() * 4 + 2);
			t.Effect = SpriteEffects.FlipHorizontally;
			break;
		case 3:
			t.Position = new Vector2(r.Next(500, 700), 0);
			t.Velocity = new Vector2((float)r.NextDouble() * -8 - 2, (float)r.NextDouble() * 4 + 2);
			t.Effect = SpriteEffects.None;
			break;
	}

	targetList.Add(t);

	lastTargetSpawnTime = TimeSpan.Zero;
}

Yeni hedef ekleyeceğimiz zaman öncelikle hedef’i ekranın dört köşesinden rastgele birtanesine koyup, rastgele hız veriyoruz. Böylece hedefler ekranın rastgele bir yerinden çıkıp rastgele hızla rastgele bir yöne doğru hareket edecek.

Update method’unda o esnada ekranda olan hedeflerin yerlerini ve ekranda kapladıkları alanı güncelliyoruz;

foreach (Target hedef in targetList)
{
	hedef.Position += hedef.Velocity;

	hedef.Area.X = (int)hedef.Position.X;
	hedef.Area.Y = (int)hedef.Position.Y;
}

Windows Phone oyunlarında, oyuncunun ekrana dokunduğu noktaların listesini TouchPanel sınıfının static GetState methodundan dönen TouchCollection ile alabilmekteyiz.

TouchCollection koleksiyonunun her bir elemanı TouchLocation tipindedir, State özelliğinin TouchLocationState enum’ından Pressed değerinde olduğunu kontrol ederek, ilgili noktaya dokunulduğu durumu yakalayabilir, eğer bir hedef ile kesişiyorsa, hedefi öldürebiliriz;

TouchCollection tc = TouchPanel.GetState();
foreach (TouchLocation tl in tc)
{
	if (tl.State == TouchLocationState.Pressed)
	{
		Rectangle touchArea = new Rectangle((int)tl.Position.X, (int)tl.Position.Y, 1, 1);

		foreach (Target hedef in targetList)
		{
			if (hedef.Area.Intersects(touchArea))
			{
				hedef.Velocity = Vector2.Zero;

				hedef.IsAlive = false;

				targetHitCount++;
			}
		}
	}
}

Update method’unda, ekran sınırları dışına çıkan hedefleri listeden çıkartmalıyız. Eğer ekran dışına çıkan hedefleri listeden temizlemezsek, liste zamanla çok büyüyecek, telefonun kısıtlı olan hafızasını dolduracak ve oyunun önce yavaşlamasına sonra kapanmasına yol açabilecektir.

foreach (Target hedef in targetList)
{
	if (!hedef.Area.Intersects(screenArea))
	{
		targetList.Remove(hedef);

		break;
	}
}

Draw method’unda elimizdeki arkaplan görselini, hayatta olan hedefleri, hayatta olmayan hedefler için kendi etrafında dönen altın görselini ve toplam kaç hedefin vurulduğunu gösteren skor‘u ekrana çizdireceğiz;

spriteBatch.Begin();

spriteBatch.Draw(backgroundTexture, Vector2.Zero, Color.White);

foreach (Target hedef in targetList)
{
	if (hedef.IsAlive)
	{
		spriteBatch.Draw(hedef.Texture, hedef.Position, null, Color.White, 0, Vector2.Zero, 1, hedef.Effect, 0);
	}
	else
	{
		spriteBatch.Draw(coinTexture, hedef.Position, coinPart, Color.White);
	}
}

spriteBatch.DrawString(hitCountFont, "Hit Count : " + targetHitCount, new Vector2(10, 10), Color.Black);
spriteBatch.DrawString(hitCountFont, "Hit Count : " + targetHitCount, new Vector2(9, 9), Color.White);

spriteBatch.End();

Oyunumuzdan bir kare;

Windows Phone - XNA Oyunu : PlaneHunter

Oyunun kodlarını buradan indirebilirsiniz.

XNA Oyunu / Vahşi Batı

25 June 2012 3 yorum

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

Her zamanki gibi önce görseller;

XNA Oyun - Vahşi Batı - Cursor XNA Oyun - Vahşi Batı - Tombstone XNA Oyun - Vahşi Batı - Horse XNA Oyun - Vahşi Batı - Background

Bir tane silah sesi dosyamız var;

Magnum Gunshot

Bir tane de Sprite Font dosyamız var;

SkorFont.spritefont ismini verdiğim dosyanın, “yorum satırları kaldırılmış halini” aşağıdaki gibi düzenledim;

<?xml version="1.0" encoding="utf-8"?>
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
	<Asset Type="Graphics:FontDescription">
		<FontName>Segoe UI Mono</FontName>
		<Size>18</Size>
		<Spacing>0</Spacing>
		<UseKerning>true</UseKerning>
		<Style>Regular</Style>
		<CharacterRegions>
			<CharacterRegion>
				<Start>&#32;</Start>
				<End>&#126;</End>
			</CharacterRegion>
		</CharacterRegions>
	</Asset>
</XnaContent>

Başlayalım oyunumuzu yazmaya; VahsiBati projemizi oluşturduktan ve Game1.cs‘in ismini GameLoop.cs olarak değiştirdikten sonra, Target isminde bir sınıf oluşturalım;

public class Target
{
	public const int FrameWidth = 104;

	public Vector2 Position;

	private Rectangle _Area = new Rectangle(0, 0, FrameWidth, FrameWidth);
	public Rectangle Area
	{
		get
		{
			_Area.X = (int)Position.X;
			_Area.Y = (int)Position.Y;

			return _Area;
		}
	}

	public bool ToLeft;

	public float RunSpeed;

	public int FrameIndex;

	public SpriteEffects SpriteEffect;

	public TimeSpan LastFrameChange = TimeSpan.Zero;

	public TimeSpan FrameChangeBuffer = TimeSpan.Zero;

	public bool IsDead = false;
}

Target sınıfı sayesinde, ekrana getireceğimiz hedef‘lerin ekranın neresinden çıkıp, hangi yöne doğru gideceğini bileceğiz, hedefin vurulup/vurulmadığını bileceğiz, çarpışma testi yapabilmek için ekranda kapladığı alanı bileceğiz, hızını bileceğiz, ayrıca hedef‘imizin harekete sahip olmasını sağlayabileceğiz (bknz; XNA ile Karakter Hareketi)

GameLoop.cs dosyasına geri dönelim, ilk önce arkaplanımızın hareketli olması için (bknz; XNA ile Hareketli Arkaplan) sınıf seviyesinde şu değişkenleri tanımlayalım;

Rectangle Screen;

Texture2D Background1;
Texture2D Background2;

Rectangle Background1Position;
Rectangle Background2Position;

GameLoop constructor‘ında Screen değişkenine;

Screen = new Rectangle(0, 0, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight);

LoadContent method’unda Background değişkenlerine değer atayalım;

Background1 = Content.Load<Texture2D>("Background");
Background2 = Content.Load<Texture2D>("Background");

Background1Position = new Rectangle(0, 0, Background1.Width, Background1.Height);
Background2Position = new Rectangle(Background1.Width, 0, Background2.Width, Background2.Height);

Update method’unda Background‘un yerini güncelleyelim;

Background1Position.X -= 1;
Background2Position.X -= 1;

if (Background1Position.X < -Background1.Width)
{
	Background1Position.X = Background2Position.X + Background2.Width;
}

if (Background2Position.X < -Background2.Width)
{
	Background2Position.X = Background1Position.X + Background1.Width;
}

Son olarak Draw method'unda Background'u ekrana çizelim;

spriteBatch.Begin();

spriteBatch.Draw(Background1, Background1Position, Color.White);
spriteBatch.Draw(Background2, Background2Position, Color.White);

spriteBatch.End();

Ekrana işaretçi çizdirmek için, sınıf seviyesinde aşağıdaki değişkenleri ekleyelim;

Texture2D Cursor;

Vector2 CursorPosition = Vector2.Zero;
Vector2 CursorCenter = Vector2.Zero;

Rectangle CursorZone;

MouseState ms;
MouseState pms;

GameLoop constructor‘ında CursorZone değişkenini dolduralım;

CursorZone = new Rectangle(0, 0, 5, 5);

LoadContent method‘unda Cursor değişkenlerine değer atayalım;

Cursor = Content.Load<Texture2D>("Cursor");
CursorCenter = new Vector2(Cursor.Width / 2, Cursor.Height / 2);

Update method‘unda işaretçinin ekranda görüntüleneceği yeri hesaplayalım;

ms = Mouse.GetState();

CursorPosition = new Vector2(ms.X, ms.Y);

pms = ms;

Draw method’unda Cursor nesnemizi ekrana çizdirelim;

spriteBatch.Draw(Cursor, CursorPosition, null, Color.White, 0f, CursorCenter, 0.6f, SpriteEffects.None, 0f);

Sınıf seviyesinde aşağıdaki değişkenleri tanımlayalım;

Texture2D Horse;

Texture2D Tombstone;

SpriteFont SkorFont;
Vector2 SkorPosition;

SoundEffect Gunshot;

Random r;

int Score = 0;

Böylece hedef‘in görselini, vurulduğunda dönüşeceği mezarlık görselini, ateş etme sırasında duyulacak silah sesini, kaç hedef vurduğumuzu tutabileceğimiz değişkenlerimiz tanımlamış olduk.

Constructor‘da r değişkenine değer ataması yapalım;

r = new Random();

LoadContent method’unda diğer değişkenlerimize değer atayalım;

Horse = Content.Load<Texture2D>("Horse");
Tombstone = Content.Load<Texture2D>("Tombstone");

SkorFont = Content.Load<SpriteFont>("SkorFont");
SkorPosition = new Vector2(20, 10);

Gunshot = Content.Load<SoundEffect>("Magnum");

Henüz ekranda hedefleri göremesek‘te oyunumuzun önemli kısımlarının hazırlıklarını tamamladık. Artık hedef listesini tanımlayabilir, Update ve Draw method’larında kullanabiliriz. Sınıf seviyesinde aşağıdaki değişkenleri tanımlayalım;

TimeSpan LastHorseSpawn = TimeSpan.Zero;
TimeSpan SpawnHorseBuffer = TimeSpan.FromSeconds(3);

List<Target> TargetList = new List<Target>();

Update method’unda son hedef üretilme zamanından itibaren yeterli süre geçmişse, yeni hedef oluşturmamız lazım;

if (gameTime.TotalGameTime - LastHorseSpawn > SpawnHorseBuffer)
{
	Target t = new Target();
	t.FrameIndex = 0;
	t.ToLeft = r.Next(0, 2) == 0;
	t.RunSpeed = Convert.ToSingle(r.Next(20, 70)) / 10;
	t.FrameChangeBuffer = TimeSpan.FromMilliseconds(100 + -t.RunSpeed);

	if (t.ToLeft)
	{
		t.Position = new Vector2(graphics.PreferredBackBufferWidth - t.Area.Width, r.Next(200, 400));
		t.SpriteEffect = SpriteEffects.FlipHorizontally;
	}
	else
	{
		t.Position = new Vector2(0, r.Next(200, 400));
		t.SpriteEffect = SpriteEffects.None;
	}

	TargetList.Add(t);

	LastHorseSpawn = gameTime.TotalGameTime;
}

Hedef’in ekranın solundan sağına/sağından soluna gideceğine rastgele karar veriyoruz (ToLeft alanı sayesinde)

Hedef’in ekranda koşma hızına rastgele karar veriyoruz (RunSpeed alanı sayesinde)

Update method’unda hedef listesindeki hedeflerin hala ekranda göründüğünü kontrol ediyoruz, eğer ekrandan çıkmışlarsa hedef listesinden de çıkartıyoruz. Ekranda kalan hedefleri, koşma hızları kadar koşturuyoruz;

foreach (var t in TargetList)
{
	if (!Screen.Intersects(t.Area))
	{
		TargetList.Remove(t);
		break;
	}

	if (gameTime.TotalGameTime - t.LastFrameChange > t.FrameChangeBuffer)
	{
		t.FrameIndex += 1;
		t.LastFrameChange = gameTime.TotalGameTime;
	}

	if (t.FrameIndex > 3)
	{
		t.FrameIndex = 0;
	}

	if (t.ToLeft)
	{
		t.Position.X -= t.RunSpeed;
	}
	else
	{
		t.Position.X += t.RunSpeed;
	}
}

Update method’unda ayrıca, mouse‘un sol tuşuna basıldığında ateş edilmesini sağlamamız gerekiyor. Öncelikle sol tuşa ilk basıldığı anı buluyoruz, silah sesi çıkartıyoruz, hedef listesindeki hedefleri kontrol ediyoruz. Eğer vurulan hedef varsa, mezarlık görseline döndürüyoruz, Skor‘umuzu 1 arttırıyoruz;

if (ms.LeftButton == ButtonState.Pressed && pms.LeftButton == ButtonState.Released)
{
	Gunshot.Play();

	CursorZone.X = ms.X - 2;
	CursorZone.Y = ms.Y - 2;

	foreach (var t in TargetList)
	{
		if (!t.IsDead && t.Area.Intersects(CursorZone))
		{
			Score++;
			t.Position += new Vector2(20, 20);
			t.IsDead = true;
			t.ToLeft = true;
			t.RunSpeed = 1;
			break;
		}
	}
}

Son olarak Draw method’unda, hala hedef listesinde olan hedefleri ve skorumuzu ekrana çizdirmemiz gerekiyor;

foreach (var t in TargetList)
{
	if (t.IsDead)
	{
		spriteBatch.Draw(Tombstone, t.Position, Color.White);
	}
	else
	{
		spriteBatch.Draw(Horse, t.Position, new Rectangle(t.FrameIndex * Target.FrameWidth, 0, Target.FrameWidth, Target.FrameWidth), Color.White, 0f, Vector2.Zero, Vector2.One, t.SpriteEffect, 0);
	}
}

spriteBatch.DrawString(SkorFont, "Skor : " + Score, SkorPosition + Vector2.One, Color.Black);
spriteBatch.DrawString(SkorFont, "Skor : " + Score, SkorPosition, Color.White);

İşte Vahşi Batı oyunundan bir ekran görüntüsü. Oyunun kaynak kodlarını buradan indirebilirsiniz.

XNA Oyun : Vahşi Batı

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.