Oyun sektörü gün geçtikçe büyüyor. İyileşen grafik kalitesi ve cihazlardaki donanım seviyesi günümüz insanlarını ekran başına kilitlemiş durumda. Artan bu yoğun ilgi Apple tarafından da görülmüş olacak ki hayatımıza SpriteKit girmiş oldu. iOS 7 ile kullanmaya başladığımız bu framework, 2D oyunlar yapmak için oldukça kullanışlı. Fakat SpriteKit kullanarak nasıl oyun yaparız? Gelin biraz yakından bakalım.
Projemizi Oluşturalım
SpriteKit kullanırken Xcode tarafından sunulan farklı oyun araçları mevcut. Bu yüzden proje oluşturma aşaması sırasında farklı bir adımdan ilerleyeceğiz. Xcode açıldıktan sonra her zamanki “Create a new Project (Yeni Proje Oluştur)” seçeneğine tıklıyoruz. Ardından açılan ekranda yandaki görselde de gösterildiği gibi “Game” temasını seçelim.
Temayı seçtikten sonra yanda bulunan görseldeki adım ile karşılaşacaksınız. Burada proje adını yazdıktan sonra “Game Technology” kısmının “SpriteKit” seçili olduğundan emin olalım. İşlem tamamlanınca “Next” ve ardından “Create” dedikten sonra artık projemiz oluşmuş oldu.
Oyun Görselleri Bulmak
Girişte de dediğimiz gibi bir oyun için en önemli şey grafiklerdir. Eğer tasarımdan anlıyorsanız bu sizin için büyük avantaj. Fakat tasarım yapmak istemeyenler için de birçok kaynak var. Bunlardan en iyisi opengameart . Bu site aracılığıyla birçok ücretsiz oyun karakteri, arka plan, müzik gibi araçlar bulabilirsiniz. Örnek olarak yapacağımız oyun bir kuş fırlatlama oyunu olacak. Fırlayan kuş ile birkaç tahta kutuyu devirip puan almaya çalışacağız. Bunun için kullanmak istediğimiz görseller şu şekilde;
Oyun içinde kullanacağımız görseller listedeki gibi olacak. Fakat siz farklı görsellerle ilerlemek isterseniz siteyi tekrar inceleyebilirsiniz. Listeyi kullanarak ilerlediğinizde açılan sayfalarda kırmızı ile çerçevelediğimiz zip dosyalarını bilgisayarınıza indirmelisiniz. İndirdiğimiz tahta kutu zip dosyasında kutuların birli, ikili ve üçlü hali mevcut. Biz burada tekli halini kulanacağız. Çünkü kuş çarptıkça hareket etmesini istiyoruz.
İnen dosyaları arşivden çıkarınca bize lazım olan görselleri bulabiliriz. Bu resimleri aldıktan sonra proje dosyalarımız içerisinde bulunan Assets klasörü içerisine yandaki şekilde sürükleyelim. Artık görseller oyunumuz için hazır.
Oyun Tasarımını Yapalım
Sıra geldi görselleri kullanmaya. Burada işlem yapmadan önce değinmemiz gereken önemli bir nokta var. Tasarımı isterseniz GameScene üzerinden isterseniz kod kullanarak oluşturabilirsiniz. Kodla oluşturmanın en büyük avantajı tabiki de sağladığı olağanüstü uyumluluk. Bu sayede oyunun çalışacağı birden fazla cihaz için ekran uyumluluğu sorunsuz olacaktır. Fakat bu işlem tasarım sürecini oldukça uzatacaktır. Şimdi biz GameScene üzerinden tasarımı oluşturalım. Tasarım sonunda kod ile oluşturmaya da bir örnek verelim.
A) GameScene ile Tasarım Oluşturmak
GameScene’ e biraz yakından bakalım. Çünkü bazı alışkın olmadığımız yapılar olduğunu görebilirsiniz. Öncelikle sayfa açıldığında karşınıza bir tasarım ekranı çıkıyor. Sağ tarafta Size, Anchor Point ve Gravity alanını görüyoruz. Biz oyunumuzu yatay eksende çalıştıracağımız için Size bölümündeki W(Width) ve H(Height) değerlerini tersine çevirelim. Böylece ekranımız yatay şekilde kullanılabilir. Anchor Point dediğimiz kısım ise aslında yaratacağımız dünyanın orta noktası oluyor. Bu nokta yandaki resimde gördüğünüz gibi kalabilir. Buna göre tasarımımızı yapacağız. Son olarak Gravity seçeneğine bakalım. Bu seçenek aslında yaratmak istediğiniz evrenin yer çekimi oluyor. Biz yapacağımız oyun için hazır değeri kullanacağız.
Şimdi tasarım için nesne ekleme tuşuna basalım. Burada “Color Sprite” seçeneğini göreceksiniz. Üzerine basılı tutup telefon ekranımıza sürükleyelim. Color Sprite‘ı ekrana göre konumlandırdıktan sonra sağ taraftaki menüde “Texture” seçeneğinin açıldığını göreceksiniz. Bu yapıyı bir arka plan olarak düşünebiliriz. Bizde görsellerimizi eklediğimiz Color Sprite‘lara bu şekilde vereceğiz. İlk görselimizi verdikten sonra aynı menüde Z ekseni ayarlarını göreceğiz. “Position” ayarı bizim için oldukça önemli. Çünkü bu ayar eklediğimiz Texture yapılarının ekranda hangi sıra ile görüneceğini belirlememize yarıyor. Yani bizim oyunumuzun arka plan resmi oyun içerisinde en arkada olmalı. Bu yüzden Z Position değerini -1 yapabiliriz. Arka plan resminden sonra gelecek olan diğer Texture yapıları 0, 1 şeklinde sıralanabilir.
Bizim yaptığımız tasarımın son hali yandaki şekilde. Eğer tasarımı değiştirmek isterseniz, yukarıda gösterildiği gibi kendi tasarımınızı yapabilirsiniz.
B) Kod Kullanarak Tasarım Oluşturmak
GameScene ile tasarım yapmak gördüğünüz gibi oldukça kolay. Fakat yaptığınız tasarım bazen simülatörde hiçte koyduğunuz yerde görünmeyebilir. İşte bu gibi sorunları ortadan kaldırmak için kodla tasarım yapmak her ne kadar zor olsa da dediğimiz gibi güvenli bir yöntemdir. Bunun nasıl yapıldığını görmek için isterseniz ekrana bir kuş daha koyalım. Bunun için öncelikle GameScene.swift dosyasında hazır verilen fonksiyonların içini temizleyelim. Ardından kuşumuzu tanımlamalıyız. Bunun için SKSpriteNode() sınıfından yararlanacağız. Bu aslında GameScene’de ColorSprite seçmek gibi düşünülebilir.
var bird = SKSpriteNode()
Şimdi sıra Texture oluşturmakta. Bunun için didMove fonksiyonu içerisinde SKTexture sınıfından bir değişken oluşturacağız. Birçok şekilde veri alabilen bu sınıfın imageNamed özelliğini kullanarak kuş resmimizin adı olan “bird” değerini verelim.
var bird = SKSpriteNode()
override func didMove(to view: SKView) {
var texture = SKTexture(imageNamed: "bird")
}
Sıra geldi oluşturduğumuz Texture’ı kuş değişkenimize atamakta. Bunun için yine SKSpriteNode sınıfını kullanacağız. Bu sınıfın bize bir texture sorduğu özelliğine oluşturduğumuz texture değişkenini verelim.
var bird = SKSpriteNode()
override func didMove(to view: SKView) {
var texture = SKTexture(imageNamed: "bird")
bird = SKSpriteNode(texture: texture)
}
Ardından kuşumuzun büyüklüğünü ve ekranda nerede duracağını belirleyelim. Büyüklüğü için size metodunu, pozisyonu için ise position metodunu kullanacağız. Büyüklüğünü CGSize ile belirlerken pozisyonunu CGPoint ile belirleyeceğiz. CGSize ile bize genişlik(width) ve yükseklik(height) sorulacak. Şimdilik ikisine de 100 değerini verelim. CGPoint ile de bizden bir x bir de y değeri istenecek. Örnek olması açısından ikisine de 0 yazabiliriz. Burada yapacağımız görüntü dediğimiz gibi bir test olacak. Bu yüzden pozisyon ve büyüklük değerlerini sabit oluşturuyoruz. Fakat siz daha hassas çalışmak isterseniz self.frame.width ve self.frame.height ile oyunun çalışacağı ekranın genişlik ve yükseklik değerlerini alıp, ona göre değerler verebilirsiniz.
var bird = SKSpriteNode()
override func didMove(to view: SKView) {
var texture = SKTexture(imageNamed: "bird")
bird = SKSpriteNode(texture: texture)
bird.size = CGSize(width: 100, height: 100)
bird.position = CGPoint(x: 0, y: 0)
}
Artık son aşamaya geçelim. Hatırlayacaksınız ki ekrana eklenen cisimlerin z eksenindeki pozisyonları oldukça önemli demiştik. Bu yüzden kuşumuzun z position değerini 1 olarak ayarlayabiliriz. Z position verdikten sonra kuşun ekranda görünmesi için addChild metodu ile kuşu ekrana yerleştirelim. Böylece kod ile kolayca bir kuş oluşturup ekrana yerleştirdik.
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var bird = SKSpriteNode()
override func didMove(to view: SKView) {
var texture = SKTexture(imageNamed: "bird")
bird = SKSpriteNode(texture: texture)
bird.size = CGSize(width: 100, height: 100)
bird.position = CGPoint(x: 0, y: 0)
bird.zPosition = 1
self.addChild(bird)
}
func touchDown(atPoint pos : CGPoint) {}
func touchMoved(toPoint pos : CGPoint) {}
func touchUp(atPoint pos : CGPoint) {}
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 touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {}
override func update(_ currentTime: TimeInterval) {}
}
Tasarımı Fiziksel Dünya ile Birleştirelim
Bir önceki başlıkta GameScene üzerinden yaptığımız tasarım ile devam edeceğimizi söylemiştik. Bu yüzden kod kullanarak yaptığımız tasarımı yorum satırı içine alıyoruz. Ardından geçelim en önemli soruya. Tasarımı yaptık ama biz bu görselleri nasıl kontrol edeceğiz? Bunun için tabi ki de tek çözüm kullandığımız görselleri koda aktarmak. Bunun için önceklikle görsellere id vermeliyiz. Burada şunu belirtmekte fayda var. Oyunumuzda sadece kuş ve kutular hareket edeceği için bunlara id verip koda aktarmak yeterli olacaktır. Arka plan ve ağaç zaten sabit.
Görsellerimize id verebilmek için GameScene ekranına dönelim. Burada görsellerin üzerinde tıklayınca sağda çıkan Sprite sekmesinde bulunan “Name” kısmına vermek istediğimiz ismi yazalım. Yazdığımız bu isimler artık bizim için id oldu.
Şimdi GameScene.swift dosyasına geri dönelim. Artık görselleri tanımlama vakti. Bunun için öncelikle kodla tasarım oluşturma kısmında da yaptığımız gibi görselleri SKSpriteNode() olarak tanımlayalım.
var bird = SKSpriteNode()
var brick1 = SKSpriteNode()
var brick2 = SKSpriteNode()
var brick3 = SKSpriteNode()
var brick4 = SKSpriteNode()
var brick5 = SKSpriteNode()
Ardından childNode sınıfını kullanalım. Bu bize bir withName özelliği soruyor. GameScene ekranında belirlediğimiz id isimlerini verelim.
bird = childNode(withName: "bird") as! SKSpriteNode
brick1 = childNode(withName: "brick1") as! SKSpriteNode
brick2 = childNode(withName: "brick2") as! SKSpriteNode
brick3 = childNode(withName: "brick3") as! SKSpriteNode
brick4 = childNode(withName: "brick4") as! SKSpriteNode
brick5 = childNode(withName: "brick5") as! SKSpriteNode
Kuş ve kutuların fiziksel özelliklerine geçmeden önce bu görsellere ait Texture yapılarını tanımlayalım. Bu sayede vereceğimiz fiziksel boyutlar daha güvenli olacaktır.
let birdTexture = SKTexture(imageNamed: "bird")
let brickTexture = SKTexture(imageNamed: "brick")
Texture değişkenlerimizi de oluşturduğumuza göre artık geçelim fiziksel özelliklere. Bunun için SKPhysicsBody() sınıfını kullanacağız. Kuş görseli yuvarlak, tahta kutularımız ise dörtgen bir biçimde. Bu yüzden kuşun fiziksel özelliği için circleOfRadius, kutular için ise rectangleOf özelliğini kullanacağız. Kullanacağız ama şimdi aklınıza şu takılmış olabilir. Ya biz bunu yapıyoruzda bu ne ki? Yapacağımız bu işlem aslında kuş ve kutuların etrafına görünmez bir alan oluşturuyor. Bu sayede cisimlerin hareketini ve çarpışmasını algılayabiliyoruz.
bird.physicsBody = SKPhysicsBody(circleOfRadius: birdTexture.size().width / 22)
brick1.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: brickTexture.size().width / 8, height: brickTexture.size().height / 8))
brick2.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: brickTexture.size().width / 8, height: brickTexture.size().height / 8))
brick3.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: brickTexture.size().width / 8, height: brickTexture.size().height / 8))
brick4.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: brickTexture.size().width / 8, height: brickTexture.size().height / 8))
brick5.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: brickTexture.size().width / 8, height: brickTexture.size().height / 8))
Sıra geldi diğer fiziksel özellikleri belirlemeye. Burada kullanacağımız metotlar bize yine physicsBody metodu ile geliyor. Bu metottan sonra .mass dersek görsele bir kütle vermiş oluyoruz. Ardından .affectedByGravity ile görselin yer çekiminden etkilenip etkilenmeyeceğini belirliyoruz. Bundan sonra .isDynamic ile görsellerin simülasyonlardan ve hareketlerden etkileneceğini belirtiyoruz. Son olarak sadece kutular için .allowsRotation özelliğini de aktif ederek kutuların hareket esnasında dönebileceğini söylüyoruz.
bird.physicsBody?.mass = 0.15
brick1.physicsBody?.mass = 0.4
brick2.physicsBody?.mass = 0.4
brick3.physicsBody?.mass = 0.4
brick4.physicsBody?.mass = 0.4
brick5.physicsBody?.mass = 0.4
bird.physicsBody?.affectedByGravity = false
brick1.physicsBody?.affectedByGravity = true
brick2.physicsBody?.affectedByGravity = true
brick3.physicsBody?.affectedByGravity = true
brick4.physicsBody?.affectedByGravity = true
brick5.physicsBody?.affectedByGravity = true
bird.physicsBody?.isDynamic = true
brick1.physicsBody?.isDynamic = true
brick2.physicsBody?.isDynamic = true
brick3.physicsBody?.isDynamic = true
brick4.physicsBody?.isDynamic = true
brick5.physicsBody?.isDynamic = true
brick1.physicsBody?.allowsRotation = true
brick2.physicsBody?.allowsRotation = true
brick3.physicsBody?.allowsRotation = true
brick4.physicsBody?.allowsRotation = true
brick5.physicsBody?.allowsRotation = true
Son olarak kuşun ve kutuların ekran çerçevesi içinde kalması ve ekranı biraz daha düzgün görebilmek için bir kod ekleyelim. Ekran sınırlarına çerçeve çizmek için yine physicsBody metodundan yararlanacağız. Ekran görüntü tipini değiştirmek içinde scene.scaleMode bloğundan faydalanacağız.
self.physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
self.scene?.scaleMode = .aspectFit
Oyun Fonksiyonlarını Yazmak
Artık oyunumuza fonksiyonellik kazandırma vakti. Amacımız oyuncu kuşu önce tutsun, sonra çeksin ve bıraktığında çektiği vektörün tersi yönünde bir güç uygulansın. Uygulanan güç ile fırlayan kuş kutulara çarpacak ve oyuncu puan kazanacak. Kuş çarptıktan sonra hızı 0’a inince oyun yeniden başlayacak. Şimdi bu işlemleri adım adım yapalım.
A) Kuşu Taşımak
Bunun için öncelikle oyunun başlayıp başlamadığını bize söyleyen bir boolean değişkeni tanımlayalım.
var gameStarted = false
Ardından bize hazır verilen touchesBegan ve touchesMoved fonksiyonlarını kullanacağız. İki fonksiyon içine de aynı işlemini yazacağız. Bu yüzden touchesBegan fonksiyonundan başlayalım. Öncelikle oyunun başlayıp başlamadığını bir if bloğu ile kontrol edelim. Ardından touches.first metodunu kullanarak bir if let bloğu içerisinde touch değişkeni oluşturalım. Bu değişken bize ekranda ilk dokunulan noktayı verecek. Bloğun içerisine girdikten sonra dokunulan ilk nokta olan touch değişkeninin lokasyonunu almalıyız. Bunun için touch.location metodunu kullanıyoruz. Bu metot bize bir View soruyor. Buna self diyerek bulunduğumuz ekranı veriyoruz.
if gameStarted == false {
if let touch = touches.first {
let touchLocation = touch.location(in: self)
}
Şimdi oyunda kullanıcının kutuları bu şekilde hareket ettirmemesi lazım. Bu yüzden kullanıcının taşımak istediği görselin kuş olduğundan emin olmalıyız. Kısaca aldığımız lokasyonda ki node‘ları kontrol etmeliyiz. Bunu nodes ile yapalım ve dokunulan lokasyonu içerisine verip touchNode değişkenine atayalım. Değişkenin üzerine tıklarsanız bir dizi olduğunu göreceksiniz. Bu yüzden bir if bloğu kullanarak isEmpty metodu ile boş olup olmadığını kontrol edelim.
if gameStarted == false {
if let touch = touches.first {
let touchLocation = touch.location(in: self)
let touchNode = nodes(at: touchLocation)
if touchNode.isEmpty == false {
}
}
}
Daha sonra dizinin içerisindeki bütün node’lara erişmek için bir for döngüsü başlatalım. Eriştiğimiz node’ları bir if let bloğu ile SKSpriteNode olarak cast edip sprite değişkenine atayalım. Bu sayede artık hem sprite değişkeni hem de kuşumuz bir SKSpriteNode. Bu yüzden bir if bloğu ile bunların birbirine eşit olup olmadığını kontrol edelim. Eğer eşitse kuşumuzun yeni lokasyonu ilk dokunulan noktanın lokasyonuna eşit olsun diyelim. Başta da dediğimiz gibi işlemin geçerli olması için aynı yöntemin touchesMoved fonksiyonuna da atanması gerekir.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if gameStarted == false {
if let touch = touches.first {
let touchLocation = touch.location(in: self)
let touchNode = nodes(at: touchLocation)
if touchNode.isEmpty == false {
for node in touchNode {
if let sprite = node as? SKSpriteNode {
if sprite == bird {
bird.position = touchLocation
}
}
}
}
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if gameStarted == false {
if let touch = touches.first {
let touchLocation = touch.location(in: self)
let touchNode = nodes(at: touchLocation)
if touchNode.isEmpty == false {
for node in touchNode {
if let sprite = node as? SKSpriteNode {
if sprite == bird {
bird.position = touchLocation
}
}
}
}
}
}
}
B) Kuşu Fırlatmak
Geldik kuşu fırlatma aşamasına. Burada oyuncu kuşu oyun açıldığındaki ilk konumundan tutup istediği yere taşıyacak. Bıraktığında ise kuş fırlayacak. Fakat fırlama sırasındaki verilecek güç kullanıcının ne kadar çektiğine bağlı olacak. Yani ilk konumdan fazla çekerse daha hızlı, daha az çekerse daha yavaş hareket sağlanacak. Verilecek gücün hesaplanması için kuşun ilk konumundan son konumunu çıkarmalı ve bunu itme gücü olarak kullanmalıyız. İlk konumu almak için öncelikle bir değişken oluşturalım. Ardından kuşun fiziksel özelliklerini verdiğimiz aşamada bu değişkene kuşun ilk konumunu atayalım.
var firstLocation : CGPoint?
override func didMove(to view: SKView) {
firstLocation = bird.position
}
Oyuncu kuşu çekip bıraktığında yani dokunma bittiğinde kuşun fırlamasını istiyoruz. Bu yüzden bize hazır verilen touchesEnded fonksiyonuna gelelim. Bir önceki fonksiyonlarda yaptığımız işlemi buraya kopyalayalım. Fakat burada kuşa yeni pozisyon vermeyeceğimiz için son aşamaya ihtiyacımız olmayacak.
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if gameStarted == false {
if let touch = touches.first {
let touchLocation = touch.location(in: self)
let touchNode = nodes(at: touchLocation)
if touchNode.isEmpty == false {
for node in touchNode {
if let sprite = node as? SKSpriteNode {
if sprite == bird {
}
}
}
}
}
}
}
Dediğimiz gibi ilk konumdan son konumu çıkarıp aradaki farkı almalıyız. Bunun için x ve y eksenleri arasındaki farkı ayrı ayrı alıp ters etki oluşturmak için -1 ile çarpalım. Ardından bir impulse değişkeni oluşturup CGVector yardımıyla alınan x ve y değerlerini verelim.
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if gameStarted == false {
if let touch = touches.first {
let touchLocation = touch.location(in: self)
let touchNode = nodes(at: touchLocation)
if touchNode.isEmpty == false {
for node in touchNode {
if let sprite = node as? SKSpriteNode {
if sprite == bird {
let dx = -(touchLocation.x - firstLocation!.x)
let dy = -(touchLocation.y - firstLocation!.y)
let impulse = CGVector(dx: dx, dy: dy)
}
}
}
}
}
}
}
Şimdi ise physicsBody tarafından bize sunulan applyImpulse metoduna oluşturduğumuz impulse değişkenini verelim. Ayrıca oyun başladığı için kuş yerçekiminden etkilenmeli ve oyunun başladığını kontrol eden boolean true olmalı.
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if gameStarted == false {
if let touch = touches.first {
let touchLocation = touch.location(in: self)
let touchNode = nodes(at: touchLocation)
if touchNode.isEmpty == false {
for node in touchNode {
if let sprite = node as? SKSpriteNode {
if sprite == bird {
let dx = -(touchLocation.x - firstLocation!.x)
let dy = -(touchLocation.y - firstLocation!.y)
let impulse = CGVector(dx: dx, dy: dy)
bird.physicsBody?.applyImpulse(impulse)
bird.physicsBody?.affectedByGravity = true
self.gameStarted = true
}
}
}
}
}
}
}
C) Oyunu Başa Almak
Farkettiyseniz şuan kuşu fırlattıktan sonra kuş sürekli yuvarlanıyor. Yani oyun başa dönmüyor. Kuş belli bir hızın altına düştüğünde başa dönmesini istesek bunu nasıl yapardık? Bunun için bize hazır olarak sunulan update fonksiyonunu kullanacağız. Bir if bloğu ile oyunun başlayıp başlamadığını ayrıca kuşun x, y ve açısal hızlarını kontrol edelim.
override func update(_ currentTime: TimeInterval) {
if (bird.physicsBody?.velocity.dx)! <= 0.1 && (bird.physicsBody?.velocity.dy)! <= 0.1 && bird.physicsBody!.angularVelocity <= 0.1 && gameStarted == true {
}
}
Şimdi ise oyunu başa alacak kodları yazalım. Bunun için öncelikle kuşu ilk pozisyonuna alıyoruz. Açısal ve koordinat düzleminde bütün hızının 0 olacağını söyledikten sonra yer çekiminden de etkilenmeyeceğini belirtiyoruz. Son olarak z eksenindeki pozisyonunu ve oyunun başladığını kontrol eden boolean değişkenini false haline getiriyoruz.
override func update(_ currentTime: TimeInterval) {
if (bird.physicsBody?.velocity.dx)! <= 0.1 && (bird.physicsBody?.velocity.dy)! <= 0.1 && bird.physicsBody!.angularVelocity <= 0.1 && gameStarted == true {
bird.position = firstLocation!
bird.physicsBody?.affectedByGravity = false
bird.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
bird.physicsBody?.angularVelocity = 0
bird.zPosition = 1
gameStarted = false
}
}
D) Skor Takibi Yapmak
Kuşumuzu kutulara çarptırıyoruz fakat bunun henüz bir anlamı yok. Çünkü bir oyun için en önemli şey skor yapmaktır. Bunu yapabilmek için ilk başta kutuların ve kuşun birbiri ile olan çarpışmasını algılamalıyız. Bu yüzden kullanmamız gereken birkaç metot var. Fakat bunları kullanmadan önce, fonksiyonlar için gerekli bir enum yapısı oluşturalım. Bu sayede hangi iki cismin birbiri ile çarpışacağını belirleyeceğiz.
enum ColliderType : UInt32 {
case Bird = 1
case Box = 2
}
Burada şunu söylemekte fayda var. Sizin yapacağınız oyunda bir çarpışan daha varsa bunun numarasını 3 değilde 4 olarak vermelisiniz. Çünkü kategorideki durumların toplamları birbirini vermemeli. Şimdi geçelim metotlara. Bunun için fiziksel özellikleri tanımladığımız yere gidelim. Kuşumuz için contactTestBitMask, categoryBitMask ve collisionBitMask değerlerini verelim. Yalnız collisionBitMask değerini verirken kutu ile çarpışacağı için kutu kategorisini vermeliyiz.
bird.physicsBody?.contactTestBitMask = ColliderType.Bird.rawValue
bird.physicsBody?.categoryBitMask = ColliderType.Bird.rawValue
bird.physicsBody?.collisionBitMask = ColliderType.Box.rawValue
Kutularımızın collisionBitMask değerine ise kuş kategorisini verelim.
brick1.physicsBody?.collisionBitMask = ColliderType.Bird.rawValue
brick2.physicsBody?.collisionBitMask = ColliderType.Bird.rawValue
brick3.physicsBody?.collisionBitMask = ColliderType.Bird.rawValue
brick4.physicsBody?.collisionBitMask = ColliderType.Bird.rawValue
brick5.physicsBody?.collisionBitMask = ColliderType.Bird.rawValue
Çarpışma algılamak için yapmamız gereken bir adım daha var. Buda kullanacağımız fonksiyonu bize getiren delegate metotlarını çağırmak. Bunun için öncelikle SKPhysicsContactDelegate sınıfını ekleyelim. Ardından physicsWorld.contactDelegate metodunu self olacak şekilde belirleyelim.
class GameScene: SKScene, SKPhysicsContactDelegate {
override func didMove(to view: SKView) {
self.physicsWorld.contactDelegate = self
}
}
Şimdi ise skorumuzu ekrana gösteren bir Label oluşturalım. Bunun için öncelikle SKLabelNode() sınıfından bir Label ve skor bilgisini tutan birer değişken oluşturalım. Ardından Label’ın font bilgilerini, x-y-z pozisyonunu ve text değerini verdikten sonra self.addChild() metodu ile ekrana ekleyelim.
var score = 0
var scoreLabel = SKLabelNode()
scoreLabel.fontName = "Helvetica"
scoreLabel.fontSize = 60
scoreLabel.text = "0"
scoreLabel.position = CGPoint(x: 0, y: self.frame.height / 4)
scoreLabel.zPosition = 2
self.addChild(scoreLabel)
Sıra geldi skoru artırmaya. Bunun için delegate fonksiyonlarından didBegin fonksiyonunu kullanacağız. Burada bize bir contact veriliyor. Bu değerin bodyA ve bodyB değerlerinin kuş kategorisine eşit olup olmadığını kontrol edelim. Eğer öyleyse skoru artırıp Label’a yazalım.
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.collisionBitMask == ColliderType.Bird.rawValue || contact.bodyB.collisionBitMask == ColliderType.Bird.rawValue {
score += 1
scoreLabel.text = String(score)
}
}
Oyun yeniden başladığında skorun sıfırlanması için update fonksiyonuna oyun bittiğinde score değişkeninin 0 olması gerektiğini söyleyelim.
override func update(_ currentTime: TimeInterval) {
if (bird.physicsBody?.velocity.dx)! <= 0.1 && (bird.physicsBody?.velocity.dy)! <= 0.1 && bird.physicsBody!.angularVelocity <= 0.1 && gameStarted == true {
bird.position = firstLocation!
bird.physicsBody?.affectedByGravity = false
bird.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
bird.physicsBody?.angularVelocity = 0
bird.zPosition = 1
score = 0
scoreLabel.text = String(score)
gameStarted = false
}
}
Final
Uzun bir yapım aşamasından sonra nihayet oyuna yakından bakabiliriz. Sizlerde burada öğrendiklerinizle kendi oyunlarınızı yapabilir, bir başka örnek daha görmek isterseniz buradan ulaşabilirsiniz.
import SpriteKit
import GameplayKit
class GameScene: SKScene, SKPhysicsContactDelegate {
var bird = SKSpriteNode()
var brick1 = SKSpriteNode()
var brick2 = SKSpriteNode()
var brick3 = SKSpriteNode()
var brick4 = SKSpriteNode()
var brick5 = SKSpriteNode()
var score = 0
var scoreLabel = SKLabelNode()
var gameStarted = false
var firstLocation : CGPoint?
enum ColliderType : UInt32 {
case Bird = 1
case Box = 2
}
override func didMove(to view: SKView) {
self.physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
self.scene?.scaleMode = .aspectFit
self.physicsWorld.contactDelegate = self
bird = childNode(withName: "bird") as! SKSpriteNode
brick1 = childNode(withName: "brick1") as! SKSpriteNode
brick2 = childNode(withName: "brick2") as! SKSpriteNode
brick3 = childNode(withName: "brick3") as! SKSpriteNode
brick4 = childNode(withName: "brick4") as! SKSpriteNode
brick5 = childNode(withName: "brick5") as! SKSpriteNode
let birdTexture = SKTexture(imageNamed: "bird")
let brickTexture = SKTexture(imageNamed: "brick")
bird.physicsBody = SKPhysicsBody(circleOfRadius: birdTexture.size().width / 22)
brick1.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: brickTexture.size().width / 8, height: brickTexture.size().height / 8))
brick2.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: brickTexture.size().width / 8, height: brickTexture.size().height / 8))
brick3.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: brickTexture.size().width / 8, height: brickTexture.size().height / 8))
brick4.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: brickTexture.size().width / 8, height: brickTexture.size().height / 8))
brick5.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: brickTexture.size().width / 8, height: brickTexture.size().height / 8))
firstLocation = bird.position
bird.physicsBody?.contactTestBitMask = ColliderType.Bird.rawValue
bird.physicsBody?.categoryBitMask = ColliderType.Bird.rawValue
bird.physicsBody?.collisionBitMask = ColliderType.Box.rawValue
brick1.physicsBody?.collisionBitMask = ColliderType.Bird.rawValue
brick2.physicsBody?.collisionBitMask = ColliderType.Bird.rawValue
brick3.physicsBody?.collisionBitMask = ColliderType.Bird.rawValue
brick4.physicsBody?.collisionBitMask = ColliderType.Bird.rawValue
brick5.physicsBody?.collisionBitMask = ColliderType.Bird.rawValue
bird.physicsBody?.mass = 0.15
brick1.physicsBody?.mass = 0.4
brick2.physicsBody?.mass = 0.4
brick3.physicsBody?.mass = 0.4
brick4.physicsBody?.mass = 0.4
brick5.physicsBody?.mass = 0.4
bird.physicsBody?.affectedByGravity = false
brick1.physicsBody?.affectedByGravity = true
brick2.physicsBody?.affectedByGravity = true
brick3.physicsBody?.affectedByGravity = true
brick4.physicsBody?.affectedByGravity = true
brick5.physicsBody?.affectedByGravity = true
bird.physicsBody?.isDynamic = true
brick1.physicsBody?.isDynamic = true
brick2.physicsBody?.isDynamic = true
brick3.physicsBody?.isDynamic = true
brick4.physicsBody?.isDynamic = true
brick5.physicsBody?.isDynamic = true
brick1.physicsBody?.allowsRotation = true
brick2.physicsBody?.allowsRotation = true
brick3.physicsBody?.allowsRotation = true
brick4.physicsBody?.allowsRotation = true
brick5.physicsBody?.allowsRotation = true
scoreLabel.fontName = "Helvetica"
scoreLabel.fontSize = 60
scoreLabel.text = "0"
scoreLabel.position = CGPoint(x: 0, y: self.frame.height / 4)
scoreLabel.zPosition = 2
self.addChild(scoreLabel)
}
func didBegin(_ contact: SKPhysicsContact) {
if contact.bodyA.collisionBitMask == ColliderType.Bird.rawValue || contact.bodyB.collisionBitMask == ColliderType.Bird.rawValue {
score += 1
scoreLabel.text = String(score)
}
}
func touchDown(atPoint pos : CGPoint) {
}
func touchMoved(toPoint pos : CGPoint) {
}
func touchUp(atPoint pos : CGPoint) {
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if gameStarted == false {
if let touch = touches.first {
let touchLocation = touch.location(in: self)
let touchNode = nodes(at: touchLocation)
if touchNode.isEmpty == false {
for node in touchNode {
if let sprite = node as? SKSpriteNode {
if sprite == bird {
bird.position = touchLocation
}
}
}
}
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if gameStarted == false {
if let touch = touches.first {
let touchLocation = touch.location(in: self)
let touchNode = nodes(at: touchLocation)
if touchNode.isEmpty == false {
for node in touchNode {
if let sprite = node as? SKSpriteNode {
if sprite == bird {
bird.position = touchLocation
}
}
}
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if gameStarted == false {
if let touch = touches.first {
let touchLocation = touch.location(in: self)
let touchNode = nodes(at: touchLocation)
if touchNode.isEmpty == false {
for node in touchNode {
if let sprite = node as? SKSpriteNode {
if sprite == bird {
let dx = -(touchLocation.x - firstLocation!.x)
let dy = -(touchLocation.y - firstLocation!.y)
let impulse = CGVector(dx: dx, dy: dy)
bird.physicsBody?.applyImpulse(impulse)
bird.physicsBody?.affectedByGravity = true
self.gameStarted = true
}
}
}
}
}
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func update(_ currentTime: TimeInterval) {
if (bird.physicsBody?.velocity.dx)! <= 0.1 && (bird.physicsBody?.velocity.dy)! <= 0.1 && bird.physicsBody!.angularVelocity <= 0.1 && gameStarted == true {
bird.position = firstLocation!
bird.physicsBody?.affectedByGravity = false
bird.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
bird.physicsBody?.angularVelocity = 0
bird.zPosition = 1
score = 0
scoreLabel.text = String(score)
gameStarted = false
}
}
}