Swift5 ve SpriteKit ile Angry Birds Oyunu

Bir zamanlar ortalığı kasıp kavuran Angry Birds oyununun çok kısa bir sürede nasıl yapıldığını anlatmaya çalışacağım. Bu yazıyı uygulayanlar yazının sonunda iOS telefonlar için bir oyun ortaya çıkarmış olacaklar.

Bu yazıdaki uygulamayı ortaya çıkarmak için aşağıdaki gereksinimleri karşılamanız gerekmektedir.

  1. Bir MacOs işletim sistemi (En güncel versiyonu olmalı, şu an en güncel versiyon: 10.15.4 Catalina)
  2. Xcode IDE
  3. Apple üyeliği (Developer hesabı değil, sadece ücretsiz apple üyeliği)

Angry Birds Oyunu

Şekil-1: Xcode Başlangıç Ekranı

Xcode uygulamasını açınca karşımıza Şekil-1’deki karşılama ekranı çıkacaktır. Buradan 2. seçenek olan “Create a new Xcode project” seçeceğini seçiyoruz.

Şekil-2: Xcode Proje template yapılandırması

Yeni proje oluştur kısmını geçtikten sonra bizi “Choose a template for your new project:” ekranı karşılacaktır. Buradan Game seçeceğini seçip devam ediyoruz.

Şekil-3: Xcode Yeni Proje Yapılandırması

Yeni projemizin oyun temasında olacağını seçip ilerledikten sonra karşımıza Şekil-3’deki proje yapılandırma ekranı gelecektir. Android üzerinden uygulama ve oyun geliştiren arkadaşların aşina olabileceklerini düşündüğüm bu ekranda projemizi isimlendirip uygulama geliştirici bilgilerini giriyoruz.

Yeni oyun projesi oluşturup ilerlediğiniz zaman Şekil-3’de görüldüğü gibi yazılım dili Swift ve Oyun Teknolojisi SpriteKit seçili olarak gelmektedir.

Ekranda bize seçilebilir olarak sunulan diller:

  1. Swift
  2. Objective-C

Bunun yanında oyun teknolojileri olarak da bize sunulan teknolojiler:

  1. RealityKit
  2. SceneKit
  3. SpriteKit
  4. Metal

Yukarıdaki seçeneklerden bu oyun için gerekli olanlar seçili olarak gelenlerdir. Ancak 3 boyutlu oyunlar veya tüm yapay etkileşimleri kendinizin yazacağı bir oyun oluşturmak istersek oyun motorlarından SceneKit veya Metal opsiyonlarını seçebilirdik. Şu an için bunlar bizim için önemli değiller.

Şekil-4: Xcode Editor Oyun Projesi Teması

Proje yapılandırmamızı tamamladıktan sonra Xcode Şekil-4’deki gibi oyun teması ile açılacaktır. Proje Navigatöründe normal bir iOS uygulaması geliştirilen gördüğümüz ögelerden farklı ögeler görülmektedir. Bunlardan bizim için önemli olarak:

  1. GameScene.sks
  2. GameScene.swift
  3. GameViewController.swift

Ortaya çıkarmaya çalışacağımız Angry Birds 2D oyunu için bu dosyaları kullanacağız.

Şekil-5: Xcode GameScene.sks görüntüsü

Oyunun görsel kısmının toolbox kullanarak çek bırak yöntemiyle oluşturulduğu dosya Şekil-5’de de görüldüğü gibi GameScene.sks dosyasıdır. Bu uygulamayıda bu şekilde .ek bırak yöntemiyle oluşturacağız. Ancak bir oyun oluşturulurken bu şekilde sabit girdiler ile değil dinamik ve her cihada uyumlu şekilde kod ile oluşturulurlar. Biz bu uygulamayı çek bırak yöntemiyle oluşturacağız ancak isteyenler yazının sonunda bunu çok basit bir şekilde kod ile de oluşturabilecek seviyeye gelecektir.

Şekil-6: Attributes Inspector

Dikkat edilirse proje başlarken uygulama ekranımız dik bir şekilde görüntülenmektedir. Bizim ortaya çıkaracağımız oyun ise yatay oynanan bir oyun olacaktır. Bu sebeple bazı ayarlamalar yapmak gerekmektedir. İlk olarak GameScene.sks içersinde sağ kısımda Şekil-6’da görülen Attributes Inspector kısmında boyutları yatay olarak ayarlanmalıdır. Bunu yaparken size kısmına gelip telefon modeli seçip ölçüleri girebiliriz.

