SpriteKit: Fiziksel Kurallar

SpriteKit kütüphanesi ile yer çekimi, itme, geri sıçrama ve yoğunluk gibi kavramları fiziksel kurallar başlığı altında öğrenin.

Erdem Özgür
TurkishKit
11 min readNov 5, 2018

--

Oyunlarda fiziksel olaylar ile sıkça karşılaşırız fakat nasıl kodlandıklarını ve nasıl çalıştıklarını merak etmeyiz. Dürüst olmak gerekirse çoğu insan gibi ben de oyunlarda bu hareketleri oyun geliştirmeye başlamadan önce merak etmezdim. Sadece oyunumu oynardım. Fakat oyunlarda kullanılan hareketler, örneğin Flappy Bird, yer çekiminden etkilenir ve ekrana her tıkladığımızda yukarı yönlü hız, “velocity” kazanarak hareketine devam eder.

Bu yazıda iOS oyunlarındaki fizik dünyasının derinliklerine ineceğiz ve oyunlarda yer çekimi, nesnelere itme uygulayarak veya fizik gövdeleri tanımlayarak nesnelerin geri sıçramalarını göreceğiz. Belki bir sonraki yazıda Flappy Bird oyununu kodlarız. 😁

İlk olarak playground ekranı oluşturacağız. Playground’ın ne olduğu hakkında soru işaretleriniz var ise Berkin Ceylan’ın “Playground Ekranını Tanıyalım” adlı yazısını okumanızı öneririm.

Playground Oluşturma

1- XCode’u çalıştıralım ve Get started with a playground’ a tıklayalım,

2- Blank’e tıklayarak devam edelim,

3- Proje ismimize SpriteKitPhysicsTest ismini verelim ve Next’e tıklayarak projemizi oluşturalım.

Buraya kadar yaptığımız işlemde boş bir playground projesi oluşturduk. Şimdi ise SpriteKit Playground’ını oluşturalım ve

kütüphanelerini projemize ekleyelim.

PlaygroundSupport modülü bizlere SpriteKit sahnemizi Playground ekranında görsellemeye yarayacak. Bu kütüphane, oyun alanı sayfasının durumunu göstermeye ve Xcode ile etkileşime geçmenizi sağlayan bir nesne. View > Assistant Editor > Show Assistant Editor kısmı, ekranımızın canlı görüntüsü göstermeye yardımcı olacak.

Şimdi ise aşağıdaki kodları projemize ekleyelim.

1- SKView, Sprite Kit içeriğini görüntülemeye yardımcı bir nesnedir. İlk olarak SKView görüntümüzü oluşturduk ve frame boyutunu 480x320 yaptık.(1.1). Sonra, SKScene sahnemizi oluşturduk ve aynı boyutları verdik. Sahnemiz 480-x noktasına ve 320-y noktasına sahip oldu.(1.2). SKScene, sahnenin görüntülenmek üzere canlandırdığı ve oluşturduğu içeriği sağlar. Bir sahneyi görüntülemek için, onu bir SKView nesnesinden sunarız. presentScene ile oluşturduğumuz scene’i, view’de sunmasını istedik.

2- PlaygroundPage; nesnelerin canlı bir görünümünü yönetmenizi ve oyun alanının yürütülmesini kontrol etmenizi sağlar.

(2.1) İlk ve en önemlisi, kodumuzun en üstten işlenmeye başlandığında en alt kodda yürütmenin durdurulması. Fakat .needsIndefiniteExecution değişkeni, Xcode’a oyun kodunuzu kaynak koddan geçtiği anda yürütmesini iptal etmemesini söyler. Bir oyun prototipinde kod içindekilerin sürekli olarak çalışmasını isteriz. Böylelikle ekranımızdaki değişiklikleri canlı olarak devamlı şekilde görebiliriz. Varsayılan değeri false’tur.

(2.2)Asistan editör, canlı görünümün mevcut durumunu render eder. Kaydedilen görüntüyü geri sarma, ileri sarma ve kayma yapabilmeniz için zaman içindeki görüntüyü kaydeder. Bunu ise anlık olarak gösterir.

Canlı görüntüyü görüntülemek için .needsIndefiniteExecution öğesinin true değerine ayarlanmasını gereklidir. LiveView nil olmayan bir değere ayarlandığında, sistem .needsIndefiniteExecution öğesini true olarak ayarlar.

480x320 boyutundaki ekranımız oluştu ve artık anlık olarak asistan üzerinden görüntüleyebiliyoruz.

