Swift 3'te SpriteKit Oyunu Nasıl Oluşturulur (Bölüm 3)
Yayınlanan: 2022-03-10Bir SpriteKit oyunu oluşturmak için ne gerektiğini hiç merak ettiniz mi? Düğmeler olması gerekenden daha büyük bir görev gibi mi görünüyor? Bir oyunda ayarları nasıl sürdüreceğinizi hiç merak ettiniz mi? SpriteKit'in piyasaya sürülmesinden bu yana iOS'ta oyun yapmak hiç bu kadar kolay olmamıştı. Bu üç bölümlük serinin üçüncü bölümünde, RainCat oyunumuzu ve SpriteKit'e girişimizi tamamlayacağız.
Bir önceki dersi kaçırdıysanız, kodu GitHub'dan alarak devam edebilirsiniz. Bu öğreticinin Xcode 8 ve Swift 3 gerektirdiğini unutmayın.
SmashingMag'de Daha Fazla Okuma : Bağlantı
- Oyunlaştırma ve UX: Kullanıcıların Kazandığı veya Kaybettiği Yer
- Eğlenceli UX Tasarımı: Daha İyi Bir Oyun Oluşturma
- Kullanıcı Davranışını Değiştirmek için UX Tasarımını ve Psikolojisini Birleştirme
Bu, RainCat yolculuğumuzun üçüncü dersi. Bir önceki dersimizde basit animasyonlar, kedi davranışları, hızlı ses efektleri ve fon müziği ile geçen uzun bir gün geçirdik.
Bugün aşağıdakilere odaklanacağız:
- puanlama için teke tek ekran (HUD);
- ana menü — düğmeli;
- sesleri kapatma seçenekleri;
- oyundan çıkma seçeneği.
Daha Fazla Varlık
Son dersin varlıkları GitHub'da mevcuttur. Önceki derslerde yaptığımız gibi, görüntüleri tekrar Assets.xcassets
içine sürükleyin.
Dikkat et!
Skoru korumanın bir yoluna ihtiyacımız var. Bunu yapmak için bir head-up display (HUD) oluşturabiliriz. Bu oldukça basit olacak; puan ve oyundan çıkmak için bir düğme içeren bir SKNode
olacaktır. Şimdilik sadece skora odaklanacağız. Kullanacağımız font, Dafont.com'dan edinebileceğiniz Pixel Digivolve. Size ait olmayan görüntüleri veya sesleri kullanırken olduğu gibi, kullanmadan önce yazı tipinin lisansını okuyun. Bu, kişisel kullanım için ücretsiz olduğunu belirtiyor ancak yazı tipini gerçekten beğendiyseniz sayfadan yazara bağışta bulunabilirsiniz. Her şeyi her zaman kendiniz yapamazsınız, bu yüzden size bu yolda yardım edenlere geri vermek güzeldir.
Ardından, projeye özel yazı tipini eklememiz gerekiyor. Bu süreç ilk kez zor olabilir.
Yazı tipini indirin ve bir “Yazı Tipleri” klasörünün altındaki proje klasörüne taşıyın. Bunu önceki derslerde birkaç kez yaptık, bu yüzden bu süreci biraz daha hızlı geçeceğiz. Projeye Fonts
adlı bir grup ekleyin ve Pixel digivolve.otf
dosyasını ekleyin.
Şimdi zor kısım geliyor. Bu kısmı kaçırırsanız, muhtemelen yazı tipini kullanamayacaksınız. Info.plist
dosyamıza eklememiz gerekiyor. Bu dosya, Xcode'un sol bölmesindedir. Tıklayın ve özellik listesini (veya plist
) göreceksiniz. Listeye sağ tıklayın ve “Satır Ekle” ye tıklayın.
Yeni satır geldiğinde aşağıdakileri girin:
Fonts provided by application
Ardından Item 0
altına yazı tipimizin adını eklememiz gerekiyor. plist
aşağıdaki gibi görünmelidir:
Yazı tipi kullanıma hazır olmalıdır! Amaçlandığı gibi çalıştığından emin olmak için hızlı bir test yapmalıyız. GameScene.swift
ve sceneDidLoad
en üstüne aşağıdaki kodu ekleyin:
let label = SKLabelNode(fontNamed: "PixelDigivolve") label.text = "Hello World!" label.position = CGPoint(x: size.width / 2, y: size.height / 2) label.zPosition = 1000 addChild(label)
Çalışıyor mu?
Çalışırsa, her şeyi doğru yaptınız. Değilse, o zaman bir şeyler yanlıştır. Code With Chris, daha ayrıntılı bir sorun giderme kılavuzuna sahiptir, ancak bunun Swift'in daha eski bir sürümü için olduğunu unutmayın, bu nedenle Swift 3'e getirmek için küçük değişiklikler yapmanız gerekecek.
Artık özel yazı tiplerini yükleyebildiğimize göre, HUD'ımıza başlayabiliriz. “Merhaba Dünya” etiketini silin, çünkü onu yalnızca yazı tipimizin yüklendiğinden emin olmak için kullandık. HUD, HUD öğelerimiz için bir kapsayıcı gibi davranan bir SKNode
olacaktır. Bu, birinci derste arka plan düğümünü oluştururken izlediğimiz sürecin aynısıdır.
Her zamanki yöntemleri kullanarak HudNode.swift
dosyasını oluşturun ve aşağıdaki kodu girin:
import SpriteKit class HudNode : SKNode { private let scoreKey = "RAINCAT_HIGHSCORE" private let scoreNode = SKLabelNode(fontNamed: "PixelDigivolve") private(set) var score : Int = 0 private var highScore : Int = 0 private var showingHighScore = false /// Set up HUD here. public func setup(size: CGSize) { let defaults = UserDefaults.standard highScore = defaults.integer(forKey: scoreKey) scoreNode.text = "\(score)" scoreNode.fontSize = 70 scoreNode.position = CGPoint(x: size.width / 2, y: size.height - 100) scoreNode.zPosition = 1 addChild(scoreNode) } /// Add point. /// - Increments the score. /// - Saves to user defaults. /// - If a high score is achieved, then enlarge the scoreNode and update the color. public func addPoint() { score += 1 updateScoreboard() if score > highScore { let defaults = UserDefaults.standard defaults.set(score, forKey: scoreKey) if !showingHighScore { showingHighScore = true scoreNode.run(SKAction.scale(to: 1.5, duration: 0.25)) scoreNode.fontColor = SKColor(red:0.99, green:0.92, blue:0.55, alpha:1.0) } } } /// Reset points. /// - Sets score to zero. /// - Updates score label. /// - Resets color and size to default values. public func resetPoints() { score = 0 updateScoreboard() if showingHighScore { showingHighScore = false scoreNode.run(SKAction.scale(to: 1.0, duration: 0.25)) scoreNode.fontColor = SKColor.white } } /// Updates the score label to show the current score. private func updateScoreboard() { scoreNode.text = "\(score)" } }
Başka bir şey yapmadan önce, Constants.swift
açın ve dosyanın altına aşağıdaki satırı ekleyin - yüksek puanı almak ve sürdürmek için kullanacağız:
let ScoreKey = "RAINCAT_HIGHSCORE"
Kodda, puan tablosuyla ilgili beş değişkenimiz var. İlk değişken, etiketi sunmak için kullandığımız gerçek SKLabelNode
. Sıradaki mevcut puanı tutacak değişkenimiz; sonra en iyi puanı tutan değişken. Son değişken, bize şu anda yüksek puanı gösterip göstermediğimizi söyleyen bir booleandır (bunu, skorbord ölçeğini artırmak ve onu zeminin sarısına renklendirmek için bir SKAction
çalıştırmamız gerekip gerekmediğini belirlemek için kullanırız).
İlk işlev, setup(size:)
, her şeyi ayarlamak için orada. SKLabelNode
daha önce yaptığımız gibi kurduk. SKNode
sınıfı varsayılan olarak herhangi bir boyut özelliğine sahip değildir, bu nedenle scoreNode
etiketimizi konumlandırmak için bir boyut ayarlamanın bir yolunu oluşturmamız gerekir. Ayrıca UserDefaults
mevcut en yüksek puanı alıyoruz. Bu, küçük veri parçalarını kaydetmenin hızlı ve kolay bir yoludur, ancak güvenli değildir. Bu örnekte güvenlik konusunda endişelenmediğimiz için UserDefaults
gayet iyi.
addPoint()
mevcut score
değişkenini artırıyoruz ve kullanıcının yüksek puan alıp almadığını kontrol ediyoruz. Yüksek puanları varsa, o puanı UserDefaults
ve şu anda en iyi puanı gösterip göstermediğimizi kontrol ederiz. Kullanıcı yüksek bir puan elde ettiyse, scoreNode
boyutunu ve rengini canlandırabiliriz.
resetPoints()
fonksiyonunda mevcut puanı 0
olarak ayarlıyoruz. Ardından, yüksek puanı gösterip göstermediğimizi kontrol etmemiz ve gerekirse boyut ve rengi varsayılan değerlere sıfırlamamız gerekir.
Son olarak updateScoreboard
adında küçük bir fonksiyonumuz var. Bu, puanı scoreNode
'ın metnine ayarlamak için dahili bir işlevdir. Bu, hem addPoint()
hem de resetPoints() içinde resetPoints()
.
HUD'yi Bağlamak
HUD'ımızın doğru çalışıp çalışmadığını test etmemiz gerekiyor. GameScene.swift
ve dosyanın en üstündeki foodNode
değişkeninin altına aşağıdaki satırı ekleyin:
private let hudNode = HudNode()
sceneDidLoad()
işlevine, üste yakın bir yere aşağıdaki iki satırı ekleyin:
hudNode.setup(size: size) addChild(hudNode)
Ardından, spawnCat()
işlevinde, kedinin ekrandan düşmesi durumunda noktaları sıfırlayın. Sahneye cat sprite ekledikten sonra aşağıdaki satırı ekleyin:
hudNode.resetPoints()
Ardından, handleCatCollision(contact:)
işlevinde, kediye yağmur çarptığında puanı tekrar sıfırlamamız gerekiyor. Diğer gövde bir RainDropCategory
olduğunda, işlevin sonundaki switch
ifadesine aşağıdaki satırı ekleyin:
hudNode.resetPoints()
Son olarak, kullanıcının puan kazandığını puan tablosuna söylememiz gerekiyor. handleFoodHit(contact:)
içindeki dosyanın sonunda, buraya kadar aşağıdaki satırları bulun:
//TODO increment points print("fed cat")
Ve bunları şununla değiştirin:
hudNode.addPoint()
işte!
HUD'yi çalışırken görmelisiniz. Etrafta dolaşın ve biraz yiyecek toplayın. İlk kez yiyecek topladığınızda, puanın sarardığını ve ölçeğin büyüdüğünü görmelisiniz. Bunun olduğunu gördüğünüzde, kedinin vurulmasına izin verin. Skor sıfırlanırsa, doğru yolda olduğunuzu bileceksiniz!
Sonraki Sahne
Bu doğru, başka bir sahneye geçiyoruz! Aslında tamamlandığında bu uygulamamızın ilk ekranı olacak. Başka bir şey yapmadan önce Constants.swift
açın ve dosyanın altına aşağıdaki satırı ekleyin - yüksek puanı almak ve sürdürmek için kullanacağız:
let ScoreKey = "RAINCAT_HIGHSCORE"
Yeni sahneyi yaratın, onu “Scenes” klasörünün altına yerleştirin ve MenuScene.swift
olarak adlandırın. MenuScene.swift
dosyasına aşağıdaki kodu girin:
import SpriteKit class MenuScene : SKScene { let startButtonTexture = SKTexture(imageNamed: "button_start") let startButtonPressedTexture = SKTexture(imageNamed: "button_start_pressed") let soundButtonTexture = SKTexture(imageNamed: "speaker_on") let soundButtonTextureOff = SKTexture(imageNamed: "speaker_off") let logoSprite = SKSpriteNode(imageNamed: "logo") var startButton : SKSpriteNode! = nil var soundButton : SKSpriteNode! = nil let highScoreNode = SKLabelNode(fontNamed: "PixelDigivolve") var selectedButton : SKSpriteNode? override func sceneDidLoad() { backgroundColor = SKColor(red:0.30, green:0.81, blue:0.89, alpha:1.0) //Set up logo - sprite initialized earlier logoSprite.position = CGPoint(x: size.width / 2, y: size.height / 2 + 100) addChild(logoSprite) //Set up start button startButton = SKSpriteNode(texture: startButtonTexture) startButton.position = CGPoint(x: size.width / 2, y: size.height / 2 - startButton.size.height / 2) addChild(startButton) let edgeMargin : CGFloat = 25 //Set up sound button soundButton = SKSpriteNode(texture: soundButtonTexture) soundButton.position = CGPoint(x: size.width - soundButton.size.width / 2 - edgeMargin, y: soundButton.size.height / 2 + edgeMargin) addChild(soundButton) //Set up high-score node let defaults = UserDefaults.standard let highScore = defaults.integer(forKey: ScoreKey) highScoreNode.text = "\(highScore)" highScoreNode.fontSize = 90 highScoreNode.verticalAlignmentMode = .top highScoreNode.position = CGPoint(x: size.width / 2, y: startButton.position.y - startButton.size.height / 2 - 50) highScoreNode.zPosition = 1 addChild(highScoreNode) } }
Bu sahne nispeten basit olduğu için herhangi bir özel sınıf oluşturmayacağız. Sahnemiz iki butondan oluşacaktır. Bunlar kendi SKSpriteNodes
sınıfları olabilir (ve muhtemelen hak ediyor olabilir), ancak yeterince farklı oldukları için onlar için yeni sınıflar oluşturmamız gerekmeyecek. Bu, kendi oyununuzu oluştururken önemli bir ipucudur: İşler karmaşıklaştığında nerede duracağınızı ve kodu yeniden düzenleyebileceğinizi belirlemeniz gerekir. Bir oyuna üç veya dörtten fazla düğme ekledikten sonra, durup menü düğmesinin kodunu kendi sınıfına yeniden düzenlemenin zamanı gelmiş olabilir.
Yukarıdaki kod özel bir şey yapmıyor; dört sprite'ın pozisyonlarını belirliyor. Ayrıca tüm arka planın doğru değer olması için sahnenin arka plan rengini de ayarlıyoruz. Xcode için HEX dizelerinden renk kodları oluşturmak için güzel bir araç UI Color'dır. Yukarıdaki kod, düğme durumlarımız için dokuları da ayarlıyor. Oyunu başlatma düğmesinin normal bir durumu ve basılı bir durumu vardır, oysa ses düğmesi bir geçiştir. Geçiş işlemini basitleştirmek için, kullanıcının basması üzerine ses düğmesinin alfa değerini değiştireceğiz. Ayrıca yüksek puanlı SKLabelNode
.
MenuScene
oldukça iyi görünüyor. Şimdi uygulama yüklendiğinde sahneyi göstermemiz gerekiyor. GameViewController.swift
ve aşağıdaki satırı bulun:
let sceneNode = GameScene(size: view.frame.size)
Bunu şununla değiştirin:
let sceneNode = MenuScene(size: view.frame.size)
Bu küçük değişiklik, MenuScene
yerine varsayılan olarak GameScene
.
Düğme Durumları
SpriteKit'te düğmeler yanıltıcı olabilir. Çok sayıda üçüncü taraf seçeneği mevcut (hatta kendim bile yaptım), ancak teoride sadece üç dokunma yöntemini bilmeniz gerekiyor:
-
touchesBegan(_ touches: with event:)
-
touchesMoved(_ touches: with event:)
-
touchesEnded(_ touches: with event:)
Şemsiyeyi güncellerken bunu kısaca ele aldık, ancak şimdi aşağıdakileri bilmemiz gerekiyor: hangi düğmeye dokunulduğu, kullanıcının dokunuşunu bırakıp bırakmadığı veya o düğmeyi tıklayıp tıklamadığı ve kullanıcının hala ona dokunup dokunmadığı. İşte burada selectedButton
değişkenimiz devreye giriyor. Bir dokunma başladığında, kullanıcının tıklamaya başladığı düğmeyi o değişkenle yakalayabiliriz. Düğmenin dışına sürüklerlerse, bunu halledebiliriz ve ona uygun dokuyu verebiliriz. Dokunmayı bıraktıklarında, düğmenin içine hala dokunup dokunmadıklarını görebiliriz. Eğer öyleyse, ilgili eylemi buna uygulayabiliriz. MenuScene.swift
altına aşağıdaki satırları ekleyin:
override func touchesBegan(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { if selectedButton != nil { handleStartButtonHover(isHovering: false) handleSoundButtonHover(isHovering: false) } // Check which button was clicked (if any) if startButton.contains(touch.location(in: self)) { selectedButton = startButton handleStartButtonHover(isHovering: true) } else if soundButton.contains(touch.location(in: self)) { selectedButton = soundButton handleSoundButtonHover(isHovering: true) } } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { // Check which button was clicked (if any) if selectedButton == startButton { handleStartButtonHover(isHovering: (startButton.contains(touch.location(in: self)))) } else if selectedButton == soundButton { handleSoundButtonHover(isHovering: (soundButton.contains(touch.location(in: self)))) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { if selectedButton == startButton { // Start button clicked handleStartButtonHover(isHovering: false) if (startButton.contains(touch.location(in: self))) { handleStartButtonClick() } } else if selectedButton == soundButton { // Sound button clicked handleSoundButtonHover(isHovering: false) if (soundButton.contains(touch.location(in: self))) { handleSoundButtonClick() } } } selectedButton = nil } /// Handles start button hover behavior func handleStartButtonHover(isHovering : Bool) { if isHovering { startButton.texture = startButtonPressedTexture } else { startButton.texture = startButtonTexture } } /// Handles sound button hover behavior func handleSoundButtonHover(isHovering : Bool) { if isHovering { soundButton.alpha = 0.5 } else { soundButton.alpha = 1.0 } } /// Stubbed out start button on click method func handleStartButtonClick() { print("start clicked") } /// Stubbed out sound button on click method func handleSoundButtonClick() { print("sound clicked") }
Bu, iki düğmemiz için basit düğme kullanımıdır. touchesBegan(_ touches: with events:)
içinde, şu anda seçili düğmelerimizin olup olmadığını kontrol ederek başlıyoruz. Bunu yaparsak, düğmenin durumunu basılmamış olarak sıfırlamamız gerekir. Daha sonra herhangi bir butona basılıp basılmadığını kontrol etmemiz gerekiyor. Birine basılırsa, düğme için vurgulanan durumu gösterecektir. Ardından, diğer iki yöntemde kullanmak için selectedButton
buton olarak ayarladık.
touchesMoved(_ touches: with events:)
içinde, hangi butona ilk dokunulduğu kontrol edilir. Ardından, mevcut dokunuşun hala selectedButton
sınırları içinde olup olmadığını kontrol ediyoruz ve vurgulanan durumu oradan güncelliyoruz. startButton
'ın vurgulanmış durumu, dokuyu, basılı durumun dokusuna değiştirir; burada soundButton
'ın vurgulanmış durumu, hareketli grafiğin alfa değerini %50'ye ayarlar.
Son olarak touchesEnded(_ touches: with event:)
'da varsa hangi butonun seçili olduğunu ve ardından touches'in hala buton sınırları içinde olup olmadığını tekrar kontrol ediyoruz. Tüm durumlar karşılanırsa, doğru düğme için handleStartButtonClick()
veya handleSoundButtonClick()
.
Harekete Geçme Zamanı
Artık temel düğme davranışına sahip olduğumuza göre, tıklandığında tetiklenecek bir olaya ihtiyacımız var. Uygulanması daha kolay olan düğme startButton
. Tıklandığında sadece GameScene
. MenuScene.swift
işlevindeki handleStartButtonClick()
öğesini aşağıdaki kodla güncelleyin:
func handleStartButtonClick() { let transition = SKTransition.reveal(with: .down, duration: 0.75) let gameScene = GameScene(size: size) gameScene.scaleMode = scaleMode view?.presentScene(gameScene, transition: transition) }
Uygulamayı şimdi çalıştırır ve düğmeye basarsanız oyun başlayacaktır!
Şimdi sessiz geçişini uygulamamız gerekiyor. Zaten bir ses yöneticimiz var, ancak sessize almanın açık mı yoksa kapalı mı olduğunu söyleyebilmemiz gerekiyor. Constants.swift
, sessize alma açıkken devam etmek için bir anahtar eklememiz gerekiyor. Aşağıdaki satırı ekleyin:
let MuteKey = "RAINCAT_MUTED"
Bunu UserDefaults
bir boole değeri kaydetmek için kullanacağız. Artık bu ayarlandığına göre SoundManager.swift
. Sessize almanın açık mı yoksa kapalı mı olduğunu görmek için UserDefaults
kontrol edip ayarlayacağız. Dosyanın en üstünde, trackPosition
değişkeninin altına aşağıdaki satırı ekleyin:
private(set) var isMuted = false
Bu, ana menünün (ve ses çalacak başka herhangi bir şeyin) sese izin verilip verilmediğini belirlemek için kontrol ettiği değişkendir. Bunu false
olarak başlatıyoruz, ancak şimdi kullanıcının ne istediğini görmek için UserDefaults
kontrol etmemiz gerekiyor. init()
işlevini aşağıdakiyle değiştirin:
private override init() { //This is private, so you can only have one Sound Manager ever. trackPosition = Int(arc4random_uniform(UInt32(SoundManager.tracks.count))) let defaults = UserDefaults.standard isMuted = defaults.bool(forKey: MuteKey) }
Artık isMuted
için varsayılan bir değerimiz olduğuna göre, onu değiştirme yeteneğine ihtiyacımız var. SoundManager.swift
altına aşağıdaki kodu ekleyin:
func toggleMute() -> Bool { isMuted = !isMuted let defaults = UserDefaults.standard defaults.set(isMuted, forKey: MuteKey) defaults.synchronize() if isMuted { audioPlayer?.stop() } else { startPlaying() } return isMuted }
Bu yöntem, sessize alınan değişkenimizi değiştirecek ve UserDefaults
güncelleyecektir. Yeni değerin sesi kapatılmazsa, müzik çalmaya başlar; yeni değerin sesi kapatılırsa, oynatma başlamaz. Aksi takdirde, mevcut parçanın çalmasını durduracağız. Bundan sonra startPlaying()
içindeki if
ifadesini düzenlememiz gerekiyor.
Aşağıdaki satırı bulun:
if audioPlayer == nil || audioPlayer?.isPlaying == false {
Ve şununla değiştirin:
if !isMuted && (audioPlayer == nil || audioPlayer?.isPlaying == false) {
Şimdi, sessize alma kapalıysa ve ses çalar ayarlanmadıysa veya mevcut müzik çalar artık çalmıyorsa, bir sonraki parçayı çalacağız.
Buradan, sessiz düğmemizi bitirmek için MenuScene.swift
geri dönebiliriz. handleSoundbuttonClick()
aşağıdaki kodla değiştirin:
func handleSoundButtonClick() { if SoundManager.sharedInstance.toggleMute() { //Is muted soundButton.texture = soundButtonTextureOff } else { //Is not muted soundButton.texture = soundButtonTexture } }
Bu, SoundManager
sesi değiştirir, sonucu kontrol eder ve ardından kullanıcıya sesin kapalı olup olmadığını göstermek için dokuyu uygun şekilde ayarlar. Neredeyse bitirdik! Başlatma sırasında yalnızca düğmenin ilk dokusunu ayarlamamız gerekiyor. sceneDidLoad()
içinde aşağıdaki satırı bulun:
soundButton = SKSpriteNode(texture: soundButtonTexture)
Ve şununla değiştirin:
soundButton = SKSpriteNode(texture: SoundManager.sharedInstance.isMuted ? soundButtonTextureOff : soundButtonTexture)
Yukarıdaki örnek, doğru dokuyu ayarlamak için üçlü bir operatör kullanır.
Artık müzik bağlandığına göre, sessize alma açıkken kedi miyavlamasını devre dışı bırakmak için CatSprite.swift
. hitByRain()
içinde, yürüyüş eylemini kaldırdıktan sonra aşağıdaki if
ifadesini ekleyebiliriz:
if SoundManager.sharedInstance.isMuted { return }
Bu ifade, kullanıcının uygulamayı sessize alıp almadığını döndürür. Bu nedenle, currentRainHits
, maxRainHits
ve miyavlama ses efektlerimizi tamamen görmezden geleceğiz.
Tüm bunlardan sonra, şimdi sessize alma düğmemizi denemenin zamanı geldi. Uygulamayı çalıştırın ve sesleri uygun şekilde çaldığını ve kapattığını doğrulayın. Sesi kapatın, uygulamayı kapatın ve yeniden açın. Sessiz ayarının devam ettiğinden emin olun. Uygulamayı Xcode'dan sessize alıp yeniden çalıştırırsanız, UserDefaults
kaydetmesi için yeterli zaman vermemiş olabileceğinizi unutmayın. Oyunu oynayın ve sessiz olduğunuzda kedinin asla miyavlamadığından emin olun.
Oyundan Çıkmak
Artık ana menü için ilk düğme tipine sahip olduğumuza göre, oyun sahnemize çıkış düğmesini ekleyerek biraz zor işlere girebiliriz. Oyun tarzımızla bazı ilginç etkileşimler ortaya çıkabilir; şu anda şemsiye, kullanıcının dokunduğu veya dokunuşunu hareket ettirdiği yere hareket edecektir. Açıkçası, kullanıcı oyundan çıkmaya çalışırken şemsiyenin çıkış düğmesine hareket etmesi oldukça kötü bir kullanıcı deneyimidir, bu yüzden bunun olmasını engellemeye çalışacağız.
Uyguladığımız çıkış düğmesi, daha önce eklediğimiz oyunu başlat düğmesini taklit edecek ve işlemin çoğu aynı kalacak. Değişiklik, dokunuşları nasıl ele aldığımızda olacak. quit_button
ve quit_button_pressed
varlıklarınızı Assets.xcassets
dosyasına alın ve aşağıdaki kodu HudNode.swift
dosyasına ekleyin:
private var quitButton : SKSpriteNode! private let quitButtonTexture = SKTexture(imageNamed: "quit_button") private let quitButtonPressedTexture = SKTexture(imageNamed: "quit_button_pressed")
Bu, düğme durumları için ayarlayacağımız dokularla birlikte quitButton
referansımızı ele alacaktır. Çıkmaya çalışırken şemsiyeyi yanlışlıkla güncellemediğimizden emin olmak için, HUD'ye (ve oyun sahnesine) şemsiyeyle değil, çık düğmesiyle etkileşimde bulunduğumuzu söyleyen bir değişkene ihtiyacımız var. showingHighScore
boole değişkeninin altına aşağıdaki kodu ekleyin:
private(set) var quitButtonPressed = false
Yine, bu yalnızca HudNode
ayarlayabileceği ancak diğer sınıfların kontrol edebileceği bir değişkendir. Artık değişkenlerimiz ayarlandığına göre, HUD'ye buton ekleyebiliriz. setup(size:)
işlevine aşağıdaki kodu ekleyin:
quitButton = SKSpriteNode(texture: quitButtonTexture) let margin : CGFloat = 15 quitButton.position = CGPoint(x: size.width - quitButton.size.width - margin, y: size.height - quitButton.size.height - margin) quitButton.zPosition = 1000 addChild(quitButton)
Yukarıdaki kod, basılmamış durumumuzun dokusu ile çık düğmesini ayarlayacaktır. Ayrıca konumu sağ üst köşeye ayarlıyoruz ve her zaman üstte çizmeye zorlamak için zPosition
yüksek bir sayıya ayarlıyoruz. Oyunu şimdi çalıştırırsanız GameScene
, ancak henüz tıklanabilir olmayacaktır.
Artık düğme konumlandırıldığına göre, onunla etkileşime geçebilmemiz gerekiyor. Şu anda GameScene
etkileşim kurduğumuz tek yer, umbrellaSprite
ile etkileşimde bulunduğumuz zamandır. Örneğimizde, HUD şemsiyeye göre önceliğe sahip olacak, böylece kullanıcılar çıkmak için şemsiyeyi yoldan çekmek zorunda kalmayacak. GameScene.swift'teki dokunma işlevini taklit etmek için aynı işlevleri HudNode.swift
GameScene.swift
. HudNode.swift
aşağıdaki kodu ekleyin:
func touchBeganAtPoint(point: CGPoint) { let containsPoint = quitButton.contains(point) if quitButtonPressed && !containsPoint { //Cancel the last click quitButtonPressed = false quitButton.texture = quitButtonTexture } else if containsPoint { quitButton.texture = quitButtonPressedTexture quitButtonPressed = true } } func touchMovedToPoint(point: CGPoint) { if quitButtonPressed { if quitButton.contains(point) { quitButton.texture = quitButtonPressedTexture } else { quitButton.texture = quitButtonTexture } } } func touchEndedAtPoint(point: CGPoint) { if quitButton.contains(point) { //TODO tell the gamescene to quit the game } quitButton.texture = quitButtonTexture }
Yukarıdaki kod MenuScene
için oluşturduğumuz koda çok benziyor. Aradaki fark, takip edilecek tek bir düğmenin bulunmasıdır, böylece bu dokunma yöntemleriyle her şeyi halledebiliriz. Ayrıca GameScene
dokunmanın yerini bileceğimiz için, butonumuzun temas noktasını içerip içermediğini kontrol edebiliriz.
GameScene.swift
ve touchesBegan(_ touches with event:)
ve touchesMoved(_ touches: with event:)
yöntemlerini aşağıdaki kodla değiştirin:
override func touchesBegan(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchBeganAtPoint(point: point) if !hudNode.quitButtonPressed { umbrellaNode.setDestination(destination: point) } } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchMovedToPoint(point: point) if !hudNode.quitButtonPressed { umbrellaNode.setDestination(destination: point) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { let touchPoint = touches.first?.location(in: self) if let point = touchPoint { hudNode.touchEndedAtPoint(point: point) } }
Burada, her yöntem her şeyi hemen hemen aynı şekilde ele alır. HUD'a kullanıcının sahneyle etkileşime girdiğini söylüyoruz. Ardından, çık düğmesinin şu anda dokunuşları yakalayıp yakalamadığını kontrol ediyoruz. Değilse, şemsiyeyi hareket ettiririz. Çık düğmesi için yapılan tıklamanın sonunu işlemek için touchesEnded(_ touches: with event:)
işlevini de ekledik, ancak bunu hala umbrellaSprite
için kullanmıyoruz.
Artık bir düğmemiz olduğuna göre, bunun GameScene
etkilemesi için bir yola ihtiyacımız var. HudeNode.swift
en üstüne aşağıdaki satırı ekleyin:
var quitButtonAction : (() -> ())?
Bu, girdisi ve çıktısı olmayan genel bir kapatmadır. Bunu GameScene.swift
dosyasındaki kod ile ayarlayacağız ve HudNode.swift
butona tıkladığımızda onu çağıracağız. Ardından, touchEndedAtPoint(point:)
işlevinde daha önce oluşturduğumuz koddaki TODO
şununla değiştirebiliriz:
if quitButton.contains(point) && quitButtonAction != nil { quitButtonAction!() }
Şimdi, quitButtonAction
kapanışını ayarlarsak, bu noktadan çağrılır.
quitButtonAction
kapanışını ayarlamak için GameScene.swift
geçmemiz gerekiyor. sceneDidLoad()
içinde, HUD kurulumumuzu aşağıdaki kodla değiştirebiliriz:
hudNode.setup(size: size) hudNode.quitButtonAction = { let transition = SKTransition.reveal(with: .up, duration: 0.75) let gameScene = MenuScene(size: self.size) gameScene.scaleMode = self.scaleMode self.view?.presentScene(gameScene, transition: transition) self.hudNode.quitButtonAction = nil } addChild(hudNode)
Uygulamayı çalıştırın, oynat'a basın ve ardından çık'a basın. Ana menüye geri döndüyseniz, çıkış düğmeniz istendiği gibi çalışıyor. Oluşturduğumuz MenuScene
bir geçiş başlattık. Ve bu kapatmayı, çıkış düğmesine tıklandığında çalışacak şekilde HUD
düğümüne ayarladık. Buradaki bir diğer önemli satır, quitButtonAction
nil
olarak ayarladığımız zamandır. Bunun nedeni, bir tutma döngüsünün gerçekleşmesidir. Sahne, HUD'nin sahneye bir referansı tuttuğu HUD'ye bir referans tutuyor. Her iki nesneye de referans olduğundan, çöp toplama zamanı geldiğinde hiçbiri atılmaz. Bu durumda, GameScene
her girip çıktığımızda, bunun başka bir örneği oluşturulacak ve asla yayınlanmayacaktır. Bu, performans için kötüdür ve uygulamanın belleği sonunda tükenecektir. Bundan kaçınmanın birkaç yolu vardır, ancak bizim durumumuzda GameScene referansını GameScene
kaldırabiliriz ve MenuScene
geri döndüğümüzde sahne ve HUD sonlandırılacaktır. Krakendev, referans türleri ve bu döngülerden nasıl kaçınılacağı konusunda daha derin bir açıklamaya sahiptir.
Şimdi GameViewController.swift
ve aşağıdaki üç kod satırını kaldırın veya yorumlayın:
view.showsPhysics = true view.showsFPS = true view.showsNodeCount = true
Hata ayıklama verileri ortadan kalktığında oyun gerçekten iyi görünüyor! Tebrikler: Şu anda beta aşamasındayız! Bugünden itibaren GitHub'daki son kodu inceleyin.
Son düşünceler
Bu, üç bölümlük bir öğreticinin son dersidir ve buraya kadar geldiyseniz, oyununuz üzerinde çok çalıştınız. Bu eğitimde, içinde kesinlikle hiçbir şey olmayan bir sahneden tamamlanmış bir oyuna geçtiniz. Tebrikler! Birinci derste zemini, yağmur damlalarını, arka planı ve şemsiye spritelarını ekledik. Ayrıca fizikle de oynadık ve yağmur damlalarımızın birikmemesini sağladık. Çarpışma tespiti ile başladık ve hafızanın tükenmemesi için düğümleri ayırma üzerinde çalıştık. Şemsiyenin kullanıcının ekranda dokunduğu yere doğru hareket etmesine izin vererek bir miktar kullanıcı etkileşimi de ekledik.
İkinci derste, her biri için özel yumurtlama yöntemleriyle birlikte kedi ve mamayı ekledik. Kedi ve yemek spritelarına izin vermek için çarpışma algılamamızı güncelledik. Ayrıca kedinin hareketi üzerinde çalıştık. Kedi bir amaç edindi: Mevcut her mamayı yiyin. Kedi için basit bir animasyon ekledik ve kedi ile yağmur arasında özel etkileşimler ekledik. Son olarak, eksiksiz bir oyun gibi hissettirmek için ses efektleri ve müzik ekledik.
Bu son derste, puan etiketimizin yanı sıra çıkış butonumuzu tutmak için bir teke tek ekran oluşturduk. Düğümler arasında eylemleri ele aldık ve kullanıcının HUD düğümünden bir geri arama ile çıkmasını sağladık. Ayrıca kullanıcının başlatabileceği ve çık düğmesine tıkladıktan sonra geri dönebileceği başka bir sahne ekledik. Oyuna başlama ve oyundaki sesi kontrol etme sürecini ele aldık.
Buradan Nereye Gidilir
Bu noktaya gelmek için çok zaman harcadık, ancak bu oyunda hala yapılabilecek çok iş var. RainCat geliştirmeye devam ediyor ve App Store'da mevcut. Aşağıda isteklerin ve eklenmesi gerekenlerin bir listesi bulunmaktadır. Bazı öğeler eklendi, diğerleri ise hala beklemede:
- Simgeler ve bir açılış ekranı ekleyin.
- Ana menüyü sonlandırın (eğitim için basitleştirilmiştir).
- Düzensiz yağmur damlaları ve birden fazla yiyecek yumurtlaması dahil olmak üzere hataları düzeltin.
- Kodu yeniden düzenleyin ve optimize edin.
- Skora göre oyunun renk paletini değiştirin.
- Skora göre zorluğu güncelleyin.
- Yemek tam üstündeyken kediyi canlandırın.
- Game Center'ı entegre edin.
- Kredi verin (müzik parçaları için uygun kredi dahil).
Bu değişiklikler gelecekte yapılacağı için GitHub'da takipte kalın. Kodla ilgili herhangi bir sorunuz varsa, lütfen bize [email protected] adresinden bir satır bırakın, tartışalım. Belirli konular yeterince dikkat çekerse, konuyu tartışan başka bir makale yazabiliriz.
Teşekkür ederim!
Oyunu yaratma ve onunla birlikte gelen makaleleri geliştirme sürecinde yardımcı olan herkese teşekkür etmek istiyorum.
- Cathryn Rowe İlk sanat, tasarım ve düzenleme için ve makaleleri Garajımızda yayınlamak için.
- Morgan Wheaton Son menü tasarımı ve renk paletleri için (bu özellikleri gerçekten uyguladığımda harika görünecek - bizi izlemeye devam edin).
- Nikki Clark Makalelerdeki harika başlıklar ve ayırıcılar ve makaleleri düzenleme konusunda yardım için.
- Laura Levisay Makalelerdeki tüm harika GIF'ler ve manevi destek için bana sevimli kedi GIF'leri gönderdiğin için.
- Tom Hudson Makaleleri düzenleme konusunda yardım için ve kim olmasaydı bu seri olmazdı.
- Lani DeGuire Bir ton iş olan makaleleri düzenleme konusunda yardım için.
- Jeff Moon Üçüncü dersi ve masa tenisini düzenlemede yardım için. Bir sürü pinpon.
- Tom Nelson Öğreticinin olması gerektiği gibi çalıştığından emin olmaya yardımcı olduğu için.
Cidden, bu makale için her şeyi hazırlamak ve mağazaya bırakmak bir ton insan aldı.
Bu cümleyi okuyan herkese de teşekkür ederim.