Uygulamamız için iPhone 6s Plus yatay(LandScape) ölçülerini kullacağız.

Ölçüleri yatay olarak girmemiz yeterli olmayacaktır. Sadece yatay görüntü almak için Proje Navigatörü kısmından Info.plist dosyasını açıp Şekil-7’de de görünen Supported Interface Orientations ve Supported Interface Orientations(iPad) kısımlarındaki Portrait seçeneklerini siliyoruz.

Şekil-7: Xcode Info.plist

Ekranın yatay ayarlarını yaptıktan sonra GameScene.sks son hali Şekil-8’de görüldüğü gibi yatay bir şekil ayalacaktır.

Şekil-8: Xcode GameScene.sks LandScape

Xcode üzerinden yeni oyun projesi açıldığı zaman bir başlangıç tasarımı ve kod bloğu vermektedir. Tasarım kısmı Şekil-8’de görüldüğü gibi “Hello, World” yazısı yer alan bir ekrandır. Kod kısmi ise Şekil-9’da görüldüğü gibidir.

Şekil-9: Xcode GameScene.swift

Yeni projede otomatik olarak gelen bu kodlar ekranda bir takım aksiyonlar oluşturmaktadır. Ancak bu kodlar bizim işimize yaramadığı için fonksiyonların hepsini siliyoruz.

Projemizde bizim işimize yarayacak fonksiyonlar UIKit tarafından sağlanmaktadır. Örnek verecek olursak ekrana dokunma, dokunmayı sürdürme, dokunmayı bırakma gibi. Bu sebeple otomatik olarak oluşturalan kodları silerken bu fonksiyonları silmek yerine içlerini temizleyebilirsiniz.

Şekil-10: Xcode Temiz bir GameScene.swift

Otomatik oluşturalan kodların temizlenmesinden sonra Şekil-10’daki gibi temiz bir GameScene.swift dosyası elde etmiş olacağız.

import SpriteKit
import GameplayKit

class GameScene: SKScene {
        
    override func didMove(to view: SKView) {
        
    }
        
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
       
    }
    
    override func update(_ currentTime: TimeInterval) {
 
    }
}

Temizlenmiş GameScene.swift dosyası kodları yukarıda da verildiği gibi olacaktır. Bu kısımdan sonra tasarım ve kodlama olarak ihtiyaçlarımıza göre eklemeler yapacağız.

AngryBirds Oyununun Görsel İhtiyaçları

Oyunda kullacağımız bazı görsel ögelere ihtiyacımız var. Bunlar:

  1. Oyun arkaplanı
  2. Kuş
  3. Kutular
  4. Sapan veya Ağaç

Bu ögelere internetten kolayca erişebiliriz. Google Görsel aramalardan PNG formatında ve ücretsiz kullanım izni olan görselleri kullanabilir veya bu iş için oluşturulmuş web sitelerinden aramalar yapabiliriz.

Kullacağımız Görseller:

Bu görsel ögeleri projemize eklemek için sol kısımda yer alan Proje Navigatörü kısmından Assets.xcassets içerisine girip Şekil-11’deki gibi ögeleri finder üzerinden çek bırak ile bu kısma taşıyoruz. Burada ögelerin ismi çok önemlidir çünkü ögeleri isimlerine göre çağıracağız.

Şekil-11: Xcode Assets.xcassets

Ögeleri ekledikten sonra sırayla bu ögeleri projemizin görsel kısmına ekleyelim. Bunun için GameScene.sks içerisine gelip, sol üst kısımdaki Library (+) alanından Color Sprite objesi seçiyoruz. Şekil-12’de ilgili pencereyi görebilirsiniz.

Şekil-12: Xcode Library’de yeni obje ekleme

GameScene üzerinden eklediğimiz Color Sprite Şekil-13’de görüldüğü gibi eklenmektedir.

Şekil-13: Xcode Color Sprite Objesi

Color Sprite ekledikten sonra bunu arkaplan resmimize çevirip ekran boyutlarına getirmeliyiz. Arkaplan resmini seçmek için sağ kısımdaki Sprite özelliklerinden Texture kısmından arkaplan ögemizi seçebiliriz. Daha önce Şekil-11’deki gibi eklemiş olduğumuz görsel ögelerimiz bu kısımda seçilebilir durumda.