Boş bir playground ile oynamak eğlencesizdir, öyleyse şimdi içerisine birkaç Sprite ekleyelim.

Dosyalar linkinden bu projemizde lazım olacak Sprite’ları indirelim.

Ve View > Navigators > Show Project Navigator’dan Resources altına indirdiğimiz dosyaları sürükleyelim.

SpriteKitPhysicsTest’e geri dönelim ve aşağıdaki kodları yazalım:

Sprite’lar, sahnedeki içeriğin çoğunu oluşturmak için kullanılan temel yapı taşlarıdır. Kare, daire ve üçgen adında 3 adet SKSpriteNode oluşturduk ve eşleşen görüntüleri onlara atadık. SKSpriteNode(imageNamed: ), bir görüntü dosyası kullanarak sprite node oluşturmak için kullanılır.

.name ile onlara belirleyici birer isim verdik.

.position ile Sprite’larımızın ekranda nerede olacaklarını belirledik. Kareyi x:120, y: 160 noktasına, daireyi x: 240, y: 160 noktasına ve üçgeni x: 360, y: 120 noktasına koyduk, ancak yine de onları ekranda görebilmemiz için sahnemize eklemeniz gerekiyor, bu yüzden aşağıdaki kodları yazalım:

.addChild(SKNode), sahnedeki çocuk düğümleri listesinin sonuna bir düğüm daha ekler. Sprite’larımızı ekranımızda görmek istiyorsak, düğüm ekleme işlemini yapmalıyız.

Fizik Bedenleri

Fizik bedenlerini, görüntülerimizi kaplayan bir sınır olarak düşünebilirsiniz.

Kodunu en alta ekleyelim.

Sprite’lara fizik gövdesini .physicsBody ile ekleriz. Fizik simülasyonu eklemek için SKNode nesnesinin physicsBody özelliğine bir SKPhysicsBody nesnesi atarız.

SKPhysicsBody(circleOfRadius: ), dairesel bir gövde oluşturur. Eğer yarıçapı, daire genişliğinin yarısı olarak verirsek, oluşturduğumuz gövde tam olarak görüntümüz ile eşleşir.

Oyun alanı kodunuzu yeniden çalıştırdıktan sonra, yer çekimi ile daire düşüşünü görürsünüz, çok hızlı şekilde ekrandan kaybolur.

Bir node’a fizik gövdesi eklersek, gravity (yer çekimi) default olarak ekranımıza eklenir. Dairemizin sahneden kaybolmasını istemiyorsak, gravity’i değiştirmeliyiz.

kodunu, presentScene(_:)’den sonra yazalım. Sahnenin, oyununuzun temel fizik kurulumunu temsil eden .physicsWorld adlı bir özelliği vardır. Fizik dünyanızın yerçekimi vektörünü değiştirdiğinizde, sahnenizdeki her fizik bedenine uygulanan sabit ivmeyi her çerçevede değiştirirsiniz.

Yerçekimini sıfır vektörüne sıfırlamak için kodu yazdığımızda, şimdi dairenin aşağı düşmeden başlangıç konumunda kaldığını göreceksiniz.

Edge(Kenar) Gövdesi Oluşturmak

Objelerimizin ekrandan çıkmasını çoğu zaman istemeyiz. Bu yüzden sahne frame’imize fizik gövdesi eklemeliyiz. .presentScene:()’ den önce alttaki kodu ekleyelim.

İlk olarak, sahnenin kendisi için fizik bedenini belirleyelim. Hatırlayalım, herhangi bir SpriteKit düğümüne fizik bedeni verebiliyorduk ve bir sahne de bir düğümdür. Bu yüzden sahnenin fizik bedenini belirlerken farklı bir gövde türü yarattık; bir daire yerine edge kenar döngüsü. Circle loop ile edge loop arasında önemli bir fark vardır:

  • Daire gövdesi (circle loop), dinamik bir fizik gövdesidir, yani hareket eder. Katıdır, kütlesi vardır ve başka herhangi bir fizik bedeniyle çarpışabilir. Fizik simülasyonu, hacim temelli organları hareket ettirmek için çeşitli güçler uygulayabilir.
  • Kenar döngü gövdesi (edge loop), statik hacimsiz bir fizik gövdesidir, yani hareket etmemektedir. Adından da anlaşılacağı gibi, bir kenar döngü sadece bir şeklin kenarlarını tanımlar. Bir kütleye sahip değildir, diğer kenar döngü bedenleriyle çarpışamaz. Diğer nesneler kenarlarının içinde veya dışında olabilir.

