Bir önceki yazıda modeli yüklediğimize göre, artık çizmeye hazırız. Çizdirme işlemlerini Draw() fonksiyonu içinde yapacağız. Oyunlar görüntüdeki nesneleri saniyede onlarca kez yeniden çizer. Bu çizilen her bir kareye “frame” adı verilir ve hızlı frame çizilmesi bilgisayarın özellikleri ile doğrudan ilgilidir. Bir oyunun hızı saniyede çizilen kare sayısı “FPS” (frame per second) ile ölçülür. İnsan gözü 24 FPS ile görmektedir, daha hızlı olarak gösterilen kareleri ayırt edememektedir. Böylece kareleri ayrı kareler gibi değil, sanki kare içindeki nesnelerin hareket ettiği gibi algılar. Bu yüzden oyunumuzun 24 FPS’den hızlı çalıştığından emin olmalıyız. Filmler de aynı prensiple oluşturulmaktadır.
Ekrana bir nesne çizdirmeden önce ekranda olabilecek şeyleri sildirmemiz gerekir. Bu işlemi XNA’da bulunan GraphicsDevice.Clear() methodu yapar. Bu method ayrıca parametre olarak bir renk alıp, ekranı istenen renge boyama yeteneğine de sahiptir.
GraphicsDevice.Clear(Color.CornflowerBlue);
Bir modeli çizdirmek için yapmamız gereken ilk işlem modelin görünüm (view) ve projeksiyon (projection) matrislerini hesaplamktır. Bunu yapmak için Matrix sınıfının statik CreateLookAt() methodunu kullanacağız. Bu method parametre olarak sırasıyla kamera pozisyonu, hedef konumu ve kamera yönü değerleri almaktadır. Buradaki
Kamera Pozisyonu (Vector3) – kameranın 3 boyutlu uzaydaki yeri,
Hedef Konumu (Vector3) – kameranın baktığı hedef noktanın 3 boyutlu uzaydaki yeri,
Kameranın Yönü (Vector3) – kameraya göre “üst” kısmı veren değer.
Görünümü oluşturmak için Draw() methodu içinde ekranı temizlediğimiz GraphicsDevice.Clear() methodunun hemen altına bu görünüm (view) matrisini yerleştirebiliriz.
Matrix view = Matrix.CreateLookAt( new Vector3(150, 100, 200), new Vector3(0, 50, 0), Vector3.Up);
Projeksiyon matrisini oluşturmak için ise Matrix sınıfının CreatePerspectiveFieldOfView() methodundan faydalanacağız. Bu method sırasıyla gösterilecek alanın açısını (field of view) radyan olarak, en-boy oranını (aspect ratio) ve kameranın sınırlarını belirleyen yakın ve uzak düzlemleri ondalıklı (float) sayı olarak parametre olarak almaktadır. Bu değerlerin karşılık geldiği alan aşağıda gösterilmiştir. XNA yakın düzlem ile uzak düzlem arasında kalan ve verilen açı ile sınırlanmış alanı oyuncuya gösterecektir. Bu alana view frustum denir.
Yakın ve uzak düzlemler ne kadar büyük, bakış açısı (field of view) değeri ise ne kadar geniş bir alan çizileceğini gösterir. Bu alan dışında kalan objeler çizilmez. Çoğu “First Person Shooter” oyunlarda (örn: Counter-Strike) field of view değeri için 45 ila 60 derece arası bir değer seçilir. Değerin çok fazla büyütülmesi ekranın kenarlarda yamulup bükülmesine sebep olur. Balık gözü adı verilen lens 180 derecelik bakış açısına sahiptir. 45 derece insan gözüne oldukça yakın bir sonuç veren ve resmi eğmeyen bir değerdir. En-Boy oranı ise ekranın genişliğinin yüksekliğe bölünmesi ile bulunur. Bu değer GraphicsDevice sınıfı içinde zaten mevcuttur. Projeksiyon matrisi için gerekli kod şöyledir:
Matrix projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 10000.0f);
Bir modeli çizdirmek için hazırlamamız gereken son matris ise evren (world) matrisidir. Burada her modelin her yüzeyinin (mesh) modelin dönüşümlerinden ayrı olarak kendine has dönüşümleri olabileceğini hatırlamak gerekir (Örnek: bir insan modelinin kolunu ayrı hareket ettirmek vs.). Her mesh ayrıca kendine has bir efekte (effect) sahip olabilir. Bu detaylara daha sonra gireceğiz. Ancak şimdilik modelimizi çizdirmek için temel olarak modelimize XNA içinde bulunan basit efekt sınıfı olan BasicEffect sınıfını kullanacağız.
Sonuç olarak, Draw() methodu içinde World, View ve Projection matrislerini ve mesh’lerin efektlerini BasicEffect sınıfı ile oluşturup çizmemiz gerekir.
// Evren (World) Matrisini hesapla Matrix baseWorld = Matrix.CreateScale(0.4f) * Matrix.CreateRotationY(MathHelper.ToRadians(180)); foreach (ModelMesh mesh in model.Meshes) { // Her bir yüzeyin (mesh) world matrisini hesapla Matrix localWorld = modelTransforms[mesh.ParentBone.Index] * baseWorld; // Her mesh'in efektlerini ekle foreach (ModelMeshPart part in mesh.MeshParts) { BasicEffect e = (BasicEffect)part.Effect; // World, view, ve projection matrislerini efekte ekle e.World = localWorld; e.View = view; e.Projection = projection; e.EnableDefaultLighting(); } // Mesh'i çiz mesh.Draw(); }
Oyunun çizilmesini tamamlamak için gereken son kod XNA tarafından hali hazırda eklenmiş durumdadır. Mesh’lerin çizimi bittikten sonra çağırılması gereken bu kod oyunun çizilmesidir. Burada base ile nitelendirilen Game sınıfıdır.
base.Draw(gameTime);
Modeli çizmemiz için gereken bütün kodları baştan bir gözden geçirirsek, oyunumuzun Game1 sınıfı şu haldedir.
public class Game1 : Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; Model model; Matrix[] modelTransforms; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; graphics.PreferredBackBufferWidth = 1280; graphics.PreferredBackBufferHeight = 800; } protected override void Initialize() { base.Initialize(); } protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); model = Content.Load<Model>("Skyline"); modelTransforms = new Matrix[model.Bones.Count]; model.CopyAbsoluteBoneTransformsTo(modelTransforms); } protected override void UnloadContent() { } protected override void Update(GameTime gameTime) { // ESC'ye veya Joystick'den Geri tuşuna basarak oyundan çıkmak için if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState(PlayerIndex.One).IsKeyDown(Keys.Escape)) this.Exit(); base.Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); Matrix view = Matrix.CreateLookAt(new Vector3(150, 100, 200), new Vector3(0, 50, 0), Vector3.Up); Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45),GraphicsDevice.Viewport.AspectRatio,0.1f, 10000.0f); // Evren (World) Matrisini hesapla Matrix baseWorld = Matrix.CreateScale(0.4f) * Matrix.CreateRotationY(MathHelper.ToRadians(180)); foreach (ModelMesh mesh in model.Meshes) { // Her bir yüzeyin (mesh) world matrisini hesapla Matrix localWorld = modelTransforms[mesh.ParentBone.Index] * baseWorld; // Her mesh'in efektlerini ekle foreach (ModelMeshPart part in mesh.MeshParts) { BasicEffect e = (BasicEffect)part.Effect; // World, view, ve projection matrislerini efekte ekle e.World = localWorld; e.View = view; e.Projection = projection; e.EnableDefaultLighting(); } // Mesh'i çiz mesh.Draw(); } base.Draw(gameTime); } }
Ve işte uzun çalışmalar sonucu emeğimizin karşılığını aldık :)