Şekil-14: Xcode Color Sprite Texture

Arkaplanı ekledikten sonra Şekil-14’deki son hali elde etmiş olacağız. Bu kısımdan sonra ağaç, kuş ve kutu ögelerini de yine aynı şekilde Color Sprite ögesi kullanarak ekranda istediğimiz yere konumlandıracağız.

Şekil-15: Angry Birds Oyunumuzun tasarımda son hali

Oyun tasarım kısmını bitirdik, burada son olarak bilmemiz gereken eklediğimiz Color Sprite objelerinin Sprite özelliklerinde name kısmında isimlendirmeyi unutmamak.

Şekil-16: Angry Birds Oyunu Test Görüntülemesi

Tasarımı yaptıktan sonra bir test yaptık ve eklediğimiz kuş ekranda görünmemekte.

Şekil-17: Sprite Z-Index Ayarlaması

Bunun sebebini web tasarım veya JS tabanlı oyun ile ilgilenen arkadaşlar tahmin etmiştir ki bunun sebebi kat sıralaması yani z-index’tir.

Kuş arkaplan ögesinden geride kaldığı için görünmemektedir. Bunu çözmek için arkaplan Color Sprite seçip z değeri -1 girilebilir.

Çözüm için yapılaması gereken Şekil-17’de gösterilmiştir.

Görüntüleme aşaması da geçildikten sonra sıra kodlama aşamasındadır.

    var zemin = SKSpriteNode()
    
    var kus = SKSpriteNode()
    
    var kutu1 = SKSpriteNode()
    var kutu2 = SKSpriteNode()
    var kutu3 = SKSpriteNode()
    var kutu4 = SKSpriteNode()
    var kutu5 = SKSpriteNode()
    var kutu6 = SKSpriteNode()
    var kutu7 = SKSpriteNode()
    var kutu8 = SKSpriteNode()
    
    var atisDurumu = false

Kuş ve kutuların tanımını yaptıktan sonra oluşmak için;

override func didMove(to view: SKView) {
    
        self.physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
        self.scene?.scaleMode = .aspectFit
        self.physicsWorld.contactDelegate = self

        zemin = childNode(withName: "zemin") as! SKSpriteNode
        zemin.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: zemin.size.width, height: zemin.size.height))
        zemin.physicsBody?.affectedByGravity = true
        zemin.physicsBody?.isDynamic = true
        zemin.physicsBody?.mass = 100

        kus = childNode(withName: "kus") as! SKSpriteNode
        kus.physicsBody = SKPhysicsBody(circleOfRadius: 22.5)
        kus.physicsBody?.affectedByGravity = false
        kus.physicsBody?.isDynamic = true
        kus.physicsBody?.mass = 0.15
        kus.physicsBody?.allowsRotation = false
        
        let size = CGSize(width: 45, height: 45)
        
        kutu1 = childNode(withName: "kutu1") as! SKSpriteNode
        kutu1.physicsBody = SKPhysicsBody(rectangleOf: size)
        kutu1.physicsBody?.isDynamic = true
        kutu1.physicsBody?.affectedByGravity = true
        kutu1.physicsBody?.allowsRotation = true
        kutu1.physicsBody?.mass=0.1
        
        kutu2 = childNode(withName: "kutu2") as! SKSpriteNode
        kutu2.physicsBody = SKPhysicsBody(rectangleOf: size)
        kutu2.physicsBody?.isDynamic = true
        kutu2.physicsBody?.affectedByGravity = true
        kutu2.physicsBody?.allowsRotation = true
        kutu2.physicsBody?.mass=0.1
        
        kutu3 = childNode(withName: "kutu3") as! SKSpriteNode
        kutu3.physicsBody = SKPhysicsBody(rectangleOf: size)
        kutu3.physicsBody?.isDynamic = true
        kutu3.physicsBody?.affectedByGravity = true
        kutu3.physicsBody?.allowsRotation = true
        kutu3.physicsBody?.mass=0.1
        
        kutu4 = childNode(withName: "kutu4") as! SKSpriteNode
        kutu4.physicsBody = SKPhysicsBody(rectangleOf: size)
        kutu4.physicsBody?.isDynamic = true
        kutu4.physicsBody?.affectedByGravity = true
        kutu4.physicsBody?.allowsRotation = true
        kutu4.physicsBody?.mass=0.1
 
        kutu5 = childNode(withName: "kutu5") as! SKSpriteNode
        kutu5.physicsBody = SKPhysicsBody(rectangleOf: size)
        kutu5.physicsBody?.isDynamic = true
        kutu5.physicsBody?.affectedByGravity = true
        kutu5.physicsBody?.allowsRotation = true
        kutu5.physicsBody?.mass=0.1
        
        kutu6 = childNode(withName: "kutu6") as! SKSpriteNode
        kutu6.physicsBody = SKPhysicsBody(rectangleOf: size)
        kutu6.physicsBody?.isDynamic = true
        kutu6.physicsBody?.affectedByGravity = true
        kutu6.physicsBody?.allowsRotation = true
        kutu6.physicsBody?.mass=0.1
        
        kutu7 = childNode(withName: "kutu7") as! SKSpriteNode
        kutu7.physicsBody = SKPhysicsBody(rectangleOf: size)
        kutu7.physicsBody?.isDynamic = true
        kutu7.physicsBody?.affectedByGravity = true
        kutu7.physicsBody?.allowsRotation = true
        kutu7.physicsBody?.mass=0.1
        
        kutu8 = childNode(withName: "kutu8") as! SKSpriteNode
        kutu8.physicsBody = SKPhysicsBody(rectangleOf: size)
        kutu8.physicsBody?.isDynamic = true
        kutu8.physicsBody?.affectedByGravity = true
        kutu8.physicsBody?.allowsRotation = true
        kutu8.physicsBody?.mass=0.1
    }