Bir kenar döngü gövdesi için en yaygın kullanım, çarpışma alanlarını tanımlamaktır.

Gravity’i tekrardan -9.8 değerini vererek kodumuzu çalıştıralım.

Boyutumuzun sınırlarında static bir fizik gövdesi oluşturmuş olduk, böylelikle dairemizi ekranımızdan çıkamadı.

Dikdörtgensel Gövdeler

Aşağıdaki satırı kodumuza ekleyerek kare hareketli grafiğinin fizik gövdesini ekleyelim:

Dikdörtgen bir fizik gövdesi oluşturmanın, dairesel bir beden oluşturmaya çok benzediğini görebilirsiniz. Tek fark; dairenin yarıçapından geçmek yerine, dikdörtgenin genişliğini ve yüksekliğini temsil eden bir CGSize vermenizdir.

Özel Şekilli Gövdeler

Şu anda ekranda fizik gövdesine sahip iki basit şeklimiz var — bir daire ve bir kare. Daha karmaşık bir şekle ihtiyamız varsa… Örneğin, yerleşik bir üçgen şekli SpriteKit’te yok.
SpriteKit’e, gövdenin sınırlarını tanımlayan bir Çekirdek Grafik yolu vererek, rasgele şekilli yapılar oluşturabilirsiniz. Bunun nasıl çalıştığını anlamanın en kolay yolu bir örneğe bakmaktır; bu yüzden üçgen şekliyle devam edelim.

Bu kodları adım adım ele alalım:
1. İlk olarak, üçgenin noktalarını çizmek için kullanacağınız yeni bir CGMutablePath oluşturduk.
2. Ardından, sanal “kalemimizi” üçgenin ilk noktasına taşıyoruz. Koordinatların, varsayılan olarak merkezin anchor point’ine göre olduğunu unutmayın. Anchor Point(0.5,0.5), bu sabitleme noktası şeklimizin merkezini (0,0) yapar. (0,0) üçgenin merkezidir.
3. Daha sonra, üçgenin üç köşesinin her biri için addLine (to :) çağırarak üç çizgi çiziyoruz. Sırayla Sanal Kalem - 1.nokta, 1.nokta - 2.nokta ve 2.nokta - 3.nokta arası çizgileri verdik.
4. Son olarak, üçgenin yolunu SKPhysicsBody’ye (polygonFrom :) geçirerek gövdeyi yaratırsınız.

Özel şekilli fizik gövdeleri oluşturmak diğerlerine göre daha zor gözükebilir, fakat bu işlem için çok kolay bir yolumuz var. Birazdan o yolu öğreneceğiz.

Böylelikle üçgenimiz artık özel şekilli gövdeye sahip oldu. Şimdi ekranımıza “sand” ekleyeceğiz. Ekranımızda rastgele x noktasında çıkmasını istediğimiz için rastgele sayı üretmemiz lazım. Bunun için alttaki fonksiyonu kullanacağız.

Bu kod klasik random sayı üretme kodudur, detaylı bilgiyi internetten “how to generate a random number on swift” şeklinde aratarak bulabilirsiniz.

Şimdi ise sand fonksiyonunu yazalım;

Bu fonksiyonda, daha önce yaptığınız gibi, sand.png olarak adlandırılan dokudan küçük bir dairesel fizik gövdesi yaptık ve sand.position’un x kısmına 0 ile sahnemizin genişliği kadar (480) arasında sayı üretmesini istedik, böylelikle ekranımızda sand’ler rastgele oluşturulacak . Ayrıca, hareketli grafiğin adını daha sonra kolayca erişebilmek için .name ile “sand” verdik.

Ne olduğunu görmek için bu kum parçacıklarından 100 tane ekleme zamanı.

Kod bir dizi eylemi 100 kez tekrar eder. Tekrarlanan dizi, spawnSand’ı çağırır ve sonra 0.1 saniye bekler.
Sahne Asistan editöründe görüntülemeye başladığında, 100 kum parçacıklarının yağdığı ve yerdeki üç cisim arasındaki boşlukları doldurduğu için “kum fırtınası” görürsünüz:

Karmaşık Şekilleri Olan Fizik Bedenleri

Oluşturulacak karmaşık bir fizik gövde şekline sahipseniz ve kod tarafından yolunu oluşturacaksanız bu iş uzun ve hantal bir hâl alabilir.

Özel yollardan nasıl beden oluşturulacağını bilmek yararlı olsa da, karmaşık şekilleri ele almanın çok daha kolay bir yolu vardır.
Başlamadan önce, sahneye eklemeniz gereken bir şekil daha var ve biraz döndürülmüş büyük bir “L” gibi görünüyor:

Üçgen bir yol tanımlamak için yazdığınız kodu göz önünde bulundurarak, muhtemelen yukarıdaki şeklin kodla bir araya getirilmesinin acı verici olacağını fark etmişsinizdir.
Çok daha kolay bir yolumuzun olduğunun ipucunu yukarıda verdim. Bu yüzden kaygılanmanıza gerek yok. 😁

SKPhysicsBody başlatıcısı (texture: size :), Sprite şeklini otomatik olarak algılayan modeldir. SKTexture ve CGSize parametrelerini alır.
Yukarıdaki örnekte, bu hareketli grafiğin fizik gövdesini oluşturmak için hareketli grafiğin texture’ını kullanırsınız.
SKPhysicsBody (texture: size :) boyut parametresini ayarlayarak oluşturulan gövdenin boyutunu da kontrol edebilirsiniz.

Şimdi L şeklinin otomatik olarak taslağını izleyen bir fizik bedenine sahip olduğunu göreceksiniz.

SpriteKit fizik motoru çok kullanışlı bir özellik sunar: Fiziklerin canlı sahnelere hata ayıklamasını sağlayan bir API. Nesnelerinizin ana hatlarını, aralarındaki eklemleri, oluşturduğunuz fizik kısıtlarını ve daha fazlasını görebilirsiniz. Kodunuzdaki “fps”gösterimini sağlayan sceneView.showsFPS = true değerini bulun ve bu satırı altına ekleyin:

Sahne yeniden oluşturmaya başlar başlamaz, tüm bedenlerin şekillerini görüyoruz. Bu özellik sayesinde, fizik kurulumunuzun bazı ciddi hata ayıklamalarını yapabilirsiniz.

Fizik Gövdelerinin Özellikleri

Çarpışma tespitinde birçok fizik özelliği vardır. Bir gövdenin özelliklerinin oyun fiziğini nasıl etkilediğini görmek için kumun özelliklerini düzenleyelim. Şu anda, kum çok ağır, çok granüllü bir kayaya benziyor. Parçalar yumuşak, elastik kauçuktan yapılmışsa ne olur?

spawnSand():’ın içinde sona ekleyelim.

Restitution(geri sıçrama veya iade) özelliği, gövdenin başka bir gövdeye değdiği zaman ne kadar enerji kaybettiğini belirlemek için kullanılır.
Değerler, gövdenin hiç sıçramadığı 0.0'dan, gövdenin çarpışmaya başladığı enerjiyle sıçradığı 1.0'a kadar değişebilir. Varsayılan değeri 0,2'dir.

SpriteKit, fiziksel özelliklerin tüm özelliklerini varsayılan olarak makul değerlere ayarlar. Bir nesnenin varsayılan ağırlığı, ekranda ne kadar büyük göründüğüne dayalıdır; restitüsyon ve sürtünme “slipperiness” varsayılan olarak, çoğu gündelik nesnelerin malzemelerine uyan değerlere göre ayarlanmıştır.

Bir şey daha: Geçerli restitüsyon değerleri 0 ile 1 arasında olmalıdır, ancak bu aralığın dışında değerler sağladığınızda derleyici size şikayette bulunmaz.

Ancak bu gerçekçi bir davranış değildir ve değerler kartopu etkisiyle büyüyeceğinden fizik simülasyonunuzu çabucak bozacaktır. Bu, gerçek bir uygulamada önerdiğim bir şey değildir ancak biraz eğlenmek istiyorsanız bunu da denemenizi tavsiye ederim. 😁

Şimdi parçacıkları daha yoğun hale getirelim, böylece diğer şekillerden daha ağır olacaklar.

spawnSand():’ın içinde sona ekleyelim.

Yoğunluk, birim hacim başına kütle olarak tanımlanır — başka bir deyişle, bir nesnenin yoğunluğu ne kadar yüksek olursa, boyutu o kadar ağır olacaktır. Yoğunluk varsayılan olarak 1,0'dır, bu yüzden bu kod ile kumun 20x kadar yoğun olmasını sağlarsınız.

Kırmızı parçacıklar yoğunluğu arttığında ağırlıkları artar ve daha büyük, ancak daha açık mavi şekilleri bir kenara itebilir. Fiziği kontrol ettiğinizde, boyut mutlaka önemli değildir!

Fizik gövdesindeki diğer özelliklerin kısa bir turu:
friction: Bu bir nesnenin “kaymasını” ayarlar. Değerler, gövdenin bir buz küpü gibi yüzeyler boyunca düzgün bir şekilde kaydığı 0.0'a kadar değişebilir, burada yüzeyler boyunca kayarken vücut hızla yavaşlar ve durur. Varsayılan değer 0,2'dir.
isDynamic: Bazen, çarpışma tespiti için fizik gövdelerini kullanmak istersiniz. False olarak ayarlarsanız fizik motoru, fizik gövdesindeki tüm kuvvetleri ve dürtüleri yok sayar ve node’u kendinizin hareket ettirmenize izin verir.
usesPreciseCollisionDetection: Varsayılan olarak, SpriteKit hassas çarpışma algılaması gerçekleştirmez. Bunun bir yan etkisi vardır: Bir nesne çok hızlı hareket ederse, bir mermi gibi, başka bir nesneden geçebilir. Çarpışma tespitini sağlamak için bu özelliği açmayı deneyin.

  • allowRotation: Fizik motoru ile istediğiniz hareketli bir texture’ın simüle edilmesini ama asla dönmemesini isteyebilirsiniz. Bu durumda, bu özelliği False olarak ayarlamanız yeterlidir.
    linearDamping ve angularDamping: Bu değerler, doğrusal hızın veya açısal hızın (rotasyon) zamanla ne kadar azaldığını etkiler. Hızın hemen düştüğü değerler, hızın asla düşmediği 0,0 değerine kadar değişebilir. Varsayılan değer 0.1'dir.
    affectedByGravity: Tüm nesneler varsayılan olarak yerçekiminden etkilenir, ancak bir nesneye bu özelliği false olarak ayarlayarak yer çekimi etkisini kapatabilirsiniz.
    resting: Fizik motoru, bir süre hareket etmeyen nesnelerin “dinlenme” olarak işaretlendiği bir optimizasyona sahiptir, böylece fizik motoru artık üzerinde hesaplama yapmak zorunda kalmaz. Bir dinlenme nesnesini elle “uyandırmak” gerekiyorsa, bu özelliği false olarak ayarlamanız yeterlidir.
    mass ve area: Bunlar, fizik kütlesinin şekline ve yoğunluğuna göre otomatik olarak hesaplanır. Ancak, kütleyi manuel olarak geçersiz kılmanız gerekiyorsa, yapabilirsiniz.

Impulse Eklemek

Şimdi sahnemizdeki nesneleri biraz titretelim.

Bu işlev, sahnedeki tüm düğümlerde enumerateChildNodes(withName: “sand”) sand ismini arar ve bulduğu anda her birine bir dürtü uygular. Sadece yukarı yönlü bir dürtü eklemek istediğimizden dolayı, x bileşenini sıfıra eşit ve y bileşenini ise 20 ile 40 arasında rastgele bir pozitif y bileşenine sahip olarak yukarı doğru bir itme uygularsınız.

Şimdi ise fonkisyonumuzu çalıştırmak için bu bloğu kullanalım.

İlk olarak DispatchQueue.main.asyncAfter(deadline: .now() + 5) {} ile sahnemiz yürütüldükten 5 saniye sonra alt kodları çalıştır deriz.

Alt kodlarda ise sahnemize shake fonksiyonunu 2 saniye aralıklar ile 100 defa run etmesi gerektiğini söyledik.

Kumlarımız artık her iki saniyede bir shake fonksiyonu sayesinde yukarı 20–40 arası bir değer ile etkilenirler.

Kum parçacıklarımız güzel görünüyor fakat kare, üçgen, çember ve serbest şekilli gövdemizin hareketi yok. Şimdi shape’ler üzerinde de aynı işlemi yapalım;

shake() fonksiyonu içine aşağıdaki kodları ekleyelim.

Bu işlev, sahnedeki tüm düğümlerde enumerateChildNodes(withName: “shape”) shape ismini arar ve bulduğu anda her birine bir dürtü uygular. x bileşenine ve y bileşenine 20 ile 60 arasında rastgele bir sayı verir ve impulse bu yöne doğru uygulanır.

Bu yazımızda, oyunlarda kullanılan fizik özelliklerinden bazılarını inceledik. Gelecek yazılarda görüşmek üzere 👋.

*Bu yazıda “Ray Wenderlich, 2D Apple Games by Tutorials: iOS, MacOS, WatchOS” kitabından yararlanılmıştır.

--

--