Kodlar ile kuş ve kutuyu oluşturduk. Yani görsel olarak çek bırak ile oluşturduğumuz ögeleri kod ile tanımladık.

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        kus.physicsBody?.affectedByGravity = true;
        atisDurumu = true
        
        if let touch = touches.first {
            let touchLocation = touch.location(in: self)
            let touchNodes = nodes(at: touchLocation)
            
            if touchNodes.isEmpty == false{
                for node in touchNodes{
                    if let sprite = node as? SKSpriteNode{
                        if sprite == kus{
                            kus.position = touchLocation
                        }
                    }
                }
            }
        }
    }

Ekrana dokunma başlayınca olan kodlarımız yukarıdaki şekilde,

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        if let touch = touches.first {
            let touchLocation = touch.location(in: self)
            let touchNodes = nodes(at: touchLocation)
            
            if touchNodes.isEmpty == false{
                for node in touchNodes{
                    if let sprite = node as? SKSpriteNode{
                        if sprite == kus{
                            kus.position = touchLocation
                        }
                    }
                }
            }
        }
    }

Dokunma ekran üzerinde devam ettikçe çalışacak kod yukarıdaki şekilde,

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
      if atisDurumu == false{
        if let touch = touches.first {
           let touchLocation = touch.location(in: self)
           let touchNodes = nodes(at: touchLocation)
           
           if touchNodes.isEmpty == false{
               for node in touchNodes{
                   if let sprite = node as? SKSpriteNode{
                       if sprite == kus{
                           let DX = -(touchLocation.x - -272)
                           let DY = -(touchLocation.y - -10)
                           
                        let impulse = CGVector(dx:DX*1.4, dy:DY*1.4)
                           
                           kus.physicsBody?.applyImpulse(impulse)
                           kus.physicsBody?.affectedByGravity = true
                            atisDurumu = true
                       }
                   }
               }
           }
       }
        }
    }

Ekrandan elimizi çektiğimizde çalışacak kodlar yukarıdaki şekilde,

override func update(_ currentTime: TimeInterval) {
        if let birdPhysicsBody = kus.physicsBody {
            if birdPhysicsBody.velocity.dx <= 0.1
                && birdPhysicsBody.velocity.dy <= 0.1
                && birdPhysicsBody.angularVelocity <= 0.1
                && atisDurumu == true{
                
                kus.physicsBody?.affectedByGravity = false
                kus.physicsBody?.velocity = CGVector(dx:0, dy:0)
                kus.physicsBody?.angularVelocity = 0
                kus.position = CGPoint(x: -272, y: -10)
                atisDurumu = false
            }
        }
 
    }

İşlem süresince bu kodlar çalışmaktadır, istediğimiz durumlar oluşunca istediğimiz işlemi yaptırabiliriz.

Kolay Gelsin 🙂

Comments

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir